diff --git a/README.md b/README.md index 3e7b898e042d9958a8dd1bf90f305ac77ef4b9fd..cc144f0a85be62316039ae3d6bd2602c9c57c8ec 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,48 @@ # Paddle Quantum (量桨) +- [特色](#特色) +- [安装步骤](#安装步骤) + - [安装 PaddlePaddle](#安装-paddlepaddle) + - [下载 Paddle Quantum 并安装](#下载-paddle-quantum-并安装) + - [或使用 requirements.txt 安装依赖包](#或使用-requirementstxt-安装依赖包) + - [使用 openfermion 读取 xyz 描述文件](#使用-openfermion-读取-xyz-描述文件) + - [运行](#运行) +- [入门与开发](#入门与开发) + - [教程入门](#教程入门) + - [案例入门](#案例入门) + - [API 文档](#api-文档) + - [开发](#开发) +- [交流与反馈](#交流与反馈) +- [使用 Paddle Quantum 的工作](#使用-paddle-quantum-的工作) +- [FAQ](#faq) +- [Copyright and License](#copyright-and-license) +- [References](#references) + Paddle Quantum(量桨)是基于百度飞桨开发的量子机器学习工具集,支持量子神经网络的搭建与训练,提供易用的量子机器学习开发套件与量子优化、量子化学等前沿量子应用工具集,使得百度飞桨也因此成为国内首个目前也是唯一一个支持量子机器学习的深度学习框架。 ![](https://release-data.cdn.bcebos.com/Paddle%20Quantum.png) 量桨建立起了人工智能与量子计算的桥梁,不但可以快速实现量子神经网络的搭建与训练,还提供易用的量子机器学习开发套件与量子优化、量子化学等前沿量子应用工具集,并提供多项自研量子机器学习应用。通过百度飞桨深度学习平台赋能量子计算,量桨为领域内的科研人员以及开发者便捷地开发量子人工智能的应用提供了强有力的支撑,同时也为广大量子计算爱好者提供了一条可行的学习途径。 - - ## 特色 -- 易用性:提供简洁的神经网络搭建与丰富的量子机器学习案例。 -- 通用性与拓展性:支持常用量子电路模型,提供多项优化工具。 -- 特色工具集:提供量子优化、量子化学等前沿量子应用工具集,自研多项量子机器学习应用。 - - +- 易用性 + - 高效搭建量子神经网络 + - 多种量子神经网络模板 + - 丰富量子算法教程(10+用例) +- 可拓展性 + - 支持通用量子电路模型 + - 高性能模拟器支持20多个量子比特的模拟运算 + - 提供多种优化工具和 GPU 加速 +- 特色工具集 + - 提供组合优化和量子化学等前沿领域的计算工具箱 + - 自研多种量子机器学习算法 ## 安装步骤 -### Install PaddlePaddle -请参考 [PaddlePaddle](https://www.paddlepaddle.org.cn/documentation/docs/zh/beginners_guide/index_cn.html) 安装配置页面。此项目需求 PaddlePaddle 1.8.0 或更高版本。 - +### 安装 PaddlePaddle +请参考 [PaddlePaddle](https://www.paddlepaddle.org.cn/install/quick) 安装配置页面。此项目需求 PaddlePaddle 1.8.3 或更高版本。 ### 下载 Paddle Quantum 并安装 @@ -34,30 +55,33 @@ cd quantum pip install -e . ``` - - ### 或使用 requirements.txt 安装依赖包 ```bash python -m pip install --upgrade -r requirements.txt ``` -### 使用 openfermion 读取xyz 描述文件 (仅可在linux下安装使用) -VQE中调用 openfermion 读取分子xyz文件并计算,因此需要安装 openfermion 和 openfermionpyscf。 +### 使用 openfermion 读取 xyz 描述文件 + +> 仅在 macOS 和 linux 下可以使用 openfermion 读取 xyz 描述文件。 + +VQE中调用 openfermion 读取分子 xyz 文件并计算,因此需要安装 openfermion 和 openfermionpyscf。 + ```bash pip install openfermion pip install openfermionpyscf ``` - ### 运行 +现在,可以试着运行一段程序来验证量桨是否已安装成功。这里我们运行量桨提供的量子近似优化算法 (QAOA) 的例子。 + ```bash cd paddle_quantum/QAOA/example python main.py ``` - +> 关于 QAOA 的介绍可以参考我们的 [QAOA 教程](./tutorial/QAOA)。 ## 入门与开发 @@ -65,65 +89,93 @@ python main.py 量子计算是由量子力学与计算理论交叉而成的全新计算模型,具有强大的信息处理优势和广阔的应用前景,被视作未来计算技术的心脏。量子计算的相关介绍与入门知识可以参考 [1-3]。 -量子机器学习是一门结合量子计算与机器学习的交叉学科,一方面利用量子计算的信息处理优势促进人工智能的发展,另一方面也利用现有的人工智能的技术突破量子计算的研发瓶颈。关于量子机器学习的入门资料可以参考 [4-6]。Paddle Quantum(量桨)建立起了人工智能与量子计算的桥梁,为量子机器学习领域的研发提供强有力的支撑,也提供了丰富的案例供开发者学习。 +量子机器学习是一门结合量子计算与机器学习的交叉学科,一方面利用量子计算的信息处理优势促进人工智能的发展,另一方面也利用现有的人工智能的技术突破量子计算的研发瓶颈。关于量子机器学习的入门资料可以参考 [4-6]。 +这里,我们提供了一份[**入门手册**](./introduction)方便用户快速上手 Paddle Quantum。目前支持 PDF 阅读和运行 Jupyter Notebook 两种方式。内容上,该手册包括以下几个方面: +- Paddle Quantum 的详细安装教程 +- 量子计算的基础知识介绍 +- Paddle Quantum 的使用介绍 +- PaddlePaddle 飞桨优化器使用教程 +- 具体的量子机器学习案例—VQE ### 案例入门 -特别的,我们提供了涵盖量子优化、量子化学、量子机器学习等多个领域的案例供大家学习。比如: - -- 量子近似优化(QAOA),完成安装步骤后打开 tutorial\QAOA.ipynb 即可进行研究学习。 +Paddle Quantum(量桨)建立起了人工智能与量子计算的桥梁,为量子机器学习领域的研发提供强有力的支撑,也提供了丰富的案例供开发者学习。 -```bash -cd tutorial -jupyter notebook QAOA.ipynb -``` +在这里,我们提供了涵盖量子优化、量子化学、量子机器学习等多个领域的案例供大家学习。与[入门手册](./introduction)类似,每个教程目前支持 PDF 阅读和运行 Jupyter Notebook 两种方式。我们推荐用户下载 Notebook 后,本地运行进行实践。 -- 量子特征求解器(VQE),完成安装步骤后打开 tutorial\VQE.ipynb 即可进行研究学习。 +- [量子近似优化算法 (QAOA)](./tutorial/QAOA) +- [变分量子特征求解器 (VQE)](./tutorial/VQE) +- [量子神经网络的贫瘠高原效应 (Barren Plateaus)](./tutorial/Barren) +- [量子分类器 (Quantum Classifier)](./tutorial/Q-Classifier) +- [量子变分自编码器 (Quantum Autoencoder)](./tutorial/Q-Autoencoder) +- [量子生成对抗网络 (Quantum GAN)](./tutorial/Q-GAN) +- [子空间搜索 - 量子变分特征求解器 (SSVQE)](./tutorial/SSVQE) +- [变分量子态对角化算法 (VQSD)](./tutorial/VQSD) +- [吉布斯态的制备 (Gibbs State Preparation)](./tutorial/Gibbs) +- [变分量子奇异值分解 (VQSVD)](./tutorial/VQSVD) -``` -cd tutorial -jupyter notebook VQE.ipynb -``` +此外,Paddle Quantum 也支持在 GPU 上进行量子机器学习的训练,具体的方法请参考案例:[在 GPU 上使用 Paddle Quantum](./tutorial/GPU)。 +### API 文档 +我们为 Paddle Quantum 提供了独立的 [API 文档页面](https://paddle-quantum.readthedocs.io/zh_CN/latest/),包含了供用户使用的所有函数和类的详细说明与用法。 ### 开发 -Paddle Quantum 使用 setuptools 的develop 模式进行安装,相关代码修改可以直接进入`paddle_quantum` 文件夹进行修改。python 文件携带了自说明注释。 +Paddle Quantum 使用 setuptools 的 develop 模式进行安装,相关代码修改可以直接进入`paddle_quantum` 文件夹进行修改。python 文件携带了自说明注释。 +## 交流与反馈 +- 我们非常欢迎您通过 [Github Issues](https://github.com/PaddlePaddle/Quantum/issues) 来提交问题、报告与建议。 -## 交流与反馈 +- 技术交流QQ群:1076223166 -- 我们非常欢迎您欢迎您通过[Github Issues](https://github.com/PaddlePaddle/Quantum/issues)来提交问题、报告与建议。 +## 使用 Paddle Quantum 的工作 -- QQ技术交流群: 1076223166 +我们非常欢迎开发者使用 Paddle Quantum 进行量子机器学习的研发,如果您的工作有使用 Paddle Quantum,也非常欢迎联系我们。目前使用 Paddle Quantum 的代表性工作包括了吉布斯态的制备和变分量子奇异值分解: -## 使用Paddle Quantum的工作 +[1] Wang, Y., Li, G. & Wang, X. Variational quantum Gibbs state preparation with a truncated Taylor series. arXiv:2005.08797 (2020). [[pdf](https://arxiv.org/pdf/2005.08797.pdf)] -我们非常欢迎开发者使用Paddle Quantum进行量子机器学习的研发,如果您的工作有使用Paddle Quantum,也非常欢迎联系我们。目前使用 Paddle Quantum 的代表性工作关于 Gibbs 态制备如下: +[2] Wang, X., Song, Z. & Wang, Y. Variational Quantum Singular Value Decomposition. arXiv:2006.02336 (2020). [[pdf](https://arxiv.org/pdf/2006.02336.pdf)] -[1] Youle Wang, Guangxi Li, and Xin Wang. 2020. Variational quantum Gibbs state preparation with a truncated Taylor series. arXiv2005.08797. [[pdf](https://arxiv.org/pdf/2005.08797.pdf)] +## FAQ +1. 问:**研究量子机器学习有什么意义?它有哪些应用场景?** -## Copyright and License + 答:量子机器学习是将量子计算与机器学习相结合的一门学科,它一方面可以利用现有人工智能技术突破量子计算的研发瓶颈,另一方面也能利用量子计算的信息处理优势促进传统人工智能的发展。量子机器学习不仅适用于量子化学模拟(如[变分量子特征求解器 (VQE)](./tutorial/VQE))等量子问题,也可以用来解决一些经典问题(如[量子近似优化算法 (QAOA)](./tutorial/QAOA))。 + +2. 问:**想做量子机器学习,但对量子计算不是很了解,该如何入门?** -Paddle Quantum 使用 [Apache-2.0 license](LICENSE)许可证。 + 答:Nielsen 和 Chuang 所著的《量子计算与量子信息》是量子计算领域公认的经典入门教材。建议读者首先学习这本书的第一、二、四章,介绍了量子计算中的基本概念、数学和物理基础、以及量子电路模型。读者也可以阅读量桨的[入门手册](./introduction),其中包含了对量子计算的简单介绍,并有互动性的例子供读者尝试。对量子计算有了大致了解后,读者可以尝试学习量桨提供的一些前沿[量子机器学习案例](./tutorial)。 +3. 问:**现阶段没有规模化的量子硬件,怎么开发量子应用?** + + 答:使用量桨,用户可以方便地在经典计算机上模拟量子算法,进行量子应用的开发与验证,为未来使用规模化的量子硬件做技术积累。 + +4. 问:**量桨有哪些优势?** + + 答:量桨是基于百度飞桨开发的量子机器学习工具集。飞桨作为国内首个开源开放的产业级深度学习平台,技术领先且功能完备。拥有飞桨的技术支持,特别是其强大的动态图机制,量桨可以方便地进行机器学习的优化以及 GPU 的加速。同时,基于百度量子计算研究所研发的高性能量子模拟器,量桨在个人笔记本电脑上也能支持20多个量子比特的运算。另外,量桨还有丰富的[量子机器学习案例](./tutorial)供大家参考和学习。 + +5. 问:**非常想试用量桨,该怎么入门呢?** + + 答:建议新用户首先阅读量桨的[入门手册](./introduction),它包含量桨详细的安装步骤以及入门教程。另外,量桨提供了丰富的[量子机器学习案例](./tutorial),以 Jupyter Notebook 和 PDF 的方式呈现,方便用户学习和实践。如在学习和使用过程中遇到任何问题,欢迎用户通过 [Github Issues](https://github.com/PaddlePaddle/Quantum/issues) 以及技术交流QQ群(1076223166)与我们交流。 + +## Copyright and License +Paddle Quantum 使用 [Apache-2.0 license](LICENSE) 许可证。 ## References -[1] [量子计算 - 百度百科](https://baike.baidu.com/item/量子计算/11035661?fr=aladdin) +[1] [量子计算 - 百度百科](https://baike.baidu.com/item/%E9%87%8F%E5%AD%90%E8%AE%A1%E7%AE%97/11035661) -[2] Michael A Nielsen and Isaac L Chuang. 2010. Quantum computation and quantum information. Cambridge university press. +[2] Nielsen, M. A. & Chuang, I. L. Quantum computation and quantum information. (Cambridge university press, 2010). -[3] Phillip Kaye, Raymond Laflamme, and Michele Mosca. 2007. An Introduction to Quantum Computing. +[3] Phillip Kaye, Laflamme, R. & Mosca, M. An Introduction to Quantum Computing. (2007). -[4] Jacob Biamonte, Peter Wittek, Nicola Pancotti, Patrick Rebentrost, Nathan Wiebe, and Seth Lloyd. 2017. Quantum machine learning. Nature 549, 7671, 195–202. [[pdf](https://arxiv.org/pdf/1611.09347)] +[4] [Biamonte, J. et al. Quantum machine learning. Nature 549, 195–202 (2017).](https://www.nature.com/articles/nature23474) -[5] Maria Schuld, Ilya Sinayskiy, and Francesco Petruccione. 2015. An introduction to quantum machine learning. Contemp. Phys. 56, 2, 172–185. [[pdf](https://arxiv.org/pdf/1409.3097)] +[5] [Schuld, M., Sinayskiy, I. & Petruccione, F. An introduction to quantum machine learning. Contemp. Phys. 56, 172–185 (2015).](https://www.tandfonline.com/doi/abs/10.1080/00107514.2014.964942) -[6] Marcello Benedetti, Erika Lloyd, Stefan Sack, and Mattia Fiorentini. 2019. Parameterized quantum circuits as machine learning models. Quantum Sci. Technol. 4, 4, 043001. [[pdf](https://arxiv.org/pdf/1906.07682)] \ No newline at end of file +[6] [Benedetti, M., Lloyd, E., Sack, S. & Fiorentini, M. Parameterized quantum circuits as machine learning models. Quantum Sci. Technol. 4, 043001 (2019).](https://iopscience.iop.org/article/10.1088/2058-9565/ab4eb5) \ No newline at end of file diff --git a/Simulator/main.py b/Simulator/main.py new file mode 100644 index 0000000000000000000000000000000000000000..29396ea168eca3be050f2b09b4764aeb980730ad --- /dev/null +++ b/Simulator/main.py @@ -0,0 +1,750 @@ +""" +This simulator uses statevector(Tensor) to simulate quantum behaviors. +Basically, the core of the algorithm is tensor contraction with one-way calculation that each gate is +contracted to the init vector when imported by the program. All the states including the gate and init +are converted to TENSOR and the calculating is also around tensor. + +:DEBUG INFO: +Sim2 +-Sim2Main.py: this file, main entry of sim2 +-InitProcess.py: Initial the state. +-StateTransfer.py: Decide the gate matrix by gate name and real state process. +-TransferProcess.py: Real transfer state process +-MeasureProcess.py: Measure process +Ancilla +-Random Circuit: Generate random circuit by requiring qubits and circuit depth +-DEFINE_GATE: Gate matrix. +Two measure types are provided: Meas_MED = MEAS_METHOD.PROB and Meas_MED = MEAS_METHOD.SINGLE. PROB is the sample with +probability and SINGLE is by the state collapse method. Former is significant faster than the later. +""" + +import numpy as np +import paddle +import paddle.fluid +import gc +from collections import Counter +import copy +from interval import Interval +from enum import Enum +import random + + +### InitPorcess ### +def init_state_10(n): + """ + Generate state with n qubits + :param n: number of qubits + :return: tensor of state + """ + re1 = paddle.fluid.layers.ones([1], 'float64') + re0 = paddle.fluid.layers.zeros([2 ** n - 1], 'float64') + re = paddle.fluid.layers.concat([re1, re0]) + del re1, re0 + gc.collect() # free the intermediate big data immediately + im = paddle.fluid.layers.zeros([2 ** n], 'float64') + state = paddle.fluid.ComplexVariable(re, im) + del re, im + gc.collect() # free the intermediate big data immediately + # print(state.numpy()) + + return state + + +def init_state_gen(n, i = 0): + """ + Generate state with n qubits + :param n: number of qubits + :param i: the ith vector in computational basis + :return: tensor of state + """ + assert 0 <= i < 2**n, 'Invalid index' + + if n == 1: + re1 = paddle.fluid.layers.ones([1], 'float64') + re0 = paddle.fluid.layers.zeros([2 ** n - 1], 'float64') + + if i == 0: + re = paddle.fluid.layers.concat([re1, re0]) + else: + re = paddle.fluid.layers.concat([re0, re1]) + im = paddle.fluid.layers.zeros([2 ** n], 'float64') + state = paddle.fluid.ComplexVariable(re, im) + else: + if i == 0: + re1 = paddle.fluid.layers.ones([1], 'float64') + re0 = paddle.fluid.layers.zeros([2 ** n - 1], 'float64') + re = paddle.fluid.layers.concat([re1, re0]) + elif i == 2 ** n - 1: + re1 = paddle.fluid.layers.ones([1], 'float64') + re0 = paddle.fluid.layers.zeros([2 ** n - 1], 'float64') + re = paddle.fluid.layers.concat([re0, re1]) + else: + re1 = paddle.fluid.layers.ones([1], 'float64') + re0 = paddle.fluid.layers.zeros([i], 'float64') + re00 = paddle.fluid.layers.zeros([2 ** n - i - 1], 'float64') + re = paddle.fluid.layers.concat([re0, re1, re00]) + + del re1, re0 + gc.collect() # free the intermediate big data immediately + im = paddle.fluid.layers.zeros([2 ** n], 'float64') + state = paddle.fluid.ComplexVariable(re, im) + del re, im + gc.collect() # free the intermediate big data immediately + return state + + + +### DEFINE_GATE ### +def x_gate_matrix(): + """ + Pauli x + :return: + """ + return np.array([[0, 1], + [1, 0]], dtype=complex) + + +def y_gate_matrix(): + """ + Pauli y + :return: + """ + return np.array([[0, -1j], + [1j, 0]], dtype=complex) + + +def z_gate_matrix(): + """ + Pauli y + :return: + """ + return np.array([[1, 0], + [0, -1]], dtype=complex) + + +def h_gate_matrix(): + """ + Hgate + :return: + """ + isqrt_2 = 1.0 / np.sqrt(2.0) + return np.array([[isqrt_2, isqrt_2], + [isqrt_2, -isqrt_2]], dtype=complex) + + +def u_gate_matrix(params): + """ + U3 + :param params: + :return: + """ + theta, phi, lam = params + + if (type(theta) is paddle.fluid.core_avx.VarBase and + type(phi) is paddle.fluid.core_avx.VarBase and + type(lam) is paddle.fluid.core_avx.VarBase): + re_a = paddle.fluid.layers.cos(theta / 2) + re_b = - paddle.fluid.layers.cos(lam) * paddle.fluid.layers.sin(theta / 2) + re_c = paddle.fluid.layers.cos(phi) * paddle.fluid.layers.sin(theta / 2) + re_d = paddle.fluid.layers.cos(phi + lam) * paddle.fluid.layers.cos(theta / 2) + im_a = paddle.fluid.layers.zeros([1], 'float64') + im_b = - paddle.fluid.layers.sin(lam) * paddle.fluid.layers.sin(theta / 2) + im_c = paddle.fluid.layers.sin(phi) * paddle.fluid.layers.sin(theta / 2) + im_d = paddle.fluid.layers.sin(phi + lam) * paddle.fluid.layers.cos(theta / 2) + re = paddle.fluid.layers.reshape(paddle.fluid.layers.concat([re_a, re_b, re_c, re_d]), [2, 2]) + im = paddle.fluid.layers.reshape(paddle.fluid.layers.concat([im_a, im_b, im_c, im_d]), [2, 2]) + return paddle.fluid.framework.ComplexVariable(re, im) + elif (type(theta) is float and + type(phi) is float and + type(lam) is float): + return np.array([[np.cos(theta / 2), + -np.exp(1j * lam) * np.sin(theta / 2)], + [np.exp(1j * phi) * np.sin(theta / 2), + np.exp(1j * phi + 1j * lam) * np.cos(theta / 2)]]) + else: + assert False + + +# compare the paddle and np version, they should be equal +# a = u_gate_matrix([1.0, 2.0, 3.0]) +# print(a) +# with paddle.fluid.dygraph.guard(): +# a = u_gate_matrix([paddle.fluid.dygraph.to_variable(np.array([1.0])), +# paddle.fluid.dygraph.to_variable(np.array([2.0])), +# paddle.fluid.dygraph.to_variable(np.array([3.0]))]) +# print(a.numpy()) + + +def cx_gate_matrix(): + """ + Control Not + :return: + """ + return np.array([[1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 0, 1], + [0, 0, 1, 0]], dtype=complex).reshape(2, 2, 2, 2) + + +### PaddleE ### +def normalize_axis(axis, ndim): + if axis < 0: + axis += ndim + + if axis >= ndim or axis < 0: + raise ValueError("Invalid axis index %d for ndim=%d" % (axis, ndim)) + + return axis + + +def _operator_index(a): + return a.__index__() + + +def _normalize_axis_tuple(axis, ndim, argname=None, allow_duplicate=False): + # Optimization to speed-up the most common cases. + if type(axis) not in (tuple, list): + try: + axis = [_operator_index(axis)] + except TypeError: + pass + # Going via an iterator directly is slower than via list comprehension. + axis = tuple([normalize_axis(ax, ndim) for ax in axis]) + if not allow_duplicate and len(set(axis)) != len(axis): + if argname: + raise ValueError('repeated axis in `{}` argument'.format(argname)) + else: + raise ValueError('repeated axis') + return axis + + +def moveaxis(m, source, destination): + """ + extend paddle + :param m: + :param source: + :param destination: + :return: + """ + source = _normalize_axis_tuple(source, len(m.shape), 'source') + destination = _normalize_axis_tuple(destination, len(m.shape), 'destination') + if len(source) != len(destination): + raise ValueError('`source` and `destination` arguments must have ' + 'the same number of elements') + + order = [n for n in range(len(m.shape)) if n not in source] + + for dest, src in sorted(zip(destination, source)): + order.insert(dest, src) + + result = paddle.fluid.layers.transpose(m, order) + return result + + +def complex_moveaxis(m, source, destination): + """ + extend paddle + :param m: + :param source: + :param destination: + :return: + """ + source = _normalize_axis_tuple(source, len(m.shape), 'source') + destination = _normalize_axis_tuple(destination, len(m.shape), 'destination') + if len(source) != len(destination): + raise ValueError('`source` and `destination` arguments must have ' + 'the same number of elements') + + order = [n for n in range(len(m.shape)) if n not in source] + + for dest, src in sorted(zip(destination, source)): + order.insert(dest, src) + + result = paddle.complex.transpose(m, order) + return result + + +def complex_abs(m): + # check1 = np.abs(m.numpy()) + + re = paddle.fluid.layers.elementwise_mul(m.real, m.real) + im = paddle.fluid.layers.elementwise_mul(m.imag, m.imag) + m = paddle.fluid.layers.elementwise_add(re, im) + m = paddle.fluid.layers.sqrt(m) # m = paddle.fluid.layers.elementwise_pow(m, paddle.fluid.layers.ones_like(m) * 0.5) + + # check2 = m.numpy() + # assert (check1 == check2).all() + + return m + + +### TransferProcess ### +def transfer_state(state, gate_matrix, bits): + """ + Transfer to the next state + :param state: + :param gate_matrix: + :param bits: + :return: + """ + + assert type(gate_matrix) is np.ndarray or type(gate_matrix) is paddle.fluid.framework.ComplexVariable + + assert type(state) is paddle.fluid.ComplexVariable and len(state.shape) == 1 + # calc source_pos target_pos + n = int(np.log2(state.shape[0])) + source_pos = copy.deepcopy(bits) # copy bits, it should NOT change the order of bits + # source_pos = [n - 1 - idex for idex in source_pos] # qubit index + # source_pos = list(reversed(source_pos)) # reverse qubit index + target_pos = list(range(len(bits))) + + # ### check + # state_check = transfer_state(paddle.complex.reshape(state, [2] * n), gate_matrix, bits) + # state_check = paddle.complex.reshape(state_check, [2 ** n]) + + # compressed moveaxis + # compress the continuous dim before moveaxis + # e.g. single operand: before moveaxis 2*2*[2]*2*2 -compress-> 4*[2]*4, after moveaxis [2]*2*2*2*2 -compress-> [2]*4*4 + # double operands: before moveaxis 2*2*[2]*2*2*[2]*2*2 -compress-> 4*[2]*4*[2]*4, after moveaxis [2]*[2]*2*2*2*2*2*2 -compress-> [2]*[2]*4*4*4 + # the peak rank is 5 when the number of operands is 2 + assert len(source_pos) == 1 or len(source_pos) == 2 + compressed_shape_before_moveaxis = [1] + compressed_source_pos = [-1] * len(source_pos) + for i in range(n): + if i in source_pos: + compressed_source_pos[source_pos.index(i)] = len(compressed_shape_before_moveaxis) + compressed_shape_before_moveaxis.append(2) + compressed_shape_before_moveaxis.append(1) + else: + compressed_shape_before_moveaxis[-1] = compressed_shape_before_moveaxis[-1] * 2 + # print([2] * n) + # print(source_pos) + # print('->') + # print(compressed_shape) + # print(compressed_source_pos) # always [1], [1, 3], or [3, 1] + state = paddle.complex.reshape(state, compressed_shape_before_moveaxis) + state = complex_moveaxis(state, compressed_source_pos, target_pos) + compressed_shape_after_moveaxis = state.shape + + # reshape + state_new_shape = [2 ** len(bits), 2 ** (n - len(bits))] + state = paddle.complex.reshape(state, state_new_shape) + + # gate_matrix + if type(gate_matrix) is np.ndarray: + gate_new_shape = [2 ** (len(gate_matrix.shape) - len(bits)), 2 ** len(bits)] + gate_matrix = gate_matrix.reshape(gate_new_shape) + gate_matrix = paddle.fluid.dygraph.to_variable(gate_matrix) + elif type(gate_matrix) is paddle.fluid.framework.ComplexVariable: + pass + else: + assert False + + # matmul + state = paddle.complex.matmul(gate_matrix, state) + + # restore compressed moveaxis reshape + state = paddle.complex.reshape(state, compressed_shape_after_moveaxis) + state = complex_moveaxis(state, target_pos, compressed_source_pos) + state = paddle.complex.reshape(state, [2 ** n]) + + # ### check + # assert (np.all(state.numpy() == state_check.numpy())) + + return state + + +### StateTranfer ### +def StateTranfer(state, gate_name, bits, params=None): + """ + To transfer state by only gate name and bits + :param state: the last step state, can be init vector or the last step vector. + :param gate_name:x,y,z,h,CNOT + :param bits: the gate working on the bits. + :param params: params for u gate. + :return: the updated state + """ + if gate_name == 'h': + # print('----------', gate_name, bits, '----------') + gate_matrix = h_gate_matrix() + elif gate_name == 'x': + # print('----------', gate_name, bits, '----------') + gate_matrix = x_gate_matrix() + elif gate_name == 'y': + # print('----------', gate_name, bits, '----------') + gate_matrix = y_gate_matrix() + elif gate_name == 'z': + # print('----------', gate_name, bits, '----------') + gate_matrix = z_gate_matrix() + elif gate_name == 'CNOT': + # print('----------', gate_name, bits, '----------') + gate_matrix = cx_gate_matrix() + elif gate_name == 'u': + # print('----------', gate_name, bits, '----------') + gate_matrix = u_gate_matrix(params) + else: + raise Exception("Gate name error") + + state = transfer_state(state, gate_matrix, bits) + return state + + +### MeasureProcess ### +class MEAS_METHOD(Enum): + """ + To control the measure method + """ + SINGLE = 1 + PROB = 2 + + +Meas_MED = MEAS_METHOD.PROB + + +def oct_to_bin_str(oct_number, n): + """ + Oct to bin by real order + :param oct_number: + :param n: + :return: + """ + bin_string = bin(oct_number)[2:].zfill(n) + # return (''.join(reversed(bin_string))) + return bin_string + + +def measure_single(state, bit): + """ + Method one qubit one time + :param state: + :param bit: + :return: + """ + n = len(state.shape) + axis = list(range(n)) + axis.remove(n - 1 - bit) + probs = np.sum(np.abs(state) ** 2, axis=tuple(axis)) + rnd = np.random.rand() + + # measure single bit + if rnd < probs[0]: + out = 0 + prob = probs[0] + else: + out = 1 + prob = probs[1] + + # collapse single bit + if out == 0: + matrix = np.array([[1.0 / np.sqrt(prob), 0.0], + [0.0, 0.0]], complex) + else: + matrix = np.array([[0.0, 0.0], + [0.0, 1.0 / np.sqrt(prob)]], complex) + state = transfer_state(state, matrix, [bit]) + + return out, state + + +def measure_all(state): + """ + Method all by single qubits + :param state: + :return: + """ + n = len(state.shape) + outs = '' + for i in range(n): + out, state = measure_single(state, i) # measure qubit0 will collapse it + outs = str(out) + outs # from low to high position + return outs + + +def measure_by_single_accumulation(state, shots): + """ + Method by accumulation, one shot one time + :param state: + :param shots: + :return: + """ + print("Measure method Single Accu") + result = {} + for i in range(shots): + outs = measure_all(state) + if outs not in result: + result[outs] = 0 + result[outs] += 1 + return result + + +def measure_by_probability(state, times): + """ + Measure by probability method + :param state: + :param times: + :return: + """ + # print("Measure method Probability") + + assert type(state) is paddle.fluid.ComplexVariable and len(state.shape) == 1 + n = int(np.log2(state.shape[0])) + prob_array = complex_abs(state) # complex -> real + prob_array = paddle.fluid.layers.elementwise_mul(prob_array, prob_array) + prob_array = prob_array.numpy() + gc.collect() + + """ + prob_key = [] + prob_values = [] + pos_list = list(np.nonzero(prob_array)[0]) + for index in pos_list: + string = oct_to_bin_str(index, n) + prob_key.append(string) + prob_values.append(prob_array[index]) + + # print("The sum prob is ", sum(prob_values)) + + samples = np.random.choice(len(prob_key), times, p=prob_values) + """ + samples = np.random.choice(range(2 ** n), times, p=prob_array) + count_samples = Counter(samples) + result = {} + for idex in count_samples: + """ + result[prob_key[idex]] = count_samples[idex] + """ + result[oct_to_bin_str(idex, n)] = count_samples[idex] + return result + + +def measure_state(state, shots): + """ + Measure main entry + :param state: + :param shots: + :return: + """ + if Meas_MED == MEAS_METHOD.SINGLE: + return measure_by_single_accumulation(state, shots) + elif Meas_MED == MEAS_METHOD.PROB: + return measure_by_probability(state, shots) + else: + raise Exception("Measure Error") + + +### RandomCircuit ### +def GenerateRandomCirc(state, circ_depth, n): + """ + Generate random circ + :param state: The whole state + :param circ_depth: number of circuit + :param n: number of qubits + :return: state + """ + gate_string = ['x', 'y', 'z', 'h', 'CNOT'] + internal_state = state + for index in range(circ_depth): + rand_gate_pos = random.randint(0, len(gate_string) - 1) + if rand_gate_pos == (len(gate_string) - 1): + rand_gate_bits = random.sample(range(n), 2) + else: + rand_gate_bits = random.sample(range(n), 1) + internal_state = StateTranfer(internal_state, gate_string[rand_gate_pos], rand_gate_bits) + state = internal_state + return state + + +def GenerateRandomCircAndRev(state, circ_depth, n): + """ + Generate random circ + :param state: The whole state + :param circ_depth: number of circuit + :param n: number of qubits + :return: state + """ + rand_gate_pos_all = [] + rand_gate_bits_all = [] + gate_string = ['x', 'y', 'z', 'h', 'CNOT'] + internal_state = state + for index in range(circ_depth): + rand_gate_pos = random.randint(0, len(gate_string) - 1) + if rand_gate_pos == (len(gate_string) - 1): + rand_gate_bits = random.sample(range(n), 2) + else: + rand_gate_bits = random.sample(range(n), 1) + + rand_gate_pos_all.append(rand_gate_pos) + rand_gate_bits_all.append(rand_gate_bits) + + internal_state = StateTranfer(internal_state, gate_string[rand_gate_pos], rand_gate_bits) + + rand_gate_pos_all = list(reversed(rand_gate_pos_all)) + rand_gate_bits_all = list(reversed(rand_gate_bits_all)) + + for idex, item in enumerate(rand_gate_pos_all): + internal_state = StateTranfer(internal_state, gate_string[item], rand_gate_bits_all[idex]) + + state = internal_state + return state + + +### Tester ### +def RandomTestIt(bits_num, circ_num): + """ + Random Check + :param bits_num: + :param circ_num: + :return: + """ + n = bits_num + repeat_num = 1 # 2 ** 5 + state = init_state_10(n) + + for _ in range(repeat_num): + # state = copy.deepcopy(state_origin) + state = GenerateRandomCircAndRev(state, circ_num, n) + re = measure_state(state, 2 ** 10) + # print(re) + assert (str('0' * n) in list(re.keys())) + assert (2 ** 10 in list(re.values())) + + return True + + +def Test_x_h_CNOT(): # 3 bits coverage test + """ + The smallest tester program + :return: + """ + state = init_state_10(3) + + state = StateTranfer(state, 'x', [0]) + state = StateTranfer(state, 'h', [1]) + state = StateTranfer(state, 'CNOT', [1, 2]) + re = measure_state(state, 2 ** 10) + # print(re) + assert ('001' in list(re.keys()) and '111' in list(re.keys())) + assert (list(re.values())[0] / list(re.values())[1] in Interval(0.9, 1.1)) + return True + + +def Test_cnot_1(): + """ + Check CNOT using reverse circ + :return: + """ + state = init_state_10(3) + + state = StateTranfer(state, 'h', [0]) + state = StateTranfer(state, 'CNOT', [0, 1]) + state = StateTranfer(state, 'CNOT', [0, 2]) + state = StateTranfer(state, 'CNOT', [1, 2]) + state = StateTranfer(state, 'CNOT', [1, 2]) + state = StateTranfer(state, 'CNOT', [0, 2]) + state = StateTranfer(state, 'CNOT', [0, 1]) + state = StateTranfer(state, 'h', [0]) + re = measure_state(state, 2 ** 10) + assert ('000' in list(re.keys())) + assert (2 ** 10 in list(re.values())) + return True + + +def Test_cnot_2(): + """ + Check retation of CNOTS + :return: + """ + state = init_state_10(3) + + state = StateTranfer(state, 'h', [2]) + state = StateTranfer(state, 'CNOT', [2, 1]) + state = StateTranfer(state, 'CNOT', [2, 0]) + state = StateTranfer(state, 'CNOT', [1, 0]) + state = StateTranfer(state, 'CNOT', [1, 0]) + state = StateTranfer(state, 'CNOT', [2, 0]) + state = StateTranfer(state, 'CNOT', [2, 1]) + state = StateTranfer(state, 'h', [2]) + re = measure_state(state, 2 ** 10) + assert ('000' in list(re.keys())) + assert (2 ** 10 in list(re.values())) + return True + + +def Test3All(): + """ + Check all 3 qubits cases + :return: + """ + return Test_x_h_CNOT() and Test_cnot_1() and Test_cnot_2() + + +### Sim2Main ### +def main(bits_num=None, circ_num=None): + """ + :param bits_num: the number of qubits + :param circ_num: the circuit depth to be expected + These two args can be None type and the default behavior is bits_num =5 and circ_num=50. + Two args must be given together. + :return: + :DEBUG INFO: + 1) np.random.seed(0) can be uncomment to guarantee the random sequence can be tracked. + 2) re is the return result: vector string: counter + """ + + # init seed + # np.random.seed(0) + + print('----------', 'init', bits_num, 'bits', '----------') + print('----------', 'init', circ_num, 'circ depth', '----------') + + state = init_state_10(bits_num) + + # ------------------tik------- + # state = StateTranfer(state, 'h', [0]) + # state = StateTranfer(state, 'x', [1]) + # state = StateTranfer(state, 'x', [2]) + # state = StateTranfer(state, 'u', [3], [0.3, 0.5, 0.7]) + # state = StateTranfer(state, 'CNOT', [0, 1]) + # state = StateTranfer(state, 'CNOT', [2, 1]) + # -----------------tok--------- + + # ------------------tik------- + state = StateTranfer(state, 'x', [0]) + state = StateTranfer(state, 'h', [1]) + #state = StateTranfer(state, 'CNOT', [0, 1]) + # -----------------tok--------- + + # ------------------tik------- + # state = GenerateRandomCirc(state, circ_num, n) + # -----------------tok--------- + + # ------------------tik------- + re = measure_state(state, 2 ** 10) + # -----------------tok--------- + + print(re) + + +def Tester(bits_num=None, circ_num=None): + """ + This part is to guarantee the behaviors of the simualtor2. Every Modification MUST ensure this function can be executed correctly. + :return: True: Pass or False: NO Pass + """ + # random tester + + check_value_rand = RandomTestIt(bits_num, circ_num) + check_value_3 = Test3All() + check_all = check_value_rand and check_value_3 + print(check_all) + return check_all + + +if __name__ == '__main__': + with paddle.fluid.dygraph.guard(): + # main(bits_num=5, circ_num=50) + main(bits_num=5) + # RandomTestIt(bits_num=5, circ_num=50) + # print(Test3All()) + + # print(Tester(bits_num=5, circ_num=50)) + #Tester(bits_num=6, circ_num=100) diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..d0c3cbf1020d5c292abdedf27627c6abe25e2293 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000000000000000000000000000000000000..9534b018135ed7d5caed6298980c55e8b1d2ec82 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..2fde2c13d46e187367f542cb7af1821533326231 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,10 @@ +paddlepaddle>=1.8.3 +networkx>=2.4 +matplotlib>=3.3.0 +interval>=1.0.0 +progressbar>=2.5 +sphinx +sphinx-rtd-theme +PyStemmer +readthedocs-sphinx-search +jieba \ No newline at end of file diff --git a/docs/source/_static/logo.png b/docs/source/_static/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b550d9355393abed962a1855bda078250d898ad0 Binary files /dev/null and b/docs/source/_static/logo.png differ diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000000000000000000000000000000000000..26e0695ade3cd47c499aa8570c89ad5bf2f876a7 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('../..')) + + +# -- Project information ----------------------------------------------------- + +project = 'Paddle Quantum' +copyright = u'2020, Institute for Quantum Computing, Baidu Inc.' +author = u'Institute for Quantum Computing, Baidu Inc.' + +# The full version, including alpha/beta/rc tags +release = '1.1.0' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.napoleon', + 'sphinx.ext.mathjax', + 'sphinx_search.extension', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = 'zh_CN' + +html_search_language = 'zh' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +html_logo = '_static/logo.png' + +master_doc = 'index' + +# Autodoc +autodoc_member_order = 'bysource' diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..1a39f6fe2bf9540f653d0b6e9de50d922230f0fd --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,20 @@ +.. Paddle Quantum documentation master file, created by + sphinx-quickstart on Fri Aug 21 10:54:43 2020. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Paddle Quantum's documentation! +========================================== + +.. toctree:: + :maxdepth: 2 + :caption: Paddle Quantum 入门 + + introduction + tutorial + +.. toctree:: + :maxdepth: 4 + :caption: API 文档 + + modules diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst new file mode 100644 index 0000000000000000000000000000000000000000..39aafaa5ae36f30d057c779fb590c752301629fe --- /dev/null +++ b/docs/source/introduction.rst @@ -0,0 +1,121 @@ +.. _header-n0: + +Paddle Quantum (量桨) +======================= + +`Paddle Quantum(量桨) `__\ 是基于百度飞桨开发的量子机器学习工具集,支持量子神经网络的搭建与训练,提供易用的量子机器学习开发套件与量子优化、量子化学等前沿量子应用工具集,使得百度飞桨也因此成为国内首个目前也是唯一一个支持量子机器学习的深度学习框架。 + +.. figure:: https://release-data.cdn.bcebos.com/Paddle%20Quantum.png + :target: https://github.com/PaddlePaddle/Quantum + +量桨建立起了人工智能与量子计算的桥梁,不但可以快速实现量子神经网络的搭建与训练,还提供易用的量子机器学习开发套件与量子优化、量子化学等前沿量子应用工具集,并提供多项自研量子机器学习应用。通过百度飞桨深度学习平台赋能量子计算,量桨为领域内的科研人员以及开发者便捷地开发量子人工智能的应用提供了强有力的支撑,同时也为广大量子计算爱好者提供了一条可行的学习途径。 + + 关于量桨的更多内容可以查看 GitHub 页面:https://github.com/PaddlePaddle/Quantum + +.. _header-n6: + +特色 +---- + +- 易用性 + + - 高效搭建量子神经网络 + - 多种量子神经网络模板 + - 丰富量子算法教程(10+用例) + +- 可拓展性 + + - 支持通用量子电路模型 + - 高性能模拟器支持20多个量子比特的模拟运算 + - 提供多种优化工具和 GPU 加速 + +- 特色工具集 + + - 提供组合优化和量子化学等前沿领域的计算工具箱 + - 自研多种量子机器学习算法 + +.. _header-n15: + +安装步骤 +-------- + +.. _header-n16: + +安装 PaddlePaddle +~~~~~~~~~~~~~~~~~ + +请参考 +`PaddlePaddle `__ +安装配置页面。此项目需求 PaddlePaddle 1.8.3 或更高版本。 + +.. _header-n19: + +下载 Paddle Quantum 并安装 +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: shell + + git clone http://github.com/PaddlePaddle/quantum + +.. code:: shell + + cd quantum + pip install -e . + +.. _header-n23: + +或使用 requirements.txt 安装依赖包 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: shell + + python -m pip install --upgrade -r requirements.txt + +.. _header-n25: + +使用 openfermion 读取 xyz 描述文件 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. note:: 仅在 macOS 和 linux 下可以使用 openfermion 读取 xyz 描述文件。 + +VQE中调用 openfermion 读取分子 xyz 文件并计算,因此需要安装 openfermion 和 +openfermionpyscf。 + +.. code:: shell + + pip install openfermion + pip install openfermionpyscf + +.. _header-n29: + +运行 +~~~~ + +现在,可以试着运行一段程序来验证量桨是否已安装成功。这里我们运行量桨提供的量子近似优化算法 +(QAOA) 的例子。 + +.. code:: shell + + cd paddle_quantum/QAOA/example + python main.py + +.. + +.. note:: 关于 QAOA 的介绍可以参考我们的 `QAOA 教程 `__。 + +.. _header-n51: + +交流与反馈 +---------- + +- 我们非常欢迎您通过 `Github + Issues `__ + 来提交问题、报告与建议。 +- 技术交流QQ群:1076223166 + +.. _header-n118: + +Copyright and License +--------------------- + +Paddle Quantum 使用 `Apache-2.0 license `__ 许可证。 diff --git a/docs/source/modules.rst b/docs/source/modules.rst new file mode 100644 index 0000000000000000000000000000000000000000..a5fd70fbfc869f41067dcc046ef92c45d47f0dd3 --- /dev/null +++ b/docs/source/modules.rst @@ -0,0 +1,7 @@ +paddle_quantum +============== + +.. toctree:: + :maxdepth: 4 + + paddle_quantum diff --git a/docs/source/paddle_quantum.circuit.rst b/docs/source/paddle_quantum.circuit.rst new file mode 100644 index 0000000000000000000000000000000000000000..f370a529166454f5b28e3601fd88b7ece8da5f15 --- /dev/null +++ b/docs/source/paddle_quantum.circuit.rst @@ -0,0 +1,7 @@ +paddle\_quantum.circuit module +============================== + +.. automodule:: paddle_quantum.circuit + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/paddle_quantum.rst b/docs/source/paddle_quantum.rst new file mode 100644 index 0000000000000000000000000000000000000000..be55387e212cef55cc2d8671865ef2bd53ce809e --- /dev/null +++ b/docs/source/paddle_quantum.rst @@ -0,0 +1,17 @@ +paddle\_quantum package +======================= + +.. automodule:: paddle_quantum + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + paddle_quantum.circuit + paddle_quantum.state + paddle_quantum.utils diff --git a/docs/source/paddle_quantum.state.rst b/docs/source/paddle_quantum.state.rst new file mode 100644 index 0000000000000000000000000000000000000000..ecb7d63c08875ce1261c887ac1c0f2106eef8111 --- /dev/null +++ b/docs/source/paddle_quantum.state.rst @@ -0,0 +1,7 @@ +paddle\_quantum.state module +============================ + +.. automodule:: paddle_quantum.state + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/paddle_quantum.utils.rst b/docs/source/paddle_quantum.utils.rst new file mode 100644 index 0000000000000000000000000000000000000000..7d7fa4af0ab2ee46c34043f9e29532319283b6d8 --- /dev/null +++ b/docs/source/paddle_quantum.utils.rst @@ -0,0 +1,7 @@ +paddle\_quantum.utils module +============================ + +.. automodule:: paddle_quantum.utils + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst new file mode 100644 index 0000000000000000000000000000000000000000..ea593d6baff0e9302e51bf74c4b2d1abbf1a80d4 --- /dev/null +++ b/docs/source/tutorial.rst @@ -0,0 +1,41 @@ +教程与案例 +======================= + +我们准备了入门教程和入门案例,来帮助用户快速学会如何使用量桨(Paddle Quantum)。 + +.. _header-n33: + +入门教程 +-------- + +我们提供了一份 `Paddle Quantum 入门手册 `__\ 来方便用户快速上手 +Paddle Quantum。目前支持 PDF 阅读和运行 Jupyter Notebook +两种方式。内容上,该手册包括以下几个方面: + +- Paddle Quantum 的详细安装教程 +- 量子计算的基础知识介绍 +- Paddle Quantum 的使用介绍 +- PaddlePaddle 飞桨优化器使用教程 +- 具体的量子机器学习案例—VQE + +入门案例 +-------- + +我们提供了涵盖量子优化、量子化学、量子机器学习等多个领域的案例供大家学习。与\ `入门手册 `__\ 类似,每个教程目前支持 +PDF 阅读和运行 Jupyter Notebook 两种方式。我们推荐用户下载 Notebook +后,本地运行进行实践。 + +1. `量子近似优化算法 (QAOA) `__ +2. `变分量子特征求解器 (VQE) `__ +3. `量子神经网络的贫瘠高原效应 (Barren Plateaus) `__ +4. `量子分类器 (Quantum Classifier) `__ +5. `量子变分自编码器 (Quantum Autoencoder) `__ +6. `量子生成对抗网络 (Quantum GAN) `__ +7. `子空间搜索 - 量子变分特征求解器 (SSVQE) `__ +8. `变分量子态对角化算法 (VQSD) `__ +9. `吉布斯态的制备 (Gibbs State Preparation) `__ +10. `变分量子奇异值分解 (VQSVD) `__ + +此外,Paddle Quantum 也支持在 GPU +上进行量子机器学习的训练,具体的方法请参考案例:`在 GPU 上使用 Paddle +Quantum `__。 diff --git a/introduction/PaddleQuantum_Tutorial_CN.ipynb b/introduction/PaddleQuantum_Tutorial_CN.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..a84cfa2437fd353e744ca9d8e01758f232991d4a --- /dev/null +++ b/introduction/PaddleQuantum_Tutorial_CN.ipynb @@ -0,0 +1,1736 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Paddle Quantum 入门手册\n", + "\n", + " Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 总览" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这是一份简洁、实用的关于量子机器学习(Quantum Machine Learnig,QML)的介绍,面向读者包括但不限于物理、数学和计算机背景。本手册主要采用 Jupyter Notebook 的交互形式 (调用 Numpy, Matplotlib等 Python包以及飞桨Paddlepaddle深度学习框架来实现基于线性代数的量子运算和机器学习优化问题)。我们不仅提供了关于量子计算的一些基础教程同时还能手把手带你完成属于你自己的第一份量子机器学习算法。这并不是一份关于量子计算的百科全书,但我们涉及的案例经常出现在教科书中以及文献中。如果你想深入挖掘一些相关的基础知识,我们也提供了一些外部链接方便用户自己学习。\n", + "\n", + "\n", + "最后修改于: 2020年9月9日 由量桨 Paddle Quantum开发小组共同完成。\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 目录 \n", + "\n", + "\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 安装教程\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Conda 与 Python 环境安装" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们推荐使用 [Anaconda](https://www.anaconda.com/download) 作为 Python3的开发环境,支持多种主流操作系统(Windows, MacOS, 以及 Linux)。Anaconda本身提供 Scipy, Numpy, Matplotlib等科学计算、作图包,最主要的是其自带 Python开发环境的管理器 conda,可以用来安装或者更新主流 Python包。这里我们提供一个例子来学习使用 conda创建和管理环境:\n", + "\n", + "1. 首先进入命令行 (Terminal) 界面:Windows用户可以使用组合键 `win + R` 打开运行程序再输入 `cmd`/ Mac用户可以使用组合键 `command⌘ + 空格` 再输入 `Terminal`。\n", + "1. 进入 Terminal 后输入 `conda create --name paddle_quantum_env python=3.6` 创建名为 `paddle_quantum_env` 的 Python3.6 环境。\n", + "1. 在 Terminal 内通过 `conda env list` 查看已有的环境,然后通过 `conda activate paddle_quantum_env ` 进入我们刚建立的环境。\n", + "1. 为了能正确运行 Jupyter Notebook 我们还需要安装 `conda install jupyter notebook`。安装完成之后,如果你想开启 Jupyter 只需要在Terminal内激活正确的环境然后输入 `jupyter notebook` 即可。\n", + "\n", + "\n", + "\n", + "关于 conda 更多的本地指令请参考 [官方教程](https://docs.conda.io/projects/conda/en/latest/user-guide/getting-started.html)。\n", + " 此外,你也可以通过使用 Anaconda Navigator 开启 jupyter notebook。\n", + "\n", + "以下是这个教程中你需要使用的包:\n", + "
    \n", + "
  • Numpy\n", + "
  • Paddlepaddle 1.8.3 以上\n", + "
  • Paddle Quantum \n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 安装 Paddle和 Paddle Quantum" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "接着我们先讲解下如何安装飞桨Paddlepaddle深度学习框架。用户需要再次打开 Terminal 界面,输入 `conda activate paddle_quantum_env `进入我们新建的Python 环境。接着输入 `conda install paddlepaddle` 安装最新的飞桨Paddle。如果你在按照以上方案安装 paddle-cpu 或者 paddle-gpu 遇到问题们可以参考 [官方链接](https://www.paddlepaddle.org.cn/documentation/docs/zh/install/install_Conda.html)。或者考虑以下备选方案:在 Terminal 界面输入 `pip install paddlepaddle` 或者开启 jupyter notebook 后运行以下的命令。" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import clear_output\n", + "\n", + "# 备用方法: 通过PyPI安装最新的飞桨Paddle\n", + "!pip install paddlepaddle\n", + "clear_output()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在安装完成后,请通过以下代码段来检验是否成功安装 Paddle。" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "HPV-UyAqsKFp" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running Verify Fluid Program ... \n", + "Your Paddle Fluid works well on SINGLE GPU or CPU.\n", + "Your Paddle Fluid works well on MUTIPLE GPU or CPU.\n", + "Your Paddle Fluid is installed successfully! Let's start deep Learning with Paddle Fluid now\n" + ] + } + ], + "source": [ + "from paddle import fluid\n", + "\n", + "# 检查飞桨Paddle是否成功安装\n", + "fluid.install_check.run_check()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "接着我们安装 Paddle Quantum包,很遗憾目前我们还不支持通过 pip安装。需要用户在 Terminal 界面通过 git指令 `git clone http://github.com/PaddlePaddle/quantum` 下载文件,然后输入 `cd quantum` 和 `pip install -e .` 完成安装。接着在 Terminal 界面输入`pip list`查看是否在正确的环境中安装完成。关于 git的使用和安装,请参考这篇 [教程](https://git-scm.com/book/zh/v2/%E8%B5%B7%E6%AD%A5-%E5%AE%89%E8%A3%85-Git)。此外,如果你需要更多的关于安装Paddle-Quantum 的帮助,可以参考我们的 [Github链接](https://github.com/PaddlePaddle/Quantum) 或者通过 Github Issues联系我们。" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from paddle import fluid\n", + "from paddle.complex import matmul, transpose, trace\n", + "from paddle_quantum.circuit import UAnsatz\n", + "from paddle_quantum.utils import hermitian, random_pauli_str_generator, pauli_str_to_matrix\n", + "from paddle_quantum.state import vec, vec_random, density_op, density_op_random" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "以上的几个代码块没有任何报错的话,恭喜你!接着就可以顺利运行全部的教程了!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "

[回到 目录]

\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 量子计算基础" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "量子计算 (Quantum Computing QC )是利用量子物理中特有的现象 (量子叠加态,量子纠缠)来设计相应的量子算法以解决 (物理、化学、计算机等领域)特定的任务。现有的量子计算有好几种模型,比如有基于绝热定理的绝热计算模型以及基于测量的MBQC模型等等。在本手册中,我们主要讨论目前影响力最大、使用最广泛的量子电路(Quantum circuit)模型。在量子电路的框架下,运算最基本的组成单元是量子比特 (qubit)。这与经典计算机中比特 (bit) 的概念很相似。经典比特只能处于0和1两种状态中的某一种 (物理图景上可以对应高低电位)。与之不同的是,量子比特不仅可以处于两个状态$\\lvert {0}\\rangle$ 还有 $\\lvert {1}\\rangle$还可以处于两者的叠加态 (稍后我们来具体讲解下这一概念)。而所谓的量子计算,就是利用量子逻辑门操控这些量子比特。逻辑门运算的基本理论是线性代数,在此我们假定读者已经具备一定的线性代数基础。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 什么是量子比特?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 数学表示\n", + "\n", + "在量子力学中,一个微观粒子的量子态可以表示为由两个正规正交基线性组合得到的向量,这些基向量一般可以写为:\n", + "\n", + "$$ \\lvert {0}\\rangle := \\begin{bmatrix} 1 \\\\ 0 \\end{bmatrix}, \\quad \\lvert {1}\\rangle := \\begin{bmatrix} 0 \\\\ 1 \\end{bmatrix} $$\n", + "\n", + "我们这里向量的表示方法采用了量子物理上传统的狄拉克表示 (bra-ket)。这两个单位正交向量$\\{\\lvert {0}\\rangle, \\lvert {1}\\rangle \\}$,它俩一般被称为**计算基** (computational basis)。物理图景中我们可以认为 $\\lvert {0}\\rangle$ 和 $\\lvert {1}\\rangle$ 分别对应一个原子的能量基态和激发态或者其他一些二分类状态。 一个量子比特所有可能的态可以看作是二维希尔伯特空间中所有的归一化向量,这个希尔伯特空间的一组正规正交基正是$\\{\\lvert {0}\\rangle, \\lvert {1}\\rangle \\}$。而更多的量子比特系统也同样可以由高维度的希尔伯特空间中的的单位向量表示,而这个高维希尔伯特空间的正交基就是$\\{\\lvert {0}\\rangle, \\lvert {1}\\rangle \\}$的张量积。比如说,一个两量子比特 (2-qubit)系统可以被一个4维的希尔伯特空间里的单位复数向量表示,而这个希尔伯特空间的正规正交基是:\n", + "\n", + "$$ \n", + "\\lvert {00}\\rangle = \\lvert {0}\\rangle\\otimes \\lvert {0}\\rangle := \\begin{bmatrix} 1 \\\\ 0 \\\\ 0 \\\\ 0 \\end{bmatrix}, \\quad \n", + "\\lvert {01}\\rangle = \\lvert {0}\\rangle\\otimes \\lvert {1}\\rangle := \\begin{bmatrix} 0 \\\\ 1 \\\\ 0 \\\\ 0 \\end{bmatrix}, \\quad\n", + "\\lvert {10}\\rangle = \\lvert {1}\\rangle\\otimes \\lvert {0}\\rangle := \\begin{bmatrix} 0 \\\\ 0 \\\\ 1 \\\\ 0 \\end{bmatrix}, \\quad\n", + "\\lvert {11}\\rangle = \\lvert {1}\\rangle\\otimes \\lvert {0}\\rangle := \\begin{bmatrix} 0 \\\\ 0 \\\\ 0 \\\\ 1 \\end{bmatrix}\n", + "$$\n", + "\n", + "我们默认最左边的位置代表第一个量子比特,依此类推。其中符号 $\\otimes$ 是张量积运算。其工作原理大概如下:\n", + "给定两个矩阵$A_{m\\times n}$ 还有 $B_{p \\times q}$,那么$A,B$的张量积为\n", + "\n", + "\n", + "$$\n", + "A \\otimes B = \n", + "\\begin{bmatrix}\n", + "a_{11}B & \\cdots & a_{1 n}B\\\\\n", + "\\vdots & \\ddots & \\vdots \\\\\n", + "a_{m1}B & \\cdots & a_{m n}B\n", + "\\end{bmatrix}_{(mp)\\times (nq)}\n", + "$$\n", + "\n", + "一个单量子比特所处的任意量子态 $\\lvert {\\psi}\\rangle$可以写成基向量 $\\lvert {0}\\rangle$ 和 $\\lvert {1}\\rangle$ 的线性叠加,也就是说,它可以被描述成一个$\\lvert {0}\\rangle$ 和 $\\lvert {1}\\rangle$的线性组合\n", + "\n", + "\n", + "$$\\lvert {\\psi}\\rangle = \\alpha \\lvert {0}\\rangle + \\beta \\lvert {1}\\rangle\n", + ":= \\begin{bmatrix} \\alpha \\\\ \\beta \\end{bmatrix},\\,\\alpha, \\beta \\in \\mathbb{C}$$\n", + "\n", + "其中$\\alpha$ 和 $\\beta$ 可以是**复数**,他们表示概率振幅。这意味着当我们测量这个量子比特时,根据波恩法则,测量得到量子比特处于$\\lvert {0}\\rangle$ 状态的概率是$|\\alpha|^2$;而测量得到$\\lvert {1}\\rangle$的概率是$|\\beta|^2$。由于概率相加等于1,我们必须要加入如下的限制条件:\n", + "\n", + "$$|\\alpha|^2 + |\\beta|^2 = 1$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 布洛赫球面 (Bloch Sphere) 表示\n", + "\n", + "我们用一个球面上的点来表示一个量子比特可能处于的量子态,这个球面被称为**布洛赫球面**,(见图 1)\n", + "\n", + "$$ \n", + "\\lvert {\\psi}\\rangle = \\alpha \\lvert {0}\\rangle + \\beta \\lvert {1}\\rangle \n", + "= \\cos\\bigg(\\frac{\\theta}{2}\\bigg) \\lvert {0}\\rangle + e^{i\\varphi}\\sin\\bigg(\\frac{\\theta}{2}\\bigg) \\lvert {1}\\rangle\n", + "$$\n", + "\n", + "注意:多个量子系统的状态就无法用布洛赫球面来表示。如果是一个经典比特的话,那么它只有两个状态0和1,也就是布洛赫球面的北极和南极。这两个位置恰好对应着$\\lvert {0}\\rangle$ 和 $\\lvert {1}\\rangle$。**而一个量子比特不光可以处于两极,它可以在球面上任意一点,这样一种叠加的状态是经典比特做不到的**。举例来说,量子态 $\\frac{1}{\\sqrt{2}}\\big(\\lvert {0}\\rangle + i\\lvert {1}\\rangle\\big)$就处于球面赤道和 y-正半轴的交界处。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "          \n", + "         \n", + "         \n", + "         \n", + "  \n", + "**图 1.** \n", + "单量子比特的布洛赫球面表示. [[图片来源]](https://en.wikipedia.org/wiki/Qubit)\n", + "\n", + "下面的内容面向对量子计算更熟悉的读者。如果你阅读这段感到困难,不用担心,您可以选择略过这一节,这不会对理解接下的内容产生影响。由于量子比特之间的交互以及去相干问题 (Decoherence),因此,对于一个具有多量子比特的系统来说,它的单量子比特子系统将不再处于纯态 (pure state),而是演变成混合态 (mixed state)。混合态可以看成不同纯态的按照一定概率的混合。\n", + " **单比特的混合态可以看成是布洛赫球内部的点,而不是存在于球表面**。通常来说,混合态需要用到量子力学的密度矩阵形式来描述,比如\n", + "\n", + "$$ \n", + "\\rho_{\\text{mixed}} \n", + "= \\sum_i P_i \\lvert {\\psi_i}\\rangle\\langle{\\psi_i} \\lvert\n", + "= \\frac{1}{2} \\lvert {0}\\rangle \\langle{0} \\lvert + \\frac{1}{2} \\lvert {1}\\rangle\\langle{1} \\lvert\n", + ":= \\frac{1}{2} \\begin{bmatrix} 1 \\\\ 0\\end{bmatrix} \\begin{bmatrix} 1 & 0 \\end{bmatrix} + \\frac{1}{2} \\begin{bmatrix} 0 \\\\ 1\\end{bmatrix} \\begin{bmatrix} 0 & 1 \\end{bmatrix} \n", + "= \\frac{1}{2} \\begin{bmatrix} 1 & 0\\\\ 0 & 1 \\end{bmatrix}\n", + "$$\n", + "\n", + "其中行向量 (bra) $ \\langle{0} \\lvert $ 是列向量 (ket) $\\lvert {0}\\rangle$ 的复共轭转置。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**注:**如需更多信息,可参考维基百科 [链接](https://en.wikipedia.org/wiki/Qubit)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 什么是量子逻辑门?\n", + "\n", + "在经典计算机中,我们可以在经典比特上施加基本的逻辑运算( 非门 NOT, 与非门 NAND, 异或门 XOR, 与门 AND, 或门 OR)并组合成更复杂的运算。而量子计算则有完全不同的一套逻辑运算,它们被称为量子门 (quantum gate)。我们并不能在一个量子计算机上编译现有的C++程序。因为**经典计算机和量子计算机有不同的逻辑门构造,所谓的量子算法是需要利用这些量子门的特殊性来构造的**。\n", + "\n", + "量子门在数学上可以被表示成酉矩阵 (unitary matrix)。酉矩阵操作可以保证向量的长度不变,这是个很好的性质。不然我们对一个纯态量子比特进行操作,会让它劣化成混合态导致其无法接着使用。那么什么是酉矩阵呢?\n", + "\n", + "$$ \n", + "U^{\\dagger}U = UU^{\\dagger} = I,\\,\n", + "\\Vert \\lvert {\\psi}\\rangle \\Vert = \\Vert U\\lvert {\\psi}\\rangle\\Vert = 1\n", + "$$\n", + "\n", + "其中$U^{\\dagger}$是$U$的埃尔米特转置,$I$ 表示单位矩阵。但是酉矩阵作为量子门的物理意义是什么?这意味着**所有的量子门都必须是可逆的**。对于任何一个量子门运算,都可以找到一个与其对应的反向运算。除此之外,酉矩阵必须是个方阵。因为量子门的输入和输出要求有同样数量的量子比特。一个作用在 $n$ 量子比特的量子门可以写成一个 $2^n \\times 2^n$ 的酉矩阵。最常见的(也是物理上最容易实现的)量子门作用在一个或两个量子比特上,就像经典逻辑门那样。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 单量子比特门\n", + "\n", + "接下来,我们介绍在量子计算中非常重要的单量子比特门,包括泡利矩阵 $\\{X, Y, Z\\}$ 或 $\\{\\sigma_x, \\sigma_y, \\sigma_z\\}$、单比特旋转门 $\\{R_x, R_y, R_z\\}$ 和哈达玛门 $H$。有一个对于经典或量子计算都很重要的门是 **非门 (NOT gate)**,其可以表示成如下酉矩阵:\n", + "\n", + "\n", + "$$ X := \\begin{bmatrix} 0 &1 \\\\ 1 &0 \\end{bmatrix}$$\n", + "\n", + "这个量子门(酉矩阵)作用在单量子比特(一个复向量)上本质上的运算是**矩阵乘以向量**\n", + "\n", + "\n", + "$$ \n", + "X \\lvert {0}\\rangle := \\begin{bmatrix} 0 &1 \\\\ 1 &0 \\end{bmatrix} \\begin{bmatrix} 1 \\\\0 \\end{bmatrix} \n", + "=\\begin{bmatrix} 0 \\\\1 \\end{bmatrix} = \\lvert {1}\\rangle \n", + "\\quad \\text{and} \\quad \n", + "X \\lvert {1}\\rangle := \\begin{bmatrix} 0 &1 \\\\ 1 &0 \\end{bmatrix} \\begin{bmatrix} 0 \\\\1 \\end{bmatrix} \n", + "=\\begin{bmatrix} 1 \\\\0 \\end{bmatrix}=\\lvert {0}\\rangle\n", + "$$\n", + "\n", + "回忆起前面的布洛赫球面表示,这个矩阵 $X$ 作用在一个量子比特(布洛赫球面上的一点)就相当于**关于布洛赫球的X轴旋转角度 $\\pi$**。这就是为什么\n", + " $X$ 可以表示成 $R_x(\\pi)$(只相差了一个无关紧要的全局相位 $e^{-i\\pi/2} = -i$ )。其他两个泡利矩阵 $Y$ 和 $Z$ 在这一点上也非常相似 (代表绕 Y和 Z轴旋转 $\\pi$运算 ):\n", + "\n", + "\n", + "$$ \n", + "Y := \\begin{bmatrix} 0 &-i \\\\ i &0 \\end{bmatrix}\n", + "\\quad \\text{and} \\quad \n", + "Z := \\begin{bmatrix} 1 &0 \\\\ 0 &-1 \\end{bmatrix}\n", + "$$\n", + "\n", + "一般来说,任何一个在布洛赫球关于相应的轴旋转 $\\theta$ 角度的量子门可以表示为:\n", + "\n", + "$$ \n", + "R_x(\\theta) := \n", + "\\begin{bmatrix} \n", + "\\cos \\frac{\\theta}{2} &-i\\sin \\frac{\\theta}{2} \\\\ \n", + "-i\\sin \\frac{\\theta}{2} &\\cos \\frac{\\theta}{2} \n", + "\\end{bmatrix}\n", + ",\\quad \n", + "R_y(\\theta) := \n", + "\\begin{bmatrix}\n", + "\\cos \\frac{\\theta}{2} &-\\sin \\frac{\\theta}{2} \\\\ \n", + "\\sin \\frac{\\theta}{2} &\\cos \\frac{\\theta}{2} \n", + "\\end{bmatrix}\n", + ",\\quad \n", + "R_z(\\theta) := \n", + "\\begin{bmatrix}\n", + "e^{-i\\frac{\\theta}{2}} & 0 \\\\ \n", + "0 & e^{i\\frac{\\theta}{2}}\n", + "\\end{bmatrix}\n", + "$$\n", + "\n", + "除了旋转门之外,最重要的单比特门就是哈达玛门了。对应的布洛赫球面解释是两个旋转组成的,先是按Z轴旋转$\\pi$,然后按Y轴旋转$\\pi/2$。它的矩阵表示是:\n", + "\n", + "$$H := \\frac{1}{\\sqrt{2}}\\begin{bmatrix} 1 &1 \\\\ 1 &-1 \\end{bmatrix} $$\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 两比特量子门\n", + "\n", + "从单量子比特门我们可以拓展到两量子比特门。有两种拓展方式,第一种是只挑选出一个量子比特,在上面施加单量子比特门,其他的量子比特则不受影响。有的时候,您会见到如下图所示的量子电路:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "          \n", + "         \n", + "         \n", + "         \n", + "**图 2.** \n", + "两量子比特逻辑运算的电路表示和解读. [[图片来源]](https://en.wikipedia.org/wiki/Quantum_logic_gate)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "作用在两量子比特上的量子门可以表示成一个$4\\times4$酉矩阵\n", + "$$\n", + "U = H \\otimes I \n", + "= \\frac{1}{\\sqrt{2}} \\begin{bmatrix} 1 &1 \\\\ 1 &-1 \\end{bmatrix} \n", + "\\otimes \\begin{bmatrix} 1 &0 \\\\ 0 &1 \\end{bmatrix} \n", + "= \\frac{1}{\\sqrt{2}} \\,\n", + "\\begin{bmatrix}\n", + "1 &0 &1 &0 \\\\ \n", + "0 &1 &0 &1 \\\\\n", + "1 &0 &-1 &0 \\\\\n", + "0 &1 &0 &-1 \n", + "\\end{bmatrix}\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "另一种拓展方式是作用在全部两个量子比特上。比如CNOT,这个门会使得一个量子比特的状态影响到另一个量子比特的状态\n", + "\n", + "$$\n", + "CNOT := \n", + "\\begin{bmatrix} \n", + "1 &0 &0 &0 \\\\ \n", + "0 &1 &0 &0 \\\\\n", + "0 &0 &0 &1 \\\\\n", + "0 &0 &1 &0 \n", + "\\end{bmatrix}\n", + "$$\n", + "\n", + "我们近距离观察一下它作用在不同的初始量子态上\n", + "\n", + "$$\n", + "CNOT \\lvert {00}\\rangle = \\lvert {00}\\rangle, \\quad\n", + "CNOT \\lvert {01}\\rangle = \\lvert {01}\\rangle, \\quad\n", + "CNOT \\lvert {10}\\rangle = \\lvert {11}\\rangle, \\quad\n", + "CNOT \\lvert {11}\\rangle = \\lvert {10}\\rangle\n", + "$$\n", + "\n", + "也就是说,当第一个量子比特处于 $\\lvert {1}\\rangle$状态时,CNOT会在第二个量子比特上施加$X$门,如果第一个量子比特处于 $\\lvert {0}\\rangle$状态,那么第二个量子比特则不受任何影响。这也是为什么 CNOT 会被称为受控非门。下面是一些常见的量子门及它们的矩阵表示" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "          \n", + "         \n", + "         \n", + "         \n", + "  \n", + "**图 3.** \n", + "常见的量子门. [[图片来源]](https://en.wikipedia.org/wiki/Quantum_logic_gate)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**注**:更多信息可见如下维基百科 [链接](https://en.wikipedia.org/wiki/Quantum_logic_gate)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 什么是量子力学中的测量?\n", + "\n", + "对于一个两分类的量子态,比如电子的自旋(Spin),可以自旋向上。这时我们规定该电子处于 $\\lvert {0}\\rangle$态。当然电子也可以自旋向下,这时我们规定它处于 $\\lvert {1}\\rangle$ 态。神奇的是,电子等微观粒子在被观测之前可以同时处于自旋向上和自旋向下的叠加态 $\\lvert {\\psi}\\rangle =\\alpha \\lvert {0}\\rangle + \\beta \\lvert {1}\\rangle$。那么这个奇怪的叠加态到底指的是什么呢?答案很简单,我们可以去测量一下这个处于\"叠加态\"的电子。值得注意的是,量子力学中的测量通常指的是一个统计结果而不是单次测量。这是由于测量本身的特性会使得观察后的量子态塌缩。就拿我们前面提到的处于 $\\lvert {\\psi}\\rangle$ 态的这个电子来举例,如果我们测量这一个电子的自旋,我们会有 $|\\alpha|^2$ 的概率观测到自旋向上并且观测后量子态塌缩成 $ \\lvert {0}\\rangle$。同样的,我们也有 $|\\beta|^2$ 的概率测量得到自旋向下$\\lvert {1}\\rangle$。那么想要精确的得到 $\\alpha$ 的数值,一次实验显然是不够的。我们需要拜托物理学家朋友准备了好多好多处于叠加态 $\\alpha \\lvert {0}\\rangle + \\beta \\lvert {1}\\rangle$ 的电子,把每一个电子的自旋都测量了一下再统计频率。测量在量子力学中地位比较特殊,如果读者觉得难理解。请参阅 [维基百科-量子力学中的测量](https://en.wikipedia.org/wiki/Measurement_in_quantum_mechanics#:~:text=In%20quantum%20physics%2C%20a%20measurement,makes%20are%20in%20general%20probabilistic.) 获取更多知识。\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 示例以及练习\n", + "\n", + "### 示例: 用Paddle Quantum创建 $X$ 门\n", + "\n", + "**注意:** 所有的单比特旋转门都按如下规定建立:\n", + "\n", + "$$ \n", + "R_x(\\theta) := \n", + "\\begin{bmatrix} \n", + "\\cos \\frac{\\theta}{2} &-i\\sin \\frac{\\theta}{2} \\\\ \n", + "-i\\sin \\frac{\\theta}{2} &\\cos \\frac{\\theta}{2} \n", + "\\end{bmatrix}\n", + ",\\quad \n", + "R_y(\\theta) := \n", + "\\begin{bmatrix}\n", + "\\cos \\frac{\\theta}{2} &-\\sin \\frac{\\theta}{2} \\\\ \n", + "\\sin \\frac{\\theta}{2} &\\cos \\frac{\\theta}{2} \n", + "\\end{bmatrix}\n", + ",\\quad \n", + "R_z(\\theta) := \n", + "\\begin{bmatrix}\n", + "e^{-i\\frac{\\theta}{2}} & 0 \\\\ \n", + "0 & e^{i\\frac{\\theta}{2}}\n", + "\\end{bmatrix}\n", + "$$\n", + "\n", + "因此,我们不难看出 $X$ 门可以表示为$R_x(\\pi)$。以下是代码展示:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "量子门的矩阵表达式为:\n", + "[[ 6.123234e-17+0.j -6.123234e-17-1.j]\n", + " [ 6.123234e-17-1.j 6.123234e-17+0.j]]\n" + ] + } + ], + "source": [ + "# 设置角度参数 theta = pi\n", + "theta = np.array([np.pi])\n", + "\n", + "# 启动 Paddle 动态图模式\n", + "with fluid.dygraph.guard():\n", + " \n", + " # 我们需要将 Numpy array 转换成 Paddle 动态图模式中支持的 variable\n", + " theta = fluid.dygraph.to_variable(theta)\n", + " \n", + " # 设置计算所需的量子比特数量\n", + " num_qubits = 1\n", + " \n", + " # 初始化我们的单比特量子电路\n", + " cir = UAnsatz(num_qubits)\n", + " \n", + " # 在第一个量子比特(第0号量子比特)的位置上施加一个 Rx 旋转门, 角度为 pi\n", + " which_qubit = 0\n", + " cir.rx(theta, which_qubit)\n", + " \n", + " # 打印出这个量子门\n", + " # 转换成 numpy \n", + " print('量子门的矩阵表达式为:')\n", + " print(cir.U.numpy())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "结果和$X (NOT)$门只相差一个全局相位$-i$\n", + "\n", + "\n", + "$$ \\text{output} = \\begin{bmatrix} 0 &-i \\\\ -i &0 \\end{bmatrix}\n", + "= -i\\begin{bmatrix} 0 &1 \\\\ 1 &0 \\end{bmatrix} = -i X\n", + "$$\n", + "\n", + "有兴趣的话,你可以仔细思考一下为什么在量子计算中,全局相位并不重要。\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 练习: 创建 $Y$ 门\n", + "\n", + "那么按照以上的例子依葫芦画瓢,你是否可以试着自己创建一个 $Y$ 门?试着补全下面的代码" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "theta = \"your code\"\n", + "with fluid.dygraph.guard():\n", + " \n", + " theta = fluid.dygraph.to_variable(theta)\n", + " num_qubits = 1\n", + " cir = UAnsatz(\"your code\")\n", + " cir.ry(\"your code\")\n", + " print(cir.U.numpy())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "和原来一样,我们还是多了一个全局相位\n", + "\n", + "\n", + "$$ \\text{output} = \\begin{bmatrix} 0 &-1 \\\\ 1 &0 \\end{bmatrix}\n", + "= -i\\begin{bmatrix} 0 &-i \\\\ i &0 \\end{bmatrix} = -i Y\n", + "$$\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "

[回到 目录]

\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 量子电路模板/量子神经网络" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "经过上面的准备,你现在有一定的知识基础可以了解量子机器学习了。简单来说我们要做的就是利用量子电路来替代传统的神经网络来完成机器学习的任务。我们一般会准备一个可调节参数的量子电路(量子神经网络 QNN),很多时候也被称为模板 (Ansatz),里面的参数是人为可调节的(这些参数其实就是旋转门的角度$\\theta$)。如果再加上一个精心设计的损失函数,就可以将一个量子计算问题转化为经典的寻找损失函数最小值问题。然后不断调节电路中的参数直到损失函数下降至收敛 (此时损失函数达到最优值或次优值), 我们就完成优化了。\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 示例: 如何创建量子神经网络 QNN?\n", + "\n", + "所谓的QNN就是通常可以表示为一些单比特量子旋转门和双比特门的组合。其中一个可以高效利用硬件的架构是只包含 $\\{R_x, R_y, R_z, \\text{CNOT} \\}$这四种量子门的模板。它们很容易在NISQ设备(通常是超导量子比特)上实现,因为CNOT只需要实施在相邻量子比特上。一个例子可见下图:\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "通常来说,每条线代表一个量子比特。我们把图最上端的认为是第一个量子比特,依次往下。从左到右代表我们施加门的时间顺序,先施加最左边的量子门。接下来,我们来看看如何在量桨上建造这个简单的两比特量子神经网络\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "图中量子神经网络 U(theta=pi) 的矩阵表达式是:\n", + "[[ 0.0000000e+00 -1.0000000e+00 6.1232340e-17 -6.1232340e-17]\n", + " [-1.0000000e+00 0.0000000e+00 -6.1232340e-17 6.1232340e-17]\n", + " [-6.1232340e-17 6.1232340e-17 1.0000000e+00 1.2246468e-16]\n", + " [ 6.1232340e-17 -6.1232340e-17 -1.2246468e-16 1.0000000e+00]]\n" + ] + } + ], + "source": [ + "# 设置角度参数 theta \n", + "theta = np.full([4], np.pi)\n", + "\n", + "# 启动 Paddle 动态图模式\n", + "with fluid.dygraph.guard():\n", + " \n", + " # 我们需要将 Numpy array 转换成 Paddle 动态图模式中支持的 variable\n", + " theta = fluid.dygraph.to_variable(theta)\n", + " \n", + " # 初始化量子电路\n", + " num_qubits = 2\n", + " cir = UAnsatz(num_qubits)\n", + " \n", + " # 添加单比特旋转门\n", + " cir.ry(theta[0], 0)\n", + " cir.ry(theta[1], 1)\n", + "\n", + " # 添加两比特门\n", + " cir.cnot([0, 1])\n", + "\n", + " # 添加单比特旋转门\n", + " cir.ry(theta[2], 0)\n", + " cir.ry(theta[3], 1)\n", + " \n", + " print('图中量子神经网络 U(theta=pi) 的矩阵表达式是:')\n", + " print(cir.U.numpy().real)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "$$ \\text{output} = \n", + "\\begin{bmatrix} \n", + "0 &-1 &0 &0 \\\\ \n", + "-1 &0 &0 &0 \\\\\n", + "0 &0 &1 &0 \\\\\n", + "0 &0 &0 &1 \n", + "\\end{bmatrix}\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 练习:\n", + "\n", + "给你如下代码,你能想象出对应的电路吗?" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "theta = np.full([6], np.pi)\n", + "with fluid.dygraph.guard():\n", + " \n", + " theta = fluid.dygraph.to_variable(theta)\n", + " \n", + " num_qubits = 3\n", + " cir = UAnsatz(num_qubits) \n", + " \n", + " cir.ry(theta[0], 0)\n", + " cir.ry(theta[1], 1)\n", + " cir.ry(theta[2], 2)\n", + " \n", + " cir.cnot([0, 1])\n", + " cir.cnot([1, 2])\n", + "\n", + " cir.ry(theta[3], 0)\n", + " cir.ry(theta[4], 1)\n", + " cir.ry(theta[5], 2)\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "答案如下:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 内置的电路模板" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在最新版本的 Paddle Quantum中,我们提供了一些内置的电路模板方便场景部署。" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "N = 3 # 设置量子比特数\n", + "\n", + "# 启动 Paddle 动态图模式\n", + "with fluid.dygraph.guard():\n", + " \n", + " # 初始化量子电路\n", + " cir = UAnsatz(N)\n", + " \n", + " # 给每一个量子比特施加哈达玛门 H\n", + " cir.superposition_layer()\n", + " \n", + " # 制备输出态\n", + " # 如果用户不输入初始量子态,默认初始为|00..0>\n", + " final_state = cir.run_state_vector()\n", + " \n", + " # 获取概率分布的理论值,令 shots = 0\n", + " cir.measure(shots = 0, plot = True)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "N = 3 # 设置量子比特数\n", + "\n", + "# 启动 Paddle 动态图模式\n", + "with fluid.dygraph.guard():\n", + " \n", + " # 初始化量子电路\n", + " cir = UAnsatz(N)\n", + " \n", + " # 给每一个量子比特施加 Ry(pi/4)旋转\n", + " cir.weak_superposition_layer()\n", + " \n", + " # 制备输出态\n", + " # 如果用户不输入初始量子态,默认初始为|00..0>\n", + " final_state = cir.run_state_vector()\n", + " \n", + " # 获取概率分布的理论值,令 shots = 0\n", + " cir.measure(shots = 0, plot = True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "以下是一个使用频率较高的电路模板 `complex_entangled_layer(theta, DEPTH)`,用户可按照电路深度参数 `DEPTH/D` 快速拓展电路。其中涉及的广义旋转门 $U_3$ 的定义为:\n", + "\n", + "$$\n", + "U_3(\\theta, \\phi, \\varphi) := \n", + "\\begin{bmatrix} \n", + "\\cos \\frac{\\theta}{2} & -e^{i \\varphi}\\sin \\frac{\\theta}{2} \\\\ \n", + "e^{i \\phi}\\sin \\frac{\\theta}{2} &e^{i (\\phi+\\varphi)} \\cos \\frac{\\theta}{2} \n", + "\\end{bmatrix}\n", + "$$\n", + "\n", + "$U_3$ 旋转门在效果上是等价于以下组合旋转门的,\n", + "\n", + "$$\n", + "U_3(\\theta, \\phi, \\varphi) \n", + "= R_z(\\phi)*R_y(\\theta)*R_z(\\varphi)\n", + ":=\n", + "\\begin{bmatrix}\n", + "e^{-i\\frac{\\phi}{2}} & 0 \\\\ \n", + "0 & e^{i\\frac{\\phi}{2}}\n", + "\\end{bmatrix}\n", + "\\begin{bmatrix}\n", + "\\cos \\frac{\\theta}{2} &-\\sin \\frac{\\theta}{2} \\\\ \n", + "\\sin \\frac{\\theta}{2} &\\cos \\frac{\\theta}{2} \n", + "\\end{bmatrix}\n", + "\\begin{bmatrix}\n", + "e^{-i\\frac{\\varphi}{2}} & 0 \\\\ \n", + "0 & e^{i\\frac{\\varphi}{2}}\n", + "\\end{bmatrix}\n", + "$$\n", + "\n", + "感兴趣的读者不妨自行验证一下。\n", + "\n", + "\n", + "\n", + "特别地,当我们处理的任务不涉及虚数时,使用电路模板 `real_entangled_layer(theta, DEPTH)` 会更加高效 ($R_y$旋转门替代$U_3$)。" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "N = 4 # 设置量子比特数\n", + "DEPTH = 6 # 设置量子电路深度\n", + "theta = np.random.randn(DEPTH, N, 3)\n", + "\n", + "# 启动 Paddle 动态图模式\n", + "with fluid.dygraph.guard():\n", + " \n", + " # 我们需要将 Numpy array 转换成 Paddle 动态图模式中支持的 variable\n", + " theta = fluid.dygraph.to_variable(theta)\n", + " \n", + " # 初始化量子电路\n", + " cir = UAnsatz(N)\n", + " \n", + " # 添加深度为 D = 6 的复数强纠缠结构QNN {Rz+Ry+Rz/U3 + CNOT's}\n", + " cir.complex_entangled_layer(theta, DEPTH)\n", + " \n", + " # 制备输出态\n", + " # 如果用户不输入初始量子态,默认初始为|00..0>\n", + " final_state = cir.run_state_vector()\n", + " \n", + " # 测量输出态的[0,1,2]号量子比特2048次,统计测量结果的频率\n", + " cir.measure(shots = 2048, which_qubits = [0, 1, 2], plot = True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "

[回到 目录]

\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 量桨的运行模式说明" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 波函数向量模式 \n", + "\n", + "所谓的波函数模式也就是用复数向量表示和储存量子态。向量模式只能处理纯态,但这种模式在家用电脑硬件高效支持**20+量子比特**的运算。用户可以测试下自己电脑的极限在哪里。在这种表示下,量子门 (酉矩阵)作用在量子比特 (一个复向量)上本质上的运算是**矩阵乘以向量**:\n", + "\n", + "$$\\lvert {\\psi}\\rangle = U \\lvert {\\psi_0}\\rangle$$\n", + "\n", + "代码中,具体体现在 UAnsatz的调用 `cir.run_state_vector(input_state = None)`。如果我们不输入任何初始量子态,就会默认所有的量子比特都处于$\\lvert {0}\\rangle$态。接着来看个具体的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 0.00042441+0.j 0.00029816+0.j 0.00019942+0.j ... 0.00039183+0.j\n", + " -0.00052556+0.j 0.00061023+0.j]]\n" + ] + } + ], + "source": [ + "from paddle_quantum.state import vec, vec_random\n", + "\n", + "N = 20 # 设置量子比特数\n", + "DEPTH = 6 # 设置量子电路深度\n", + "theta = np.random.randn(DEPTH, N, 1)\n", + "\n", + "# 调用内置的 |00..0> 初始态\n", + "initial_state1 = vec(N)\n", + "# 调用内置的随机量子态 |psi>\n", + "initial_state2 = vec_random(N)\n", + "\n", + "# 启动 Paddle 动态图模式\n", + "with fluid.dygraph.guard():\n", + " \n", + " # 我们需要将 Numpy array 转换成 Paddle 动态图模式中支持的 variable\n", + " theta = fluid.dygraph.to_variable(theta)\n", + " initial_state = fluid.dygraph.to_variable(initial_state1)\n", + " \n", + " # 初始化量子电路\n", + " cir = UAnsatz(N)\n", + " \n", + " # 添加深度为 Depth 的实数强纠缠结构QNN {Ry+CNOT's}\n", + " cir.real_entangled_layer(theta, DEPTH)\n", + " \n", + " # 制备输出态\n", + " # 如果用户不输入初始量子态,默认初始为|00..0>\n", + " final_state = cir.run_state_vector(initial_state)\n", + " print(final_state.numpy())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 密度矩阵模式 \n", + "\n", + "同时 Paddle quantum也支持了密度矩阵运算模式,也就是用一个密度矩阵 $\\rho = \\sum_i P_i \\lvert {\\psi_i}\\rangle\\langle{\\psi_i} \\lvert$表示和储存量子态。该模式下可以根据算法需要支持**混合态模拟**。但是在密度矩阵模式下,家用电脑硬件只能运行10个左右的量子比特。请用户注意这方面的限制,我们也在不断优化这个模式下的模拟器性能。在这种表示下,量子门 (酉矩阵)作用在量子态(一个迹为1的厄尔米特矩阵)上本质上的运算是**矩阵乘法**:\n", + "\n", + "$$\\rho = U \\rho_0 U^\\dagger$$\n", + "\n", + "代码中,具体体现在 UAnsatz的调用 `cir.run_density_matrix()`。接着来看个具体的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 0.11259972+0.j -0.28258005+0.j 0.03785432+0.j -0.13651589+0.j]\n", + " [-0.28258005+0.j 0.70916239+0.j -0.09499913+0.j 0.34260002+0.j]\n", + " [ 0.03785432+0.j -0.09499913+0.j 0.01272605+0.j -0.04589457+0.j]\n", + " [-0.13651589+0.j 0.34260002+0.j -0.04589457+0.j 0.16551185+0.j]]\n" + ] + } + ], + "source": [ + "from paddle_quantum.state import density_op, density_op_random, completely_mixed_computational\n", + "\n", + "N = 2 # 设置量子比特数\n", + "DEPTH = 6 # 设置量子电路深度\n", + "theta = np.random.randn(DEPTH, N, 1)\n", + "\n", + "# 调用内置的 |00..0><00..0| 初始态\n", + "initial_state1 = density_op(N)\n", + "# 调用内置的随机量子态, 可以指定是否允许复数元素和矩阵秩 \n", + "initial_state2 = density_op_random(N, real_or_complex=2, rank=4)\n", + "# 调用内置的计算基下的完全混合态 \n", + "initial_state3 = completely_mixed_computational(N)\n", + "\n", + "# 启动 Paddle 动态图模式\n", + "with fluid.dygraph.guard():\n", + " \n", + " # 我们需要将 Numpy array 转换成 Paddle 动态图模式中支持的 variable\n", + " theta = fluid.dygraph.to_variable(theta)\n", + " initial_state = fluid.dygraph.to_variable(initial_state1)\n", + " \n", + " # 初始化量子电路\n", + " cir = UAnsatz(N)\n", + " \n", + " # 添加深度为 Depth 的实数强纠缠结构QNN {Ry+CNOT's}\n", + " cir.real_entangled_layer(theta, DEPTH)\n", + " \n", + " # 制备输出态\n", + " # 如果用户不输入初始量子态,默认初始为|00..0><00..0|\n", + " final_state = cir.run_density_matrix(initial_state)\n", + " print(final_state.numpy())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果你对源代码有兴趣,可以利用如下 magic command即时查看。或者关注我们的 [[Github]](https://github.com/PaddlePaddle/Quantum)!" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "%psource density_op_random" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 练习:如何从计算基制备贝尔态\n", + "\n", + "贝尔态是一种很常用的量子纠缠态, 可以表示为\n", + "\n", + "$$\n", + "\\lvert {\\Phi^+}\\rangle = \\frac{1}{\\sqrt{2}} \\big(\\lvert {00}\\rangle + \\lvert {11}\\rangle\\big)\n", + "= \\frac{1}{\\sqrt{2}} \\,\n", + "\\begin{bmatrix}\n", + "1 \\\\\n", + "0 \\\\\n", + "0 \\\\\n", + "1\n", + "\\end{bmatrix}\n", + "$$\n", + "\n", + "那么我们如何用量桨来制备一个贝尔态呢? 只需要如下的量子电路:\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "我们制备出的贝尔态是:\n", + "[0. +0.j 0.70710678+0.j 0.70710678+0.j 0. +0.j]\n" + ] + } + ], + "source": [ + "# 启动 Paddle 动态图模式\n", + "with fluid.dygraph.guard():\n", + " \n", + " # 初始化量子电路\n", + " cir = UAnsatz(2)\n", + "\n", + " # 添加量子门\n", + " cir.x(1)\n", + " cir.h(0)\n", + " cir.cnot([0, 1]) \n", + " \n", + " # 制备输出态\n", + " # 如果用户不输入初始量子态,默认初始为|00..0>\n", + " output_state = cir.run_state_vector()\n", + " \n", + " # 我们测量输出态2048次,获得测量结果频率分布\n", + " # 如果用户想获取概率分布的理论值,可以令 shots = 0\n", + " cir.measure(shots = 2048, plot = True)\n", + " \n", + " print('我们制备出的贝尔态是:')\n", + " print(output_state.numpy())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "

[回到 目录]

\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 飞桨 Paddlepaddle 优化器使用教程" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 示例: 利用飞桨的梯度下降来优化多元函数\n", + "\n", + "\n", + "在这一节,我们学习如何用飞桨动态图机制找到一个多元函数的极小值\n", + "\n", + "$$\n", + "\\min_{\\boldsymbol{\\theta}}\\mathcal{L}(\\theta_1, \\theta_2, \\theta_3)\n", + "= (\\theta_1)^2 + (\\theta_2)^2 + (\\theta_3)^2 + 10\n", + "$$\n", + "\n", + "可以看出,只有当 $\\theta_1 = \\theta_2 = \\theta_3 = 0$ 的时候,$\\mathcal{L}$ 取最小值10。\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "损失函数的最小值是: 10.000000153748474\n" + ] + } + ], + "source": [ + "# 超参数设置\n", + "theta_size = 3 \n", + "ITR = 200 # 设置迭代次数\n", + "LR = 0.5 # 设置学习速率\n", + "SEED = 1 # 固定随机数种子\n", + "\n", + "class Optimization_ex1(fluid.dygraph.Layer):\n", + " \n", + "\n", + " def __init__(self, shape, param_attr=fluid.initializer.Uniform(low=-5., high=5., seed=SEED), dtype='float64'):\n", + " super(Optimization_ex1, self).__init__()\n", + " \n", + " # 初始化一个长度为 theta_size的可学习参数列表,并用 [-5, 5] 的均匀分布来填充初始值\n", + " self.theta = self.create_parameter(shape=shape, attr=param_attr, dtype=dtype, is_bias=False)\n", + "\n", + " # 定义损失函数和前向传播机制\n", + " def forward(self):\n", + " loss = self.theta[0] ** 2 + self.theta[1] ** 2 + self.theta[2] ** 2 + 10\n", + " return loss\n", + " \n", + "# 记录中间优化结果\n", + "loss_list = []\n", + "parameter_list = []\n", + "\n", + "# 初始化 paddle 动态图机制\n", + "with fluid.dygraph.guard():\n", + " \n", + " # 定义网络维度\n", + " myLayer = Optimization_ex1([theta_size])\n", + " \n", + " # 一般来说,我们利用Adam优化器来获得相对好的收敛,当然你可以改成SGD或者是RMSprop.\n", + " optimizer = fluid.optimizer.AdagradOptimizer(learning_rate = LR, parameter_list = myLayer.parameters()) \n", + " \n", + " # 优化循环\n", + " for itr in range(ITR):\n", + " \n", + " # 向前传播计算损失函数\n", + " loss = myLayer()[0]\n", + " \n", + " # 在动态图机制下,反向传播优化损失函数\n", + " loss.backward()\n", + " optimizer.minimize(loss)\n", + " myLayer.clear_gradients()\n", + " \n", + " # 记录学习曲线\n", + " loss_list.append(loss.numpy()[0])\n", + " parameter_list.append(myLayer.parameters()[0].numpy())\n", + " \n", + " print('损失函数的最小值是: ', loss_list[-1])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### 练习: 特征值寻找\n", + "\n", + "接下来,我们试一个更复杂的损失函数。 首先我们介绍一个随机的埃尔米特矩阵 $H$ 其**特征值**为矩阵 $D$ 的对角元素。 \n", + "\n", + "$$ D = \\begin{bmatrix} 0.2 &0 \\\\ 0 &0.8 \\end{bmatrix} $$\n", + "\n", + "不用担心,我们会帮你生成这个埃尔米特矩阵$H$. \n", + "\n", + "然后我们初始化参数向量$\\boldsymbol{\\theta}$,构造出一个简单的线性运算 $U(\\boldsymbol{\\theta}) = R_z(\\theta_1)*R_y(\\theta_2)*R_z(\\theta_3)$\n", + "\n", + "\n", + "$$ \n", + "U(\\theta_1, \\theta_2, \\theta_3) = \n", + "\\begin{bmatrix}\n", + "e^{-i\\frac{\\theta_1}{2}} & 0 \\\\ \n", + "0 & e^{i\\frac{\\theta_1}{2}}\n", + "\\end{bmatrix}\n", + "\\begin{bmatrix}\n", + "\\cos \\frac{\\theta_2}{2} &-\\sin \\frac{\\theta_2}{2} \\\\ \n", + "\\sin \\frac{\\theta_2}{2} &\\cos \\frac{\\theta_2}{2} \n", + "\\end{bmatrix}\n", + "\\begin{bmatrix}\n", + "e^{-i\\frac{\\theta_3}{2}} & 0 \\\\ \n", + "0 & e^{i\\frac{\\theta_3}{2}}\n", + "\\end{bmatrix}\n", + "$$\n", + "\n", + "我们让这个矩阵(模板)乘以 $\\lvert {0}\\rangle$,得到一个新的2维复向量$\\lvert {\\phi}\\rangle$\n", + "\n", + "\n", + "$$ \n", + "\\lvert {\\phi}\\rangle = U(\\theta_1, \\theta_2, \\theta_3)\\lvert {0}\\rangle\n", + "$$\n", + "\n", + "然后,我们定义损失函数为:\n", + "\n", + "$$\n", + "\\min_{\\boldsymbol{\\theta}}\\mathcal{L}(\\theta_1, \\theta_2, \\theta_3) \n", + "= \\langle{\\phi} \\lvert H \\lvert {\\phi}\\rangle \n", + "= \\langle{0} \\lvert U^{\\dagger}H U \\lvert {0}\\rangle\n", + "$$\n", + "\n", + "来看看优化后我们得到了什么!" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "随机生成的矩阵 H 是:\n", + "[[ 0.73879472-2.77555756e-17j -0.12692858+1.29870005e-01j]\n", + " [-0.12692858-1.29870005e-01j 0.26120528+0.00000000e+00j]] \n", + "\n", + "不出所料,H 的特征值是:\n", + "[0.2 0.8]\n" + ] + } + ], + "source": [ + "from scipy.stats import unitary_group\n", + "\n", + "# V 是一个 2x2 的随机酉矩阵\n", + "V = unitary_group.rvs(2)\n", + "\n", + "# D 的对角元是H的特征值\n", + "# 你可以任意改变这里的对角元数值\n", + "D = np.diag([0.2, 0.8])\n", + "\n", + "# V_dagger 是 V 的埃尔米特转置\n", + "V_dagger = V.conj().T\n", + "\n", + "# @:代表矩阵乘积运算\n", + "H = (V @ D @ V_dagger)\n", + "print('随机生成的矩阵 H 是:')\n", + "print(H, '\\n')\n", + "print('不出所料,H 的特征值是:')\n", + "print(np.linalg.eigh(H)[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "# 超参数设置\n", + "theta_size = 3 # 设置 theta 维度\n", + "num_qubits = 1 # 设置量子比特数\n", + "ITR = 10 # 设置迭代次数\n", + "LR = 0.8 # 设置学习速率\n", + "SEED = 1 # 固定theta参数的随机数种子\n", + "\n", + "\n", + "# 单独设置电路模块\n", + "def U_theta(theta):\n", + " \n", + " # 初始化电路然后添加量子门\n", + " cir = UAnsatz(num_qubits)\n", + " cir.rz(theta[0], 0)\n", + " cir.ry(theta[1], 0)\n", + " cir.rz(theta[2], 0)\n", + " \n", + " # 返回参数化矩阵\n", + " return cir.U" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "class Optimization_ex2(fluid.dygraph.Layer):\n", + " \n", + "\n", + " def __init__(self, shape, param_attr=fluid.initializer.Uniform(low=0., high=2*np.pi, seed=SEED), dtype='float64'):\n", + " super(Optimization_ex2, self).__init__()\n", + " \n", + " # 初始化一个长度为 theta_size的可学习参数列表,并用 [0, 2*pi] 的均匀分布来填充初始值\n", + " self.theta = self.create_parameter(shape=shape, attr=param_attr, dtype=dtype, is_bias=False)\n", + " self.H = fluid.dygraph.to_variable(H)\n", + " \n", + " # 定义损失函数和前向传播机制\n", + " def forward(self):\n", + " \n", + " # 获取量子神经网络的酉矩阵表示\n", + " U = U_theta(self.theta)\n", + " \n", + " # 埃尔米特转置运算\n", + " U_dagger = hermitian(U)\n", + " \n", + " # 计算损失函数函数\n", + " loss = matmul(U_dagger, matmul(self.H, U)).real[0][0]\n", + " \n", + " return loss" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "iter: 0 loss: 0.7911\n", + "iter: 1 loss: 0.5878\n", + "iter: 2 loss: 0.2913\n", + "iter: 3 loss: 0.2244\n", + "iter: 4 loss: 0.2142\n", + "iter: 5 loss: 0.2071\n", + "iter: 6 loss: 0.2031\n", + "iter: 7 loss: 0.2013\n", + "iter: 8 loss: 0.2005\n", + "iter: 9 loss: 0.2002\n", + "损失函数的最小值是: 0.20019311438922205\n" + ] + } + ], + "source": [ + "loss_list = []\n", + "parameter_list = []\n", + "\n", + "\n", + "# 初始化paddle动态图机制\n", + "with fluid.dygraph.guard():\n", + " \n", + " myLayer = Optimization_ex2([theta_size])\n", + " \n", + " # 一般来说,我们利用Adam优化器来获得相对好的收敛,当然你可以改成SGD或者是RMS prop.\n", + " optimizer = fluid.optimizer.AdagradOptimizer(learning_rate = LR, parameter_list = myLayer.parameters()) \n", + " \n", + " # 优化循环\n", + " for itr in range(ITR):\n", + " \n", + " # 前向传播计算损失函数\n", + " loss = myLayer()[0]\n", + " \n", + " # 在动态图机制下,反向传播极小化损失函数\n", + " loss.backward()\n", + " optimizer.minimize(loss)\n", + " myLayer.clear_gradients()\n", + " \n", + " # 记录学习曲线\n", + " loss_list.append(loss.numpy()[0])\n", + " parameter_list.append(myLayer.parameters()[0].numpy())\n", + " print('iter:', itr, ' loss: %.4f' % loss.numpy())\n", + " \n", + " print('损失函数的最小值是: ', loss_list[-1])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们可以改变一下$H$的特征值。如果将它对角化后的的对角矩阵改变为\n", + "\n", + "$$ D = \\begin{bmatrix} 0.8 &0 \\\\ 0 &1.2 \\end{bmatrix} $$\n", + "\n", + "你会发现我们仍然得到了$H$的最小特征值0.8, 你能找到背后的原因吗?还是说这背后隐藏着什么理论?\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "

[回到 目录]

\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 量子机器学习案例" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 变分量子特征求解器 (VQE) -- 无监督学习\n", + "\n", + "正如大名鼎鼎的John Preskill 所言,目前阶段理想的可容错的量子计算机还是遥遥无期。我们目前只能造出有噪音的,中等规模量子计算系统(NISQ)。现在一个利用 NISQ 的量子设备很有希望的算法种类就是量子-经典混合算法。人们期望这套方法也许可以在某些应用中超越经典计算机的表现。变分量子特征求解器(VQE)就是里面的一个杰出代表。它利用参数化的电路搜寻广阔的希尔伯特空间,并利用经典机器学习中的梯度下降来找到最优参数,并接近一个哈密顿量的基态(也就是找到一个埃尔米特矩阵的最小特征值)。这个电路其实你之前见过。为了确保你能理解, 我们来一起过一遍以下两量子比特 (2-qubit)的例子。\n", + "\n", + "假设我们想找到如下哈密顿量的基态:\n", + "\n", + "$$ H = 0.4 \\, Z \\otimes I + 0.4 \\, I \\otimes Z + 0.2 \\, X \\otimes X$$\n", + "\n", + "给定一种常见的量子神经网络架构" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们已经学会如何建造这个电路了。如果你忘了, 请转到 这里。 有关VQE的更多信息, 请参见 [[博客]](https://www.mustythoughts.com/post/variational-quantum-eigensolver-explained)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "from paddle_quantum.utils import pauli_str_to_matrix\n", + "\n", + "# 首先生成泡利字符串表示下的哈密顿量\n", + "# 相当于0.4*kron(I, Z) + 0.4*kron(Z, I) + 0.2*kron(X, X)\n", + "# 其中, X,Y, Z是泡利矩阵, I是单位矩阵\n", + "H_info = [[0.4, 'z0'], [0.4, 'z1'], [0.2, 'x0,x1']]\n", + "\n", + "# 超参数设置\n", + "num_qubits = 2\n", + "theta_size = 4\n", + "ITR = 10\n", + "LR = 0.5\n", + "SEED = 1 \n", + "\n", + "# 把记录的关于哈密顿量的信息转化为矩阵表示\n", + "H_matrix = pauli_str_to_matrix(H_info, num_qubits)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "class vqe_demo(fluid.dygraph.Layer):\n", + " \n", + "\n", + " def __init__(self, shape, param_attr=fluid.initializer.Uniform(low=0., high=2*np.pi, seed=SEED), dtype='float64'):\n", + " super(vqe_demo, self).__init__()\n", + " \n", + " # 初始化一个长度为theta_size的可学习参数列表,并用[0, 2*pi]的均匀分布来填充初始值\n", + " self.theta = self.create_parameter(shape=shape, attr=param_attr, dtype=dtype, is_bias=False)\n", + " self.H = fluid.dygraph.to_variable(H)\n", + " \n", + " # 定义损失函数和前向传播机制\n", + " def forward(self):\n", + " \n", + " # 初始量子电路\n", + " cir = UAnsatz(num_qubits)\n", + " \n", + " # 添加量子门\n", + " cir.ry(self.theta[0], 0)\n", + " cir.ry(self.theta[1], 1)\n", + " cir.cnot([0, 1])\n", + " cir.ry(self.theta[2], 0)\n", + " cir.ry(self.theta[3], 1)\n", + " \n", + " # 选择用量子态的向量表示\n", + " cir.run_state_vector()\n", + " \n", + " # 计算当前量子态下关于观测量H_info的期望值\n", + " # 也就是 \n", + " loss = cir.expecval(H_info)\n", + " \n", + " return loss" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "colab_type": "code", + "id": "S9fO_sGR64LV", + "outputId": "f1be9cac-5d5e-4944-c13d-32983628fa6c" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "iter: 0 loss: 0.3195\n", + "iter: 1 loss: -0.3615\n", + "iter: 2 loss: -0.6528\n", + "iter: 3 loss: -0.7420\n", + "iter: 4 loss: -0.7813\n", + "iter: 5 loss: -0.8022\n", + "iter: 6 loss: -0.8130\n", + "iter: 7 loss: -0.8187\n", + "iter: 8 loss: -0.8216\n", + "iter: 9 loss: -0.8231\n", + "计算得到的基态能量是: -0.8230721264692568\n", + "真实的基态能量为: -0.8246211251235321\n" + ] + } + ], + "source": [ + "loss_list = []\n", + "parameter_list = []\n", + "\n", + "\n", + "# 初始化paddle动态图机制\n", + "with fluid.dygraph.guard():\n", + " \n", + " # 定义网络维度\n", + " vqe = vqe_demo([theta_size])\n", + " \n", + " # 一般来说,我们利用Adam优化器来获得相对好的收敛,当然你可以改成SGD或者是RMS prop.\n", + " optimizer = fluid.optimizer.AdagradOptimizer(learning_rate = LR, parameter_list = vqe.parameters()) \n", + " \n", + " # 优化循环\n", + " for itr in range(ITR):\n", + " \n", + " # 前向传播计算损失函数\n", + " loss = vqe()\n", + " \n", + " # 在动态图机制下,反向传播极小化损失函数\n", + " loss.backward()\n", + " optimizer.minimize(loss)\n", + " vqe.clear_gradients()\n", + " \n", + " # 记录学习曲线\n", + " loss_list.append(loss.numpy()[0])\n", + " parameter_list.append(vqe.parameters()[0].numpy())\n", + " print('iter:', itr, ' loss: %.4f' % loss.numpy())\n", + " \n", + " \n", + " print('计算得到的基态能量是: ', loss_list[-1])\n", + " print('真实的基态能量为: ', np.linalg.eigh(H_matrix)[0][0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "

[回到 目录]

\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 参考文献\n", + "\n", + "[1] [Peruzzo, A. et al. A variational eigenvalue solver on a photonic quantum processor. Nat. Commun. 5, 4213 (2014).](https://www.nature.com/articles/ncomms5213)\n", + "\n", + "[2] [McClean, J. R., Romero, J., Babbush, R. & Aspuru-Guzik, A. The theory of variational hybrid quantum-classical algorithms. New J. Phys. 18, 023023 (2016).](https://iopscience.iop.org/article/10.1088/1367-2630/18/2/023023)\n", + "\n", + "[3] [Kandala, A. et al. Hardware-efficient variational quantum eigensolver for small molecules and quantum magnets. Nature 549, 242–246 (2017).](https://www.nature.com/articles/nature23879)\n", + "\n", + "[4] [Mitarai, K., Negoro, M., Kitagawa, M. & Fujii, K. Quantum circuit learning. Phys. Rev. A 98, 032309 (2018).](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.98.032309)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "

[回到 目录]

\n", + "\n", + "
" + ] + } + ], + "metadata": { + "colab": { + "name": "QSD-Paddle.ipynb", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/introduction/PaddleQuantum_Tutorial_CN.pdf b/introduction/PaddleQuantum_Tutorial_CN.pdf new file mode 100644 index 0000000000000000000000000000000000000000..35cf2fd51e3c0d5b1bc57be79d26f6a015fc888b Binary files /dev/null and b/introduction/PaddleQuantum_Tutorial_CN.pdf differ diff --git a/introduction/figures/ansatz1.png b/introduction/figures/ansatz1.png new file mode 100644 index 0000000000000000000000000000000000000000..1ff434832e2d5b51ee92b90518d9a2596be0b7d8 Binary files /dev/null and b/introduction/figures/ansatz1.png differ diff --git a/introduction/figures/bell2.png b/introduction/figures/bell2.png new file mode 100644 index 0000000000000000000000000000000000000000..c8a4af2ad8a383d094d9fa100a765b31c004ac41 Binary files /dev/null and b/introduction/figures/bell2.png differ diff --git a/introduction/figures/bloch.png b/introduction/figures/bloch.png new file mode 100644 index 0000000000000000000000000000000000000000..ad80072c8ce7755074c46537c65d38bb01c6310b Binary files /dev/null and b/introduction/figures/bloch.png differ diff --git a/introduction/figures/complex_entangled_layer.png b/introduction/figures/complex_entangled_layer.png new file mode 100644 index 0000000000000000000000000000000000000000..e08f1216b810fe4f2dad36f331c0288f8cfe82ad Binary files /dev/null and b/introduction/figures/complex_entangled_layer.png differ diff --git a/introduction/figures/gate.png b/introduction/figures/gate.png new file mode 100644 index 0000000000000000000000000000000000000000..fc053da7ec89e000dc8c7fdc616302abe7bf4a5e Binary files /dev/null and b/introduction/figures/gate.png differ diff --git a/introduction/figures/gate1.png b/introduction/figures/gate1.png new file mode 100644 index 0000000000000000000000000000000000000000..b3c792e6bb7022a84c0c4edef197dd13282e5d91 Binary files /dev/null and b/introduction/figures/gate1.png differ diff --git a/introduction/figures/gate2.png b/introduction/figures/gate2.png new file mode 100644 index 0000000000000000000000000000000000000000..b4782c3582846ba06c1d3b86365066150b3f9f67 Binary files /dev/null and b/introduction/figures/gate2.png differ diff --git a/introduction/figures/hardmad.png b/introduction/figures/hardmad.png new file mode 100644 index 0000000000000000000000000000000000000000..239cd9e57aeae5730342892d051cd0e18f10963a Binary files /dev/null and b/introduction/figures/hardmad.png differ diff --git a/introduction/figures/ssvqeAnsatz.png b/introduction/figures/ssvqeAnsatz.png new file mode 100644 index 0000000000000000000000000000000000000000..3cb0c59952919582f4cfb282639da2c8abb589bb Binary files /dev/null and b/introduction/figures/ssvqeAnsatz.png differ diff --git a/introduction/figures/terminal.png b/introduction/figures/terminal.png new file mode 100644 index 0000000000000000000000000000000000000000..0d23d06fec2a32ecbf8f3e10f5fe66911a9547fb Binary files /dev/null and b/introduction/figures/terminal.png differ diff --git a/introduction/figures/vqeAnsatz.png b/introduction/figures/vqeAnsatz.png new file mode 100644 index 0000000000000000000000000000000000000000..7f5793b9fb93049e8f9c7d872e7343ff65260e0a Binary files /dev/null and b/introduction/figures/vqeAnsatz.png differ diff --git a/introduction/figures/vqsdAnsatz.png b/introduction/figures/vqsdAnsatz.png new file mode 100644 index 0000000000000000000000000000000000000000..d2bf96525b9d68b21c91380d0159699a632480b3 Binary files /dev/null and b/introduction/figures/vqsdAnsatz.png differ diff --git a/paddle_quantum/GIBBS/HGenerator.py b/paddle_quantum/GIBBS/HGenerator.py index d307792f3226b2ee799616296d3a73977d7368c7..2e59dd664a4a1c18e3174a17640e9cddec1a5ab7 100644 --- a/paddle_quantum/GIBBS/HGenerator.py +++ b/paddle_quantum/GIBBS/HGenerator.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,8 +16,9 @@ HGenerator """ -from numpy import array, kron, trace import scipy +from numpy import trace as np_trace +from paddle_quantum.utils import pauli_str_to_matrix __all__ = ["H_generator", ] @@ -27,16 +28,18 @@ def H_generator(): Generate a Hamiltonian with trivial descriptions Returns: A Hamiltonian """ + # 生成用泡利字符串表示的特定的哈密顿量 + H = [[-1.0, 'z0,z1'], [-1.0, 'z1,z2'], [-1.0, 'z0,z2']] - beta = 1 - sigma_I = array([[1, 0], [0, 1]]) - sigma_Z = array([[1, 0], [0, -1]]) + # 生成哈密顿量的矩阵信息 + N_SYS_B = 3 # 用于生成吉布斯态的子系统B的量子比特数 + hamiltonian = pauli_str_to_matrix(H, N_SYS_B) - H = (-kron(kron(sigma_Z, sigma_Z), sigma_I) - kron( - kron(sigma_I, sigma_Z), sigma_Z) - kron( - kron(sigma_Z, sigma_I), sigma_Z)) + # 生成理想情况下的目标吉布斯态 rho + beta = 1.5 # 设置逆温度参数 beta + rho_G = scipy.linalg.expm(-1 * beta * hamiltonian) / np_trace(scipy.linalg.expm(-1 * beta * hamiltonian)) - rho = scipy.linalg.expm(-1 * beta * - H) / trace(scipy.linalg.expm(-1 * beta * H)) - - return H.astype("complex64"), rho.astype("complex64") + # 设置成 Paddle quantum 所支持的数据类型 + hamiltonian = hamiltonian.astype("complex128") + rho_G = rho_G.astype("complex128") + return hamiltonian, rho_G \ No newline at end of file diff --git a/paddle_quantum/GIBBS/Paddle_GIBBS.py b/paddle_quantum/GIBBS/Paddle_GIBBS.py index 6caef0879df52bca9c06c77611410ef5f220a70e..dbd54a68cc588b969359cbcdd1cf1b104b11fc55 100644 --- a/paddle_quantum/GIBBS/Paddle_GIBBS.py +++ b/paddle_quantum/GIBBS/Paddle_GIBBS.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,17 +16,18 @@ Paddle_GIBBS """ -from numpy import concatenate, zeros +import scipy + from numpy import pi as PI from paddle import fluid -from paddle.complex import matmul, transpose, trace +from paddle.complex import matmul, trace from paddle_quantum.circuit import UAnsatz -from paddle_quantum.utils import compute_fid, partial_trace - +from paddle_quantum.state import density_op +from paddle_quantum.utils import state_fidelity, partial_trace from paddle_quantum.GIBBS.HGenerator import H_generator -SEED = 1 +SEED = 14 # 固定随机种子 __all__ = [ "U_theta", @@ -35,29 +36,25 @@ __all__ = [ ] -def U_theta(theta, input_state, N, D): # definition of U_theta +def U_theta(initial_state, theta, N, D): """ - :param theta: - :param input_state: - :return: + Quantum Neural Network """ - cir = UAnsatz(N, input_state=input_state) - for i in range(N): - cir.rx(theta=theta[0][0][i], which_qubit=i + 1) - cir.ry(theta=theta[0][1][i], which_qubit=i + 1) - cir.rx(theta=theta[0][2][i], which_qubit=i + 1) + # 按照量子比特数量/网络宽度初始化量子神经网络 + cir = UAnsatz(N) + + # 内置的 {R_y + CNOT} 电路模板 + cir.real_entangled_layer(theta[:D], D) - for repeat in range(D): - for i in range(1, N): - cir.cnot(control=[i, i + 1]) + # 铺上最后一列 R_y 旋转门 + for i in range(N): + cir.ry(theta=theta[D][i][0], which_qubit=i) - for i in range(N): - cir.ry(theta=theta[repeat][0][i], which_qubit=i + 1) - # cir.ry(theta=theta[repeat][1][i], which_qubit=i + 1) - # cir.ry(theta=theta[repeat][2][i], which_qubit=i + 1) + # 量子神经网络作用在给定的初始态上 + final_state = cir.run_density_matrix(initial_state) - return cir.state + return final_state class Net(fluid.dygraph.Layer): @@ -65,96 +62,83 @@ class Net(fluid.dygraph.Layer): Construct the model net """ - def __init__(self, - shape, - param_attr=fluid.initializer.Uniform( - low=0.0, high=PI, seed=SEED), - dtype='float32'): + def __init__(self, N, shape, param_attr=fluid.initializer.Uniform(low=0.0, high=2 * PI, seed=SEED), + dtype='float64'): super(Net, self).__init__() - self.theta = self.create_parameter( - shape=shape, attr=param_attr, dtype=dtype, is_bias=False) - - def forward(self, input_state, H, N, N_SYS_B, D): - """ - Args: - input_state: The initial state with default |0..> - H: The target Hamiltonian - Returns: - The loss. - """ - - out_state = U_theta(self.theta, input_state, N, D) - - # rho_AB = utils.matmul(utils.matrix_conjugate_transpose(out_state), out_state) - rho_AB = matmul( - transpose( - fluid.framework.ComplexVariable(out_state.real, - -out_state.imag), - perm=[1, 0]), - out_state) - - # compute the partial trace and three losses - rho_B = partial_trace(rho_AB, 2**(N - N_SYS_B), 2**(N_SYS_B), 1) + # 初始化 theta 参数列表,并用 [0, 2*pi] 的均匀分布来填充初始值 + self.theta = self.create_parameter(shape=shape, attr=param_attr, dtype=dtype, is_bias=False) + + # 初始化 rho = |0..0><0..0| 的密度矩阵 + self.initial_state = fluid.dygraph.to_variable(density_op(N)) + + # 定义损失函数和前向传播机制 + def forward(self, H, N, N_SYS_B, beta, D): + # 施加量子神经网络 + rho_AB = U_theta(self.initial_state, self.theta, N, D) + + # 计算偏迹 partial trace 来获得子系统B所处的量子态 rho_B + rho_B = partial_trace(rho_AB, 2 ** (N - N_SYS_B), 2 ** (N_SYS_B), 1) + + # 计算三个子损失函数 rho_B_squre = matmul(rho_B, rho_B) loss1 = (trace(matmul(rho_B, H))).real - loss2 = (trace(rho_B_squre)).real * 2 - loss3 = -(trace(matmul(rho_B_squre, rho_B))).real / 2 + loss2 = (trace(rho_B_squre)).real * 2 / beta + loss3 = - ((trace(matmul(rho_B_squre, rho_B))).real + 3) / (2 * beta) - loss = loss1 + loss2 + loss3 # 损失函数 + # 最终的损失函数 + loss = loss1 + loss2 + loss3 - # option: if you want to check whether the imaginary part is 0, uncomment the following - # print('loss_iminary_part: ', loss.numpy()[1]) - return loss - 3 / 2, rho_B + return loss, rho_B -def Paddle_GIBBS(hamiltonian, rho=None, N=5, N_SYS_B=3, D=1, ITR=100, LR=0.5): - """ +def Paddle_GIBBS(hamiltonian, rho_G, N=4, N_SYS_B=3, beta=1.5, D=1, ITR=50, LR=0.5): + r""" Paddle_GIBBS + :param hamiltonian: 哈密顿量 + :param rho_G: 目标吉布斯态 rho + :param N: 量子神经网络的宽度 + :param N_SYS_B: 用于生成吉布斯态的子系统B的量子比特数 + :param D: 设置量子神经网络中重复计算模块的深度 Depth + :param ITR: 设置训练的总迭代次数 + :param LR: 设置学习速率 + :return: todo """ - + # 初始化paddle动态图机制 with fluid.dygraph.guard(): - # initial state preparing - _initial_state_np = concatenate( - ([[1.]], zeros([1, 2**N - 1])), axis=1).astype('complex64') - initial_state = fluid.dygraph.to_variable(_initial_state_np) - - # gibbs Hamiltonian preparing + # 我们需要将 Numpy array 转换成 Paddle 动态图模式中支持的 variable H = fluid.dygraph.to_variable(hamiltonian) - # net - net = Net(shape=[D + 1, 3, N]) + # 确定网络的参数维度 + net = Net(N, shape=[D + 1, N, 1]) - # optimizer - opt = fluid.optimizer.AdamOptimizer( - learning_rate=LR, parameter_list=net.parameters()) + # 一般来说,我们利用Adam优化器来获得相对好的收敛,当然你可以改成SGD或者是RMS prop. + opt = fluid.optimizer.AdamOptimizer(learning_rate=LR, parameter_list=net.parameters()) - # gradient descent loop + # 优化循环 for itr in range(1, ITR + 1): - loss, rho_B = net(initial_state, H, N, N_SYS_B, D) + # 前向传播计算损失函数并返回生成的量子态 rho_B + loss, rho_B = net(H, N, N_SYS_B, beta, D) + # 在动态图机制下,反向传播极小化损失函数 loss.backward() opt.minimize(loss) net.clear_gradients() + # 转换成 Numpy array 用以计算量子态的保真度 F(rho_B, rho_G) rho_B = rho_B.numpy() + fid = state_fidelity(rho_B, rho_G) - if rho is not None: - fid = compute_fid(rho_B, rho) - print('iter:', itr, 'loss:', '%.4f' % loss.numpy(), 'fid:', - '%.4f' % fid) - + # 打印训练结果 + if itr % 5 == 0: + print('iter:', itr, 'loss:', '%.4f' % loss.numpy(), 'fid:', '%.4f' % fid) return rho_B def main(): - """ - main - """ - # gibbs Hamiltonian preparing - hamiltonian, rho = H_generator() - rho_B = Paddle_GIBBS(hamiltonian, rho) + hamiltonian, rho_G = H_generator() + rho_B = Paddle_GIBBS(hamiltonian, rho_G) print(rho_B) diff --git a/paddle_quantum/GIBBS/example/main.py b/paddle_quantum/GIBBS/example/main.py index e747251cc9d866ff665db82947851941600532bc..cd297827989bbae985bcf6b3a0ef9e55cda6e688 100644 --- a/paddle_quantum/GIBBS/example/main.py +++ b/paddle_quantum/GIBBS/example/main.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,13 +16,29 @@ main """ -from paddle_quantum.GIBBS.HGenerator import H_generator +import scipy +from numpy import trace as np_trace +from paddle_quantum.utils import pauli_str_to_matrix from paddle_quantum.GIBBS.Paddle_GIBBS import Paddle_GIBBS def main(): - hamiltonian, rho = H_generator() - rho_B = Paddle_GIBBS(hamiltonian, rho) + # 生成用泡利字符串表示的特定的哈密顿量 + H = [[-1.0, 'z0,z1'], [-1.0, 'z1,z2'], [-1.0, 'z0,z2']] + + # 生成哈密顿量的矩阵信息 + N_SYS_B = 3 # 用于生成吉布斯态的子系统B的量子比特数 + hamiltonian = pauli_str_to_matrix(H, N_SYS_B) + + # 生成理想情况下的目标吉布斯态 rho + beta = 1.5 # 设置逆温度参数 beta + rho_G = scipy.linalg.expm(-1 * beta * hamiltonian) / np_trace(scipy.linalg.expm(-1 * beta * hamiltonian)) + + # 设置成 Paddle quantum 所支持的数据类型 + hamiltonian = hamiltonian.astype("complex128") + rho_G = rho_G.astype("complex128") + + rho_B = Paddle_GIBBS(hamiltonian, rho_G) print(rho_B) diff --git a/paddle_quantum/GIBBS/main.py b/paddle_quantum/GIBBS/main.py deleted file mode 100644 index 7d264250e9a29176cda619517c0cf59fcdd84a62..0000000000000000000000000000000000000000 --- a/paddle_quantum/GIBBS/main.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -main -""" - -from paddle_quantum.GIBBS.HGenerator import H_generator -from paddle_quantum.GIBBS.Paddle_GIBBS import Paddle_GIBBS - - -def main(): - # gibbs Hamiltonian preparing - hamiltonian, rho = H_generator() - rho_B = Paddle_GIBBS(hamiltonian, rho) - print(rho_B) - - -if __name__ == '__main__': - main() diff --git a/paddle_quantum/QAOA/Paddle_QAOA.py b/paddle_quantum/QAOA/Paddle_QAOA.py index bc7577589a9cd87aec5fa7e9d561e8aef2a78b17..547e33bb1566e731d013e0e9130ca4bf6dff8efa 100644 --- a/paddle_quantum/QAOA/Paddle_QAOA.py +++ b/paddle_quantum/QAOA/Paddle_QAOA.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,19 +17,19 @@ Paddle_QAOA: To learn more about the functions and properties of this applicatio you could check the corresponding Jupyter notebook under the Tutorial folder. """ -import os from paddle import fluid -from paddle.complex import matmul as pp_matmul -from paddle.complex import transpose + +import os +import numpy as np +import matplotlib.pyplot as plt +import networkx as nx + from paddle_quantum.circuit import UAnsatz +from paddle_quantum.utils import pauli_str_to_matrix from paddle_quantum.QAOA.QAOA_Prefunc import generate_graph, H_generator -from numpy import ones, abs, conjugate, real, savez, sqrt, zeros -from numpy import matmul as np_matmul -from numpy import pi as PI - # Random seed for optimizer -SEED = 1 +SEED = 1024 __all__ = [ "circuit_QAOA", @@ -39,48 +39,46 @@ __all__ = [ ] -def circuit_QAOA(theta, input_state, adjacency_matrix, N, P): +def circuit_QAOA(theta, adjacency_matrix, N, P): """ This function constructs the parameterized QAOA circuit which is composed of P layers of two blocks: - one block is U_theta[layer][0] based on the problem Hamiltonian H which encodes the classical problem, - and the other is U_theta[layer][1] constructed from the driving Hamiltonian describing the rotation around Pauli X - acting on each qubit. It finally outputs the final state of the QAOA circuit. + one block is based on the problem Hamiltonian H which encodes the classical problem, + and the other is constructed from the driving Hamiltonian describing the rotation around Pauli X + acting on each qubit. It outputs the final state of the QAOA circuit. Args: - theta: parameters to be optimized in the QAOA circuit - input_state: initial state of the QAOA circuit which usually is the uniform superposition of 2^N bit-strings - in the computational basis $|0\rangle, |1\rangle$ - adjacency_matrix: the adjacency matrix of the graph encoding the classical problem - N: number of qubits, or equivalently, the number of nodes in the given graph - P: number of layers of two blocks in the QAOA circuit + theta: parameters to be optimized in the QAOA circuit + adjacency_matrix: the adjacency matrix of the graph encoding the classical problem + N: number of qubits, or equivalently, the number of parameters in the original classical problem + P: number of layers of two blocks in the QAOA circuit Returns: - the final state of the QAOA circuit: cir.state - + the QAOA circuit """ - cir = UAnsatz(N, input_state=input_state) - # The first loop defines the QAOA circuit with P layers of two blocks + cir = UAnsatz(N) + + # prepare the input state in the uniform superposition of 2^N bit-strings in the computational basis + cir.superposition_layer() + # This loop defines the QAOA circuit with P layers of two blocks for layer in range(P): - # The second and third loops aim to construct the first block U_theta[layer][0] which involves - # two-qubit operation e^{-i\beta Z_iZ_j} acting on a pair of qubits or nodes i and j in the circuit. + # The second and third loops construct the first block which involves two-qubit operation + # e^{-i\gamma Z_iZ_j} acting on a pair of qubits or nodes i and j in the circuit in each layer. for row in range(N): for col in range(N): - if abs(adjacency_matrix[row, col]) and row < col: - cir.cnot([row + 1, col + 1]) - cir.rz( - theta=theta[layer][0] * adjacency_matrix[row, col], - which_qubit=col + 1, ) - cir.cnot([row + 1, col + 1]) - # This loops constructs the second block U_theta only involving the single-qubit operation e^{-i\beta X}. - for i in range(1, N + 1): - cir.rx(theta=theta[layer][1], which_qubit=i) + if adjacency_matrix[row, col] and row < col: + cir.cnot([row, col]) + cir.rz(theta[layer][0], col) + cir.cnot([row, col]) + # This loop constructs the second block only involving the single-qubit operation e^{-i\beta X}. + for i in range(N): + cir.rx(theta[layer][1], i) - return cir.state + return cir -def circuit_extend_QAOA(theta, input_state, adjacency_matrix, N, P): +def circuit_extend_QAOA(theta, adjacency_matrix, N, P): """ - This is an extended version of the QAOA circuit, and the main difference is U_theta[layer]([1]-[3]) constructed + This is an extended version of the QAOA circuit, and the main difference is the block constructed from the driving Hamiltonian describing the rotation around an arbitrary direction on each qubit. Args: @@ -91,35 +89,29 @@ def circuit_extend_QAOA(theta, input_state, adjacency_matrix, N, P): N: number of qubits, or equivalently, the number of parameters in the original classical problem P: number of layers of two blocks in the QAOA circuit Returns: - final state of the QAOA circuit: cir.state + the extended QAOA circuit - Note: If this U_extend_theta function is used to construct QAOA circuit, then we need to change the parameter layer - in the Net function defined below from the Net(shape=[D, 2]) for U_theta function to Net(shape=[D, 4]) - because the number of parameters doubles in each layer in this QAOA circuit. + Note: + If this circuit_extend_QAOA function is used to construct QAOA circuit, then we need to change the parameter layer + in the Net function defined below from the Net(shape=[D, 2]) for circuit_QAOA function to Net(shape=[D, 4]) + because the number of parameters doubles in each layer in this QAOA circuit. """ + cir = UAnsatz(N) - cir = UAnsatz(N, input_state=input_state) - - # The first loop defines the QAOA circuit with P layers of two blocks + # prepare the input state in the uniform superposition of 2^N bit-strings in the computational basis + cir.superposition_layer() for layer in range(P): - # The second and third loops aim to construct the first block U_theta[layer][0] which involves - # two-qubit operation e^{-i\beta Z_iZ_j} acting on a pair of qubits or nodes i and j in the circuit. for row in range(N): for col in range(N): - if abs(adjacency_matrix[row, col]) and row < col: - cir.cnot([row + 1, col + 1]) - cir.rz( - theta=theta[layer][0] * adjacency_matrix[row, col], - which_qubit=col + 1, ) - cir.cnot([row + 1, col + 1]) - # This loops constructs the second block U_theta[layer][1]-[3] composed of three single-qubit operation - # e^{-i\beta[1] Z}e^{-i\beta[2] X}e^{-i\beta[3] X} sequentially acting on single qubits. - for i in range(1, N + 1): - cir.rz(theta=theta[layer][1], which_qubit=i) - cir.rx(theta=theta[layer][2], which_qubit=i) - cir.rz(theta=theta[layer][3], which_qubit=i) - - return cir.state + if adjacency_matrix[row, col] and row < col: + cir.cnot([row, col]) + cir.rz(theta[layer][0], col) + cir.cnot([row, col]) + + for i in range(N): + cir.u3(*theta[layer][1:], i) + + return cir class Net(fluid.dygraph.Layer): @@ -132,72 +124,48 @@ class Net(fluid.dygraph.Layer): def __init__( self, shape, - param_attr=fluid.initializer.Uniform( - low=0.0, high=PI, seed=SEED), - dtype="float32", ): + param_attr=fluid.initializer.Uniform(low=0.0, high=np.pi, seed=SEED), + dtype="float64", + ): super(Net, self).__init__() self.theta = self.create_parameter( - shape=shape, attr=param_attr, dtype=dtype, is_bias=False) + shape=shape, attr=param_attr, dtype=dtype, is_bias=False + ) - def forward(self, input_state, adjacency_matrix, out_state_store, N, P, - METHOD): + def forward(self, adjacency_matrix, N, P, METHOD): """ This function constructs the loss function for the QAOA circuit. Args: - self: the free parameters to be optimized in the QAOA circuit and defined in the above function - input_state: initial state of the QAOA circuit which usually is the uniform superposition of 2^N bit-strings - in the computational basis $|0\rangle, |1\rangle$ adjacency_matrix: the adjacency matrix generated from the graph encoding the classical problem - out_state_store: the output state of the QAOA circuit N: number of qubits P: number of layers METHOD: which version of QAOA is chosen to solve the problem, i.e., standard version labeled by 1 or extended version by 2. Returns: - The loss function for the parameterized QAOA circuit. + the loss function for the parameterized QAOA circuit and the circuit itself """ # Generate the problem_based quantum Hamiltonian H_problem based on the classical problem in paddle - H, _ = H_generator(N, adjacency_matrix) - H_problem = fluid.dygraph.to_variable(H) + H_problem = H_generator(N, adjacency_matrix) # The standard QAOA circuit: the function circuit_QAOA is used to construct the circuit, indexed by METHOD 1. if METHOD == 1: - out_state = circuit_QAOA(self.theta, input_state, adjacency_matrix, - N, P) + cir = circuit_QAOA(self.theta, adjacency_matrix, N, P) # The extended QAOA circuit: the function circuit_extend_QAOA is used to construct the net, indexed by METHOD 2. elif METHOD == 2: - out_state = circuit_extend_QAOA(self.theta, input_state, - adjacency_matrix, N, P) + cir = circuit_extend_QAOA(self.theta, adjacency_matrix, N, P) else: raise ValueError("Wrong method called!") - out_state_store.append(out_state.numpy()) - loss = pp_matmul( - pp_matmul(out_state, H_problem), - transpose( - fluid.framework.ComplexVariable(out_state.real, - -out_state.imag), - perm=[1, 0], ), ) - - return loss.real - - -def main(N=4): - """ - This is the main function which maps the classical problem to the quantum version solved by QAOA and outputs - the quantum solution and its corresponding classical ones. Here, N=4 is a 4-qubit example to show how QAOA works. + cir.run_state_vector() + loss = cir.expecval(H_problem) - """ - # Generate the adjacency matrix from the description of the problem-based graph - _, classical_graph_adjacency = generate_graph(N, 1) - Paddle_QAOA(classical_graph_adjacency) + return loss, cir -def Paddle_QAOA(classical_graph_adjacency, N=4, P=4, METHOD=1, ITR=120, - LR=0.1): +def Paddle_QAOA(classical_graph_adjacency, N, P, METHOD, ITR, LR): """ This is the core function to run QAOA. @@ -209,15 +177,9 @@ def Paddle_QAOA(classical_graph_adjacency, N=4, P=4, METHOD=1, ITR=120, ITR: number of iteration steps for QAOA (default value ITR=120) LR: learning rate for the gradient-based optimization method (default value LR=0.1) Returns: - optimized parameters theta and the bitstrings sampled from the output state with maximal probability + the optimized QAOA circuit """ - - out_state_store = [] with fluid.dygraph.guard(): - # Preparing the initial state - _initial_state = ones([1, 2**N]).astype("complex64") / sqrt(2**N) - initial_state = fluid.dygraph.to_variable(_initial_state) - # Construct the net or QAOA circuits based on the standard modules if METHOD == 1: net = Net(shape=[P, 2]) @@ -228,41 +190,109 @@ def Paddle_QAOA(classical_graph_adjacency, N=4, P=4, METHOD=1, ITR=120, raise ValueError("Wrong method called!") # Classical optimizer - opt = fluid.optimizer.AdamOptimizer( - learning_rate=LR, parameter_list=net.parameters()) + opt = fluid.optimizer.AdamOptimizer(learning_rate=LR, parameter_list=net.parameters()) # Gradient descent loop summary_iter, summary_loss = [], [] for itr in range(1, ITR + 1): - loss = net(initial_state, classical_graph_adjacency, - out_state_store, N, P, METHOD) + loss, cir = net( + classical_graph_adjacency, N, P, METHOD + ) loss.backward() opt.minimize(loss) net.clear_gradients() - print("iter:", itr, " loss:", "%.4f" % loss.numpy()) + if itr % 10 == 0: + print("iter:", itr, " loss:", "%.4f" % loss.numpy()) summary_loss.append(loss[0][0].numpy()) summary_iter.append(itr) theta_opt = net.parameters()[0].numpy() - print(theta_opt) + print("Optmized parameters theta:\n", theta_opt) os.makedirs("output", exist_ok=True) - savez("./output/summary_data", iter=summary_iter, energy=summary_loss) + np.savez("./output/summary_data", iter=summary_iter, energy=summary_loss) + + return cir + + +def main(N=4): + # number of qubits or number of nodes in the graph + N = 4 + classical_graph, classical_graph_adjacency = generate_graph(N, GRAPHMETHOD=1) + print(classical_graph_adjacency) + + # Convert the Hamiltonian's list form to matrix form + H_matrix = pauli_str_to_matrix(H_generator(N, classical_graph_adjacency), N) - # Output the measurement probability distribution sampled from the output state of optimized QAOA circuit. - prob_measure = zeros([1, 2**N]).astype("complex") + H_diag = np.diag(H_matrix).real + H_max = np.max(H_diag) + H_min = np.min(H_diag) - rho_out = out_state_store[-1] - rho_out = np_matmul(conjugate(rho_out).T, rho_out).astype("complex") + print(H_diag) + print('H_max:', H_max, ' H_min:', H_min) - for index in range(0, 2**N): - comput_basis = zeros([1, 2**N]) - comput_basis[0][index] = 1 - prob_measure[0][index] = real( - np_matmul(np_matmul(comput_basis, rho_out), comput_basis.T)) + pos = nx.circular_layout(classical_graph) + nx.draw(classical_graph, pos, width=4, with_labels=True, font_weight='bold') + plt.show() - return prob_measure + classical_graph, classical_graph_adjacency = generate_graph(N, 1) + + opt_cir = Paddle_QAOA(classical_graph_adjacency, N=4, P=4, METHOD=1, ITR=120, LR=0.1) + + # Load the data of QAOA + x1 = np.load('./output/summary_data.npz') + + H_min = np.ones([len(x1['iter'])]) * H_min + + # Plot loss + loss_QAOA, = plt.plot(x1['iter'], x1['energy'], alpha=0.7, marker='', linestyle="--", linewidth=2, color='m') + benchmark, = plt.plot(x1['iter'], H_min, alpha=0.7, marker='', linestyle=":", linewidth=2, color='b') + plt.xlabel('Number of iteration') + plt.ylabel('Performance of the loss function for QAOA') + + plt.legend(handles=[ + loss_QAOA, + benchmark + ], + labels=[ + r'Loss function $\left\langle {\psi \left( {\bf{\theta }} \right)} ' + r'\right|H\left| {\psi \left( {\bf{\theta }} \right)} \right\rangle $', + 'The benchmark result', + ], loc='best') + + # Show the plot + plt.show() + + with fluid.dygraph.guard(): + # Measure the output state of the QAOA circuit for 1024 shots by default + prob_measure = opt_cir.measure(plot=True) + + # Find the max value in measured probability of bitstrings + max_prob = max(prob_measure.values()) + # Find the bitstring with max probability + solution_list = [result[0] for result in prob_measure.items() if result[1] == max_prob] + print("The output bitstring:", solution_list) + + # Draw the graph representing the first bitstring in the solution_list to the MaxCut-like problem + head_bitstring = solution_list[0] + + node_cut = ["blue" if head_bitstring[node] == "1" else "red" for node in classical_graph] + + edge_cut = [ + "solid" if head_bitstring[node_row] == head_bitstring[node_col] else "dashed" + for node_row, node_col in classical_graph.edges() + ] + nx.draw( + classical_graph, + pos, + node_color=node_cut, + style=edge_cut, + width=4, + with_labels=True, + font_weight="bold", + ) + plt.show() if __name__ == "__main__": diff --git a/paddle_quantum/QAOA/QAOA_Prefunc.py b/paddle_quantum/QAOA/QAOA_Prefunc.py index 7b8eafeb2d0dfd716d61122ba075e6521f16af6b..4ce750a0467b252a38ecf10d65761fa78fe8d404 100644 --- a/paddle_quantum/QAOA/QAOA_Prefunc.py +++ b/paddle_quantum/QAOA/QAOA_Prefunc.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,21 +16,15 @@ Aid func """ from matplotlib import pyplot +import numpy as np from numpy import abs, array, binary_repr, diag, kron, max, ones, real, where, zeros import networkx -__all__ = [ - "plot_graph", - "generate_graph", - "H_generator", -] - def plot_graph(measure_prob_distribution, graph, N): """ This function plots the graph encoding the combinatorial problem such as Max-Cut and the final graph encoding the approximate solution obtained from QAOA - Args: measure_prob_distribution: the measurement probability distribution which is sampled from the output state of optimized QAOA circuit. @@ -93,68 +87,63 @@ def plot_graph(measure_prob_distribution, graph, N): def generate_graph(N, GRAPHMETHOD): """ - This function offers two methods to generate a graph. + It plots an N-node graph which is specified by Method 1 or 2. Args: - N: number of nodes (vertices) in the graph, which is also the number of qubits - GRAPHMETHOD: which method to generate a graph - Return: - graph description and its adjacency matrix + N: number of nodes (vertices) in the graph + METHOD: choose which method to generate a graph + Returns: + the specific graph and its adjacency matrix """ - # Method 1 generates a graph by self-definition. Note that the node label starts from 0 to N-1, while the edges - # could be attributed to weights additionally. If no specific rules are given, then all weights are set to 1. + # Method 1 generates a graph by self-definition if GRAPHMETHOD == 1: - print( - "Method 1 generates the graph from self-definition using EDGE description" - ) + print("Method 1 generates the graph from self-definition using EDGE description") graph = networkx.Graph() graph_nodelist = range(N) graph.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 0)]) - graph_adjacency = networkx.to_numpy_matrix( - graph, nodelist=graph_nodelist) + graph_adjacency = networkx.to_numpy_matrix(graph, nodelist=graph_nodelist) # Method 2 generates a graph by using its adjacency matrix directly elif GRAPHMETHOD == 2: - print( - "Method 2 generates the graph from networks using adjacency matrix") - graph_adjacency = array( - [[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 1, 0]]) + print("Method 2 generates the graph from networks using adjacency matrix") + graph_adjacency = np.array([[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 1, 0]]) graph = networkx.Graph(graph_adjacency) else: print("Method doesn't exist ") - output_graph = graph - output_graph_adjacency = graph_adjacency - - return output_graph, output_graph_adjacency + return graph, graph_adjacency def H_generator(N, adjacency_matrix): """ - This function generates the problem-based Hamiltonian, given the graph with its adjacency matrix description. + This function maps the given graph via its adjacency matrix to the corresponding Hamiltiona H_c. Args: N: number of qubits, or number of nodes in the graph, or number of parameters in the classical problem adjacency_matrix: the adjacency matrix generated from the graph encoding the classical problem - Return: - H_graph: the problem-based Hamiltonian H generated from the graph_adjacency matrix for the given graph - H_graph_diag: the real part of the problem-based Hamiltonian H_graph + Returns: + the problem-based Hmiltonian H's list form generated from the graph_adjacency matrix for the given graph """ - - sigma_Z = array([[1, 0], [0, -1]]) - H = zeros([2**N, 2**N]) - + H_list = [] + # Generate the Hamiltonian H_c from the graph via its adjacency matrix for row in range(N): for col in range(N): - if abs(adjacency_matrix[N - row - 1, N - col - 1]) and row < col: - identity_1 = diag(ones([2**row])) - identity_2 = diag(ones([2**(col - row - 1)])) - identity_3 = diag(ones([2**(N - col - 1)])) - H += adjacency_matrix[N - row - 1, N - col - 1] * kron( - kron( - kron(kron(identity_1, sigma_Z), identity_2), sigma_Z), - identity_3, ) - - H_graph = H.astype("complex64") - H_graph_diag = diag(H_graph).real - - return H_graph, H_graph_diag + if adjacency_matrix[row, col] and row < col: + # Construct the Hamiltonian in the list form for the calculation of expectation value + H_list.append([1.0, 'z' + str(row) + ',z' + str(col)]) + + return H_list + + +def main(): + # number of qubits or number of nodes in the graph + N = 4 + classical_graph, classical_graph_adjacency = generate_graph(N, GRAPHMETHOD=1) + print(classical_graph_adjacency) + + pos = networkx.circular_layout(classical_graph) + networkx.draw(classical_graph, pos, width=4, with_labels=True, font_weight='bold') + pyplot.show() + + +if __name__ == "__main__": + main() diff --git a/paddle_quantum/QAOA/benchmark.py b/paddle_quantum/QAOA/benchmark.py index 80a0ef87e57dec012ee809c7eac81d993712fd61..910acd05f9bcf602a4af4592a33b30c8097b43c3 100644 --- a/paddle_quantum/QAOA/benchmark.py +++ b/paddle_quantum/QAOA/benchmark.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,7 +16,8 @@ Benchmark """ from matplotlib import pyplot -from numpy import max, min, load, ones +from numpy import diag, max, min, load, ones +from paddle_quantum.utils import pauli_str_to_matrix from paddle_quantum.QAOA.QAOA_Prefunc import generate_graph, H_generator @@ -25,24 +26,25 @@ def benchmark_QAOA(classical_graph_adjacency=None, N=None): This function benchmarks the performance of QAOA. Indeed, it compares its approximate solution obtained from QAOA with predetermined parameters, such as iteration step = 120 and learning rate = 0.1, to the exact solution to the classical problem. - """ # Generate the graph and its adjacency matrix from the classical problem, such as the Max-Cut problem if all(var is None for var in (classical_graph_adjacency, N)): N = 4 _, classical_graph_adjacency = generate_graph(N, 1) + # Convert the Hamiltonian's list form to matrix form + H_matrix = pauli_str_to_matrix(H_generator(N, classical_graph_adjacency), N) + H_diag = diag(H_matrix).real # Compute the exact solution of the original problem to benchmark the performance of QAOA - _, H_problem_diag = H_generator(N, classical_graph_adjacency) + H_max = max(H_diag) + H_min = min(H_diag) - H_graph_max = max(H_problem_diag) - H_graph_min = min(H_problem_diag) - print('H_max:', H_graph_max, ' H_min:', H_graph_min) + print('H_max:', H_max, ' H_min:', H_min) # Load the data of QAOA x1 = load('./output/summary_data.npz') - H_min = ones([len(x1['iter'])]) * H_graph_min + H_min = ones([len(x1['iter'])]) * H_min # Plot it pyplot.figure(1) diff --git a/paddle_quantum/QAOA/example/main.py b/paddle_quantum/QAOA/example/main.py index cb040773214fec5beb735fb427ba38eb961213a8..f70f6c1aeecb37a118fbcfe765ce6106f98b5a2d 100644 --- a/paddle_quantum/QAOA/example/main.py +++ b/paddle_quantum/QAOA/example/main.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,27 +16,96 @@ main """ -from paddle_quantum.QAOA.Paddle_QAOA import Paddle_QAOA -from paddle_quantum.QAOA.QAOA_Prefunc import plot_graph, generate_graph +from paddle import fluid + +import os +import numpy as np +import matplotlib.pyplot as plt +import networkx as nx -# Random seed for optimizer -SEED = 1 +from paddle_quantum.utils import pauli_str_to_matrix +from paddle_quantum.QAOA.Paddle_QAOA import Paddle_QAOA +from paddle_quantum.QAOA.QAOA_Prefunc import generate_graph, H_generator def main(N=4): - """ - QAOA Main - """ + # number of qubits or number of nodes in the graph + N = 4 + classical_graph, classical_graph_adjacency = generate_graph(N, GRAPHMETHOD=1) + print(classical_graph_adjacency) + + # Convert the Hamiltonian's list form to matrix form + H_matrix = pauli_str_to_matrix(H_generator(N, classical_graph_adjacency), N) + + H_diag = np.diag(H_matrix).real + H_max = np.max(H_diag) + H_min = np.min(H_diag) + + print(H_diag) + print('H_max:', H_max, ' H_min:', H_min) + + pos = nx.circular_layout(classical_graph) + nx.draw(classical_graph, pos, width=4, with_labels=True, font_weight='bold') + plt.show() classical_graph, classical_graph_adjacency = generate_graph(N, 1) - print(classical_graph_adjacency) - prob_measure = Paddle_QAOA(classical_graph_adjacency) - # Flatten array[[]] to [] - prob_measure = prob_measure.flatten() - # Plot it! - plot_graph(prob_measure, classical_graph, N) + opt_cir = Paddle_QAOA(classical_graph_adjacency, N=4, P=4, METHOD=1, ITR=120, LR=0.1) + + # Load the data of QAOA + x1 = np.load('./output/summary_data.npz') + + H_min = np.ones([len(x1['iter'])]) * H_min + + # Plot loss + loss_QAOA, = plt.plot(x1['iter'], x1['energy'], alpha=0.7, marker='', linestyle="--", linewidth=2, color='m') + benchmark, = plt.plot(x1['iter'], H_min, alpha=0.7, marker='', linestyle=":", linewidth=2, color='b') + plt.xlabel('Number of iteration') + plt.ylabel('Performance of the loss function for QAOA') + + plt.legend(handles=[ + loss_QAOA, + benchmark + ], + labels=[ + r'Loss function $\left\langle {\psi \left( {\bf{\theta }} \right)} ' + r'\right|H\left| {\psi \left( {\bf{\theta }} \right)} \right\rangle $', + 'The benchmark result', + ], loc='best') + + # Show the plot + plt.show() + + with fluid.dygraph.guard(): + # Measure the output state of the QAOA circuit for 1024 shots by default + prob_measure = opt_cir.measure(plot=True) + + # Find the max value in measured probability of bitstrings + max_prob = max(prob_measure.values()) + # Find the bitstring with max probability + solution_list = [result[0] for result in prob_measure.items() if result[1] == max_prob] + print("The output bitstring:", solution_list) + + # Draw the graph representing the first bitstring in the solution_list to the MaxCut-like problem + head_bitstring = solution_list[0] + + node_cut = ["blue" if head_bitstring[node] == "1" else "red" for node in classical_graph] + + edge_cut = [ + "solid" if head_bitstring[node_row] == head_bitstring[node_col] else "dashed" + for node_row, node_col in classical_graph.edges() + ] + nx.draw( + classical_graph, + pos, + node_color=node_cut, + style=edge_cut, + width=4, + with_labels=True, + font_weight="bold", + ) + plt.show() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/paddle_quantum/QAOA/main.py b/paddle_quantum/QAOA/main.py deleted file mode 100644 index cb040773214fec5beb735fb427ba38eb961213a8..0000000000000000000000000000000000000000 --- a/paddle_quantum/QAOA/main.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -main -""" - -from paddle_quantum.QAOA.Paddle_QAOA import Paddle_QAOA -from paddle_quantum.QAOA.QAOA_Prefunc import plot_graph, generate_graph - -# Random seed for optimizer -SEED = 1 - - -def main(N=4): - """ - QAOA Main - """ - - classical_graph, classical_graph_adjacency = generate_graph(N, 1) - print(classical_graph_adjacency) - prob_measure = Paddle_QAOA(classical_graph_adjacency) - - # Flatten array[[]] to [] - prob_measure = prob_measure.flatten() - # Plot it! - plot_graph(prob_measure, classical_graph, N) - - -if __name__ == '__main__': - main() diff --git a/paddle_quantum/SSVQE/HGenerator.py b/paddle_quantum/SSVQE/HGenerator.py index 9e21a1cffce132f1a21a029d09780fc9b560611a..167b1be5f1b3193d66a7f5334818beb2bf968a44 100644 --- a/paddle_quantum/SSVQE/HGenerator.py +++ b/paddle_quantum/SSVQE/HGenerator.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,22 +16,20 @@ HGenerator """ -from numpy import array, kron +from paddle_quantum.utils import random_pauli_str_generator, pauli_str_to_matrix __all__ = ["H_generator"] -def H_generator(): +def H_generator(N): """ Generate a Hamiltonian with trivial descriptions Returns: A Hamiltonian - """ - sigma_I = array([[1, 0], [0, 1]]) - sigma_X = array([[0, 1], [1, 0]]) - sigma_Y = array([[0, -1j], [1j, 0]]) - sigma_Z = array([[1, 0], [0, -1]]) - H = 0.4 * kron(sigma_Z, sigma_I) + 0.4 * kron( - sigma_I, sigma_Z) + 0.2 * kron(sigma_X, sigma_X) - # H = numpy.diag([0.1, 0.2, 0.3, 0.4]) - return H.astype('complex64') + # 生成用泡利字符串表示的随机哈密顿量 + hamiltonian = random_pauli_str_generator(N, terms=10) + print("Random Hamiltonian in Pauli string format = \n", hamiltonian) + + # 生成哈密顿量的矩阵信息 + H = pauli_str_to_matrix(hamiltonian, N) + return H diff --git a/paddle_quantum/SSVQE/Paddle_SSVQE.py b/paddle_quantum/SSVQE/Paddle_SSVQE.py index 6de16e505bca64166faeb8ac0e8bd25db5ac1378..471b9d8c9c8d4a1b9e6a40ea6de098917ae35fd6 100644 --- a/paddle_quantum/SSVQE/Paddle_SSVQE.py +++ b/paddle_quantum/SSVQE/Paddle_SSVQE.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,12 +16,16 @@ Paddle_SSVQE: To learn more about the functions and properties of this application, you could check the corresponding Jupyter notebook under the Tutorial folder. """ + import numpy -from paddle.complex import matmul, transpose + +from paddle.complex import matmul from paddle import fluid from paddle_quantum.circuit import UAnsatz +from paddle_quantum.utils import hermitian +from paddle_quantum.SSVQE.HGenerator import H_generator -SEED = 1 +SEED = 14 # 固定随机种子 __all__ = [ "U_theta", @@ -30,35 +34,19 @@ __all__ = [ ] -# definition of U_theta def U_theta(theta, N): """ - U_theta + Quantum Neural Network """ + # 按照量子比特数量/网络宽度初始化量子神经网络 cir = UAnsatz(N) - # ============== D1=2 ============== - cir.ry(theta[0], 2) - cir.rz(theta[1], 2) - cir.cnot([2, 1]) - cir.ry(theta[2], 2) - cir.rz(theta[3], 2) - cir.cnot([2, 1]) - - # ============== D2=2 ============== - cir.ry(theta[4], 1) - cir.ry(theta[5], 2) - cir.rz(theta[6], 1) - cir.rz(theta[7], 2) - cir.cnot([1, 2]) - - cir.ry(theta[8], 1) - cir.ry(theta[9], 2) - cir.rz(theta[10], 1) - cir.rz(theta[11], 2) - cir.cnot([1, 2]) - - return cir.state + + # 调用内置的量子神经网络模板 + cir.universal_2_qubit_gate(theta) + + # 返回量子神经网络所模拟的酉矩阵 U + return cir.U class Net(fluid.dygraph.Layer): @@ -66,68 +54,91 @@ class Net(fluid.dygraph.Layer): Construct the model net """ - def __init__(self, - shape, - param_attr=fluid.initializer.Uniform( - low=0.0, high=2 * numpy.pi, seed=SEED), - dtype='float32'): + def __init__(self, shape, param_attr=fluid.initializer.Uniform(low=0.0, high=2 * numpy.pi, seed=SEED), + dtype='float64'): super(Net, self).__init__() - self.theta = self.create_parameter( - shape=shape, attr=param_attr, dtype=dtype, is_bias=False) + # 初始化 theta 参数列表,并用 [0, 2*pi] 的均匀分布来填充初始值 + self.theta = self.create_parameter(shape=shape, attr=param_attr, dtype=dtype, is_bias=False) + # 定义损失函数和前向传播机制 def forward(self, H, N): - """ - Args: - input_state: The initial state with default |0..> - H: The target Hamiltonian - Returns: - The loss. - """ - out_state = U_theta(self.theta, N) - - loss_struct = matmul( - matmul( - transpose( - fluid.framework.ComplexVariable(out_state.real, - -out_state.imag), - perm=[1, 0]), - H), - out_state).real + # 施加量子神经网络 + U = U_theta(self.theta, N) + # 计算损失函数 + loss_struct = matmul(matmul(hermitian(U), H), U).real + + # 输入计算基去计算每个子期望值,相当于取 U^dagger*H*U 的对角元 loss_components = [ - loss_struct[0][0], loss_struct[1][1], loss_struct[2][2], + loss_struct[0][0], + loss_struct[1][1], + loss_struct[2][2], loss_struct[3][3] ] - loss = 4 * loss_components[0] + 3 * loss_components[ - 1] + 2 * loss_components[2] + 1 * loss_components[3] + # 最终加权求和后的损失函数 + loss = 4 * loss_components[0] + 3 * loss_components[1] + 2 * loss_components[2] + 1 * loss_components[3] + return loss, loss_components -def Paddle_SSVQE(H, N=2, THETA_SIZE=12, ITR=60, LR=0.2): +def Paddle_SSVQE(H, N=2, THETA_SIZE=15, ITR=50, LR=0.3): + r""" + Paddle_SSVQE + :param H: 哈密顿量 + :param N: 量子比特数/量子神经网络的宽度 + :param THETA_SIZE: 量子神经网络中参数的数量 + :param ITR: 设置训练的总迭代次数 + :param LR: 设置学习速率 + :return: 哈密顿量的前几个最小特征值 """ - main - """ + # 初始化paddle动态图机制 with fluid.dygraph.guard(): - # Harmiltonian preparing - H = fluid.dygraph.to_variable(H) + # 我们需要将 Numpy array 转换成 Paddle 动态图模式中支持的 variable + hamiltonian = fluid.dygraph.to_variable(H) - # net + # 确定网络的参数维度 net = Net(shape=[THETA_SIZE]) - # optimizer - opt = fluid.optimizer.AdagradOptimizer( - learning_rate=LR, parameter_list=net.parameters()) + # 一般来说,我们利用Adam优化器来获得相对好的收敛,当然你可以改成SGD或者是RMS prop. + opt = fluid.optimizer.AdagradOptimizer(learning_rate=LR, parameter_list=net.parameters()) - # gradient descent loop + # 优化循环 for itr in range(1, ITR + 1): - loss, loss_components = net(H, N) + # 前向传播计算损失函数并返回估计的能谱 + loss, loss_components = net(hamiltonian, N) + + # 在动态图机制下,反向传播极小化损失函数 loss.backward() opt.minimize(loss) net.clear_gradients() - print('iter:', itr, 'loss:', '%.4f' % loss.numpy()[0]) + # 打印训练结果 + if itr % 10 == 0: + print('iter:', itr, 'loss:', '%.4f' % loss.numpy()[0]) + return loss_components + + +def main(): + N = 2 + H = H_generator(N) + + loss_components = Paddle_SSVQE(H) + + print('The estimated ground state energy is: ', loss_components[0].numpy()) + print('The theoretical ground state energy: ', numpy.linalg.eigh(H)[0][0]) + + print('The estimated 1st excited state energy is: ', loss_components[1].numpy()) + print('The theoretical 1st excited state energy: ', numpy.linalg.eigh(H)[0][1]) + + print('The estimated 2nd excited state energy is: ', loss_components[2].numpy()) + print('The theoretical 2nd excited state energy: ', numpy.linalg.eigh(H)[0][2]) + + print('The estimated 3rd excited state energy is: ', loss_components[3].numpy()) + print('The theoretical 3rd excited state energy: ', numpy.linalg.eigh(H)[0][3]) + - return loss_components +if __name__ == '__main__': + main() diff --git a/paddle_quantum/SSVQE/example/main.py b/paddle_quantum/SSVQE/example/main.py index 62c81c77491024e3d2c94f73955242daa76693aa..57bf6db43fd884fadfcab4a40dce26ec0534a267 100644 --- a/paddle_quantum/SSVQE/example/main.py +++ b/paddle_quantum/SSVQE/example/main.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,32 +17,28 @@ main """ import numpy + from paddle_quantum.SSVQE.HGenerator import H_generator from paddle_quantum.SSVQE.Paddle_SSVQE import Paddle_SSVQE def main(): - """ - main - """ - hamiltonian = H_generator() - loss_components = Paddle_SSVQE(hamiltonian) + N = 2 + H = H_generator(N) + + loss_components = Paddle_SSVQE(H) print('The estimated ground state energy is: ', loss_components[0].numpy()) - print('The estimated 1st excited state energy is: ', - loss_components[1].numpy()) - print('The estimated 2nd excited state energy is: ', - loss_components[2].numpy()) - print('The estimated 3rd excited state energy is: ', - loss_components[3].numpy()) - print('The theoretical ground state energy: ', - numpy.linalg.eigh(hamiltonian)[0][0]) - print('The theoretical 1st excited state energy: ', - numpy.linalg.eigh(hamiltonian)[0][1]) - print('The theoretical 2nd excited state energy: ', - numpy.linalg.eigh(hamiltonian)[0][2]) - print('The theoretical 3rd excited state energy: ', - numpy.linalg.eigh(hamiltonian)[0][3]) + print('The theoretical ground state energy: ', numpy.linalg.eigh(H)[0][0]) + + print('The estimated 1st excited state energy is: ', loss_components[1].numpy()) + print('The theoretical 1st excited state energy: ', numpy.linalg.eigh(H)[0][1]) + + print('The estimated 2nd excited state energy is: ', loss_components[2].numpy()) + print('The theoretical 2nd excited state energy: ', numpy.linalg.eigh(H)[0][2]) + + print('The estimated 3rd excited state energy is: ', loss_components[3].numpy()) + print('The theoretical 3rd excited state energy: ', numpy.linalg.eigh(H)[0][3]) if __name__ == '__main__': diff --git a/paddle_quantum/SSVQE/main.py b/paddle_quantum/SSVQE/main.py deleted file mode 100644 index 99cf6c05f12b906e7150f34f2df60b5de5be0190..0000000000000000000000000000000000000000 --- a/paddle_quantum/SSVQE/main.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -main -""" - -import numpy -from paddle_quantum.SSVQE.HGenerator import H_generator -from paddle_quantum.SSVQE.Paddle_SSVQE import Paddle_SSVQE - - -def main(): - """ - main - """ - - hamiltonian = H_generator() - loss_components = Paddle_SSVQE(hamiltonian) - - print('The estimated ground state energy is: ', loss_components[0].numpy()) - print('The estimated 1st excited state energy is: ', - loss_components[1].numpy()) - print('The estimated 2nd excited state energy is: ', - loss_components[2].numpy()) - print('The estimated 3rd excited state energy is: ', - loss_components[3].numpy()) - print('The theoretical ground state energy: ', - numpy.linalg.eigh(hamiltonian)[0][0]) - print('The theoretical 1st excited state energy: ', - numpy.linalg.eigh(hamiltonian)[0][1]) - print('The theoretical 2nd excited state energy: ', - numpy.linalg.eigh(hamiltonian)[0][2]) - print('The theoretical 3rd excited state energy: ', - numpy.linalg.eigh(hamiltonian)[0][3]) - - -if __name__ == '__main__': - main() diff --git a/paddle_quantum/VQE/Paddle_VQE.py b/paddle_quantum/VQE/Paddle_VQE.py index b4855d87581ac8b41c51b7dee37df16118706e3a..52179887af774e7ef0065d0ba39bf59dbba39458 100644 --- a/paddle_quantum/VQE/Paddle_VQE.py +++ b/paddle_quantum/VQE/Paddle_VQE.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,14 +18,15 @@ you could check the corresponding Jupyter notebook under the Tutorial folder. """ import os +import platform -from numpy import concatenate from numpy import pi as PI -from numpy import savez, zeros +from numpy import savez from paddle import fluid -from paddle.complex import matmul, transpose - from paddle_quantum.circuit import UAnsatz +from paddle_quantum.VQE.benchmark import benchmark_result +from paddle_quantum.VQE.chemistrysub import H2_generator + __all__ = [ "U_theta", @@ -34,27 +35,28 @@ __all__ = [ ] -def U_theta(theta, input_state, N, D): +def U_theta(theta, Hamiltonian, N, D): """ - Circuit + Quantum Neural Network """ - cir = UAnsatz(N, input_state=input_state) + # 按照量子比特数量/网络宽度初始化量子神经网络 + cir = UAnsatz(N) + + # 内置的 {R_y + CNOT} 电路模板 + cir.real_entangled_layer(theta[:D], D) + + # 铺上最后一列 R_y 旋转门 for i in range(N): - cir.rz(theta=theta[0][0][i], which_qubit=i + 1) - cir.ry(theta=theta[0][1][i], which_qubit=i + 1) - cir.rz(theta=theta[0][2][i], which_qubit=i + 1) + cir.ry(theta=theta[D][i][0], which_qubit=i) - for repeat in range(D): - for i in range(1, N): - cir.cnot(control=[i, i + 1]) + # 量子神经网络作用在默认的初始态 |0000>上 + cir.run_state_vector() - for i in range(N): - cir.ry(theta=theta[repeat][0][i], which_qubit=i + 1) - cir.ry(theta=theta[repeat][1][i], which_qubit=i + 1) - cir.rz(theta=theta[repeat][2][i], which_qubit=i + 1) + # 计算给定哈密顿量的期望值 + expectation_val = cir.expecval(Hamiltonian) - return cir.state + return expectation_val class StateNet(fluid.dygraph.Layer): @@ -62,85 +64,92 @@ class StateNet(fluid.dygraph.Layer): Construct the model net """ - def __init__( - self, - shape, - param_attr=fluid.initializer.Uniform( - low=0.0, high=2 * PI), - dtype="float32", ): + def __init__(self, shape, param_attr=fluid.initializer.Uniform(low=0.0, high=2 * PI), dtype="float64"): super(StateNet, self).__init__() - self.theta = self.create_parameter( - shape=shape, attr=param_attr, dtype=dtype, is_bias=False) - def forward(self, input_state, H, N, D): - """ - :param input_state: The initial state with default |0..>, 'mat' - :param H: The target Hamiltonian, 'mat' - :return: The loss, 'float' - """ + # 初始化 theta 参数列表,并用 [0, 2*pi] 的均匀分布来填充初始值 + self.theta = self.create_parameter(shape=shape, attr=param_attr, dtype=dtype, is_bias=False) - out_state = U_theta(self.theta, input_state, N, D) - loss = matmul( - matmul(out_state, H), - transpose( - fluid.framework.ComplexVariable(out_state.real, - -out_state.imag), - perm=[1, 0], ), ) + # 定义损失函数和前向传播机制 + def forward(self, Hamiltonian, N, D): + # 计算损失函数/期望值 + loss = U_theta(self.theta, Hamiltonian, N, D) - return loss.real + return loss -def Paddle_VQE(Hamiltonian, N, D=1, ITR=120, LR=0.15): +def Paddle_VQE(Hamiltonian, N, D=2, ITR=80, LR=0.2): + r""" + Main Learning network using dynamic graph + :param Hamiltonian: + :param N: + :param D: 设置量子神经网络中重复计算模块的深度 Depth + :param ITR: 设置训练的总迭代次数 + :param LR: 设置学习速率 + :return: return: Plot or No return """ - Main Learning network using dynamic graph - :return: Plot or No return - """ - with fluid.dygraph.guard(): - # initial state preparing - _initial_state_np = concatenate( - ([[1.0]], zeros([1, 2**N - 1])), axis=1).astype("complex64") - initial_state = fluid.dygraph.to_variable(_initial_state_np) - # Store H - H = fluid.dygraph.to_variable(Hamiltonian) - - # net - net = StateNet(shape=[D + 1, 3, N]) + # 初始化paddle动态图机制 + with fluid.dygraph.guard(): + # 确定网络的参数维度 + net = StateNet(shape=[D + 1, N, 1]) - # optimizer - opt = fluid.optimizer.AdamOptimizer( - learning_rate=LR, parameter_list=net.parameters()) + # 一般来说,我们利用Adam优化器来获得相对好的收敛,当然你可以改成SGD或者是RMS prop. + opt = fluid.optimizer.AdamOptimizer(learning_rate=LR, parameter_list=net.parameters()) - # gradient descent loop + # 记录优化结果 summary_iter, summary_loss = [], [] + + # 优化循环 for itr in range(1, ITR + 1): - # forward calc, loss - loss = net(initial_state, H, N, D) - # backward calculation for gradient value + # 前向传播计算损失函数 + loss = net(Hamiltonian, N, D) + + # 在动态图机制下,反向传播极小化损失函数 loss.backward() - # using gradients to update the variable theta opt.minimize(loss) - # clear gradients net.clear_gradients() - summary_loss.append(loss[0][0].numpy()) + # 更新优化结果 + summary_loss.append(loss.numpy()) summary_iter.append(itr) - print("iter:", itr, "loss:", "%.4f" % loss.numpy()) - print("iter:", itr, "Ground state energy:", - "%.4f Ha" % loss.numpy()) - # print('theta:', net.parameters()[0].numpy()) + # 打印结果 + if itr % 20 == 0: + print("iter:", itr, "loss:", "%.4f" % loss.numpy()) + print("iter:", itr, "Ground state energy:", "%.4f Ha" % loss.numpy()) + # 储存训练结果到 output 文件夹 os.makedirs("output", exist_ok=True) savez("./output/summary_data", iter=summary_iter, energy=summary_loss) def main(): - """ - :return: - """ + # Read data from built-in function or xyz file depending on OS + sysStr = platform.system() + + if sysStr == 'Windows': + # Windows does not support SCF, using H2_generator instead + print('Molecule data will be read from built-in function') + hamiltonian, N = H2_generator() + print('Read Process Finished') + + elif sysStr in ('Linux', 'Darwin'): + # for linux only + from paddle_quantum.VQE.chemistrygen import read_calc_H + # Hamiltonian and cnot module preparing, must be executed under Linux + # Read the H2 molecule data + print('Molecule data will be read from h2.xyz') + hamiltonian, N = read_calc_H(geo_fn='h2.xyz') + print('Read Process Finished') + + else: + print("Don't support this OS.") + + Paddle_VQE(hamiltonian, N) + benchmark_result() -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/paddle_quantum/VQE/benchmark.py b/paddle_quantum/VQE/benchmark.py index b3d722b3a1c4a79e21f9b454a0d1f5e093fab6c5..9bae73a959f579f676a186e4c37de9eb6cb93fe6 100644 --- a/paddle_quantum/VQE/benchmark.py +++ b/paddle_quantum/VQE/benchmark.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,24 +16,26 @@ benchmark the result """ + import platform import matplotlib.pyplot as plt import numpy +from paddle_quantum.utils import pauli_str_to_matrix from paddle_quantum.VQE.chemistrysub import H2_generator +__all__ = [ + "benchmark_result", +] -def benchmark_result(): - """ - benchmark using numpy - """ +def benchmark_result(): # Read H and calc using numpy sysStr = platform.system() if sysStr == 'Windows': # Windows does not support SCF, using H2_generator instead print('Molecule data will be read from built-in function') - _H, _, _ = H2_generator() + Hamiltonian, N = H2_generator() print('Read Process Finished') elif sysStr == 'Linux' or sysStr == 'Darwin': # for linux only @@ -41,51 +43,32 @@ def benchmark_result(): # Harmiltonian and cnot module preparing, must be executed under Linux # Read the H2 molecule data print('Molecule data will be read from h2.xyz') - _H, _, _ = read_calc_H(geo_fn='h2.xyz') + Hamiltonian, N = read_calc_H(geo_fn='h2.xyz') print('Read Process Finished') else: print("Don't support this os.") - # plot - x1 = numpy.load('./output/summary_data.npz') + result = numpy.load('./output/summary_data.npz') - eig_val, eig_state = numpy.linalg.eig(_H) - min_eig_H = numpy.min(eig_val) - min_loss = numpy.ones([len(x1['iter'])]) * min_eig_H + eig_val, eig_state = numpy.linalg.eig(pauli_str_to_matrix(Hamiltonian, N)) + min_eig_H = numpy.min(eig_val.real) + min_loss = numpy.ones([len(result['iter'])]) * min_eig_H plt.figure(1) - func1, = plt.plot( - x1['iter'], - x1['energy'], - alpha=0.7, - marker='', - linestyle="--", - color='m') - func_min, = plt.plot( - x1['iter'], min_loss, alpha=0.7, marker='', linestyle=":", color='b') + func1, = plt.plot(result['iter'], result['energy'], alpha=0.7, marker='', linestyle="-", color='r') + func_min, = plt.plot(result['iter'], min_loss, alpha=0.7, marker='', linestyle=":", color='b') plt.xlabel('Number of iteration') plt.ylabel('Energy (Ha)') - plt.legend( - handles=[func1, func_min], + plt.legend(handles=[ + func1, + func_min + ], labels=[ - r'$\left\langle {\psi \left( {\bf{\theta }} \right)} ' - r'\right|H\left| {\psi \left( {\bf{\theta }} \right)} \right\rangle $', - 'Minimum energy', - ], - loc='best') + r'$\left\langle {\psi \left( {\theta } \right)} ' + r'\right|H\left| {\psi \left( {\theta } \right)} \right\rangle $', + 'Ground-state energy', + ], loc='best') - # output the picture + # plt.savefig("vqe.png", bbox_inches='tight', dpi=300) plt.show() - - -def main(): - """ - Call the real benchmark function - """ - - benchmark_result() - - -if __name__ == '__main__': - main() diff --git a/paddle_quantum/VQE/chemistrygen.py b/paddle_quantum/VQE/chemistrygen.py index b8cfea5d8257016c539dfc38b57371988df9e665..0b3128c9ee7eb74725ffc20ff5e8ecffa432ddd2 100644 --- a/paddle_quantum/VQE/chemistrygen.py +++ b/paddle_quantum/VQE/chemistrygen.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -28,6 +28,32 @@ __all__ = [ ] +# todo +def Hamiltonian_str_convert(qubit_op): + ''' + 转换提供的哈密顿量信息成为我们熟悉的泡利字符串 + ''' + info_dic = qubit_op.terms + + def process_tuple(tup): + if len(tup) == 0: + return 'i0' + else: + res = '' + for ele in tup: + res += ele[1].lower() + res += str(ele[0]) + res += ',' + return res[:-1] + + H_info = [] + + for key, value in qubit_op.terms.items(): + H_info.append([value.real, process_tuple(key)]) + + return H_info + + def calc_H_rho_from_qubit_operator(qubit_op, n_qubits): """ Generate a Hamiltonian from QubitOperator @@ -63,8 +89,8 @@ def calc_H_rho_from_qubit_operator(qubit_op, n_qubits): rho = scipy.linalg.expm(-1 * beta * H) / trace(scipy.linalg.expm(-1 * beta * H)) - return H.astype('complex64'), rho.astype( - 'complex64') # the returned dic will have 2 ** n value + return H.astype('complex128'), rho.astype( + 'complex128') # the returned dic will have 2 ** n value def read_calc_H(geo_fn, multiplicity=1, charge=0): @@ -91,7 +117,6 @@ def read_calc_H(geo_fn, multiplicity=1, charge=0): charge) openfermionpyscf.run_pyscf(mol) - terms_molecular_hamiltonian = mol.get_molecular_hamiltonian( ) fermionic_hamiltonian = openfermion.transforms.get_fermion_operator( @@ -99,10 +124,9 @@ def read_calc_H(geo_fn, multiplicity=1, charge=0): qubit_op = openfermion.transforms.jordan_wigner( fermionic_hamiltonian) - - # calc H, rho - H, rho = calc_H_rho_from_qubit_operator(qubit_op, mol.n_qubits) - return H, rho, mol.n_qubits + # calc H + Hamiltonian = Hamiltonian_str_convert(qubit_op) + return Hamiltonian, mol.n_qubits def main(): @@ -112,10 +136,8 @@ def main(): """ filename = 'h2.xyz' - H, rho, N = read_calc_H(geo_fn=filename) + H, N = read_calc_H(geo_fn=filename) print('H', H) - print("-------------------------- ") - print('rho', rho) if __name__ == '__main__': diff --git a/paddle_quantum/VQE/chemistrysub.py b/paddle_quantum/VQE/chemistrysub.py index 1e5624816c199ae89dbba623141aa01d68b42964..b3200d21e61d2091e0138020bd4008cabf7cfaed 100644 --- a/paddle_quantum/VQE/chemistrysub.py +++ b/paddle_quantum/VQE/chemistrysub.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -42,7 +42,7 @@ def H_generator(): sigma_I, sigma_Z) + 0.2 * kron(sigma_X, sigma_X) rho = scipy.linalg.expm(-1 * beta * H) / trace(scipy.linalg.expm(-1 * beta * H)) - return H.astype('complex64'), rho.astype('complex64') + return H.astype('complex128'), rho.astype('complex128') def H2_generator(): @@ -56,22 +56,39 @@ def H2_generator(): sigma_Z = array([[1, 0], [0, -1]]) sigma_X = array([[0, 1], [1, 0]]) sigma_Y = array([[0, -1j], [1j, 0]]) - H = (-0.04207897647782276) * kron(kron(kron(sigma_I, sigma_I), sigma_I), sigma_I) \ - + (0.17771287465139946) * kron(kron(kron(sigma_Z, sigma_I), sigma_I), sigma_I) \ - + (0.1777128746513994) * kron(kron(kron(sigma_I, sigma_Z), sigma_I), sigma_I) \ - + (-0.24274280513140462) * kron(kron(kron(sigma_I, sigma_I), sigma_Z), sigma_I) \ - + (-0.24274280513140462) * kron(kron(kron(sigma_I, sigma_I), sigma_I), sigma_Z) \ - + (0.17059738328801052) * kron(kron(kron(sigma_Z, sigma_Z), sigma_I), sigma_I) \ - + (0.04475014401535161) * kron(kron(kron(sigma_Y, sigma_X), sigma_X), sigma_Y) \ - + (-0.04475014401535161) * kron(kron(kron(sigma_Y, sigma_Y), sigma_X), sigma_X) \ - + (-0.04475014401535161) * kron(kron(kron(sigma_X, sigma_X), sigma_Y), sigma_Y) \ - + (0.04475014401535161) * kron(kron(kron(sigma_X, sigma_Y), sigma_Y), sigma_X) \ - + (0.12293305056183798) * kron(kron(kron(sigma_Z, sigma_I), sigma_Z), sigma_I) \ - + (0.1676831945771896) * kron(kron(kron(sigma_Z, sigma_I), sigma_I), sigma_Z) \ - + (0.1676831945771896) * kron(kron(kron(sigma_I, sigma_Z), sigma_Z), sigma_I) \ - + (0.12293305056183798) * kron(kron(kron(sigma_I, sigma_Z), sigma_I), sigma_Z) \ - + (0.17627640804319591) * kron(kron(kron(sigma_I, sigma_I), sigma_Z), sigma_Z) - rho = scipy.linalg.expm(-1 * beta * - H) / trace(scipy.linalg.expm(-1 * beta * H)) + # H = (-0.04207897647782276) * kron(kron(kron(sigma_I, sigma_I), sigma_I), sigma_I) \ + # + (0.17771287465139946) * kron(kron(kron(sigma_Z, sigma_I), sigma_I), sigma_I) \ + # + (0.1777128746513994) * kron(kron(kron(sigma_I, sigma_Z), sigma_I), sigma_I) \ + # + (-0.24274280513140462) * kron(kron(kron(sigma_I, sigma_I), sigma_Z), sigma_I) \ + # + (-0.24274280513140462) * kron(kron(kron(sigma_I, sigma_I), sigma_I), sigma_Z) \ + # + (0.17059738328801052) * kron(kron(kron(sigma_Z, sigma_Z), sigma_I), sigma_I) \ + # + (0.04475014401535161) * kron(kron(kron(sigma_Y, sigma_X), sigma_X), sigma_Y) \ + # + (-0.04475014401535161) * kron(kron(kron(sigma_Y, sigma_Y), sigma_X), sigma_X) \ + # + (-0.04475014401535161) * kron(kron(kron(sigma_X, sigma_X), sigma_Y), sigma_Y) \ + # + (0.04475014401535161) * kron(kron(kron(sigma_X, sigma_Y), sigma_Y), sigma_X) \ + # + (0.12293305056183798) * kron(kron(kron(sigma_Z, sigma_I), sigma_Z), sigma_I) \ + # + (0.1676831945771896) * kron(kron(kron(sigma_Z, sigma_I), sigma_I), sigma_Z) \ + # + (0.1676831945771896) * kron(kron(kron(sigma_I, sigma_Z), sigma_Z), sigma_I) \ + # + (0.12293305056183798) * kron(kron(kron(sigma_I, sigma_Z), sigma_I), sigma_Z) \ + # + (0.17627640804319591) * kron(kron(kron(sigma_I, sigma_I), sigma_Z), sigma_Z) + H = [ + [-0.04207897647782277, 'i0'], + [0.17771287465139946, 'z0'], + [0.1777128746513994, 'z1'], + [-0.2427428051314046, 'z2'], + [-0.24274280513140462, 'z3'], + [0.17059738328801055, 'z0,z1'], + [0.04475014401535163, 'y0,x1,x2,y3'], + [-0.04475014401535163, 'y0,y1,x2,x3'], + [-0.04475014401535163, 'x0,x1,y2,y3'], + [0.04475014401535163, 'x0,y1,y2,x3'], + [0.12293305056183797, 'z0,z2'], + [0.1676831945771896, 'z0,z3'], + [0.1676831945771896, 'z1,z2'], + [0.12293305056183797, 'z1,z3'], + [0.1762764080431959, 'z2,z3'] + ] + # rho = scipy.linalg.expm(-1 * beta * + # H) / trace(scipy.linalg.expm(-1 * beta * H)) N = 4 - return H.astype('complex64'), rho.astype('complex64'), N + return H, N diff --git a/paddle_quantum/VQE/example/main.py b/paddle_quantum/VQE/example/main.py index 889f8065ec724afa29195f212f233b45e3b7f793..3a4271961497519de70d4562132fb27cf3f95361 100644 --- a/paddle_quantum/VQE/example/main.py +++ b/paddle_quantum/VQE/example/main.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -28,28 +28,28 @@ def main(): Main Learning network using dynamic graph :return: Plot or No return """ - # Read data from built-in function or xyz file depending on OS sysStr = platform.system() + if sysStr == 'Windows': # Windows does not support SCF, using H2_generator instead print('Molecule data will be read from built-in function') - _H, _, N = H2_generator() + hamiltonian, N = H2_generator() print('Read Process Finished') elif sysStr in ('Linux', 'Darwin'): # for linux only from paddle_quantum.VQE.chemistrygen import read_calc_H - # Harmiltonian and cnot module preparing, must be executed under Linux + # Hamiltonian and cnot module preparing, must be executed under Linux # Read the H2 molecule data print('Molecule data will be read from h2.xyz') - _H, _, N = read_calc_H(geo_fn='h2.xyz') + hamiltonian, N = read_calc_H(geo_fn='h2.xyz') print('Read Process Finished') else: print("Don't support this OS.") - Paddle_VQE(_H, N) + Paddle_VQE(hamiltonian, N) benchmark_result() diff --git a/paddle_quantum/VQE/main.py b/paddle_quantum/VQE/main.py deleted file mode 100644 index 0518d2f6c493d4baaa42542d093e592591d9ca72..0000000000000000000000000000000000000000 --- a/paddle_quantum/VQE/main.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -main -""" - -import platform - -from paddle_quantum.VQE.Paddle_VQE import Paddle_VQE -from paddle_quantum.VQE.benchmark import benchmark_result -from paddle_quantum.VQE.chemistrysub import H2_generator - - -def main(): - """ - Main Learning network using dynamic graph - :return: Plot or No return - """ - # Read data from built-in function or xyz file depending on OS - sysStr = platform.system() - - if sysStr == 'Windows': - # Windows does not support SCF, using H2_generator instead - print('Molecule data will be read from built-in function') - hamiltonian, _, N = H2_generator() - print('Read Process Finished') - - elif sysStr in ('Linux', 'Darwin'): - # for linux only - from paddle_quantum.VQE.chemistrygen import read_calc_H - # Hamiltonian and cnot module preparing, must be executed under Linux - # Read the H2 molecule data - print('Molecule data will be read from h2.xyz') - hamiltonian, _, N = read_calc_H(geo_fn='h2.xyz') - print('Read Process Finished') - - else: - print("Don't support this OS.") - - Paddle_VQE(hamiltonian, N, D=2, LR=0.1) - benchmark_result() - - -if __name__ == '__main__': - main() diff --git a/paddle_quantum/VQSD/HGenerator.py b/paddle_quantum/VQSD/HGenerator.py index 9ef7319091dda40f4b48170cd5a86ae623aa95b1..2ab215f23f10474dbecbb5b40411156b74b015d2 100644 --- a/paddle_quantum/VQSD/HGenerator.py +++ b/paddle_quantum/VQSD/HGenerator.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,19 +19,19 @@ HGenerator from numpy import diag import scipy -SEED = 1 +SEED = 13 __all__ = ["generate_rho_sigma", ] def generate_rho_sigma(): - # V is a 4x4 random Unitary scipy.random.seed(SEED) - V = scipy.stats.unitary_group.rvs(4) - # generate rho - D = diag([0.1, 0.2, 0.3, 0.4]) - sigma = diag([0.4, 0.3, 0.2, 0.1]) + V = scipy.stats.unitary_group.rvs(4) # 随机生成一个酉矩阵 + D = diag([0.5, 0.3, 0.1, 0.1]) # 输入目标态 rho 的谱 V_H = V.conj().T - rho = V @D @V_H + rho = V @ D @ V_H # 生成 rho + # print(rho) # 打印量子态 rho - return rho.astype('complex64'), sigma.astype('complex64') + # 输入用来标记的量子态sigma + sigma = diag([0.1, 0.2, 0.3, 0.4]).astype('complex128') + return rho, sigma diff --git a/paddle_quantum/VQSD/Paddle_VQSD.py b/paddle_quantum/VQSD/Paddle_VQSD.py index 9130e1f32e87ee69237e32fd406e4237bc91eb9a..a306f24060070a18a10988e63d2568016d5dbf40 100644 --- a/paddle_quantum/VQSD/Paddle_VQSD.py +++ b/paddle_quantum/VQSD/Paddle_VQSD.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,12 +18,13 @@ you could check the corresponding Jupyter notebook under the Tutorial folder. """ import numpy - from paddle import fluid from paddle_quantum.circuit import UAnsatz -from paddle.complex import matmul, trace, transpose +from paddle_quantum.utils import hermitian +from paddle.complex import matmul, trace +from paddle_quantum.VQSD.HGenerator import generate_rho_sigma -SEED = 1 +SEED = 14 __all__ = [ "U_theta", @@ -32,37 +33,19 @@ __all__ = [ ] -# definition of U_theta def U_theta(theta, N): """ - U_theta + Quantum Neural Network """ + # 按照量子比特数量/网络宽度初始化量子神经网络 cir = UAnsatz(N) - cir.rz(theta[0], 1) - cir.ry(theta[1], 1) - cir.rz(theta[2], 1) - - cir.rz(theta[3], 2) - cir.ry(theta[4], 2) - cir.rz(theta[5], 2) - - cir.cnot([2, 1]) - - cir.rz(theta[6], 1) - cir.ry(theta[7], 2) - - cir.cnot([1, 2]) - - cir.rz(theta[8], 1) - cir.ry(theta[9], 1) - cir.rz(theta[10], 1) - cir.rz(theta[11], 2) - cir.ry(theta[12], 2) - cir.rz(theta[13], 2) + # 调用内置的量子神经网络模板 + cir.universal_2_qubit_gate(theta) - return cir.state + # 返回量子神经网络所模拟的酉矩阵 U + return cir.U class Net(fluid.dygraph.Layer): @@ -70,65 +53,79 @@ class Net(fluid.dygraph.Layer): Construct the model net """ - def __init__(self, - shape, - rho, - sigma, - param_attr=fluid.initializer.Uniform( - low=0.0, high=2 * numpy.pi, seed=SEED), - dtype='float32'): + def __init__(self, shape, rho, sigma, param_attr=fluid.initializer.Uniform(low=0.0, high=2 * numpy.pi, seed=SEED), + dtype='float64'): super(Net, self).__init__() + # 将 Numpy array 转换成 Paddle 动态图模式中支持的 variable self.rho = fluid.dygraph.to_variable(rho) self.sigma = fluid.dygraph.to_variable(sigma) - self.theta = self.create_parameter( - shape=shape, attr=param_attr, dtype=dtype, is_bias=False) + # 初始化 theta 参数列表,并用 [0, 2*pi] 的均匀分布来填充初始值 + self.theta = self.create_parameter(shape=shape, attr=param_attr, dtype=dtype, is_bias=False) + # 定义损失函数和前向传播机制 def forward(self, N): - """ - Args: - Returns: - The loss. - """ - - out_state = U_theta(self.theta, N) - - # rho_tilde is what you get after you put self.rho through the circuit - rho_tilde = matmul( - matmul(out_state, self.rho), - transpose( - fluid.framework.ComplexVariable(out_state.real, - -out_state.imag), - perm=[1, 0])) - - # record the new loss + # 施加量子神经网络 + U = U_theta(self.theta, N) + + # rho_tilde 是将 U 作用在 rho 后得到的量子态 U*rho*U^dagger + rho_tilde = matmul(matmul(U, self.rho), hermitian(U)) + + # 计算损失函数 loss = trace(matmul(self.sigma, rho_tilde)) return loss.real, rho_tilde -def Paddle_VQSD(rho, sigma, N=2, THETA_SIZE=14, ITR=50, LR=0.1): - """ +def Paddle_VQSD(rho, sigma, N=2, THETA_SIZE=15, ITR=50, LR=0.1): + r""" Paddle_VQSD + :param rho: 待对角化的量子态 + :param sigma: 输入用来标记的量子态sigma + :param N: 量子神经网络的宽度 + :param THETA_SIZE: 量子神经网络中参数的数量 + :param ITR: 设置训练的总的迭代次数 + :param LR: 设置学习速率 + :return: 优化之后量子态rho接近对角态的numpy形式 """ - + # 初始化paddle动态图机制 with fluid.dygraph.guard(): - # net + # 确定网络的参数维度 net = Net(shape=[THETA_SIZE], rho=rho, sigma=sigma) - # optimizer - opt = fluid.optimizer.AdagradOptimizer( - learning_rate=LR, parameter_list=net.parameters()) - # gradient descent loop + # 一般来说,我们利用Adam优化器来获得相对好的收敛,当然你可以改成SGD或者是RMS prop. + opt = fluid.optimizer.AdagradOptimizer(learning_rate=LR, parameter_list=net.parameters()) + + # 优化循环 for itr in range(ITR): - loss, rho_tilde = net(N) + # 前向传播计算损失函数并返回估计的能谱 + loss, rho_tilde = net(N) rho_tilde_np = rho_tilde.numpy() + + # 在动态图机制下,反向传播极小化损失函数 loss.backward() opt.minimize(loss) net.clear_gradients() - print('iter:', itr, 'loss:', '%.4f' % loss.numpy()[0]) - + # 打印训练结果 + if itr % 10 == 0: + print('iter:', itr, 'loss:', '%.4f' % loss.numpy()[0]) return rho_tilde_np + + +def main(): + + D = [0.5, 0.3, 0.1, 0.1] + + rho, sigma = generate_rho_sigma() + + rho_tilde_np = Paddle_VQSD(rho, sigma) + + print("The estimated spectrum is:", numpy.real(numpy.diag(rho_tilde_np))) + print('The target spectrum is:', D) + + +if __name__ == '__main__': + main() diff --git a/paddle_quantum/VQSD/example/main.py b/paddle_quantum/VQSD/example/main.py index 01e29ad38e1581afae3482d21821c5eb0af6c97c..158f8eff7e20e1f4573f1f6baacf75b15296e094 100644 --- a/paddle_quantum/VQSD/example/main.py +++ b/paddle_quantum/VQSD/example/main.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,19 +17,23 @@ Main """ import numpy + from paddle_quantum.VQSD.HGenerator import generate_rho_sigma from paddle_quantum.VQSD.Paddle_VQSD import Paddle_VQSD def main(): """ - Main - :return: + main """ + D = [0.5, 0.3, 0.1, 0.1] rho, sigma = generate_rho_sigma() + rho_tilde_np = Paddle_VQSD(rho, sigma) - print('spectrum:', numpy.real(numpy.diag(rho_tilde_np))) + + print("The estimated spectrum is:", numpy.real(numpy.diag(rho_tilde_np))) + print('The target spectrum is:', D) if __name__ == '__main__': diff --git a/paddle_quantum/VQSD/main.py b/paddle_quantum/VQSD/main.py deleted file mode 100644 index 929a60301d62643c3cdca6471205dee483d8bdfb..0000000000000000000000000000000000000000 --- a/paddle_quantum/VQSD/main.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -main -""" - -import numpy -from paddle_quantum.VQSD.HGenerator import generate_rho_sigma -from paddle_quantum.VQSD.Paddle_VQSD import Paddle_VQSD - - -def main(): - rho, sigma = generate_rho_sigma() - rho_tilde_np = Paddle_VQSD(rho, sigma) - print('spectrum:', numpy.real(numpy.diag(rho_tilde_np))) - - -if __name__ == '__main__': - main() diff --git a/paddle_quantum/__init__.py b/paddle_quantum/__init__.py index 96f877b423450c7dab74955266649c8c77d50880..4bd234e7d0c7e937b2acbe0fea1fa8cde08528fc 100644 --- a/paddle_quantum/__init__.py +++ b/paddle_quantum/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,6 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" +r""" Paddle Quantum Library """ diff --git a/paddle_quantum/circuit.py b/paddle_quantum/circuit.py index 498628459a79fd7c221a7ddb034c6711de0c21f3..4da0d233662a006efcb622fbec8214bae08ce224 100644 --- a/paddle_quantum/circuit.py +++ b/paddle_quantum/circuit.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,178 +12,1140 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" -circuit -""" - +import gc +import math +from functools import reduce +from collections import defaultdict +import numpy as np from numpy import binary_repr, eye, identity +import matplotlib.pyplot as plt + +from Simulator.main import StateTranfer, init_state_gen, measure_state +import paddle from paddle.complex import kron as pp_kron -from paddle.complex import matmul +from paddle.complex import reshape as complex_reshape +from paddle.complex import matmul, transpose, trace +from paddle.complex.tensor.math import elementwise_mul +import paddle.fluid as fluid from paddle.fluid import dygraph +from paddle.fluid.layers import reshape, cast, eye, zeros +from paddle.fluid.framework import ComplexVariable -from paddle_quantum.utils import rotation_x, rotation_y, rotation_z +from paddle_quantum.utils import hermitian, pauli_str_to_matrix +from paddle_quantum.intrinsic import * +from paddle_quantum.state import density_op __all__ = [ - "dic_between2and10", - "base_2_change", - "cnot_construct", - "identity_generator", - "single_gate_construct", "UAnsatz", + "H_prob" ] -def dic_between2and10(n): - """ - :param n: number of qubits - :return: dictionary between binary and decimal +class UAnsatz: + r"""基于Paddle的动态图机制实现量子线路的 ``class`` 。 + + 用户可以通过实例化该 ``class`` 来搭建自己的量子线路。 - for example: if n=3, the dictionary is - dic2to10: {'000': 0, '011': 3, '010': 2, '111': 7, '100': 4, '101': 5, '110': 6, '001': 1} - dic10to2: ['000', '001', '010', '011', '100', '101', '110', '111'] + Attributes: + n (int): 该线路的量子比特数 """ - dic2to10 = {} - dic10to2 = [None] * 2**n + def __init__(self, n): + r"""UAnsatz的构造函数,用于实例化一个UAnsatz对象 - for i in range(2**n): - binary_text = binary_repr(i, width=n) - dic2to10[binary_text] = i - dic10to2[i] = binary_text + Args: + n (int): 该线路的量子比特数 - return dic2to10, dic10to2 # the returned dic will have 2 ** n value + """ + self.n = n + self.__state = None + self.__run_state = '' + # Record history of adding gates to the circuit + self.__history = [] + def run_state_vector(self, input_state=None, store_state=True): + r"""运行当前的量子线路,输入输出的形式为态矢量。 + + Args: + input_state (ComplexVariable, optional): 输入的态矢量,默认为 :math:`|00...0\rangle` + store_state (Bool, optional): 是否存储输出的态矢量,默认为 ``True`` ,即存储 + + Returns: + ComplexVariable: 量子线路输出的态矢量 + + 代码示例: -def base_2_change(string_n, i_ctrl, j_target): - """ - :param string_n: an n-bit string - :param i_ctrl: i-th bit is control, 'int' - :param j_target: j-th bit is target, 'int' - :return: if i-th bit is 1, j-th bit takes inverse - """ + .. code-block:: python + + import numpy as np + from paddle import fluid + from paddle_quantum.circuit import UAnsatz + n = 2 + theta = np.ones(3) + input_state = np.ones(2**n)+0j + input_state = input_state / np.linalg.norm(input_state) + with fluid.dygraph.guard(): + + input_state_var = fluid.dygraph.to_variable(input_state) + theta = fluid.dygraph.to_variable(theta) + cir = UAnsatz(n) + cir.rx(theta[0], 0) + cir.ry(theta[1], 1) + cir.rz(theta[2], 1) + vec = cir.run_state_vector(input_state_var).numpy() + print(f"运行后的向量是 {vec}") - string_n_list = list(string_n) - if string_n_list[i_ctrl] == "1": - string_n_list[j_target] = str((int(string_n_list[j_target]) + 1) % 2) - return "".join(string_n_list) + :: + 运行后的向量是 [0.17470783-0.09544332j 0.59544332+0.32529217j 0.17470783-0.09544332j 0.59544332+0.32529217j] + """ + state = init_state_gen(self.n, 0) if input_state is None else input_state + old_shape = state.shape + assert reduce(lambda x, y: x * y, old_shape) == 2 ** self.n, 'The length of the input vector is not right' + state = complex_reshape(state, (2 ** self.n,)) -def cnot_construct(n, ctrl): - """ - cnot_construct: The controlled-NOT gate - :param n: number of qubits - :param ctrl: a shape [2] vector, in 'int' type. ctrl[0]-th qubit controls the ctrl[1]-qubit - :return: cnot module - """ + state_conj = ComplexVariable(state.real, -state.imag) + assert fluid.layers.abs(paddle.complex.sum(elementwise_mul(state_conj, state)).real - 1) < 1e-8, \ + 'Input state is not a normalized vector' - mat = eye(2**n) - dummy_mat = eye(2**n) - dic2to10, dic10to2 = dic_between2and10(n) - """ for example: if n=3, the dictionary is - dic2to10: {'000': 0, '011': 3, '010': 2, '111': 7, '100': 4, '101': 5, '110': 6, '001': 1} - dic10to2: ['000', '001', '010', '011', '100', '101', '110', '111'] - """ + for history_ele in self.__history: + if history_ele[0] == 'u': + state = StateTranfer(state, 'u', history_ele[1], history_ele[2]) + elif history_ele[0] in {'x', 'y', 'z', 'h'}: + state = StateTranfer(state, history_ele[0], history_ele[1], params=history_ele[2]) + elif history_ele[0] == 'CNOT': + state = StateTranfer(state, 'CNOT', history_ele[1]) + + if store_state: + self.__state = state + # Add info about which function user called + self.__run_state = 'state_vector' + + return complex_reshape(state, old_shape) + + def run_density_matrix(self, input_state=None, store_state=True): + r"""运行当前的量子线路,输入输出的形式为密度矩阵。 + + Args: + input_state (ComplexVariable, optional): 输入的密度矩阵,默认为 :math:`|00...0\rangle \langle00...0|` + store_state (bool, optional): 是否存储输出的密度矩阵,默认为 ``True`` ,即存储 + + Returns: + ComplexVariable: 量子线路输出的密度矩阵 + + 代码示例: + + .. code-block:: python + + import numpy as np + from paddle import fluid + from paddle_quantum.circuit import UAnsatz + n = 1 + theta = np.ones(3) + input_state = np.diag(np.arange(2**n))+0j + input_state = input_state / np.trace(input_state) + with fluid.dygraph.guard(): + + input_state_var = fluid.dygraph.to_variable(input_state) + theta = fluid.dygraph.to_variable(theta) + cir = UAnsatz(n) + cir.rx(theta[0], 0) + cir.ry(theta[1], 0) + cir.rz(theta[2], 0) + density = cir.run_density_matrix(input_state_var).numpy() + print(f"密度矩阵是\n{density}") + + :: + + 密度矩阵是 + [[ 0.35403671+0.j -0.47686058-0.03603751j] + [-0.47686058+0.03603751j 0.64596329+0.j ]] + """ + state = dygraph.to_variable(density_op(self.n)) if input_state is None else input_state - for row in range(2**n): - """ for each decimal index 'row', transform it into binary, - and use 'base_2_change()' function to compute the new binary index 'row'. - Lastly, use 'dic2to10' to transform this new 'row' into decimal. + assert state.real.shape == [2 ** self.n, 2 ** self.n], "The dimension is not right" + state = matmul(self.U, matmul(state, hermitian(self.U))) - For instance, n=3, ctrl=[1,3]. if row = 5, - its process is 5 -> '101' -> '100' -> 4 + if store_state: + self.__state = state + # Add info about which function user called + self.__run_state = 'density_matrix' + + return state + + @property + def U(self): + r"""量子线路的酉矩阵形式。 + + Returns: + ComplexVariable: 当前线路的酉矩阵表示 + + 代码示例: + + .. code-block:: python + + from paddle import fluid + from paddle_quantum.circuit import UAnsatz + n = 2 + with fluid.dygraph.guard(): + cir = UAnsatz(2) + cir.h(0) + cir.cnot([0, 1]) + matrix = cir.U + print("生成贝尔态电路的酉矩阵表示为\n",matrix.numpy()) + + :: + + 生成贝尔态电路的酉矩阵表示为 + [[ 0.70710678+0.j 0. +0.j 0.70710678+0.j 0. +0.j] + [ 0. +0.j 0.70710678+0.j 0. +0.j 0.70710678+0.j] + [ 0. +0.j 0.70710678+0.j 0. +0.j -0.70710678+0.j] + [ 0.70710678+0.j 0. +0.j -0.70710678+0.j 0. +0.j]] """ - new_string_base_2 = base_2_change(dic10to2[row], ctrl[0] - 1, - ctrl[1] - 1) - new_int_base_10 = dic2to10[new_string_base_2] - mat[row] = dummy_mat[new_int_base_10] + state = ComplexVariable(eye(2 ** self.n, dtype='float64'), zeros([2 ** self.n, 2 ** self.n], dtype='float64')) + shape = (2 ** self.n, 2 ** self.n) + num_ele = reduce(lambda x, y: x * y, shape) + state = ComplexVariable(reshape(state.real, [num_ele]), reshape(state.imag, [num_ele])) - return mat.astype("complex64") + for history_ele in self.__history: + if history_ele[0] == 'u': + state = StateTranfer(state, 'u', history_ele[1], history_ele[2]) + elif history_ele[0] in {'x', 'y', 'z', 'h'}: + state = StateTranfer(state, history_ele[0], history_ele[1], params=history_ele[2]) + elif history_ele[0] == 'CNOT': + state = StateTranfer(state, 'CNOT', history_ele[1]) + return ComplexVariable(reshape(state.real, shape), reshape(state.imag, shape)) -def identity_generator(n): """ - identity_generator + Common Gates """ - idty_np = identity(2**n, dtype="float32") - idty = dygraph.to_variable(idty_np) + def rx(self, theta, which_qubit): + r"""添加关于x轴的单量子比特旋转门。 + + 其矩阵形式为: + + .. math:: + + \begin{bmatrix} \cos\frac{\theta}{2} & -i\sin\frac{\theta}{2} \\ -i\sin\frac{\theta}{2} & \cos\frac{\theta}{2} \end{bmatrix} + + Args: + theta (Variable): 旋转角度 + which_qubit (int): 作用在的qubit的编号,其值应该在[0, n)范围内,n为该量子线路的量子比特数 + + .. code-block:: python + + import numpy as np + from paddle import fluid + from paddle_quantum.circuit import UAnsatz + theta = np.array([np.pi], np.float64) + with fluid.dygraph.guard(): + theta = fluid.dygraph.to_variable(theta) + num_qubits = 1 + cir = UAnsatz(num_qubits) + which_qubit = 0 + cir.rx(theta[0], which_qubit) + """ + assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n(the number of qubit)" + self.__history.append(['u', [which_qubit], [theta, + dygraph.to_variable(np.array([-math.pi / 2])), + dygraph.to_variable(np.array([math.pi / 2]))]]) + + def ry(self, theta, which_qubit): + r"""添加关于y轴的单量子比特旋转门。 + + 其矩阵形式为: + + .. math:: + + \begin{bmatrix} \cos\frac{\theta}{2} & -\sin\frac{\theta}{2} \\ \sin\frac{\theta}{2} & \cos\frac{\theta}{2} \end{bmatrix} + + Args: + theta (Variable): 旋转角度 + which_qubit (int): 作用在的qubit的编号,其值应该在[0, n)范围内,n为该量子线路的量子比特数 + + .. code-block:: python + + import numpy as np + from paddle import fluid + from paddle_quantum.circuit import UAnsatz + theta = np.array([np.pi], np.float64) + with fluid.dygraph.guard(): + theta = fluid.dygraph.to_variable(theta) + num_qubits = 1 + cir = UAnsatz(num_qubits) + which_qubit = 0 + cir.ry(theta[0], which_qubit) + """ + assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n(the number of qubit)" + self.__history.append(['u', [which_qubit], [theta, + dygraph.to_variable(np.array([0.0])), + dygraph.to_variable(np.array([0.0]))]]) + + def rz(self, theta, which_qubit): + r"""添加关于y轴的单量子比特旋转门。 + + 其矩阵形式为: + + .. math:: + + \begin{bmatrix} e^{-\frac{i\theta}{2}} & 0 \\ 0 & e^{\frac{i\theta}{2}} \end{bmatrix} + + Args: + theta (Variable): 旋转角度 + which_qubit (int): 作用在的qubit的编号,其值应该在[0, n)范围内,n为该量子线路的量子比特数 + + .. code-block:: python + + import numpy as np + from paddle import fluid + from paddle_quantum.circuit import UAnsatz + theta = np.array([np.pi], np.float64) + with fluid.dygraph.guard(): + theta = fluid.dygraph.to_variable(theta) + num_qubits = 1 + cir = UAnsatz(num_qubits) + which_qubit = 0 + cir.ry(theta[0], which_qubit) + """ + assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n(the number of qubit)" + self.__history.append(['u', [which_qubit], [dygraph.to_variable(np.array([0.0])), + dygraph.to_variable(np.array([0.0])), + theta]]) + + def cnot(self, control): + r"""添加一个CNOT门。 + + 对于2量子比特的量子线路,当control为 ``[0, 1]`` 时,其矩阵形式为: + + .. math:: + + \begin{align} + CNOT &=|0\rangle \langle 0|\otimes I + |1 \rangle \langle 1|\otimes X\\ + &=\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \end{bmatrix} + \end{align} + + Args: + control (list): 作用在的qubit的编号,``control[0]`` 为控制位,``control[1]`` 为目标位,其值都应该在 :math:`[0, n)`范围内, :math:`n` 为该量子线路的量子比特数 + + .. code-block:: python + + import numpy as np + from paddle import fluid + from paddle_quantum.circuit import UAnsatz + num_qubits = 2 + with fluid.dygraph.guard(): + cir = UAnsatz(num_qubits) + cir.cnot([0, 1]) + """ + assert 0 <= control[0] < self.n and 0 <= control[1] < self.n,\ + "the qubit should >= 0 and < n(the number of qubit)" + assert control[0] != control[1], "the control qubit is the same as the target qubit" + self.__history.append(['CNOT', control]) + + def x(self, which_qubit): + r"""添加单量子比特X门。 + + 其矩阵形式为: + + .. math:: + + \begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix} + + Args: + which_qubit (int): 作用在的qubit的编号,其值应该在[0, n)范围内,n为该量子线路的量子比特数 + + .. code-block:: python + + from paddle import fluid + from paddle_quantum.circuit import UAnsatz + with fluid.dygraph.guard(): + num_qubits = 1 + cir = UAnsatz(num_qubits) + which_qubit = 0 + cir.x(which_qubit) + """ + assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n(the number of qubit)" + self.__history.append(['x', [which_qubit], None]) + + def y(self, which_qubit): + r"""添加单量子比特Y门。 + + 其矩阵形式为: + + .. math:: + + \begin{bmatrix} 0 & -i \\ i & 0 \end{bmatrix} + + Args: + which_qubit (int): 作用在的qubit的编号,其值应该在[0, n)范围内,n为该量子线路的量子比特数 + + .. code-block:: python + + from paddle import fluid + from paddle_quantum.circuit import UAnsatz + with fluid.dygraph.guard(): + num_qubits = 1 + cir = UAnsatz(num_qubits) + which_qubit = 0 + cir.y(which_qubit) + """ + assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n(the number of qubit)" + self.__history.append(['y', [which_qubit], None]) + + def z(self, which_qubit): + r"""添加单量子比特Z门。 + + 其矩阵形式为: + + .. math:: + + \begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix} + + Args: + which_qubit (int): 作用在的qubit的编号,其值应该在[0, n)范围内,n为该量子线路的量子比特数 + + .. code-block:: python + + from paddle import fluid + from paddle_quantum.circuit import UAnsatz + with fluid.dygraph.guard(): + num_qubits = 1 + cir = UAnsatz(num_qubits) + which_qubit = 0 + cir.z(which_qubit) + """ + assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n(the number of qubit)" + self.__history.append(['z', [which_qubit], None]) + + def h(self, which_qubit): + r"""添加一个单量子比特的Hadamard门。 + + 具体形式为 + + .. math:: + + H = \frac{1}{\sqrt{2}}\begin{bmatrix} 1&1\\1&-1 \end{bmatrix} + + Args: + which_qubit (int): 作用在的qubit的编号,其值应该在[0, n)范围内,n为该量子线路的量子比特数 + """ + assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n(the number of qubit)" + self.__history.append(['h', [which_qubit], None]) + + def s(self, which_qubit): + r"""添加一个单量子比特的S门。 + + 具体形式为 + + .. math:: + + S = \begin{bmatrix} 1&0\\0&i \end{bmatrix} + + Args: + which_qubit (int): 作用在的qubit的编号,其值应该在[0, n)范围内,n为该量子线路的量子比特数 + """ + assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n(the number of qubit)" + self.__history.append(['u', [which_qubit], [dygraph.to_variable(np.array([0.0])), + dygraph.to_variable(np.array([0.0])), + dygraph.to_variable(np.array([math.pi / 2]))]]) + + def t(self, which_qubit): + r"""添加一个单量子比特的T门。 + + 具体形式为 + + .. math:: + + T = \begin{bmatrix} 1&0\\0&e^\frac{i\pi}{4} \end{bmatrix} - return idty + Args: + which_qubit (int): 作用在的qubit的编号,其值应该在[0, n)范围内,n为该量子线路的量子比特数 + """ + assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n(the number of qubit)" + self.__history.append(['u', [which_qubit], [dygraph.to_variable(np.array([0.0])), + dygraph.to_variable(np.array([0.0])), + dygraph.to_variable(np.array([math.pi / 4]))]]) + + def u3(self, theta, phi, lam, which_qubit): + r"""添加一个单量子比特的旋转门。 + + 具体形式为 + .. math:: + + \begin{align} + U3(\theta, \phi, \lambda) = + \begin{bmatrix} + \cos\frac\theta2&-e^{i\lambda}\sin\frac\theta2\\ + e^{i\phi}\sin\frac\theta2&e^{i(\phi+\lambda)}\cos\frac\theta2 + \end{bmatrix} + \end{align} + + Args: + theta (Variable): 旋转角度 :math:`\theta` 。 + phi (Variable): 旋转角度 :math:`\phi` 。 + lam (Variable): 旋转角度 :math:`\lambda` 。 + which_qubit (int): 作用在的qubit的编号,其值应该在[0, n)范围内,n为该量子线路的量子比特数 + """ + assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n(the number of qubit)" + self.__history.append(['u', [which_qubit], [theta, phi, lam]]) -def single_gate_construct(mat, n, which_qubit): """ - :param mat: the input matrix - :param n: number of qubits - :param which_qubit: indicate which qubit the matrix is placed on - :return: the kronecker product of the matrix and identity + Measurements """ - idty = identity_generator(n - 1) + def __process_string(self, s, which_qubits): + r""" + This functions return part of string s baesd on which_qubits + If s = 'abcdefg', which_qubits = [0,2,5], then it returns 'acf' - if which_qubit == 1: - mat = pp_kron(mat, idty) + Note: + 这是内部函数,你并不需要直接调用到该函数。 + """ + new_s = ''.join(s[j] for j in which_qubits) + return new_s - elif which_qubit == n: - mat = pp_kron(idty, mat) + def __process_similiar(self, result): + r""" + This functions merges values based on identical keys. + If result = [('00', 10), ('01', 20), ('11', 30), ('11', 40), ('11', 50), ('00', 60)], then it returns {'00': 70, '01': 20, '11': 120} - else: - I_top = identity_generator(which_qubit - 1) - I_bot = identity_generator(n - which_qubit) - mat = pp_kron(pp_kron(I_top, mat), I_bot) + Note: + 这是内部函数,你并不需要直接调用到该函数。 + """ + data = defaultdict(int) + for idx, val in result: + data[idx] += val - return mat + return dict(data) + def __measure_hist(self, result, which_qubits, shots): + r"""将测量的结果以柱状图的形式呈现。 + + Note: + 这是内部函数,你并不需要直接调用到该函数。 + + Args: + result (dictionary): 测量结果 + which_qubits (list): 测量的量子比特,如测量所有则是 ``None`` + shots(int): 测量次数 + + Returns + dict: 测量结果 + + """ + n = self.n if which_qubits is None else len(which_qubits) + assert n < 6, "Too many qubits to plot" + + ylabel = "Measured Probabilities" + if shots == 0: + shots = 1 + ylabel = "Probabilities" + + state_list = [np.binary_repr(index, width=n) for index in range(0, 2 ** n)] + freq = [] + for state in state_list: + freq.append(result.get(state, 0.0) / shots) + + plt.bar(range(2 ** n), freq, tick_label=state_list) + plt.xticks(rotation=90) + plt.xlabel("Qubit State") + plt.ylabel(ylabel) + plt.show() + + return result + + # Which_qubits is list-like + def measure(self, which_qubits=None, shots=2 ** 10, plot=False): + r"""对量子线路输出的量子态进行测量。 + + Warning: + 当plot为True时,当前量子线路的量子比特数需要小于6,否则无法绘制图片,会抛出异常。 + + Args: + which_qubits (list, optional): 要测量的qubit的编号,默认全都测量 + shots (int, optional): 该量子线路输出的量子态的测量次数,默认为1024次;若为0,则输出测量期望值的精确值 + plot (bool, optional): 是否绘制测量结果图,默认为 ``False`` ,即不绘制 + + Returns: + dict: 测量的结果 + + 代码示例: + + .. code-block:: python + + import paddle + from paddle_quantum.circuit import UAnsatz + with paddle.fluid.dygraph.guard(): + cir = UAnsatz(2) + cir.h(0) + cir.cnot([0,1]) + cir.run_state_vector() + result = cir.measure(shots = 2048, which_qubits = [1]) + print(f"测量第1号量子比特2048次的结果是{result}") + + :: + + 测量第1号量子比特2048次的结果是{'0': 964, '1': 1084} + + .. code-block:: python + + import paddle + from paddle_quantum.circuit import UAnsatz + with paddle.fluid.dygraph.guard(): + cir = UAnsatz(2) + cir.h(0) + cir.cnot([0,1]) + cir.run_state_vector() + result = cir.measure(shots = 0, which_qubits = [1]) + print(f"测量第1号量子比特的概率结果是{result}") + + :: + + 测量第1号量子比特的概率结果是{'0': 0.4999999999999999, '1': 0.4999999999999999} + """ + if self.__run_state == 'state_vector': + state = self.__state + elif self.__run_state == 'density_matrix': + # Take the diagonal of the density matrix as a probability distribution + diag = np.diag(self.__state.numpy()) + state = fluid.dygraph.to_variable(np.sqrt(diag)) + else: + # Raise error + raise ValueError("no state for measurement; please run the circuit first") + + if shots == 0: # Returns probability distribution over all measurement results + dic2to10, dic10to2 = dic_between2and10(self.n) + result = {} + for i in range(2 ** self.n): + result[dic10to2[i]] = (state.real[i] ** 2 + state.imag[i] ** 2).numpy()[0] + + if which_qubits is not None: + new_result = [(self.__process_string(key, which_qubits), value) for key, value in result.items()] + result = self.__process_similiar(new_result) + else: + if which_qubits is None: # Return all the qubits + result = measure_state(state, shots) + else: + assert all([e < self.n for e in which_qubits]), 'Qubit index out of range' + which_qubits.sort() # Sort in ascending order + + collapse_all = measure_state(state, shots) + new_collapse_all = [(self.__process_string(key, which_qubits), value) for key, value in + collapse_all.items()] + result = self.__process_similiar(new_collapse_all) + + return result if not plot else self.__measure_hist(result, which_qubits, shots) + + def expecval(self, H): + r"""量子线路输出的量子态关于可观测量H的期望值。 + + Hint: + 如果想输入的可观测量的矩阵为 :math:`0.7Z\otimes X\otimes I+0.2I\otimes Z\otimes I` 。则 ``H`` 应为 ``[[0.7, 'z0,x1'], [0.2, 'z1']]`` 。 + Args: + H (list): 可观测量的相关信息 + Returns: + Variable: 量子线路输出的量子态关于H的期望值 + + 代码示例: + + .. code-block:: python + + import numpy as np + from paddle import fluid + from paddle_quantum.circuit import UAnsatz + n = 5 + H_info = [[0.1, 'x1'], [0.2, 'y0,z4']] + theta = np.ones(3) + input_state = np.ones(2**n)+0j + input_state = input_state / np.linalg.norm(input_state) + with fluid.dygraph.guard(): + input_state_var = fluid.dygraph.to_variable(input_state) + theta = fluid.dygraph.to_variable(theta) + cir = UAnsatz(n) + cir.rx(theta[0], 0) + cir.rz(theta[1], 1) + cir.rx(theta[2], 2) + cir.run_state_vector(input_state_var) + expect_value = cir.expecval(H_info).numpy() + print(f'计算得到的{H_info}期望值是{expect_value}') + + :: + + 计算得到的[[0.1, 'x1'], [0.2, 'y0,z4']]期望值是[0.05403023] + + .. code-block:: python + + import numpy as np + from paddle import fluid + from paddle_quantum.circuit import UAnsatz + n = 5 + H_info = [[0.1, 'x1'], [0.2, 'y0,z4']] + theta = np.ones(3) + input_state = np.diag(np.arange(2**n))+0j + input_state = input_state / np.trace(input_state) + with fluid.dygraph.guard(): + input_state_var = fluid.dygraph.to_variable(input_state) + theta = fluid.dygraph.to_variable(theta) + cir = UAnsatz(n) + cir.rx(theta[0], 0) + cir.ry(theta[1], 1) + cir.rz(theta[2], 2) + cir.run_density_matrix(input_state_var) + expect_value = cir.expecval(H_info).numpy() + print(f'计算得到的{H_info}期望值是{expect_value}') + + :: + + 计算得到的[[0.1, 'x1'], [0.2, 'y0,z4']]期望值是[-0.02171538] + """ + if self.__run_state == 'state_vector': + return vec_expecval(H, self.__state).real + elif self.__run_state == 'density_matrix': + state = self.__state + H_mat = fluid.dygraph.to_variable(pauli_str_to_matrix(H, self.n)) + return trace(matmul(state, H_mat)).real + else: + # Raise error + raise ValueError("no state for measurement; please run the circuit first") -class UAnsatz: """ - UAnsatz: ansatz for the parameterized quantum circuit or quantum neural network. + Circuit Templates """ - def __init__(self, n, input_state=None): + def superposition_layer(self): + r"""添加一层Hadamard门。 + + 代码示例: + + .. code-block:: python + + import paddle + from paddle_quantum.circuit import UAnsatz + with paddle.fluid.dygraph.guard(): + cir = UAnsatz(2) + cir.superposition_layer() + cir.run_state_vector() + result = cir.measure(shots = 0) + print(f"测量全部量子比特的结果是{result}") + + :: + + 测量全部量子比特结果是{'00': 0.2499999999999999, '01': 0.2499999999999999, '10': 0.2499999999999999, '11': 0.2499999999999999} """ - :param input_state: if the input_state is 'None', the self.mat is a matrix, - if the input_state is a row complex vector, the self.mat is a row vector + for i in range(self.n): + self.h(i) + + def weak_superposition_layer(self): + r"""添加一层Hadamard的平方根门,即 :math:`\sqrt{H}` 门。 + + 代码示例: + + .. code-block:: python + + import paddle + from paddle_quantum.circuit import UAnsatz + with paddle.fluid.dygraph.guard(): + cir = UAnsatz(2) + cir.weak_superposition_layer() + cir.run_state_vector() + result = cir.measure(shots = 0) + print(f"测量全部量子比特的结果是{result}") + + :: + + 测量全部量子比特的结果是{'00': 0.7285533905932737, '01': 0.12500000000000003, '10': 0.12500000000000003, '11': 0.021446609406726238} """ + _theta = fluid.dygraph.to_variable(np.array([np.pi / 4])) # Used in fixed Ry gate + for i in range(self.n): + self.ry(_theta, i) - self.n = n - self.state = input_state if input_state is not None else identity_generator( - self.n) + def real_entangled_layer(self, theta, depth): + r"""添加一层包含Ry门的强纠缠层。 - def rx(self, theta, which_qubit): + Note: + 这一层量子门的数学表示形式为实数酉矩阵。 + + Attention: + ``theta`` 的维度为 ``(depth, n, 1)`` + + Args: + theta (Variable): Ry门的旋转角度 + depth (int): 纠缠层的深度 + + 代码示例: + + .. code-block:: python + + import numpy as np + from paddle import fluid + from paddle_quantum.circuit import UAnsatz + n = 2 + DEPTH = 3 + theta = np.ones([DEPTH, n, 1]) + with fluid.dygraph.guard(): + theta = fluid.dygraph.to_variable(theta) + cir = UAnsatz(n) + cir.real_entangled_layer(fluid.dygraph.to_variable(theta), DEPTH) + cir.run_state_vector() + print(cir.measure(shots = 0)) + + :: + + {'00': 2.52129874867343e-05, '01': 0.295456784923382, '10': 0.7045028818254718, '11': 1.5120263659845063e-05} """ - Rx: the single qubit X rotation + assert self.n > 1, 'you need at least 2 qubits' + assert len(theta.shape) == 3, 'the shape of theta is not right' + assert theta.shape[2] == 1, 'the shape of theta is not right' + assert theta.shape[1] == self.n, 'the shape of theta is not right' + assert theta.shape[0] == depth, 'the depth of theta has a mismatch' + + for repeat in range(depth): + for i in range(self.n): + self.ry(theta=theta[repeat][i][0], which_qubit=i) + for i in range(self.n - 1): + self.cnot(control=[i, i + 1]) + self.cnot([self.n - 1, 0]) + + def complex_entangled_layer(self, theta, depth): + r"""添加一层包含U3门的强纠缠层。 + + Note: + 这一层量子门的数学表示形式为复数酉矩阵。 + + Attention: + ``theta`` 的维度为 ``(depth, n, 3)`` ,最低维内容为对应的 ``u3`` 的参数 ``(theta, phi, lam)`` + + Args: + theta (Variable): U3门的旋转角度 + depth (int): 纠缠层的深度 + + 代码示例: + + .. code-block:: python + + import numpy as np + from paddle import fluid + from paddle_quantum.circuit import UAnsatz + n = 2 + DEPTH = 3 + theta = np.ones([DEPTH, n, 1]) + with fluid.dygraph.guard(): + theta = fluid.dygraph.to_variable(theta) + cir = UAnsatz(n) + cir.complex_entangled_layer(fluid.dygraph.to_variable(theta), DEPTH) + cir.run_state_vector() + print(cir.measure(shots = 0)) + + :: + + {'00': 0.15032627279218896, '01': 0.564191201239618, '10': 0.03285998070292556, '11': 0.25262254526526823} """ + assert self.n > 1, 'you need at least 2 qubits' + assert len(theta.shape) == 3, 'the shape of theta is not right' + assert theta.shape[2] == 3, 'the shape of theta is not right' + assert theta.shape[1] == self.n, 'the shape of theta is not right' + assert theta.shape[0] == depth, 'the depth of theta has a mismatch' - transform = single_gate_construct( - rotation_x(theta), self.n, which_qubit) - self.state = matmul(self.state, transform) + for repeat in range(depth): + for i in range(self.n): + self.u3(theta[repeat][i][0], theta[repeat][i][1], theta[repeat][i][2], i) + for i in range(self.n - 1): + self.cnot([i, i + 1]) + self.cnot([self.n - 1, 0]) - def ry(self, theta, which_qubit): + def universal_2_qubit_gate(self, theta): + r"""添加2-qubit通用门,这个通用门需要15个参数。 + + Attention: + 只适用于量子比特数为2的量子线路。 + + Args: + theta (Variable): 2-qubit通用门的参数,其维度为 ``(15, )`` + + 代码示例: + + .. code-block:: python + + import numpy as np + from paddle import fluid + from paddle_quantum.circuit import UAnsatz + n = 2 + theta = np.ones(15) + with fluid.dygraph.guard(): + theta = fluid.dygraph.to_variable(theta) + cir = UAnsatz(n) + cir.universal_2_qubit_gate(fluid.dygraph.to_variable(theta)) + cir.run_state_vector() + print(cir.measure(shots = 0)) + + :: + + {'00': 0.4306256106527819, '01': 0.07994547866706268, '10': 0.07994547866706264, '11': 0.40948343201309334} """ - Ry: the single qubit Y rotation + assert self.n == 2, 'It only works on 2-qubit circuit' + assert len(theta.shape) == 1, 'The shape of theta is not right' + assert len(theta) == 15, 'This Ansatz accepts 15 parameters' + + self.u3(theta[0], theta[1], theta[2], 0) + self.u3(theta[3], theta[4], theta[5], 1) + + self.cnot([1, 0]) + + self.rz(theta[6], 0) + self.ry(theta[7], 1) + + self.cnot([0, 1]) + + self.ry(theta[8], 1) + + self.cnot([1, 0]) + + self.u3(theta[9], theta[10], theta[11], 0) + self.u3(theta[12], theta[13], theta[14], 1) + + def __add_real_block(self, theta, position): + r""" + Add a real block to the circuit in (position). theta is a one dimensional tensor + + Note: + 这是内部函数,你并不需要直接调用到该函数。 """ + assert len(theta) == 4, 'the length of theta is not right' + assert 0 <= position[0] < self.n and 0 <= position[1] < self.n, 'position is out of range' + self.ry(theta[0], position[0]) + self.ry(theta[1], position[1]) - transform = single_gate_construct( - rotation_y(theta), self.n, which_qubit) - self.state = matmul(self.state, transform) + self.cnot([position[0], position[1]]) - def rz(self, theta, which_qubit): + self.ry(theta[2], position[0]) + self.ry(theta[3], position[1]) + + def __add_complex_block(self, theta, position): + r""" + Add a complex block to the circuit in (position). theta is a one dimensional tensor + + Note: + 这是内部函数,你并不需要直接调用到该函数。 + """ + assert len(theta) == 12, 'the length of theta is not right' + assert 0 <= position[0] < self.n and 0 <= position[1] < self.n, 'position is out of range' + self.u3(theta[0], theta[1], theta[2], position[0]) + self.u3(theta[3], theta[4], theta[5], position[1]) + + self.cnot([position[0], position[1]]) + + self.u3(theta[6], theta[7], theta[8], position[0]) + self.u3(theta[9], theta[10], theta[11], position[1]) + + def __add_real_layer(self, theta, position): + r""" + Add a real layer on the circuit. theta is a two dimensional tensor. position is the qubit range the layer needs to cover + + Note: + 这是内部函数,你并不需要直接调用到该函数。 """ - Rz: the single qubit Z rotation + assert theta.shape[1] == 4 and theta.shape[0] == (position[1] - position[0] + 1) / 2,\ + 'the shape of theta is not right' + for i in range(position[0], position[1], 2): + self.__add_real_block(theta[int((i - position[0]) / 2)], [i, i + 1]) + + def __add_complex_layer(self, theta, position): + r""" + Add a complex layer on the circuit. theta is a two dimensional tensor. position is the qubit range the layer needs to cover + + Note: + 这是内部函数,你并不需要直接调用到该函数。 """ + assert theta.shape[1] == 12 and theta.shape[0] == (position[1] - position[0] + 1) / 2,\ + 'the shape of theta is not right' + for i in range(position[0], position[1], 2): + self.__add_complex_block(theta[int((i - position[0]) / 2)], [i, i + 1]) - transform = single_gate_construct( - rotation_z(theta), self.n, which_qubit) - self.state = matmul(self.state, transform) + def real_block_layer(self, theta, depth): + r"""添加一层包含Ry门的弱纠缠层。 - def cnot(self, control): + Note: + 这一层量子门的数学表示形式为实数酉矩阵。 + + Attention: + ``theta`` 的维度为 ``(depth, n-1, 4)`` + + Args: + theta(Variable): Ry门的旋转角度 + depth(int): 纠缠层的深度 + + 代码示例: + + .. code-block:: python + + n = 4 + DEPTH = 3 + theta = np.ones([DEPTH, n-1, 4]) + with fluid.dygraph.guard(): + theta = fluid.dygraph.to_variable(theta) + cir = UAnsatz(n) + cir.real_block_layer(fluid.dygraph.to_variable(theta), DEPTH) + cir.run_density_matrix() + print(cir.measure(shots = 0, which_qubits = [0])) + + :: + + {'0': 0.9646724056906162, '1': 0.035327594309385896} """ - :param control: [1,3], the 1st qubit controls 3rd qubit - :return: cnot module + assert self.n > 1, 'you need at least 2 qubits' + assert len(theta.shape) == 3, 'The dimension of theta is not right' + _depth, m, block = theta.shape + assert depth > 0, 'depth must be greater than zero' + assert _depth == depth, 'the depth of parameters has a mismatch' + assert m == self.n - 1 and block == 4, 'The shape of theta is not right' + + if self.n % 2 == 0: + for i in range(depth): + self.__add_real_layer(theta[i][:int(self.n / 2)], [0, self.n - 1]) + self.__add_real_layer(theta[i][int(self.n / 2):], [1, self.n - 2]) if self.n > 2 else None + else: + for i in range(depth): + self.__add_real_layer(theta[i][:int((self.n - 1) / 2)], [0, self.n - 2]) + self.__add_real_layer(theta[i][int((self.n - 1) / 2):], [1, self.n - 1]) + + def complex_block_layer(self, theta, depth): + r"""添加一层包含U3门的弱纠缠层。 + + Note: + 这一层量子门的数学表示形式为复数酉矩阵。 + + Attention: + ``theta`` 的维度为 ``(depth, n-1, 12)`` + + Args: + theta (Variable): U3门的角度信息 + depth (int): 纠缠层的深度 + + 代码示例: + + .. code-block:: python + + n = 4 + DEPTH = 3 + theta = np.ones([DEPTH, n-1, 12]) + with fluid.dygraph.guard(): + theta = fluid.dygraph.to_variable(theta) + cir = UAnsatz(n) + cir.complex_block_layer(fluid.dygraph.to_variable(theta), DEPTH) + cir.run_density_matrix() + print(cir.measure(shots = 0, which_qubits = [0])) + + :: + + {'0': 0.5271554811768046, '1': 0.4728445188231988} """ + assert self.n > 1, 'you need at least 2 qubits' + assert len(theta.shape) == 3, 'The dimension of theta is not right' + assert depth > 0, 'depth must be greater than zero' + _depth, m, block = theta.shape + assert _depth == depth, 'the depth of parameters has a mismatch' + assert m == self.n - 1 and block == 12, 'The shape of theta is not right' + + if self.n % 2 == 0: + for i in range(depth): + self.__add_complex_layer(theta[i][:int(self.n / 2)], [0, self.n - 1]) + self.__add_complex_layer(theta[i][int(self.n / 2):], [1, self.n - 2]) if self.n > 2 else None + else: + for i in range(depth): + self.__add_complex_layer(theta[i][:int((self.n - 1) / 2)], [0, self.n - 2]) + self.__add_complex_layer(theta[i][int((self.n - 1) / 2):], [1, self.n - 1]) + - cnot = dygraph.to_variable(cnot_construct(self.n, control)) - self.state = matmul(self.state, cnot) +def local_H_prob(cir, hamiltonian, shots=1024): + r""" + 构造出Pauli测量电路并测量ancilla,处理实验结果来得到 ``H`` (只有一项)期望值的实验测量值。 + + Note: + 这是内部函数,你并不需要直接调用到该函数。 + """ + # Add one ancilla, which we later measure and process the result + new_cir = UAnsatz(cir.n + 1) + input_state = pp_kron(cir.run_state_vector(store_state=False), init_state_gen(1)) + # Used in fixed Rz gate + _theta = fluid.dygraph.to_variable(np.array([-np.pi / 2])) + + op_list = hamiltonian.split(',') + # Set up pauli measurement circuit + for op in op_list: + element = op[0] + index = int(op[1:]) + if element == 'x': + new_cir.h(index) + new_cir.cnot([index, cir.n]) + elif element == 'z': + new_cir.cnot([index, cir.n]) + elif element == 'y': + new_cir.rz(_theta, index) + new_cir.h(index) + new_cir.cnot([index, cir.n]) + + new_cir.run_state_vector(input_state) + prob_result = new_cir.measure(shots=shots, which_qubits=[cir.n]) + if shots > 0: + if len(prob_result) == 1: + if '0' in prob_result: + result = (prob_result['0']) / shots + else: + result = (prob_result['1']) / shots + else: + result = (prob_result['0'] - prob_result['1']) / shots + else: + result = (prob_result['0'] - prob_result['1']) + + return result + + +def H_prob(cir, H, shots=1024): + r"""构造Pauli测量电路并测量关于H的期望值。 + + Args: + cir (UAnsatz): UAnsatz的一个实例化对象 + H (list): 记录哈密顿量信息的列表 + shots (int, optional): 默认为1024,表示测量次数;若为0,则表示返回测量期望值的精确值,即测量无穷次后的期望值 + + Returns: + float: 测量得到的H的期望值 + + 代码示例: + + .. code-block:: python + + import numpy as np + from paddle import fluid + from paddle_quantum.circuit import UAnsatz, H_prob + n = 4 + theta = np.ones(3) + experiment_shots = 2**10 + H_info = [[0.1, 'x2'], [0.3, 'y1,z3']] + input_state = np.ones(2**n)+0j + input_state = input_state / np.linalg.norm(input_state) + with fluid.dygraph.guard(): + theta = fluid.dygraph.to_variable(theta) + cir = UAnsatz(n) + cir.rx(theta[0], 0) + cir.ry(theta[1], 1) + cir.rz(theta[2], 1) + result_1 = H_prob(cir, H_info, shots = experiment_shots) + result_2 = H_prob(cir, H_info, shots = 0) + print(f'消耗 {experiment_shots} 次测量后的期望值实验值是 {result_1}') + print(f'H期望值精确值是 {result_2}') + + :: + + 消耗 1024 次测量后的期望值实验值是 0.2326171875 + H期望值精确值是 0.21242202548207134 + """ + expval = 0 + for term in H: + expval += term[0] * local_H_prob(cir, term[1], shots=shots) + return expval diff --git a/paddle_quantum/intrinsic.py b/paddle_quantum/intrinsic.py new file mode 100644 index 0000000000000000000000000000000000000000..69a960240e954c512dc941f3929c565e8cb76aa4 --- /dev/null +++ b/paddle_quantum/intrinsic.py @@ -0,0 +1,119 @@ +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math +import numpy as np +from numpy import binary_repr + +import paddle +import paddle.fluid as fluid +from paddle.fluid.framework import ComplexVariable +from paddle.complex.tensor.math import elementwise_mul, elementwise_add + + +def dic_between2and10(n): + r""" + :param n: number of qubits + :return: dictionary between binary and decimal + + for example: if n=3, the dictionary is + dic2to10: {'000': 0, '011': 3, '010': 2, '111': 7, '100': 4, '101': 5, '110': 6, '001': 1} + dic10to2: ['000', '001', '010', '011', '100', '101', '110', '111'] + + Note: + 这是内部函数,你并不需要直接调用到该函数。 + """ + dic2to10 = {} + dic10to2 = [None] * 2 ** n + + for i in range(2 ** n): + binary_text = binary_repr(i, width=n) + dic2to10[binary_text] = i + dic10to2[i] = binary_text + + return dic2to10, dic10to2 # the returned dic will have 2 ** n value + + +def single_H_vec_i(H, target_vec): + r""" + If H = 'x0,z1,z2,y3,z5', target_vec = '100111', then it returns H * target_vec, which we record as [1j, '000011'] + + Note: + 这是内部函数,你并不需要直接调用到该函数。 + """ + op_list = H.split(',') + coef = 1 + 0*1j # Coefficient for the vector + new_vec = list(target_vec) + for op in op_list: + pos = int(op[1:]) + if op[0] == 'x': + new_vec[pos] = '0' if target_vec[pos] == '1' else '1' + elif op[0] == 'y': + new_vec[pos] = '0' if target_vec[pos] == '1' else '1' + coef *= 1j if target_vec[pos] == '0' else -1j + elif op[0] == 'z': + new_vec[pos] = target_vec[pos] + coef *= 1 if target_vec[pos] == '0' else -1 + + return [coef, ''.join(new_vec)] + + +def single_H_vec(H, vec): + r""" + If vec is a paddle variable [a1, a2, a3, a4], and H = 'x0,z1', then it returns H * vec, + which is [a3, -a4, a1, -a2] + + Note: + 这是内部函数,你并不需要直接调用到该函数。 + """ + old_vec = vec.numpy() + new_vec = np.zeros(len(old_vec)) + 0j + dic2to10, dic10to2 = dic_between2and10(int(math.log(len(old_vec), 2))) + # Iterate through all vectors in the computational basis + for i in range(len(old_vec)): + # If old_vec[i] is 0, the result is 0 + if old_vec[i] != 0: + coef, target_update = single_H_vec_i(H, dic10to2[i]) + index_update = dic2to10[target_update] + new_vec[index_update] = coef * old_vec[i] + return fluid.dygraph.to_variable(new_vec) + + +def H_vec(H, vec): + r""" + If H = [[0.2, 'x0,z1'], [0.6, 'x0'], [0.1, 'z1'], [-0.7, 'y0,y1']], then it returns H * vec + + Note: + 这是内部函数,你并不需要直接调用到该函数。 + """ + coefs = fluid.dygraph.to_variable(np.array([coef for coef, Hi in H], dtype=np.float64)) + # Convert all strings to lowercase + H_list = [Hi.lower() for coef, Hi in H] + result = fluid.layers.zeros(shape=vec.shape, dtype='float64') + for i in range(len(coefs)): + xi = elementwise_mul(coefs[i], single_H_vec(H_list[i], vec)) + result = elementwise_add(result, xi) + return result + + +def vec_expecval(H, vec): + r""" + It returns expectation value of H with respect to vector vec + + Note: + 这是内部函数,你并不需要直接调用到该函数。 + """ + vec_conj = ComplexVariable(vec.real, -vec.imag) + result = paddle.complex.sum(elementwise_mul(vec_conj, H_vec(H, vec))) + return result diff --git a/paddle_quantum/state.py b/paddle_quantum/state.py new file mode 100644 index 0000000000000000000000000000000000000000..2d59a2739d86968ed271091f7a2664825a286fa8 --- /dev/null +++ b/paddle_quantum/state.py @@ -0,0 +1,203 @@ +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy +from numpy import concatenate +from numpy import trace as np_trace +from numpy import matmul as np_matmul +from numpy import random as np_random +from numpy import zeros as np_zeros +from numpy import eye as np_eye + +__all__ = [ + "vec", + "vec_random", + "w_state", + "density_op", + "density_op_random", + "completely_mixed_computational" +] + + +def vec(n): + r"""生成量子态 :math:`|00...0\rangle` 的numpy形式。 + + Args: + n(int): 量子比特数量 + + Returns: + numpy.ndarray: 一个形状为 ``(1, 2**n)`` 的numpy数组 ``[[1, 0, 0, ..., 0]]`` + + 代码示例: + + .. code-block:: python + + from paddle_quantum.state import vec + vector = vec(3) + print(vector) + + :: + + [[1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]] + """ + assert n > 0, 'qubit number must be larger than 1' + state = concatenate(([[1.0]], np_zeros([1, 2**n- 1])), axis=1) + return state.astype("complex128") + + +def vec_random(n, real_or_complex=2): + r"""随机生成一个量子态,使用numpy形式。 + + Args: + n (int): 量子比特数量 + real_or_complex (int, optional): 默认为2,即生成复数组;若为1,则生成实数组 + + Returns: + numpy.ndarray: 一个形状为 ``(1, 2**n)`` 的numpy数组 + """ + assert n > 0, 'qubit number must be larger than 1' + assert real_or_complex == 1 or real_or_complex == 2, 'real_or_complex must be 1 or 2' + # real + if real_or_complex == 1: + psi = np_random.randn(1, 2**n) + # complex + else: + psi = np_random.randn(1, 2**n) + 1j * np_random.randn(1, 2**n) + psi = psi/numpy.linalg.norm(psi) + + return psi.astype("complex128") + + +def w_state(n, coeff=None): + r"""生成一个W-state的numpy形式。 + + Args: + n (int): 量子比特数量 + coeff (numpy.ndarray, optional): 默认为 ``None`` ,即生成平均概率幅(系数) + + Returns: + numpy.ndarray: 一个形状为 ``(1, 2**n)`` 的numpy数组 + + 代码示例: + + .. code-block:: python + + from paddle_quantum.state import w_state + vector = w_state(3) + print(vector) + + :: + + [[0. +0.j 0.57735027+0.j 0.57735027+0.j 0. +0.j + 0.57735027+0.j 0. +0.j 0. +0.j 0. +0.j]] + """ + assert n > 0, 'qubit number must be larger than 1' + + c = coeff if coeff is not None else numpy.ones((1, 2**n))/numpy.sqrt(n) + assert c.shape[0] == 1 and c.shape[1] == 2**n, 'The dimension of coeff is not right' + + state = np_zeros((1, 2**n)) + for i in range(n): + state[0][2**i] = c[0][n-i-1] + + return state.astype("complex128") + + +def density_op(n): + r"""生成密度矩阵 :math:`|00..0\rangle \langle00..0|` 的numpy形式。 + + Args: + n (int): 量子比特数量 + + Returns: + numpy.ndarray: 一个形状为 ``(2**n, 2**n)`` 的numpy数组 + + 代码示例: + + .. code-block:: python + + from paddle_quantum.state import density_op + state = density_op(2) + print(state) + + :: + + [[1.+0.j 0.+0.j 0.+0.j 0.+0.j] + [0.+0.j 0.+0.j 0.+0.j 0.+0.j] + [0.+0.j 0.+0.j 0.+0.j 0.+0.j] + [0.+0.j 0.+0.j 0.+0.j 0.+0.j]] + + """ + assert n > 0, 'qubit number must be positive' + rho = np_zeros((2**n, 2**n)) + rho[0, 0] = 1 + + return rho.astype("complex128") + + +def density_op_random(n, real_or_complex=2, rank=None): + r"""随机生成一个密度矩阵的numpy形式。 + + Args: + n (int): 量子比特数量 + real_or_complex (int, optional): 默认为2,即生成复数组,若为1,则生成实数组 + rank (int, optional): 矩阵的秩,默认为 :math:`2^n` (当rank为 ``None`` 时) + + Returns: + numpy.ndarray: 一个形状为 ``(2**n, 2**n)`` 的numpy数组 + """ + assert n > 0, 'qubit number must be positive' + rank = rank if rank is not None else 2**n + assert 0 < rank <= 2**n, 'rank is an invalid number' + + if real_or_complex == 1: + psi = np_random.randn(2**n, rank) + else: + psi = np_random.randn(2**n, rank) + 1j*np_random.randn(2**n, rank) + + psi_dagger = psi.conj().T + rho = np_matmul(psi, psi_dagger) + rho = rho/np_trace(rho) + + return rho.astype('complex128') + + +def completely_mixed_computational(n): + r"""生成完全混合态的密度矩阵的numpy形式。 + + Args: + n (int): 量子比特数量 + + Returns: + numpy.ndarray: 一个形状为 ``(2**n, 2**n)`` 的numpy数组 + + 代码示例: + + .. code-block:: python + + from paddle_quantum.state import completely_mixed_computational + state = completely_mixed_computational(2) + print(state) + + :: + + [[0.25+0.j 0. +0.j 0. +0.j 0. +0.j] + [0. +0.j 0.25+0.j 0. +0.j 0. +0.j] + [0. +0.j 0. +0.j 0.25+0.j 0. +0.j] + [0. +0.j 0. +0.j 0. +0.j 0.25+0.j]] + """ + assert n > 0, 'qubit number must be positive' + rho = np_eye(2**n)/2**n + + return rho.astype('complex128') diff --git a/paddle_quantum/utils.py b/paddle_quantum/utils.py index 9126de73c45910939bb8d1bd22450fa7c04ca371..d400aafa5590c457f7eadc28cc4b628cf0ab220b 100644 --- a/paddle_quantum/utils.py +++ b/paddle_quantum/utils.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,14 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" -function -""" - from functools import reduce +import numpy +from numpy import absolute, log from numpy import diag, dot, identity from numpy import kron as np_kron +from numpy import trace as np_trace +from numpy import matmul as np_matmul +from numpy import random as np_random from numpy import linalg, sqrt from numpy import sum as np_sum from numpy import transpose as np_transpose @@ -35,133 +36,282 @@ from paddle.fluid.framework import ComplexVariable from paddle.fluid.layers import concat, cos, ones, reshape, sin from paddle.fluid.layers import zeros as pp_zeros +from scipy.linalg import logm, sqrtm + __all__ = [ - "rotation_x", - "rotation_y", - "rotation_z", "partial_trace", - "compute_fid", + "state_fidelity", + "gate_fidelity", + "purity", + "von_neumann_entropy", + "relative_entropy", "NKron", + "hermitian", + "random_pauli_str_generator", + "pauli_str_to_matrix" ] -def rotation_x(theta): - """ - :param theta: must be a scale, shape [1], 'float32' - :return: +def partial_trace(rho_AB, dim1, dim2, A_or_B): + r"""计算量子态的偏迹。 + + Args: + rho_AB (ComplexVariable): 输入的量子态 + dim1 (int): 系统A的维数 + dim2 (int): 系统B的维数 + A_or_B (int): 1或者2,1表示去除A,2表示去除B + + Returns: + ComplexVariable: 量子态的偏迹 + """ + if A_or_B == 2: + dim1, dim2 = dim2, dim1 + + idty_np = identity(dim2).astype("complex128") + idty_B = to_variable(idty_np) + + zero_np = np_zeros([dim2, dim2], "complex128") + res = to_variable(zero_np) + + for dim_j in range(dim1): + row_top = pp_zeros([1, dim_j], dtype="float64") + row_mid = ones([1, 1], dtype="float64") + row_bot = pp_zeros([1, dim1 - dim_j - 1], dtype="float64") + bra_j_re = concat([row_top, row_mid, row_bot], axis=1) + bra_j_im = pp_zeros([1, dim1], dtype="float64") + bra_j = ComplexVariable(bra_j_re, bra_j_im) - cos_value = cos(theta/2) - sin_value = sin(theta/2) - zero_pd = pp_zeros([1], "float32") - rx_re = concat([cos_value, zero_pd, zero_pd, cos_value], axis=0) - rx_im = concat([zero_pd, -sin_value, -sin_value, zero_pd], axis=0) + if A_or_B == 1: + row_tmp = pp_kron(bra_j, idty_B) + res = elementwise_add(res, matmul(matmul(row_tmp, rho_AB), + pp_transpose(ComplexVariable(row_tmp.real, -row_tmp.imag), + perm=[1, 0]), ), ) + + if A_or_B == 2: + row_tmp = pp_kron(idty_B, bra_j) + res = elementwise_add(res, matmul(matmul(row_tmp, rho_AB), + pp_transpose(ComplexVariable(row_tmp.real, -row_tmp.imag), + perm=[1, 0]), ), ) + + return res + + +def state_fidelity(rho, sigma): + r"""计算两个量子态的保真度。 - return ComplexVariable(reshape(rx_re, [2, 2]), reshape(rx_im, [2, 2])) + .. math:: + F(\rho, \sigma) = \text{tr}(\sqrt{\sqrt{\rho}\sigma\sqrt{\rho}}) + Args: + rho (numpy.ndarray): 量子态的密度矩阵形式 + sigma (numpy.ndarray): 量子态的密度矩阵形式 -def rotation_y(theta): + Returns: + float: 输入的量子态之间的保真度 """ - :param theta: must be a scale, shape [1], 'float32' - :return: + assert rho.shape == sigma.shape, 'The shape of two quantum states are different' + fidelity = numpy.trace(sqrtm(sqrtm(rho) @ sigma @ sqrtm(rho))).real + + return fidelity + + +def gate_fidelity(U, V): + r"""计算两个量子门的保真度。 + + .. math:: + + F(U, V) = |\text{tr}(UV^\dagger)|/2^n + + :math:`U` 是一个 :math:`2^n\times 2^n` 的 Unitary 矩阵。 + + Args: + U (numpy.ndarray): 量子门的酉矩阵形式 + V (numpy.ndarray): 量子门的酉矩阵形式 + + Returns: + float: 输入的量子门之间的保真度 """ + assert U.shape == V.shape, 'The shape of two unitary matrices are different' + dim = U.shape[0] + fidelity = absolute(np_trace(np_matmul(U, V.conj().T)))/dim + + return fidelity - cos_value = cos(theta/2) - sin_value = sin(theta/2) - ry_re = concat([cos_value, -sin_value, sin_value, cos_value], axis=0) - ry_im = pp_zeros([2, 2], "float32") - return ComplexVariable(reshape(ry_re, [2, 2]), ry_im) +def purity(rho): + r"""计算量子态的纯度。 -def rotation_z(theta): + .. math:: + + P = \text{tr}(\rho^2) + + Args: + rho (numpy.ndarray): 量子态的密度矩阵形式 + + Returns: + float: 输入的量子态的纯度 """ - :param theta: must be a scale, shape [1], 'float32' - :return: + gamma = np_trace(np_matmul(rho, rho)) + + return gamma.real + + +def von_neumann_entropy(rho): + r"""计算量子态的冯诺依曼熵。 + + .. math:: + + S = -\text{tr}(\rho \log(\rho)) + + Args: + rho(numpy.ndarray): 量子态的密度矩阵形式 + + Returns: + float: 输入的量子态的冯诺依曼熵 """ + rho_eigenvalue, _ = linalg.eig(rho) + entropy = -np_sum(rho_eigenvalue*log(rho_eigenvalue)) + + return entropy.real - cos_value = cos(theta/2) - sin_value = sin(theta/2) - zero_pd = pp_zeros([1], "float32") - rz_re = concat([cos_value, zero_pd, zero_pd, cos_value], axis=0) - rz_im = concat([-sin_value, zero_pd, zero_pd, sin_value], axis=0) - return ComplexVariable(reshape(rz_re, [2, 2]), reshape(rz_im, [2, 2])) +def relative_entropy(rho, sig): + r"""计算两个量子态的相对熵。 + .. math:: -def partial_trace(rho_AB, dim1, dim2, A_or_B): + S(\rho \| \sigma)=\text{tr} \rho(\log \rho-\log \sigma) + + Args: + rho (numpy.ndarray): 量子态的密度矩阵形式 + sig (numpy.ndarray): 量子态的密度矩阵形式 + + Returns: + float: 输入的量子态之间的相对熵 """ - :param rho_AB: the input density matrix - :param dim1: dimension for system A - :param dim2: dimension for system B - :param A_or_B: 1 or 2, choose the system that you want trace out. - :return: partial trace + assert rho.shape == sig.shape, 'The shape of two quantum states are different' + res = numpy.trace(rho @ logm(rho) - rho @ logm(sig)) + return res.real + + +def NKron(matrix_A, matrix_B, *args): + r"""计算两个及以上的矩阵的Kronecker积。 + + Args: + matrix_A (numpy.ndarray): 一个矩阵 + matrix_B (numpy.ndarray): 一个矩阵 + *args (numpy.ndarray): 其余矩阵 + + Returns: + ComplexVariable: 输入矩阵的Kronecker积 + + .. code-block:: python + + from paddle_quantum.state import density_op_random + from paddle_quantum.utils import NKron + A = density_op_random(2) + B = density_op_random(2) + C = density_op_random(2) + result = NKron(A, B, C) + + ``result`` 应为: :math:`A \otimes B \otimes C` """ + return reduce(lambda result, index: np_kron(result, index), args, np_kron(matrix_A, matrix_B), ) - # dim_total = dim1 * dim2 - if A_or_B == 2: - dim1, dim2 = dim2, dim1 - idty_np = identity(dim2).astype("complex64") - idty_B = to_variable(idty_np) +def hermitian(matrix): + r"""计算矩阵的埃尔米特转置,即Hermitian transpose。 - zero_np = np_zeros([dim2, dim2], "complex64") - res = to_variable(zero_np) + Args: + matrix (ComplexVariable): 需要埃尔米特转置的矩阵 - for dim_j in range(dim1): - row_top = pp_zeros([1, dim_j], dtype="float32") - row_mid = ones([1, 1], dtype="float32") - row_bot = pp_zeros([1, dim1 - dim_j - 1], dtype="float32") - bra_j_re = concat([row_top, row_mid, row_bot], axis=1) - bra_j_im = pp_zeros([1, dim1], dtype="float32") - bra_j = ComplexVariable(bra_j_re, bra_j_im) + Returns: + ComplexVariable: 输入矩阵的埃尔米特转置 - if A_or_B == 1: - row_tmp = pp_kron(bra_j, idty_B) - res = elementwise_add( - res, - matmul( - matmul(row_tmp, rho_AB), - pp_transpose( - ComplexVariable(row_tmp.real, -row_tmp.imag), - perm=[1, 0]), ), ) + 代码示例: - if A_or_B == 2: - row_tmp = pp_kron(idty_B, bra_j) - res += matmul( - matmul(row_tmp, rho_AB), - pp_transpose( - ComplexVariable(row_tmp.real, -row_tmp.imag), perm=[1, 0]), - ) - return res + .. code-block:: python + + from paddle_quantum.utils import hermitian + from paddle import fluid + import numpy as np + with fluid.dygraph.guard(): + rho = fluid.dygraph.to_variable(np.array([[1+1j, 2+2j], [3+3j, 4+4j]])) + print(hermitian(rho).numpy()) + :: -def compute_fid(rho, sigma): - """ - compute_fid: compute the fidelity between two density operators - :param rho: - :param sigma: - :return: + [[1.-1.j 3.-3.j] + [2.-2.j 4.-4.j]] """ + matrix_dagger = pp_transpose(ComplexVariable(matrix.real, -matrix.imag), perm=[1, 0]) + + return matrix_dagger - rho_eigenval, rho_eigenmatrix = linalg.eig(rho) - sr_rho = dot( - dot(rho_eigenmatrix, diag(sqrt(rho_eigenval.real + 1e-7))), - np_transpose(rho_eigenmatrix).conjugate(), ) - rho_sigma_rho = dot(dot(sr_rho, sigma), sr_rho) - return np_sum(sqrt(linalg.eigvals(rho_sigma_rho).real + 1e-7)) +def random_pauli_str_generator(n, terms=3): + r"""随机生成一个可观测量(observable)的列表( ``list`` )形式。 + 一个可观测量 :math:`O=0.3X\otimes I\otimes I+0.5Y\otimes I\otimes Z` 的 + 列表形式为 ``[[0.3, 'x0'], [0.5, 'y0,z2']]`` 。这样一个可观测量是由 + 调用 ``random_pauli_str_generator(3, terms=2)`` 生成的。 -def NKron(AMatrix, BMatrix, *args): + Args: + n (int): 量子比特数量 + terms (int, optional): 可观测量的项数 + + Returns: + list: 随机生成的可观测量的列表形式 """ - Recursively execute kron n times. This function at least has two matrices. - :param AMatrix: First matrix - :param BMatrix: Second matrix - :param args: If have more matrix, they are delivered by this matrix - :return: The result of tensor product. + pauli_str = [] + for sublen in np_random.randint(1, high=n+1, size=terms): + # Tips: -1 <= coef < 1 + coef = np_random.rand()*2-1 + ops = np_random.choice(['x', 'y', 'z'], size=sublen) + pos = np_random.choice(range(n), size=sublen, replace=False) + op_list = [ops[i]+str(pos[i]) for i in range(sublen)] + pauli_str.append([coef, ','.join(op_list)]) + return pauli_str + + +def pauli_str_to_matrix(pauli_str, n): + r"""将输入的可观测量(observable)的列表( ``list`` )形式转换为其矩阵形式。 + + 如输入的 ``pauli_str`` 为 ``[[0.7, 'z0,x1'], [0.2, 'z1']]`` 且 ``n=3`` , + 则此函数返回可观测量 :math:`0.7Z\otimes X\otimes I+0.2I\otimes Z\otimes I` 的 + 矩阵形式。 + + Args: + pauli_str (list): 一个可观测量的列表形式 + n (int): 量子比特数量 + + Returns: + numpy.ndarray: 输入列表对应的可观测量的矩阵形式 """ - - return reduce( - lambda result, index: np_kron(result, index), - args, - np_kron(AMatrix, BMatrix), ) + pauli_dict = {'i': numpy.eye(2) + 0j, 'x': numpy.array([[0, 1], [1, 0]]) + 0j, + 'y': numpy.array([[0, -1j], [1j, 0]]), 'z': numpy.array([[1, 0], [0, -1]]) + 0j} + + # Parse pauli_str; 'x0,z1,y4' to 'xziiy' + new_pauli_str = [] + for coeff, op_str in pauli_str: + init = list('i'*n) + op_list = op_str.split(',') + for op in op_list: + pos = int(op[1:]) + assert pos < n, 'n is too small' + init[pos] = op[0] + new_pauli_str.append([coeff, ''.join(init)]) + + # Convert new_pauli_str to matrix; 'xziiy' to NKron(x, z, i, i, y) + matrices = [] + for coeff, op_str in new_pauli_str: + sub_matrices = [] + for op in op_str: + sub_matrices.append(pauli_dict[op]) + if len(op_str) == 1: + matrices.append(coeff * sub_matrices[0]) + else: + matrices.append(coeff * NKron(sub_matrices[0], sub_matrices[1], *sub_matrices[2:])) + + return sum(matrices) diff --git a/requirements.txt b/requirements.txt index 4eba1412a7b79948588431edd8b5e1f7c2a31553..cc5eb6e473dcd86841c216103ac4e555c2cfffec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ -paddlepaddle>=1.8.0 -networkx -matplotlib \ No newline at end of file +paddlepaddle>=1.8.3 +networkx>=2.4 +matplotlib>=3.3.0 +interval>=1.0.0 +progressbar>=2.5 \ No newline at end of file diff --git a/setup.py b/setup.py index 940f1d6720acb691f4527b1aea96a8b8f4fd10de..3fb427a48f8edbd6c473256580d0c0d0230a9e2a 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Paddle Quantum Authors. All Rights Reserved. +# Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,10 +20,10 @@ from setuptools import setup setup( name='paddle_quantum', - version='1.0.0', + version='1.1.0', description='Paddle Quantum circuit and function', author='Institute for Quantum Computing, Baidu INC.', author_email='quantum@baidu.com', - url='http://quantum-hub.baidu.com', + url='http://quantum.baidu.com', packages=['paddle_quantum'], - install_requires=['paddlepaddle>=1.8.0', 'networkx', 'matplotlib']) + install_requires=['paddlepaddle>=1.8.3', 'networkx>=2.4', 'matplotlib>=3.3.0', 'interval>=1.0.0', 'progressbar>=2.5']) diff --git a/tutorial/Barren/BarrenPlateaus_Tutorial_CN.ipynb b/tutorial/Barren/BarrenPlateaus_Tutorial_CN.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..3b468bedf4324164244d4332a33e5d83d94748d5 --- /dev/null +++ b/tutorial/Barren/BarrenPlateaus_Tutorial_CN.ipynb @@ -0,0 +1,625 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 量子神经网络的贫瘠高原效应 (Barren Plateaus)\n", + "\n", + " Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 概览\n", + "\n", + "在经典神经网络的训练中,基于梯度的优化方法不仅仅会遇到局部最小值的问题,同时还要面对在鞍点附近梯度消失的问题。相对应的,在量子神经网络中也存在着一种**贫瘠高原效应**(Barren plateaus)。这个奇特的现象首先是由 McClean et al. 在 2018 年发现的 [1][[arXiv:1803.11173]](https://arxiv.org/abs/1803.11173)。简单来说,当你选取的随机电路结构满足一定复杂程度时,优化曲面(Optimization landscape)会变得很平坦, 从而导致基于梯度下降的优化方法很难找到全局最小值。对于大多数的变分量子算法(VQE等等),这个现象意味着当量子比特数量越来越多时,选取随机结构的电路有可能效果并不好。这会让你设计的损失函数所对应的优化曲面变成一个巨大的高原,从而导致量子神经网络的训练愈加困难。你随机找到的初始值很难逃离这个高原,梯度下降收敛速度会很缓慢。\n", + "\n", + "\n", + "\n", + "                                \n", + "图片由 [Gradient Descent Viz](https://github.com/lilipads/gradient_descent_viz) 生成\n", + "\n", + "本教程主要讨论如何在量桨(PaddleQuantum)平台上展示贫瘠高原这一现象。其中并不涉及任何算法创新,但能提升读者对于量子神经网络训练中梯度问题的认识。首先我们先引入必要的 library和 package:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "import numpy as np\n", + "\n", + "from progressbar import ProgressBar\n", + "from matplotlib import pyplot as plt \n", + "from scipy.stats import unitary_group \n", + "\n", + "import paddle.fluid as fluid\n", + "from paddle.fluid.framework import ComplexVariable\n", + "from paddle.complex import matmul, transpose \n", + "from paddle_quantum.circuit import UAnsatz\n", + "from paddle_quantum.utils import hermitian\n", + "from paddle_quantum.state import density_op" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 随机的网络结构\n", + "\n", + "这里我们按照原作者 McClean (2018)论文中提及的类似方法搭建如下随机电路(目前我们不支持内置的 control-Z 门,方便起见用 CNOT 替代):\n", + "\n", + "\n", + "首先作用在所有量子比特上绕布洛赫球的 Y-轴旋转 $\\pi/4$。\n", + "\n", + "以上结构加起来构成一个模块(Block), 每个模块共分为两层:\n", + "\n", + "- 第一层搭建随机的旋转门, 其中 $R_{\\ell,n} \\in \\{R_x, R_y, R_z\\}$。下标 $\\ell$ 表示处于第 $\\ell$ 个重复的模块, 上图中 $\\ell =1$。第二个下标 $n$ 表示作用在第几个量子比特上。\n", + "- 第二层由 CNOT 门组成,作用在每两个相邻的量子比特上。\n", + "\n", + "在量桨中, 我们可以这么搭建。\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "def rand_circuit(theta, target, num_qubits):\n", + " \n", + " # 我们需要将 Numpy array 转换成 Paddle 动态图模式中支持的 variable\n", + " const = fluid.dygraph.to_variable(np.array([np.pi/4]))\n", + " \n", + " # 初始化量子电路\n", + " cir = UAnsatz(num_qubits)\n", + " \n", + " # ============== 第一层 ==============\n", + " # 固定角度的 Ry 旋转门\n", + " for i in range(num_qubits):\n", + " cir.ry(const, i)\n", + "\n", + " # ============== 第二层 ==============\n", + " # target是一个随机的数组,用来帮助我们抽取随机的单比特门 \n", + " for i in range(num_qubits):\n", + " if target[i] == 0:\n", + " cir.rz(theta[i], i)\n", + " elif target[i] == 1:\n", + " cir.ry(theta[i], i)\n", + " else:\n", + " cir.rx(theta[i], i)\n", + " \n", + "\n", + " # ============== 第三层 ==============\n", + " # 搭建两两相邻的 CNOT 门\n", + " for i in range(num_qubits - 1):\n", + " cir.cnot([i, i + 1])\n", + " \n", + " return cir.U" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 损失函数与优化曲面 \n", + "\n", + "当我们确定了电路的结构之后,我们还需要定义一个损失函数(Loss function)来确定优化曲面(Optimization landscape)。按照原作者 McClean (2018)论文中提及的,我们采用 VQE算法中常用的损失函数:\n", + "$$\n", + "\\mathcal{L}= \\langle{0} \\lvert U^{\\dagger}(\\boldsymbol{\\theta})H U(\\boldsymbol{\\theta}) \\lvert {0}\\rangle\n", + "$$\n", + "\n", + "其中的酉矩阵 $U(\\boldsymbol{\\theta})$ 就是我们上一部分搭建的带有随机结构的量子神经网络。对于其中的哈密顿量 $H$ 我们不妨先取最简单的形式 $H = \\lvert {00\\cdots 0}\\rangle\\langle{00\\cdots 0} \\lvert$。设定好这些后,我们就可以从最简单的两个量子比特的情形开始采样了 -- 生成 300 组随机网络结构和不同的随机初始参数 $\\{\\theta_{\\ell,n}^{(i)}\\}_{i=1}^{300}$。每次计算梯度都是按照 VQE 的解析梯度公式计算关于**第一个参数 $\\theta_{1,1}$**的偏导数。然后统计得到的这 300 个梯度的平均值和方差。其中解析梯度的公式为:\n", + "\n", + "$$\n", + "\\partial \\theta_{j} \n", + "\\equiv \\frac{\\partial \\mathcal{L}}{\\partial \\theta_j}\n", + "= \\frac{1}{2} \\big[\\mathcal{L}(\\theta_j + \\frac{\\pi}{2}) - \\mathcal{L}(\\theta_j - \\frac{\\pi}{2})\\big]\n", + "$$\n", + "\n", + "具体推导请参见:[arXiv:1803.00745](https://arxiv.org/abs/1803.00745)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "主程序段总共运行了 9.175814151763916 秒\n", + "采样 300 个随机网络关于第一个参数梯度的均值是: 0.005925709445960606\n", + "采样 300 个随机网络关于第一个参数梯度的方差是: 0.028249053148446363\n" + ] + } + ], + "source": [ + "# 超参数设置\n", + "np.random.seed(42) # 固定 Numpy 的随机种子\n", + "N = 2 # 设置量子比特数量 \n", + "samples = 300 # 设定采样随机网络结构的数量\n", + "THETA_SIZE = N # 设置参数 theta 的大小\n", + "ITR = 1 # 设置迭代次数\n", + "LR = 0.2 # 设定学习速率\n", + "SEED = 1 # 固定优化器中随机初始化的种子\n", + "\n", + "# 初始化梯度数值的寄存器\n", + "grad_info = []\n", + "\n", + "class manual_gradient(fluid.dygraph.Layer):\n", + " \n", + " # 初始化一个长度为 THETA_SIZE 的可学习参数列表,并用 [0, 2*pi] 的均匀分布来填充初始值\n", + " def __init__(self, shape, param_attr=fluid.initializer.Uniform(\n", + " low=0.0, high=2 * np.pi, seed=1),dtype='float64'):\n", + " super(manual_gradient, self).__init__()\n", + " \n", + " # 我们需要将 Numpy array 转换成 Paddle 动态图模式中支持的 variable\n", + " self.H = fluid.dygraph.to_variable(density_op(N))\n", + " \n", + " # 定义损失函数和前向传播机制 \n", + " def forward(self):\n", + " \n", + " # 初始化三个 theta 参数列表\n", + " theta_np = np.random.uniform(\n", + " low=0., high= 2 * np.pi, size=(THETA_SIZE))\n", + " theta_plus_np = np.copy(theta_np) \n", + " theta_minus_np = np.copy(theta_np) \n", + " \n", + " # 修改用以计算解析梯度\n", + " theta_plus_np[0] += np.pi/2\n", + " theta_minus_np[0] -= np.pi/2\n", + " \n", + " # 我们需要将 Numpy array 转换成 Paddle 动态图模式中支持的 variable\n", + " theta = fluid.dygraph.to_variable(theta_np)\n", + " theta_plus = fluid.dygraph.to_variable(theta_plus_np)\n", + " theta_minus = fluid.dygraph.to_variable(theta_minus_np)\n", + " \n", + " # 生成随机目标,在 rand_circuit 中随机选取电路门\n", + " target = np.random.choice(3, N) \n", + " \n", + " U = rand_circuit(theta, target, N)\n", + " U_dagger = hermitian(U) \n", + " U_plus = rand_circuit(theta_plus, target, N)\n", + " U_plus_dagger = hermitian(U_plus) \n", + " U_minus = rand_circuit(theta_minus, target, N)\n", + " U_minus_dagger = hermitian(U_minus) \n", + "\n", + " # 计算解析梯度\n", + " grad = ( matmul(matmul(U_plus_dagger, self.H), U_plus).real[0][0] \n", + " - matmul(matmul(U_minus_dagger, self.H), U_minus).real[0][0])/2 \n", + " return grad\n", + "\n", + "# 定义主程序段\n", + "def main():\n", + " \n", + " # 初始化paddle动态图机制\n", + " with fluid.dygraph.guard():\n", + " \n", + " sampling = manual_gradient(shape=[THETA_SIZE])\n", + " \n", + " # 采样获得梯度信息\n", + " grad = sampling() \n", + " \n", + " return grad.numpy()\n", + "\n", + "\n", + "# 记录运行时间\n", + "time_start = time.time()\n", + "\n", + "# 开始采样\n", + "for i in range(samples):\n", + " if __name__ == '__main__':\n", + " grad = main()\n", + " grad_info.append(grad)\n", + "\n", + "time_span = time.time() - time_start \n", + "print('主程序段总共运行了', time_span, '秒')\n", + "print(\"采样\", samples, \"个随机网络关于第一个参数梯度的均值是:\", np.mean(grad_info))\n", + "print(\"采样\", samples, \"个随机网络关于第一个参数梯度的方差是:\", np.var(grad_info))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 优化曲面的可视化\n", + "\n", + "接下来我们试着利用 Matplotlib来可视化这个优化曲面(Optimization landscape)。在**两个量子比特**的情况下,我们只有两个参数 $\\theta_1$ 和 $\\theta_2$, 并且第二层的随机电路电路总共有9种情况。这个很容易画出来:\n", + "\n", + "\n", + "\n", + "可以看到的是最后一张图中 $R_z$-$R_z$ 结构所展示出的高原结构是我们非常不想见到的。在这种情况下,我们不能收敛到理论最小值。如果你想自己试着画一些优化曲面,不妨参见以下代码:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "主程序段总共运行了 3.2303848266601562 秒\n" + ] + } + ], + "source": [ + "# 引入必要的 package\n", + "from matplotlib import cm\n", + "from mpl_toolkits.mplot3d import Axes3D\n", + "from matplotlib.ticker import LinearLocator, FormatStrFormatter\n", + "\n", + "time_start = time.time()\n", + "N = 2\n", + "\n", + "# 设置图像比例 竖:横 = 0.3 \n", + "fig = plt.figure(figsize=plt.figaspect(0.3))\n", + "\n", + "# 生成 x, y 轴上的点\n", + "X = np.linspace(0, 2*np.pi, 80)\n", + "Y = np.linspace(0, 2*np.pi, 80)\n", + "\n", + "# 生成 2D 网格 (mesh)\n", + "xx, yy = np.meshgrid(X, Y)\n", + "\n", + "\n", + "# 定义必要的逻辑门\n", + "def rx(theta):\n", + " mat = np.array([[np.cos(theta/2), -1j*np.sin(theta/2)],\n", + " [-1j*np.sin(theta/2), np.cos(theta/2)]])\n", + " return mat\n", + "\n", + "def ry(theta):\n", + " mat = np.array([[np.cos(theta/2), -1*np.sin(theta/2)],\n", + " [np.sin(theta/2), np.cos(theta/2)]])\n", + " return mat\n", + "\n", + "def rz(theta):\n", + " mat = np.array([[np.exp(-1j*theta/2), 0],\n", + " [0, np.exp(1j*theta/2)]])\n", + " return mat\n", + "\n", + "def CNOT():\n", + " mat = np.array([[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]])\n", + " return mat\n", + "\n", + "# ============= 第一张图 =============\n", + "# 我们可视化第二层是 kron(Ry, Ry) 的情况\n", + "ax = fig.add_subplot(1, 2, 1, projection='3d')\n", + "\n", + "# 向前传播计算损失函数:\n", + "def cost_yy(para):\n", + " L1 = np.kron(ry(np.pi/4), ry(np.pi/4))\n", + " L2 = np.kron(ry(para[0]), ry(para[1]))\n", + " U = np.matmul(np.matmul(L1, L2), CNOT())\n", + " H = np.zeros((2 ** N, 2 ** N))\n", + " H[0, 0] = 1\n", + " val = (U.conj().T @ H@ U).real[0][0]\n", + " return val\n", + "\n", + "# 画出图像\n", + "Z = np.array([[cost_yy([x, y]) for x in X] for y in Y]).reshape(len(Y), len(X))\n", + "surf = ax.plot_surface(xx, yy, Z, cmap='plasma')\n", + "#cset = ax.contourf(xx, yy, Z, zdir='z', offset=np.min(Z), cmap='viridis')\n", + "ax.set_xlabel(r\"$\\theta_1$\")\n", + "ax.set_ylabel(r\"$\\theta_2$\")\n", + "ax.set_title(\"Optimization Landscape for Ry-Ry Layer\")\n", + "#fig.colorbar(surf, shrink=0.5, aspect=5)\n", + "\n", + "# ============= 第二张图 =============\n", + "# 我们可视化第二层是 kron(Rx, Rz) 的情况\n", + "ax = fig.add_subplot(1, 2, 2, projection='3d')\n", + "\n", + "def cost_xz(para):\n", + " L1 = np.kron(ry(np.pi/4), ry(np.pi/4))\n", + " L2 = np.kron(rx(para[0]), rz(para[1]))\n", + " U = np.matmul(np.matmul(L1, L2), CNOT())\n", + " H = np.zeros((2 ** N, 2 ** N))\n", + " H[0, 0] = 1\n", + " val = (U.conj().T @ H@ U).real[0][0]\n", + " return val\n", + "\n", + "Z = np.array([[cost_xz([x, y]) for x in X] for y in Y]).reshape(len(Y), len(X))\n", + "surf = ax.plot_surface(xx, yy, Z, cmap='viridis')\n", + "#cset = ax.contourf(xx, yy, Z, zdir='z', offset=np.min(Z), cmap='viridis')\n", + "ax.set_xlabel(r\"$\\theta_1$\")\n", + "ax.set_ylabel(r\"$\\theta_2$\")\n", + "ax.set_title(\"Optimization Landscape for Rx-Rz Layer\")\n", + "\n", + "\n", + "plt.show()\n", + "\n", + "time_span = time.time() - time_start \n", + "print('主程序段总共运行了', time_span, '秒')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 更多的量子比特\n", + "\n", + "接着我们再看看不断的增加量子比特的数量,会对我们的梯度带来什么影响。" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100% |########################################################################|\n", + "100% |########################################################################|\n", + "100% |########################################################################|\n", + "100% |########################################################################|\r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "主程序段总共运行了 299.93358182907104 秒\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "# 超参数设置\n", + "selected_qubit = [2, 4, 6, 8]\n", + "samples = 300\n", + "grad_val = []\n", + "means, variances = [], []\n", + "\n", + "# 记录运算时长\n", + "time_start = time.time()\n", + "\n", + "# 不断增加量子比特数量\n", + "for N in selected_qubit:\n", + " grad_info = []\n", + " THETA_SIZE = N\n", + " pbar = ProgressBar()\n", + " for i in pbar(range(samples)):\n", + " class manual_gradient(fluid.dygraph.Layer):\n", + " # 初始化一个长度为 THETA_SIZE 的可学习参数列表\n", + " def __init__(self, shape, param_attr=fluid.initializer.Uniform(\n", + " low=0.0, high=2 * np.pi, seed=1),dtype='float64'):\n", + " super(manual_gradient, self).__init__()\n", + "\n", + " # 我们需要将 Numpy array 转换成 Paddle 动态图模式中支持的 variable\n", + " self.H = fluid.dygraph.to_variable(\n", + " density_op(N))\n", + "\n", + " # 定义损失函数和前向传播机制 \n", + " def forward(self):\n", + "\n", + " # 初始化三个 theta 参数列表\n", + " theta_np = np.random.uniform(\n", + " low=0., high= 2 * np.pi, size=(THETA_SIZE))\n", + " theta_plus_np = np.copy(theta_np) \n", + " theta_minus_np = np.copy(theta_np) \n", + "\n", + " # 修改用以计算解析梯度\n", + " theta_plus_np[0] += np.pi/2\n", + " theta_minus_np[0] -= np.pi/2\n", + "\n", + " # 我们需要将 Numpy array 转换成 Paddle 动态图模式中支持的 variable\n", + " theta = fluid.dygraph.to_variable(theta_np)\n", + " theta_plus = fluid.dygraph.to_variable(theta_plus_np)\n", + " theta_minus = fluid.dygraph.to_variable(theta_minus_np)\n", + "\n", + " # 生成随机目标,在 rand_circuit 中随机选取电路门\n", + " target = np.random.choice(3, N) \n", + " \n", + " U = rand_circuit(theta, target, N)\n", + " U_dagger = hermitian(U) \n", + "\n", + " U_plus = rand_circuit(theta_plus, target, N)\n", + " U_plus_dagger = hermitian(U_plus) \n", + "\n", + " U_minus = rand_circuit(theta_minus, target, N)\n", + " U_minus_dagger = hermitian(U_minus) \n", + "\n", + " # 计算解析梯度\n", + " grad = ( matmul(matmul(U_plus_dagger, self.H), \n", + " U_plus).real[0][0] \n", + " - matmul(matmul(U_minus_dagger, self.H), \n", + " U_minus).real[0][0])/2 \n", + " return grad \n", + " \n", + " \n", + " # 定义主程序段 \n", + " def main():\n", + " # 初始化paddle动态图机制\n", + " with fluid.dygraph.guard():\n", + "\n", + " sampling = manual_gradient(shape=[THETA_SIZE])\n", + " \n", + " # 采样获得梯度信息\n", + " grad = sampling()\n", + " \n", + " return grad.numpy()\n", + " \n", + " if __name__ == '__main__':\n", + " grad = main()\n", + " grad_info.append(grad)\n", + " \n", + " # 记录采样信息\n", + " grad_val.append(grad_info) \n", + " means.append(np.mean(grad_info))\n", + " variances.append(np.var(grad_info))\n", + " \n", + "time_span = time.time() - time_start \n", + "print('主程序段总共运行了', time_span, '秒')" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "我们接着画出这个采样出来的梯度的统计结果:\n" + ] + }, + { + "data": { + "image/png": "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IiIJlpeXx7Rp0xg3bhxt27ZlxIgR3FeSFEXef/99hgwZQtu2bcnJyeHkk0/mtddeA0KNx4gRIwA45ZRTeOONNygqKuKtt97i+OOPp3///vzmN79hwYIFCY373XffpUmTJuy8884AbNy4kXnz5rHXXnvx4Ycfsueee/L73/++dN/cuXMpLCxkwoQJnHnmmaxcuZK77rqLww47jPz8/ITGFkt9YkRERERk21dYWPm+Jk2q3t+mTdX7K5Gdnc2QIUMYMmQI/fr14/777+e0006r8XkgDElcXFxMy5YtSzvcV2bYsGEsWrSI3Xbbjbvvvpvvv/+ejRs3kpOTw7x58+jYsWOl7504cWJpLQxA69atadKkCcceeywAxx9/PPfeey8QanUGDx5MgwYN6NatG7169WLu3Lm8/fbbvP7669x1110UFRWxfv168vLyuOGGG7bqs1dENTEiIiK1pD4xIlLe7NmzmTt3bun69OnT6dKlS5ljdt99d1599VWWLl3Kpk2bmDBhAj/96U+B0CTrscceA+Dhhx9mn332oXnz5nTr1o1HH30UCP1d/vvf/25x7SlTpjB9+nTuuecezIz99tuv9Fz3338/Rx99dIUxFxcXM2nSpDL9YcyMI488ksIoiZs6dSp9+vQBYPjw4aXbly5dypw5c+jevTsPPfQQ33zzDV999RVjxozhl7/8ZUITGFASIyIiUmvqEyMi5RUVFXHqqafSp08fdtllFz799FNGjx5d5pj27dtzww03sP/++7PrrrsyaNCg0gSjadOmvPfee+y88868/PLLXHHFFUAYCvnee+9l1113pW/fvjz99NPVxnLVVVcxduxYevTowbJlyzj99NMBmDx5cul5AV577TU6depE9+7dy7z/xhtvZPTo0eyyyy48+OCD3HLLLUCo8WndujV9+vRh//335+abb6Z169Zb/Z3VhJqTiYiIiIgk2KBBg3jrrbcq3FcY0zTtxBNPLNN8K9bYsWO32NatW7fSDvTx6tatG++9994W24866iiOOuqo0vUhQ4bwzjvvbHFcly5dSvvqxDIzxo4dW2GcJU477bStbkJXFdXEiIiIiIhIRlESIyIiIiKSZtTHrmpKYkRERERkm+TuqQ5BKlHb342SGBERkVrS6GQi6Sc3N5dly5YpkUlD7s6yZcvIzc3d6nOoY7+IiEgtufszwDMFBQVnpjoWEQny8/OZN28eS5YsSdo11q5dW6s/xOtKOsaZm5tbq8kwlcSIiIiIyDanZALGZCosLGTAgAFJvUYiZEqcNaHmZCIiIiIiklGUxIiIiIiISEZREiMiIiIiIhlFSYyIiIiIiGQUJTEiIiK1pCGWRUTqlpIYERGRWnL3Z9x9ZF5eXqpDERGpF5TEiIiIiIhIRlESIyIiIiIiGSWtkhgzO8TMZpvZZ2Z2SQX7G5nZI9H+d82sa7n9nc2syMx+X2dBi4iIiIhIncpJdQAlzCwbuBM4CJgHvG9mk93905jDTgdWuHsPMzsBuBEYEbN/LPDvuopZREQkUxx2++t8umDV5g0vPAdAn/bNef78fVMUlYjI1kmnmpjdgc/c/Qt3Xw9MBI4ud8zRwP3R8mPAUDMzADMbDnwJzKibcEVERDLHwM4taZBtZbY1yDYGdmmVoohERLZeOiUxHYFvY9bnRdsqPMbdNwLfA63NLA+4GLiqDuIUERHJOOcN7UmWlU1iss04b2iPFEUkIrL10qY5WS2NBm519yIrd4Muz8xGAiMB2rZtS2FhYdKDq62ioiLFmWCZEqviTKxMiRMyK1bJDO2a53L8oHwe+eBbNmxyGmQbx+3WiXbNclMdmohIjaVTEjMf6BSznh9tq+iYeWaWA7QAlgGDgePM7CagJVBsZmvd/Y7yF3H3ccA4gIKCAh8yZEiCP0biFRYWojgTK1NiVZyJlSlxQmbFKmGyS+DIDh06pDqUKp03tCePTpsHuGphRCSjpVNzsveBnmbWzcwaAicAk8sdMxk4NVo+DnjZg33dvau7dwVuA/5cUQIjIiKSDJky2WW75rmcslNLLnj9IU7u00q1MCKSsdKmJsbdN5rZKGAKkA2Md/cZZnY18IG7TwbuBR40s8+A5YRER0REROJ03vrPaPHWBIrnToU118GvfgXZ2akOS0SkRtImiQFw9+eB58ttuyJmeS1wfDXnGJ2U4ERERLYBLX71C6at+4FBDz4IZ54Jd9wBt90Gar4oIhkknZqTiYiISB1Y3bs3vPEGTJwIK1bAjTemOiQRkRpREiMiIlIfmcGIETBrFowfH7Z9+SVccgmsWlX1e0VEUkxJjIiISH3WuDG0bx+Wp0yBm26Cnj3h7rth06bUxiYiUgklMSIiIhKcdRa8/z706gUjR8LAgfDKK6mOSkRkC0piREREZLNBg+C112DSJPj+e3jssVRHJCKyBSUxIiIiUpYZHH88zJwJ118ftr31Flx0UUhsRERSTEmMiIiIVKxxY2jePCy//jqMGRP6y4wbp/4yIpJSSmJERESkehdfDB98AL17w29+E/rLFBamOioRqaeUxIiIiEh8Bg6EV18N/WRWrQpJjYhICuSkOgAREZFMZ2ZHAkd26NAh1aEknxn87Gdw+OGQFT0LnTgxJDR//CO0bJnS8ESkflBNjIiISC25+zPuPjIvLy/VodSd3Fxo2DAsf/wxjB0b+sv84x+wcWNqYxORbZ6SGBEREamd664LNTF9+oS5ZgYODAMBiIgkiZIYERERqb2Sjv6PPQZFRbB4caojEpFtmPrEiIiISGKU9Jc54ojNTc1uugkWLoQrrlB/GRFJGNXEiIiISGI1ahQSGoDvvoPbboMePeBvf1N/GRFJCCUxIiIikjy33QYffgg77wznnAP9+8M776Q6KhHJcEpiREREJLn694dXXoEnnoB16yAnas3untKwRCRzKYkRERGR5DODY46BWbNgt93CtnPOgd/9DlasSG1sIpJxlMSIiIhI3cnODj+Li8PP224L88vceaf6y4hI3JTEiIiISN3Lygod/T/6CPr1g1GjYNddw7qISDWUxIiIiEjq7LorvPwyPPlkqKVp2zZsL6mpERGpgOaJERGRtGJmBpwMdHf3q82sM7CDu7+X4tAkWcxg+HA4+uiw7A6HHsqOzZuHJKdVq1RHKCJpRjUxIiKSbu4C9gROjNZXA3emLhypMyVzy6xfD926kf/EE2F+GfWXEZFylMSIiEi6Gezu5wJrAdx9BdAwtSFJnWrUCP7+dz4YNy4Mz1zSX2b27FRHJiJpQkmMiIikmw1mlg04gJm1BdRBoh76Yccd4T//gaefDn1lOnYMO9avT21gIpJySmJERCTd/AV4EmhnZtcBbwB/Tm1IkjJmcNRRUFgIeXkhgRkwAM47D5YvT3V0IpIiSmJERCStuPtDwEXA9cACYLi7P5raqCRtrFsH++0X+sn06AF//Sts2JDqqESkjimJERGRtOPus9z9Tne/w91npjoeSSPNmoX5ZaZPh4EDQ43MLrvAvHmpjkxE6lBaJTFmdoiZzTazz8zskgr2NzKzR6L975pZ12j7QWY2zcw+jn4eUOfBi4hIQpjZ/WbWMma9lZmNT0Ecw83s7qjcObiury/V6NcPXnoJJk8Oyx06hO1FRamNS0TqRNokMVEnzjuBQ4E+wIlm1qfcYacDK9y9B3ArcGO0fSlwpLv3A04FHqybqEVEJAl2cfeVJSvR6GQDanICMxtvZovN7JNy26t8WBbL3Z9y9zOBs4ARNbm+1BEzOPJImDQJsrJg2TLo3h3+7//Csohss+JOYqJakJPM7DIzu6LklcBYdgc+c/cv3H09MBE4utwxRwP3R8uPAUPNzNz9I3f/Lto+A2hsZo0SGJuIiNSdLDMrnd3QzLaj5pMz3wccEruhsodlZtbPzJ4t92oX89Y/onlqMoMZHHcc3HVX6C9z++3qLyOyjTJ3j+9AsxeA74FpwKaS7e5+S0ICMTsOOMTdz4jWf0GYK2BUzDGfRMfMi9Y/j45ZWu48Z7n7gZVcZyQwEqBt27aDJk2alIjwk6qoqIi8vLxUh1GtTIkTMidWxZlYmRInZE6s+++//zR33y2R5zSzXwKXA5MAA44D/uzuD9TwPF2BZ91952h9T2C0uw+L1i8FcPfrK3m/ATcAL7n7f6q4jsqVJNnaOJt++SU73nkn202bxppOnZh2111sSuLnzZTvEzInVsWZWJkSJ9SgXHH3uF7AJ/EeuzUvQiF1T8z6L4A7yscA5Mesfw60iVnvG23bMZ5r9urVyzPBK6+8kuoQ4pIpcbpnTqyKM7EyJU73zIkV+MCTUyb0AUYB5wJ9tvIcXWPLrnjKmXLvP4/w4O7vhIdjKlfqWK3iLC52f+YZ9wsu2LxtyZJax1SRTPk+3TMnVsWZWJkSp3v85UpN+sS8ZWb9anB8Tc0HOsWs50fbKjzGzHKAFsCyaD2fMK/AL9398yTGKSIiSRQ1B+4PNAdaA8cluPlyXNz9L+4+yN3Pcve/1/X1pZbM4IgjYOzYsD57NnTqBKNGwdKlVb9XRNJeTZKYfYBpUYfI/0Ujgf0vgbG8D/Q0s25m1hA4AZhc7pjJhI77EJ6ovezuHo1i8xxwibu/mcCYRESk7j1N6AO5Efgh5lVb8Tws2ypmdqSZjSvSyFjpq00bOP10+PvfoWdPuO22MHGmiGSkmnSUPDRpUQDuvtHMRgFTgGxgvLvPMLOrCdVKk4F7gQfN7DNgOSHRgdDkoAcQO9jAwe6+OJkxi4hIUuS7+yHVH1ZjpQ/LCMnLCcBJiTixuz8DPFNQUHBmIs4nSdC6NdxxB5x9NlxwQXjdey98+CE0aJDq6ESkhuJOYtz962i0mJ5AbsyurxMVjLs/DzxfbtsVMctrgeMreN+1wLWJikNERFLqLTPr5+4fb+0JzGwCMARoY2bzgCvd/d6KHpYlJGLJHH37wpQp8PzzoYlZSQLzzTfQuXNqYxORuMWdxJjZGcD5hOr36cAewNuAJpYUEZFE2gc4zcy+BNYRRihzd98l3hO4+4mVbN/iYZnUQ2Zw+OHhBTB1KgwbBr/5DVx1VWh6JiJprSZ9Ys4HfgJ87e77EyYeW5mMoEREpF47lFDrfzBwJHBE9DNtqU9MhuvfPzQz+8c/wvwyt96q/jIiaa4mfWLWuvtaM8PMGrn7LDMrSFpkIiJSL9VF8+VEU5+YDNe6Nfz1ryGR+d3vwuvRR+HNN0OtTQUOu/11Pl2wavOGF54DoE/75jx//r51EbVIvVaTJGZeNArYU8BLZraCNC5QREQkM6n5sqRMnz7wwgvw73/DqlUhgdm0CebMgZ12KnPowM4tmbt4NRs2bZ40vEG2MbBLq7qOWqReirs5mbsf4+4r3X008CfCSGHDkxSXiIjUX2q+LKl16KEwYkRYvv9+2HlnOOccWLKk9JDzhvYkq1wtTbYZ5w3tUZeRitRbcScxFpxiZle4+6uEp2P9kxWYiIjUW2uj0ShLmy8Dad18WX1itmHDh4cJMseNC/PLjB0L69fTrnkuxw/Kp0F2SGQaZBvH7daJds1yqz6fiCRETTr23wXsCZSM+LIauDPhEYmISH1Xvvny06R582V3f8bdR+bl5aU6FEm07baD22+Hjz+GvfaCCy+En/0MKFsbo1oYkbpVkz4xg919oJl9BODuK8ysYZLiEhGResrdj4kWR5vZK0AL4IUUhiQS+sQ8/3zoL9O4MQDtsjYyqs0axi7MVS2MSB2rSRKzwcyyAQcws7ZAcVKiEhERAaLmyyLp49BDNy/fcgujrr6abj8ZxuCRf09dTCL1UE2ak/0FeBJoZ2bXAW8Af05KVCIiUu+Y2RvRz9VmtirmtdrMVlX3fpE693//h40axWEfvEjbAf1gzBhYty7VUYnUC3HXxLj7Q2Y2DRhKmD15uLvPTFpkIiJSr7j7PmZmQF93/ybV8dSEmR0JHNmhQ4dUhyJ1Keov88GgQew+aRL84Q8wYwb885+pjkxkm1eT5mREI8TMSlIsIiJSz7m7m9lzQL9Ux1ITmuyyflvTuTM8+yxMmQL5+WHjvHmwbBnsumtqgxPZRlWbxJjZ5Kr2u/tRiQtHRESED83sJ+7+fqoDEamRYcM2L191FYwfD2ecAddcA+3apS4ukW1QPDUxewLfAhOAdwlNyURERJJlMHCymX0N/EAod9zdd0ltWCI1cNNNkJcHd9wBEybAn/4E550HjRqlOjKRbUI8ScwOwEGE+WFOAp4DJrj7jGQGJiIi9daw6g8RSXOtWsGtt8JZZ4W5ZS66KDQvu+GGVEcmsk2oNolx902E8flfMLNGhGSm0Myucvc7kh2giIjUL+7+tZm1AnoCsRNvpPWElyIVKijY3F+mf/+w7eOPobhY/WVEaiGuIZbNrJGZHQv8CziXzcMti4iIJJSZnQG8BkwBrop+jk5lTNUxsyPNbFxRUVGqQ5F0NWwYbL99WL70UhgwAEaOhEWLUhuXSIaqNokxsweAt4GBwFXu/hN3v8bd5yc9OhERqY/OB34CfO3u+wMDgJUpjaga7v6Mu4/My8tLdSiSCR58EC64IAzF3LNn6D+j+WVEaiSemphTCFX65wNvafKxuvPUR/PZ+4aXOe2FH9j7hpd56iPljSJSL6x197UQWgJEw/sXpDgmkcRp1QpuuSXMKbP//nDxxfCPf6Q6KpGMEk+fmLianEliPfXRfC594mN+3LAJgPkrf+TSJz4GYPiAjqkMTUQk2eaZWUvgKeAlM1uB+sPItqhXL3j6aXj5Zdhrr7DtlVegZcvQ3ExEKqUEJU3dPGV2aQJT4scNm7j+3zNx9xRFJSKSfO5+jLuvdPfRwJ+Ae4HhKQ1KJJkOOAByc8E91MoMGhTml1m4MNWRiaSteIZYlhT4buWPFW5ftGod/Ua/SO8dmtG7fTN679Ccndo3p/cOzWjaSL9OEclcZnYn8LC7v1myzd1fTWFIInXLDF58Ea69Fv7yF5g0CS6/HM4/PyQ5IlJKNTFpqkPLxhVub9m4AccO7IgZPP3Rd/zxqU/42d/e4l/vhJYWS4vWcetLc/h62Q91Ga6ISCLMAcaY2VdmdpOZqT2N1D8tW8KYMZv7y1xyCTz3XKqjEkk7enSfpv4wrKBMnxiAxg2yGX1U39I+Me7O/JU/MmvBanpuH0bEmbNoNX95eS5792hDl9ZNmTpzEXe88hm9d2hOn/bN6N2+OQU7NKN5boOUfC4Rkcq4++3A7WbWBTgBGG9mjYEJhEmW56Q0wCqY2ZHAkR06dEh1KLKt6Nkz9Jd5+23YY4+w7ZFHwvaBA1Mbm0gaiCuJiSYdOxI4BugFfAk8DTzt7ouTF179VZKo3DxlNvNX/kjHlo35w7CCMp36zYz8Vk3Ib9WkdNteO7bh06sOISfbAMgyo2F2Fs9/vIAJ731Telx+q8ZlEpshBW1p0lA5rYiknrt/DdwI3BjVxowHrgCyUxpYFdz9GeCZgoKCM1Mdi2xj9twz/Ny4ES67DL78En71K7juOthhh9TGJpJC1f7VamZPAK2A54CL3X2OmXUGjgb+ZWYN3X1IcsOsn4YP6MjwAR0pLCxkyJAhcb+vccPN5fz+vduxf+92uDsLV61l5oJVzFywmpkLVjFr4WpenrWIYoeP/nQQTRrCpA++5aNvVvLnY3bGzNiwqZgG2Wp1KCJ1x8xygEMJtTFDgULSfLJLkaTLyYFp00Lycvvtob/MZZeF+WbUX0bqoXgevf/a3VfGbnD3b4C/An+NhsGUNGdmtG/RmPYtGnNA7+1Lt6/dsInPlxTRqmlDAOav+JFZC1dhFmpyRj7wAbMXrg6DB5QOJNCMrq2bkqPkRkQSyMwOAk4EDgPeAyYCI91dnfxEIPSXuflm+M1v4Pe/D0nMAQfA4MGpjkykzsUzT8xKM+tNqHkpacs0H5js7jPLJzi1YWaHALcTmgzc4+43lNvfCHgAGAQsA0a4+1fRvkuB04FNwHnuPiVRcW3Lchtk07dDi9L1Cw7qxQUH9SpdP7DP9jRv3IBZC1bz6pwlbCwOwzs3ysmi1/bN6L1DM/bo3pqfDcqv89hFZJtzKfAwcKG7r0h1MCJpq0cPeOqp0Pm/b9+w7fbbYZ99wvDMIvVAPM3JLiY8GZtIeDIGkA9MMLOJ5RONrWVm2cCdwEHAPOB9M5vs7p/GHHY6sMLde5jZCYQ20yPMrA+h2UFfoAPwHzPr5e5lJ1qRGjt5cBdOHtwFgHUbN/HZ4iJmLVjNrIWhWdrLsxbz/Y8bSpOYY+96k4P67MDZQ3bE3flscRFd2zRVk7Rt1FMfzd/cb+udl7fotyVSE+5+QKpjEMkoJQlMURHceGNoWnbaaaHJWfv2KQ1NJNniaU52OtDX3TfEbjSzscAMICFJDLA78Jm7fxGdfyKh9ic2iTmaze2iHwPusNDu6WhgoruvA740s8+i872doNgEaJQTam1ia24gNEkDKHana+umtMkLTdOWFK3joFtfo2F2Fj3a5bFT+9AUraRJWuu8RnX+GSRxnvpofpkR9Oav/JFLn/gYQIlMLSgxFJEay8uDmTPhz3+G224r21+mccVTNohkuniSmGJC7cbX5ba3j/YlSkfg25j1eUD5Rp6lx7j7RjP7HmgdbX+n3HsrLPXNbCQwEqBt27YUFhYmIvakKioqyog41/zwA0dtb1C0ksLCz/lxozNyl0Z8u7qYb1f/wNQZq3n8Qy89vkUjo1NeFod1b0Cf1tmlTdVysizpsWbKd5oucRa78+NGWLPB+XFjWL7jo7X8uKHscT9u2MQ1T/+XhV/OYsmaYrKzjCyDLIPs6GfJcrYZO7UOg1As+qGY9cXQqVmosVv6YzEbi8seb9F7Yrc1zK7Zv5V0+T4r89Z3G7jvk/Wsj+6s81f+yEWPTufTmZ+yVwcNiy4iVWjRItTGjBwJf/gDXHMNnHwydOmS6shEkiKeJOa3wFQzm8vmJKMz0AMYlaS4ksbdxwHjAAoKCrwmo36lSk1HJ0uViuI8tNwxy4rWMWvh6tJR0mYtXMVOOxcwpKAdb362lNP++R4TR+7BoC7b8dXSH/hm+Rp6t29G27xGpYMNJCvWdJSIOIuLndXrNrJ67QZWr90YvcLy/r3b0aJxA975YhmT//sdVxzRh9wG2fzzzS+Z8N43pccXrdsY9/WWr3VmrNuOZz75rsrjmjbMZsbVQwE4b8JHfDz/e175/RAARvzjbd79cnmV7+/Wpmnp8SfdHZ5hPHxmmEvhqDve4Nvla8jOyiIny8iOXuvWZtEsz8jJMnbNb8mNx+1Sev0d2+Zx/oE9ATj7X9PYsKmYLDNyso3srKyQeGVlkZ0Vfg7o1JKf/6QTALe8OJsBnVtyQO/tWb+xmH+8+jnZ2eE6WRZ+ZmdnkR0tZ2UZBds3o19+CzZuKmbqrMX03qEZl7/zbmkCU2J9MTz3TTaXnTQkru9fROq5HXeEJ56Ar78OCYx7mDDz+ONht91SHZ1IwsTTsf8FM+tFaJ4V27H//QT3OZkPdIpZz4+2VXTMvGgIzhaEDv7xvFfSQOu8RuzdoxF792izxb7tm+dy+j7d6dYmTNz57P++Y8yLYW671k0bhhHSdgjz2vTeoRk9t8+jUU7aThuREMXurCpNPsr+XLV2I0N6taXTdk34ZP73jHvtC/4wrIBO2zXh6enzufHfs0ICsn4j7hWf/9n/24cWHVvw9bIfeHHGQn53UC9yG2TTonEDurfJo1luDs1yG0Q/c2jeuAHNo22/fWQ6S1av2+KcHVo25qqj+nLRsAI2FTsbi51idzZucjYVO5vc2VRc9i/1kft1Z/XazYnS+UN7sqRoXZn3bCx2iqPzbSouplnMhK1H7Vp2gsEDd9qeJavXhWtt2hzDdwvW0bptHpuKnbbNNjdnzIlqjEosK1rPD+s3hmtHr43llrOM0iTmwXe+ZmOxc0Dv7Vm7cRO3vFT9nIwj9+tOv/wWrNmwid88OI0/Hr4T3638scJjK9u+LcrUeck02aWknZIamO++g/vug5tuglNPpeERR6Q0LJFEiWt2Q3cvpmxzrWR4H+hpZt0ICcgJwEnljpkMnEro63Ic8LK7u5lNBh6O+ul0AHqyeRACyRA92uVxyaG9S9d/sUdXBnXZLprTJsxr8+A7X7NuY/gDODvL2LFtUx4/ey+a5Tbg2+VraJiTxfbNKx8vvy77GxQXOz+sD8lGRQnI6rUb2GvHNvTv1JJvl6/hyskzOGfIjuzWdTvemLuUs/81jaJ1G/EpL1Z6jb+dPJBO2zWhaN1G/jdvJavWhvZd7ZrlslePNqVJSPPcHJqXJiObk5KOrUJb6RE/6cyIn3QuPe+xA/M5dmDVI85dfthOZfrEADRukM0fhhWwXdOGbBcN2x2PnTuW7We1VwVJblVO2L1zmfXzhvas8LhQs7XlyD1jR/Qvsz7prD1rdP3pVxxcutysUQ5zrzu0TMJTHJsERYlVXm64/TZpkM1z5+1Du2a5/PPNr5hfQcLSoWX9aNOeyfOSabJLSVsdO8LcuaG/zK23MviRR2DWLLjwQvWXkYwWz+hknas7JrLS3VdtbSBRH5dRwBTCEMvj3X2GmV0NfODuk4F7gQejjvvLCYkO0XGTCIMAbATO1chkma9FkwbsuWNr9tyxdem2TcXOl0t/CEnNgtV8vXwNeY3CP+MxL87m/S+X89aloYnSpPe/BYOddmhOz+3zeOGThXF3RHd3fli/iVU/lk8+yjbJ6t+pJQf33YEf1m3ktH++x8mDuzB8QEc+W1zEQbe+WmkNSInLDjP6d2oJwOLVa1mzPsTWoWUuPxuUz/JF8+lX0KO0FqR8EtKqSUgU9ujemsI/7F963vLfWzKUfGelSWHLxuqETpiTqUG20SDOSsKc7KzSwTL+MKyg0sSwntC8ZCLJ0Lw53HADjBzJ8l/9irZ33gnnn5/qqERqJZ6amPvjOMaB+whzuGw1d38eeL7ctitiltcCx1fy3uuA62pzfUl/2VlGj3Z59GiXxxG7lN13+j7dOHKXzU057nnjC+YsKip9n0Hp4AElftywiZunzGb4gI4c//e32KdHW84/sCfrNhaz85VVTzWUnWX8eu+uHNx3BxrlZJX2uwBom9eIUfv3iKkJ2Zx4lNSMNMttQG6D0JG903ZNePb/9i09d/e2eYw+qi+FhUsYsl/3rf26km74gI4MH9AxY/oYpbv6nhjW5bxkIvVS9+7MuOoqhuy8MzRrBhs2wBlnwKhR8JOfpDo6kRqJJ4k5C5jjXt0zZZHU2iW/ZZn1f5+/H98sXxOaoy1YxV9e/qzC95X0N+jeJo92zUM/iUY5WVx+2E7kxSQezXJzSpOPZrk5NG6QXTrYQE52FhNHbm6C1KJJAy48uN48PZcEqs+JYV3NSyZS77WJmuzOnQtTpsADD8AvfxmanHWsHw9NJPPFk8Q8BnQxsznA/4CPS36mcydLkewso1ubpnRr05TD+rXn8Q/nV9nfoGSkKghNgs5M4xoQkW1UXc1LJiIAffrAnDlw/fUwdiw89lgYyezii6Fh/P0aRVKh2mnU3b0f0BY4mzBiTHfgMuB/ZrYwueGJJM4fhhXQuFxHhXrW30Ak3ZXMS1ZeouclE5ESzZuHJGbmTDjssJDIZG/bI3/KtiHe0cnWAe+bWZG7/1/J9mgoTJGMUN/7G4hkgN+yDc1LJpJRuneHRx+FoqKQxKxcCaecAn/6EwwuP/e4SOrFlcTEKNMvxt1XJDAWkaSrz/0NRNJdHc5LJiKVyQtztTF7NkybBnvsEZKZ66+H/KqH3hepS9U2JzOzO83sdDMbACRuynQREZFy3L3Y3d9x98ej1ztKYERSYPDg0F/m0ktDDU2vXnDVVVCslp2SHuKpifkv0B/4JdDMzD4ldLD8FPjU3R9JXngiIlJf1NW8ZCISp2bNwohlZ54ZOvt/8AFkVfv8W6ROVJvEuPu42HUzywf6AbsARwBKYkREJBHqbF4yEamBbt1g0iRYvz6sf/45/PrXcOONobmZSArUtE8M7j4PmAf8O/HhiIhIPaZ5yUTSWcmwy19/HZqa7bknnHxy6C/TqVNqY5N6J54+MZ3jfDWvi4BFRGSb9RjwvZl9YGbjzewCMxtqZu1SHVh1zOxIMxtXVFSU6lBEku+AA8JEmZdfHoZkLiiAa69NdVRSz8RTE6PqfRERSTp372dmjQjNlZ8HfiA0W+5rZrj7DikNsAru/gzwTEFBwZmpjkWkTuTlhcTljDNCf5nFMfOfu4NpLChJrnj6xOxfF4GIiIhoXjKRDNO1KzzyCGyKBhF87TW46CK47Tb1l5GkiqtPjJn1Bo6m7Lj9T7v7rGQFJiIi9ZrmJRPJJNnZ4efq1fDNN6G/zEknwQ03qL+MJEU8fWIuBiYS5oh5L3oZMNHMLklueCIiUl9oXjKRbcDhh4dO/3/8IzzxROgvM2ZMqqOSbVA8NTGnA33dfUPsRjMbS5gv5oZkBCYiIvWO5iUT2Rbk5cE114T+MpdcAjnRn5slE2VqrhlJgHiSmGKgA/B1ue3to30iIiK1pnnJRLYxXbrAhAmhoz/Agw/CnXeG/jJ77ZXS0CTzxZPE/BaYamZzgW+jbZ2BHsCoJMUlIiL1nOYlE9lGlIxU1qwZzJ8Pe+8NJ5wQJsvs3Dm1sUnGimd0shfMrBewO2U79r/v7puSGZyIiNQfZhbvXzMr3X1VUoMRkcQ79lg4+GC46Sa4+WZ46qnQX+bcc1MdmWSgapMYM8ty92LgnWr2i4iI1IbmJRPZ1uXlwdVXb+4v0zF6Pr5+feg7o/4yEqd4mpO9ZGZLgaeA59x9lZk1AQ4BjiG0V+6ftAhFRKRe0LxkIvVI587w8MOb1//8Z3juudBfZu+9UxaWZI54mpMNNbM+hHlinjOzhoQO/VOAW939wyTHKCIi9YTmJROpp3baCe65B/bZB0aMoNHw4amOSNJcXJNduvunhCEurzezxu7+Y3LDEhGR+iaal+xEwtxk70Wb8wnzkk10dw3pL7KtGjECjjgi9JW56SZ2f/JJWLsWTjst1ZFJmooriSnnUjPLAaYD0919TmJDEhGRekrzkonUZ02bwujRcPrpLP31r9l+113D9qIiaNJE/WWkjBr/a3D3K4Dbge+BY8zs7oRHJSIi9VHJvGTlaV4ykfqkUydmXn45DBgQ1keNgt13hzfeSG1cklbiGZ2sK3AusCOwnFAD84y7TyH0ixEREUmE36J5yUSkvGHDYOpU2Hdf+PnPw/wyXbumOipJsXhqYp4GZgF3AgcBuwKvmdmdZtYomcGJiEj94e4vAL2AqwgPyaYAo4GCaJ+I1EcnngizZoWmZs88A717wxNPpDoqSbF4kphsd7/X3acCy939TEKtzFfAuGQGJyIi9UfJvGPu/o67Px693imZWNnM1CBepL5q2hSuvBLmzIGTToI99gjbly6FYrU2rY/iKRD+Y2Yl1fgO4O4b3f1mYM9EBGFm25nZS2Y2N/rZqpLjTo2OmWtmp0bbmpjZc2Y2y8xmmJk6foqIZKaXzOwRMzvRzJpD6T3+WDN7EKjTIf3NbCcz+7uZPWZmZ9fltUWkEvn5MH48dOgA7nDssfCTn8Drr6c6Mqlj8SQxvwNamNkHQEczG2lmp5jZncCyBMVxCTDV3XsCU6P1MsxsO+BKYDCwO3BlTLIzxt17AwOAvc3s0ATFJSIidcTdhxKaknUlzEv2LqFM2IUwL1n/eM9lZuPNbLGZfVJu+yFmNtvMPjOzLcqacvHMdPezgJ8Dmn1PJB2dfTYsXgz77Rf6y3z5ZaojkjpSbRITVe1fB+wHnAnsAAwEPgESlSwcDdwfLd8PDK/gmGHAS+6+3N1XAC8Bh7j7Gnd/JYp1PeFJXX6C4hIRkTrk7p+6+/Xuvi8wxN33dPfRWzGx8n3AIbEbzCyb0L/zUKAPcKKZ9TGzfmb2bLlXu+g9RwHPAc/X9rOJSIKZhf4ys2fDVVfBc8+FSTOnTk11ZFIHzN2rPyh04L8I2AsoIiQKk9z984QEYbbS3VtGywasKFmPOeb3QK67Xxut/wn40d3HxBzTMortQHf/opJrjQRGArRt23bQpEmTEvERkqqoqIi8vLxUh1GtTIkTMidWxZlYmRInZE6s+++//zR33y0Z5zazqwmjaE5nK+Yli0bXfNbdd47W9wRGu/uwaP1SAHe/Po5zPefuh1eyT+VKkijOxMuUWLcmzoZLltB54kS+OPNMinNzabRwIevatoXs7CRFuW1/n6kSb7kS72SXNwNNCM28TgNaA+PN7G53/1c8JzCz/xBqccq7PHbF3d3Mqs+stjx/DjAB+EtlCUx0/nFEAxIUFBT4kCFDanqpOldYWIjiTKxMiVVxJlamxAmZFWuyuPsVZrY90J8wL1mPaHCZrdWRzUM3A8wjNFGukJkNAY4FGlFFTYzKleRRnImXKbFudZzHHx+a46xbB336QIsWcNttoblZEmzz32caizeJ2RU42t1Xmtnu7r53VDtTCMSVxLj7gZXtM7NFZtbe3ReYWXtgcQWHzQeGxKznR9cvMQ6Y6+63xROPiIikl3Sbl8zdCylbzohIpmjYEK67Di66CH76UzjuOLjpJujWLdWRSYLEO1zlzTHHLjWzscCpwLoExTE5Oh/Rz6crOGYKcLCZtYo69B8cbcPMrgVaECZKExGRzJTsecnmA51i1vOjbSKyrTGDE04I88tccw08/3yYX+ajj1IdmSRIXEmMuz/r7suj1eOBD4A84KQExXEDcFA0S/OB0TpmtpuZ3RPFsBy4Bng/el3t7svNLJ/QJK0P8KGZTTezMxIUl4iI1J1kz0v2PtDTzLqZWUPgBMJDtFozsyPNbFxRUVEiTiciidKkCfzxj2F+mUsvhV13Dds/+QQ2bUptbFIr8TYnKxWNAPZwIoNw92XA0Aq2fwCcEbM+Hhhf7ph5gCUyHhERSYn/mNkod7+DmHnJgJvNrKad+icQmiC3MbN5wJXufm8079kUIBsY7+4zEhG4uz8DPFNQUFCbPjsi9dpht7/OpwtWbd7wwnMA9GnfnOfP37d2J+/YEUaPDsvLl8M++0DXrqG/zDbWV6S+0OzHIiKSLhI2L5m7n+ju7d29gbvnu/u90fbn3b2Xu+8YTR8gImliYOeWNMgu+1y6QbYxsEuFc6BvvVat4B//gBUrYP/9w4SZnydkwF2pQ0piREQkLdTRvGRJoeZkIrV33tCeZFnZJCbbjPOG9kjshcxgxIjQX+baa+HFF8NIZt98k9jrSFIpiRERkbQRdeC/EDgb6AcsAV5095WpjKs67v6Mu4/MlHkYRNJRu+a5HD8ov7Q2pkG2cdxunWjXLDc5F2zcGC6/PPSXufVW6Nw5bH/rLfWXyQBKYkREJJ3cDHQhzEs2j83zkp2S0qhEpE7E1sYkpRamIh06wDnnhOXPPw9zygwaBK+8kvxry1ZTEiMiIulkV+D37v5fYHd3/z1hSP1zUxuWiNSFktoYg+TWwlSme3eYMAFWroQDDlB/mTSmJEZERNJJsuclE5E0d97QnvRslVU3tTDlmcHxx8PMmWGyzBdfhAED4Pvv6z4WqZKSGBERSRt1MC9ZUqhjv0jitGuey2WDG9d9LUysxo3hsstg7lwYPx5atAjbn3tO/WXShJIYERFJS+6+3t0fdvex7v5dquOpijr2i2yj2reH444Ly2+/DUccAQMHwssvpzYuURIjIiIiIlKtPfaARx+FVatg6FAYPpzG8+alOqp6S0mMiIiIiEh1zEKtzMyZcP31MHUq/S+4ADZsSHVk9ZKSGBERkVpSnxiReiQ3Fy65BObOZebll0ODBrBxIzz0UPgpdUJJjIiISC2pT4xIPbTDDqzs3z8sT54Mp5wSRjKbOjWlYdUXSmJERERERGrjmGPgscegqAgOPBCOPjqMbCZJoyRGRERERKQ2zOBnPwv9ZW64IYxe9vOfg3uqI9tmKYkREREREUmE3Fy4+OJQC3PffSG5WbUK7r5b/WUSTEmMiIiIiEgi7bAD7LprWH7oIRg5Evr3h5deSmlY2xIlMSIiIrWk0clEpFJnnQWPPw5r1sDBB8NRR8GcOamOKuMpiREREakljU4mIpUyg2OPDf1lbrwRCgvhd79LdVQZT0mMiIiIiEiyNWoEF10U+svccUfY9tVXcNdd6i+zFZTEiIiIiIjUle23h65dw/KDD8K554b+My++mNKwMo2SGBERERGRVPjjH+HJJ2HdOhg2DI44AmbPTnVUGSEn1QGIiIiIiNRLZjB8OBx6KPz1r3DNNXD77aGJWS0ddvvrfLpg1eYNLzwHQJ/2zXn+/H1rff5UU02MiIiIiEgqNWoEv/996C9zzTVh2zvvwJ13bnV/mYGdW9Ig28psa5BtDOzSqrbRpgUlMSIiIrWkIZZFJCHatYPWrcPyxIkwalToLzNlSo1Pdd7QnmRZ2SQm24zzhvZIRKQppyRGRESkljTEsogk3K23wlNPhf4yhxwChx8Os2bF/fZ2zXM5flB+aW1Mg2zjuN060a5ZbpICrltKYkRERERE0o0ZHH00zJgBY8bAG2/A5Mk1OkVsbcy2VAsDSmJERERERNJXo0Zw4YXw2Wdw/vlh2xNPhIEANmyo8q0ltTEG21QtDKRJEmNm25nZS2Y2N/pZYY8jMzs1OmaumZ1awf7JZvZJ8iMWEREREalDbduGhAZCjcx558Euu8C//13l284b2pOerbK2qVoYSJMkBrgEmOruPYGp0XoZZrYdcCUwGNgduDI22TGzYwH1qBQRERGRbds//xkSmU2b4LDDwquS/jLtmudy2eDG21QtDKRPEnM0cH+0fD8wvIJjhgEvuftyd18BvAQcAmBmecDvgGuTH6qIiIiISAqZwZFHwiefwC23wFtvwccfpzqqOpUuk11u7+4LouWFwPYVHNMR+DZmfV60DeAa4BZgTdIiFBERERFJJw0bwu9+B6edBq2iBkp//SsUF8M550CDBikNL5nqLIkxs/8AO1Sw6/LYFXd3M/ManLc/sKO7X2BmXeM4fiQwEqBt27YUFhbGe6mUKSoqUpwJlimxKs7EypQ4IbNiFRGRFNtuu/DTHV55BZ58Ev72Nxg7Fg49NLWxJUmdJTHufmBl+8xskZm1d/cFZtYeWFzBYfOBITHr+UAhsCewm5l9Rfg87cys0N2HUAF3HweMAygoKPAhQyo8LK0UFhaiOBMrU2JVnImVKXFCZsUqYbJL4MgOHTqkOhQRqc/M4PHH4bnnQg3N4YfDsGE0PumkVEeWcOnSJ2YyUDLa2KnA0xUcMwU42MxaRR36DwamuPvf3L2Du3cF9gHmVJbAiIiIJIMmuxSRtGEGRxwR+suMHQvvvUfOmm2vx0W6JDE3AAeZ2VzgwGgdM9vNzO4BcPflhL4v70evq6NtIiIiIiISq2FDuOAC+PZbVvfunepoEi4tOva7+zJgaAXbPwDOiFkfD4yv4jxfATsnIUQRERERkczTtGmqI0iKdKmJERERERERiYuSGBERERERyShKYkREREREJKMoiRERERERkYyiJEZERERERDKKkhgREREREckoSmJERERERCSjKIkREREREZGMoiRGREREREQyipIYERGRWjKzI81sXFFRUapDERGpF5TEiIiI1JK7P+PuI/Py8lIdiohIvaAkRkREREREMoqSGBERERERyShKYkREREREJKMoiRERERERkYyiJEZERERERDKKkhgREREREckoSmJERERERCSjKIkREREREZGMoiRGREREREQyipIYERERERHJKEpiREREREQkoyiJERERERGRjKIkRkREREREMoqSGBERERERyShKYkREREREJKMoiRERERERkYyiJEZERERERDJKWiQxZradmb1kZnOjn60qOe7U6Ji5ZnZqzPaGZjbOzOaY2Swz+1ndRS8iItsqM2tqZh+Y2RGpjkVERDZLiyQGuASY6u49ganRehlmth1wJTAY2B24MibZuRxY7O69gD7Aq3UStYiIpCUzG29mi83sk3LbDzGz2Wb2mZltUdZU4GJgUnKiFBGRrZWT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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "grad = np.array(grad_val)\n", + "means = np.array(means)\n", + "variances = np.array(variances)\n", + "n = np.array(selected_qubit)\n", + "print(\"我们接着画出这个采样出来的梯度的统计结果:\")\n", + "fig = plt.figure(figsize=plt.figaspect(0.3))\n", + "\n", + "\n", + "# ============= 第一张图 =============\n", + "# 统计出随机采样的梯度平均值和量子比特数量的关系\n", + "plt.subplot(1, 2, 1)\n", + "plt.plot(n, means, \"o-.\")\n", + "plt.xlabel(r\"Qubit #\")\n", + "plt.ylabel(r\"$ \\partial \\theta_{i} \\langle 0|H |0\\rangle$ Mean\")\n", + "plt.title(\"Mean of {} sampled graident\".format(samples))\n", + "plt.xlim([1,9])\n", + "plt.ylim([-0.06, 0.06])\n", + "plt.grid()\n", + "\n", + "# ============= 第二张图 =============\n", + "# 统计出随机采样的梯度的方差和量子比特数量的关系\n", + "plt.subplot(1, 2, 2)\n", + "plt.semilogy(n, variances, \"v\")\n", + "\n", + "# 多项式拟合\n", + "fit = np.polyfit(n, np.log(variances), 1)\n", + "slope = fit[0] \n", + "intercept = fit[1] \n", + "plt.semilogy(n, np.exp(n*slope + intercept), \"r--\", label=\"Slope {:03.4f}\".format(slope))\n", + "plt.xlabel(r\"Qubit #\")\n", + "plt.ylabel(r\"$ \\partial \\theta_{i} \\langle 0|H |0\\rangle$ Variance\")\n", + "plt.title(\"Variance of {} sampled graident\".format(samples))\n", + "plt.legend()\n", + "plt.xlim([1,9])\n", + "plt.ylim([0.0001, 0.1])\n", + "plt.grid()\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "要注意的是,在理论上,只有当我们选取的网络结构还有损失函数满足一定条件时 (2-design)详见论文 [[1]](https://arxiv.org/abs/1803.11173), 才会出现这种效应。接着我们不妨可视化一下不同量子比特数量对的优化曲面的影响:\n", + "\n", + "\n", + "\n", + "**图 1.**        \n", + "      \n", + "       \n", + "      \n", + "(a). 2-量子比特 \n", + "       \n", + "       \n", + "       \n", + "       \n", + "      \n", + "       \n", + "       \n", + "      \n", + "(b). 4-量子比特 \n", + "       \n", + "      \n", + "       \n", + "      \n", + "       \n", + "       \n", + "       \n", + "       \n", + "(c). 6-量子比特\n", + "\n", + "画图时 $\\theta_1$ 和 $\\theta_2$ 是前两个电路参数, 剩余参数全部固定为 $\\pi$。不然我们画不出这个高维度的流形。\n", + "结果不出所料,陡峭程度随着 $n$ 的增大越来越小了,**注意到 Z 轴尺度的极速减小**。相对于2量子比特的情况,6量子比特的优化曲面已经非常扁平了。\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 参考文献\n", + "\n", + "[1] [McClean, J. R., Boixo, S., Smelyanskiy, V. N., Babbush, R. & Neven, H. Barren plateaus in quantum neural network training landscapes. Nat. Commun. 9, 4812 (2018).](https://www.nature.com/articles/s41467-018-07090-4)\n", + "\n", + "\n", + "[2] [Cerezo, M., Sone, A., Volkoff, T., Cincio, L. & Coles, P. J. Cost-Function-Dependent Barren Plateaus in Shallow Quantum Neural Networks. arXiv:2001.00550 (2020).](https://arxiv.org/abs/2001.00550)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/tutorial/Barren/BarrenPlateaus_Tutorial_CN.pdf b/tutorial/Barren/BarrenPlateaus_Tutorial_CN.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e963c8160a52dfa51d9222cbe9294673ae76fc7c Binary files /dev/null and b/tutorial/Barren/BarrenPlateaus_Tutorial_CN.pdf differ diff --git a/tutorial/Barren/figures/2qubit_landscape.png b/tutorial/Barren/figures/2qubit_landscape.png new file mode 100644 index 0000000000000000000000000000000000000000..a26a7be64a910ebcd91ca935cacb167e6785a84e Binary files /dev/null and b/tutorial/Barren/figures/2qubit_landscape.png differ diff --git a/tutorial/Barren/figures/Barren_circuit.png b/tutorial/Barren/figures/Barren_circuit.png new file mode 100644 index 0000000000000000000000000000000000000000..1cc22ad086d6c02809ba6de81bd2598e5b65cd22 Binary files /dev/null and b/tutorial/Barren/figures/Barren_circuit.png differ diff --git a/tutorial/Barren/figures/barren.png b/tutorial/Barren/figures/barren.png new file mode 100644 index 0000000000000000000000000000000000000000..e6f5dbd67d6bc69be0f5c8704bf884abac067fe6 Binary files /dev/null and b/tutorial/Barren/figures/barren.png differ diff --git a/tutorial/Barren/figures/landscape2.png b/tutorial/Barren/figures/landscape2.png new file mode 100644 index 0000000000000000000000000000000000000000..ba385d9e16aa682c4a48f552ff64864f5c438385 Binary files /dev/null and b/tutorial/Barren/figures/landscape2.png differ diff --git a/tutorial/Barren/figures/qubit_landscape.png b/tutorial/Barren/figures/qubit_landscape.png new file mode 100644 index 0000000000000000000000000000000000000000..0c95304029226de7927c74ce9b832312a23dc7ef Binary files /dev/null and b/tutorial/Barren/figures/qubit_landscape.png differ diff --git a/tutorial/Barren/figures/qubit_landscape_compare.png b/tutorial/Barren/figures/qubit_landscape_compare.png new file mode 100644 index 0000000000000000000000000000000000000000..8fbd0b1fea12bb8be34e463270d9c2dbd4ce9070 Binary files /dev/null and b/tutorial/Barren/figures/qubit_landscape_compare.png differ diff --git a/tutorial/GIBBS.ipynb b/tutorial/GIBBS.ipynb deleted file mode 100644 index 25ba86e111f1c14e42d402276ae3c17a8f6d9ceb..0000000000000000000000000000000000000000 --- a/tutorial/GIBBS.ipynb +++ /dev/null @@ -1,351 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 概览\n", - "- 在这个案例中,我们将展示如何通过Paddle Quantum训练量子神经网络来制备量子吉布斯态。\n", - "\n", - "- 首先,让我们通过下面几行代码引入必要的library和package。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "from numpy import array, kron, concatenate, zeros\n", - "from numpy import pi as PI\n", - "from numpy import trace as nptrace\n", - "from paddle import fluid\n", - "from paddle.complex import matmul, transpose, trace\n", - "from paddle_quantum.circuit import UAnsatz\n", - "from paddle_quantum.utils import compute_fid, partial_trace\n", - "import scipy" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "## 背景\n", - "量子计算中的前沿方向是量子机器学习和量子优化,在这两个方向,特定量子态的制备是及其重要的问题。特别的,尤其是吉布斯态(Gibbs state)的制备是实现诸多量子算法所必须的一个步骤。\n", - "- 给定一个n量子位的哈密顿量H(一般来说这是一个$2^n\\times2^n$的厄米矩阵),其在温度$T$下的吉布斯态为$\\rho_G {\\rm{ = }}\\frac{{{e^{ - \\beta H}}}}{{tr({e^{ - \\beta H}})}}$\n", - "- 其中${e^{ - \\beta H}}$是矩阵$ - \\beta H$的矩阵指数,$\\beta = \\frac{1}{{kT}}$是系统的逆温度参数,以及$T$是温度参数,$k$是玻尔兹曼常数(这个例子中我们取为$1$)。\n", - "\n", - "量子计算的诸多应用所需要调用的步骤都依赖于特定哈密顿量的吉布斯态,所以制备出目标哈密顿量的吉布斯态在量子计算和量子机器学习等方向都至关重要,尤其是有着广泛的应用,比如可以用于如下应用中:\n", - "- 量子机器学习中受限波尔兹曼机的学习 [1]\n", - "- 解决凸优化和半正定规划等优化问题 [2]\n", - "- 组合优化问题 [3]\n", - "\n", - "作为一个上手的例子,这里我们首先考虑一个简单的哈密顿量及其吉布斯态。\n", - "- 首先我们考虑的是3量子位的哈密顿量,\n", - "$H = Z \\otimes Z \\otimes I + I \\otimes Z \\otimes Z + Z \\otimes I \\otimes Z$, \n", - "其中\n", - "$$\n", - "I=\\left [\n", - "\\begin{matrix}\n", - "1 & 0 \\\\\n", - "0 & 1 \\\\\n", - "\\end{matrix} \n", - "\\right ], \\quad \n", - "Z=\\left [\n", - "\\begin{matrix}\n", - "1 & 0 \\\\\n", - "0 & -1 \\\\\n", - "\\end{matrix} \n", - "\\right ].\n", - "$$\n", - "\n", - "- 这个例子中,我们将逆温度参数设置为$\\beta = 1.5$。\n", - "\n", - "- 此外,为了方便测试结果,我们利用定义提前生成好了理想情况的吉布斯态。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "beta = 1.5\n", - "\n", - "sigma_I = array([[1, 0], [0, 1]])\n", - "sigma_Z = array([[1, 0], [0, -1]])\n", - "hamiltonian = (-kron(kron(sigma_Z, sigma_Z), sigma_I) - kron(kron(sigma_I, sigma_Z), sigma_Z) - kron(kron(sigma_Z, sigma_I), sigma_Z))\n", - "rho = scipy.linalg.expm(-1 * beta * hamiltonian) / nptrace(scipy.linalg.expm(-1 * beta * hamiltonian))\n", - "hamiltonian = hamiltonian.astype(\"complex64\")\n", - "rho = rho.astype(\"complex64\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 搭建量子神经网络\n", - "- 在这个案例中,我们将通过训练量子神经网络QNN(也可以理解为参数化量子电路)来训练吉布斯态。这里,我们提供一个简单的4量子位的量子电路如下:\n", - "![Ugibbs.jpg](https://release-data.cdn.bcebos.com/PIC%2FUgibbs.jpg)\n", - "\n", - "- 我们预设一些该参数化电路的参数,比如宽度为4量子位,其中第1个量子位是辅助系统,第2-4个量子位是最后产生吉布斯态的系统。\n", - "\n", - "- 初始化其中的变量参数,${\\bf{\\theta }}$代表我们量子神经网络中的参数组成的向量。\n", - " " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%% \n" - } - }, - "outputs": [], - "source": [ - "N = 4 #量子神经网络的宽度\n", - "N_SYS_B = 3 #生成吉布斯态的系统B的量子位数 \n", - "D = 1 #量子神经网络中重复模块的深度,这里只用1层\n", - "SEED = 1 #种子" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "- 接下来我们根据上图中的电路设计,具体通过 Paddle Quantum 的UAnsatz函数来搭建量子神经网络。 " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "def U_theta(theta, input_state, N, D):\n", - " \"\"\"\n", - " Circuit\n", - " \"\"\"\n", - "\n", - " cir = UAnsatz(N, input_state=input_state)\n", - " for i in range(N):\n", - " cir.ry(theta=theta[0][1][i], which_qubit=i + 1)\n", - " \n", - " for repeat in range(D):\n", - " for i in range(1, N):\n", - " cir.cnot(control=[i, i + 1])\n", - " \n", - " for i in range(N):\n", - " cir.ry(theta=theta[repeat][0][i], which_qubit=i + 1)\n", - " return cir.state\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "## 配置训练模型 - 损失函数\n", - "- 现在我们已经有了数据和量子神经网络的架构,我们将进一步定义合适的训练参数、模型和损失函数来达到我们的目标。\n", - "- 具体的我们参考的是[4]中的方法(核心思想是利用吉布斯态达到了最小自由能的性质)。\n", - "- 通过作用量子神经网络$U(\\theta)$在初始态上,我们可以得到输出态$\\left| {\\psi \\left( {\\bf{\\theta }} \\right)} \\right\\rangle $, 其在第2-4个量子位的态记为$\\rho_B(\\theta)$.\n", - "- 设置训练模型中的的损失函数,在吉布斯态学习中,我们利用冯诺依曼熵函数的截断来进行自由能的估计,相应的损失函数参考[4]可以设为 \n", - "$loss= {L_1} + {L_2} + {L_3}$,其中 ${L_1}= tr(H\\rho_B)$, ${L_2} = 2{\\beta^{-1}}{tr}(\\rho_B^2)$ , $L_3 = - {\\beta ^{ - 1}}\\frac{{tr(\\rho_B^3) + 3}}{2}$." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "class Net(fluid.dygraph.Layer):\n", - " \"\"\"\n", - " Construct the model net\n", - " \"\"\"\n", - "\n", - " def __init__(self, shape, param_attr=fluid.initializer.Uniform(low=0.0, high=PI, seed=SEED),\n", - " dtype='float32'):\n", - " super(Net, self).__init__()\n", - "\n", - " self.theta = self.create_parameter(shape=shape, attr=param_attr, dtype=dtype, is_bias=False)\n", - "\n", - " def forward(self, input_state, H, N, N_SYS_B, D):\n", - " \"\"\"\n", - " Args:\n", - " input_state: The initial state with default |0..>\n", - " H: The target Hamiltonian\n", - " Returns:\n", - " The loss.\n", - " \"\"\"\n", - "\n", - " out_state = U_theta(self.theta, input_state, N, D)\n", - "\n", - " # rho_AB = utils.matmul(utils.matrix_conjugate_transpose(out_state), out_state)\n", - " rho_AB = matmul(transpose(\n", - " fluid.framework.ComplexVariable(out_state.real, -out_state.imag), perm=[1, 0]), out_state)\n", - "\n", - " # compute the partial trace and three losses\n", - " rho_B = partial_trace(rho_AB, 2 ** (N - N_SYS_B), 2 ** (N_SYS_B), 1)\n", - " rho_B_squre = matmul(rho_B, rho_B)\n", - " loss1 = (trace(matmul(rho_B, H))).real\n", - " loss2 = (trace(rho_B_squre)).real * 2\n", - " loss3 = - (trace(matmul(rho_B_squre, rho_B))).real / 2\n", - "\n", - " loss = loss1 + loss2 + loss3 # 损失函数\n", - "\n", - " # option: if you want to check whether the imaginary part is 0, uncomment the following\n", - " # print('loss_iminary_part: ', loss.numpy()[1])\n", - " return loss - 3 / 2, rho_B" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 配置训练模型 - 模型参数\n", - "在进行量子神经网络的训练之前,我们还需要进行一些训练(超)参数的设置,例如学习速率与迭代次数。\n", - "- 设定学习速率(learning rate)为0.5, 迭代次数为50次。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "ITR = 50 #训练的总的迭代次数\n", - "\n", - "LR = 0.5 #学习速率" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 进行训练\n", - "\n", - "- 当训练模型的各项参数都设置完成后,我们将数据转化为Paddle动态图中的变量,进而进行量子神经网络的训练。\n", - "- 训练过程中我们用的是[Adam Optimizer](https://www.paddlepaddle.org.cn/documentation/docs/zh/api_cn/optimizer_cn/AdagradOptimizer_cn.html),也可以调用Paddle中提供的其他优化器。\n", - "- 我们将训练过程中的结果依次输出。\n", - "- 特别的我们依次输出了我们学习到的量子态$\\rho_B(\\theta)$与吉布斯态$\\rho_G$的保真度,保真度越高说明QNN输出的态越接近于吉布斯态。\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%% \n" - } - }, - "outputs": [], - "source": [ - "with fluid.dygraph.guard():\n", - " # initial state preparing\n", - " _initial_state_np = concatenate(([[1.]], zeros([1, 2 ** N - 1])), axis=1).astype('complex64')\n", - " initial_state = fluid.dygraph.to_variable(_initial_state_np)\n", - "\n", - " # gibbs Hamiltonian preparing\n", - " H = fluid.dygraph.to_variable(hamiltonian)\n", - "\n", - " # net\n", - " net = Net(shape=[D + 1, 3, N])\n", - "\n", - " # optimizer\n", - " opt = fluid.optimizer.AdamOptimizer(learning_rate=LR, parameter_list=net.parameters())\n", - "\n", - " # gradient descent loop\n", - " for itr in range(1, ITR + 1):\n", - " loss, rho_B = net(initial_state, H, N, N_SYS_B, D)\n", - "\n", - " loss.backward()\n", - " opt.minimize(loss)\n", - " net.clear_gradients()\n", - "\n", - " rho_B = rho_B.numpy()\n", - "\n", - " fid = compute_fid(rho_B, rho)\n", - " print('iter:', itr, 'loss:', '%.4f' % loss.numpy(), 'fid:', '%.4f' % fid)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 总结\n", - "根据上面训练得到的结果,通过大概50次迭代,我们就能达到高于99.5%保真度的高精度吉布斯态,高效并精确地完成了吉布斯态的制备。我们可以通过print函数来输出学习到的量子神经网络的参数和它的输出态。\n", - "\n", - "### 参考文献\n", - "\n", - "[1] M. Kieferová and N. Wiebe, “Tomography and generative training with quantum Boltzmann machines,” Phys. Rev. A, vol. 96, no. 6, p. 062327, Dec. 2017.\n", - "\n", - "[2] F. G. S. L. Brandao and K. M. Svore, “Quantum Speed-Ups for Solving Semidefinite Programs,” in 2017 IEEE 58th Annual Symposium on Foundations of Computer Science (FOCS), 2017, pp. 415–426.\n", - "\n", - "[3] R. D. Somma, S. Boixo, H. Barnum, and E. Knill, “Quantum Simulations of Classical Annealing Processes,” Phys. Rev. Lett., vol. 101, no. 13, p. 130504, Sep. 2008.\n", - "\n", - "[4] Y. Wang, G. Li, and X. Wang, “Variational quantum Gibbs state preparation with a truncated Taylor series,” arXiv:2005.08797, May 2020. [[pdf](https://arxiv.org/pdf/2005.08797.pdf)]" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.10" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/tutorial/GPU/GPU_Tutorial_CN.ipynb b/tutorial/GPU/GPU_Tutorial_CN.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..bee35d487d9f76cd7a057b684bd997341f66cc88 --- /dev/null +++ b/tutorial/GPU/GPU_Tutorial_CN.ipynb @@ -0,0 +1,346 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 在 GPU 上使用 Paddle Quantum\n", + "\n", + " Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. 简介\n", + "\n", + "> 注意,本篇教程具有时效性。同时不同电脑也会有个体差异性,本篇教程不保证所有电脑可以安装成功。\n", + "\n", + "众所周知,在深度学习中,大家都会选择使用 GPU 来进行神经网络模型的训练,因为与 CPU 相比,GPU在浮点数运算方面有着显著的优势。因此,使用 GPU 来训练神经网络模型逐渐成为共同的选择。在 Paddle Quantum 中,我们的量子态和量子门也采用基于浮点数的复数表示,因此我们的模型如果能部署到 GPU 上进行训练,也会显著提升训练速度。\n", + "\n", + "## 2. GPU 选择\n", + "\n", + "在这里,我们推荐选择 Nvidia 的硬件设备,其 CUDA(Compute Unified Device Architecture) 对深度学习的框架支持更好。我们的 PaddlePaddle 也可以比较方便地安装在 CUDA 上。\n", + "\n", + "## 3. 配置 CUDA 环境\n", + "\n", + "### 3.1 安装 CUDA\n", + "\n", + "这里,我们介绍如何在 x64 平台上的 Windows10 系统中配置 CUDA 环境。首先,在[CUDA GPUs | NVIDIA Developer](https://developer.nvidia.com/cuda-gpus)上查看你的GPU是否可以安装CUDA环境。然后,在[NVIDIA 驱动程序下载](https://www.nvidia.cn/Download/index.aspx?lang=cn)下载你的显卡的最新版驱动,并安装到电脑上。\n", + "\n", + "在[飞桨的安装步骤](https://www.paddlepaddle.org.cn/install/quick)中,我们发现,**Paddle Paddle 在 Windows 下仅支持 CUDA 9.0/10.0 的单卡模式;不支持 CUDA 9.1/9.2/10.1**,所以,我们需要安装 CUDA10.0(CUDA9.0在理论上也可以)。在[CUDA Toolkit Archive | NVIDIA Developer](https://developer.nvidia.com/cuda-toolkit-archive)找到 CUDA 10.0 的下载地址:[CUDA Toolkit 10.0 Archive | NVIDIA Developer](https://developer.nvidia.com/cuda-10.0-download-archive),下载CUDA后,运行安装。\n", + "\n", + "在安装过程中,选择**自定义安装**,在 CUDA 选项中,勾选除 Visual Studio Intergration 外的其他内容(除非你理解 Visual Studio Intergration 的作用),然后除 CUDA 之外,其他选项均不勾选。然后安装位置选择默认位置(请留意你的 CUDA 的安装位置,后面需要设置环境变量),等待安装完成。\n", + "\n", + "安装完成之后,打开 Windows 命令行,输入`nvcc -V`,如果看到版本信息,则说明 CUDA 安装成功。\n", + "\n", + "### 3.2 安装 cuDNN\n", + "\n", + "在[NVIDIA cuDNN | NVIDIA Developer](https://developer.nvidia.com/cudnn)下载 cuDNN,根据[飞桨的安装步骤](https://www.paddlepaddle.org.cn/install/quick)中的要求,我们**需要使用 cuDNN 7.6+** ,因此我们下载支持 CUDA 10.0 的最新版 cuDNN 即可。下载完成 cuDNN 后进行解压缩。然后,假设我们的 CUDA 的安装路径为`C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v10.0`,我们将 cuDNN 解压缩后里面的`bin`、`include`和`lib`中的文件都替换 CUDA 的安装路径下的对应文件(如果文件已存在则进行替换,如果未存在则直接粘贴到对应目录中)。到这里,cuDNN 也就安装完成了。\n", + "\n", + "\n", + "### 3.3 配置环境变量\n", + "\n", + "接下来还需要配置环境变量。右键电脑桌面上的“此电脑”(或“文件资源管理器”左栏的“此电脑”),选择“属性”,然后选择左侧的“高级系统设置”,在“高级”这一栏下选择“环境变量”。\n", + "\n", + "现在就进入到了环境变量的设置页面,在系统变量中选择`Path`,点击“编辑”。在出现的页面中,查看是否有`C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v10.0\\bin`和`C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v10.0\\libnvvp`这两个地址(其前缀`C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v10.0`应该为你的 CUDA 的安装位置),如果没有,请手动添加。\n", + "\n", + "### 3.4 验证是否安装成功\n", + "\n", + "打开命令行,输入`cd C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v10.0\\extras\\demo_suite`进入到 CUDA 安装路径(这里也应该为你的 CUDA 的安装位置)。然后分别执行`.\\bandwidthTest.exe`和`.\\deviceQuery.exe`,如果都出现`Result = PASS`,则说明安装成功。\n", + "\n", + "\n", + "## 4. 在 CUDA 环境上安装 PaddlePaddle\n", + "\n", + "根据[飞桨的安装步骤](https://www.paddlepaddle.org.cn/install/quick)中的说明,我们首先需要确定自己的python环境,用`python --version`来查看 python 版本,保证**python版本是3.5.1+/3.6+/3.7+**,并且用`python -m ensurepip`和`python -m pip --version`来查看 pip 版本,**确认是 9.0.1+**。然后,使用`python -m pip install paddlepaddle-gpu==1.8.4.post107 -i https://pypi.tuna.tsinghua.edu.cn/simple`来安装 GPU 版本的 PaddlePaddle。\n", + "\n", + "\n", + "## 5. 安装 Paddle Quantum\n", + "\n", + "下载 Paddle Quantum 的安装包,修改`setup.py`,将其中的`paddlepaddle`改为`paddlepaddle-gpu`,然后按照 Paddle Quantum 的安装要求,执行`pip install -e .`即可。\n", + "\n", + "> 如果你是在一个新的 python 环境中安装了 paddlepaddle-gpu 和 paddle_quantum,请在新 python 环境中安装 jupyter,并在新的 jupyter 下重新打开本教程并运行。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. 检测是否安装成功\n", + "\n", + "打开我们 GPU 版本的 PaddlePaddle 环境,执行下面的命令,若输出为`True`则表示当前 PaddlePaddle 框架可以在GPU上运行。" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import paddle \n", + "from paddle import fluid\n", + "print(fluid.is_compiled_with_cuda())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. 使用教程和示例\n", + "\n", + "在 Paddle Quantum 中,我们使用动态图机制来定义和训练我们的参数化量子线路。在这里,我们依然使用动态图机制,只需要定义动态图机制的运行设备即可。方式如下:\n", + "\n", + "```python\n", + "# 0 表示使用编号为0的GPU\n", + "place = fluid.CUDAPlace(0)\n", + "with fluid.dygraph.guard(place):\n", + " # build and train your quantum circuit model\n", + "```\n", + "\n", + "当我们想在 CPU 上运行时,也采用类似的方式,定义运行设备为CPU:\n", + "```python\n", + "place = fluid.CPUPlace()\n", + "with fluid.dygraph.guard(place):\n", + " # build and train your quantum circuit model\n", + "```\n", + "\n", + "我们可以在命令行中输入`nvidia-smi`来查看 GPU 的使用情况,包括有哪些程序在哪些 GPU 上运行,以及其显存占用情况。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里,我们以 [VQE](https://github.com/PaddlePaddle/Quantum/blob/master/tutorial/VQE) 为例来说明我们该如何使用 GPU。首先,导入相关的包并定义相关的变量和函数。" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from numpy import concatenate\n", + "from numpy import pi as PI\n", + "from numpy import savez, zeros\n", + "from paddle import fluid\n", + "from paddle.complex import matmul, transpose\n", + "from paddle_quantum.circuit import UAnsatz\n", + "from paddle_quantum.utils import hermitian\n", + "import matplotlib.pyplot as plt\n", + "import numpy\n", + "from paddle_quantum.VQE.chemistrysub import H2_generator\n", + "from time import time\n", + "\n", + "Hamiltonian, N = H2_generator()\n", + "\n", + "\n", + "def U_theta(theta, Hamiltonian, N, D):\n", + " \"\"\"\n", + " Quantum Neural Network\n", + " \"\"\"\n", + "\n", + " # 按照量子比特数量/网络宽度初始化量子神经网络\n", + " cir = UAnsatz(N)\n", + "\n", + " # 内置的 {R_y + CNOT} 电路模板\n", + " cir.real_entangled_layer(theta[:D], D)\n", + "\n", + " # 铺上最后一列 R_y 旋转门\n", + " for i in range(N):\n", + " cir.ry(theta=theta[D][i][0], which_qubit=i)\n", + "\n", + " # 量子神经网络作用在默认的初始态 |0000>上\n", + " cir.run_state_vector()\n", + "\n", + " # 计算给定哈密顿量的期望值\n", + " expectation_val = cir.expecval(Hamiltonian)\n", + "\n", + " return expectation_val\n", + "\n", + "\n", + "class StateNet(fluid.dygraph.Layer):\n", + " \"\"\"\n", + " Construct the model net\n", + " \"\"\"\n", + "\n", + " def __init__(self, shape, param_attr=fluid.initializer.Uniform(low=0.0, high=2 * PI), dtype=\"float64\"):\n", + " super(StateNet, self).__init__()\n", + "\n", + " # 初始化 theta 参数列表,并用 [0, 2*pi] 的均匀分布来填充初始值\n", + " self.theta = self.create_parameter(shape=shape, attr=param_attr, dtype=dtype, is_bias=False)\n", + "\n", + " # 定义损失函数和前向传播机制\n", + " def forward(self, Hamiltonian, N, D):\n", + " # 计算损失函数/期望值\n", + " loss = U_theta(self.theta, Hamiltonian, N, D)\n", + "\n", + " return loss\n", + "\n", + "ITR = 80 # 设置训练的总迭代次数\n", + "LR = 0.2 # 设置学习速率\n", + "D = 2 # 设置量⼦神经⽹络中重复计算模块的深度 Depth" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果要使用GPU训练,则运行下面的程序:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 0 表示使用编号为0的GPU\n", + "place_gpu = fluid.CUDAPlace(0)\n", + "with fluid.dygraph.guard(palce_gpu):\n", + " # 确定网络的参数维度\n", + " net = StateNet(shape=[D + 1, N, 1])\n", + "\n", + " # 一般来说,我们利用Adam优化器来获得相对好的收敛,当然你可以改成SGD或者是RMS prop.\n", + " opt = fluid.optimizer.AdamOptimizer(learning_rate=LR, parameter_list=net.parameters())\n", + "\n", + " # 记录优化结果\n", + " summary_iter, summary_loss = [], []\n", + "\n", + " # 优化循环\n", + " for itr in range(1, ITR + 1):\n", + "\n", + " # 前向传播计算损失函数\n", + " loss = net(Hamiltonian, N, D)\n", + "\n", + " # 在动态图机制下,反向传播极小化损失函数\n", + " loss.backward()\n", + " opt.minimize(loss)\n", + " net.clear_gradients()\n", + "\n", + " # 更新优化结果\n", + " summary_loss.append(loss.numpy())\n", + " summary_iter.append(itr)\n", + "\n", + " # 打印结果\n", + " if itr % 20 == 0:\n", + " print(\"iter:\", itr, \"loss:\", \"%.4f\" % loss.numpy())\n", + " print(\"iter:\", itr, \"Ground state energy:\", \"%.4f Ha\" % loss.numpy())\n", + "\n", + " # 储存训练结果到 output 文件夹\n", + " os.makedirs(\"output\", exist_ok=True)\n", + " savez(\"./output/summary_data\", iter=summary_iter, energy=summary_loss)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果要使用CPU训练,则运行下面的程序:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "iter: 20 loss: -1.0669\n", + "iter: 20 Ground state energy: -1.0669 Ha\n", + "iter: 40 loss: -1.1129\n", + "iter: 40 Ground state energy: -1.1129 Ha\n", + "iter: 60 loss: -1.1163\n", + "iter: 60 Ground state energy: -1.1163 Ha\n", + "iter: 80 loss: -1.1172\n", + "iter: 80 Ground state energy: -1.1172 Ha\n" + ] + } + ], + "source": [ + "# 表示使用 CPU\n", + "place_cpu = fluid.CPUPlace()\n", + "with fluid.dygraph.guard(place_cpu):\n", + " # 确定网络的参数维度\n", + " net = StateNet(shape=[D + 1, N, 1])\n", + "\n", + " # 一般来说,我们利用Adam优化器来获得相对好的收敛,当然你可以改成SGD或者是RMS prop.\n", + " opt = fluid.optimizer.AdamOptimizer(learning_rate=LR, parameter_list=net.parameters())\n", + "\n", + " # 记录优化结果\n", + " summary_iter, summary_loss = [], []\n", + "\n", + " # 优化循环\n", + " for itr in range(1, ITR + 1):\n", + "\n", + " # 前向传播计算损失函数\n", + " loss = net(Hamiltonian, N, D)\n", + "\n", + " # 在动态图机制下,反向传播极小化损失函数\n", + " loss.backward()\n", + " opt.minimize(loss)\n", + " net.clear_gradients()\n", + "\n", + " # 更新优化结果\n", + " summary_loss.append(loss.numpy())\n", + " summary_iter.append(itr)\n", + "\n", + " # 打印结果\n", + " if itr % 20 == 0:\n", + " print(\"iter:\", itr, \"loss:\", \"%.4f\" % loss.numpy())\n", + " print(\"iter:\", itr, \"Ground state energy:\", \"%.4f Ha\" % loss.numpy())\n", + "\n", + " # 储存训练结果到 output 文件夹\n", + " # os.makedirs(\"output\", exist_ok=True)\n", + " # savez(\"./output/summary_data\", iter=summary_iter, energy=summary_loss)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8. 总结\n", + "\n", + "实践证明,我们现在的 paddle_quantum 可以在 GPU 下运行,目前需要比较好的 GPU 资源才能体现出 GPU 的加速效果。在未来的版本中,我们也会不断优化 paddle_quantum 在 GPU 下的性能表现。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 参考资料\n", + "\n", + "[1] [Installation Guide Windows :: CUDA Toolkit Documentation](https://docs.nvidia.com/cuda/cuda-installation-guide-microsoft-windows/index.html)\n", + "\n", + "[2] [Installation Guide :: NVIDIA Deep Learning cuDNN Documentation](https://docs.nvidia.com/deeplearning/cudnn/install-guide/index.html#installwindows)\n", + "\n", + "[3] [开始使用_飞桨-源于产业实践的开源深度学习平台](https://www.paddlepaddle.org.cn/install/quick)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/tutorial/GPU/GPU_Tutorial_CN.pdf b/tutorial/GPU/GPU_Tutorial_CN.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a42e51adaf45246f084fa009f2f44da43c30e5db Binary files /dev/null and b/tutorial/GPU/GPU_Tutorial_CN.pdf differ diff --git a/tutorial/Gibbs/GIBBS_Tutorial_CN.ipynb b/tutorial/Gibbs/GIBBS_Tutorial_CN.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..df717fd8216d0d097a087baa9fe074e17048015d --- /dev/null +++ b/tutorial/Gibbs/GIBBS_Tutorial_CN.ipynb @@ -0,0 +1,393 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 吉布斯态的制备 (Gibbs State Preparation)\n", + "\n", + " Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 概览\n", + "- 在这个案例中,我们将展示如何通过Paddle Quantum训练量子神经网络来制备量子吉布斯态。\n", + "\n", + "- 让我们通过下面几行代码引入必要的library和package。" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "import scipy\n", + "\n", + "from numpy import array, concatenate, zeros\n", + "from numpy import pi as PI\n", + "from numpy import trace as np_trace\n", + "\n", + "from paddle import fluid\n", + "from paddle.complex import matmul, trace\n", + "from paddle_quantum.circuit import UAnsatz\n", + "from paddle_quantum.state import density_op\n", + "from paddle_quantum.utils import state_fidelity, partial_trace, hermitian, pauli_str_to_matrix" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## 背景\n", + "\n", + "量子计算中的前沿方向包括量子机器学习和量子优化,在这两个方向中,特定量子态的制备是非常重要的问题。特别的,吉布斯态(Gibbs state)的制备是实现诸多量子算法所必须的一个步骤并且广泛应用于:\n", + "- 量子机器学习中受限波尔兹曼机的学习 [1]\n", + "- 解决凸优化和半正定规划等优化问题 [2]\n", + "- 组合优化问题 [3]\n", + "\n", + "具体的吉布斯态定义如下:给定一个 $n$ 量子位的哈密顿量 $H$(一般来说这是一个$2^n\\times2^n$的厄米矩阵),其在温度 $T$ 下的吉布斯态为 \n", + "$$\n", + "\\rho_G = \\frac{{{e^{ - \\beta H}}}}{{tr({e^{ - \\beta H}})}}\n", + "$$\n", + "\n", + "其中 ${e^{ - \\beta H}}$ 是矩阵 $ - \\beta H$ 的矩阵指数,$\\beta = \\frac{1}{{kT}}$ 是系统的逆温度参数,其中 $T$ 是温度参数,$k$ 是玻尔兹曼常数 (这个例子中我们取 $k = 1$)。作为一个上手的例子,这里我们首先考虑一个3量子比特的哈密顿量及其吉布斯态。\n", + "\n", + "$$\n", + "H = -Z \\otimes Z \\otimes I - I \\otimes Z \\otimes Z - Z \\otimes I \\otimes Z,\\,\n", + "I=\\left [\n", + "\\begin{matrix}\n", + "1 & 0 \\\\\n", + "0 & 1 \\\\\n", + "\\end{matrix} \n", + "\\right ],\n", + "Z=\\left [\n", + "\\begin{matrix}\n", + "1 & 0 \\\\\n", + "0 & -1 \\\\\n", + "\\end{matrix} \n", + "\\right ].$$\n", + "\n", + "\n", + "这个例子中,我们将逆温度参数设置为 $\\beta = 1.5$。此外,为了方便测试结果,我们按照定义提前生成好了理想情况的吉布斯态。" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%% \n" + } + }, + "outputs": [], + "source": [ + "N = 4 # 量子神经网络的宽度\n", + "N_SYS_B = 3 # 用于生成吉布斯态的子系统B的量子比特数 \n", + "SEED = 14 # 固定随机种子" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "beta = 1.5 # 设置逆温度参数 beta\n", + "\n", + "# 生成用泡利字符串表示的特定的哈密顿量\n", + "H = [[-1.0, 'z0,z1'], [-1.0, 'z1,z2'], [-1.0, 'z0,z2']]\n", + "\n", + "# 生成哈密顿量的矩阵信息\n", + "hamiltonian = pauli_str_to_matrix(H, N_SYS_B)\n", + "\n", + "# 生成理想情况下的目标吉布斯态 rho\n", + "rho_G = scipy.linalg.expm(-1 * beta * hamiltonian) / np_trace(scipy.linalg.expm(-1 * beta * hamiltonian))\n", + "\n", + "# 设置成 Paddle quantum 所支持的数据类型\n", + "hamiltonian = hamiltonian.astype(\"complex128\")\n", + "rho_G = rho_G.astype(\"complex128\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 搭建量子神经网络\n", + "\n", + "- 在这个案例中,我们将通过训练量子神经网络QNN(也可以理解为参数化量子电路)来训练吉布斯态。这里,我们提供一个简单的4量子位的量子电路如下:\n", + "![Ugibbs.jpg](https://release-data.cdn.bcebos.com/PIC%2FUgibbs.jpg)\n", + "\n", + "- 我们需要预设一些电路的参数,比如电路有4量子比特,其中第1个量子位是辅助系统,第2-4个量子位是用以产生吉布斯态的子系统。\n", + "\n", + "- 初始化其中的变量参数,${\\bf{\\theta }}$ 代表我们量子神经网络中的参数组成的向量。\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "接下来我们根据上图中的电路设计,通过 Paddle Quantum 的 `UAnsatz` 函数和内置的 `real_entangled_layer(theta, D)` 电路模板来高效搭建量子神经网络。 " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "def U_theta(initial_state, theta, N, D):\n", + " \"\"\"\n", + " Quantum Neural Network\n", + " \"\"\"\n", + " \n", + " # 按照量子比特数量/网络宽度初始化量子神经网络\n", + " cir = UAnsatz(N)\n", + " \n", + " # 内置的 {R_y + CNOT} 电路模板\n", + " cir.real_entangled_layer(theta[:D], D)\n", + " \n", + " # 铺上最后一列 R_y 旋转门\n", + " for i in range(N):\n", + " cir.ry(theta=theta[D][i][0], which_qubit=i)\n", + " \n", + " # 量子神经网络作用在给定的初始态上\n", + " final_state = cir.run_density_matrix(initial_state)\n", + "\n", + " return final_state" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## 配置训练模型 - 损失函数\n", + "- 现在我们已经有了数据和量子神经网络的架构,我们将进一步定义合适的训练参数、模型和损失函数来达到我们的目标。\n", + "- 具体的我们参考的是论文[4]中的方法,核心思想是利用吉布斯态达到了最小自由能的性质。\n", + "- 通过作用量子神经网络 $U(\\theta)$ 在初始态上,我们可以得到输出态 $\\left| {\\psi \\left( {\\bf{\\theta }} \\right)} \\right\\rangle $, 其在第2-4个量子位的态记为 $\\rho_B(\\theta)$。\n", + "- 设置训练模型中的的损失函数。在吉布斯态学习中,我们利用冯诺依曼熵函数的截断来进行自由能的估计,相应的损失函数参考[4]可以设为 \n", + "$loss= {L_1} + {L_2} + {L_3}$,其中 ${L_1}= tr(H\\rho_B)$, ${L_2} = 2{\\beta^{-1}}{tr}(\\rho_B^2)$ , $L_3 = - {\\beta ^{ - 1}}\\big(tr(\\rho_B^3) + 3\\big)/2$。" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "class Net(fluid.dygraph.Layer):\n", + " \"\"\"\n", + " Construct the model net\n", + " \"\"\"\n", + "\n", + " def __init__(self, shape, param_attr=fluid.initializer.Uniform(low=0.0, high=2*PI, seed=SEED),\n", + " dtype='float64'):\n", + " super(Net, self).__init__()\n", + " \n", + " # 初始化 theta 参数列表,并用 [0, 2*pi] 的均匀分布来填充初始值\n", + " self.theta = self.create_parameter(shape=shape, attr=param_attr, dtype=dtype, is_bias=False)\n", + " \n", + " # 初始化 rho = |0..0><0..0| 的密度矩阵\n", + " self.initial_state = fluid.dygraph.to_variable(density_op(N))\n", + "\n", + " # 定义损失函数和前向传播机制\n", + " def forward(self, H, N, N_SYS_B, D):\n", + "\n", + " # 施加量子神经网络\n", + " rho_AB = U_theta(self.initial_state, self.theta, N, D)\n", + "\n", + " # 计算偏迹 partial trace 来获得子系统B所处的量子态 rho_B\n", + " rho_B = partial_trace(rho_AB, 2 ** (N - N_SYS_B), 2 ** (N_SYS_B), 1)\n", + " \n", + " # 计算三个子损失函数\n", + " rho_B_squre = matmul(rho_B, rho_B)\n", + " loss1 = (trace(matmul(rho_B, H))).real\n", + " loss2 = (trace(rho_B_squre)).real * 2 / beta\n", + " loss3 = - ((trace(matmul(rho_B_squre, rho_B))).real + 3) / (2 * beta)\n", + " \n", + " # 最终的损失函数\n", + " loss = loss1 + loss2 + loss3 \n", + "\n", + " return loss, rho_B" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 配置训练模型 - 模型参数\n", + "\n", + "在进行量子神经网络的训练之前,我们还需要进行一些训练的超参数设置,主要是学习速率 (LR, learning rate)、迭代次数(ITR, iteration)和量子神经网络计算模块的深度 (D, Depth)。这里我们设定学习速率为0.5, 迭代次数为50次。读者不妨自行调整来直观感受下超参数调整对训练效果的影响。" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "ITR = 50 # 设置训练的总迭代次数\n", + "LR = 0.5 # 设置学习速率\n", + "D = 1 # 设置量子神经网络中重复计算模块的深度 Depth" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 进行训练\n", + "\n", + "- 当训练模型的各项参数都设置完成后,我们将数据转化为Paddle动态图中的变量,进而进行量子神经网络的训练。\n", + "- 训练过程中我们用的是[Adam Optimizer](https://www.paddlepaddle.org.cn/documentation/docs/zh/api_cn/optimizer_cn/AdagradOptimizer_cn.html),也可以调用Paddle中提供的其他优化器。\n", + "- 我们将训练过程中的结果依次输出。\n", + "- 特别的我们依次输出了我们学习到的量子态$\\rho_B(\\theta)$与吉布斯态$\\rho_G$的保真度,保真度越高说明QNN输出的态越接近于吉布斯态。\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%% \n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "iter: 5 loss: -2.5615 fid: 0.8631\n", + "iter: 10 loss: -3.1189 fid: 0.9504\n", + "iter: 15 loss: -3.3290 fid: 0.9732\n", + "iter: 20 loss: -3.3502 fid: 0.9846\n", + "iter: 25 loss: -3.3446 fid: 0.9868\n", + "iter: 30 loss: -3.3630 fid: 0.9873\n", + "iter: 35 loss: -3.3937 fid: 0.9916\n", + "iter: 40 loss: -3.4087 fid: 0.9948\n", + "iter: 45 loss: -3.4108 fid: 0.9954\n", + "iter: 50 loss: -3.4110 fid: 0.9953\n" + ] + } + ], + "source": [ + "# 初始化paddle动态图机制\n", + "with fluid.dygraph.guard():\n", + " \n", + " # 我们需要将 Numpy array 转换成 Paddle 动态图模式中支持的 variable\n", + " H = fluid.dygraph.to_variable(hamiltonian)\n", + "\n", + " # 确定网络的参数维度\n", + " net = Net(shape=[D + 1, N, 1])\n", + "\n", + " # 一般来说,我们利用Adam优化器来获得相对好的收敛,当然你可以改成SGD或者是RMS prop.\n", + " opt = fluid.optimizer.AdamOptimizer(learning_rate=LR, parameter_list=net.parameters())\n", + "\n", + " # 优化循环\n", + " for itr in range(1, ITR + 1):\n", + " \n", + " # 前向传播计算损失函数并返回生成的量子态 rho_B\n", + " loss, rho_B = net(H, N, N_SYS_B, D)\n", + " \n", + " # 在动态图机制下,反向传播极小化损失函数\n", + " loss.backward()\n", + " opt.minimize(loss)\n", + " net.clear_gradients()\n", + "\n", + " # 转换成 Numpy array 用以计算量子态的保真度 F(rho_B, rho_G)\n", + " rho_B = rho_B.numpy()\n", + " fid = state_fidelity(rho_B, rho_G)\n", + " \n", + " # 打印训练结果\n", + " if itr % 5 == 0:\n", + " print('iter:', itr, 'loss:', '%.4f' % loss.numpy(), 'fid:', '%.4f' % fid)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 总结\n", + "根据上面训练得到的结果,通过大概50次迭代,我们就能达到高于99.5%保真度的高精度吉布斯态,高效并精确地完成了吉布斯态的制备。我们可以通过print函数来输出学习到的量子神经网络的参数和它的输出态。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 参考文献\n", + "\n", + "[1] [Kieferová, M. & Wiebe, N. Tomography and generative training with quantum Boltzmann machines. Phys. Rev. A 96, 062327 (2017).](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.96.062327)\n", + "\n", + "[2] [Brandao, F. G. S. L. & Svore, K. M. Quantum Speed-Ups for Solving Semidefinite Programs. in 2017 IEEE 58th Annual Symposium on Foundations of Computer Science (FOCS) 415–426 (IEEE, 2017). ](https://ieeexplore.ieee.org/abstract/document/8104077)\n", + "\n", + "[3] [Somma, R. D., Boixo, S., Barnum, H. & Knill, E. Quantum Simulations of Classical Annealing Processes. Phys. Rev. Lett. 101, 130504 (2008).](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.101.130504)\n", + "\n", + "[4] [Wang, Y., Li, G. & Wang, X. Variational quantum Gibbs state preparation with a truncated Taylor series. arXiv:2005.08797 (2020).](https://arxiv.org/pdf/2005.08797.pdf)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/tutorial/Gibbs/GIBBS_Tutorial_CN.pdf b/tutorial/Gibbs/GIBBS_Tutorial_CN.pdf new file mode 100644 index 0000000000000000000000000000000000000000..58bf3b661a9f873ff0107987f6957a9062f03f6d Binary files /dev/null and b/tutorial/Gibbs/GIBBS_Tutorial_CN.pdf differ diff --git a/tutorial/Q-Autoencoder/Quantum_Autoencoder_Tutorial_CN.ipynb b/tutorial/Q-Autoencoder/Quantum_Autoencoder_Tutorial_CN.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..f7b4ff66e911f0caa0382ac61c8b46bfea4673ff --- /dev/null +++ b/tutorial/Q-Autoencoder/Quantum_Autoencoder_Tutorial_CN.ipynb @@ -0,0 +1,276 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 量子变分自编码器 (Quantum Autoencoder)\n", + "\n", + " Copyright (c) Institute for Quantum Computing, Baidu Inc. All Rights Reserved. \n", + "\n", + "## 概览\n", + "\n", + "在这个案例中,我们将展示如何训练量子自编码器来压缩和重建给定的量子态(混合态)[1]。量子自编码器的形式与经典自编码器非常相似,同样由编码器 $E$(encoder)和解码器 $D$(decoder)组成。对于输入的 $N$ 量子比特系统的量子态 $\\rho_{in}$(这里我们采用量子力学的密度算符表示来描述混合态),先用编码器 $E = U(\\theta)$ 将信息编码到其中的部分量子比特上。这部分量子比特记为**系统 $A$**。对剩余的量子比特 (这部分记为**系统 $B$**)进行测量并丢弃后,我们就得到了压缩后的量子态 $\\rho_{encode}$!压缩后的量子态维度和量子系统 $A$ 的维度大小保持一致。假设我们需要 $N_A$ 个量子比特来描述系统 $A$ ,那么编码后量子态 $\\rho_{encode}$ 的维度就是 $2^{N_A}\\times 2^{N_A}$。 这里比较特殊的是, 对应我们这一步测量并丢弃的操作的数学操作是 partial trace。读者在直观上可以理解为张量积 $\\otimes$ 的逆运算。\n", + "\n", + "我们不妨来看一个具体的例子来理解:\n", + "给定一个由 $N_A$ 个量子比特描述的量子态 $\\rho_A$ 和另外一个由 $N_B$ 个量子比特描述的量子态 $\\rho_B$, 那么由 $A、B$ 两个子系统构成的的整体量子系统 $N = N_A+ N_B$ 的量子态就可以描述为: $\\rho_{AB} = \\rho_A \\otimes \\rho_B$。现在我们让整个量子系统在酉矩阵 $U$ 的作用下演化一段时间后,得到了一个新的量子态 $\\tilde{\\rho_{AB}} = U\\rho_{AB}U^\\dagger$。 那么如果这时候我们只想得到量子子系统 $A$ 所处于的新的量子态 $\\tilde{\\rho_A}$, 我们该怎么做呢?很简单,只需要测量量子子系统 $B$ 然后丢弃测量结果。运算上这一步由 partial trace 来完成 $\\tilde{\\rho_A} = \\text{Tr}_B (\\tilde{\\rho_{AB}})$。在量桨上,我们可以用内置的 `partial_trace(rho_AB, 2**N_A, 2**N_B, 2)` 指令来完成这一运算。**注意:** 其中最后一个输入为2,表示我们想丢弃量子系统 $B$ 的量子态。\n", + "\n", + "\n", + "\n", + "在讨论完编码的过程后,我们来看看如何解码。为了解码量子态 $\\rho_{encode}$,我们需要引入与系统 $B$ 维度相同的系统 $C$ 并且初始态取为全0态。再用解码器 $D = U^\\dagger(\\theta)$ 作用在整个量子系统 $A+C$ 上对系统 $A$ 中压缩的信息进行解码。我们希望最后得到的量子态 $\\rho_{out}$ 与 $\\rho_{in}$ 尽可能相似并用 Uhlmann-Josza 保真度 $F$ (Fidelity)来衡量他们之间的相似度。\n", + "\n", + "$$ F(\\rho_{in}, \\rho_{out}) = \\left(\\operatorname{tr} \\sqrt{\\sqrt{\\rho_{in}} \\rho_{out} \\sqrt{\\rho_{in}}} \\right)^{2}$$\n", + "\n", + "最后,通过优化编码器里的参数,我们就可以尽可能地提高 $\\rho_{in}$ 与 $\\rho_{out}$ 的保真度。这里我们先引入必要的 package。" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from numpy import diag\n", + "import scipy\n", + "\n", + "import paddle\n", + "from paddle import fluid\n", + "from paddle_quantum.circuit import UAnsatz\n", + "from paddle.complex import matmul, trace, kron\n", + "from paddle_quantum.utils import hermitian, state_fidelity, partial_trace" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 生成初始态\n", + "\n", + "下面我们通过一个简单的例子来展示量子自编码器的工作流程和原理。\n", + "\n", + "我们考虑 $N = 3$ 个量子比特上的量子态 $\\rho_{in}$。我们先通过编码器将其信息编码到下方的两个量子比特(系统 $A$),之后对第一个量子比特(系统 $B$)测量并丢弃,之后引入一个处于0态的量子比特(新的参考系统 $C$)来替代丢弃的量子比特 $B$ 的维度。最后通过解码器,将 $A$ 中压缩的信息复原成 $\\rho_{out}$。在这里,我们假设初态是一个混合态并且 $\\rho_{in}$ 的特征谱为 $\\lambda_i \\in \\{0.4, 0.2, 0.2, 0.1, 0.1, \\,0, \\,0, \\,0 \\}$,然后通过作用一个随机的酉变换来生成初态 $\\rho_{in}$。\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "N_A = 2 # 系统 A 的量子比特数\n", + "N_B = 1 # 系统 B 的量子比特数\n", + "N = N_A + N_B # 总的量子比特数\n", + "\n", + "scipy.random.seed(1) # 固定随机种子\n", + "V = scipy.stats.unitary_group.rvs(2**N) # 随机生成一个酉矩阵\n", + "D = diag([0.4, 0.2, 0.2, 0.1, 0.1, 0, 0, 0]) # 输入目标态rho的谱\n", + "V_H = V.conj().T # 进行厄尔米特转置\n", + "rho_in = (V @ D @ V_H).astype('complex128') # 生成 rho_in\n", + "\n", + "# 将 C 量子系统初始化为\n", + "rho_C = np.diag([1,0]).astype('complex128')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 搭建量子神经网络\n", + "\n", + "在这里,我们用量子神经网络来作为编码器和解码器。假设系统 $A$ 有 $N_A$ 个量子比特,系统 $B$ 和 $C$ 分别有$N_B$ 个量子比特,量子神经网络的深度为 D。编码器 $E$ 作用在系统 $A$ 和 $B$ 共同构成的总系统上,解码器 $D$ 作用在$A$ 和 $C$ 共同构成的总系统上。在我们的问题里,$N_{A} = 2$,$N_{B} = 1$。" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# 设置电路参数\n", + "cir_depth = 6 # 电路深度\n", + "block_len = 2 # 每个模组的长度\n", + "theta_size = N*block_len*cir_depth # 网络参数 theta 的大小\n", + "\n", + "\n", + "# 搭建编码器 Encoder E\n", + "def Encoder(theta):\n", + "\n", + " # 用 UAnsatz 初始化网络\n", + " cir = UAnsatz(N)\n", + " \n", + " # 搭建层级结构:\n", + " for layer_num in range(cir_depth):\n", + " \n", + " for which_qubit in range(N):\n", + " cir.ry(theta[block_len*layer_num*N + which_qubit], which_qubit)\n", + " cir.rz(theta[(block_len*layer_num + 1)*N + which_qubit], which_qubit)\n", + "\n", + " for which_qubit in range(N-1):\n", + " cir.cnot([which_qubit, which_qubit + 1])\n", + " cir.cnot([N-1, 0])\n", + "\n", + " return cir.U" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 配置训练模型-损失函数\n", + "\n", + "在这里,我们定义的损失函数为 \n", + "\n", + "$$\n", + "Loss = 1 - \\langle 0...0|\\rho_{trash}|0...0\\rangle\n", + "$$\n", + "\n", + "其中 $\\rho_{trash}$ 是经过编码后丢弃的 $B$ 系统的量子态。接着我们通过飞桨的自动微分框架来训练量子神经网络,最小化损失函数。如果损失函数最后达到 0,则输入态和输出态完全相同。这就意味着我们完美地实现了压缩和解压缩,这时初态和末态的保真度为 $F(\\rho_{in}, \\rho_{out}) = 1$。" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "iter: 10 loss: 0.1795 fid: 0.8076\n", + "iter: 20 loss: 0.1374 fid: 0.8573\n", + "iter: 30 loss: 0.1149 fid: 0.8812\n", + "iter: 40 loss: 0.1073 fid: 0.8886\n", + "iter: 50 loss: 0.1044 fid: 0.8910\n", + "iter: 60 loss: 0.1029 fid: 0.8921\n", + "iter: 70 loss: 0.1019 fid: 0.8927\n", + "iter: 80 loss: 0.1013 fid: 0.8931\n", + "iter: 90 loss: 0.1009 fid: 0.8933\n", + "iter: 100 loss: 0.1006 fid: 0.8935\n" + ] + } + ], + "source": [ + "# 超参数设置\n", + "N_A = 2 # 系统 A 的量子比特数\n", + "N_B = 1 # 系统 B 的量子比特数\n", + "N = N_A + N_B # 总的量子比特数\n", + "LR = 0.2 # 设置学习速率\n", + "ITR = 100 # 设置迭代次数\n", + "SEED = 14 # 固定初始化参数用的随机数种子\n", + "\n", + "class NET(fluid.dygraph.Layer):\n", + " \"\"\"\n", + " Construct the model net\n", + " \"\"\"\n", + " def __init__(self, shape, param_attr=fluid.initializer.Uniform(\n", + " low=0.0, high=2 * np.pi, seed = SEED), dtype='float64'):\n", + " super(NET, self).__init__()\n", + " \n", + " # 我们需要将 Numpy array 转换成 Paddle 动态图模式中支持的 variable\n", + " self.rho_in = fluid.dygraph.to_variable(rho_in)\n", + " self.rho_C = fluid.dygraph.to_variable(rho_C)\n", + " self.theta = self.create_parameter(shape=shape, \n", + " attr=param_attr, dtype=dtype, is_bias=False)\n", + " \n", + " # 定义损失函数和前向传播机制\n", + " def forward(self):\n", + " # 生成初始的编码器 E 和解码器 D\\n\",\n", + " E = Encoder(self.theta)\n", + " E_dagger = hermitian(E)\n", + " D = E_dagger\n", + " D_dagger = E\n", + "\n", + " # 编码量子态 rho_in\n", + " rho_BA = matmul(matmul(E, self.rho_in), E_dagger)\n", + " \n", + " # 取 partial_trace() 获得 rho_encode 与 rho_trash\n", + " rho_encode = partial_trace(rho_BA, 2 ** N_B, 2 ** N_A, 1)\n", + " rho_trash = partial_trace(rho_BA, 2 ** N_B, 2 ** N_A, 2)\n", + "\n", + " # 解码得到量子态 rho_out\n", + " rho_CA = kron(self.rho_C, rho_encode)\n", + " rho_out = matmul(matmul(D, rho_CA), D_dagger)\n", + " \n", + " # 通过 rho_trash 计算损失函数\n", + " \n", + " zero_Hamiltonian = fluid.dygraph.to_variable(np.diag([1,0]).astype('complex128'))\n", + " loss = 1 - (trace(matmul(zero_Hamiltonian, rho_trash))).real\n", + "\n", + " return loss, self.rho_in, rho_out\n", + "\n", + "\n", + "# 初始化paddle动态图机制 \n", + "with fluid.dygraph.guard():\n", + "\n", + " # 生成网络\n", + " net = NET([theta_size])\n", + "\n", + " # 一般来说,我们利用Adam优化器来获得相对好的收敛,当然你可以改成SGD或者是RMS prop.\n", + " opt = fluid.optimizer.AdagradOptimizer(learning_rate=LR, \n", + " parameter_list=net.parameters())\n", + "\n", + " # 优化循环\n", + " for itr in range(1, ITR + 1):\n", + " \n", + " # 前向传播计算损失函数\n", + " loss, rho_in, rho_out = net()\n", + " \n", + " # 在动态图机制下,反向传播极小化损失函数\n", + " loss.backward()\n", + " opt.minimize(loss)\n", + " net.clear_gradients()\n", + " \n", + " # 计算并打印保真度\n", + " fid = state_fidelity(rho_in.numpy(), rho_out.numpy())\n", + "\n", + " if itr % 10 == 0:\n", + " print('iter:', itr, 'loss:', '%.4f' % loss, 'fid:', '%.4f' % np.square(fid))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 总结\n", + "\n", + "如果系统 $A$ 的维度为 $d_A$,容易证明量子自编码器能实现的压缩重建最大保真度为 $\\rho_{in}$ 最大的 $d_A$ 个本征值之和。在我们的示例里 $d_A = 4$,最大保真度为 \n", + "\n", + "$$ F_{limit} = 0.4 + 0.2 + 0.2 + 0.1 = 0.9$$\n", + "\n", + "通过 100 次迭代,我们训练出的量子自编码器保真度达到 0.89 以上,和最优情况非常接近。\n", + "\n", + "\n", + "## 参考文献\n", + "\n", + "[1] [Romero, J., Olson, J. P. & Aspuru-Guzik, A. Quantum autoencoders for efficient compression of quantum data. Quantum Sci. Technol. 2, 045001 (2017).](https://iopscience.iop.org/article/10.1088/2058-9565/aa8072)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/tutorial/Q-Autoencoder/Quantum_Autoencoder_Tutorial_CN.pdf b/tutorial/Q-Autoencoder/Quantum_Autoencoder_Tutorial_CN.pdf new file mode 100644 index 0000000000000000000000000000000000000000..29779f165dbf9caea4edf82977cd90830e975820 Binary files /dev/null and b/tutorial/Q-Autoencoder/Quantum_Autoencoder_Tutorial_CN.pdf differ diff --git a/tutorial/Q-Autoencoder/figures/encoder_pipeline.png b/tutorial/Q-Autoencoder/figures/encoder_pipeline.png new file mode 100644 index 0000000000000000000000000000000000000000..6387d344b372d7f2b6776c3e59acc8be1de77596 Binary files /dev/null and b/tutorial/Q-Autoencoder/figures/encoder_pipeline.png differ diff --git a/tutorial/Q-Classifier/Quantum_Classifier_Tutorial_CN.ipynb b/tutorial/Q-Classifier/Quantum_Classifier_Tutorial_CN.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..1ddb00d07ca0a2dda9f39f0ffcc595dc6efd0e92 --- /dev/null +++ b/tutorial/Q-Classifier/Quantum_Classifier_Tutorial_CN.ipynb @@ -0,0 +1,930 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 量子分类器 (Quantum Classifier)\n", + "\n", + "\n", + " Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 概览\n", + "\n", + "在本教程中我们一起来学习下如何利用量子神经网络来完成**两分类**任务(后续我们会补充难度更大的多分类任务)。这类方法早期工作的主要代表是 Mitarai et al.(2018) 的 [Quantum Circuit Learning](https://arxiv.org/abs/1803.00745) [1] 还有 Schuld et al.(2018) 的 [Circuit-centric quantum classifiers](https://arxiv.org/abs/1804.00633) [2]。本教程重点复现了前者的理论工作, 请读者跟随我们一起探索其中的奥秘。有经典机器学习基础的读者不妨参考下图思考一下,这个框架和经典的方法有什么异同。\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "首先我们还是引入需要的 library和 package:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "import matplotlib\n", + "import numpy as np\n", + "from numpy import pi as PI\n", + "from matplotlib import pyplot as plt\n", + "\n", + "from paddle import fluid\n", + "from paddle.fluid.framework import ComplexVariable\n", + "from paddle.complex import matmul, transpose\n", + "from paddle_quantum.circuit import UAnsatz\n", + "from paddle_quantum.utils import pauli_str_to_matrix" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# 这是教程中会用到的几个主要函数,下面我们来逐一分析\n", + "__all__ = [\n", + " \"circle_data_point_generator\",\n", + " \"data_point_plot\",\n", + " \"heatmap_plot\",\n", + " \"myRy\",\n", + " \"myRz\",\n", + " \"Observable\",\n", + " \"U_theta\",\n", + " \"Net\",\n", + " \"QClassifier\",\n", + " \"main\",\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 数据集的生成\n", + "\n", + "对于监督学习来说,我们绕不开的一个问题就是 -- `作者采用的数据集是什么样的` ?在这个教程中我们按照论文里所提及方法生成简单的圆形决策边界二分数据集 $\\{(x^{(i)}, y^{(i)})\\}$。其中数据点 $x^{(i)}\\in \\mathbb{R}^{2}$,标签 $y^{(i)} \\in \\{0,1\\}$。\n", + "\n", + "\n", + "\n", + "具体的生成方式和可视化请见如下代码:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# 圆形决策边界两分类数据集生成器\n", + "def circle_data_point_generator(Ntrain, Ntest, boundary_gap, seed_data):\n", + " \"\"\"\n", + " :param Ntrain: number of train samples\n", + " :param Ntest: number of test samples\n", + " :param boundary_gap: value in (0, 0.5), means the gap between two classes\n", + " :param seed_data: random seed\n", + " :return: 'Ntrain' samples for training and 'Ntest' samples for testing\n", + " \"\"\"\n", + " train_x, train_y = [], []\n", + " num_samples, seed_para = 0, 0\n", + " while num_samples < Ntrain + Ntest:\n", + " np.random.seed((seed_data + 10) * 1000 + seed_para + num_samples)\n", + " data_point = np.random.rand(2) * 2 - 1\n", + " # 如果数据点的模小于(0.7 - gap),标为0\n", + " if np.linalg.norm(data_point) < 0.7 - boundary_gap / 2:\n", + " train_x.append(data_point)\n", + " train_y.append(0.)\n", + " num_samples += 1\n", + " # 如果数据点的模大于(0.7 + gap),标为1\n", + " elif np.linalg.norm(data_point) > 0.7 + boundary_gap / 2:\n", + " train_x.append(data_point)\n", + " train_y.append(1.)\n", + " num_samples += 1\n", + " else:\n", + " seed_para += 1\n", + "\n", + " train_x = np.array(train_x).astype(\"float64\")\n", + " train_y = np.array([train_y]).astype(\"float64\").T\n", + " print(\"训练集的维度大小 x {} 和 y {}\".format(np.shape(train_x[0:Ntrain]),\n", + " np.shape(train_y[0:Ntrain])))\n", + " print(\"测试集的维度大小 x {} 和 y {}\".format(np.shape(train_x[Ntrain:]),\n", + " np.shape(train_y[Ntrain:])), \"\\n\")\n", + " return train_x[0:Ntrain], train_y[0:Ntrain], train_x[Ntrain:], train_y[Ntrain:]\n", + "\n", + "\n", + "# 用以可视化生成的数据集\n", + "def data_point_plot(data, label):\n", + " \"\"\"\n", + " :param data: shape [M, 2], means M 2-D data points\n", + " :param label: value 0 or 1\n", + " :return: plot these data points\n", + " \"\"\"\n", + " dim_samples, dim_useless = np.shape(data)\n", + " plt.figure(1)\n", + " for i in range(dim_samples):\n", + " if label[i] == 0:\n", + " plt.plot(data[i][0], data[i][1], color=\"r\", marker=\"o\")\n", + " elif label[i] == 1:\n", + " plt.plot(data[i][0], data[i][1], color=\"b\", marker=\"o\")\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "训练集的维度大小 x (200, 2) 和 y (200, 1)\n", + "测试集的维度大小 x (100, 2) 和 y (100, 1) \n", + "\n", + "训练集 200 个数据点的可视化:\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "测试集 100 个数据点的可视化:\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " 读者不妨自己调节数据集的参数设置来生成属于自己的数据集吧!\n" + ] + } + ], + "source": [ + "# 数据集参数设置\n", + "Ntrain=200 # 规定训练集大小\n", + "Ntest=100 # 规定测试集大小\n", + "boundary_gap=0.5 # 设置决策边界的宽度\n", + "seed_data=2 # 固定随机种子\n", + "\n", + "# 生成自己的数据集\n", + "train_x, train_y, test_x, test_y = circle_data_point_generator(\n", + " Ntrain, Ntest, boundary_gap, seed_data)\n", + "print(\"训练集 {} 个数据点的可视化:\".format(Ntrain))\n", + "data_point_plot(train_x, train_y)\n", + "print(\"测试集 {} 个数据点的可视化:\".format(Ntest))\n", + "data_point_plot(test_x, test_y)\n", + "print(\"\\n 读者不妨自己调节数据集的参数设置来生成属于自己的数据集吧!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 数据的预处理\n", + "\n", + "与经典机器学习不同的是,量子分类器在实际工作的时候需要考虑数据的预处理。我们需要多加一个步骤将经典的数据转化成量子信息才能放在量子计算机上跑。接下来我们看看具体是怎么完成的。首先我们确定需要使用的量子比特数量。因为我们的数据 $\\{x^{(i)} = (x^{(i)}_0, x^{(i)}_1)\\}$ 是二维的, 按照 Mitarai (2018) 论文中的编码方式我们至少需要2个量子比特。接着准备一系列的初始量子态 $\\lvert {00}\\rangle$。然后将经典信息 $\\{x^{(i)}\\}$ 编码成一系列量子门 $U(x^{(i)})$ 并作用在初始量子态上。最终得到一系列的量子态 $\\lvert {\\psi^{(i)}}\\rangle = U(x^{(i)})\\lvert {00}\\rangle$。这样我们就完成从经典信息到量子信息的编码了!给定 $m$ 个量子比特去编码二维的经典数据点,则量子门的构造为:\n", + "\n", + "$$\n", + "U(x^{(i)}) = \\otimes_{j=0}^{m-1} R_j^z\\big[\\arccos(x^{(i)}_{j \\, \\text{mod} \\, 2}\\cdot x^{(i)}_{j \\, \\text{mod} \\, 2})\\big] R_j^y\\big[\\arcsin(x^{(i)}_{j \\, \\text{mod} \\, 2}) \\big]\n", + "$$\n", + "\n", + "注意:这种表示下,我们将第一个量子比特编号为 $j = 0$。下标 $j$ 表示旋转门 $R_z$ 或者 $R_y$ 作用在第 $j+1$ 个量子比特上。论文中作者并没有提及为何要这么编码经典信息,更多编码方式见 [Robust data encodings for quantum classifiers](https://arxiv.org/pdf/2003.01695.pdf)。这里我们也欢迎读者自己创新尝试全新的编码方式!由于这种编码的方式看着比较复杂,我们不妨来举一个简单的例子。假设我们给定一个数据点 $x = (x_0, x_1)= (1,0)$, 显然这个数据点的标签应该为 1,对应上图**蓝色**的点。同时数据点对应的2比特量子门 $U(x)$ 是\n", + "\n", + "$$\n", + "U(x) = \n", + "\\bigg( R_0^z\\big[\\arccos(x_{0}\\cdot x_{0})\\big] R_0^y\\big[\\arcsin(x_{0}) \\big] \\bigg)\n", + "\\otimes \n", + "\\bigg( R_1^z\\big[\\arccos(x_{1}\\cdot x_{1})\\big] R_1^y\\big[\\arcsin(x_{1}) \\big] \\bigg)\n", + "$$\n", + "\n", + "把具体的数值带入我们就能得到:\n", + "$$\n", + "U(x) = \n", + "\\bigg( R_0^z\\big[0\\big] R_0^y\\big[\\pi/2 \\big] \\bigg)\n", + "\\otimes \n", + "\\bigg( R_1^z\\big[\\pi/2\\big] R_1^y\\big[0 \\big] \\bigg)\n", + "$$\n", + "\n", + "我们回忆一下常用的旋转门的矩阵形式:\n", + "\n", + "\n", + "$$ \n", + "R_x(\\theta) := \n", + "\\begin{bmatrix} \n", + "\\cos \\frac{\\theta}{2} &-i\\sin \\frac{\\theta}{2} \\\\ \n", + "-i\\sin \\frac{\\theta}{2} &\\cos \\frac{\\theta}{2} \n", + "\\end{bmatrix}\n", + ",\\quad \n", + "R_y(\\theta) := \n", + "\\begin{bmatrix}\n", + "\\cos \\frac{\\theta}{2} &-\\sin \\frac{\\theta}{2} \\\\ \n", + "\\sin \\frac{\\theta}{2} &\\cos \\frac{\\theta}{2} \n", + "\\end{bmatrix}\n", + ",\\quad \n", + "R_z(\\theta) := \n", + "\\begin{bmatrix}\n", + "e^{-i\\frac{\\theta}{2}} & 0 \\\\ \n", + "0 & e^{i\\frac{\\theta}{2}}\n", + "\\end{bmatrix}\n", + "$$\n", + "\n", + "那么这个两比特量子门 $U(x)$ 的矩阵形式可以写为:\n", + "\n", + "$$\n", + "U(x) = \n", + "\\bigg(\n", + "\\begin{bmatrix}\n", + "1 & 0 \\\\ \n", + "0 & 1\n", + "\\end{bmatrix}\n", + "\\begin{bmatrix}\n", + "\\cos \\frac{\\pi}{4} &-\\sin \\frac{\\pi}{4} \\\\ \n", + "\\sin \\frac{\\pi}{4} &\\cos \\frac{\\pi}{4} \n", + "\\end{bmatrix}\n", + "\\bigg)\n", + "\\otimes \n", + "\\bigg(\n", + "\\begin{bmatrix}\n", + "e^{-i\\frac{\\pi}{4}} & 0 \\\\ \n", + "0 & e^{i\\frac{\\pi}{4}}\n", + "\\end{bmatrix}\n", + "\\begin{bmatrix}\n", + "1 &0 \\\\ \n", + "0 &1\n", + "\\end{bmatrix}\n", + "\\bigg)\n", + "$$\n", + "\n", + "化简后我们作用在零初始化的 $\\lvert {00}\\rangle$ 量子态上可以得到编码后的量子态 $\\lvert {\\psi}\\rangle$,\n", + "\n", + "$$\n", + "\\lvert {\\psi}\\rangle =\n", + "U(x)\\lvert {00}\\rangle = \\frac{1}{2}\n", + "\\begin{bmatrix}\n", + "1-i &0 &-1+i &0 \\\\ \n", + "0 &1+i &0 &-1-i \\\\\n", + "1-i &0 &1-i &0 \\\\\n", + "0 &1+i &0 &1+i \n", + "\\end{bmatrix}\n", + "\\begin{bmatrix}\n", + "1 \\\\\n", + "0 \\\\\n", + "0 \\\\\n", + "0\n", + "\\end{bmatrix}\n", + "= \\frac{1}{2}\n", + "\\begin{bmatrix}\n", + "1-i \\\\\n", + "0 \\\\\n", + "1-i \\\\\n", + "0\n", + "\\end{bmatrix}\n", + "$$\n", + "\n", + "接着我们来看看代码上怎么实现这种编码方式。需要注意的是:代码中使用了一个张量积的小技巧\n", + "\n", + "$$\n", + "(U_1 \\lvert {0}\\rangle)\\otimes (U_2 \\lvert {0}\\rangle) = (U_1 \\otimes U_2) \\lvert {0}\\rangle\\otimes\\lvert {0}\\rangle\n", + "= (U_1 \\otimes U_2) \\lvert {00}\\rangle\n", + "$$" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "作为测试我们输入以上的经典信息:\n", + "(x_0, x_1) = (1, 0)\n", + "编码后输出的2比特量子态为:\n", + "[[[0.5-0.5j 0. +0.j 0.5-0.5j 0. +0.j ]]]\n" + ] + } + ], + "source": [ + "def myRy(theta):\n", + " \"\"\"\n", + " :param theta: parameter\n", + " :return: Y rotation matrix\n", + " \"\"\"\n", + " return np.array([[np.cos(theta / 2), -np.sin(theta / 2)],\n", + " [np.sin(theta / 2), np.cos(theta / 2)]])\n", + "\n", + "def myRz(theta):\n", + " \"\"\"\n", + " :param theta: parameter\n", + " :return: Z rotation matrix\n", + " \"\"\"\n", + " return np.array([[np.cos(theta / 2) - np.sin(theta / 2) * 1j, 0],\n", + " [0, np.cos(theta / 2) + np.sin(theta / 2) * 1j]])\n", + "\n", + "# 经典 -> 量子数据编码器\n", + "def datapoints_transform_to_state(data, n_qubits):\n", + " \"\"\"\n", + " :param data: shape [-1, 2]\n", + " :param n_qubits: the number of qubits to which the data transformed\n", + " :return: shape [-1, 1, 2 ^ n_qubits]\n", + " \"\"\"\n", + " dim1, dim2 = data.shape\n", + " res = []\n", + " for sam in range(dim1):\n", + " res_state = 1.\n", + " zero_state = np.array([[1, 0]])\n", + " for i in range(n_qubits):\n", + " if i % 2 == 0:\n", + " state_tmp=np.dot(zero_state, myRy(np.arcsin(data[sam][0])).T)\n", + " state_tmp=np.dot(state_tmp, myRz(np.arccos(data[sam][0] ** 2)).T)\n", + " res_state=np.kron(res_state, state_tmp)\n", + " elif i % 2 == 1:\n", + " state_tmp=np.dot(zero_state, myRy(np.arcsin(data[sam][1])).T)\n", + " state_tmp=np.dot(state_tmp, myRz(np.arccos(data[sam][1] ** 2)).T)\n", + " res_state=np.kron(res_state, state_tmp)\n", + " res.append(res_state)\n", + "\n", + " res = np.array(res)\n", + " return res.astype(\"complex128\")\n", + "\n", + "print(\"作为测试我们输入以上的经典信息:\")\n", + "print(\"(x_0, x_1) = (1, 0)\")\n", + "print(\"编码后输出的2比特量子态为:\")\n", + "print(datapoints_transform_to_state(np.array([[1, 0]]), n_qubits=2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 构造量子神经网络 (QNN)\n", + "\n", + "那么在完成上述从经典数据到量子数据的编码后,我们现在可以把这些量子态输入到量子计算机里面了。在那之前,我们还需要设计下我们所采用的网络结构。\n", + "\n", + "\n", + "\n", + "为了方便,我们统一将上述参数化的量子神经网络称为 $U(\\boldsymbol{\\theta})$。这个 $U(\\boldsymbol{\\theta})$ 是我们分类器的关键组成部分,他需要一定的复杂结构来拟合我们的决策边界。这一点和传统的神经网络是类似的。我们还是拿原来提过的这个数据点 $x = (x_0, x_1)= (1,0)$ 来举例子,编码过后我们已经得到了一个量子态 $\\lvert {\\psi}\\rangle$,\n", + "\n", + "$$\n", + "\\lvert {\\psi}\\rangle =\n", + "\\frac{1}{2}\n", + "\\begin{bmatrix}\n", + "1-i \\\\\n", + "0 \\\\\n", + "1-i \\\\\n", + "0\n", + "\\end{bmatrix}\n", + "$$\n", + "\n", + "接着我们把这个量子态输入进我们的量子神经网络(QNN),也就是把一个酉矩阵乘以一个向量。得到处理过后的量子态 $\\lvert {\\varphi}\\rangle$\n", + "\n", + "$$\n", + "\\lvert {\\varphi}\\rangle = U(\\boldsymbol{\\theta})\\lvert {\\psi}\\rangle\n", + "$$\n", + "\n", + "如果我们把所有的参数 $\\theta$ 都设置为 $\\theta = \\pi$, 那么我们就可以写出具体的矩阵了:\n", + "\n", + "$$\n", + "\\lvert {\\varphi}\\rangle = \n", + "U(\\boldsymbol{\\theta} =\\pi)\\lvert {\\psi}\\rangle=\n", + "\\begin{bmatrix}\n", + "0 &0 &-1 &0 \\\\ \n", + "-1 &0 &0 &0 \\\\\n", + "0 &1 &0 &0 \\\\\n", + "0 &0 &0 &1 \n", + "\\end{bmatrix}\n", + "\\cdot\n", + "\\frac{1}{2}\n", + "\\begin{bmatrix}\n", + "1-i \\\\\n", + "0 \\\\\n", + "1-i \\\\\n", + "0\n", + "\\end{bmatrix}\n", + "= \\frac{1}{2}\n", + "\\begin{bmatrix}\n", + "-1+i \\\\\n", + "-1+i \\\\\n", + "0 \\\\\n", + "0\n", + "\\end{bmatrix}\n", + "$$" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# 模拟搭建量子神经网络\n", + "def U_theta(theta, n, depth): \n", + " \"\"\"\n", + " :param theta: dim: [n, depth + 3]\n", + " :param n: number of qubits\n", + " :param depth: circuit depth\n", + " :return: U_theta\n", + " \"\"\"\n", + " # 初始化网络\n", + " cir = UAnsatz(n)\n", + " \n", + " # 先搭建广义的旋转层\n", + " for i in range(n):\n", + " cir.rz(theta[i][0], i)\n", + " cir.ry(theta[i][1], i)\n", + " cir.rz(theta[i][2], i)\n", + "\n", + " # 默认深度为 depth = 1\n", + " # 搭建纠缠层和 Ry旋转层\n", + " for d in range(3, depth + 3):\n", + " for i in range(n-1):\n", + " cir.cnot([i, i + 1])\n", + " cir.cnot([n-1, 0])\n", + " for i in range(n):\n", + " cir.ry(theta[i][d], i)\n", + "\n", + " return cir.U" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 测量与损失函数\n", + "\n", + "当我们在量子计算机上(QPU)用 QNN 处理过初始量子态 $\\lvert {\\psi}\\rangle$ 后, 我们需要重新测量这个新的量子态 $\\lvert {\\varphi}\\rangle$ 来获取经典信息。这些处理过后的经典信息可以用来计算损失函数 $\\mathcal{L}(\\boldsymbol{\\theta})$。最后我们再通过经典计算机(CPU)来不断更新QNN参数 $\\boldsymbol{\\theta}$ 并优化损失函数。这里我们采用的测量方式是测量泡利 $Z$ 算符在第一个量子比特上的期望值。 具体来说,\n", + "\n", + "$$\n", + "\\langle Z \\rangle = \n", + "\\langle \\varphi |Z\\otimes I\\cdots \\otimes I| \\varphi\\rangle\n", + "$$\n", + "\n", + "复习一下,泡利 $Z$ 算符的矩阵形式为:\n", + "\n", + "$$ Z := \\begin{bmatrix} 1 &0 \\\\ 0 &-1 \\end{bmatrix}$$\n", + "\n", + "继续我们前面的2量子比特的例子,测量过后我们得到的期望值就是:\n", + "\n", + "$$\n", + "\\langle Z \\rangle = \n", + "\\langle \\varphi |Z\\otimes I| \\varphi\\rangle = \n", + "\\frac{1}{2}\n", + "\\begin{bmatrix}\n", + "-1-i \\quad\n", + "-1-i \\quad\n", + "0 \\quad\n", + "0\n", + "\\end{bmatrix}\n", + "\\begin{bmatrix}\n", + "1 &0 &0 &0 \\\\ \n", + "0 &1 &0 &0 \\\\\n", + "0 &0 &-1 &0 \\\\\n", + "0 &0 &0 &-1 \n", + "\\end{bmatrix}\n", + "\\cdot\n", + "\\frac{1}{2}\n", + "\\begin{bmatrix}\n", + "-1+i \\\\\n", + "-1+i \\\\\n", + "0 \\\\\n", + "0\n", + "\\end{bmatrix}\n", + "= 1\n", + "$$\n", + "\n", + "好奇的读者就会问了,咦?这个测量结果好像就是我们原来的标签1啊,这是不是意味着我们已经成功的分类这个数据点了?其实并不然,因为 $\\langle Z \\rangle$ 的取值范围通常在 $[-1,1]$之间。 为了对应我们的标签范围 $y^{(i)} \\in \\{0,1\\}$, 我们还需要将区间上下限映射上。这个映射最简单的做法就是让\n", + "\n", + "$$\n", + "\\tilde{y}^{(i)} = \\frac{\\langle Z \\rangle}{2} + \\frac{1}{2} + bias \\quad \\in [0, 1]\n", + "$$\n", + "\n", + "其中加入偏置(bias)是机器学习中的一个小技巧,目的就是为了让决策边界不受制于原点或者一些超平面。一般我们默认偏置初始化为0,并且优化器在迭代过程中会类似于参数 $\\theta$ 一样不断更新偏置确保 $\\tilde{y}^{(i)} \\in [0, 1]$。当然读者也可以选择其他复杂的映射比如说 sigmoid 函数。映射过后我们就可以把 $\\tilde{y}^{(i)}$ 看作是我们估计出的标签(label)了。如果 $\\tilde{y}^{(i)}< 0.5$ 就对应标签 0,如果 $\\tilde{y}^{(i)}> 0.5$ 就对应标签 1。 我们稍微复习一下整个流程,\n", + "\n", + "\n", + "$$\n", + "x^{(i)} \\rightarrow \\lvert {\\psi}\\rangle^{(i)} \\rightarrow U(\\boldsymbol{\\theta})\\lvert {\\psi}\\rangle^{(i)} \\rightarrow\n", + "\\lvert {\\varphi}\\rangle^{(i)} \\rightarrow ^{(i)}\\langle \\varphi |Z\\otimes I\\cdots \\otimes I| \\varphi\\rangle^{(i)}\n", + "\\rightarrow \\langle Z \\rangle \\rightarrow \\tilde{y}^{(i)}\n", + "$$\n", + "\n", + "最后我们就可以把损失函数定义为平方损失函数:\n", + "\n", + "$$\n", + "\\mathcal{L} = \\sum_{(i)} |y^{(i)} - \\tilde{y}^{(i)}|^2\n", + "$$" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "# 生成只作用在第一个量子比特上的泡利 Z 算符\n", + "# 其余量子比特上都作用单位矩阵\n", + "def Observable(n):\n", + " \"\"\"\n", + " :param n: number of qubits\n", + " :return: local observable: Z \\otimes I \\otimes ...\\otimes I\n", + " \"\"\"\n", + " Ob = pauli_str_to_matrix([[1.0, 'z0']], n)\n", + " return Ob" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "# 搭建整个优化流程图\n", + "class Net(fluid.dygraph.Layer):\n", + " \"\"\"\n", + " Construct the model net\n", + " \"\"\"\n", + " def __init__(self,\n", + " n, # number of qubits\n", + " depth, # circuit depth\n", + " seed_paras=1,\n", + " dtype='float64'):\n", + " super(Net, self).__init__()\n", + "\n", + " self.n = n\n", + " self.depth = depth\n", + " \n", + " # 初始化参数列表 theta,并用 [0, 2*pi] 的均匀分布来填充初始值\n", + " self.theta = self.create_parameter(\n", + " shape=[n, depth + 3],\n", + " attr=fluid.initializer.Uniform(\n", + " low=0.0, high=2*PI, seed=seed_paras),\n", + " dtype=dtype,\n", + " is_bias=False)\n", + " \n", + " # 初始化偏置 (bias)\n", + " self.bias = self.create_parameter(\n", + " shape=[1],\n", + " attr=fluid.initializer.NormalInitializer(\n", + " scale=0.01, seed=seed_paras + 10),\n", + " dtype=dtype,\n", + " is_bias=False)\n", + "\n", + " # 定义向前传播机制、计算损失函数 和交叉验证正确率\n", + " def forward(self, state_in, label):\n", + " \"\"\"\n", + " Args:\n", + " state_in: The input quantum state, shape [-1, 1, 2^n]\n", + " label: label for the input state, shape [-1, 1]\n", + " Returns:\n", + " The loss:\n", + " L = (( + 1)/2 + bias - label)^2\n", + " \"\"\"\n", + " \n", + " # 我们需要将 Numpy array 转换成 Paddle 动态图模式中支持的 variable\n", + " Ob = fluid.dygraph.to_variable(Observable(self.n))\n", + " label_pp = fluid.dygraph.to_variable(label)\n", + "\n", + " # 按照随机初始化的参数 theta \n", + " Utheta = U_theta(self.theta, n=self.n, depth=self.depth)\n", + " \n", + " # 因为 Utheta是学习得到的,我们这里用行向量运算来提速而不会影响训练效果\n", + " state_out = matmul(state_in, Utheta) # 维度 [-1, 1, 2 ** n]\n", + " \n", + " # 测量得到泡利 Z 算符的期望值 \n", + " E_Z = matmul(matmul(state_out, Ob),\n", + " transpose(ComplexVariable(state_out.real, -state_out.imag),\n", + " perm=[0, 2, 1]))\n", + " \n", + " # 映射 处理成标签的估计值 \n", + " state_predict = E_Z.real[:, 0] * 0.5 + 0.5 + self.bias\n", + " loss = fluid.layers.reduce_mean((state_predict - label_pp) ** 2)\n", + " \n", + " # 计算交叉验证正确率\n", + " is_correct = fluid.layers.where(\n", + " fluid.layers.abs(state_predict - label_pp) < 0.5).shape[0]\n", + " acc = is_correct / label.shape[0]\n", + "\n", + " return loss, acc, state_predict.numpy()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 训练效果与调参\n", + "\n", + "好了, 那么定义完以上所有的概念之后我们不妨来看看实际的训练效果!" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "def heatmap_plot(net, N):\n", + " # generate data points x_y_\n", + " Num_points = 30\n", + " x_y_ = []\n", + " for row_y in np.linspace(0.9, -0.9, Num_points):\n", + " row = []\n", + " for row_x in np.linspace(-0.9, 0.9, Num_points):\n", + " row.append([row_x, row_y])\n", + " x_y_.append(row)\n", + " x_y_ = np.array(x_y_).reshape(-1, 2).astype(\"float64\")\n", + "\n", + " # compute the prediction: heat_data\n", + " input_state_test = fluid.dygraph.to_variable(\n", + " datapoints_transform_to_state(x_y_, N))\n", + " loss_useless, acc_useless, state_predict = net(state_in=input_state_test,\n", + " label=x_y_[:, 0])\n", + " heat_data = state_predict.reshape(Num_points, Num_points)\n", + "\n", + " # plot\n", + " fig = plt.figure(1)\n", + " ax = fig.add_subplot(111)\n", + " x_label = np.linspace(-0.9, 0.9, 3)\n", + " y_label = np.linspace(0.9, -0.9, 3)\n", + " ax.set_xticks([0, Num_points // 2, Num_points - 1])\n", + " ax.set_xticklabels(x_label)\n", + " ax.set_yticks([0, Num_points // 2, Num_points - 1])\n", + " ax.set_yticklabels(y_label)\n", + " im = ax.imshow(heat_data, cmap=plt.cm.RdBu)\n", + " plt.colorbar(im)\n", + " plt.show()\n", + "\n", + "def QClassifier(Ntrain, Ntest, gap, N, D, EPOCH, LR, BATCH, seed_paras, seed_data,):\n", + " \"\"\"\n", + " Quantum Binary Classifier\n", + " \"\"\"\n", + " # 初始化paddle动态图机制\n", + " with fluid.dygraph.guard():\n", + " \n", + " # 生成数据集\n", + " train_x, train_y, test_x, test_y = circle_data_point_generator(\n", + " Ntrain=Ntrain, Ntest=Ntest, boundary_gap=gap, seed_data=seed_data)\n", + " \n", + " # 读取训练集的维度\n", + " N_train = train_x.shape[0]\n", + " \n", + " # 定义优化图\n", + " net = Net(n=N, depth=D, seed_paras=seed_paras)\n", + " \n", + " # 一般来说,我们利用Adam优化器来获得相对好的收敛,当然你可以改成SGD或者是RMSprop\n", + " opt = fluid.optimizer.AdamOptimizer(\n", + " learning_rate=LR, parameter_list=net.parameters())\n", + " \n", + " # 初始化寄存器存储正确率 acc 等信息\n", + " summary_iter, summary_test_acc = [], []\n", + " \n", + " # 优化循环\n", + " for ep in range(EPOCH):\n", + " for itr in range(N_train // BATCH):\n", + " \n", + " # 将经典数据编码成量子态 |psi>, 维度 [-1, 2 ** N]\n", + " input_state = fluid.dygraph.to_variable(\n", + " datapoints_transform_to_state(\n", + " train_x[itr * BATCH:(itr + 1) * BATCH], N))\n", + " \n", + " # 前向传播计算损失函数\n", + " loss, train_acc, state_predict_useless \\\n", + " = net(state_in=input_state,\n", + " label=train_y[itr * BATCH:(itr + 1) * BATCH])\n", + " if itr % 10 == 0:\n", + " # 计算测试集上的正确率 test_acc\n", + " input_state_test = fluid.dygraph.to_variable(\n", + " datapoints_transform_to_state(test_x, N))\n", + " loss_useless, test_acc, state_predict_useless \\\n", + " = net(state_in=input_state_test,\n", + " label=test_y)\n", + " print(\"epoch:\", ep, \"iter:\", itr,\n", + " \"loss: %.4f\" % loss.numpy(),\n", + " \"train acc: %.4f\" % train_acc,\n", + " \"test acc: %.4f\" % test_acc)\n", + " \n", + " # 存储正确率 acc 等信息\n", + " summary_iter.append(itr + ep * N_train)\n", + " summary_test_acc.append(test_acc)\n", + " \n", + " # 在动态图机制下,反向传播极小化损失函数\n", + " loss.backward()\n", + " opt.minimize(loss)\n", + " net.clear_gradients()\n", + "\n", + " # 画出 heatmap 表示的决策边界\n", + " heatmap_plot(net, N=N)\n", + "\n", + " return summary_test_acc" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "训练集的维度大小 x (200, 2) 和 y (200, 1)\n", + "测试集的维度大小 x (100, 2) 和 y (100, 1) \n", + "\n", + "epoch: 0 iter: 0 loss: 0.0249 train acc: 1.0000 test acc: 0.5200\n", + "epoch: 0 iter: 10 loss: 0.5726 train acc: 0.0000 test acc: 0.5700\n", + "epoch: 0 iter: 20 loss: 0.0702 train acc: 1.0000 test acc: 0.6500\n", + "epoch: 0 iter: 30 loss: 0.0947 train acc: 1.0000 test acc: 0.6900\n", + "epoch: 0 iter: 40 loss: 0.2799 train acc: 0.0000 test acc: 0.7100\n", + "epoch: 0 iter: 50 loss: 0.1501 train acc: 1.0000 test acc: 0.7500\n", + "epoch: 0 iter: 60 loss: 0.1587 train acc: 1.0000 test acc: 0.8400\n", + "epoch: 0 iter: 70 loss: 0.1633 train acc: 1.0000 test acc: 0.9500\n", + "epoch: 0 iter: 80 loss: 0.2033 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 0 iter: 90 loss: 0.1852 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 0 iter: 100 loss: 0.1369 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 0 iter: 110 loss: 0.1306 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 0 iter: 120 loss: 0.1341 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 0 iter: 130 loss: 0.1072 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 0 iter: 140 loss: 0.0822 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 0 iter: 150 loss: 0.0838 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 0 iter: 160 loss: 0.1581 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 0 iter: 170 loss: 0.1007 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 0 iter: 180 loss: 0.1596 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 0 iter: 190 loss: 0.1123 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 1 iter: 0 loss: 0.1815 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 1 iter: 10 loss: 0.0504 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 1 iter: 20 loss: 0.1949 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 1 iter: 30 loss: 0.1756 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 1 iter: 40 loss: 0.0438 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 1 iter: 50 loss: 0.1355 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 1 iter: 60 loss: 0.1786 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 1 iter: 70 loss: 0.1217 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 1 iter: 80 loss: 0.2004 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 1 iter: 90 loss: 0.0691 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 1 iter: 100 loss: 0.0649 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 1 iter: 110 loss: 0.1367 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 1 iter: 120 loss: 0.0856 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 1 iter: 130 loss: 0.0660 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 1 iter: 140 loss: 0.0495 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 1 iter: 150 loss: 0.0781 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 1 iter: 160 loss: 0.1616 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 1 iter: 170 loss: 0.1413 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 1 iter: 180 loss: 0.1501 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 1 iter: 190 loss: 0.1235 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 2 iter: 0 loss: 0.1762 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 2 iter: 10 loss: 0.0502 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 2 iter: 20 loss: 0.1897 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 2 iter: 30 loss: 0.1797 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 2 iter: 40 loss: 0.0395 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 2 iter: 50 loss: 0.1357 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 2 iter: 60 loss: 0.1772 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 2 iter: 70 loss: 0.1269 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 2 iter: 80 loss: 0.1983 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 2 iter: 90 loss: 0.0622 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 2 iter: 100 loss: 0.0608 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 2 iter: 110 loss: 0.1372 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 2 iter: 120 loss: 0.0882 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 2 iter: 130 loss: 0.0722 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 2 iter: 140 loss: 0.0474 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 2 iter: 150 loss: 0.0761 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 2 iter: 160 loss: 0.1618 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 2 iter: 170 loss: 0.1443 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 2 iter: 180 loss: 0.1495 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 2 iter: 190 loss: 0.1243 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 3 iter: 0 loss: 0.1759 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 3 iter: 10 loss: 0.0554 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 3 iter: 20 loss: 0.1893 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 3 iter: 30 loss: 0.1803 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 3 iter: 40 loss: 0.0390 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 3 iter: 50 loss: 0.1329 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 3 iter: 60 loss: 0.1754 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 3 iter: 70 loss: 0.1275 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 3 iter: 80 loss: 0.1969 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 3 iter: 90 loss: 0.0617 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 3 iter: 100 loss: 0.0603 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 3 iter: 110 loss: 0.1366 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 3 iter: 120 loss: 0.0900 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 3 iter: 130 loss: 0.0789 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 3 iter: 140 loss: 0.0470 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 3 iter: 150 loss: 0.0741 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 3 iter: 160 loss: 0.1613 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 3 iter: 170 loss: 0.1443 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 3 iter: 180 loss: 0.1487 train acc: 1.0000 test acc: 1.0000\n", + "epoch: 3 iter: 190 loss: 0.1231 train acc: 1.0000 test acc: 1.0000\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "主程序段总共运行了 52.354016065597534 秒\n" + ] + } + ], + "source": [ + "def main():\n", + " \"\"\"\n", + " main\n", + " \"\"\"\n", + " time_start = time.time()\n", + " acc = QClassifier(\n", + " Ntrain = 200, # 规定训练集大小\n", + " Ntest = 100, # 规定测试集大小\n", + " gap = 0.5, # 设定决策边界的宽度\n", + " N = 4, # 所需的量子比特数量\n", + " D = 1, # 采用的电路深度\n", + " EPOCH = 4, # 训练 epoch 轮数\n", + " LR = 0.01, # 设置学习速率\n", + " BATCH = 1, # 训练时 batch 的大小\n", + " seed_paras = 19, # 设置随机种子用以初始化各种参数\n", + " seed_data = 2, # 固定生成数据集所需要的随机种子\n", + " )\n", + " \n", + " time_span = time.time() - time_start\n", + " print('主程序段总共运行了', time_span, '秒')\n", + "\n", + "if __name__ == '__main__':\n", + " main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "\n", + "# 参考文献\n", + "\n", + "[1] [Mitarai, K., Negoro, M., Kitagawa, M. & Fujii, K. Quantum circuit learning. Phys. Rev. A 98, 032309 (2018).](https://arxiv.org/abs/1803.00745)\n", + "\n", + "[2] [Schuld, M., Bocharov, A., Svore, K. M. & Wiebe, N. Circuit-centric quantum classifiers. Phys. Rev. A 101, 032308 (2020).](https://arxiv.org/abs/1804.00633)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/tutorial/Q-Classifier/Quantum_Classifier_Tutorial_CN.pdf b/tutorial/Q-Classifier/Quantum_Classifier_Tutorial_CN.pdf new file mode 100644 index 0000000000000000000000000000000000000000..23ce66100ad5735c2e896d45dd740a68ccdf34fc Binary files /dev/null and b/tutorial/Q-Classifier/Quantum_Classifier_Tutorial_CN.pdf differ diff --git a/tutorial/Q-Classifier/figures/classifier_circuit.png b/tutorial/Q-Classifier/figures/classifier_circuit.png new file mode 100644 index 0000000000000000000000000000000000000000..a553790bbdea4c4c2090c81dbf348fdba4845060 Binary files /dev/null and b/tutorial/Q-Classifier/figures/classifier_circuit.png differ diff --git a/tutorial/Q-Classifier/figures/data.png b/tutorial/Q-Classifier/figures/data.png new file mode 100644 index 0000000000000000000000000000000000000000..0824f70aa3e2b6830ac57182c163430e235ff3bf Binary files /dev/null and b/tutorial/Q-Classifier/figures/data.png differ diff --git a/tutorial/Q-Classifier/figures/pipeline.png b/tutorial/Q-Classifier/figures/pipeline.png new file mode 100644 index 0000000000000000000000000000000000000000..319abb80e6a43326643a0de243e05d9a94ba88b6 Binary files /dev/null and b/tutorial/Q-Classifier/figures/pipeline.png differ diff --git a/tutorial/Q-GAN/QGAN_Tutorial_CN.ipynb b/tutorial/Q-GAN/QGAN_Tutorial_CN.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..3c80a65bafa8ebe2e0b42bfa5b50670a654eb100 --- /dev/null +++ b/tutorial/Q-GAN/QGAN_Tutorial_CN.ipynb @@ -0,0 +1,490 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 量子生成对抗网络(Quantum GAN)\n", + "\n", + " Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. 经典生成对抗网络\n", + "\n", + "### 生成对抗网络简介\n", + "\n", + "生成对抗网络(Generative Adversarial Network, GAN)是生成模型的一种,是深度学习在近些年中一个重要的发展[1]。它分为两个部分:生成器 $G$(Generator)和判别器 $D$ (Discriminator)。生成器接受随机的噪声信号,以此为输入来生成我们期望得到的数据。判别器判断接收到的数据是不是来自真实数据,通常输出一个 $P(x)$,表示输入数据 $x$ 是真实数据的概率。\n", + "\n", + "### 纳什均衡\n", + "\n", + "纳什均衡(Nash equilibrium)是指在包含两个或以上参与者的非合作博弈(Non-cooperative game)中,假设每个参与者都知道其他参与者的均衡策略的情况下,没有参与者可以通过改变自身策略使自身受益时的一个概念解。在博弈论中,如果每个参与者都选择了自己的策略,并且没有玩家可以通过改变策略而其他参与者保持不变而获益,那么当前的策略选择的集合及其相应的结果构成了纳什均衡。\n", + "\n", + "GAN 采用了纳什均衡的思想。在 GAN 中,生成器和判别器在进行非合作博弈。在双方博弈过程中,不论生成器的策略是什么,判别器最好的策略就是尽量做出判别;而无论判别器的策略是什么,生成器最好的策略就是尽量使判别器无法判别。因此博弈的两个当事人的策略组合及其相应的结果就构成了纳什均衡。**当达到纳什均衡时,生成器就具备了生成真实数据的能力,而判别器也无法区分生成数据和真实数据了**。\n", + "\n", + "### 优化目标\n", + "\n", + "在 GAN 中,我们重点想要得到的是一个优秀的生成器(但是只有优秀的判别器才能准确判断生成器是否优秀),所以我们训练的理想结果是判别器无法识别出数据是来自真实数据还是生成数据。\n", + "\n", + "因此我们的目标函数如下:\n", + "\n", + "$$\\min_{G}\\max_{D} V(G,D)= \\min_{G}\\max_{D}\\mathbb{E}_{x\\sim P_{data}}[\\log D(x)]+\\mathbb{E}_{z\\sim P_{z}}[\\log(1-D(G(z)))]$$\n", + "\n", + "这里,$G$ 表示生成器的参数,$D$ 表示判别器的参数。实际过程中,通常采用交替训练的方式,即先固定 $G$,训练 $D$,然后再固定 $D$,训练 $G$,不断往复。当两者的性能足够时,模型会收敛,两者达到纳什均衡。\n", + "\n", + "### 优点\n", + "\n", + "- 相对其他生成模型,GAN 的生成效果更好。\n", + "- 理论上,只要是可微分函数都可以用于构建生成器和判别器,因此能够与深度神经网络结合做深度生成模型。\n", + "- GAN 相对其他生成模型来说,不依赖先验假设,我们事先不需要假设数据的分布和规律。\n", + "- GAN 生成数据的形式也很简单,只需要通过生成器进行前向传播即可。\n", + "\n", + "### 缺点\n", + "\n", + "- GAN 无需预先建模,因此过于自由导致训练难以收敛而且不稳定。\n", + "- GAN 存在梯度消失问题,即很可能会达到这样一种状态,判别器的效果特别好,生成器的效果特别差。在这种情况下,判别器的训练没有任何损失,因此也没有有效的梯度信息去回传给生成器让它优化自己。\n", + "- GAN 的学习过程可能发生崩溃问题,生成器开始退化,总是生成同样的样本点,无法继续学习。而此时,判别器也会对相似的样本点指向相似的方向,模型参数已经不再更新,但是实际效果却很差。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. 量子生成对抗网络\n", + "\n", + "量子生成对抗网络与经典的类似,只不过不再用于生成经典数据,而是生成量子态[2-3]。在实践中,如果我们有一个量子态,其在观测后会坍缩为某一本征态,无法恢复到之前的量子态,因此我们如果有一个方法可以根据已有的目标量子态生成出很多与之相同(或相近)的量子态,会很方便我们的实验。\n", + "\n", + "假设我们已有的目标量子态都来自一个混合态,它们属于同一个系综,其密度算符为$\\rho$。然后我们需要有一个生成器 $G$,它的输入是一个噪声数据,我们用一个系综 $\\rho_{z}=\\sum_{i}p_{i}|z_{i}\\rangle\\langle z_{i}|$ 来表示。因此我们每次取出一个随机噪声样本 $|z_{i}\\rangle$,通过生成器后得到生成的量子态 $|x\\rangle=G|z_{i}\\rangle$,我们期望生成的 $|x\\rangle$ 与目标量子态相近。\n", + "\n", + "值得注意的是,对于上文中提到的目标态的系综和噪声数据的系综,我们都认为有一个已有的物理设备可以生成出一个该系综下的量子态,而由于量子物理的相关性质,我们每次可以得到一个真正随机的量子态。但是在计算机程序中,我们仍然只能模拟这一过程。\n", + "\n", + "对于判别器,我们期望判别器可以判断我们输入的量子态是已有的目标态还是生成的量子态,这一过程需要由测量给出。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. 一个简单的例子\n", + "\n", + "### 简介\n", + "\n", + "简单起见,我们假设已有的目标量子态是一个纯态,且生成器接受的输入为$|0\\rangle$。\n", + "\n", + "制备已有的目标量子态的线路:\n", + "\n", + "\n", + "\n", + "生成器的线路为:\n", + "\n", + "\n", + "\n", + "判别器的线路为:\n", + "\n", + "\n", + "\n", + "通过对判别器输出的量子态进行测量,我们可以得到将目标态判断为目标态的概率 $P_{T}$ 和将生成态判断为目标态的概率 $P_{G}$(通过对判别器连接目标态和生成器这两个不同的输入得到)。\n", + "\n", + "\n", + "### 具体过程\n", + "\n", + "假设已有的目标量子态为 $|\\psi\\rangle$,生成器生成的量子态为 $|x\\rangle=G|00\\rangle$(生成器采用两量子比特线路,其中第0个量子比特认为是生成的量子态)。\n", + "\n", + "判别器对数据进行判别并得到量子态$|\\phi\\rangle$,那么当输入为目标态时,$|\\phi\\rangle=D(|\\psi\\rangle\\otimes |00\\rangle)$;当输入为生成态时,$|\\phi\\rangle=D(G\\otimes I)|000\\rangle$。\n", + "\n", + "对于判别器得到的量子态,我们还需要采用泡利 Z 门对第3个量子比特进行测量,从而得到判别器对输入量子态的判断结果(即判别器认为输入是目标态的概率)。首先有 $M_{z}=I\\otimes I\\otimes\\sigma_{z}$,而测量结果为 $\\text{disc_output}=\\langle\\phi|M_{z}|\\phi\\rangle$,所以测量结果为目标态的概率是 $P=(\\text{disc_output}+1)/2$。\n", + "\n", + "我们定义判别器的损失函数为 $\\mathcal{L}_{D}=P_{G}(\\text{gen_theta}, \\text{disc_phi})-P_{T}(\\text{disc_phi})$,生成器的损失函数为 $\\mathcal{L}_{G}=-P_{G}(\\text{gen_theta}, \\text{disc_phi})$。这里的 $P_{G}$ 和 $P_{T}$ 分别是输入量子态为生成态和目标态时,$P=(\\text{disc_output}+1)/2$ 的表达式,gen_theta 和 disc_phi 分别是生成器和判别器线路的参数。\n", + "\n", + "因此我们只需要分别优化目标函数 $\\min_{\\text{disc_phi}}\\mathcal{L}_{D}$ 和 $\\min_{\\text{gen_theta}}\\mathcal{L}_{G}$ 即可交替训练判别器和生成器。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. 在 paddle quantum 上的实现" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "首先导入相关的包。" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import paddle\n", + "from paddle import fluid\n", + "from paddle_quantum.circuit import UAnsatz\n", + "from paddle_quantum.utils import partial_trace, hermitian, state_fidelity\n", + "from paddle import complex\n", + "from progressbar import *" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "然后定义我们的网络模型 QGAN。" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "class QGAN(fluid.dygraph.Layer):\n", + " def __init__(self):\n", + " super(QGAN, self).__init__()\n", + " # 用以制备目标量子态的角度\n", + " target_omega_0 = 0.9 * np.pi\n", + " target_omega_1 = 0.2 * np.pi\n", + " self.target_omega = fluid.dygraph.to_variable(np.array([target_omega_0, target_omega_1], np.float64))\n", + " # 生成器和判别器电路的参数\n", + " self.gen_theta = self.create_parameter([9], dtype=\"float64\", attr=fluid.initializer.Uniform(low=0.0, high=np.pi, seed=7))\n", + " self.disc_phi = self.create_parameter([9], dtype=\"float64\", attr=fluid.initializer.Uniform(low=0.0, high=np.pi, seed=8))\n", + " # 制备目标量子态\n", + " cir = UAnsatz(3)\n", + " cir.ry(self.target_omega[0], 0)\n", + " cir.rz(self.target_omega[1], 0)\n", + " self.target_state = cir.run_state_vector()\n", + "\n", + " def generator(self, theta):\n", + " \"\"\"\n", + " 生成器的量子线路\n", + " \"\"\"\n", + " cir = UAnsatz(3)\n", + " cir.u3(*theta[:3], 0)\n", + " cir.u3(*theta[3:6], 1)\n", + " cir.cnot([0, 1])\n", + " cir.u3(*theta[6:], 0)\n", + "\n", + " return cir\n", + "\n", + " def discriminator(self, phi):\n", + " \"\"\"\n", + " 判别器的量子线路\n", + " \"\"\"\n", + " cir = UAnsatz(3)\n", + " cir.u3(*phi[:3], 0)\n", + " cir.u3(*phi[3:6], 2)\n", + " cir.cnot([0, 2])\n", + " cir.u3(*phi[6:], 0)\n", + "\n", + " return cir\n", + "\n", + " def disc_target_as_target(self):\n", + " \"\"\"\n", + " 判别器将目标态判断为目标态的概率\n", + " \"\"\"\n", + " # 判别器电路\n", + " cir = self.discriminator(self.disc_phi)\n", + " cir.run_state_vector(self.target_state)\n", + " # 判别器对目标态的判断结果\n", + " target_disc_output = cir.expecval([[1.0, 'z2']])\n", + " prob_as_target = (target_disc_output + 1) / 2\n", + "\n", + " return prob_as_target\n", + "\n", + " def disc_gen_as_target(self):\n", + " \"\"\"\n", + " 判别器将生成态判断为目标态的概率\n", + " \"\"\"\n", + " # 得到生成器生成的量子态\n", + " gen_state = self.generator(self.gen_theta).run_state_vector()\n", + " # 判别器电路\n", + " cir = self.discriminator(self.disc_phi)\n", + " cir.run_state_vector(gen_state)\n", + " # 判别器对生成态的判断结果\n", + " gen_disc_output = cir.expecval([[1.0, 'z2']])\n", + " prob_as_target = (gen_disc_output + 1) / 2\n", + " \n", + " return prob_as_target\n", + "\n", + " def forward(self, model_name):\n", + " if model_name == 'gen':\n", + " # 计算生成器的损失函数,loss值的区间为[-1, 0],0表示生成效果极差,为-1表示生成效果极好\n", + " loss = -1 * self.disc_gen_as_target()\n", + " else:\n", + " # 计算判别器的损失函数,loss值的区间为[-1, 1],为-1表示完美区分,为0表示无法区分,为1表示区分颠倒\n", + " loss = self.disc_gen_as_target() - self.disc_target_as_target()\n", + "\n", + " return loss\n", + "\n", + " def get_target_state(self):\n", + " \"\"\"\n", + " 得到目标态的密度矩阵表示\n", + " \"\"\"\n", + " state = self.target_state\n", + " state = complex.reshape(state, [1] + state.shape)\n", + " density_matrix = complex.matmul(hermitian(state), state)\n", + " state = partial_trace(density_matrix, 2, 4, 2)\n", + "\n", + " return state.numpy()\n", + "\n", + " def get_generated_state(self):\n", + " \"\"\"\n", + " 得到生成态的密度矩阵表示\n", + " \"\"\"\n", + " state = self.generator(self.gen_theta).run_state_vector()\n", + " state = complex.reshape(state, [1] + state.shape)\n", + " density_matrix = complex.matmul(hermitian(state), state)\n", + " state = partial_trace(density_matrix, 2, 4, 2)\n", + "\n", + " return state.numpy()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "接下来我们使用 paddle 的动态图机制来训练我们的模型。" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Training: 100% |##########################| Elapsed Time: 0:01:30 Time: 0:01:30\r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "the density matrix of the target state:\n", + "[[0.02447174+0.j 0.125 +0.09081782j]\n", + " [0.125 -0.09081782j 0.97552826+0.j ]] \n", + "\n", + "the density matrix of the generated state:\n", + "[[0.01664936+0.j 0.03736201+0.11797786j]\n", + " [0.03736201-0.11797786j 0.98335064+0.j ]] \n", + "\n", + "the distance between these two quantum states is 0.016958549205174953 \n", + "\n", + "the fidelity between these two quantum states is 0.9952202072599429\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "# 学习率\n", + "LR = 0.1\n", + "# 总的迭代次数\n", + "ITR = 15\n", + "# 每次迭代时,判别器的迭代次数\n", + "ITR1 = 20\n", + "# 每次迭代时,生成器的迭代次数\n", + "ITR2 = 50\n", + "\n", + "# 用来记录loss值的变化\n", + "loss_history = list()\n", + "with fluid.dygraph.guard():\n", + " gan_demo = QGAN()\n", + " optimizer = fluid.optimizer.SGDOptimizer(learning_rate=LR, parameter_list=gan_demo.parameters())\n", + " widgets = ['Training: ', Percentage(), ' ', Bar('#'), ' ', Timer(), ' ', ETA()]\n", + " pbar = ProgressBar(widgets=widgets, maxval=ITR * 70).start()\n", + " for itr0 in range(ITR):\n", + " # 记录判别器loss值的变化\n", + " loss_disc_history = list()\n", + " # 训练判别器\n", + " for itr1 in range(ITR1):\n", + " pbar.update(itr0 * (ITR1 + ITR2) + itr1)\n", + " loss_disc = gan_demo('disc')\n", + " loss_disc.backward()\n", + " optimizer.minimize(loss_disc, parameter_list=[gan_demo.disc_phi], no_grad_set=[gan_demo.gen_theta])\n", + " gan_demo.clear_gradients()\n", + " loss_disc_history.append(loss_disc.numpy()[0])\n", + "\n", + " # 记录生成器loss值的变化\n", + " loss_gen_history = list()\n", + " # 训练生成器\n", + " for itr2 in range(ITR2):\n", + " pbar.update(itr0 * (ITR1 + ITR2) + ITR1 + itr2)\n", + " loss_gen = gan_demo('gen')\n", + " loss_gen.backward()\n", + " optimizer.minimize(loss_gen, parameter_list=[gan_demo.gen_theta], no_grad_set=[gan_demo.disc_phi])\n", + " gan_demo.clear_gradients()\n", + " loss_gen_history.append(loss_gen.numpy()[0])\n", + "\n", + " loss_history.append((loss_disc_history, loss_gen_history))\n", + " pbar.finish()\n", + " \n", + " # 得到目标量子态\n", + " target_state = gan_demo.get_target_state()\n", + " # 得到生成器最终生成的量子态\n", + " gen_state = gan_demo.get_generated_state()\n", + " print(\"the density matrix of the target state:\")\n", + " print(target_state, \"\\n\")\n", + " print(\"the density matrix of the generated state:\")\n", + " print(gen_state, \"\\n\")\n", + " # 计算两个量子态之间的距离,这里的距离定义为tr[(target_state-gen_state)^2]\n", + " distance = np.trace(np.matmul(target_state-gen_state, target_state-gen_state)).real\n", + " # 计算两个量子态的保真度\n", + " fidelity = state_fidelity(target_state, gen_state)\n", + " print(\"the distance between these two quantum states is\", distance, \"\\n\")\n", + " print(\"the fidelity between these two quantum states is\", fidelity)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们通过比较目标量子态和生成量子态的密度矩阵 $\\rho_\\text{target}$ 和 $\\rho_\\text{gen}$ 以及计算它们之间的距离 $\\text{tr}[(\\rho_\\text{target}-\\rho_\\text{gen})^2]$ 和保真度可以得知,我们的生成器生成了一个与目标态很相近的量子态。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. 训练过程的可视化\n", + "接下来我们观察一下,在训练过程中,判别器和生成器的 loss 曲线变化过程。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "首先安装所需要的 package。" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import clear_output\n", + "!pip install celluloid\n", + "clear_output()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "接下来,我们绘制 loss 曲线的变化。" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "from celluloid import Camera\n", + "def draw_pic(loss_history):\n", + " fig, axes = plt.subplots(nrows=1, ncols=2)\n", + " camera = Camera(fig)\n", + " axes[0].set_title(\"discriminator\")\n", + " axes[0].set_xlabel(\"disc_iter\")\n", + " axes[0].set_ylabel(\"disc_loss\")\n", + " axes[0].set_xlim(0, 20)\n", + " axes[0].set_ylim(-1, 1)\n", + " axes[1].set_title(\"generator\")\n", + " axes[1].set_xlabel(\"gen_iter\")\n", + " axes[1].set_ylabel(\"gen_loss\")\n", + " axes[1].set_xlim(0, 50)\n", + " axes[1].set_ylim(-1, 0)\n", + " for loss in loss_history:\n", + " disc_data, gen_data = loss\n", + " disc_x_data = range(0, len(disc_data))\n", + " gen_x_data = range(0, len(gen_data))\n", + " axes[0].plot(disc_x_data, disc_data, color='red')\n", + " axes[1].plot(gen_x_data, gen_data, color='blue')\n", + " camera.snap()\n", + " animation = camera.animate(interval=600, repeat=True, repeat_delay=800)\n", + " animation.save(\"./figures/loss.gif\")\n", + "draw_pic(loss_history)\n", + "clear_output()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这是我们所绘制的loss曲线的变化过程:\n", + "\n", + "![needed_rerun_after_drawing](./figures/loss.gif)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在这个动态图片中,每个帧代表一次迭代的过程。在一次迭代中,左边的红线表示判别器的 loss 曲线,右边的蓝线表示生成器的 loss 曲线。可以看出,在初始的时候,判别器和生成器每次都能从一个比较差的判别能力和生成能力逐渐学习到当前情况下比较好的判别能力和生成能力。随着学习的进行,生成器的生成能力越来越强,判别器的能力也越来越强,但是却也无法判别出真实数据和生成数据,因为这种时候生成器已经生成出了接近真实数据的生成数据,此时模型已经收敛。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 参考文献\n", + "\n", + "\n", + "[1] [Goodfellow, I. J. et al. Generative Adversarial Nets. Proc. 27th Int. Conf. Neural Inf. Process. Syst. (2014).](https://papers.nips.cc/paper/5423-generative-adversarial-nets)\n", + "\n", + "[2] [Lloyd, S. & Weedbrook, C. Quantum Generative Adversarial Learning. Phys. Rev. Lett. 121, 040502 (2018).](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.121.040502)\n", + "\n", + "[3] [Benedetti, M., Grant, E., Wossnig, L. & Severini, S. Adversarial quantum circuit learning for pure state approximation. New J. Phys. 21, (2019).](https://iopscience.iop.org/article/10.1088/1367-2630/ab14b5)\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/tutorial/Q-GAN/QGAN_Tutorial_CN.pdf b/tutorial/Q-GAN/QGAN_Tutorial_CN.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b923a228f04abbc173539c2cf58857039bfdf52c Binary files /dev/null and b/tutorial/Q-GAN/QGAN_Tutorial_CN.pdf differ diff --git a/tutorial/Q-GAN/figures/discriminator.png b/tutorial/Q-GAN/figures/discriminator.png new file mode 100644 index 0000000000000000000000000000000000000000..1bd790092d49d72c8672cc251bf0eaddbf09dae1 Binary files /dev/null and b/tutorial/Q-GAN/figures/discriminator.png differ diff --git a/tutorial/Q-GAN/figures/generator.png b/tutorial/Q-GAN/figures/generator.png new file mode 100644 index 0000000000000000000000000000000000000000..8074555666b11bd48c27ab97891e62d93a54c80d Binary files /dev/null and b/tutorial/Q-GAN/figures/generator.png differ diff --git a/tutorial/Q-GAN/figures/loss.gif b/tutorial/Q-GAN/figures/loss.gif new file mode 100644 index 0000000000000000000000000000000000000000..3dc9db651085576eeab4df33d4fd20c99bddd40c Binary files /dev/null and b/tutorial/Q-GAN/figures/loss.gif differ diff --git a/tutorial/Q-GAN/figures/target_state.png b/tutorial/Q-GAN/figures/target_state.png new file mode 100644 index 0000000000000000000000000000000000000000..880db151e51df3c769d737422ba45c7f6fabe88e Binary files /dev/null and b/tutorial/Q-GAN/figures/target_state.png differ diff --git a/tutorial/QAOA.ipynb b/tutorial/QAOA.ipynb deleted file mode 100644 index c3e9aead34d159e113aee4ce55eeb3c22e2f5f78..0000000000000000000000000000000000000000 --- a/tutorial/QAOA.ipynb +++ /dev/null @@ -1,809 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 简体中文 | [English](./QAOA_En.ipynb)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 准备\n", - "\n", - "本文档演示 Paddle Quantum 上量子近似优化算法(QAOA,Quantum Approximate Optimization Algorithm)的工作流程 [1]。\n", - "\n", - "开始之前完成准备工作:\n", - "\n", - " - 调用飞桨 paddlepaddle\n", - "\n", - " - 调用常用的库, 例如画图工具库 networkx 和 matplotlib.pyplot\n", - "\n", - " - 调用自定义函数 " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "outputs": [], - "source": [ - "from paddle import fluid\n", - "\n", - "import os\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "import networkx as nx\n", - "\n", - "from numpy import matmul as np_matmul\n", - "from paddle.complex import matmul as pp_matmul\n", - "from paddle.complex import transpose\n", - "from paddle_quantum.circuit import UAnsatz" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 背景\n", - "\n", - "\n", - "量子近似优化算法(QAOA,Quantum Approximate Optimization Algorithm)是可以在近期有噪中等规模(NISQ,Noisy Intermediate-Scale Quantum)量子计算机上运行且具有广泛应用前景的量子算法。例如,QAOA 可以用来处理压缩图信号和二次优化等领域常见的离散组合优化问题。这类优化问题通常可以归结为下面的数学模型:\n", - "\n", - "\n", - " $$F=\\max_{z_i\\in\\{-1,1\\}} \\sum q_{ij}(1-z_iz_j)=-\\min_{z_i\\in\\{-1,1\\}} \\sum q_{ij}z_iz_j+ \\sum q_{ij}. $$\n", - "\n", - "\n", - "其中, $z_i \\in \\{-1 ,1\\} $ 是待求的二元参数,系数 $q_{ij}$ 是 $z_i z_j$ 的权重 (weight)。一般地,精确求解该问题对于经典计算机是 NP-hard 的,而 QAOA 被认为对近似求解这类困难问题具有潜在速度优势。\n", - "\n", - "QAOA 的工作原理是把上述经典优化问题(例如组合优化问题)甚至量子优化问题(例如量子多体系统中 Ising 模型的求解)等价地转化为求解一个物理系统哈密顿量(Hamiltonian)的基态能量(对应优化问题的最优值)及其相应的基态(对应于优化问题的最优解)。在数学形式上,QAOA 等价于求解一个实对角矩阵 $H$ 的最小特征值及其对应的特征向量。\n", - "\n", - "和另外一种常见的变分量子特征求解器(VQE, Variational Quantum Eigensolver) 一样,QAOA 也是一种量子-经典混杂算法。 然而 QAOA 参数化量子电路的实现更简单,仅需两个可以调节参数的量子电路模块组成。\n", - "\n", - "接下来,我们通过图的最大割问题 (Max-Cut problem)来展示 QAOA 算法的工作流程和原理。" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "# 示例\n", - "\n", - "## 1. Max-Cut 问题\n", - "\n", - "图的 Max-Cut 问题可以描述为:对于一个给定的包含 $N$ 个顶点 (nodes or vertices)和 $M$ 条边 (edges) 的无向图,找到一种分割方案将图的顶点集合分割成两个无交子集合 $S$ 和 $S^\\prime$,使得连接这两个顶点集合之间边的数目尽可能多。如图所示,我们考虑含4个顶点且具有环形结构的图: \n", - "\n", - " ![ring4.png](https://release-data.cdn.bcebos.com/PIC%2FMaxCut.png) \n", - "\n", - "Max-Cut 问题建模:在做分割时,若顶点 $i$ 属于集合 $S$ 时,赋值 $z_i=1$;若顶点 $j$ 属于 $S^\\prime$ 时,则令 $z_j=-1$。那么对于图的任意连接顶点 $(i, j)$ 的边则满足:若顶点属于同一集合 $S$ 或 $S^\\prime$ 时,$z_iz_j=1$; 若顶点分别属于不同集合时,$z_izj=-1$。于是 Max-Cut 问题转化为如下优化问题:\n", - "\n", - "$$ F=\\min_{z_i\\in\\{-1, 1\\}} z_1 z_2+z_2z_3+z_3z_4+z_4z_1.$$\n", - "\n", - "这里所有 $q_{ij}$ 均设置为 1,表示每条边的权重相等。该问题的所有可行解由比特串 $ \\boldsymbol{z}=z_1z_2z_3z_4 \\in \\{-1, 1\\}^4$ 组成,而且通常需要遍历所有比特串才能得到问题的最小值(最优解)。容易看出,比特串的数目是顶点数目 $N$ 的指数级别,即 $2^N$。因此,随着顶点数目的增加,搜索的代价也会呈指数级别增加。\n", - "\n", - "接下来,我们提供两种方法来预处理编码经典优化问题的图,即如何通过 Paddle Quantum 输入和可视化无权(或带权重)图:\n", - "\n", - "- 方法1是通过指定图的顶点和相应的边(及其权重)\n", - "- 方法2是通过直接输入图的邻接矩阵。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "outputs": [], - "source": [ - "def generate_graph(N, GRAPHMETHOD):\n", - " \"\"\"\n", - " It plots an N-node graph which is specified by Method 1 or 2.\n", - " \n", - " Args:\n", - " N: number of nodes (vertices) in the graph\n", - " METHOD: choose which method to generate a graph\n", - " Return:\n", - " the specific graph and its adjacency matrix\n", - " \"\"\"\n", - " # Method 1 generates a graph by self-definition\n", - " if GRAPHMETHOD == 1:\n", - " print(\"Method 1 generates the graph from self-definition using EDGE description\")\n", - " graph = nx.Graph()\n", - " graph_nodelist=range(N)\n", - " graph.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 0)])\n", - " graph_adjacency = nx.to_numpy_matrix(graph, nodelist=graph_nodelist)\n", - " # Method 2 generates a graph by using its adjacency matrix directly\n", - " elif GRAPHMETHOD == 2:\n", - " print(\"Method 2 generates the graph from networks using adjacency matrix\")\n", - " graph_adjacency = np.array([[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 1, 0]])\n", - " graph = nx.Graph(graph_adjacency)\n", - " else:\n", - " print(\"Method doesn't exist \")\n", - "\n", - " return graph, graph_adjacency" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里指定方法1来预处理图:\n", - "\n", - "- 图的顶点数目 $N=4$\n", - "- 图的输入方法 GRAPHMETHOD = 1 \n", - "\n", - "注意:上述两种方法给出的图的顶点均从 $0$ 开始计数。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "outputs": [], - "source": [ - "# number of qubits or number of nodes in the graph\n", - "N=4 \n", - "classical_graph, classical_graph_adjacency= generate_graph(N, GRAPHMETHOD=1)\n", - "print(classical_graph_adjacency)\n", - "\n", - "pos = nx.circular_layout(classical_graph)\n", - "nx.draw(classical_graph, pos, width=4, with_labels=True, font_weight='bold')\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2. 编码量子优化问题\n", - "\n", - "接下来需要把上述经典优化问题映射为量子优化问题。利用替代关系 $z=1\\rightarrow |0\\rangle = \\begin{bmatrix}1 \\\\ 0\\end{bmatrix}$ 和 $z=-1\\rightarrow |1\\rangle = \\begin{bmatrix}0 \\\\ 1\\end{bmatrix}$, 我们把二元参数 $z_i\\in\\{-1, 1\\}$ 对应为描述量子比特的 Pauli-Z 算符 $Z_i=\\begin{bmatrix} 1 & 0\\\\ 0 & -1\\end{bmatrix}$ 的两个本征值。于是经典优化问题里的目标函数相应地编码为一个描述系统哈密顿量的矩阵\n", - "\n", - "$$H_{c}= Z_1Z_2+Z_2Z_3+Z_3Z_4+Z_4Z_1.$$\n", - "\n", - "其中 $Z_iZ_{j}$ 是 tensor product 运算,表示 Pauli-Z 算符分别作用在量子比特 $i$ 和 $j$ 上,而其余的量子比特上作用单位算符 $I=\\begin{bmatrix} 1 & 0\\\\ 0 & 1\\end{bmatrix}$ ,例如 $Z_1Z_2 =Z_1\\otimes Z_2\\otimes I_3\\otimes I_4$。经过上述操作,我们把经典优化问题转化为求解矩阵 $H_{c}$ 的最小特征值 $F$ 及其对应的向量 $|\\psi\\rangle$, 即\n", - "\n", - "$$ F=\\min_{|\\psi\\rangle} \\langle \\psi| H_c |\\psi\\rangle.$$\n", - "\n", - "这里,$|\\psi\\rangle$ 记为一个模长为1的 $2^4=16$ 维复向量,$\\langle \\psi|$ 是其共轭转置。\n", - "\n", - "Paddle Quantum 中通过函数 H_generator 完成编码任务:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "def H_generator(N, adjacency_matrix):\n", - " \"\"\"\n", - " This function maps the given graph via its adjacency matrix to the corresponding Hamiltiona H_c.\n", - " \n", - " Args:\n", - " N: number of qubits, or number of nodes in the graph, or number of parameters in the classical problem\n", - " adjacency_matrix: the adjacency matrix generated from the graph encoding the classical problem\n", - " Return:\n", - " H_graph: the problem-based Hamiltonian H generated from the graph_adjacency matrix for the given graph\n", - " H_graph_diag: the real part of the problem-based Hamiltonian H_graph\n", - " \"\"\"\n", - "\n", - " sigma_Z = np.array([[1, 0], [0, -1]])\n", - " H = np.zeros([2 ** N, 2 ** N])\n", - " # Generate the Hamiltonian H_c from the graph via its adjacency matrix\n", - " for row in range(N):\n", - " for col in range(N):\n", - " if abs(adjacency_matrix[N - row - 1, N - col - 1]) and row < col:\n", - " identity_1 = np.diag(np.ones([2 ** row]))\n", - " identity_2 = np.diag(np.ones([2 ** (col - row - 1)]))\n", - " identity_3 = np.diag(np.ones([2 ** (N - col - 1)]))\n", - " H += adjacency_matrix[N - row - 1, N - col - 1] * np.kron(\n", - " np.kron(np.kron(np.kron(identity_1, sigma_Z), identity_2), sigma_Z),\n", - " identity_3,\n", - " )\n", - "\n", - " H_graph = H.astype(\"complex64\")\n", - " H_graph_diag = np.diag(H_graph).real\n", - "\n", - " return H_graph, H_graph_diag" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们可以查看生成矩阵 $H_c $ 的具体形式,并且获取它的特征值信息:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "outputs": [], - "source": [ - "_, H_problem_diag = H_generator(N, classical_graph_adjacency)\n", - "\n", - "H_graph_max = np.max(H_problem_diag)\n", - "H_graph_min = np.min(H_problem_diag)\n", - "\n", - "print(H_problem_diag)\n", - "print('H_max:', H_graph_max, ' H_min:', H_graph_min)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 3. 搭建 QAOA 电路\n", - "\n", - "通过交替地摆放两个参数可调的电路模块,我们得以搭建QAOA电路\n", - "\n", - "$$U_x(\\beta_P)U_c(\\gamma_P)\\dots U_x(\\beta_1)U_c(\\gamma_1),$$\n", - "\n", - "其中放置的次数记为 $P$。具体地,模块一是由描述问题哈密顿量的矩阵生成,即\n", - "\n", - "$$U_c(\\gamma)=e^{-i \\gamma H_c },$$\n", - "\n", - "其中 $i= \\sqrt{-1}$ 是虚数单位, $\\gamma\\in [0, \\pi]$ 是可以调节的参数。模块二是\n", - "\n", - "$$U_x(\\beta)=e^{-i \\beta H_x },$$\n", - "\n", - "由描述驱动哈密顿量的另一个矩阵生成 \n", - "\n", - "$$H_x =X_1+X_2+X_3+X_4. $$\n", - "\n", - "$\\beta\\in [0, \\pi]$ 也是一个可调参数,算符 $X=\\begin{bmatrix} 0 & 1\\\\ 1 & 0\\end{bmatrix}$ 是作用在量子比特上的 Pauli-X 逻辑门,例如 $X_1$ 实际数学表达式为 $X_1\\otimes I_2\\otimes I_3\\otimes I_4$。\n", - "\n", - "QAOA 电路的每一模块可以进一步分解为若干个作用在单比特和两比特上的含参的量子逻辑门,如图所示:\n", - "\n", - "\n", - "![QAOA.png](https://release-data.cdn.bcebos.com/PIC%2FQAOACir.png) \n", - "\n", - "\n", - "其中,模块 $U_x(\\beta)$ 可以分解为在每个量子比特上作用绕 $X$ 方向转动的量子逻辑门 $R_x(\\beta)= e^{-i\\beta X_j}$,而模块 $U_c(\\gamma)$ 则可分解为作用在两比特上的 $ZZ$ 逻辑门 $R_{zz}(\\gamma)= e^{-i\\gamma Z\\otimes Z}$。\n", - "\n", - "此外,我们可以设置交叉放置两个模块的次数,记为 QAOA 电路的层数 $P$。于是输入\n", - "\n", - "- 量子电路的初始状态\n", - "- 经典问题的邻接矩阵\n", - "- 电路比特数目\n", - "- 电路层数\n", - "\n", - "构建标准的 QAOA 量子电路:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "outputs": [], - "source": [ - "def circuit_QAOA(theta, input_state, adjacency_matrix, N, P):\n", - " \"\"\"\n", - " This function constructs the parameterized QAOA circuit which is composed of P layers of two blocks:\n", - " one block is U_theta[layer][0] based on the problem Hamiltonian H which encodes the classical problem,\n", - " and the other is U_theta[layer][1] constructed from the driving Hamiltonian describing the rotation around Pauli X\n", - " acting on each qubit. It finally outputs the final state of the QAOA circuit.\n", - " \n", - " Args:\n", - " theta: parameters to be optimized in the QAOA circuit\n", - " input_state: initial state of the QAOA circuit which usually is the uniform superposition of 2^N bit-strings \n", - " in the computataional basis\n", - " adjacency_matrix: the adjacency matrix of the graph encoding the classical problem\n", - " N: number of qubits, or equivalently, the number of parameters in the original classical problem\n", - " P: number of layers of two blocks in the QAOA circuit\n", - " Returns:\n", - " the final state of the QAOA circuit: cir.state\n", - "\n", - " \"\"\"\n", - "\n", - " cir = UAnsatz(N, input_state=input_state)\n", - " \n", - " # This loop defines the QAOA circuit with P layers of two blocks\n", - " for layer in range(P):\n", - " # The second and third loops construct the first block U_theta[layer][0] which involves two-qubit operation\n", - " # e^{-i\\beta Z_iZ_j} acting on a pair of qubits or nodes i and j in the circuit in each layer.\n", - " for row in range(N):\n", - " for col in range(N):\n", - " if abs(adjacency_matrix[row, col]) and row < col:\n", - " cir.cnot([row + 1, col + 1])\n", - " cir.rz(\n", - " theta=theta[layer][0]\n", - " * adjacency_matrix[row, col],\n", - " which_qubit=col + 1,\n", - " )\n", - " cir.cnot([row + 1, col + 1])\n", - " # This loop constructs the second block U_theta only involving the single-qubit operation e^{-i\\beta X}.\n", - " for i in range(1, N + 1):\n", - " cir.rx(theta=theta[layer][1], which_qubit=i)\n", - "\n", - " return cir.state" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在标准 QAOA 的基础上,我们还支持对电路结构进行扩展,进一步探索 QAOA 更多可能性。例如,可以将模块二的驱动哈密顿量 $H_x$ 中的的绕单比特 X 方向转动 $R_x(\\beta)$ 扩展为绕任意方向转动,且任意方向等价于依次绕 Z, X, Z 方向转动适当的角度,即$R_z(\\beta_1)R_x(\\beta_2)R_z(\\beta_3)$:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "outputs": [], - "source": [ - "def circuit_extend_QAOA(theta, input_state, adjacency_matrix, N, P):\n", - " \"\"\"\n", - " This is an extended version of the QAOA circuit, and the main difference is U_theta[layer]([1]-[3]) constructed\n", - " from the driving Hamiltonian describing the rotation around an arbitrary direction on each qubit.\n", - "\n", - " Args:\n", - " theta: parameters to be optimized in the QAOA circuit\n", - " input_state: input state of the QAOA circuit which usually is the uniform superposition of 2^N bit-strings\n", - " in the computational basis\n", - " adjacency_matrix: the adjacency matrix of the problem graph encoding the original problem\n", - " N: number of qubits, or equivalently, the number of parameters in the original classical problem\n", - " P: number of layers of two blocks in the QAOA circuit\n", - " Returns:\n", - " final state of the QAOA circuit: cir.state\n", - "\n", - " Note: If this U_extend_theta function is used to construct QAOA circuit, then we need to change the parameter layer\n", - " in the Net function defined below from the Net(shape=[D, 2]) for U_theta function to Net(shape=[D, 4])\n", - " because the number of parameters doubles in each layer in this QAOA circuit.\n", - " \"\"\"\n", - " cir = UAnsatz(N, input_state=input_state)\n", - "\n", - " for layer in range(P):\n", - " for row in range(N):\n", - " for col in range(N):\n", - " if abs(adjacency_matrix[row, col]) and row < col:\n", - " cir.cnot([row + 1, col + 1])\n", - " cir.rz(\n", - " theta=theta[layer][0]\n", - " * adjacency_matrix[row, col],\n", - " which_qubit=col + 1,\n", - " )\n", - " cir.cnot([row + 1, col + 1])\n", - "\n", - " for i in range(1, N + 1):\n", - " cir.rz(theta=theta[layer][1], which_qubit=i)\n", - " cir.rx(theta=theta[layer][2], which_qubit=i)\n", - " cir.rz(theta=theta[layer][3], which_qubit=i)\n", - "\n", - " return cir.state" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "搭建 QAOA 量子电路的工作完成后,此时量子电路的输出状态为\n", - "\n", - "$$|\\psi(\\boldsymbol{\\beta},\\boldsymbol{\\gamma}, P)\\rangle=U_x(\\beta_P)U_c(\\gamma_P)\\dots U_x(\\beta_1)U_c(\\gamma_1)|+\\rangle_1\\dots|+\\rangle_N.$$\n", - "\n", - "其中每个量子比特的初始状态处于量子叠加态 $|+\\rangle=\\frac{1}{\\sqrt{2}}\\left(|0\\rangle+|1\\rangle\\right)$ 。最终,我们得到量子优化问题的损失函数\n", - "\n", - "$$F_P=\\min_{\\boldsymbol{\\beta},\\boldsymbol{\\gamma}} \\langle \\psi(\\boldsymbol{\\beta},\\boldsymbol{\\gamma}, P)| H_c|\\psi(\\boldsymbol{\\beta},\\boldsymbol{\\gamma}, P)\\rangle.$$\n", - "\n", - "因为 QAOA 是一个量子-经典混杂算法,所以搭建完成 QAOA 电路且得到相应的损失函数后,我们可以进一步利用经典的优化算法寻找最优参数 $\\boldsymbol{\\beta},\\boldsymbol{\\gamma}$,从而形成一个完整的闭环网络。\n", - "\n", - "下面的函数给出了通过 Paddle Quantum 搭建的完整 QAOA 网络:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "outputs": [], - "source": [ - "class Net(fluid.dygraph.Layer):\n", - " \"\"\"\n", - " It constructs the net for QAOA which combines the QAOA circuit with the classical optimizer which sets rules\n", - " to update parameters described by theta introduced in the QAOA circuit.\n", - "\n", - " \"\"\"\n", - " def __init__(\n", - " self,\n", - " shape,\n", - " param_attr=fluid.initializer.Uniform(low=0.0, high=np.pi, seed=1024),\n", - " dtype=\"float32\",\n", - " ):\n", - " super(Net, self).__init__()\n", - "\n", - " self.theta = self.create_parameter(\n", - " shape=shape, attr=param_attr, dtype=dtype, is_bias=False\n", - " )\n", - "\n", - " def forward(self, input_state, adjacency_matrix, out_state_store, N, P, METHOD):\n", - " \"\"\"\n", - " This function constructs the loss function for the QAOA circuit.\n", - "\n", - " Args:\n", - " self: the free parameters to be optimized in the QAOA circuit and defined in the above function\n", - " input_state: initial state of the QAOA circuit which usually is the uniform superposition of 2^N bit-strings\n", - " in the computational basis $|0\\rangle, |1\\rangle$\n", - " adjacency_matrix: the adjacency matrix generated from the graph encoding the classical problem\n", - " out_state_store: the output state of the QAOA circuit\n", - " N: number of qubits\n", - " P: number of layers\n", - " METHOD: which version of QAOA is chosen to solve the problem, i.e., standard version labeled by 1 or\n", - " extended version by 2.\n", - " Returns:\n", - " The loss function for the parameterized QAOA circuit.\n", - " \"\"\"\n", - " \n", - " # Generate the problem_based quantum Hamiltonian H_problem based on the classical problem in paddle\n", - " H, _ = H_generator(N, adjacency_matrix)\n", - " H_problem = fluid.dygraph.to_variable(H)\n", - "\n", - " # The standard QAOA circuit: the function circuit_QAOA is used to construct the circuit, indexed by METHOD 1.\n", - " if METHOD == 1:\n", - " out_state = circuit_QAOA(self.theta, input_state, adjacency_matrix, N, P)\n", - " # The extended QAOA circuit: the function circuit_extend_QAOA is used to construct the net, indexed by METHOD 2.\n", - " elif METHOD == 2:\n", - " out_state = circuit_extend_QAOA(self.theta, input_state, adjacency_matrix, N, P)\n", - " else:\n", - " raise ValueError(\"Wrong method called!\")\n", - "\n", - " out_state_store.append(out_state.numpy())\n", - " loss = pp_matmul(\n", - " pp_matmul(out_state, H_problem),\n", - " transpose(\n", - " fluid.framework.ComplexVariable(out_state.real, -out_state.imag),\n", - " perm=[1, 0],\n", - " ),\n", - " )\n", - "\n", - " return loss.real\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 4. 训练网络\n", - "\n", - "我们开始训练整个 QAOA 网络,即通过优化参数向量 $\\boldsymbol{\\beta}=(\\beta_1,\\beta_2,\\beta_3,\\beta_4)$ 和 $\\boldsymbol{\\gamma}=(\\gamma_1,\\gamma_2,\\gamma_3, \\gamma_4)$ 来达到求解 $H_c$ 最小特征值的目的。\n", - "\n", - "与经典机器学习算法一样,首先设置 QAOA 网络里的超参数:\n", - "\n", - "- 电路比特数目 N\n", - "- 电路的层数 P\n", - "- 迭代次数 ITR\n", - "- 学习步长 LR" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "outputs": [], - "source": [ - "N = 4 # number of qubits, or number of nodes in the graph\n", - "P = 4 # number of layers \n", - "ITR = 120 # number of iteration steps\n", - "LR = 0.1 # learning rate" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "然后,灵活调用:\n", - "\n", - "- 量子电路初始状态:每个量子比特态处于相干叠加态 $\\frac{1}{\\sqrt{2}}\\left(|0\\rangle+|1\\rangle\\right)$\n", - "- 采用标准 QAOA (记为 METHOD=1)或者扩展 QAOA (记为 METHOD = 2)\n", - "- 经典优化器 Adam optimizer \n", - "\n", - "最后,训练模型并保存结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "outputs": [], - "source": [ - "def Paddle_QAOA(classical_graph_adjacency, N, P, METHOD, ITR, LR):\n", - " \"\"\"\n", - " This is the core function to run QAOA.\n", - "\n", - " Args:\n", - " classical_graph_adjacency: adjacency matrix to describe the graph which encodes the classical problem\n", - " N: number of qubits (default value N=4)\n", - " P: number of layers of blocks in the QAOA circuit (default value P=4)\n", - " METHOD: which version of the QAOA circuit is used: 1, standard circuit (default); 2, extended circuit\n", - " ITR: number of iteration steps for QAOA (default value ITR=120)\n", - " LR: learning rate for the gradient-based optimization method (default value LR=0.1)\n", - " Returns:\n", - " optimized parameters theta and the bitstrings sampled from the output state with maximal probability\n", - " \"\"\"\n", - " out_state_store = []\n", - " with fluid.dygraph.guard():\n", - " # Preparing the initial state\n", - " _initial_state = np.ones([1, 2 ** N]).astype(\"complex64\") / np.sqrt(2 ** N)\n", - " initial_state = fluid.dygraph.to_variable(_initial_state)\n", - "\n", - " # Construct the net or QAOA circuits based on the standard modules\n", - " if METHOD == 1:\n", - " net = Net(shape=[P, 2])\n", - " # Construct the net or QAOA circuits based on the extended modules\n", - " elif METHOD == 2:\n", - " net = Net(shape=[P, 4])\n", - " else:\n", - " raise ValueError(\"Wrong method called!\")\n", - "\n", - " # Classical optimizer\n", - " opt = fluid.optimizer.AdamOptimizer(learning_rate=LR, parameter_list=net.parameters())\n", - "\n", - " # Gradient descent loop\n", - " summary_iter, summary_loss = [], []\n", - " for itr in range(1, ITR + 1):\n", - " loss = net(\n", - " initial_state, classical_graph_adjacency, out_state_store, N, P, METHOD\n", - " )\n", - " loss.backward()\n", - " opt.minimize(loss)\n", - " net.clear_gradients()\n", - "\n", - " print(\"iter:\", itr, \" loss:\", \"%.4f\" % loss.numpy())\n", - " summary_loss.append(loss[0][0].numpy())\n", - " summary_iter.append(itr)\n", - "\n", - " theta_opt = net.parameters()[0].numpy()\n", - " print(theta_opt)\n", - "\n", - " os.makedirs(\"output\", exist_ok=True)\n", - " np.savez(\"./output/summary_data\", iter=summary_iter, energy=summary_loss)\n", - "\n", - " # Output the measurement probability distribution which is sampled from the output state of optimized QAOA circuit.\n", - " prob_measure = np.zeros([1, 2 ** N]).astype(\"complex\")\n", - "\n", - " rho_out = out_state_store[-1]\n", - " rho_out = np_matmul(np.conjugate(rho_out).T, rho_out).astype(\"complex\")\n", - "\n", - " for index in range(0, 2 ** N):\n", - " comput_basis = np.zeros([1, 2 ** N])\n", - " comput_basis[0][index] = 1\n", - " prob_measure[0][index] = np.real(np_matmul(np_matmul(comput_basis, rho_out), comput_basis.T))\n", - "\n", - " return prob_measure" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "调用模型训练结果,输出得到的最优参数向量 $\\boldsymbol{\\beta}^*$ 和 $\\boldsymbol{\\gamma}^*$,并且将 QAOA 的输出结果和真实结果进行比较:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "outputs": [], - "source": [ - "classical_graph, classical_graph_adjacency = generate_graph(N, 1)\n", - "\n", - "prob_measure_dis = Paddle_QAOA(classical_graph_adjacency, N =4, P=4, METHOD=1, ITR=120, LR=0.1)\n", - "\n", - "# Load the data of QAOA\n", - "x1 = np.load('./output/summary_data.npz')\n", - "\n", - "H_min = np.ones([len(x1['iter'])]) * H_graph_min\n", - "\n", - "# Plot it\n", - "\n", - "loss_QAOA, = plt.plot(x1['iter'], x1['energy'], \\\n", - " alpha=0.7, marker='', linestyle=\"--\", linewidth=2, color='m')\n", - "benchmark, = plt.plot(x1['iter'], H_min, alpha=0.7, marker='', linestyle=\":\", linewidth=2, color='b')\n", - "plt.xlabel('Number of iteration')\n", - "plt.ylabel('Performance of the loss function for QAOA')\n", - "\n", - "plt.legend(handles=[\n", - " loss_QAOA,\n", - " benchmark\n", - "],\n", - " labels=[\n", - " r'Loss function $\\left\\langle {\\psi \\left( {\\bf{\\theta }} \\right)} '\n", - " r'\\right|H\\left| {\\psi \\left( {\\bf{\\theta }} \\right)} \\right\\rangle $',\n", - " 'The benchmark result',\n", - " ], loc='best')\n", - "\n", - "# Show the picture\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 5. 解码量子答案\n", - "\n", - "当求得损失函数 $\\langle \\psi(\\boldsymbol{\\beta},\\boldsymbol{\\gamma}, P)| H_{\\rm Cut}|\\psi(\\boldsymbol{\\beta},\\boldsymbol{\\gamma}, P)\\rangle$ 的最小值以及相对应的一组参数 $(\\boldsymbol{\\beta}^*,\\boldsymbol{\\gamma}^*)$ 后,我们的任务还没有完成。为了进一步求得 Max-Cut 问题的解,需要从 QAOA 输出的量子态 \n", - "\n", - "$$|\\psi(\\boldsymbol{\\beta}^*,\\boldsymbol{\\gamma}^*, P)\\rangle=\\sum_{i=1}^{2^4}\\lambda_i |\\boldsymbol{x}_i\\rangle$$\n", - "\n", - "中解码出经典优化问题的答案。上式中 $\\boldsymbol{x}_i=x_1x_2x_3 x_4\\in \\{0, 1\\}^4$,对应着经典问题的一个可行解。物理上,解码量子态需要对量子态进行测量,然后统计测量结果的概率分布:\n", - " \n", - "$$ p(\\boldsymbol{x})=|\\langle \\boldsymbol{x}|\\psi(\\boldsymbol{\\beta}^*,\\boldsymbol{\\gamma}^*,P)\\rangle|^2.$$\n", - " \n", - "\n", - "某种程度上,某个比特串出现的概率越大,意味着其对应的 Max-Cut 问题最优解的可能性越大。\n", - "\n", - "此外,Paddle Quantum 提供了查看 QAOA 量子电路输出状态的测量结果概率分布的函数:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%%\n" - }, - "scrolled": true - }, - "outputs": [], - "source": [ - "prob_measure = prob_measure_dis.flatten()\n", - "\n", - "pos = nx.circular_layout(classical_graph)\n", - "# when N is large, it is not suggested to plot this figure\n", - "name_list = [np.binary_repr(index, width=N) for index in range(0, 2 ** N)]\n", - "plt.bar(\n", - " range(len(np.real(prob_measure))),\n", - " np.real(prob_measure),\n", - " width=0.7,\n", - " tick_label=name_list,\n", - " )\n", - "plt.xticks(rotation=90)\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "最后,再次利用参数代换 $|x \\rangle\\rightarrow z=2x-1\\in\\{-1, 1\\}$,可以从量子答案中解码得到 Max-Cut 问题的可行解。 此时,记 $z_i=-1$ 的顶点属于集合 $S^\\prime$ 以及 $z_j=1$ 的顶点属于集合 $S$,这两个顶点集合之间存在的边就是该图的一个可能得最大割方案。 \n", - "\n", - "选取测量结果中出现几率最大的比特串,然后将其映射回经典解,并且画出对应的最大割方案:\n", - "\n", - "- 蓝色顶点属于集合 $S$\n", - "- 红色顶点属于集合 $S^\\prime$\n", - "- 折线属于图的一条割线" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "outputs": [], - "source": [ - "\n", - "# Find the position of max value in the measure_prob_distribution\n", - "max_prob_pos_list = np.where(prob_measure == max(prob_measure))\n", - "# Store the max value from ndarray to list\n", - "max_prob_list = max_prob_pos_list[0].tolist()\n", - "# Change it to the binary format\n", - "solution_list = [np.binary_repr(index, width=N) for index in max_prob_list]\n", - "print(\"The output bitstring:\", solution_list)\n", - "\n", - "# Draw the graph representing the first bitstring in the solution_list to the MaxCut-like problem\n", - "head_bitstring = solution_list[0]\n", - "\n", - "node_cut = [\"blue\" if head_bitstring[node] == \"1\" else \"red\" for node in classical_graph]\n", - "\n", - "edge_cut = [\n", - " \"solid\" if head_bitstring[node_row] == head_bitstring[node_col] else \"dashed\"\n", - " for node_row, node_col in classical_graph.edges()\n", - " ]\n", - "nx.draw(\n", - " classical_graph,\n", - " pos,\n", - " node_color=node_cut,\n", - " style=edge_cut,\n", - " width=4,\n", - " with_labels=True,\n", - " font_weight=\"bold\",\n", - ")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "source": [ - "# 参考文献\n", - "\n", - "[1] E. Farhi, J. Goldstone, and S. Gutman. 2014. A quantum approximate optimization algorithm. arXiv:1411.4028 " - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.10" - }, - "pycharm": { - "stem_cell": { - "cell_type": "raw", - "metadata": { - "collapsed": false - }, - "source": [] - } - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/tutorial/QAOA/QAOA.ipynb b/tutorial/QAOA/QAOA.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..9c7a162e9ea2db9b5a244c2f1cafcf78d7eb2689 --- /dev/null +++ b/tutorial/QAOA/QAOA.ipynb @@ -0,0 +1,856 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# 量子近似优化算法 (QAOA)\n", + "\n", + "## 简体中文 | [English](./QAOA_En.ipynb)\n", + "\n", + " Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 准备\n", + "\n", + "本文档演示 Paddle Quantum 上量子近似优化算法(QAOA,Quantum Approximate Optimization Algorithm)的工作流程 [1]。\n", + "\n", + "开始之前完成准备工作:\n", + "\n", + " - 调用飞桨 paddlepaddle\n", + "\n", + " - 调用常用的库, 例如画图工具库 networkx 和 matplotlib.pyplot\n", + "\n", + " - 调用自定义函数 " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "outputs": [], + "source": [ + "from paddle import fluid\n", + "\n", + "import os\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import networkx as nx\n", + "\n", + "from paddle_quantum.circuit import UAnsatz\n", + "from paddle_quantum.utils import pauli_str_to_matrix" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 背景\n", + "\n", + "\n", + "量子近似优化算法(QAOA,Quantum Approximate Optimization Algorithm)是可以在近期有噪中等规模(NISQ,Noisy Intermediate-Scale Quantum)量子计算机上运行且具有广泛应用前景的量子算法。例如,QAOA 可以用来处理压缩图信号和二次优化等领域常见的离散组合优化问题。这类优化问题通常可以归结为下面的数学模型:\n", + "\n", + "\n", + " $$F=\\max_{z_i\\in\\{-1,1\\}} \\sum_{i,j} q_{ij}(1-z_iz_j)=-\\min_{z_i\\in\\{-1,1\\}} \\sum_{i,j} q_{ij}z_iz_j+ \\sum_{i,j} q_{ij}. $$\n", + "\n", + "\n", + "其中, $z_i \\in \\{-1 ,1\\} $ 是待求的二元参数,系数 $q_{ij}$ 是 $z_i z_j$ 的权重 (weight)。一般地,精确求解该问题对于经典计算机是 NP-hard 的,而 QAOA 被认为对近似求解这类困难问题具有潜在速度优势。\n", + "\n", + "QAOA 的工作原理是把上述经典优化问题(例如组合优化问题)甚至量子优化问题(例如量子多体系统中 Ising 模型的求解)等价地转化为求解一个物理系统哈密顿量(Hamiltonian)的基态能量(对应优化问题的最优值)及其相应的基态(对应于优化问题的最优解)。接下来,我们通过图的最大割问题 (Max-Cut problem)来展示 QAOA 算法的工作流程和原理。在解决最大割问题时,QAOA 在数学形式上等价于求解一个实对角矩阵 $H$ 的最小特征值及其对应的特征向量。\n", + "\n", + "和另外一种常见的变分量子特征求解器(VQE, Variational Quantum Eigensolver) 一样,QAOA 也是一种量子-经典混杂算法。 然而 QAOA 参数化量子电路的实现更简单,仅需两个可以调节参数的量子电路模块组成。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# 示例\n", + "\n", + "## 1. Max-Cut 问题\n", + "\n", + "图的 Max-Cut 问题可以描述为:对于一个给定的包含 $N$ 个顶点 (nodes or vertices)和 $M$ 条边 (edges) 的无向图,找到一种分割方案将图的顶点集合分割成两个无交子集合 $S$ 和 $S^\\prime$,使得连接这两个顶点集合之间边的数目尽可能多。如图所示,我们考虑含4个顶点且具有环形结构的图: \n", + "\n", + " ![ring4.png](https://release-data.cdn.bcebos.com/PIC%2FMaxCut.png) \n", + "\n", + "Max-Cut 问题建模:在做分割时,若顶点 $i$ 属于集合 $S$ 时,赋值 $z_i=1$;若顶点 $j$ 属于 $S^\\prime$ 时,则令 $z_j=-1$。那么对于图的任意连接顶点 $(i, j)$ 的边则满足:若顶点属于同一集合 $S$ 或 $S^\\prime$ 时,$z_iz_j=1$; 若顶点分别属于不同集合时,$z_izj=-1$。于是 Max-Cut 问题转化为如下优化问题:\n", + "\n", + "$$ F=\\min_{z_i\\in\\{-1, 1\\}} z_1 z_2+z_2z_3+z_3z_4+z_4z_1.$$\n", + "\n", + "这里所有 $q_{ij}$ 均设置为 1,表示每条边的权重相等。该问题的所有可行解由比特串 $ \\boldsymbol{z}=z_1z_2z_3z_4 \\in \\{-1, 1\\}^4$ 组成,而且通常需要遍历所有比特串才能得到问题的最小值(最优解)。容易看出,比特串的数目是顶点数目 $N$ 的指数级别,即 $2^N$。因此,随着顶点数目的增加,搜索的代价也会呈指数级别增加。\n", + "\n", + "接下来,我们提供两种方法来预处理编码经典优化问题的图,即如何通过 Paddle Quantum 输入和可视化无权(或带权重)图:\n", + "\n", + "- 方法1是通过指定图的顶点和相应的边(及其权重)\n", + "- 方法2是通过直接输入图的邻接矩阵。" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "outputs": [], + "source": [ + "def generate_graph(N, GRAPHMETHOD):\n", + " \"\"\"\n", + " It plots an N-node graph which is specified by Method 1 or 2.\n", + " \n", + " Args:\n", + " N: number of nodes (vertices) in the graph\n", + " METHOD: choose which method to generate a graph\n", + " Returns:\n", + " the specific graph and its adjacency matrix\n", + " \"\"\"\n", + " # Method 1 generates a graph by self-definition\n", + " if GRAPHMETHOD == 1:\n", + " print(\"Method 1 generates the graph from self-definition using EDGE description\")\n", + " graph = nx.Graph()\n", + " graph_nodelist=range(N)\n", + " graph.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 0)])\n", + " graph_adjacency = nx.to_numpy_matrix(graph, nodelist=graph_nodelist)\n", + " # Method 2 generates a graph by using its adjacency matrix directly\n", + " elif GRAPHMETHOD == 2:\n", + " print(\"Method 2 generates the graph from networks using adjacency matrix\")\n", + " graph_adjacency = np.array([[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 1, 0]])\n", + " graph = nx.Graph(graph_adjacency)\n", + " else:\n", + " print(\"Method doesn't exist \")\n", + "\n", + " return graph, graph_adjacency" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里指定方法1来预处理图:\n", + "\n", + "- 图的顶点数目 $N=4$\n", + "- 图的输入方法 GRAPHMETHOD = 1 \n", + "\n", + "注意:上述两种方法给出的图的顶点均从 $0$ 开始计数。" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Method 1 generates the graph from self-definition using EDGE description\n", + "[[0. 1. 0. 1.]\n", + " [1. 0. 1. 0.]\n", + " [0. 1. 0. 1.]\n", + " [1. 0. 1. 0.]]\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# number of qubits or number of nodes in the graph\n", + "N=4 \n", + "classical_graph, classical_graph_adjacency= generate_graph(N, GRAPHMETHOD=1)\n", + "print(classical_graph_adjacency)\n", + "\n", + "pos = nx.circular_layout(classical_graph)\n", + "nx.draw(classical_graph, pos, width=4, with_labels=True, font_weight='bold')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. 编码量子优化问题\n", + "\n", + "接下来需要把上述经典优化问题映射为量子优化问题。利用替代关系 $z=1\\rightarrow |0\\rangle = \\begin{bmatrix}1 \\\\ 0\\end{bmatrix}$ 和 $z=-1\\rightarrow |1\\rangle = \\begin{bmatrix}0 \\\\ 1\\end{bmatrix}$, 我们把二元参数 $z_i\\in\\{-1, 1\\}$ 对应为描述量子比特的 Pauli-Z 算符 $Z_i=\\begin{bmatrix} 1 & 0\\\\ 0 & -1\\end{bmatrix}$ 的两个本征值。于是经典优化问题里的目标函数相应地编码为一个描述系统哈密顿量的矩阵\n", + "\n", + "$$H_{c}= Z_1Z_2+Z_2Z_3+Z_3Z_4+Z_4Z_1.$$\n", + "\n", + "其中 $Z_iZ_{j}$ 是 tensor product 运算,表示 Pauli-Z 算符分别作用在量子比特 $i$ 和 $j$ 上,而其余的量子比特上作用单位算符 $I=\\begin{bmatrix} 1 & 0\\\\ 0 & 1\\end{bmatrix}$ ,例如 $Z_1Z_2 =Z_1\\otimes Z_2\\otimes I_3\\otimes I_4$。经过上述操作,我们把经典优化问题转化为求解矩阵 $H_{c}$ 的最小特征值 $F$ 及其对应的向量 $|\\psi\\rangle$, 即\n", + "\n", + "$$ F=\\min_{|\\psi\\rangle} \\langle \\psi| H_c |\\psi\\rangle.$$\n", + "\n", + "这里,$|\\psi\\rangle$ 记为一个模长为1的 $2^4=16$ 维复向量,$\\langle \\psi|$ 是其共轭转置。\n", + "\n", + "Paddle Quantum 中通过函数 H_generator 完成编码任务:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "def H_generator(N, adjacency_matrix):\n", + " \"\"\"\n", + " This function maps the given graph via its adjacency matrix to the corresponding Hamiltiona H_c.\n", + " \n", + " Args:\n", + " N: number of qubits, or number of nodes in the graph, or number of parameters in the classical problem\n", + " adjacency_matrix: the adjacency matrix generated from the graph encoding the classical problem\n", + " Returns:\n", + " the problem-based Hmiltonian H's list form generated from the graph_adjacency matrix for the given graph\n", + " \"\"\"\n", + " H_list = []\n", + " # Generate the Hamiltonian H_c from the graph via its adjacency matrix\n", + " for row in range(N):\n", + " for col in range(N):\n", + " if adjacency_matrix[row, col] and row < col:\n", + " # Construct the Hamiltonian in the list form for the calculation of expectation value\n", + " H_list.append([1.0, 'z'+str(row) + ',z' + str(col)])\n", + "\n", + " return H_list" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们可以查看生成矩阵 $H_c $ 的具体形式,并且获取它的特征值信息:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 4. 0. 0. 0. 0. -4. 0. 0. 0. 0. -4. 0. 0. 0. 0. 4.]\n", + "H_max: 4.0 H_min: -4.0\n" + ] + } + ], + "source": [ + "# Convert the Hamiltonian's list form to matrix form\n", + "H_matrix = pauli_str_to_matrix(H_generator(N, classical_graph_adjacency), N)\n", + "\n", + "H_diag = np.diag(H_matrix).real\n", + "H_max = np.max(H_diag)\n", + "H_min = np.min(H_diag)\n", + "\n", + "print(H_diag)\n", + "print('H_max:', H_max, ' H_min:', H_min)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. 搭建 QAOA 电路\n", + "\n", + "通过交替地摆放两个参数可调的电路模块,我们得以搭建QAOA电路\n", + "\n", + "$$U_x(\\beta_P)U_c(\\gamma_P)\\dots U_x(\\beta_1)U_c(\\gamma_1),$$\n", + "\n", + "其中放置的次数记为 $P$。具体地,模块一是由描述问题哈密顿量的矩阵生成,即\n", + "\n", + "$$U_c(\\gamma)=e^{-i \\gamma H_c },$$\n", + "\n", + "其中 $i= \\sqrt{-1}$ 是虚数单位, $\\gamma\\in [0, \\pi]$ 是可以调节的参数。模块二是\n", + "\n", + "$$U_x(\\beta)=e^{-i \\beta H_x },$$\n", + "\n", + "由描述驱动哈密顿量的另一个矩阵生成 \n", + "\n", + "$$H_x =X_1+X_2+X_3+X_4. $$\n", + "\n", + "$\\beta\\in [0, \\pi]$ 也是一个可调参数,算符 $X=\\begin{bmatrix} 0 & 1\\\\ 1 & 0\\end{bmatrix}$ 是作用在量子比特上的 Pauli-X 门,例如 $X_1$ 实际数学表达式为 $X_1\\otimes I_2\\otimes I_3\\otimes I_4$。\n", + "\n", + "QAOA 电路的每一模块可以进一步分解为若干个作用在单比特和两比特上的含参的量子门,如图所示:\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "其中,模块 $U_x(\\beta)$ 可以分解为在每个量子比特上作用绕 $X$ 方向转动的量子门 $R_x(\\beta)= e^{-i\\beta X}$,而模块 $U_c(\\gamma)$ 则可分解为作用在两比特上的 $ZZ$ 门 $R_{zz}(\\gamma)= e^{-i\\gamma Z\\otimes Z}$。\n", + "\n", + "此外,我们可以设置交叉放置两个模块的次数,记为 QAOA 电路的层数 $P$。于是输入\n", + "\n", + "- 量子电路的初始状态\n", + "- 经典问题的邻接矩阵\n", + "- 电路比特数目\n", + "- 电路层数\n", + "\n", + "构建标准的 QAOA 量子电路:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "outputs": [], + "source": [ + "def circuit_QAOA(theta, adjacency_matrix, N, P):\n", + " \"\"\"\n", + " This function constructs the parameterized QAOA circuit which is composed of P layers of two blocks:\n", + " one block is based on the problem Hamiltonian H which encodes the classical problem,\n", + " and the other is constructed from the driving Hamiltonian describing the rotation around Pauli X\n", + " acting on each qubit. It outputs the final state of the QAOA circuit.\n", + " \n", + " Args:\n", + " theta: parameters to be optimized in the QAOA circuit\n", + " adjacency_matrix: the adjacency matrix of the graph encoding the classical problem\n", + " N: number of qubits, or equivalently, the number of parameters in the original classical problem\n", + " P: number of layers of two blocks in the QAOA circuit\n", + " Returns:\n", + " the QAOA circuit\n", + " \"\"\"\n", + "\n", + " cir = UAnsatz(N)\n", + " \n", + " #prepare the input state in the uniform superposition of 2^N bit-strings in the computational basis\n", + " cir.superposition_layer()\n", + " # This loop defines the QAOA circuit with P layers of two blocks\n", + " for layer in range(P):\n", + " # The second and third loops construct the first block which involves two-qubit operation\n", + " # e^{-i\\gamma Z_iZ_j} acting on a pair of qubits or nodes i and j in the circuit in each layer.\n", + " for row in range(N):\n", + " for col in range(N):\n", + " if adjacency_matrix[row, col] and row < col:\n", + " cir.cnot([row, col])\n", + " cir.rz(theta[layer][0], col)\n", + " cir.cnot([row, col])\n", + " # This loop constructs the second block only involving the single-qubit operation e^{-i\\beta X}.\n", + " for i in range(N):\n", + " cir.rx(theta[layer][1], i)\n", + "\n", + " return cir" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在标准 QAOA 的基础上,我们还支持对电路结构进行扩展,进一步探索 QAOA 更多可能性。例如,可以将模块二的驱动哈密顿量 $H_x$ 中的的绕单比特 X 方向转动 $R_x(\\beta)$ 扩展为绕任意方向转动,即量桨中的$U3(\\beta_1, \\beta_2, \\beta_3)$门:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "outputs": [], + "source": [ + "def circuit_extend_QAOA(theta, adjacency_matrix, N, P):\n", + " \"\"\"\n", + " This is an extended version of the QAOA circuit, and the main difference is the block constructed\n", + " from the driving Hamiltonian describing the rotation around an arbitrary direction on each qubit.\n", + "\n", + " Args:\n", + " theta: parameters to be optimized in the QAOA circuit\n", + " input_state: input state of the QAOA circuit which usually is the uniform superposition of 2^N bit-strings\n", + " in the computational basis\n", + " adjacency_matrix: the adjacency matrix of the problem graph encoding the original problem\n", + " N: number of qubits, or equivalently, the number of parameters in the original classical problem\n", + " P: number of layers of two blocks in the QAOA circuit\n", + " Returns:\n", + " the extended QAOA circuit\n", + "\n", + " Note:\n", + " If this circuit_extend_QAOA function is used to construct QAOA circuit, then we need to change the parameter layer\n", + " in the Net function defined below from the Net(shape=[D, 2]) for circuit_QAOA function to Net(shape=[D, 4])\n", + " because the number of parameters doubles in each layer in this QAOA circuit.\n", + " \"\"\"\n", + " cir = UAnsatz(N)\n", + "\n", + " #prepare the input state in the uniform superposition of 2^N bit-strings in the computational basis\n", + " cir.superposition_layer()\n", + " for layer in range(P):\n", + " for row in range(N):\n", + " for col in range(N):\n", + " if adjacency_matrix[row, col] and row < col:\n", + " cir.cnot([row, col])\n", + " cir.rz(theta[layer][0], col)\n", + " cir.cnot([row, col])\n", + "\n", + " for i in range(N):\n", + " cir.u3(*theta[layer][1:], i)\n", + "\n", + " return cir" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "搭建 QAOA 量子电路的工作完成后,此时量子电路的输出状态为\n", + "\n", + "$$|\\psi(\\boldsymbol{\\beta},\\boldsymbol{\\gamma}, P)\\rangle=U_x(\\beta_P)U_c(\\gamma_P)\\dots U_x(\\beta_1)U_c(\\gamma_1)|+\\rangle_1\\dots|+\\rangle_N.$$\n", + "\n", + "其中每个量子比特的初始状态处于量子叠加态 $|+\\rangle=\\frac{1}{\\sqrt{2}}\\left(|0\\rangle+|1\\rangle\\right)$ 。最终,我们得到量子优化问题的损失函数\n", + "\n", + "$$F_P=\\min_{\\boldsymbol{\\beta},\\boldsymbol{\\gamma}} \\langle \\psi(\\boldsymbol{\\beta},\\boldsymbol{\\gamma}, P)| H_c|\\psi(\\boldsymbol{\\beta},\\boldsymbol{\\gamma}, P)\\rangle.$$\n", + "\n", + "因为 QAOA 是一个量子-经典混杂算法,所以搭建完成 QAOA 电路且得到相应的损失函数后,我们可以进一步利用经典的优化算法寻找最优参数 $\\boldsymbol{\\beta},\\boldsymbol{\\gamma}$,从而形成一个完整的闭环网络。\n", + "\n", + "下面的函数给出了通过 Paddle Quantum 搭建的完整 QAOA 网络:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "outputs": [], + "source": [ + "class Net(fluid.dygraph.Layer):\n", + " \"\"\"\n", + " It constructs the net for QAOA which combines the QAOA circuit with the classical optimizer which sets rules\n", + " to update parameters described by theta introduced in the QAOA circuit.\n", + "\n", + " \"\"\"\n", + " def __init__(\n", + " self,\n", + " shape,\n", + " param_attr=fluid.initializer.Uniform(low=0.0, high=np.pi, seed=1024),\n", + " dtype=\"float64\",\n", + " ):\n", + " super(Net, self).__init__()\n", + "\n", + " self.theta = self.create_parameter(\n", + " shape=shape, attr=param_attr, dtype=dtype, is_bias=False\n", + " )\n", + "\n", + " def forward(self, adjacency_matrix, N, P, METHOD):\n", + " \"\"\"\n", + " This function constructs the loss function for the QAOA circuit.\n", + "\n", + " Args:\n", + " adjacency_matrix: the adjacency matrix generated from the graph encoding the classical problem\n", + " N: number of qubits\n", + " P: number of layers\n", + " METHOD: which version of QAOA is chosen to solve the problem, i.e., standard version labeled by 1 or\n", + " extended version by 2.\n", + " Returns:\n", + " the loss function for the parameterized QAOA circuit and the circuit itself\n", + " \"\"\"\n", + " \n", + " # Generate the problem_based quantum Hamiltonian H_problem based on the classical problem in paddle\n", + " H_problem = H_generator(N, adjacency_matrix)\n", + "\n", + " # The standard QAOA circuit: the function circuit_QAOA is used to construct the circuit, indexed by METHOD 1.\n", + " if METHOD == 1:\n", + " cir = circuit_QAOA(self.theta, adjacency_matrix, N, P)\n", + " # The extended QAOA circuit: the function circuit_extend_QAOA is used to construct the net, indexed by METHOD 2.\n", + " elif METHOD == 2:\n", + " cir = circuit_extend_QAOA(self.theta, adjacency_matrix, N, P)\n", + " else:\n", + " raise ValueError(\"Wrong method called!\")\n", + "\n", + " cir.run_state_vector()\n", + " loss = cir.expecval(H_problem)\n", + "\n", + " return loss, cir" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. 训练网络\n", + "\n", + "我们开始训练整个 QAOA 网络,即通过优化参数向量 $\\boldsymbol{\\beta}=(\\beta_1,\\beta_2,\\beta_3,\\beta_4)$ 和 $\\boldsymbol{\\gamma}=(\\gamma_1,\\gamma_2,\\gamma_3, \\gamma_4)$ 来达到求解 $H_c$ 最小特征值的目的。\n", + "\n", + "与经典机器学习算法一样,首先设置 QAOA 网络里的超参数:\n", + "\n", + "- 电路比特数目 N\n", + "- 电路的层数 P\n", + "- 迭代次数 ITR\n", + "- 学习步长 LR" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "outputs": [], + "source": [ + "N = 4 # number of qubits, or number of nodes in the graph\n", + "P = 4 # number of layers \n", + "ITR = 120 # number of iteration steps\n", + "LR = 0.1 # learning rate" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "然后,灵活调用:\n", + "\n", + "- 量子电路初始状态:通过调用`superposition_layer()`使每个量子比特处于相干叠加态 $\\frac{1}{\\sqrt{2}}\\left(|0\\rangle+|1\\rangle\\right)$\n", + "- 采用标准 QAOA (记为 METHOD=1)或者扩展 QAOA (记为 METHOD = 2)\n", + "- 经典优化器 Adam optimizer \n", + "\n", + "最后,训练模型并保存结果:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "outputs": [], + "source": [ + "def Paddle_QAOA(classical_graph_adjacency, N, P, METHOD, ITR, LR):\n", + " \"\"\"\n", + " This is the core function to run QAOA.\n", + "\n", + " Args:\n", + " classical_graph_adjacency: adjacency matrix to describe the graph which encodes the classical problem\n", + " N: number of qubits (default value N=4)\n", + " P: number of layers of blocks in the QAOA circuit (default value P=4)\n", + " METHOD: which version of the QAOA circuit is used: 1, standard circuit (default); 2, extended circuit\n", + " ITR: number of iteration steps for QAOA (default value ITR=120)\n", + " LR: learning rate for the gradient-based optimization method (default value LR=0.1)\n", + " Returns:\n", + " the optimized QAOA circuit\n", + " \"\"\"\n", + " with fluid.dygraph.guard():\n", + " # Construct the net or QAOA circuits based on the standard modules\n", + " if METHOD == 1:\n", + " net = Net(shape=[P, 2])\n", + " # Construct the net or QAOA circuits based on the extended modules\n", + " elif METHOD == 2:\n", + " net = Net(shape=[P, 4])\n", + " else:\n", + " raise ValueError(\"Wrong method called!\")\n", + "\n", + " # Classical optimizer\n", + " opt = fluid.optimizer.AdamOptimizer(learning_rate=LR, parameter_list=net.parameters())\n", + "\n", + " # Gradient descent loop\n", + " summary_iter, summary_loss = [], []\n", + " for itr in range(1, ITR + 1):\n", + " loss, cir = net(\n", + " classical_graph_adjacency, N, P, METHOD\n", + " )\n", + " loss.backward()\n", + " opt.minimize(loss)\n", + " net.clear_gradients()\n", + "\n", + " if itr % 10 == 0:\n", + " print(\"iter:\", itr, \" loss:\", \"%.4f\" % loss.numpy())\n", + " summary_loss.append(loss[0][0].numpy())\n", + " summary_iter.append(itr)\n", + "\n", + " theta_opt = net.parameters()[0].numpy()\n", + " print(\"Optmized parameters theta:\\n\", theta_opt)\n", + " \n", + " os.makedirs(\"output\", exist_ok=True)\n", + " np.savez(\"./output/summary_data\", iter=summary_iter, energy=summary_loss)\n", + "\n", + " return cir" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "调用模型训练结果,输出得到的最优参数向量 $\\boldsymbol{\\beta}^*$ 和 $\\boldsymbol{\\gamma}^*$,并且将 QAOA 的输出结果和真实结果进行比较:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Method 1 generates the graph from self-definition using EDGE description\n", + "iter: 10 loss: -3.8531\n", + "iter: 20 loss: -3.9626\n", + "iter: 30 loss: -3.9845\n", + "iter: 40 loss: -3.9944\n", + "iter: 50 loss: -3.9984\n", + "iter: 60 loss: -3.9996\n", + "iter: 70 loss: -3.9999\n", + "iter: 80 loss: -4.0000\n", + "iter: 90 loss: -4.0000\n", + "iter: 100 loss: -4.0000\n", + "iter: 110 loss: -4.0000\n", + "iter: 120 loss: -4.0000\n", + "Optmized parameters theta:\n", + " [[0.24726127 0.53087308]\n", + " [0.94954664 1.9974811 ]\n", + " [1.14545257 2.27267827]\n", + " [2.98845718 2.84445401]]\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYoAAAEGCAYAAAB7DNKzAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/d3fzzAAAACXBIWXMAAAsTAAALEwEAmpwYAAA2HklEQVR4nO3deXxU5b348c93ZrITSAibEJaAgOwBAmorRdCLWK2o1VbFIr9ea221VVutCwqttrcLtra2Uveder0uFbWKqAWX63UJiCwKIpsEZEtYErIn398f52SYJJPJZJmcJHzfr9e8yDlzlu+TM8w353nO8zyiqhhjjDEN8XkdgDHGmPbNEoUxxpiILFEYY4yJyBKFMcaYiCxRGGOMiSjgdQCtrUePHjpo0CCvwzDGmA5l5cqV+1W1Z7j3Ol2iGDRoELm5uV6HYYwxHYqIbG/oPU+qnkTkDhFZIyKrRWSZiPRtYLvLRGST+7qsreM0xhjjXRvFQlUdq6rZwMvA/LobiEh3YAFwIjAZWCAi6W0apTHGGG8ShaoeDllMAcJ1Dz8DeF1VC1T1APA6MLMt4jPGGHOUZ20UIvIbYA5wCJgWZpN+wI6Q5Tx3XbhjXQFcATBgwIDWDdSYKFVUVJCXl0dpaanXoRjToMTERDIzM4mLi4t6n5glChF5A+gT5q15qrpEVecB80TkZuBqnGqmZlHV+4H7AXJycmzwKuOJvLw8UlNTGTRoECLidTjG1KOq5Ofnk5eXR1ZWVtT7xSxRqOrpUW66GHiF+oliJ3BqyHImsKLFgRkTI6WlpZYkTLsmImRkZLBv374m7efVU09DQxZnARvCbPYaMENE0t1G7BnuOmPaLUsSpr1rzmfUqzaK34nIcKAa2A5cCSAiOcCVqnq5qhaIyB3AR+4+t6tqgTfhGmPMscuTRKGq325gfS5wecjyw8DDbRFT0boi9i7eS/LIZPp8L1zTijHGHJs6Xc/s5qoqquLIuiP4Umz4K2OMCWXfii7xu/V2Vd7GYUxLdenSJebnuPvuuxkxYgSzZ89uleMdPHiQRYsW1Vr3ta99rVWOXWP69OlUVlZG3KakpISpU6dSVeV8ETz55JNkZWWRkJDAxIkTWbVqFQDl5eV84xvfqHW8U089lW3btgFw33338aMf/ajWsUePHs1nn31Wb9tYxHPgwAHOO++8Ro8fLUsUrppEoZX2dK0xjVm0aBGvv/46ixcvbpXjhUsU7733XqscG2D9+vVkZGQQCESuRHn44Yc5//zz8fv97Nixg7lz55KamsrChQvZvHkzl1xyCQDx8fGcdtppPP3002GPs3btWiZMmBBcLi0tZdu2bQwbNqxJcTc3nvT0dAoKCsjPz2/S+RpiicIlATdRVFuiMK1j7bfWNvjKX3r0P3D+0vyI27aGP/3pT4wePZrRo0fz5z//GYAjR45w1llnMW7cOEaPHs3TTz8ddl1dV155JVu2bOHMM8/krrvuYvTo0cH37rzzTn75y18CsG3bNkaMGMEPfvADRo0axYwZMygpKQHg8ccfZ+zYsYwbN47vfe973HTTTWzevJns7GxuuOEGoPadUbj4Ix2/riVLlnDuuecGl8ePH8/u3bu59dZbefTRR3nrrbe46KKLWLx4MbNmzQLgmWeeoaqqiltuuYVLLrmEOXPmsHHjxuBf8eeee26DiXLNmjW1EsXatWsZNmwYfr+/3rYNxQK0KJ6zzjqLl156KWx8TWVtFDXclGl3FKazWblyJY888ggffPABqsqJJ57I1KlT2bJlC3379uVf//oXAIcOHWLp0qX11tV17733snTpUpYvX05RUREPPfRQg+fetGkTTz31FA888ADf+c53eO655xg/fjy//vWvee+99+jRowcFBQUcPnyYdevWsXr16qjjT09PD3v8Sy+9tN4xXnnlFV5++WUAKisrKSgooE+fPnzyySdccMEFvP3224wYMYIVK1ZQM01BTdXQxRdfXOtY27dvZ8KECYwePZqPPvqIcNavX8/5558ffBS1qKiIs88+u952DcUybtw4ysvL2bJlS7PjmTVrFjfeeCNz584NG2NTWKJwBe8oqixRmNYx5qUxUW2XMTODjJkZMYvj3Xff5bzzziMlJQWA888/n3feeYeZM2fy85//nBtvvJGzzz6bKVOmMGbMmHrrWiIrK4vs7GwAJk6cyLZt2zhw4AAXXnghPXr0AKB79+4cPny4wWM0FP8555wT9vh1FRcXU15eTlpaGgAbNmzghBNOAODTTz9l5MiR/PWvf2X69OnBbcDpxQzwl7/8hZEjR/KPf/yDRx55JLje7/cTHx9PYWEhqampwf127NhBz5492bDhaPewq6++OmxP6IZiOf/889m/f3+L4hk+fDgbN25s8PfaFFb15Ap0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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "classical_graph, classical_graph_adjacency = generate_graph(N, 1)\n", + "\n", + "opt_cir = Paddle_QAOA(classical_graph_adjacency, N =4, P=4, METHOD=1, ITR=120, LR=0.1)\n", + "\n", + "# Load the data of QAOA\n", + "x1 = np.load('./output/summary_data.npz')\n", + "\n", + "H_min = np.ones([len(x1['iter'])]) * H_min\n", + "\n", + "# Plot loss\n", + "loss_QAOA, = plt.plot(x1['iter'], x1['energy'], \\\n", + " alpha=0.7, marker='', linestyle=\"--\", linewidth=2, color='m')\n", + "benchmark, = plt.plot(x1['iter'], H_min, alpha=0.7, marker='', linestyle=\":\", linewidth=2, color='b')\n", + "plt.xlabel('Number of iterations')\n", + "plt.ylabel('Loss function for QAOA')\n", + "\n", + "plt.legend(handles=[\n", + " loss_QAOA,\n", + " benchmark\n", + "],\n", + " labels=[\n", + " r'Loss function $\\left\\langle {\\psi \\left( {\\bf{\\theta }} \\right)} '\n", + " r'\\right|H\\left| {\\psi \\left( {\\bf{\\theta }} \\right)} \\right\\rangle $',\n", + " 'The benchmark result',\n", + " ], loc='best')\n", + "\n", + "# Show the plot\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 5. 解码量子答案\n", + "\n", + "当求得损失函数 $\\langle \\psi(\\boldsymbol{\\beta},\\boldsymbol{\\gamma}, P)| H_{\\rm Cut}|\\psi(\\boldsymbol{\\beta},\\boldsymbol{\\gamma}, P)\\rangle$ 的最小值以及相对应的一组参数 $(\\boldsymbol{\\beta}^*,\\boldsymbol{\\gamma}^*)$ 后,我们的任务还没有完成。为了进一步求得 Max-Cut 问题的解,需要从 QAOA 输出的量子态 \n", + "\n", + "$$|\\psi(\\boldsymbol{\\beta}^*,\\boldsymbol{\\gamma}^*, P)\\rangle=\\sum_{i=1}^{2^4}\\lambda_i |\\boldsymbol{x}_i\\rangle$$\n", + "\n", + "中解码出经典优化问题的答案。上式中 $\\boldsymbol{x}_i=x_1x_2x_3 x_4\\in \\{0, 1\\}^4$,对应着经典问题的一个可行解。物理上,解码量子态需要对量子态进行测量,然后统计测量结果的概率分布:\n", + " \n", + "$$ p(\\boldsymbol{x})=|\\langle \\boldsymbol{x}|\\psi(\\boldsymbol{\\beta}^*,\\boldsymbol{\\gamma}^*,P)\\rangle|^2.$$\n", + " \n", + "\n", + "多数情况下,某个比特串出现的概率越大,意味着其对应的 Max-Cut 问题最优解的可能性越大。\n", + "\n", + "此外,Paddle Quantum 提供了查看 QAOA 量子电路输出状态的测量结果概率分布的函数:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + }, + "scrolled": true + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYIAAAEWCAYAAABrDZDcAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/d3fzzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAbM0lEQVR4nO3de9hcZX3u8e+dcBRQQRAtCYbWUMUDqAHZtdsDgg1bDVBROSkqNraSCpe2CluLgnZv1MqubAI1ioh2S/BsqhGwKnZrt5iAKASMpAgSFI0iB2UDRu7+sVZgmMw775r3XWtm8q77c11zZdbpt34TwvxmPetZzyPbREREe80adQIRETFaKQQRES2XQhAR0XIpBBERLZdCEBHRcikEEREtt9WoExjUrrvu6nnz5o06jYiILcqVV175S9u79dq2xRWCefPmsXr16lGnERGxRZF080Tb0jQUEdFyKQQRES2XQhAR0XIpBBERLZdCEBHRcikEEREtl0IQEdFyKQQRES23xT1QFlu2ead8ecrH3nTmi2vMJCI2yRVBRETLpRBERLRco4VA0kJJayWtk3RKj+2vkbRB0tXl6/VN5hMREZtr7B6BpNnAUuAQYD2wStIK29d17Xqx7SVN5REREf01eUVwALDO9o227weWA4c1eL6IiJiCJgvBHsAtHcvry3XdXibpB5I+I2lur0CSFktaLWn1hg0bmsg1IqK1Rn2z+F+AebafDnwVuLDXTraX2V5ge8Fuu/WcVyEiIqaoyUJwK9D5C39Oue5Btn9l+75y8SPAsxrMJyIiemiyEKwC5kvaS9I2wFHAis4dJD2+Y3ERcH2D+URERA+N9RqyvVHSEuBSYDbwUdtrJJ0BrLa9AniTpEXARuB24DVN5RMREb01OsSE7ZXAyq51p3W8PxU4tckcIiKiv1HfLI6IiBHLoHMRLZJB/6KXXBFERLRcCkFERMulEEREtFwKQUREy6UQRES0XApBRETLpRBERLRcCkFERMulEEREtFwKQUREy6UQRES0XApBRETLpRBERLRcCkFERMulEEREtFwKQUREy6UQRES0XApBRETLpRBERLRcCkFERMulEEREtFwKQUREy6UQRES0XApBRETLpRBERLRcCkFERMulEEREtFwKQUREy6UQRES03KSFQNJzJO1Qvj9O0lmSnlAluKSFktZKWifplD77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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "with fluid.dygraph.guard():\n", + " # Measure the output state of the QAOA circuit for 1024 shots by default\n", + " prob_measure = opt_cir.measure(plot=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "最后,再次利用参数代换 $|x \\rangle\\rightarrow z=2x-1\\in\\{-1, 1\\}$,可以从量子答案中解码得到 Max-Cut 问题的可行解。 此时,记 $z_i=-1$ 的顶点属于集合 $S^\\prime$ 以及 $z_j=1$ 的顶点属于集合 $S$,这两个顶点集合之间存在的边就是该图的一个可能得最大割方案。 \n", + "\n", + "选取测量结果中出现几率最大的比特串,然后将其映射回经典解,并且画出对应的最大割方案:\n", + "\n", + "- 蓝色顶点属于集合 $S$\n", + "- 红色顶点属于集合 $S^\\prime$\n", + "- 虚线表示被割的边" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "pycharm": { + "is_executing": false + }, + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The output bitstring: ['0101']\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Find the max value in measured probability of bitstrings\n", + "max_prob = max(prob_measure.values())\n", + "# Find the bitstring with max probability\n", + "solution_list = [result[0] for result in prob_measure.items() if result[1] == max_prob]\n", + "print(\"The output bitstring:\", solution_list)\n", + "\n", + "# Draw the graph representing the first bitstring in the solution_list to the MaxCut-like problem\n", + "head_bitstring = solution_list[0]\n", + "\n", + "node_cut = [\"blue\" if head_bitstring[node] == \"1\" else \"red\" for node in classical_graph]\n", + "\n", + "edge_cut = [\n", + " \"solid\" if head_bitstring[node_row] == head_bitstring[node_col] else \"dashed\"\n", + " for node_row, node_col in classical_graph.edges()\n", + " ]\n", + "nx.draw(\n", + " classical_graph,\n", + " pos,\n", + " node_color=node_cut,\n", + " style=edge_cut,\n", + " width=4,\n", + " with_labels=True,\n", + " font_weight=\"bold\",\n", + ")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "source": [ + "# 参考文献\n", + "\n", + "[1] [Farhi, E., Goldstone, J. & Gutmann, S. A Quantum Approximate Optimization Algorithm. arXiv:1411.4028 (2014).](https://arxiv.org/abs/1411.4028)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.10" + }, + "pycharm": { + "stem_cell": { + "cell_type": "raw", + "metadata": { + "collapsed": false + }, + "source": [] + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tutorial/QAOA/QAOA.pdf b/tutorial/QAOA/QAOA.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f274bebf2c4bfcb6c2b0b6d256380174cf5770ef Binary files /dev/null and b/tutorial/QAOA/QAOA.pdf differ diff --git a/tutorial/QAOA/QAOA_En.ipynb b/tutorial/QAOA/QAOA_En.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..e16c188efdcbfb1670d030ce060b6d311bda7d1d --- /dev/null +++ b/tutorial/QAOA/QAOA_En.ipynb @@ -0,0 +1,837 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Quantum Approximate Optimization Algorithm (QAOA)\n", + "\n", + "## English | [简体中文](./QAOA.ipynb)\n", + "\n", + " Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Preparation\n", + "\n", + "This document provides an interactive experience to show how the quantum approximate optimization algorithm (QAOA) [1] works in the Paddle Quantum. \n", + "\n", + "To get started, let us import some necessary libraries and functions:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "outputs": [], + "source": [ + "from paddle import fluid\n", + "\n", + "import os\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import networkx as nx\n", + "\n", + "from paddle_quantum.circuit import UAnsatz\n", + "from paddle_quantum.utils import pauli_str_to_matrix" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Background\n", + "\n", + "QAOA is one of quantum algorithms which can be implemented on near-term quantum processors, also called as noisy intermediate-scale quantum (NISQ) processors, and may have wide applications in solving hard computational problems. For example, it could be applied to tackle a large family of optimization problems, named as the quadratic unconstrained binary optimization (QUBO) which is ubiquitous in the computer science and operation research. Basically, this class can be modeled with the form of\n", + "\n", + "$$F=\\max_{z_i\\in\\{-1,1\\}} \\sum_{i,j} q_{ij}(1-z_iz_j)=-\\min_{z_i\\in\\{-1,1\\}} \\sum_{i,j} q_{ij}z_iz_j+ \\sum_{i,j} q_{ij} $$\n", + "\n", + "\n", + "where $z_i$s are binary parameters and coefficients $q_{ij}$ refer to the weight associated to $x_i x_j$. Indeed, it is usually extremely difficult for classical computers to give the exact optimal solution, while QAOA provides an alternative approach which may have a speedup advantage over classical ones to solve these hard problems.\n", + "\n", + "QAOA works as follows: The above optimization problem is first mapped to another problem of finding the ground energy or/and the corresponding ground state for a complex many-body Hamiltonian, e.g., the well-known Ising model or spin-glass model in many-body physics. In this tutorial, we use the Max-Cut problem in graph theory to explain how QAOA works. Essentially, the Max-cut problem is transformed into a problem of finding the smallest eigenvalue and the corresponding eigenvector(s) for a real diagonal matrix $H$. Then, QAOA designates a specific routine with adjustable parameters to approximately find the best solution. Moreover, to accomplish the task, these parameters could be updated via some rules set by fast classical algorithms, such as gradient-free or gradient-based methods. Thus, it is also a quantum-classical hybrid algorithm just as the variational quantum eigensolver (VQE)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# Example\n", + "\n", + "## 1. Max-Cut problem\n", + "\n", + "Given a graph $G$ composed of $N$ nodes and $M$ edges, the problem is to find a cut protocol which divides the node set into two complementary subsets $S$ and $S^\\prime$ such that the number of edges between these sets is as large as possible. For example, consider the ring case with four nodes as shown in the figure.\n", + "\n", + " ![ring4.png](https://release-data.cdn.bcebos.com/PIC%2FMaxCut.png) \n", + " \n", + "\n", + "Thus, given a cut protocol, if the node $i$ belongs to the set $S$, then it is assigned to $z_i =1$, while $z_j= -1$ for $j \\in S^\\prime$. Then, for any edge connecting nodes $i$ and $j$, if both nodes are in the same set $S$ or $S^\\prime$, then there is $z_iz_j=1$; otherwise, $z_iz_j=-1$. Hence, the cut problem can be formulated as the optimization problem \n", + "\n", + "$$ F=\\min_{z_i\\in\\{-1, 1\\}} z_1 z_2+z_2z_3+z_3z_4+z_4z_1.$$\n", + "\n", + "Here, the weight $q_{ij}s$ are set to $1$ for all edges. Indeed, any feasible solution to the above problem can be described by a bitstring $ \\boldsymbol{z}=z_1z_2z_3z_4 \\in \\{-1, 1\\}^4$. Moreover, we need to search over all possible bitstrings of $2^N$ to find its optimal solution, which becomes computionally hard for classical algorithms.\n", + "\n", + "Two methods are provided to pre-process this optimization problem, i.e., to input the given graph with/without weights: \n", + "\n", + "- Method 1 generates the graph via its full description of nodes and edges,\n", + "- Method 2 specifies the graph via its adjacency matrix.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "def generate_graph(N, GRAPHMETHOD):\n", + " \"\"\"\n", + " It plots an N-node graph which is specified by Method 1 or 2.\n", + " \n", + " Args:\n", + " N: number of nodes (vertices) in the graph\n", + " METHOD: choose which method to generate a graph\n", + " Returns:\n", + " the specific graph and its adjacency matrix\n", + " \"\"\"\n", + " # Method 1 generates a graph by self-definition\n", + " if GRAPHMETHOD == 1:\n", + " print(\"Method 1 generates the graph from self-definition using EDGE description\")\n", + " graph = nx.Graph()\n", + " graph_nodelist=range(N)\n", + " graph.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 0)])\n", + " graph_adjacency = nx.to_numpy_matrix(graph, nodelist=graph_nodelist)\n", + " # Method 2 generates a graph by using its adjacency matrix directly\n", + " elif GRAPHMETHOD == 2:\n", + " print(\"Method 2 generates the graph from networks using adjacency matrix\")\n", + " graph_adjacency = np.array([[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 1, 0]])\n", + " graph = nx.Graph(graph_adjacency)\n", + " else:\n", + " print(\"Method doesn't exist \")\n", + "\n", + " return graph, graph_adjacency" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this notebook, Method 1 is used to process and then visualize the given graph. Note that the node label starts from $0$ to $ N-1$ in both methods for an $N$-node graph. \n", + "\n", + "Here, we need to specify:\n", + "\n", + "- number of nodes: $N=4$\n", + "- which method to preprocess the graph: GRAPHMETHOD = 1 " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Method 1 generates the graph from self-definition using EDGE description\n", + "[[0. 1. 0. 1.]\n", + " [1. 0. 1. 0.]\n", + " [0. 1. 0. 1.]\n", + " [1. 0. 1. 0.]]\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# number of qubits or number of nodes in the graph\n", + "N=4 \n", + "classical_graph, classical_graph_adjacency= generate_graph(N, GRAPHMETHOD=1)\n", + "print(classical_graph_adjacency)\n", + "\n", + "pos = nx.circular_layout(classical_graph)\n", + "nx.draw(classical_graph, pos, width=4, with_labels=True, font_weight='bold')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Encoding\n", + "\n", + "This step encodes the classical optimization problem to its quantum version. Using the transformation $z=1\\rightarrow |0\\rangle = \\begin{bmatrix}1 \\\\ 0\\end{bmatrix}$ and $z=-1\\rightarrow |1\\rangle = \\begin{bmatrix}0 \\\\ 1\\end{bmatrix}$, the binary parameter $z$ is encoded as the eigenvalues of the Pauli-Z operator acting on the single qubit, i.e., $z\\rightarrow Z=\\begin{bmatrix} 1 & 0\\\\ 0 & -1\\end{bmatrix}$. It yields that the objective function in the classical optimization problem is transformed to the Hamiltonian\n", + "\n", + "$$H_c= Z_1Z_2+Z_2Z_3+Z_3Z_4+Z_4Z_1.$$\n", + "\n", + "Here, for simplicity $Z_iZ_{j}$ stands for the tensor product $Z_i\\otimes Z_j$ which represents that Pauli-Z operator acts on each qubit $i, j$ and the identity operation is imposed on the rest. And the Max-Cut problem is mapped to the following quantum optimization problem\n", + "\n", + "$$ F=\\min_{|\\psi\\rangle}\\, \\langle \\psi| H_c |\\psi\\rangle$$\n", + "\n", + "where the state vector $|\\psi\\rangle$ describes a $4$-dimensional complex vector which is normalized to $1$, and $\\langle \\psi|$ is its conjugate transpose form. It is equivalent to find the smallest eigenvalue $F$ and the corresponding eigenstate(s) for the matrix $H_c$." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "def H_generator(N, adjacency_matrix):\n", + " \"\"\"\n", + " This function maps the given graph via its adjacency matrix to the corresponding Hamiltiona H_c.\n", + " \n", + " Args:\n", + " N: number of qubits, or number of nodes in the graph, or number of parameters in the classical problem\n", + " adjacency_matrix: the adjacency matrix generated from the graph encoding the classical problem\n", + " Returns:\n", + " the problem-based Hmiltonian H's list form generated from the graph_adjacency matrix for the given graph\n", + " \"\"\"\n", + " H_list = []\n", + " # Generate the Hamiltonian H_c from the graph via its adjacency matrix\n", + " for row in range(N):\n", + " for col in range(N):\n", + " if adjacency_matrix[row, col] and row < col:\n", + " # Construct the Hamiltonian in the list form for the calculation of expectation value\n", + " H_list.append([1.0, 'z'+str(row) + ',z' + str(col)])\n", + "\n", + " return H_list" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The explicit form of the matrix $H_c$, including its maximal and minimal eigenvalues, can be imported, which later could be used to benchmark the performance of QAOA. " + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 4. 0. 0. 0. 0. -4. 0. 0. 0. 0. -4. 0. 0. 0. 0. 4.]\n", + "H_max: 4.0 H_min: -4.0\n" + ] + } + ], + "source": [ + "# Convert the Hamiltonian's list form to matrix form\n", + "H_matrix = pauli_str_to_matrix(H_generator(N, classical_graph_adjacency), N)\n", + "\n", + "H_diag = np.diag(H_matrix).real\n", + "H_max = np.max(H_diag)\n", + "H_min = np.min(H_diag)\n", + "\n", + "print(H_diag)\n", + "print('H_max:', H_max, ' H_min:', H_min)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Building\n", + "\n", + "This part is to build up the parameterized quantum circuit of QAOA to perform the computation process. Particularly, the QAOA circuit is constructed by alternatively placing two parameterized modules\n", + "\n", + "$$U_x(\\beta_P)U_c(\\gamma_P)\\dots U_x(\\beta_1)U_c(\\gamma_1)$$\n", + "\n", + "where $P$ is the number of layers to place these two modules. Particularly, one is governed by the encoding matrix $H_c$ via the unitary transformation\n", + "\n", + "$$U_c(\\gamma)=e^{-i \\gamma H_c }$$\n", + "\n", + "where $i$ is the imaginary unit, and $\\gamma\\in [0, \\pi]$ is to be optimized. The other one is \n", + "\n", + "$$U_x(\\beta)=e^{-i \\beta H_x },$$\n", + "\n", + "where $\\beta\\in [0, \\pi]$ and the driving Hamiltonian or matrix $H_x$ adimits an explicit form of\n", + "\n", + "$$H_x =X_1+X_2+X_3+X_4 $$\n", + "\n", + "where the operator $X=\\begin{bmatrix} 0 & 1\\\\ 1& 0\\end{bmatrix}$ defines the Pauli-X operation acting on the qubit.\n", + "\n", + "\n", + "\n", + "Further, each module in the QAOA circuit can be decomposed into a series of operations acting on single qubits and two qubits. In particular, the first has the decomposition of $U_c(\\gamma)=\\prod_{(i, j)}e^{-i \\gamma Z_i\\otimes Z_j }$ while there is $U_x(\\beta)=\\prod_{i}e^{-i \\beta X_i}$ for the second. This is illustrated in the following figure.\n", + "\n", + " \n", + "\n", + "\n", + "Then, based on\n", + "\n", + "- initial state of QAOA circuits \n", + "- adjacency matrix describing the graph\n", + "- number of qubits\n", + "- number of layers\n", + "\n", + "we are able to construct the standard QAOA circuit:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "outputs": [], + "source": [ + "def circuit_QAOA(theta, adjacency_matrix, N, P):\n", + " \"\"\"\n", + " This function constructs the parameterized QAOA circuit which is composed of P layers of two blocks:\n", + " one block is based on the problem Hamiltonian H which encodes the classical problem,\n", + " and the other is constructed from the driving Hamiltonian describing the rotation around Pauli X\n", + " acting on each qubit. It outputs the final state of the QAOA circuit.\n", + " \n", + " Args:\n", + " theta: parameters to be optimized in the QAOA circuit\n", + " adjacency_matrix: the adjacency matrix of the graph encoding the classical problem\n", + " N: number of qubits, or equivalently, the number of parameters in the original classical problem\n", + " P: number of layers of two blocks in the QAOA circuit\n", + " Returns:\n", + " the QAOA circuit\n", + " \"\"\"\n", + "\n", + " cir = UAnsatz(N)\n", + " \n", + " #prepare the input state in the uniform superposition of 2^N bit-strings in the computational basis\n", + " cir.superposition_layer()\n", + " # This loop defines the QAOA circuit with P layers of two blocks\n", + " for layer in range(P):\n", + " # The second and third loops construct the first block which involves two-qubit operation\n", + " # e^{-i\\gamma Z_iZ_j} acting on a pair of qubits or nodes i and j in the circuit in each layer.\n", + " for row in range(N):\n", + " for col in range(N):\n", + " if adjacency_matrix[row, col] and row < col:\n", + " cir.cnot([row, col])\n", + " cir.rz(theta[layer][0], col)\n", + " cir.cnot([row, col])\n", + " # This loop constructs the second block only involving the single-qubit operation e^{-i\\beta X}.\n", + " for i in range(N):\n", + " cir.rx(theta[layer][1], i)\n", + "\n", + " return cir" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Indeed, the QAOA circuit could be extended to other structures by replacing the modules in the above standard circuit to improve QAOA performance. Here, we provide one candidate extension in which the Pauli-X rotation $R_x(\\beta) $ on each qubit in the driving matrix $H_x$ is replaced by an arbitrary rotation described by $U3(\\beta_1,\\beta_2,\\beta_3)$. " + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "outputs": [], + "source": [ + "def circuit_extend_QAOA(theta, adjacency_matrix, N, P):\n", + " \"\"\"\n", + " This is an extended version of the QAOA circuit, and the main difference is the block constructed\n", + " from the driving Hamiltonian describing the rotation around an arbitrary direction on each qubit.\n", + "\n", + " Args:\n", + " theta: parameters to be optimized in the QAOA circuit\n", + " input_state: input state of the QAOA circuit which usually is the uniform superposition of 2^N bit-strings\n", + " in the computational basis\n", + " adjacency_matrix: the adjacency matrix of the problem graph encoding the original problem\n", + " N: number of qubits, or equivalently, the number of parameters in the original classical problem\n", + " P: number of layers of two blocks in the QAOA circuit\n", + " Returns:\n", + " the extended QAOA circuit\n", + "\n", + " Note:\n", + " If this circuit_extend_QAOA function is used to construct QAOA circuit, then we need to change the parameter layer\n", + " in the Net function defined below from the Net(shape=[D, 2]) for circuit_QAOA function to Net(shape=[D, 4])\n", + " because the number of parameters doubles in each layer in this QAOA circuit.\n", + " \"\"\"\n", + " cir = UAnsatz(N)\n", + "\n", + " #prepare the input state in the uniform superposition of 2^N bit-strings in the computational basis\n", + " cir.superposition_layer()\n", + " for layer in range(P):\n", + " for row in range(N):\n", + " for col in range(N):\n", + " if adjacency_matrix[row, col] and row < col:\n", + " cir.cnot([row, col])\n", + " cir.rz(theta[layer][0], col)\n", + " cir.cnot([row, col])\n", + "\n", + " for i in range(N):\n", + " cir.u3(*theta[layer][1:], i)\n", + "\n", + " return cir" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, the QAOA circuit outputs\n", + "\n", + "$$|\\psi(\\boldsymbol{\\beta},\\boldsymbol{\\gamma}, P)\\rangle=U_x(\\beta_P)U_c(\\gamma_P)\\dots U_x(\\beta_1)U_c(\\gamma_1)|+\\rangle_1\\dots|+\\rangle_N$$\n", + "\n", + "where each qubit is initialized as the superposition state $|+\\rangle=\\frac{1}{\\sqrt{2}}\\left(|0\\rangle+|1\\rangle\\right)$. And we are able to obtain the loss function for the QAOA circuit\n", + "\n", + "$$F_P=\\min_{\\boldsymbol{\\beta},\\boldsymbol{\\gamma}} \\langle \\psi(\\boldsymbol{\\beta},\\boldsymbol{\\gamma}, P)| H_c|\\psi(\\boldsymbol{\\beta},\\boldsymbol{\\gamma}, P)\\rangle.$$\n", + "\n", + "Additionally, we may tend to fast classical algorithms to update the parameter vectors $\\boldsymbol{\\beta},\\boldsymbol{\\gamma}$ to achieve the optimal value for the above quantum optimization problem. \n", + "\n", + "In Paddle Quantum, this process is accomplished in the Net function:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "outputs": [], + "source": [ + "class Net(fluid.dygraph.Layer):\n", + " \"\"\"\n", + " It constructs the net for QAOA which combines the QAOA circuit with the classical optimizer which sets rules\n", + " to update parameters described by theta introduced in the QAOA circuit.\n", + "\n", + " \"\"\"\n", + " def __init__(\n", + " self,\n", + " shape,\n", + " param_attr=fluid.initializer.Uniform(low=0.0, high=np.pi, seed=1024),\n", + " dtype=\"float64\",\n", + " ):\n", + " super(Net, self).__init__()\n", + "\n", + " self.theta = self.create_parameter(\n", + " shape=shape, attr=param_attr, dtype=dtype, is_bias=False\n", + " )\n", + "\n", + " def forward(self, adjacency_matrix, N, P, METHOD):\n", + " \"\"\"\n", + " This function constructs the loss function for the QAOA circuit.\n", + "\n", + " Args:\n", + " adjacency_matrix: the adjacency matrix generated from the graph encoding the classical problem\n", + " N: number of qubits\n", + " P: number of layers\n", + " METHOD: which version of QAOA is chosen to solve the problem, i.e., standard version labeled by 1 or\n", + " extended version by 2.\n", + " Returns:\n", + " the loss function for the parameterized QAOA circuit and the circuit itself\n", + " \"\"\"\n", + " \n", + " # Generate the problem_based quantum Hamiltonian H_problem based on the classical problem in paddle\n", + " H_problem = H_generator(N, adjacency_matrix)\n", + "\n", + " # The standard QAOA circuit: the function circuit_QAOA is used to construct the circuit, indexed by METHOD 1.\n", + " if METHOD == 1:\n", + " cir = circuit_QAOA(self.theta, adjacency_matrix, N, P)\n", + " # The extended QAOA circuit: the function circuit_extend_QAOA is used to construct the net, indexed by METHOD 2.\n", + " elif METHOD == 2:\n", + " cir = circuit_extend_QAOA(self.theta, adjacency_matrix, N, P)\n", + " else:\n", + " raise ValueError(\"Wrong method called!\")\n", + "\n", + " cir.run_state_vector()\n", + " loss = cir.expecval(H_problem)\n", + "\n", + " return loss, cir" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Training\n", + "\n", + "In this part, the QAOA circuit is trained to find the \"optimal\" solution to the optimization problem.\n", + "\n", + "First, let us specify some parameters:\n", + "\n", + "- number of qubits: N\n", + "- number of layes: P\n", + "- iteration steps: ITR\n", + "- learning rate: LR" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "outputs": [], + "source": [ + "N = 4 # number of qubits, or number of nodes in the graph\n", + "P = 4 # number of layers \n", + "ITR = 120 # number of iteration steps\n", + "LR = 0.1 # learning rate" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then, with the following inputs\n", + "\n", + "- initial state: each qubit is initialized as $\\frac{1}{\\sqrt{2}}\\left(|0\\rangle+|1\\rangle\\right)$ by calling `cir.superposition_layer()`\n", + "- Standard QAOA circuit (METHOD = 1) or Extended QAOA (METHOD = 2) \n", + "- Classical optimizer: Adam optimizer\n", + "\n", + "we are able to train the whole net:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "outputs": [], + "source": [ + "def Paddle_QAOA(classical_graph_adjacency, N, P, METHOD, ITR, LR):\n", + " \"\"\"\n", + " This is the core function to run QAOA.\n", + "\n", + " Args:\n", + " classical_graph_adjacency: adjacency matrix to describe the graph which encodes the classical problem\n", + " N: number of qubits (default value N=4)\n", + " P: number of layers of blocks in the QAOA circuit (default value P=4)\n", + " METHOD: which version of the QAOA circuit is used: 1, standard circuit (default); 2, extended circuit\n", + " ITR: number of iteration steps for QAOA (default value ITR=120)\n", + " LR: learning rate for the gradient-based optimization method (default value LR=0.1)\n", + " Returns:\n", + " the optimized QAOA circuit\n", + " \"\"\"\n", + " with fluid.dygraph.guard():\n", + " # Construct the net or QAOA circuits based on the standard modules\n", + " if METHOD == 1:\n", + " net = Net(shape=[P, 2])\n", + " # Construct the net or QAOA circuits based on the extended modules\n", + " elif METHOD == 2:\n", + " net = Net(shape=[P, 4])\n", + " else:\n", + " raise ValueError(\"Wrong method called!\")\n", + "\n", + " # Classical optimizer\n", + " opt = fluid.optimizer.AdamOptimizer(learning_rate=LR, parameter_list=net.parameters())\n", + "\n", + " # Gradient descent loop\n", + " summary_iter, summary_loss = [], []\n", + " for itr in range(1, ITR + 1):\n", + " loss, cir = net(\n", + " classical_graph_adjacency, N, P, METHOD\n", + " )\n", + " loss.backward()\n", + " opt.minimize(loss)\n", + " net.clear_gradients()\n", + "\n", + " if itr % 10 == 0:\n", + " print(\"iter:\", itr, \" loss:\", \"%.4f\" % loss.numpy())\n", + " summary_loss.append(loss[0][0].numpy())\n", + " summary_iter.append(itr)\n", + "\n", + " theta_opt = net.parameters()[0].numpy()\n", + " print(\"Optmized parameters theta:\\n\", theta_opt)\n", + " \n", + " os.makedirs(\"output\", exist_ok=True)\n", + " np.savez(\"./output/summary_data\", iter=summary_iter, energy=summary_loss)\n", + "\n", + " return cir" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After the completion of training, the QAOA outputs the results, including the optimal parameters $\\boldsymbol{\\beta}^*$ and $\\boldsymbol{\\gamma}^*$. By contrast, its performance can be evaluated with the true value of the optimization problem." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Method 1 generates the graph from self-definition using EDGE description\n", + "iter: 10 loss: -3.8531\n", + "iter: 20 loss: -3.9626\n", + "iter: 30 loss: -3.9845\n", + "iter: 40 loss: -3.9944\n", + "iter: 50 loss: -3.9984\n", + "iter: 60 loss: -3.9996\n", + "iter: 70 loss: -3.9999\n", + "iter: 80 loss: -4.0000\n", + "iter: 90 loss: -4.0000\n", + "iter: 100 loss: -4.0000\n", + "iter: 110 loss: -4.0000\n", + "iter: 120 loss: -4.0000\n", + "Optmized parameters theta:\n", + " [[0.24726127 0.53087308]\n", + " [0.94954664 1.9974811 ]\n", + " [1.14545257 2.27267827]\n", + " [2.98845718 2.84445401]]\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "classical_graph, classical_graph_adjacency = generate_graph(N, 1)\n", + "\n", + "opt_cir = Paddle_QAOA(classical_graph_adjacency, N =4, P=4, METHOD=1, ITR=120, LR=0.1)\n", + "\n", + "# Load the data of QAOA\n", + "x1 = np.load('./output/summary_data.npz')\n", + "\n", + "H_min = np.ones([len(x1['iter'])]) * H_min\n", + "\n", + "# Plot loss\n", + "loss_QAOA, = plt.plot(x1['iter'], x1['energy'], \\\n", + " alpha=0.7, marker='', linestyle=\"--\", linewidth=2, color='m')\n", + "benchmark, = plt.plot(x1['iter'], H_min, alpha=0.7, marker='', linestyle=\":\", linewidth=2, color='b')\n", + "plt.xlabel('Number of iterations')\n", + "plt.ylabel('Loss function for QAOA')\n", + "\n", + "plt.legend(handles=[\n", + " loss_QAOA,\n", + " benchmark\n", + "],\n", + " labels=[\n", + " r'Loss function $\\left\\langle {\\psi \\left( {\\bf{\\theta }} \\right)} '\n", + " r'\\right|H\\left| {\\psi \\left( {\\bf{\\theta }} \\right)} \\right\\rangle $',\n", + " 'The benchmark result',\n", + " ], loc='best')\n", + "\n", + "# Show the plot\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 5. Decoding \n", + "\n", + "However, the output of optimized QAOA circuits \n", + "\n", + "$$|\\psi(\\boldsymbol{\\beta}^*,\\boldsymbol{\\gamma}^*, P)\\rangle=\\sum_{i=1}^{2^4}\\lambda_i |\\boldsymbol{x}_i\\rangle$$\n", + "\n", + "does not give us the answer to the Max-Cut problem directly. Instead, each bitstring $\\boldsymbol{x}_i=x_1x_2x_3 x_4\\in \\{0, 1\\}^4$ in the state $|\\psi(\\boldsymbol{\\beta}^*,\\boldsymbol{\\gamma}^*, P)\\rangle$ represents a possible classical solution. Thus, we need to decode the ouptut of QAOA circuits. \n", + "\n", + "The task of decoding quantum answer can be accomplished via measurement. Given the output state, the measurement statistics for each bitstring obeys the probability distribution\n", + "\n", + "$$ p(\\boldsymbol{x})=|\\langle \\boldsymbol{x}|\\psi(\\boldsymbol{\\beta}^*,\\boldsymbol{\\gamma}^*,P)\\rangle|^2.$$\n", + " \n", + "And this distribution is plotted using the following function:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + }, + "scrolled": true + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "with fluid.dygraph.guard():\n", + " # Measure the output state of the QAOA circuit for 1024 shots by default\n", + " prob_measure = opt_cir.measure(plot=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Again, using the relation $|x \\rangle\\rightarrow z=2x-1$, we are able to obtain a classical answer from the quantum state. Specifically, assume that $z_i=-1$ for $i \\in S$ and $z_j=-1$ for $j \\in S^\\prime$. Thus, one bistring sampled from the output state of QAOA corresponds to one feasible cut to the given graph. And it is highly possible that the higher probability the bitstring is, the more likely it gives rise to the max cut protocol.\n", + "\n", + "The bistring with the largest probability is picked up, and then mapped back to solution to the Max-Cut problem :\n", + "\n", + "- the node set $S$ is in blue color\n", + "- the node set $S^\\prime$ is in red color\n", + "- the dashed lines represent the cut edges " + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The output bitstring: ['1010']\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Find the max value in measured probability of bitstrings\n", + "max_prob = max(prob_measure.values())\n", + "# Find the bitstring with max probability\n", + "solution_list = [result[0] for result in prob_measure.items() if result[1] == max_prob]\n", + "print(\"The output bitstring:\", solution_list)\n", + "\n", + "# Draw the graph representing the first bitstring in the solution_list to the MaxCut-like problem\n", + "head_bitstring = solution_list[0]\n", + "\n", + "node_cut = [\"blue\" if head_bitstring[node] == \"1\" else \"red\" for node in classical_graph]\n", + "\n", + "edge_cut = [\n", + " \"solid\" if head_bitstring[node_row] == head_bitstring[node_col] else \"dashed\"\n", + " for node_row, node_col in classical_graph.edges()\n", + " ]\n", + "nx.draw(\n", + " classical_graph,\n", + " pos,\n", + " node_color=node_cut,\n", + " style=edge_cut,\n", + " width=4,\n", + " with_labels=True,\n", + " font_weight=\"bold\",\n", + ")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "source": [ + "# References\n", + "\n", + "[1] [Farhi, E., Goldstone, J. & Gutmann, S. A Quantum Approximate Optimization Algorithm. arXiv:1411.4028 (2014).](https://arxiv.org/abs/1411.4028)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.10" + }, + "pycharm": { + "stem_cell": { + "cell_type": "raw", + "metadata": { + "collapsed": false + }, + "source": [] + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tutorial/QAOA/QAOA_En.pdf b/tutorial/QAOA/QAOA_En.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2e7a69776291fbc3727b00fd09047120ad8fa2c7 Binary files /dev/null and b/tutorial/QAOA/QAOA_En.pdf differ diff --git a/tutorial/QAOA/figures/QAOAcircuit.png b/tutorial/QAOA/figures/QAOAcircuit.png new file mode 100644 index 0000000000000000000000000000000000000000..f51b3b6856ffa31152b8daaa8340cfa043d2bceb Binary files /dev/null and b/tutorial/QAOA/figures/QAOAcircuit.png differ diff --git a/tutorial/QAOA_En.ipynb b/tutorial/QAOA_En.ipynb deleted file mode 100644 index f40247c7426bf11ccc3f67ec66248c68dcd14bb6..0000000000000000000000000000000000000000 --- a/tutorial/QAOA_En.ipynb +++ /dev/null @@ -1,795 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## English | [简体中文](./QAOA.ipynb)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Preparation\n", - "\n", - "This document provides an interactive experience to show how the quantum approximate optimization algorithm (QAOA) [1] works in the Paddle Quantum. \n", - "\n", - "To get started, let us import some necessary libraries and functions:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "outputs": [], - "source": [ - "from paddle import fluid\n", - "\n", - "import os\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "import networkx as nx\n", - "\n", - "from numpy import matmul as np_matmul\n", - "from paddle.complex import matmul as pp_matmul\n", - "from paddle.complex import transpose\n", - "from paddle_quantum.circuit import UAnsatz" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Background\n", - "\n", - "QAOA is one of quantum algorithms which can be implemented on near-term quantum processors, also called as noisy intermediate-scale quantum (NISQ) processors, and may have wide applications in solving hard computational problems. For example, it could be applied to tackle a large family of optimization problems, named as the quadratic unconstrained binary optimization (QUBO) which is ubiquitous in the computer science and operation research. Basically, this class can be modeled with the form of\n", - "\n", - "$$F=\\max_{z_i\\in\\{-1,1\\}} \\sum q_{ij}(1-z_iz_j)=-\\min_{z_i\\in\\{-1,1\\}} \\sum q_{ij}z_iz_j+ \\sum q_{ij} $$\n", - "\n", - "\n", - "where $z_i$s are binary parameters and coefficients $q_{ij}$ refer to the weight associated to $x_i x_j$. Indeed, it is usually extremely difficult for classical computers to give the exact optimal solution, while QAOA provides an alternative approach which may have a speedup advantage over classical ones to solve these hard problems.\n", - "\n", - "QAOA works as follows: The above optimization problem is first mapped to another problem of finding the ground energy or/and the corresponding ground state for a complex many-body Hamiltonian, e.g., the well-known Ising model or spin-glass model in many-body physics. Essentially, it is transformed to find the smallest eigenvalue and the corresponding eigenvector(s) for a real diagonal matrix $H$. Then, QAOA designates a specific routine with adjustable parameters to approximately find the best solution. Moreover, to accomplish the task, these parameters could be updated via some rules set by fast classical algorithms, such as gradient-free or gradient-based methods. Thus, it is also a quantum-classical hybrid algorithm just as the variational quantum eigensolver (VQE).\n", - "\n", - "Here, the Max-Cut problem in graph theory is used to explain how QAOA works." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "# Example\n", - "\n", - "## 1. Max-Cut problem\n", - "\n", - "Given a graph $G$ composed of $N$ nodes and $M$ edges, the problem is to find a cut protocol which divides the node set into two complementary subsets $S$ and $S^\\prime$ such that the number of edges between these sets is as large as possible. For example, consider the ring case with four nodes as shown in the figure.\n", - "\n", - " ![ring4.png](https://release-data.cdn.bcebos.com/PIC%2FMaxCut.png) \n", - " \n", - "\n", - "Thus, given a cut protocol, if the node $i$ belongs to the set $S$, then it is assigned to $z_i =1$, while $z_j= -1$ for $j \\in S^\\prime$. Then, for any edge connecting nodes $i$ and $j$, if both nodes are in the same set $S$ or $S^\\prime$, then there is $z_iz_j=1$; otherwise, $z_iz_j=-1$. Hence, the cut problem can be formulated as the optimization problem \n", - "\n", - "$$ F=\\min_{z_i\\in\\{-1, 1\\}} z_1 z_2+z_2z_3+z_3z_4+z_4z_1.$$\n", - "\n", - "Here, the weight $q_{ij}s$ are set to $1$ for all edges. Indeed, any feasible solution to the above problem can be described by a bitstring $ \\boldsymbol{z}=z_1z_2z_3z_4 \\in \\{-1, 1\\}^4$. Moreover, we need to search over all possible bitstrings of $2^N$ to find its optimal solution, which becomes computionally hard for classical algorithms.\n", - "\n", - "Two methods are provided to pre-process this optimization problem, i.e., to input the given graph with/without weights: \n", - "\n", - "- Method 1 generates the graph via its full description of nodes and edges,\n", - "- Method 2 specifies the graph via its adjacency matrix.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def generate_graph(N, GRAPHMETHOD):\n", - " \"\"\"\n", - " Args:\n", - " N: number of nodes (vertices) in the graph\n", - " METHOD: choose which method to generate a graph\n", - " Return:\n", - " the specific graph and its adjacency matrix\n", - " \"\"\"\n", - " # Method 1 generates a graph by self-definition\n", - " if GRAPHMETHOD == 1:\n", - " print(\"Method 1 generates the graph from self-definition using EDGE description\")\n", - " graph = nx.Graph()\n", - " graph_nodelist=range(N)\n", - " graph.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 0)])\n", - " graph_adjacency = nx.to_numpy_matrix(graph, nodelist=graph_nodelist)\n", - " # Method 2 generates a graph by using its adjacency matrix directly\n", - " elif GRAPHMETHOD == 2:\n", - " print(\"Method 2 generates the graph from networks using adjacency matrix\")\n", - " graph_adjacency = np.array([[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 1, 0]])\n", - " graph = nx.Graph(graph_adjacency)\n", - " else:\n", - " print(\"Method doesn't exist \")\n", - "\n", - " return graph, graph_adjacency" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this notebook, Method 1 is used to process and then visualize the given graph. Note that the node label starts from $0$ to $ N-1$ in both methods for an $N$-node graph. \n", - "\n", - "Here, we need to specify:\n", - "\n", - "- number of nodes: $N=4$\n", - "- which method to preprocess the graph: GRAPHMETHOD = 1 " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "outputs": [], - "source": [ - "# number of qubits or number of nodes in the graph\n", - "N=4 \n", - "classical_graph, classical_graph_adjacency= generate_graph(N, GRAPHMETHOD=1)\n", - "print(classical_graph_adjacency)\n", - "\n", - "pos = nx.circular_layout(classical_graph)\n", - "nx.draw(classical_graph, pos, width=4, with_labels=True, font_weight='bold')\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2. Encoding\n", - "\n", - "This step encodes the classical optimization problem to its quantum version. Using the transformation $z=1\\rightarrow |0\\rangle = \\begin{bmatrix}1 \\\\ 0\\end{bmatrix}$ and $z=-1\\rightarrow |1\\rangle = \\begin{bmatrix}0 \\\\ 1\\end{bmatrix}$, the binary parameter $z$ is encoded as the eigenvalues of the Pauli-Z operator acting on the single qubit, i.e., $z\\rightarrow Z=\\begin{bmatrix} 1 & 0\\\\ 0 & -1\\end{bmatrix}$. It yields that the objective function in the classical optimization problem is transformed to the Hamiltonian\n", - "\n", - "$$H_c= Z_1Z_2+Z_2Z_3+Z_3Z_4+Z_4Z_1.$$\n", - "\n", - "Here, for simplicity $Z_iZ_{j}$ stands for the tensor product $Z_i\\otimes Z_j$ which represents that Pauli-Z operator acts on each qubit $i, j$ and the identity operation is imposed on the rest. And the Max-Cut problem is mapped to the following quantum optimization problem\n", - "\n", - "$$ F=\\min_{|\\psi\\rangle}\\, \\langle \\psi| H_c |\\psi\\rangle$$\n", - "\n", - "where the state vector $|\\psi\\rangle$ describes a $4$-dimensional complex vector which is normalized to $1$, and $\\langle \\psi|$ is its conjugate transpose form. It is equivalent to find the smallest eigenvalue $F$ and the corresponding eigenstate(s) for the matrix $H_c$." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "def H_generator(N, adjacency_matrix):\n", - " \"\"\"\n", - " This function maps the given graph via its adjacency matrix to the corresponding Hamiltiona H_c.\n", - " \n", - " Args:\n", - " N: number of qubits, or number of nodes in the graph, or number of parameters in the classical problem\n", - " adjacency_matrix: the adjacency matrix generated from the graph encoding the classical problem\n", - " Return:\n", - " H_graph: the problem-based Hamiltonian H generated from the graph_adjacency matrix for the given graph\n", - " H_graph_diag: the real part of the problem-based Hamiltonian H_graph\n", - " \"\"\"\n", - "\n", - " sigma_Z = np.array([[1, 0], [0, -1]])\n", - " H = np.zeros([2 ** N, 2 ** N])\n", - " # Generate the Hamiltonian H_c from the graph via its adjacency matrix\n", - " for row in range(N):\n", - " for col in range(N):\n", - " if abs(adjacency_matrix[N - row - 1, N - col - 1]) and row < col:\n", - " identity_1 = np.diag(np.ones([2 ** row]))\n", - " identity_2 = np.diag(np.ones([2 ** (col - row - 1)]))\n", - " identity_3 = np.diag(np.ones([2 ** (N - col - 1)]))\n", - " H += adjacency_matrix[N - row - 1, N - col - 1] * np.kron(\n", - " np.kron(np.kron(np.kron(identity_1, sigma_Z), identity_2), sigma_Z),\n", - " identity_3,\n", - " )\n", - "\n", - " H_graph = H.astype(\"complex64\")\n", - " H_graph_diag = np.diag(H_graph).real\n", - "\n", - " return H_graph, H_graph_diag" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The explicit form of the matrix $H_c$, including its maximal and minimal eigenvalues, can be imported, which later could be used to benchmark the performance of QAOA. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "outputs": [], - "source": [ - "_, H_problem_diag = H_generator(N, classical_graph_adjacency)\n", - "\n", - "H_graph_max = np.max(H_problem_diag)\n", - "H_graph_min = np.min(H_problem_diag)\n", - "\n", - "print(H_problem_diag)\n", - "print('H_max:', H_graph_max, ' H_min:', H_graph_min)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 3. Building\n", - "\n", - "This part is to build up the parameterized quantum circuit of QAOA to perform the computation process. Particularly, the QAOA circuit is constructed by alternatively placing two parameterized modules\n", - "\n", - "$$U_x(\\beta_P)U_c(\\gamma_P)\\dots U_x(\\beta_1)U_c(\\gamma_1)$$\n", - "\n", - "where $P$ is the number of layers to place these two modules. Particularly, one is governed by the encoding matrix $H_c$ via the unitary transformation\n", - "\n", - "$$U_c(\\gamma)=e^{-i \\gamma H_c }$$\n", - "\n", - "where $i$ is the imaginary unit, and $\\gamma\\in [0, \\pi]$ is to be optimized. The other one is \n", - "\n", - "$$U_x(\\beta)=e^{-i \\beta H_x },$$\n", - "\n", - "where $\\beta\\in [0, \\pi]$ and the driving Hamiltonian or matrix $H_x$ adimits an explicit form of\n", - "\n", - "$$H_x =X_1+X_2+X_3+X_4 $$\n", - "\n", - "where the operator $X=\\begin{bmatrix} 0 & 1\\\\ 1& 0\\end{bmatrix}$ defines the Pauli-X operation acting on the qubit.\n", - "\n", - "\n", - "\n", - "Further, each module in the QAOA circuit can be decomposed into a series of operations acting on single qubits and two qubits. In particular, the first has the decomposition of $U_c(\\gamma)=\\prod_{(i, j)}e^{-i \\gamma Z_i\\otimes Z_j }$ while there is $U_x(\\beta)=\\prod_{i}e^{-i \\beta X_i}$ for the second. This is illustrated in the following figure.\n", - "\n", - " ![Circ](https://release-data.cdn.bcebos.com/PIC%2FQAOACir.png) \n", - "\n", - "Then, based on\n", - "\n", - "- initial state of QAOA circuits \n", - "- adjacency matrix describing the graph\n", - "- number of qubits\n", - "- number of layers\n", - "\n", - "we are able to construct the standard QAOA circuit:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "outputs": [], - "source": [ - "def circuit_QAOA(theta, input_state, adjacency_matrix, N, P):\n", - " \"\"\"\n", - " This function constructs the parameterized QAOA circuit which is composed of P layers of two blocks:\n", - " one block is U_theta[layer][0] based on the problem Hamiltonian H which encodes the classical problem,\n", - " and the other is U_theta[layer][1] constructed from the driving Hamiltonian describing the rotation around Pauli X\n", - " acting on each qubit. It finally outputs the final state of the QAOA circuit.\n", - " \n", - " Args:\n", - " theta: parameters to be optimized in the QAOA circuit\n", - " input_state: initial state of the QAOA circuit which usually is the uniform superposition of 2^N bit-strings \n", - " in the computataional basis\n", - " adjacency_matrix: the adjacency matrix of the graph encoding the classical problem\n", - " N: number of qubits, or equivalently, the number of parameters in the original classical problem\n", - " P: number of layers of two blocks in the QAOA circuit\n", - " Returns:\n", - " the final state of the QAOA circuit: cir.state\n", - "\n", - " \"\"\"\n", - "\n", - " cir = UAnsatz(N, input_state=input_state)\n", - " \n", - " # This loop defines the QAOA circuit with P layers of two blocks\n", - " for layer in range(P):\n", - " # The second and third loops construct the first block U_theta[layer][0] which involves two-qubit operation\n", - " # e^{-i\\beta Z_iZ_j} acting on a pair of qubits or nodes i and j in the circuit in each layer.\n", - " for row in range(N):\n", - " for col in range(N):\n", - " if abs(adjacency_matrix[row, col]) and row < col:\n", - " cir.cnot([row + 1, col + 1])\n", - " cir.rz(\n", - " theta=theta[layer][0]\n", - " * adjacency_matrix[row, col],\n", - " which_qubit=col + 1,\n", - " )\n", - " cir.cnot([row + 1, col + 1])\n", - " # This loop constructs the second block U_theta only involving the single-qubit operation e^{-i\\beta X}.\n", - " for i in range(1, N + 1):\n", - " cir.rx(theta=theta[layer][1], which_qubit=i)\n", - "\n", - " return cir.state" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Indeed, the QAOA circuit could be extended to other structures by replacing the modules in the above standard circuit to improve QAOA performance. Here, we provide one candidate extension in which the Pauli-X rotation $R_x(\\beta) $ on each qubit in the driving matrix $H_x$ is replaced by an arbitrary rotation described by $R_z(\\beta_1)R_x(\\beta_2)R_z(\\beta_3)$. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "outputs": [], - "source": [ - "def circuit_extend_QAOA(theta, input_state, adjacency_matrix, N, P):\n", - " \"\"\"\n", - " This is an extended version of the QAOA circuit, and the main difference is U_theta[layer]([1]-[3]) constructed\n", - " from the driving Hamiltonian describing the rotation around an arbitrary direction on each qubit.\n", - "\n", - " Args:\n", - " theta: parameters to be optimized in the QAOA circuit\n", - " input_state: input state of the QAOA circuit which usually is the uniform superposition of 2^N bit-strings\n", - " in the computational basis\n", - " adjacency_matrix: the adjacency matrix of the problem graph encoding the original problem\n", - " N: number of qubits, or equivalently, the number of parameters in the original classical problem\n", - " P: number of layers of two blocks in the QAOA circuit\n", - " Returns:\n", - " final state of the QAOA circuit: cir.state\n", - "\n", - " Note: If this U_extend_theta function is used to construct QAOA circuit, then we need to change the parameter layer\n", - " in the Net function defined below from the Net(shape=[D, 2]) for U_theta function to Net(shape=[D, 4])\n", - " because the number of parameters doubles in each layer in this QAOA circuit.\n", - " \"\"\"\n", - " cir = UAnsatz(N, input_state=input_state)\n", - "\n", - " for layer in range(P):\n", - " for row in range(N):\n", - " for col in range(N):\n", - " if abs(adjacency_matrix[row, col]) and row < col:\n", - " cir.cnot([row + 1, col + 1])\n", - " cir.rz(\n", - " theta=theta[layer][0]\n", - " * adjacency_matrix[row, col],\n", - " which_qubit=col + 1,\n", - " )\n", - " cir.cnot([row + 1, col + 1])\n", - "\n", - " for i in range(1, N + 1):\n", - " cir.rz(theta=theta[layer][1], which_qubit=i)\n", - " cir.rx(theta=theta[layer][2], which_qubit=i)\n", - " cir.rz(theta=theta[layer][3], which_qubit=i)\n", - "\n", - " return cir.state" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Finally, the QAOA circuit outputs\n", - "\n", - "$$|\\psi(\\boldsymbol{\\beta},\\boldsymbol{\\gamma}, P)\\rangle=U_x(\\beta_P)U_c(\\gamma_P)\\dots U_x(\\beta_1)U_c(\\gamma_1)|+\\rangle_1\\dots|+\\rangle_N$$\n", - "\n", - "where each qubit is initialized as the superposition state $|+\\rangle=\\frac{1}{\\sqrt{2}}\\left(|0\\rangle+|1\\rangle\\right)$. And we are able to obtain the loss function for the QAOA circuit\n", - "\n", - "$$F_P=\\min_{\\boldsymbol{\\beta},\\boldsymbol{\\gamma}} \\langle \\psi(\\boldsymbol{\\beta},\\boldsymbol{\\gamma}, P)| H_c|\\psi(\\boldsymbol{\\beta},\\boldsymbol{\\gamma}, P)\\rangle.$$\n", - "\n", - "Additionally, we may tend to fast classical algorithms to update the parameter vectors $\\boldsymbol{\\beta},\\boldsymbol{\\gamma}$ to achieve the optimal value for the above quantum optimization problem. \n", - "\n", - "In Paddle Quantum, this process is accomplished in the Net function:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "outputs": [], - "source": [ - "class Net(fluid.dygraph.Layer):\n", - " \"\"\"\n", - " It constructs the net for QAOA which combines the QAOA circuit with the classical optimizer which sets rules\n", - " to update parameters described by theta introduced in the QAOA circuit.\n", - "\n", - " \"\"\"\n", - " def __init__(\n", - " self,\n", - " shape,\n", - " param_attr=fluid.initializer.Uniform(low=0.0, high=np.pi, seed=1024),\n", - " dtype=\"float32\",\n", - " ):\n", - " super(Net, self).__init__()\n", - "\n", - " self.theta = self.create_parameter(\n", - " shape=shape, attr=param_attr, dtype=dtype, is_bias=False\n", - " )\n", - "\n", - " def forward(self, input_state, adjacency_matrix, out_state_store, N, P, METHOD):\n", - " \"\"\"\n", - " This function constructs the loss function for the QAOA circuit.\n", - "\n", - " Args:\n", - " self: the free parameters to be optimized in the QAOA circuit and defined in the above function\n", - " input_state: initial state of the QAOA circuit which usually is the uniform superposition of 2^N bit-strings\n", - " in the computational basis $|0\\rangle, |1\\rangle$\n", - " adjacency_matrix: the adjacency matrix generated from the graph encoding the classical problem\n", - " out_state_store: the output state of the QAOA circuit\n", - " N: number of qubits\n", - " P: number of layers\n", - " METHOD: which version of QAOA is chosen to solve the problem, i.e., standard version labeled by 1 or\n", - " extended version by 2.\n", - " Returns:\n", - " The loss function for the parameterized QAOA circuit.\n", - " \"\"\"\n", - " \n", - " # Generate the problem_based quantum Hamiltonian H_problem based on the classical problem in paddle\n", - " H, _ = H_generator(N, adjacency_matrix)\n", - " H_problem = fluid.dygraph.to_variable(H)\n", - "\n", - " # The standard QAOA circuit: the function circuit_QAOA is used to construct the circuit, indexed by METHOD 1.\n", - " if METHOD == 1:\n", - " out_state = circuit_QAOA(self.theta, input_state, adjacency_matrix, N, P)\n", - " # The extended QAOA circuit: the function circuit_extend_QAOA is used to construct the net, indexed by METHOD 2.\n", - " elif METHOD == 2:\n", - " out_state = circuit_extend_QAOA(self.theta, input_state, adjacency_matrix, N, P)\n", - " else:\n", - " raise ValueError(\"Wrong method called!\")\n", - "\n", - " out_state_store.append(out_state.numpy())\n", - " loss = pp_matmul(\n", - " pp_matmul(out_state, H_problem),\n", - " transpose(\n", - " fluid.framework.ComplexVariable(out_state.real, -out_state.imag),\n", - " perm=[1, 0],\n", - " ),\n", - " )\n", - "\n", - " return loss.real\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 4. Training\n", - "\n", - "In this part, the QAOA circuit is trained to find the \"optimal\" solution to the optimization problem.\n", - "\n", - "First, let us specify some parameters:\n", - "\n", - "- number of qubits: N\n", - "- number of layes: P\n", - "- iteration steps: ITR\n", - "- learning rate: LR" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "outputs": [], - "source": [ - "N = 4 # number of qubits, or number of nodes in the graph\n", - "P = 4 # number of layers \n", - "ITR = 120 # number of iteration steps\n", - "LR = 0.1 # learning rate" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Then, with the following inputs\n", - "\n", - "- initial state: each qubit is initialized as $\\frac{1}{\\sqrt{2}}\\left(|0\\rangle+|1\\rangle\\right)$\n", - "- Standard QAOA circuit (METHOD = 1) or Extended QAOA (METHOD = 2) \n", - "- Classical optimizer: Adam optimizer\n", - "\n", - "we are able to train the whole net:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "outputs": [], - "source": [ - "def Paddle_QAOA(classical_graph_adjacency, N, P, METHOD, ITR, LR):\n", - " \"\"\"\n", - " This is the core function to run QAOA.\n", - "\n", - " Args:\n", - " classical_graph_adjacency: adjacency matrix to describe the graph which encodes the classical problem\n", - " N: number of qubits (default value N=4)\n", - " P: number of layers of blocks in the QAOA circuit (default value P=4)\n", - " METHOD: which version of the QAOA circuit is used: 1, standard circuit (default); 2, extended circuit\n", - " ITR: number of iteration steps for QAOA (default value ITR=120)\n", - " LR: learning rate for the gradient-based optimization method (default value LR=0.1)\n", - " Returns:\n", - " optimized parameters theta and the bitstrings sampled from the output state with maximal probability\n", - " \"\"\"\n", - " out_state_store = []\n", - " with fluid.dygraph.guard():\n", - " # Preparing the initial state\n", - " _initial_state = np.ones([1, 2 ** N]).astype(\"complex64\") / np.sqrt(2 ** N)\n", - " initial_state = fluid.dygraph.to_variable(_initial_state)\n", - "\n", - " # Construct the net or QAOA circuits based on the standard modules\n", - " if METHOD == 1:\n", - " net = Net(shape=[P, 2])\n", - " # Construct the net or QAOA circuits based on the extended modules\n", - " elif METHOD == 2:\n", - " net = Net(shape=[P, 4])\n", - " else:\n", - " raise ValueError(\"Wrong method called!\")\n", - "\n", - " # Classical optimizer\n", - " opt = fluid.optimizer.AdamOptimizer(learning_rate=LR, parameter_list=net.parameters())\n", - "\n", - " # Gradient descent loop\n", - " summary_iter, summary_loss = [], []\n", - " for itr in range(1, ITR + 1):\n", - " loss = net(\n", - " initial_state, classical_graph_adjacency, out_state_store, N, P, METHOD\n", - " )\n", - " loss.backward()\n", - " opt.minimize(loss)\n", - " net.clear_gradients()\n", - "\n", - " print(\"iter:\", itr, \" loss:\", \"%.4f\" % loss.numpy())\n", - " summary_loss.append(loss[0][0].numpy())\n", - " summary_iter.append(itr)\n", - "\n", - " theta_opt = net.parameters()[0].numpy()\n", - " print(theta_opt)\n", - "\n", - " os.makedirs(\"output\", exist_ok=True)\n", - " np.savez(\"./output/summary_data\", iter=summary_iter, energy=summary_loss)\n", - "\n", - " # Output the measurement probability distribution which is sampled from the output state of optimized QAOA circuit.\n", - " prob_measure = np.zeros([1, 2 ** N]).astype(\"complex\")\n", - "\n", - " rho_out = out_state_store[-1]\n", - " rho_out = np_matmul(np.conjugate(rho_out).T, rho_out).astype(\"complex\")\n", - "\n", - " for index in range(0, 2 ** N):\n", - " comput_basis = np.zeros([1, 2 ** N])\n", - " comput_basis[0][index] = 1\n", - " prob_measure[0][index] = np.real(np_matmul(np_matmul(comput_basis, rho_out), comput_basis.T))\n", - "\n", - " return prob_measure" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "After the completion of training, the QAOA outputs the results, including the optimal parameters $\\boldsymbol{\\beta}^*$ and $\\boldsymbol{\\gamma}^*$. By contrast, its performance can be evaluated with the true value of the optimization problem." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "outputs": [], - "source": [ - "classical_graph, classical_graph_adjacency = generate_graph(N, 1)\n", - "\n", - "prob_measure_dis = Paddle_QAOA(classical_graph_adjacency, N =4, P=4, METHOD=1, ITR=120, LR=0.1)\n", - "\n", - "# Load the data of QAOA\n", - "x1 = np.load('./output/summary_data.npz')\n", - "\n", - "H_min = np.ones([len(x1['iter'])]) * H_graph_min\n", - "\n", - "# Plot it\n", - "\n", - "loss_QAOA, = plt.plot(x1['iter'], x1['energy'], \\\n", - " alpha=0.7, marker='', linestyle=\"--\", linewidth=2, color='m')\n", - "benchmark, = plt.plot(x1['iter'], H_min, alpha=0.7, marker='', linestyle=\":\", linewidth=2, color='b')\n", - "plt.xlabel('Number of iteration')\n", - "plt.ylabel('Performance of the loss function for QAOA')\n", - "\n", - "plt.legend(handles=[\n", - " loss_QAOA,\n", - " benchmark\n", - "],\n", - " labels=[\n", - " r'Loss function $\\left\\langle {\\psi \\left( {\\bf{\\theta }} \\right)} '\n", - " r'\\right|H\\left| {\\psi \\left( {\\bf{\\theta }} \\right)} \\right\\rangle $',\n", - " 'The benchmark result',\n", - " ], loc='best')\n", - "\n", - "# Show the picture\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 5. Decoding \n", - "\n", - "However, the output of optimized QAOA circuits \n", - "\n", - "$$|\\psi(\\boldsymbol{\\beta}^*,\\boldsymbol{\\gamma}^*, P)\\rangle=\\sum_{i=1}^{2^4}\\lambda_i |\\boldsymbol{x}_i\\rangle$$\n", - "\n", - "does not give us the answer to the Max-Cut problem directly. Instead, each bitstring $\\boldsymbol{x}_i=x_1x_2x_3 x_4\\in \\{0, 1\\}^4$ in the state $|\\psi(\\boldsymbol{\\beta}^*,\\boldsymbol{\\gamma}^*, P)$ represents a possible classical solution. Thus, we need to decode the ouptut of QAOA circuits. \n", - "\n", - "The task of decoding quantum answer can be accomplished via measurement. Given the output state, the measurement statistics for each bitstring obeys the probability distribution\n", - "\n", - "$$ p(\\boldsymbol{x})=|\\langle \\boldsymbol{x}|\\psi(\\boldsymbol{\\beta}^*,\\boldsymbol{\\gamma}^*,P)\\rangle|^2.$$\n", - " \n", - "And this distribution is plotted using the following function:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%%\n" - }, - "scrolled": true - }, - "outputs": [], - "source": [ - "prob_measure = prob_measure_dis.flatten()\n", - "\n", - "pos = nx.circular_layout(classical_graph)\n", - "# when N is large, it is not suggested to plot this figure\n", - "name_list = [np.binary_repr(index, width=N) for index in range(0, 2 ** N)]\n", - "plt.bar(\n", - " range(len(np.real(prob_measure))),\n", - " np.real(prob_measure),\n", - " width=0.7,\n", - " tick_label=name_list,\n", - " )\n", - "plt.xticks(rotation=90)\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Again, using the relation $|x \\rangle\\rightarrow z=2x-1$, we are able to obtain a classical answer from the quantum state. Specifically, assume that $z_i=-1$ for $i \\in S$ and $z_j=-1$ for $j \\in S^\\prime$. Thus, one bistring sampled from the output state of QAOA corresponds to one feasible cut to the given graph. And it is highly possible that the higher probability the bitstring is, the more likely it gives rise to the max cut protocol.\n", - "\n", - "The bistring with the largest probability is picked up, and then mapped back to solution to the Max-Cut problem :\n", - "\n", - "- the node set $S$ is in blue color\n", - "- the node set $S^\\prime$ is in red color\n", - "- the dashed lines represent the cut edges " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "outputs": [], - "source": [ - "\n", - "# Find the position of max value in the measure_prob_distribution\n", - "max_prob_pos_list = np.where(prob_measure == max(prob_measure))\n", - "# Store the max value from ndarray to list\n", - "max_prob_list = max_prob_pos_list[0].tolist()\n", - "# Change it to the binary format\n", - "solution_list = [np.binary_repr(index, width=N) for index in max_prob_list]\n", - "print(\"The output bitstring:\", solution_list)\n", - "\n", - "# Draw the graph representing the first bitstring in the solution_list to the MaxCut-like problem\n", - "head_bitstring = solution_list[0]\n", - "\n", - "node_cut = [\"blue\" if head_bitstring[node] == \"1\" else \"red\" for node in classical_graph]\n", - "\n", - "edge_cut = [\n", - " \"solid\" if head_bitstring[node_row] == head_bitstring[node_col] else \"dashed\"\n", - " for node_row, node_col in classical_graph.edges()\n", - " ]\n", - "nx.draw(\n", - " classical_graph,\n", - " pos,\n", - " node_color=node_cut,\n", - " style=edge_cut,\n", - " width=4,\n", - " with_labels=True,\n", - " font_weight=\"bold\",\n", - ")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "is_executing": false - } - }, - "source": [ - "# References\n", - "\n", - "[1] E. Farhi, J. Goldstone, and S. Gutman. 2014. A quantum approximate optimization algorithm. arXiv:1411.4028 " - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.10" - }, - "pycharm": { - "stem_cell": { - "cell_type": "raw", - "metadata": { - "collapsed": false - }, - "source": [] - } - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/tutorial/SSVQE.ipynb b/tutorial/SSVQE.ipynb deleted file mode 100644 index a8feac8996e16c82b62ba5855a259bcfc615111a..0000000000000000000000000000000000000000 --- a/tutorial/SSVQE.ipynb +++ /dev/null @@ -1,335 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 概览\n", - "- 在这个案例中,我们将展示如何通过Paddle Quantum训练量子神经网络来求解量子系统的特征。\n", - "\n", - "- 首先,让我们通过下面几行代码引入必要的library和package。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "import numpy\n", - "from paddle.complex import matmul, transpose\n", - "from paddle import fluid\n", - "from paddle_quantum.circuit import UAnsatz\n", - "from numpy import array, kron" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 背景\n", - "- 量子计算中在近期非常有前途的一个量子算法是变分量子特征求解器(VQE, variational quantum eigensolver (VQE)) [1-3].\n", - "- VQE是量子化学在近期有噪量子设备(NISQ device)上的核心应用之一,其中一个功能比较强大的版本是SSVQE [4],其核心是去求解一个物理系统的哈密顿量的基态和激发态的性质。数学上,可以理解为求解一个厄米矩阵(Hermitian matrix)的特征值及其对应的特征向量。该哈密顿量的特征值组成的集合我们称其为能谱。\n", - "- 接下来我们将通过一个简单的例子学习如何通过训练量子神经网络解决这个问题,即求解出给定哈密顿量的能谱。\n", - "\n", - "## SSVQE分析物理系统的基态和激发态的能量\n", - "- 对于具体需要分析的分子,我们需要输入其几何、电荷构型等多项信息。具体的,通过我们内置的量子化学工具包可以利用fermionic-to-qubit映射的技术来输出目标分子的量子比特哈密顿量表示。\n", - "- 在这里,作为简单的入门案例,我们提供一个简单的2量子位哈密顿量作为例子。 " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "sigma_I = array([[1, 0], [0, 1]])\n", - "sigma_X = array([[0, 1], [1, 0]])\n", - "sigma_Y = array([[0, -1j], [1j, 0]])\n", - "sigma_Z = array([[1, 0], [0, -1]])\n", - "H = 0.4 * kron(sigma_Z, sigma_I) + 0.4 * kron(sigma_I, sigma_Z) + 0.2 * kron(sigma_X, sigma_X)\n", - "hamiltonian = H.astype('complex64')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 搭建量子神经网络(QNN)\n", - "- 在实现SSVQE的过程中,我们首先需要设计1个量子神经网络QNN(也可以理解为参数化量子电路)。这里,我们提供一个预设的2量子位量子电路。\n", - "\n", - "- 我们预设一些该参数化电路的参数,比如宽度为2量子位。\n", - "\n", - "- 初始化其中的变量参数,${\\bf{\\theta }}$代表我们量子神经网络中的参数组成的向量,一共有12个参数。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "N = 2 # 量子神经网络的宽度\n", - "THETA_SIZE = 12 # 量子神经网络中参数的数量\n", - "\n", - "def U_theta(theta, N):\n", - " \"\"\"\n", - " U_theta\n", - " \"\"\"\n", - "\n", - " cir = UAnsatz(N)\n", - " # ============== D1=2 ==============\n", - " cir.ry(theta[0], 2)\n", - " cir.rz(theta[1], 2)\n", - " cir.cnot([2, 1])\n", - " cir.ry(theta[2], 2)\n", - " cir.rz(theta[3], 2)\n", - " cir.cnot([2, 1])\n", - "\n", - " # ============== D2=2 ==============\n", - " cir.ry(theta[4], 1)\n", - " cir.ry(theta[5], 2)\n", - " cir.rz(theta[6], 1)\n", - " cir.rz(theta[7], 2)\n", - " cir.cnot([1, 2])\n", - "\n", - " cir.ry(theta[8], 1)\n", - " cir.ry(theta[9], 2)\n", - " cir.rz(theta[10], 1)\n", - " cir.rz(theta[11], 2)\n", - " cir.cnot([1, 2])\n", - "\n", - " return cir.state" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 配置训练模型 - 损失函数\n", - "- 现在我们已经有了数据和量子神经网络的架构,我们将进一步定义训练参数、模型和损失函数,具体的理论可以参考 [4].\n", - "- 通过作用量子神经网络$U(\\theta)$在1组正交的初始态上,我们将得到输出态$\\left| {\\psi_k \\left( {\\bf{\\theta }} \\right)} \\right\\rangle $。\n", - "- 进一步,在SSVQE模型中的损失函数一般由哈密顿量H与量子态$\\left| {\\psi_k \\left( {\\bf{\\theta }} \\right)} \\right\\rangle$的内积的加权求和给出。\n", - "- 具体的损失函数定义为\n", - "$$4\\left\\langle {\\psi_1 \\left( {\\bf{\\theta }} \\right)} \\right|H\\left| {\\psi_1 \\left( {\\bf{\\theta }} \\right)} \\right\\rangle + 3\\left\\langle {\\psi_2 \\left( {\\bf{\\theta }} \\right)} \\right|H\\left| {\\psi_2 \\left( {\\bf{\\theta }} \\right)} \\right\\rangle + 2\\left\\langle {\\psi_3 \\left( {\\bf{\\theta }} \\right)} \\right|H\\left| {\\psi_3 \\left( {\\bf{\\theta }} \\right)} \\right\\rangle + \\left\\langle {\\psi_4 \\left( {\\bf{\\theta }} \\right)} \\right|H\\left| {\\psi_4 \\left( {\\bf{\\theta }} \\right)} \\right\\rangle.$$" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "SEED = 1\n", - "\n", - "class Net(fluid.dygraph.Layer):\n", - " \"\"\"\n", - " Construct the model net\n", - " \"\"\"\n", - "\n", - " def __init__(self, shape, param_attr=fluid.initializer.Uniform(low=0.0, high=2 * numpy.pi, seed=SEED),\n", - " dtype='float32'):\n", - " super(Net, self).__init__()\n", - "\n", - " self.theta = self.create_parameter(shape=shape, attr=param_attr, dtype=dtype, is_bias=False)\n", - "\n", - " def forward(self, H, N):\n", - " \"\"\"\n", - " Args:\n", - " input_state: The initial state with default |0..>\n", - " H: The target Hamiltonian\n", - " Returns:\n", - " The loss.\n", - " \"\"\"\n", - " out_state = U_theta(self.theta, N)\n", - "\n", - " loss_struct = matmul(matmul(\n", - " transpose(fluid.framework.ComplexVariable(out_state.real, -out_state.imag), perm=[1, 0]), H),\n", - " out_state).real\n", - "\n", - " loss_components = [\n", - " loss_struct[0][0],\n", - " loss_struct[1][1],\n", - " loss_struct[2][2],\n", - " loss_struct[3][3]\n", - " ]\n", - "\n", - " loss = 4 * loss_components[0] + 3 * loss_components[1] + 2 * loss_components[2] + 1 * loss_components[3]\n", - " return loss, loss_components\n", - " " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 配置训练模型 - 模型参数\n", - "在进行量子神经网络的训练之前,我们还需要进行一些训练(超)参数的设置,例如学习速率与迭代次数。\n", - "- 设定学习速率(learning rate)为0.3。\n", - "- 设定迭代次数为50次。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "ITR = 50 # 迭代次数\n", - "\n", - "LR = 0.3 # 学习速率\n", - " " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 进行训练\n", - "\n", - "- 当训练模型的各项参数都设置完成后,我们将数据转化为Paddle动态图中的变量,进而进行量子神经网络的训练。\n", - "- 过程中我们用的是Adam Optimizer,也可以调用Paddle中提供的其他优化器。\n", - "- 我们将训练过程中的每一轮loss可以打印出来。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "with fluid.dygraph.guard():\n", - " # Harmiltonian preparing\n", - " hamiltonian = fluid.dygraph.to_variable(hamiltonian)\n", - "\n", - " # net\n", - " net = Net(shape=[THETA_SIZE])\n", - "\n", - " # optimizer\n", - " opt = fluid.optimizer.AdagradOptimizer(learning_rate=LR, parameter_list=net.parameters())\n", - "\n", - " # gradient descent loop\n", - " for itr in range(1, ITR + 1):\n", - " loss, loss_components = net(hamiltonian, N)\n", - "\n", - " loss.backward()\n", - " opt.minimize(loss)\n", - " net.clear_gradients()\n", - "\n", - " print('iter:', itr, 'loss:', '%.4f' % loss.numpy()[0])\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 测试效果\n", - "我们现在已经完成了量子神经网络的训练,我们将通过与理论值的对比来测试效果。\n", - "- 理论值由numpy中的工具来求解哈密顿量的各个特征值;\n", - "- 我们将训练QNN得到的各个能级的能量和理想情况下的理论值进行比对。\n", - "- 可以看到,SSVQE训练输出的值与理想值高度接近。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "print('The estimated ground state energy is: ', loss_components[0].numpy())\n", - "print('The theoretical ground state energy: ', numpy.linalg.eigh(H)[0][0])\n", - "\n", - "print('The estimated 1st excited state energy is: ', loss_components[1].numpy())\n", - "print('The theoretical 1st excited state energy: ', numpy.linalg.eigh(H)[0][1])\n", - "\n", - "print('The estimated 2nd excited state energy is: ', loss_components[2].numpy())\n", - "print('The theoretical 2nd excited state energy: ', numpy.linalg.eigh(H)[0][2])\n", - "\n", - "print('The estimated 3rd excited state energy is: ', loss_components[3].numpy())\n", - "print('The theoretical 3rd excited state energy: ', numpy.linalg.eigh(H)[0][3])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 参考文献\n", - "[1] A. Peruzzo et al., “A variational eigenvalue solver on a photonic quantum processor,” Nat. Commun., vol. 5, no. 1, p. 4213, Dec. 2014.\n", - "\n", - "[2] S. McArdle, S. Endo, A. Aspuru-Guzik, S. C. Benjamin, and X. Yuan, “Quantum computational chemistry,” Rev. Mod. Phys., vol. 92, no. 1, p. 015003, Mar. 2020.\n", - "\n", - "[3] Y. Cao et al., “Quantum chemistry in the age of quantum computing,” Chem. Rev., vol. 119, no. 19, pp. 10856–10915, 2019.\n", - "\n", - "[4] K. M. Nakanishi, K. Mitarai, and K. Fujii, “Subspace-search variational quantum eigensolver for excited states,” Phys. Rev. Res., vol. 1, no. 3, p. 033062, Oct. 2019.\n", - "\n", - "\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.10" - }, - "pycharm": { - "stem_cell": { - "cell_type": "raw", - "metadata": { - "collapsed": false - }, - "source": [] - } - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/tutorial/SSVQE/SSVQE_Tutorial_CN.ipynb b/tutorial/SSVQE/SSVQE_Tutorial_CN.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..e2a3f1550a2b98a901d2fef6f38ec9a655c5b85b --- /dev/null +++ b/tutorial/SSVQE/SSVQE_Tutorial_CN.ipynb @@ -0,0 +1,374 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 子空间搜索-量子变分特征求解器 (Subspace-Search VQE)\n", + "\n", + " Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 概览\n", + "\n", + "- 在这个案例中,我们将展示如何通过Paddle Quantum训练量子神经网络来求解量子系统的特征。\n", + "\n", + "- 首先,让我们通过下面几行代码引入必要的library和package。" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "import numpy\n", + "\n", + "from paddle.complex import matmul, transpose\n", + "from paddle import fluid\n", + "from paddle_quantum.circuit import UAnsatz\n", + "from paddle_quantum.utils import random_pauli_str_generator, pauli_str_to_matrix, hermitian" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 背景\n", + "\n", + "- 量子计算在近期内备受瞩目的一个应用就是变分量子特征求解器(VQE, variational quantum eigensolver (VQE)) [1-3].\n", + "- VQE是量子化学在近期有噪量子设备(NISQ device)上的核心应用之一,其中一个功能比较强大的版本是SSVQE [4],其核心是去求解一个物理系统的哈密顿量的基态和**激发态**的性质。数学上,可以理解为求解一个厄米矩阵(Hermitian matrix)的特征值及其对应的特征向量。该哈密顿量的特征值组成的集合我们称其为能谱 (Energy spectrum)。\n", + "- 接下来我们将通过一个简单的例子学习如何通过训练量子神经网络解决这个问题,即求解出给定哈密顿量 $H$ 的能谱。\n", + "\n", + "## SSVQE分析物理系统的基态和激发态的能量\n", + "\n", + "- 对于具体需要分析的分子,我们需要其几何构型 (geometry)、电荷 (charge) 以及自旋多重度 (spin multiplicity) 等多项信息来建模获取描述系统的哈密顿量。具体的,通过我们内置的量子化学工具包可以利用 fermionic-to-qubit 映射的技术来输出目标分子的量子比特哈密顿量表示。\n", + "- 作为简单的入门案例,我们在这里提供一个简单的随机2量子比特哈密顿量作为例子。 " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "N = 2 # 量子比特数/量子神经网络的宽度 \n", + "SEED = 14 # 固定随机种子" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Random Hamiltonian in Pauli string format = \n", + " [[-0.370073566586669, 'x0'], [0.5866720906246325, 'x0'], [-0.9723198195208609, 'x0,y1'], [0.7007292863508459, 'y0,y1'], [0.80763905789957, 'z1'], [-0.7395686405536626, 'z0'], [0.8988849291817222, 'y0'], [-0.617070687255681, 'z0,z1'], [0.8230276264234271, 'y1,z0'], [0.11655495624091028, 'y1']]\n" + ] + } + ], + "source": [ + "# 生成用泡利字符串表示的随机哈密顿量\n", + "hamiltonian = random_pauli_str_generator(N, terms=10)\n", + "print(\"Random Hamiltonian in Pauli string format = \\n\", hamiltonian)\n", + "\n", + "# 生成哈密顿量的矩阵信息\n", + "H = pauli_str_to_matrix(hamiltonian, N)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 搭建量子神经网络(QNN)\n", + "\n", + "- 在实现SSVQE的过程中,我们首先需要设计量子神经网络QNN(也即参数化量子电路)。在本教程中,我们提供一个预设的适用于2量子比特的通用量子电路模板。理论上,该模板具有足够强大的表达能力可以表示任意的2-量子比特逻辑运算 [5]。具体的实现方式是需要 3个 $CNOT$ 门加上任意15个单比特旋转门 $\\in \\{R_y, R_z\\}$。\n", + "\n", + "- 初始化其中的变量参数,${\\bf{\\theta }}$ 代表我们量子神经网络中的参数组成的向量,一共有15个参数。" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "THETA_SIZE = 15 # 量子神经网络中参数的数量\n", + "\n", + "def U_theta(theta, N):\n", + " \"\"\"\n", + " Quantum Neural Network\n", + " \"\"\"\n", + " \n", + " # 按照量子比特数量/网络宽度初始化量子神经网络\n", + " cir = UAnsatz(N)\n", + " \n", + " # 调用内置的量子神经网络模板\n", + " cir.universal_2_qubit_gate(theta)\n", + "\n", + " # 返回量子神经网络所模拟的酉矩阵 U\n", + " return cir.U" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 配置训练模型 - 损失函数\n", + "\n", + "- 现在我们已经有了数据和量子神经网络的架构,我们将进一步定义训练参数、模型和损失函数,具体的理论可以参考 [4].\n", + "- 通过作用量子神经网络 $U(\\theta)$ 在1组正交的初始态上 (方便起见,可以取计算基 $\\{|00\\rangle, |01\\rangle, |10\\rangle, |11\\rangle \\}$),我们将分别得到四个输出态 $\\left| {\\psi_k \\left( {\\bf{\\theta }} \\right)} \\right\\rangle (k=0,1,2,3)$。\n", + "- 进一步,在SSVQE模型中的损失函数一般由哈密顿量H与量子态 $\\left| {\\psi_k \\left( {\\bf{\\theta }} \\right)} \\right\\rangle$ 的内积的加权求和给出。\n", + "- 具体的损失函数定义为\n", + "$$\\mathcal{L}(\\boldsymbol{\\theta}) = 4\\left\\langle {\\psi_1 \\left( {\\bf{\\theta }} \\right)} \\right|H\\left| {\\psi_1 \\left( {\\bf{\\theta }} \\right)} \\right\\rangle + 3\\left\\langle {\\psi_2 \\left( {\\bf{\\theta }} \\right)} \\right|H\\left| {\\psi_2 \\left( {\\bf{\\theta }} \\right)} \\right\\rangle + 2\\left\\langle {\\psi_3 \\left( {\\bf{\\theta }} \\right)} \\right|H\\left| {\\psi_3 \\left( {\\bf{\\theta }} \\right)} \\right\\rangle + \\left\\langle {\\psi_4 \\left( {\\bf{\\theta }} \\right)} \\right|H\\left| {\\psi_4 \\left( {\\bf{\\theta }} \\right)} \\right\\rangle.$$" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "class Net(fluid.dygraph.Layer):\n", + " \"\"\"\n", + " Construct the model net\n", + " \"\"\"\n", + "\n", + " def __init__(self, shape, param_attr=fluid.initializer.Uniform(low=0.0, high=2 * numpy.pi, seed=SEED),\n", + " dtype='float64'):\n", + " super(Net, self).__init__()\n", + " \n", + " # 初始化 theta 参数列表,并用 [0, 2*pi] 的均匀分布来填充初始值\n", + " self.theta = self.create_parameter(shape=shape, attr=param_attr, dtype=dtype, is_bias=False)\n", + " \n", + " # 定义损失函数和前向传播机制\n", + " def forward(self, H, N):\n", + " \n", + " # 施加量子神经网络\n", + " U = U_theta(self.theta, N)\n", + " \n", + " # 计算损失函数\n", + " loss_struct = matmul(matmul(hermitian(U), H), U).real\n", + "\n", + " # 输入计算基去计算每个子期望值,相当于取 U^dagger*H*U 的对角元 \n", + " loss_components = [\n", + " loss_struct[0][0],\n", + " loss_struct[1][1],\n", + " loss_struct[2][2],\n", + " loss_struct[3][3]\n", + " ]\n", + " \n", + " # 最终加权求和后的损失函数\n", + " loss = 4 * loss_components[0] + 3 * loss_components[1] + 2 * loss_components[2] + 1 * loss_components[3]\n", + " \n", + " return loss, loss_components" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 配置训练模型 - 模型参数\n", + "\n", + "在进行量子神经网络的训练之前,我们还需要进行一些训练的超参数设置,主要是学习速率 (LR, learning rate)、迭代次数(ITR, iteration)和量子神经网络计算模块的深度 (D, Depth)。这里我们设定学习速率为0.3, 迭代次数为50次。读者不妨自行调整来直观感受下超参数调整对训练效果的影响。" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "ITR = 50 # 设置训练的总迭代次数\n", + "LR = 0.3 # 设置学习速率" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 进行训练\n", + "\n", + "- 当训练模型的各项参数都设置完成后,我们将数据转化为Paddle动态图中的变量,进而进行量子神经网络的训练。\n", + "- 过程中我们用的是Adam Optimizer,也可以调用Paddle中提供的其他优化器。\n", + "- 我们将训练过程中的每一轮loss打印出来。" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "iter: 10 loss: -8.5409\n", + "iter: 20 loss: -8.8731\n", + "iter: 30 loss: -9.1038\n", + "iter: 40 loss: -9.2157\n", + "iter: 50 loss: -9.2681\n" + ] + } + ], + "source": [ + "# 初始化paddle动态图机制\n", + "with fluid.dygraph.guard():\n", + " \n", + " # 我们需要将 Numpy array 转换成 Paddle 动态图模式中支持的 variable\n", + " hamiltonian = fluid.dygraph.to_variable(H)\n", + "\n", + " # 确定网络的参数维度\n", + " net = Net(shape=[THETA_SIZE])\n", + "\n", + " # 一般来说,我们利用Adam优化器来获得相对好的收敛,当然你可以改成SGD或者是RMS prop.\n", + " opt = fluid.optimizer.AdagradOptimizer(learning_rate=LR, parameter_list=net.parameters())\n", + "\n", + " # 优化循环\n", + " for itr in range(1, ITR + 1):\n", + " \n", + " # 前向传播计算损失函数并返回估计的能谱\n", + " loss, loss_components = net(hamiltonian, N)\n", + " \n", + " # 在动态图机制下,反向传播极小化损失函数\n", + " loss.backward()\n", + " opt.minimize(loss)\n", + " net.clear_gradients()\n", + " \n", + " # 打印训练结果\n", + " if itr % 10 == 0:\n", + " print('iter:', itr, 'loss:', '%.4f' % loss.numpy()[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 测试效果\n", + "我们现在已经完成了量子神经网络的训练,我们将通过与理论值的对比来测试效果。\n", + "- 理论值由numpy中的工具来求解哈密顿量的各个特征值;\n", + "- 我们将训练QNN得到的各个能级的能量和理想情况下的理论值进行比对。\n", + "- 可以看到,SSVQE训练输出的值与理想值高度接近。" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The estimated ground state energy is: [-2.33059408]\n", + "The theoretical ground state energy: -2.3654429645786506\n", + "The estimated 1st excited state energy is: [-1.69552621]\n", + "The theoretical 1st excited state energy: -1.6867829339244156\n", + "The estimated 2nd excited state energy is: [1.11478154]\n", + "The theoretical 2nd excited state energy: 1.1321233803877833\n", + "The estimated 3rd excited state energy is: [2.91133876]\n", + "The theoretical 3rd excited state energy: 2.920102518115284\n" + ] + } + ], + "source": [ + "print('The estimated ground state energy is: ', loss_components[0].numpy())\n", + "print('The theoretical ground state energy: ', numpy.linalg.eigh(H)[0][0])\n", + "\n", + "print('The estimated 1st excited state energy is: ', loss_components[1].numpy())\n", + "print('The theoretical 1st excited state energy: ', numpy.linalg.eigh(H)[0][1])\n", + "\n", + "print('The estimated 2nd excited state energy is: ', loss_components[2].numpy())\n", + "print('The theoretical 2nd excited state energy: ', numpy.linalg.eigh(H)[0][2])\n", + "\n", + "print('The estimated 3rd excited state energy is: ', loss_components[3].numpy())\n", + "print('The theoretical 3rd excited state energy: ', numpy.linalg.eigh(H)[0][3])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 参考文献\n", + "\n", + "[1] [Peruzzo, A. et al. A variational eigenvalue solver on a photonic quantum processor. Nat. Commun. 5, 4213 (2014).](https://www.nature.com/articles/ncomms5213)\n", + "\n", + "[2] [McArdle, S., Endo, S., Aspuru-Guzik, A., Benjamin, S. C. & Yuan, X. Quantum computational chemistry. Rev. Mod. Phys. 92, 015003 (2020).](https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.92.015003)\n", + "\n", + "[3] [Cao, Y. et al. Quantum chemistry in the age of quantum computing. Chem. Rev. 119, 10856–10915 (2019).](https://pubs.acs.org/doi/abs/10.1021/acs.chemrev.8b00803)\n", + "\n", + "[4] [Nakanishi, K. M., Mitarai, K. & Fujii, K. Subspace-search variational quantum eigensolver for excited states. Phys. Rev. Res. 1, 033062 (2019).](https://journals.aps.org/prresearch/pdf/10.1103/PhysRevResearch.1.033062)\n", + "\n", + "[5] [Vatan, F. & Williams, C. Optimal quantum circuits for general two-qubit gates. Phys. Rev. A 69, 032315 (2004).](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.69.032315)\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.10" + }, + "pycharm": { + "stem_cell": { + "cell_type": "raw", + "metadata": { + "collapsed": false + }, + "source": [] + } + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/tutorial/SSVQE/SSVQE_Tutorial_CN.pdf b/tutorial/SSVQE/SSVQE_Tutorial_CN.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8a3c0feedcea4cba7b2b1edacd667df51681e9b0 Binary files /dev/null and b/tutorial/SSVQE/SSVQE_Tutorial_CN.pdf differ diff --git a/tutorial/VQE.ipynb b/tutorial/VQE.ipynb deleted file mode 100644 index 9acffb26c5a9fb9ff5ecacb64d9644540b234000..0000000000000000000000000000000000000000 --- a/tutorial/VQE.ipynb +++ /dev/null @@ -1,344 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 概览\n", - "- 在这个案例中,我们将展示如何通过Paddle Quantum训练量子神经网络来求解量子系统的特征。\n", - "\n", - "- 首先,让我们通过下面几行代码引入必要的library和package。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "import os\n", - "from numpy import concatenate\n", - "from numpy import pi as PI\n", - "from numpy import savez, zeros\n", - "from paddle import fluid\n", - "from paddle.complex import matmul, transpose\n", - "from paddle_quantum.circuit import UAnsatz\n", - "import matplotlib.pyplot as plt\n", - "import numpy\n", - "from paddle_quantum.VQE.chemistrysub import H2_generator" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 背景\n", - "- 量子计算中在近期非常有前途的一个量子算法是变分量子特征求解器(VQE, variational quantum eigensolver) [1-3].\n", - "- VQE是量子化学在近期有噪量子设备(NISQ device)上的核心应用之一。其核心是去求解一个物理系统的哈密顿量的基态及其对应的能量。数学上,可以理解为求解一个厄米矩阵(Hermitian matrix)的最小特征值及其对应的特征向量。\n", - "- 接下来我们将通过一个简单的例子学习如何通过训练量子神经网络解决这个问题,我们的目标是通过训练量子神经网络去找到量子态 $\\left| \\phi \\right\\rangle $ (可以理解为一个归一化的复数向量), 使得 $$\\left\\langle \\phi \\right|H\\left| \\phi \\right\\rangle =\\lambda_{\\min}(H)$$, 其中$\\left\\langle \\phi \\right|$是$\\left| \\phi \\right\\rangle$的共轭转置,$\\lambda_{\\min}(H)$是矩阵$H$的最小特征值。\n", - "\n", - "## VQE分析氢分子的性质\n", - "- 对于具体需要分析的分子,我们需要输入其几何、电荷构型等多项信息。具体的,通过我们内置的量子化学工具包可以利用fermionic-to-qubit映射的技术来输出目标分子的量子比特哈密顿量表示。\n", - "- 在这里,作为简单的入门案例,我们提供已经映射好的的氢分子的哈密顿量。 " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "Hamiltonian, _, N = H2_generator()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 搭建量子神经网络(QNN)\n", - "- 在实现VQE的过程中,我们首先需要设计1个量子神经网络QNN(也可以理解为参数化量子电路)。这里,我们提供一个简单的d层的4量子位的量子电路如下:\n", - "\n", - "![Utheta.jpg](https://release-data.cdn.bcebos.com/PIC%2FUtheta.jpg)\n", - "\n", - "- 我们预设一些该参数化电路的参数,比如宽度为4量子位。\n", - "\n", - "- 初始化其中的变量参数,${\\bf{\\theta }}$代表我们量子神经网络中的参数组成的向量。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "def U_theta(theta, input_state, N, D):\n", - " \"\"\"\n", - " Circuit\n", - " \"\"\"\n", - "\n", - " cir = UAnsatz(N, input_state=input_state)\n", - " for i in range(N):\n", - " cir.ry(theta=theta[0][1][i], which_qubit=i + 1)\n", - " \n", - " for repeat in range(D):\n", - " for i in range(1, N):\n", - " cir.cnot(control=[i, i + 1])\n", - " \n", - " for i in range(N):\n", - " cir.ry(theta=theta[repeat][0][i], which_qubit=i + 1)\n", - " return cir.state" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 配置训练模型 - 损失函数\n", - "- 现在我们已经有了数据和量子神经网络的架构,我们将进一步定义训练参数、模型和损失函数.\n", - "- 设置训练模型中的的损失函数。通过作用量子神经网络$U(\\theta)$在初始态上,我们将得到输出态$\\left| {\\psi \\left( {\\bf{\\theta }} \\right)} \\right\\rangle $。进一步,在VQE模型中的损失函数一般由哈密顿量H与量子态$\\left| {\\psi \\left( {\\bf{\\theta }} \\right)} \\right\\rangle$的内积给出,具体可定义为\n", - "$$\\left\\langle {\\psi \\left( {\\bf{\\theta }} \\right)} \\right|H\\left| {\\psi \\left( {\\bf{\\theta }} \\right)} \\right\\rangle.$$" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "class StateNet(fluid.dygraph.Layer):\n", - " \"\"\"\n", - " Construct the model net\n", - " \"\"\"\n", - "\n", - " def __init__(\n", - " self,\n", - " shape,\n", - " param_attr=fluid.initializer.Uniform(low=0.0, high=2 * PI),\n", - " dtype=\"float32\",\n", - " ):\n", - " super(StateNet, self).__init__()\n", - " self.theta = self.create_parameter(\n", - " shape=shape, attr=param_attr, dtype=dtype, is_bias=False\n", - " )\n", - "\n", - " def forward(self, input_state, H, N, D):\n", - " \"\"\"\n", - " :param input_state: The initial state with default |0..>, 'mat'\n", - " :param H: The target Hamiltonian, 'mat'\n", - " :return: The loss, 'float'\n", - " \"\"\"\n", - "\n", - " out_state = U_theta(self.theta, input_state, N, D)\n", - " loss = matmul(\n", - " matmul(out_state, H),\n", - " transpose(\n", - " fluid.framework.ComplexVariable(out_state.real, -out_state.imag),\n", - " perm=[1, 0],\n", - " ),\n", - " )\n", - "\n", - " return loss.real\n", - " " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 配置训练模型 - 模型参数\n", - "在进行量子神经网络的训练之前,我们还需要进行一些训练(超)参数的设置,例如学习速率与迭代次数。\n", - "- 设定模型的初始态为零态。\n", - "- 设定量子神经网络的深度为2层。\n", - "- 设定学习速率(learning rate)为0.2, 迭代次数为80次。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "d = 2\n", - "ITR = 80\n", - "LR = 0.3" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 进行训练\n", - "\n", - "- 当训练模型的各项参数都设置完成后,我们将数据转化为Paddle动态图中的变量,进而进行量子神经网络的训练。\n", - "- 过程中我们用的是Adam Optimizer,也可以调用Paddle中提供的其他优化器。\n", - "- 我们将训练过程中的结果存储在summary_data文件中。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "with fluid.dygraph.guard():\n", - " # initial state preparing\n", - " _initial_state_np = concatenate(\n", - " ([[1.0]], zeros([1, 2 ** N - 1])), axis=1\n", - " ).astype(\"complex64\")\n", - " initial_state = fluid.dygraph.to_variable(_initial_state_np)\n", - "\n", - " # Store H\n", - " H = fluid.dygraph.to_variable(Hamiltonian)\n", - "\n", - " # net\n", - " net = StateNet(shape=[d + 1, 3, N])\n", - "\n", - " # optimizer\n", - " opt = fluid.optimizer.AdamOptimizer(\n", - " learning_rate=LR, parameter_list=net.parameters()\n", - " )\n", - "\n", - " # gradient descent loop\n", - " summary_iter, summary_loss = [], []\n", - " for itr in range(1, ITR + 1):\n", - " # forward calc, loss\n", - " loss = net(initial_state, H, N, d)\n", - "\n", - " # backward calculation for gradient value\n", - " loss.backward()\n", - " # using gradients to update the variable theta\n", - " opt.minimize(loss)\n", - " # clear gradients\n", - " net.clear_gradients()\n", - "\n", - " summary_loss.append(loss[0][0].numpy())\n", - " summary_iter.append(itr)\n", - "\n", - " print(\"iter:\", itr, \"loss:\", \"%.4f\" % loss.numpy())\n", - " print(\"iter:\", itr, \"Ground state energy:\", \"%.4f Ha\" % loss.numpy())\n", - " # print('theta:', net.parameters()[0].numpy())\n", - "\n", - " os.makedirs(\"output\", exist_ok=True)\n", - " savez(\"./output/summary_data\", iter=summary_iter, energy=summary_loss)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 测试效果\n", - "我们现在已经完成了量子神经网络的训练,得到的基态能量的估计值大致为-1.136 Ha (注: Ha为[哈特里能量](https://baike.baidu.com/item/%E5%93%88%E7%89%B9%E9%87%8C%E8%83%BD%E9%87%8F/13777793?fr=aladdin),是原子单位制中的能量单位),我们将通过与理论值的对比来测试效果。\n", - "- 训练后得到的QNN作用在初始零态上就是VQE算法的输出态,最后更新的损失函数则为其对应的能量。\n", - "- 接下来我们将训练QNN得到的基态能量和理想情况下的理论值。\n", - "- 我们可以先求解理论值,即哈密顿量$H$的最小特征值。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": { - "is_executing": false, - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "x1 = numpy.load('./output/summary_data.npz')\n", - "\n", - "eig_val, eig_state = numpy.linalg.eig(Hamiltonian)\n", - "min_eig_H = numpy.min(eig_val)\n", - "min_loss = numpy.ones([len(x1['iter'])]) * min_eig_H\n", - "\n", - "plt.figure(1)\n", - "func1, = plt.plot(x1['iter'], x1['energy'], alpha=0.7, marker='', linestyle=\"--\", color='m')\n", - "func_min, = plt.plot(x1['iter'], min_loss, alpha=0.7, marker='', linestyle=\":\", color='b')\n", - "plt.xlabel('Number of iteration')\n", - "plt.ylabel('Energy (Ha)')\n", - "\n", - "plt.legend(handles=[\n", - " func1,\n", - " func_min\n", - "],\n", - " labels=[\n", - " r'$\\left\\langle {\\psi \\left( {\\bf{\\theta }} \\right)} '\n", - " r'\\right|H\\left| {\\psi \\left( {\\bf{\\theta }} \\right)} \\right\\rangle $',\n", - " 'Minimum energy',\n", - " ], loc='best')\n", - "\n", - "# output the picture\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "\n", - "## 参考文献\n", - "[1] A. Peruzzo et al., “A variational eigenvalue solver on a photonic quantum processor,” Nat. Commun., vol. 5, no. 1, p. 4213, Dec. 2014.\n", - "\n", - "[2] S. McArdle, S. Endo, A. Aspuru-Guzik, S. C. Benjamin, and X. Yuan, “Quantum computational chemistry,” Rev. Mod. Phys., vol. 92, no. 1, p. 015003, Mar. 2020.\n", - "\n", - "[3] Y. Cao et al., “Quantum chemistry in the age of quantum computing,” Chem. Rev., vol. 119, no. 19, pp. 10856–10915, 2019.\n", - "\n", - "\n", - "\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.10" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/tutorial/VQE/VQE_Tutorial_CN.ipynb b/tutorial/VQE/VQE_Tutorial_CN.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..d687de624f414f9817ce49796fedb8e9ba36a9ee --- /dev/null +++ b/tutorial/VQE/VQE_Tutorial_CN.ipynb @@ -0,0 +1,510 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 变分量子特征求解器(VQE)\n", + "\n", + " Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 概览\n", + "\n", + "- 在这个案例中,我们将展示如何通过Paddle Quantum训练量子神经网络来求解量子系统的能量基态。\n", + "\n", + "- 首先,让我们通过下面几行代码引入必要的library和package。" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "import os\n", + "import numpy\n", + "import platform\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from numpy import concatenate\n", + "from numpy import pi as PI\n", + "from numpy import savez, zeros\n", + "from IPython.display import clear_output\n", + "from paddle import fluid\n", + "from paddle.complex import matmul, transpose\n", + "from paddle_quantum.circuit import UAnsatz\n", + "from paddle_quantum.utils import hermitian, pauli_str_to_matrix\n", + "from paddle_quantum.VQE.chemistrysub import H2_generator" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 背景\n", + "\n", + "- 量子计算在近期非常有前景的一个应用就是变分量子特征求解器 (VQE, Variational Quantum Eigensolver) [1-3]。\n", + "- VQE作为量子化学在短期内含噪量子设备(NISQ device)上的核心应用之一, 其核心任务是求解一个量子尺度上物理系统的哈密顿量 $H$ 的基态能量及其对应的量子态。数学上,可以理解为求解一个厄米矩阵 (Hermitian matrix) 的最小特征值及其对应的特征向量。\n", + "- 接下来我们将通过一个简单的例子学习如何通过训练量子神经网络解决这个问题,我们的目标是通过训练量子神经网络去找到量子态 $\\left| \\phi \\right\\rangle $ (可以理解为一个归一化的复数向量), 使得 $$\\left\\langle \\phi \\right|H\\left| \\phi \\right\\rangle =\\lambda_{\\min}(H)$$ 其中 $\\left\\langle \\phi \\right|$ 是 $\\left| \\phi \\right\\rangle$ 的共轭转置,$\\lambda_{\\min}(H)$是矩阵$H$的最小特征值。\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## VQE分析氢分子的性质\n", + "\n", + "- 对于具体需要分析的分子,我们需要其几何构型 (geometry)、电荷 (charge) 以及自旋多重度 (spin multiplicity) 等多项信息来建模获取描述系统的哈密顿量。具体的,通过我们内置的量子化学工具包可以利用 fermionic-to-qubit 映射的技术来输出目标分子的量子比特哈密顿量表示。\n", + "- 在这里,作为简单的入门案例,我们提供已经映射好的的氢分子的哈密顿量。 " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "Hamiltonian, N = H2_generator()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "面向更高级的用户,我们这里提供一个简单的生成氢分子 (H2)哈密顿量的教程。先安装以下两个package (**仅Mac/Linux用户可使用,Windows用户暂时不支持**):\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "!pip install openfermion\n", + "clear_output()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "!pip install openfermionpyscf\n", + "clear_output()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The generated h2 Hamiltonian is \n", + " (-0.04207897647782277+0j) [] +\n", + "(-0.04475014401535163+0j) [X0 X1 Y2 Y3] +\n", + "(0.04475014401535163+0j) [X0 Y1 Y2 X3] +\n", + "(0.04475014401535163+0j) [Y0 X1 X2 Y3] +\n", + "(-0.04475014401535163+0j) [Y0 Y1 X2 X3] +\n", + "(0.17771287465139946+0j) [Z0] +\n", + "(0.17059738328801055+0j) [Z0 Z1] +\n", + "(0.12293305056183797+0j) [Z0 Z2] +\n", + "(0.1676831945771896+0j) [Z0 Z3] +\n", + "(0.1777128746513994+0j) [Z1] +\n", + "(0.1676831945771896+0j) [Z1 Z2] +\n", + "(0.12293305056183797+0j) [Z1 Z3] +\n", + "(-0.2427428051314046+0j) [Z2] +\n", + "(0.1762764080431959+0j) [Z2 Z3] +\n", + "(-0.24274280513140462+0j) [Z3]\n" + ] + } + ], + "source": [ + "# 操作系统信息\n", + "sysStr = platform.system()\n", + "\n", + "# 判断操作系统\n", + "if sysStr in ('Linux', 'Darwin'):\n", + "\n", + " import openfermion\n", + " import openfermionpyscf\n", + "\n", + " # 请检查是否正确下载了 h2 的几何构型文件\n", + " geo = 'h2.xyz'\n", + " charge = 0\n", + " multiplicity = 1\n", + "\n", + " # 生成哈密顿量\n", + " mol = openfermion.hamiltonians.MolecularData(geo, 'sto-3g', multiplicity, charge)\n", + " openfermionpyscf.run_pyscf(mol)\n", + " terms_molecular_hamiltonian = mol.get_molecular_hamiltonian()\n", + " fermionic_hamiltonian = openfermion.transforms.get_fermion_operator(terms_molecular_hamiltonian)\n", + " qubit_op = openfermion.transforms.jordan_wigner(fermionic_hamiltonian)\n", + "\n", + " # 打印结果\n", + " print(\"The generated h2 Hamiltonian is \\n\", qubit_op)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "除了氢分子 (H2) 之外, 我们也提供了氟化氢 (HF) 分子的几何构型文件 `hf.xyz`。如果你需要测试更多分子的几何构型,请移步至这个[数据库](http://smart.sns.it/molecules/index.html)。此外,我们还需要把这些本质上由泡利算符表示的哈密顿量转化成 Paddle quantum 支持的数据格式,这里我们提供这个接口。" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "def Hamiltonian_str_convert(qubit_op):\n", + " '''\n", + " 将上述提供的哈密顿量信息转为量桨支持的泡利字符串\n", + " H = [[1.0, \"z0,x1\"], [-1.0, \"y0,z1\"], ...]\n", + " '''\n", + " info_dic = qubit_op.terms\n", + " \n", + " def process_tuple(tup):\n", + " if len(tup) == 0:\n", + " return 'i0'\n", + " else:\n", + " res = ''\n", + " for ele in tup:\n", + " res += ele[1].lower()\n", + " res += str(ele[0])\n", + " res += ','\n", + " return res[:-1]\n", + " H_info = []\n", + " \n", + " for key, value in qubit_op.terms.items():\n", + " H_info.append([value.real, process_tuple(key)])\n", + " \n", + " return H_info\n", + "\n", + "if sysStr in ('Linux', 'Darwin'):\n", + " Hamiltonian = Hamiltonian_str_convert(qubit_op)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 搭建量子神经网络(QNN)\n", + "\n", + "- 在实现VQE的过程中,我们首先需要设计量子神经网络QNN(也可以理解为参数化量子电路)。这里,我们提供一个预设好的的深度为D层的4量子比特的量子电路模板,图中的虚线框内为一层:\n", + "\n", + "![Utheta.jpg](https://release-data.cdn.bcebos.com/PIC%2FUtheta.jpg)\n", + "\n", + "- 我们预设一些该参数化电路的参数,比如宽度为 $N = 4$ 量子位。\n", + "\n", + "- 初始化其中的变量参数,${\\bf{\\theta }}$ 代表我们量子神经网络中的参数组成的向量。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "接下来我们根据上图中的电路设计,通过 Paddle Quantum 的 `UAnsatz` 函数和内置的 `real_entangled_layer(theta, D)` 电路模板来高效搭建量子神经网络。 " + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "def U_theta(theta, Hamiltonian, N, D):\n", + " \"\"\"\n", + " Quantum Neural Network\n", + " \"\"\"\n", + " \n", + " # 按照量子比特数量/网络宽度初始化量子神经网络\n", + " cir = UAnsatz(N)\n", + " \n", + " # 内置的 {R_y + CNOT} 电路模板\n", + " cir.real_entangled_layer(theta[:D], D)\n", + " \n", + " # 铺上最后一列 R_y 旋转门\n", + " for i in range(N):\n", + " cir.ry(theta=theta[D][i][0], which_qubit=i)\n", + " \n", + " # 量子神经网络作用在默认的初始态 |0000>上\n", + " cir.run_state_vector()\n", + " \n", + " # 计算给定哈密顿量的期望值\n", + " expectation_val = cir.expecval(Hamiltonian)\n", + "\n", + " return expectation_val" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 配置训练模型 - 损失函数\n", + "- 现在我们已经有了数据和量子神经网络的架构,我们将进一步定义训练参数、模型和损失函数.\n", + "- 设置训练模型中的的损失函数。通过作用量子神经网络 $U(\\theta)$ 在初始态 $|0..0\\rangle$ 上,我们将得到输出态 $\\left| {\\psi \\left( {\\bf{\\theta }} \\right)} \\right\\rangle $。进一步,在VQE模型中的损失函数一般由量子态 $\\left| {\\psi \\left( {\\bf{\\theta }} \\right)} \\right\\rangle$ 关于哈密顿量 $H$ 的期望值 (能量期望值 expectation value) 给出,具体可定义为\n", + "\n", + "$$\n", + "\\mathcal{L}(\\boldsymbol \\theta) = \\left\\langle {\\psi \\left( {\\bf{\\theta }} \\right)} \\right|H\\left| {\\psi \\left( {\\bf{\\theta }} \\right)} \\right\\rangle\n", + "$$" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "class StateNet(fluid.dygraph.Layer):\n", + " \"\"\"\n", + " Construct the model net\n", + " \"\"\"\n", + "\n", + " def __init__(self, shape, param_attr=fluid.initializer.Uniform(low=0.0, high=2 * PI), dtype=\"float64\"):\n", + " super(StateNet, self).__init__()\n", + " \n", + " # 初始化 theta 参数列表,并用 [0, 2*pi] 的均匀分布来填充初始值\n", + " self.theta = self.create_parameter(shape=shape, attr=param_attr, dtype=dtype, is_bias=False)\n", + " \n", + " # 定义损失函数和前向传播机制\n", + " def forward(self, N, D):\n", + " \n", + " # 计算损失函数/期望值\n", + " loss = U_theta(self.theta, Hamiltonian, N, D)\n", + "\n", + " return loss" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 配置训练模型 - 模型参数\n", + "\n", + "在进行量子神经网络的训练之前,我们还需要进行一些训练的超参数设置,主要是学习速率 (LR, learning rate)、迭代次数(ITR, iteration)和量子神经网络计算模块的深度 (D, Depth)。这里我们设定学习速率为0.5, 迭代次数为50次。读者不妨自行调整来直观感受下超参数调整对训练效果的影响。" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "ITR = 80 # 设置训练的总迭代次数\n", + "LR = 0.2 # 设置学习速率\n", + "D = 2 # 设置量子神经网络中重复计算模块的深度 Depth" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 进行训练\n", + "\n", + "- 当训练模型的各项参数都设置完成后,我们将数据转化为Paddle动态图中的变量,进而进行量子神经网络的训练。\n", + "- 过程中我们用的是Adam Optimizer,也可以调用Paddle中提供的其他优化器。\n", + "- 我们将训练过程中的结果存储在summary_data文件中。" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "iter: 20 loss: -1.0024\n", + "iter: 20 Ground state energy: -1.0024 Ha\n", + "iter: 40 loss: -1.0935\n", + "iter: 40 Ground state energy: -1.0935 Ha\n", + "iter: 60 loss: -1.1153\n", + "iter: 60 Ground state energy: -1.1153 Ha\n", + "iter: 80 loss: -1.1174\n", + "iter: 80 Ground state energy: -1.1174 Ha\n" + ] + } + ], + "source": [ + "# 初始化paddle动态图机制\n", + "with fluid.dygraph.guard():\n", + "\n", + "\n", + " # 确定网络的参数维度\n", + " net = StateNet(shape=[D + 1, N, 1])\n", + "\n", + " # 一般来说,我们利用Adam优化器来获得相对好的收敛,当然你可以改成SGD或者是RMS prop.\n", + " opt = fluid.optimizer.AdamOptimizer(learning_rate=LR, parameter_list=net.parameters())\n", + "\n", + " # 记录优化结果\n", + " summary_iter, summary_loss = [], []\n", + " \n", + " # 优化循环\n", + " for itr in range(1, ITR + 1):\n", + " \n", + " # 前向传播计算损失函数\n", + " loss = net(N, D)\n", + "\n", + " # 在动态图机制下,反向传播极小化损失函数\n", + " loss.backward()\n", + " opt.minimize(loss)\n", + " net.clear_gradients()\n", + " \n", + " # 更新优化结果\n", + " summary_loss.append(loss.numpy())\n", + " summary_iter.append(itr)\n", + " \n", + " # 打印结果\n", + " if itr % 20 == 0:\n", + " print(\"iter:\", itr, \"loss:\", \"%.4f\" % loss.numpy())\n", + " print(\"iter:\", itr, \"Ground state energy:\", \"%.4f Ha\" % loss.numpy())\n", + "\n", + " # 储存训练结果到 output 文件夹\n", + " os.makedirs(\"output\", exist_ok=True)\n", + " savez(\"./output/summary_data\", iter=summary_iter, energy=summary_loss)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 测试效果\n", + "我们现在已经完成了量子神经网络的训练,得到的基态能量的估计值大致为-1.136 Ha (注: Ha为[哈特里能量](https://baike.baidu.com/item/%E5%93%88%E7%89%B9%E9%87%8C%E8%83%BD%E9%87%8F/13777793?fr=aladdin),是原子单位制中的能量单位),我们将通过与理论值的对比来测试效果。\n", + "- 训练后得到的QNN作用在初始零态上就是VQE算法的输出态,最后更新的损失函数则为其对应的能量。\n", + "- 接下来我们将训练QNN得到的基态能量和理想情况下的理论值。\n", + "- 我们可以先求解理论值,即哈密顿量$H$的最小特征值。" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "pycharm": { + "is_executing": false, + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYoAAAEHCAYAAACwUAEWAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/d3fzzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAvx0lEQVR4nO3deXxU1f3/8deHQEA22RFFTCwKsoSwKq0r4tK6odWvWluXuta9tv2Kj/rol9afda91q1Vbt1aL1tYiSl1AUBQXgmLYRFoIEEW2sAjKFj6/P86dMAmTISyTO0nez8fjPuaeO3fu/WQymU/uOfecY+6OiIhIdRrFHYCIiGQ3JQoREUlLiUJERNJSohARkbSUKEREJC0lChERSatxnCc3sxOB+4Ac4E/ufnuV55sCTwMDgZXA2e5eku6YHTp08Ly8vIzEKyJSX02bNm2Fu3dM9VxsicLMcoCHgOOAUmCqmb3k7rOTdrsYWOXu3c3sHOAO4Ox0x83Ly6OoqChTYYuI1EtmtrC65+KsehoC/Mfd57v7JmA0cFqVfU4DnorWXwCONTOrxRhFRBq8OBPFfsDipHJptC3lPu6+BVgDtK+V6EREBKgnjdlmdpmZFZlZ0fLly+MOR0SkXomzMftzYP+kctdoW6p9Ss2sMbA3oVG7End/FHgUYNCgQRq8ShqkzZs3U1payoYNG+IORbJYs2bN6Nq1K02aNKnxa+JMFFOBg8wsn5AQzgF+UGWfl4ALgPeAM4E3XaMYiqRUWlpKq1atyMvLQ015koq7s3LlSkpLS8nPz6/x62KreoraHK4GXgPmAM+7+ywz+42ZnRrt9megvZn9B7gBGBlPtCLZb8OGDbRv315JQqplZrRv336nrzpj7Ufh7uOAcVW2/SppfQNwVm3HJVJXKUnIjuzKZ6ReNGbvEevXw9/+BvPmxR2JiEhWUaJI9uyzMHNm3FGIiGQVJYqEFi3CsnRp3JGIiGQVJYpknTrBsmVxRyFS5w0bNowtW7ak3eebb77hqKOOory8HIDy8nKuu+46evfuTd++fZk/fz6bNm3iyCOPrHSso48+mpKSEgAeeeQRfvKTn1Q6bp8+fZgzZ852++7pWFatWsXpp59eo/ejrlOiSKZEIbLbZs2aRfv27WncOP29Mo8//jhnnHEGOTk5ANx2220ceOCBzJo1i2uvvZY//OEP5Obmcuyxx/Lcc8+lPMaMGTMYMGBARXnDhg2UlJRw8MEH71TMuxJL27ZtKSsrY+XK7bp21Tux3vWUdTp1guJicAfdPSJ12WOPwfz5e/aYBx4Il166w93GjBnDiBEjAOjfvz///ve/efDBB+nevTv5+fk8/PDDjB49mmeeeYZnn30WgPXr1/Piiy8ybdo0APLz83nllVcAGDFiBDfddBPnnXfeducqLi7moosuqijPmDGDgw8+uOILP1kmYjnppJMYO3YsF154YU3ewTpLiSJZ587wzTewbh20ahV3NCJ10rhx43j55ZfZsmULZWVl7LPPPnzyySeceeaZvP322/Tr149NmzYxf/58ElMCjB8/nsWLF1NYWAhAWVkZw4cPB0JV0tSpU1Oea9asWZxxxhkVt3yuW7eOk08+ebv9MhXLaaedxo033qhE0aB0jIZiX75ciULqthr8558JX3/9NZs2baJNmzbMnDmTnj17AjB79mx69erFAw88wBlnnMGKFSto06ZNxeumT5/Ob37zG6644goALrnkEgoKCgDIyckhNzeXr776ilZJf5eLFy+mY8eOfPrppxXbrr766pQ9jj/99NOMxNKjRw/mzp27B9657KY2imSdO4dH3fkkskuaN2+OmbFu3Trmzp1Ljx49KCsro2XLluTm5lJUVMTgwYPZa6+9KvUOXrVqFc2bNwfCf/+vv/46p5xySsXzGzdupFmzZpXONWPGDHr37l1p2+zZsyu+1JNlKpaFCxfu1FAYdZUSRbJOncKjGrRFdtkJJ5zAq6++Sm5uLp9++ilFRUX069ePv/71r+Tl5dGpUyfatm1LeXl5xRf0wQcfzPvvvw/Avffey0knnVTxBbxy5Uo6dOiw3SB2xcXF9OrVq9K2WbNm0bdv3+1iylQsY8aM4bTTqk6jU/8oUSRr2RKaNVOiENkNp512Gv/617848cQT6dmzJ+eddx6TJk2iqKiIp59+umK/448/nnfeeQeAc889l48++oju3btTXFzM7373u4r9Jk6cyEknnbTdeWbMmFEpUZSVleHu7LPPPtvtm6lYxo4d2yASBe5er5aBAwf6brnySvdbbtm9Y4jEYPbs2XGHUKFv376+efNmd3e/8MIL/fXXX99un2nTpvkPf/jDHR7r9NNP97lz51aUjzrqKF+wYEGN4qi6756MpayszI844ogaxZFtUn1WgCKv5ntVVxRVde4cGrNFZJcVFxdX9KMoLi5O2W4wYMAAjjnmmIpObqls2rSJESNG7HS/iHRx7alY2rZty9tvv71H4sp2uuupqo4dYfbsuKMQqTcS/RFS+fGPf5z2tbm5uZx//vmVtl144YWV7lJKp+q+ezqWhkKJoqrOncNIsuvXh7GfRCSr7Eyfhfrev6G2qOqpqsSdT6p+EhEBlCi2p1tkRUQqUaKoSp3uREQqUaKoqnVryM1V1ZOISESJoiqzUP2kKwoREUCJIrVOnXRFIbILli5dyg9+8AMOPPBABg4cyNChQ3nxxRdrNYaSkhL69OlT4/0nTZrElClT9th+9ZESRSq6ohDZae7OiBEjOPLII5k/fz7Tpk1j9OjRlJaWbrfvjma/q011MVHU9vunRJFKp06wdi0kjSgpIum9+eab5ObmVgzPDXDAAQdwzTXXAPDkk09y6qmnMmzYMI499ljKysoYMWIEBQUFHHbYYRQXFwMwatQo7r777opj9OnTh5KSEkpKSjjkkEO49NJL6d27N8cffzzffPMNEDrS9evXj379+vHQQw9VG+P9999Pr169KCgo4JxzzqGkpIQ//vGP3HvvvRQWFjJ58mTGjh3LoYceSv/+/Rk+fDhLly5Nud/y5cv5/ve/z+DBgxk8eDDvvvvuducrLy/nF7/4BYMHD6agoIBHHnkECEnn6KOP5swzz6wYgyqMohF+lqOOOoqBAwdywgknsGTJEiBM63r99dczaNAg7rvvPqZOnUpBQQGFhYX84he/qLiKOvLII5k+fXpFDIcffjiffPLJTv8+K6lubI+6uuz2WE/u7m+95X7yye6LFu3+sURqSdXxe0aOdB8/Pqxv3hzKb74Zyhs2hPLbb4fyunWh/O67obxmTSh/8EEol5Xt+Pz33XefX3/99dU+/8QTT/h+++3nK1eudHf3q6++2keNGuXu7hMmTPB+/fq5u/v//d//+V133VXxut69e/uCBQt8wYIFnpOT4x9//LG7u5911ln+l7/8xd3D2FJvvfWWu7v//Oc/9969e6eMoUuXLr5hwwZ3d1+1alXK85WVlfnWrVvd3f2xxx7zG264IeV+5557rk+ePNnd3RcuXOg9e/bc7nyPPPKI3xKNHbdhwwYfOHCgz58/3ydOnOitW7f2xYsXe3l5uR922GE+efJk37Rpkw8dOtSXLVvm7u6jR4/2iy66yN3DuFU/+clPKr0vU6ZMcXf3G2+8seJnfvLJJ/26665zd/e5c+d6qu/EnR3rST2zU0lMYLR0Key/f7yxiNRRV111Fe+88w65ubkVs8Idd9xxtGvXDoB33nmHf/zjHwAMGzaMlStXsnbt2rTHzM/Pr5h5buDAgZSUlLB69WpWr17NkUceCcCPfvQj/v3vf6d8fUFBAeeddx4jRoyomK61qtLSUs4++2yWLFnCpk2bqp1vYvz48cxOGu5n7dq1rFu3jpYtW1Zse/311ykuLuaFF14AYM2aNcybN4/c3FyGDBlC165dASgsLKSkpKRiwqfjjjsOCFckXbp0qTje2WefDcDq1av56quvGDp0KAA/+MEPePnllwE466yzuOWWW7jrrrt4/PHH90jvdCWKVBJ9KdTpTuqw227btt64ceVy06aVyy1aVC63bl253Lbtjs/Xu3fvii9+gIceeogVK1YwaNCgpPPseFicxo0bs3Xr1opy8qRCTZs2rVjPycmpqHqqzkUXXcTHH3/Mvvvuy7hx43jllVd4++23GTt2LLfeeiszZszY7jXXXHMNN9xwA6eeeiqTJk1i1KhRKY+9detW3n///e0mVErm7jzwwAOccMIJlbZPmjRpu59ly5YtuDu9e/fmvffeS3m8mrx/zZs357jjjmPMmDE8//zzace3qim1UaTStm34y1KiEKmxYcOGsWHDBh5++OGKbV9//XW1+x9xxBE888wzQPji7NChA61btyYvL4+PPvoIgI8++ogFCxakPW+bNm1o06ZNxXwSiWMCPPHEE0yfPp1x48axdetWFi9ezDHHHMMdd9zBmjVrWLduHa1ateKrr76qeM2aNWvYb7/9AHjqqacqtlfd7/jjj+eBBx6oKCe3CySccMIJPPzww2zevBmAzz77jPXr11f7s/To0YPly5dXJIrNmzcza9aslD9zq1at+OCDDwAYPXp0pecvueQSrr32WgYPHkzbmmT5HYglUZhZOzN7w8zmRY/b/SRmVmhm75nZLDMrNrOzazHAUP2kRCFSY2bGv/71L9566y3y8/MZMmQIF1xwAXfccUfK/UeNGsW0adMoKChg5MiRFV/K3//+9ykrK6N37948+OCDNRpi/IknnuCqq66isLCwolG4qvLycn74wx/St29f+vfvz7XXXkubNm045ZRTePHFFysa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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "result = numpy.load('./output/summary_data.npz')\n", + "\n", + "eig_val, eig_state = numpy.linalg.eig(pauli_str_to_matrix(Hamiltonian, N))\n", + "min_eig_H = numpy.min(eig_val.real)\n", + "min_loss = numpy.ones([len(result['iter'])]) * min_eig_H\n", + "\n", + "plt.figure(1)\n", + "func1, = plt.plot(result['iter'], result['energy'], alpha=0.7, marker='', linestyle=\"-\", color='r')\n", + "func_min, = plt.plot(result['iter'], min_loss, alpha=0.7, marker='', linestyle=\":\", color='b')\n", + "plt.xlabel('Number of iteration')\n", + "plt.ylabel('Energy (Ha)')\n", + "\n", + "plt.legend(handles=[\n", + " func1,\n", + " func_min\n", + "],\n", + " labels=[\n", + " r'$\\left\\langle {\\psi \\left( {\\theta } \\right)} '\n", + " r'\\right|H\\left| {\\psi \\left( {\\theta } \\right)} \\right\\rangle $',\n", + " 'Ground-state energy',\n", + " ], loc='best')\n", + "\n", + "#plt.savefig(\"vqe.png\", bbox_inches='tight', dpi=300)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "\n", + "## 参考文献\n", + "\n", + "[1] [Peruzzo, A. et al. A variational eigenvalue solver on a photonic quantum processor. Nat. Commun. 5, 4213 (2014).](https://www.nature.com/articles/ncomms5213)\n", + "\n", + "[2] [McArdle, S., Endo, S., Aspuru-Guzik, A., Benjamin, S. C. & Yuan, X. Quantum computational chemistry. Rev. Mod. Phys. 92, 015003 (2020).](https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.92.015003)\n", + "\n", + "[3] [Cao, Y. et al. Quantum chemistry in the age of quantum computing. Chem. Rev. 119, 10856–10915 (2019).](https://pubs.acs.org/doi/abs/10.1021/acs.chemrev.8b00803)\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.10" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/tutorial/VQE/VQE_Tutorial_CN.pdf b/tutorial/VQE/VQE_Tutorial_CN.pdf new file mode 100644 index 0000000000000000000000000000000000000000..fb36a33694f07ed4224b6c325f3a24ce1e8a0e6a Binary files /dev/null and b/tutorial/VQE/VQE_Tutorial_CN.pdf differ diff --git a/tutorial/VQE/h2.xyz b/tutorial/VQE/h2.xyz new file mode 100644 index 0000000000000000000000000000000000000000..13fce0d08096b23236a7d4e153653e310a1b3833 --- /dev/null +++ b/tutorial/VQE/h2.xyz @@ -0,0 +1,4 @@ +2 +in Angstrom +H 0.00000 0.00000 -0.35000 +H 0.00000 0.00000 0.35000 diff --git a/tutorial/VQE/hf.xyz b/tutorial/VQE/hf.xyz new file mode 100644 index 0000000000000000000000000000000000000000..daafa7ab33b3116480f462764cb6c87b5cbf21bd --- /dev/null +++ b/tutorial/VQE/hf.xyz @@ -0,0 +1,4 @@ +2 +hydrogen fluoride +F -0.29502 0.75280 0.00000 +H 0.64417 0.75280 0.00000 diff --git a/tutorial/VQSD.ipynb b/tutorial/VQSD/VQSD_Tutorial_CN.ipynb similarity index 54% rename from tutorial/VQSD.ipynb rename to tutorial/VQSD/VQSD_Tutorial_CN.ipynb index e31ad7a82aab67d02be5bfc0726c37cd887d2249..38ba3043b47777de1fa88aa8e65874a97effe69f 100644 --- a/tutorial/VQSD.ipynb +++ b/tutorial/VQSD/VQSD_Tutorial_CN.ipynb @@ -1,18 +1,27 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 变分量子态对角化算法(VQSD)\n", + " Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. " + ] + }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 概览\n", - "- 在这个案例中,我们将展示如何通过Paddle Quantum训练量子神经网络来将量子态进行对角化。\n", + "\n", + "- 在本案例中,我们将通过Paddle Quantum训练量子神经网络来完成量子态的对角化。\n", "\n", "- 首先,让我们通过下面几行代码引入必要的library和package。" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": { "pycharm": { "is_executing": false, @@ -26,6 +35,7 @@ "import scipy\n", "from paddle import fluid\n", "from paddle_quantum.circuit import UAnsatz\n", + "from paddle_quantum.utils import hermitian\n", "from paddle.complex import matmul, trace, transpose" ] }, @@ -39,29 +49,43 @@ "source": [ "\n", "## 背景\n", - "量子态对角化算法(VQSD)[1-3] 目标是输出一个量子态的特征谱,即其所有特征值。求解量子态的特征值在量子计算中有着诸多应用,比如可以用于计算保真度和冯诺依曼熵,也可以用于主成分分析。\n", + "量子态对角化算法(VQSD,Variational Quantum State Diagonalization)[1-3] 的目标是输出一个量子态的特征谱,即其所有特征值。求解量子态的特征值在量子计算中有着诸多应用,比如可以用于计算保真度和冯诺依曼熵,也可以用于主成分分析。\n", "- 量子态通常是一个混合态,表示如下 $\\rho_{\\text{mixed}} = \\sum_i P_i |\\psi_i\\rangle\\langle\\psi_i|$\n", - "- 作为一个简单的例子,我们考虑一个2量子位的量子态,它的特征谱为 $(0.5, 0.3, 0.1, 0.1)$, 我们先通过随机作用一个酉矩阵来生成具有这样特征谱的量子态。\n" + "- 作为一个简单的例子,我们考虑一个2量子位的量子态,它的特征谱为 $(0.5, 0.3, 0.1, 0.1)$, 我们先通过随机作用一个酉矩阵来生成具有这样特征谱的随机量子态。\n" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": { "pycharm": { "is_executing": false, "name": "#%%\n" } }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 0.25692714+2.79965123e-18j -0.01201165+4.35008229e-02j\n", + " -0.04922153-5.53435795e-03j -0.05482813+6.81592880e-02j]\n", + " [-0.01201165-4.35008229e-02j 0.29589652-4.11838221e-18j\n", + " 0.10614221-7.12575060e-02j -0.03921986-9.71359495e-02j]\n", + " [-0.04922153+5.53435795e-03j 0.10614221+7.12575060e-02j\n", + " 0.214462 -3.16199986e-18j 0.02936413-1.13227406e-01j]\n", + " [-0.05482813-6.81592880e-02j -0.03921986+9.71359495e-02j\n", + " 0.02936413+1.13227406e-01j 0.23271434+4.32784528e-18j]]\n" + ] + } + ], "source": [ - "scipy.random.seed(1)\n", - "V = scipy.stats.unitary_group.rvs(4) # 随机生成一个酉矩阵\n", - "D = diag([0.5, 0.3, 0.1, 0.1]) # 输入目标态rho的谱\n", + "scipy.random.seed(13)\n", + "V = scipy.stats.unitary_group.rvs(4) # 随机生成一个酉矩阵\n", + "D = diag([0.5, 0.3, 0.1, 0.1]) # 输入目标态 rho 的谱\n", "V_H = V.conj().T \n", - "rho = V @ D @ V_H # 生成 rho\n", - "print(rho) # 打印量子态 rho\n", - "rho = rho.astype('complex64')" + "rho = V @ D @ V_H # 生成 rho\n", + "print(rho) # 打印量子态 rho" ] }, { @@ -79,7 +103,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": { "pycharm": { "is_executing": false, @@ -88,40 +112,23 @@ }, "outputs": [], "source": [ - "N = 2 # 量子神经网络的宽度\n", - "SEED = 1 # 种子\n", - "THETA_SIZE = 14 # 网络中的参数\n", + "N = 2 # 量子神经网络的宽度\n", + "SEED = 14 # 固定随机种子\n", + "THETA_SIZE = 15 # 量子神经网络中参数的数量\n", "\n", "def U_theta(theta, N):\n", " \"\"\"\n", - " U_theta\n", + " Quantum Neural Network\n", " \"\"\"\n", - "\n", + " \n", + " # 按照量子比特数量/网络宽度初始化量子神经网络\n", " cir = UAnsatz(N)\n", - " cir.rz(theta[0], 1)\n", - " cir.ry(theta[1], 1)\n", - " cir.rz(theta[2], 1)\n", - "\n", - " cir.rz(theta[3], 2)\n", - " cir.ry(theta[4], 2)\n", - " cir.rz(theta[5], 2)\n", - "\n", - " cir.cnot([2, 1])\n", - "\n", - " cir.rz(theta[6], 1)\n", - " cir.ry(theta[7], 2)\n", - "\n", - " cir.cnot([1, 2])\n", + " \n", + " # 调用内置的量子神经网络模板\n", + " cir.universal_2_qubit_gate(theta)\n", "\n", - " cir.rz(theta[8], 1)\n", - " cir.ry(theta[9], 1)\n", - " cir.rz(theta[10], 1)\n", - "\n", - " cir.rz(theta[11], 2)\n", - " cir.ry(theta[12], 2)\n", - " cir.rz(theta[13], 2)\n", - "\n", - " return cir.state\n" + " # 返回量子神经网络所模拟的酉矩阵 U\n", + " return cir.U" ] }, { @@ -134,13 +141,13 @@ "source": [ "## 配置训练模型 - 损失函数\n", "- 现在我们已经有了数据和量子神经网络的架构,我们将进一步定义训练参数、模型和损失函数。\n", - "- 通过作用量子神经网络$U(\\theta)$在量子态$\\rho$后得到的量子态记为$\\tilde\\rho$,我们设定损失函数为$\\tilde\\rho$与用来标记的量子态$\\sigma=0.1 |00\\rangle\\langle 00| + 0.2 |01\\rangle \\langle 01| + 0.3 |10\\rangle \\langle10| + 0.4 |11 \\rangle\\langle 11|$的内积。\n", - "- 具体的,设定损失函数为 $\\mathcal{L}(\\boldsymbol{\\theta}) = Tr(\\tilde\\rho\\sigma) .$" + "- 通过作用量子神经网络$U(\\theta)$在量子态$\\rho$后得到的量子态记为$\\tilde\\rho$,我们设定损失函数为$\\tilde\\rho$与用来标记的量子态$\\sigma=0.1 |00\\rangle\\langle 00| + 0.2 |01\\rangle \\langle 01| + 0.3 |10\\rangle \\langle10| + 0.4 |11 \\rangle\\langle 11|$的推广的内积。\n", + "- 具体的,设定损失函数为 $\\mathcal{L}(\\boldsymbol{\\theta}) = \\text{Tr}(\\tilde\\rho\\sigma) .$" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": { "pycharm": { "is_executing": false, @@ -149,8 +156,8 @@ }, "outputs": [], "source": [ - "sigma = diag([0.1, 0.2, 0.3, 0.4]) # 输入用来标记的量子态sigma\n", - "sigma = sigma.astype('complex64')\n", + "# 输入用来标记的量子态sigma\n", + "sigma = diag([0.1, 0.2, 0.3, 0.4]).astype('complex128') \n", "\n", "class Net(fluid.dygraph.Layer):\n", " \"\"\"\n", @@ -158,32 +165,26 @@ " \"\"\"\n", "\n", " def __init__(self, shape, rho, sigma, param_attr=fluid.initializer.Uniform(low=0.0, high=2 * numpy.pi, seed=SEED),\n", - " dtype='float32'):\n", + " dtype='float64'):\n", " super(Net, self).__init__()\n", - "\n", + " \n", + " # 将 Numpy array 转换成 Paddle 动态图模式中支持的 variable\n", " self.rho = fluid.dygraph.to_variable(rho)\n", " self.sigma = fluid.dygraph.to_variable(sigma)\n", - "\n", + " \n", + " # 初始化 theta 参数列表,并用 [0, 2*pi] 的均匀分布来填充初始值\n", " self.theta = self.create_parameter(shape=shape, attr=param_attr, dtype=dtype, is_bias=False)\n", "\n", + " # 定义损失函数和前向传播机制\n", " def forward(self, N):\n", - " \"\"\"\n", - " Args:\n", - " Returns:\n", - " The loss.\n", - " \"\"\"\n", + " \n", + " # 施加量子神经网络\n", + " U = U_theta(self.theta, N)\n", "\n", - " out_state = U_theta(self.theta, N)\n", + " # rho_tilde 是将 U 作用在 rho 后得到的量子态 U*rho*U^dagger \n", + " rho_tilde = matmul(matmul(U, self.rho), hermitian(U))\n", "\n", - " # rho_tilde 是将U_theta作用在rho后得到的量子态 \n", - " rho_tilde = matmul(\n", - " matmul(transpose(\n", - " fluid.framework.ComplexVariable(out_state.real, -out_state.imag),\n", - " perm=[1, 0]\n", - " ), self.rho), out_state\n", - " )\n", - "\n", - " # record the new loss\n", + " # 计算损失函数\n", " loss = trace(matmul(self.sigma, rho_tilde))\n", "\n", " return loss.real, rho_tilde" @@ -194,14 +195,15 @@ "metadata": {}, "source": [ "## 配置训练模型 - 模型参数\n", + "\n", "在进行量子神经网络的训练之前,我们还需要进行一些训练(超)参数的设置,例如学习速率与迭代次数。\n", - "- 设定学习速率(learning rate)为0.1;\n", + "- 设定学习速率(learning rate)为 0.1;\n", "- 设定迭代次数为50次。" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": { "pycharm": { "is_executing": false, @@ -210,9 +212,8 @@ }, "outputs": [], "source": [ - "ITR = 50 #训练的总的迭代次数\n", - "\n", - "LR = 0.1 #学习速率" + "ITR = 50 # 设置训练的总的迭代次数\n", + "LR = 0.1 # 设置学习速率" ] }, { @@ -228,31 +229,51 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": { "pycharm": { "is_executing": false, "name": "#%% \n" } }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "iter: 0 loss: 0.2354\n", + "iter: 10 loss: 0.1912\n", + "iter: 20 loss: 0.1844\n", + "iter: 30 loss: 0.1823\n", + "iter: 40 loss: 0.1813\n" + ] + } + ], "source": [ + "# 初始化paddle动态图机制\n", "with fluid.dygraph.guard():\n", - " # net\n", + " \n", + " # 确定网络的参数维度\n", " net = Net(shape=[THETA_SIZE], rho=rho, sigma=sigma)\n", "\n", - " # optimizer\n", + " # 一般来说,我们利用Adam优化器来获得相对好的收敛,当然你可以改成SGD或者是RMS prop.\n", " opt = fluid.optimizer.AdagradOptimizer(learning_rate=LR, parameter_list=net.parameters())\n", - " # gradient descent loop\n", + " \n", + " # 优化循环\n", " for itr in range(ITR):\n", + " \n", + " # 前向传播计算损失函数并返回估计的能谱\n", " loss, rho_tilde = net(N)\n", - "\n", " rho_tilde_np = rho_tilde.numpy()\n", + " \n", + " # 在动态图机制下,反向传播极小化损失函数\n", " loss.backward()\n", " opt.minimize(loss)\n", " net.clear_gradients()\n", - "\n", - " print('iter:', itr, 'loss:', '%.4f' % loss.numpy()[0])\n" + " \n", + " # 打印训练结果\n", + " if itr % 10 == 0:\n", + " print('iter:', itr, 'loss:', '%.4f' % loss.numpy()[0])\n" ] }, { @@ -261,25 +282,31 @@ "source": [ "## 总结\n", "根据上面训练得到的结果,通过大概50次迭代,我们就比较好的完成了对角化。\n", - "我们可以通过打印$\n", - "\\tilde{\\rho} = U(\\boldsymbol{\\theta})\\rho U^\\dagger(\\boldsymbol{\\theta})\n", - "$\n", + "我们可以通过打印\n", + "$\\tilde{\\rho} = U(\\boldsymbol{\\theta})\\rho U^\\dagger(\\boldsymbol{\\theta})$\n", "的来验证谱分解的效果。特别的,我们可以验证它的对角线与我们目标谱是非常接近的。" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": { "pycharm": { "is_executing": false, "name": "#%%\n" } }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The estimated spectrum is: [0.49401064 0.30357179 0.10224927 0.10016829]\n", + "The target spectrum is: [0.5 0.3 0.1 0.1]\n" + ] + } + ], "source": [ - "print(rho_tilde_np)\n", - "\n", "print(\"The estimated spectrum is:\", numpy.real(numpy.diag(rho_tilde_np)))\n", "print(\"The target spectrum is:\", numpy.diag(D))" ] @@ -292,13 +319,13 @@ } }, "source": [ - "### 参考文献\n", + "## 参考文献\n", "\n", - "[1] R. Larose, A. Tikku, É. O. Neel-judy, L. Cincio, and P. J. Coles, “Variational quantum state diagonalization,” npj Quantum Inf., no. November 2018, 2019.\n", + "[1] [Larose, R., Tikku, A., Neel-judy, É. O., Cincio, L. & Coles, P. J. Variational quantum state diagonalization. npj Quantum Inf. (2019) doi:10.1038/s41534-019-0167-6.](https://www.nature.com/articles/s41534-019-0167-6)\n", "\n", - "[2] K. M. Nakanishi, K. Mitarai, and K. Fujii, “Subspace-search variational quantum eigensolver for excited states,” Phys. Rev. Res., vol. 1, no. 3, p. 033062, Oct. 2019.\n", + "[2] [Nakanishi, K. M., Mitarai, K. & Fujii, K. Subspace-search variational quantum eigensolver for excited states. Phys. Rev. Res. 1, 033062 (2019).](https://journals.aps.org/prresearch/pdf/10.1103/PhysRevResearch.1.033062)\n", "\n", - "[3] M. Cerezo, K. Sharma, A. Arrasmith, and P. J. Coles, “Variational Quantum State Eigensolver,” arXiv:2004.01372, no. 1, pp. 1–14, Apr. 2020.\n" + "[3] [Cerezo, M., Sharma, K., Arrasmith, A. & Coles, P. J. Variational Quantum State Eigensolver. arXiv:2004.01372 (2020).](https://arxiv.org/pdf/2004.01372.pdf)\n" ] } ], diff --git a/tutorial/VQSD/VQSD_Tutorial_CN.pdf b/tutorial/VQSD/VQSD_Tutorial_CN.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e20545f28fc4a052836e85872ea9dc3c3cfcaea6 Binary files /dev/null and b/tutorial/VQSD/VQSD_Tutorial_CN.pdf differ diff --git a/tutorial/VQSVD/VQSVD_Tutorial_CN.ipynb b/tutorial/VQSVD/VQSVD_Tutorial_CN.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..3123e572e4f1697d1e901177b07f58e64ab60416 --- /dev/null +++ b/tutorial/VQSVD/VQSVD_Tutorial_CN.ipynb @@ -0,0 +1,860 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 变分量子奇异值分解 (VQSVD)\n", + "\n", + " Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. \n", + "\n", + "\n", + "## 概览\n", + "\n", + "在本教程中,我们一起学习下经典奇异值分解(SVD)的概念以及我们自主研发的量子神经网络版本的量子奇异值分解 (VQSVD,Variational Quantum Singular Value Decomposition)[1] 是如何运作的。主体部分包括两个具体案例:\n", + "\n", + "- 分解随机生成的 8x8 复数矩阵\n", + "- 应用在图像压缩上的效果\n", + "\n", + "## 背景\n", + "\n", + "奇异值分解(SVD)有非常多的应用包括 -- 主成分分析(PCA)、求解线性方程组和推荐系统。其主要任务是给定一个复数矩阵 $M \\in \\mathbb{C}^{m \\times n}$, 找到分解形式:\n", + "\n", + "$$\n", + "M = UDV^\\dagger\n", + "$$\n", + "\n", + "其中 $U_{m\\times m}$ 和 $V^\\dagger_{n\\times n}$ 是酉矩阵(Unitary matrix), 满足性质 $UU^\\dagger = VV^\\dagger = I$。 \n", + "- 矩阵 $U$ 的列向量 $\\lvert {u_j}\\rangle$ 被称为左奇异向量(left singular vectors), $\\{\\lvert {u_j}\\rangle\\}_{j=1}^{m}$ 组成一组正交向量基。这些列向量本质上是矩阵 $MM^\\dagger$ 的特征向量。\n", + "- 类似的,矩阵 $V$ 的列向量 $\\{\\lvert {v_j}\\rangle\\}_{j=1}^{n}$ 是 $M^\\dagger M$ 的特征向量也组成一组正交向量基。\n", + "- 中间矩阵 $D_{m\\times n}$ 的对角元素上存储着由大到小排列的奇异值 $d_j$。 \n", + "\n", + "我们不妨先来看个简单的例子:(为了方便讨论,我们假设以下出现的 $M$ 都是方阵)\n", + "\n", + "$$\n", + "M = 2*X\\otimes Z + 6*Z\\otimes X + 3*I\\otimes I = \n", + "\\begin{bmatrix} \n", + "3 &6 &2 &0 \\\\\n", + "6 &3 &0 &-2 \\\\\n", + "2 &0 &3 &-6 \\\\\n", + "0 &-2 &-6 &3 \n", + "\\end{bmatrix}\n", + "$$\n", + "\n", + "那么该矩阵的奇异值分解可表示为:\n", + "\n", + "$$\n", + "M = UDV^\\dagger = \n", + "\\frac{1}{2}\n", + "\\begin{bmatrix} \n", + "-1 &-1 &1 &1 \\\\\n", + "-1 &-1 &-1 &-1 \\\\\n", + "-1 &1 &-1 &1 \\\\\n", + "1 &-1 &-1 &1 \n", + "\\end{bmatrix}\n", + "\\begin{bmatrix} \n", + "11 &0 &0 &0 \\\\\n", + "0 &7 &0 &0 \\\\\n", + "0 &0 &5 &0 \\\\\n", + "0 &0 &0 &1 \n", + "\\end{bmatrix}\n", + "\\frac{1}{2}\n", + "\\begin{bmatrix} \n", + "-1 &-1 &-1 &-1 \\\\\n", + "-1 &-1 &1 &1 \\\\\n", + "-1 &1 &1 &-1 \\\\\n", + "1 &-1 &1 &-1 \n", + "\\end{bmatrix}\n", + "$$\n", + "\n", + "首先,让我们通过下面几行代码引入必要的 library和 package。" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "import numpy as np\n", + "from progressbar import ProgressBar\n", + "from matplotlib import pyplot as plt\n", + "from scipy.stats import unitary_group\n", + "from scipy.linalg import norm\n", + "\n", + "import paddle.fluid as fluid\n", + "from paddle.complex import matmul, transpose, trace\n", + "from paddle_quantum.circuit import *\n", + "from paddle_quantum.utils import *\n", + "\n", + "\n", + "# 画出优化过程中的学习曲线\n", + "def loss_plot(loss):\n", + " '''\n", + " loss is a list, this function plots loss over iteration\n", + " '''\n", + " plt.plot(list(range(1, len(loss)+1)), loss)\n", + " plt.xlabel('iteration')\n", + " plt.ylabel('loss')\n", + " plt.title('Loss Over Iteration')\n", + " plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 经典奇异值分解\n", + "\n", + "那么在了解一些简单的数学背景之后, 我们来学习下如何用 Numpy 完成矩阵的奇异值分解。" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "我们想要分解的矩阵 M 是:\n", + "[[ 3.+0.j 6.+0.j 2.+0.j 0.+0.j]\n", + " [ 6.+0.j 3.+0.j 0.+0.j -2.+0.j]\n", + " [ 2.+0.j 0.+0.j 3.+0.j -6.+0.j]\n", + " [ 0.+0.j -2.+0.j -6.+0.j 3.+0.j]]\n" + ] + } + ], + "source": [ + "# 生成矩阵 M\n", + "def M_generator():\n", + " I = np.array([[1, 0], [0, 1]])\n", + " Z = np.array([[1, 0], [0, -1]])\n", + " X = np.array([[0, 1], [1, 0]])\n", + " Y = np.array([[0, -1j], [1j, 0]])\n", + " M = 2 *np.kron(X, Z) + 6 * np.kron(Z, X) + 3 * np.kron(I, I)\n", + " return M.astype('complex64')\n", + "\n", + "print('我们想要分解的矩阵 M 是:')\n", + "print(M_generator())" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "矩阵的奇异值从大到小分别是:\n", + "[11. 7. 5. 1.]\n", + "分解出的酉矩阵 U 是:\n", + "[[-0.5+0.j -0.5+0.j 0.5+0.j 0.5+0.j]\n", + " [-0.5+0.j -0.5+0.j -0.5+0.j -0.5+0.j]\n", + " [-0.5+0.j 0.5+0.j -0.5+0.j 0.5+0.j]\n", + " [ 0.5+0.j -0.5+0.j -0.5+0.j 0.5+0.j]]\n", + "分解出的酉矩阵 V_dagger 是:\n", + "[[-0.5+0.j -0.5+0.j -0.5+0.j 0.5+0.j]\n", + " [-0.5+0.j -0.5+0.j 0.5+0.j -0.5+0.j]\n", + " [-0.5+0.j 0.5+0.j 0.5+0.j 0.5+0.j]\n", + " [-0.5+0.j 0.5+0.j -0.5+0.j -0.5+0.j]]\n" + ] + } + ], + "source": [ + "# 我们只需要以下一行代码就可以完成 SVD \n", + "U, D, V_dagger = np.linalg.svd(M_generator(), full_matrices=True)\n", + "\n", + "# 打印分解结果\n", + "print(\"矩阵的奇异值从大到小分别是:\")\n", + "print(D)\n", + "print(\"分解出的酉矩阵 U 是:\")\n", + "print(U)\n", + "print(\"分解出的酉矩阵 V_dagger 是:\")\n", + "print(V_dagger)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 3.+0.j 6.+0.j 2.+0.j 0.+0.j]\n", + " [ 6.+0.j 3.+0.j 0.+0.j -2.+0.j]\n", + " [ 2.+0.j 0.+0.j 3.+0.j -6.+0.j]\n", + " [ 0.+0.j -2.+0.j -6.+0.j 3.+0.j]]\n" + ] + } + ], + "source": [ + "# 再组装回去, 能不能复原矩阵?\n", + "M_reconst = np.matmul(U, np.matmul(np.diag(D), V_dagger))\n", + "print(M_reconst)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "那当然是可以复原成原来的矩阵 $M$ 的!读者也可以自行修改矩阵,试试看不是方阵的情况。\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 量子奇异值分解\n", + "\n", + "接下来我们来看看量子版本的奇异值分解是怎么一回事。简单的说,我们把矩阵分解这一问题巧妙的转换成了优化问题。通过以下四个步骤:\n", + "\n", + "- 准备一组正交向量基 $\\{\\lvert {\\psi_j}\\rangle\\}$, 不妨直接取计算基 $\\{ \\lvert {000}\\rangle, \\lvert {001}\\rangle,\\cdots \\lvert {111}\\rangle\\}$ (这是3量子比特的情形)\n", + "- 准备两个参数化的量子神经网络 $U(\\theta)$ 和 $V(\\phi)$ 分别用来学习左/右奇异向量\n", + "- 利用量子神经网络估算奇异值 $m_j = \\text{Re}\\langle{\\psi_j} \\lvert U(\\theta)^{\\dagger} M V(\\phi)\\lvert {\\psi_j}\\rangle$\n", + "- 设计损失函数并且利用飞桨来优化\n", + "\n", + "$$\n", + "L(\\theta,\\phi) = \\sum_{j=1}^T q_j\\times \\text{Re} \\langle{\\psi_j} \\lvert U(\\theta)^{\\dagger} M V(\\phi)\\lvert {\\psi_j}\\rangle\n", + "$$\n", + "\n", + "其中 $q_1>\\cdots>q_T>0$ 是可以调节的权重(超参数), $T$ 表示我们想要学习到的阶数(rank),或者可以解释为总共要学习得到的奇异值个数。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 案例1:分解随机生成的 8x8 复数矩阵\n", + "\n", + "接着我们来看一个具体的例子,这可以更好的解释整体流程。" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "我们想要分解的矩阵 M 是:\n", + "[[6.+1.j 3.+9.j 7.+3.j 4.+7.j 6.+6.j 9.+8.j 2.+7.j 6.+4.j]\n", + " [7.+1.j 4.+4.j 3.+7.j 7.+9.j 7.+8.j 2.+8.j 5.+0.j 4.+8.j]\n", + " [1.+6.j 7.+8.j 5.+7.j 1.+0.j 4.+7.j 0.+7.j 9.+2.j 5.+0.j]\n", + " [8.+7.j 0.+2.j 9.+2.j 2.+0.j 6.+4.j 3.+9.j 8.+6.j 2.+9.j]\n", + " [4.+8.j 2.+6.j 6.+8.j 4.+7.j 8.+1.j 6.+0.j 1.+6.j 3.+6.j]\n", + " [8.+7.j 1.+4.j 9.+2.j 8.+7.j 9.+5.j 4.+2.j 1.+0.j 3.+2.j]\n", + " [6.+4.j 7.+2.j 2.+0.j 0.+4.j 3.+9.j 1.+6.j 7.+6.j 3.+8.j]\n", + " [1.+9.j 5.+9.j 5.+2.j 9.+6.j 3.+0.j 5.+3.j 1.+3.j 9.+4.j]]\n", + "矩阵的奇异值从大到小分别是:\n", + "[54.83484985 19.18141073 14.98866247 11.61419557 10.15927045 7.60223249\n", + " 5.81040539 3.30116001]\n" + ] + } + ], + "source": [ + "# 先固定随机种子, 为了能够复现结果\n", + "np.random.seed(42)\n", + "\n", + "# 设置量子比特数量,确定希尔伯特空间的维度\n", + "N = 3\n", + "\n", + "# 制作随机矩阵生成器\n", + "def random_M_generator():\n", + " M = np.random.randint(10, size = (2**N, 2**N))\\\n", + " + 1j*np.random.randint(10, size = (2**N, 2**N))\n", + " M1 = np.random.randint(10, size = (2**N, 2**N)) \n", + " return M\n", + "\n", + "M = random_M_generator()\n", + "M_err = np.copy(M)\n", + "\n", + "\n", + "# 打印结果\n", + "print('我们想要分解的矩阵 M 是:')\n", + "print(M)\n", + "\n", + "U, D, V_dagger = np.linalg.svd(M, full_matrices=True)\n", + "print(\"矩阵的奇异值从大到小分别是:\")\n", + "print(D)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "选取的等差权重为:\n", + "[24.+0.j 21.+0.j 18.+0.j 15.+0.j 12.+0.j 9.+0.j 6.+0.j 3.+0.j]\n" + ] + } + ], + "source": [ + "# 超参数设置\n", + "N = 3 # 量子比特数量\n", + "T = 8 # 设置想要学习的阶数\n", + "ITR = 100 # 迭代次数\n", + "LR = 0.02 # 学习速率\n", + "SEED = 1 # 随机数种子\n", + "\n", + "# 设置等差的学习权重\n", + "weight = np.arange(3 * T, 0, -3).astype('complex128')\n", + "print('选取的等差权重为:')\n", + "print(weight)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 量子神经网络的构造\n", + "\n", + "我们搭建如下的结构:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "# 设置电路参数\n", + "cir_depth = 40 # 电路深度\n", + "block_len = 2 # 每个模组的长度\n", + "theta_size = N * block_len * cir_depth # 网络参数 theta 的大小\n", + "\n", + "\n", + "# 定义量子神经网络\n", + "def U_theta(theta):\n", + "\n", + " # 用 UAnsatz 初始化网络\n", + " cir = UAnsatz(N)\n", + " \n", + " # 搭建层级结构:\n", + " for layer_num in range(cir_depth):\n", + " \n", + " for which_qubit in range(N):\n", + " cir.ry(theta[block_len * layer_num * N + which_qubit], \n", + " which_qubit)\n", + " \n", + " for which_qubit in range(N):\n", + " cir.rz(theta[(block_len * layer_num + 1) * N + which_qubit], \n", + " which_qubit)\n", + "\n", + " for which_qubit in range(1, N):\n", + " cir.cnot([which_qubit - 1, which_qubit])\n", + " cir.cnot([N - 1, 0])\n", + "\n", + " return cir.U" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100% |########################################################################|\r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "主程序段总共运行了 191.72587513923645 秒\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "class NET(fluid.dygraph.Layer):\n", + " \n", + " # 初始化长度为 theta_size 的可学习参数列表,并用 [0, 2*pi] 的均匀分布来填充初始值\n", + " def __init__(self, shape, param_attr=fluid.initializer.Uniform(low=0.0, high=2 * np.pi), dtype='float64'):\n", + " super(NET, self).__init__()\n", + " \n", + " # 创建用来学习 U 的参数 theta\n", + " self.theta = self.create_parameter(shape=shape, \n", + " attr=param_attr, dtype=dtype, is_bias=False)\n", + " \n", + " # 创建用来学习 V_dagger 的参数 phi\n", + " self.phi = self.create_parameter(shape=shape, \n", + " attr=param_attr, dtype=dtype, is_bias=False)\n", + " \n", + " # 我们需要将 Numpy array 转换成 Paddle 动态图模式中支持的 variable\n", + " self.M = fluid.dygraph.to_variable(M)\n", + " self.weight = fluid.dygraph.to_variable(weight)\n", + "\n", + " # 定义损失函数和前向传播机制\n", + " def forward(self):\n", + " \n", + " # 获取量子神经网络的酉矩阵表示\n", + " U = U_theta(self.theta)\n", + " U_dagger = hermitian(U)\n", + " \n", + " \n", + " V = U_theta(self.phi)\n", + " V_dagger = hermitian(V)\n", + " \n", + " # 初始化损失函数和奇异值存储器\n", + " loss = 0 \n", + " singular_values = np.zeros(T)\n", + " \n", + " # 定义损失函数\n", + " for i in range(T):\n", + " loss -= self.weight.real[i] * matmul(U_dagger, matmul(self.M, V)).real[i][i]\n", + " singular_values[i] = (matmul(U_dagger, matmul(self.M, V)).real[i][i]).numpy()\n", + " \n", + " # 函数返回两个矩阵 U 和 V_dagger、 学习的奇异值以及损失函数 \n", + " return U, V_dagger, loss, singular_values\n", + " \n", + "# 记录优化中间过程\n", + "loss_list, singular_value_list = [], []\n", + "U_learned, V_dagger_learned = [], []\n", + "\n", + "time_start = time.time()\n", + "# 启动 Paddle 动态图模式\n", + "with fluid.dygraph.guard():\n", + " \n", + " # 确定网络的参数维度\n", + " net = NET([theta_size])\n", + " \n", + " # 一般来说,我们利用Adam优化器来获得相对好的收敛,当然你可以改成SGD或者是RMS prop.\n", + " opt = fluid.optimizer.AdagradOptimizer(learning_rate=LR, parameter_list=net.parameters())\n", + " \n", + " # 优化循环\n", + " pbar = ProgressBar()\n", + " for itr in pbar(range(ITR)):\n", + " \n", + " # 前向传播计算损失函数\n", + " U, V_dagger, loss, singular_values = net()\n", + " \n", + " # 在动态图机制下,反向传播极小化损失函数\n", + " loss.backward()\n", + " opt.minimize(loss)\n", + " net.clear_gradients()\n", + "\n", + " # 记录优化中间结果\n", + " loss_list.append(loss[0][0].numpy())\n", + " singular_value_list.append(singular_values)\n", + "\n", + " # 记录最后学出的两个酉矩阵 \n", + " U_learned = U.real.numpy() + 1j * U.imag.numpy()\n", + " V_dagger_learned = V_dagger.real.numpy() + 1j * V_dagger.imag.numpy()\n", + "\n", + "time_span = time.time() - time_start \n", + "print('主程序段总共运行了', time_span, '秒')" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# 绘制学习曲线\n", + "loss_plot(loss_list)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "接着我们来探究下量子版本的奇异值分解的精度问题。在上述部分,我们提到过可以用分解得到的更少的信息来表达原矩阵。具体来说,就是用前 $T$ 个奇异值和前 $T$ 列左右奇异向量重构一个矩阵:\n", + "\n", + "$$\n", + "M_{re}^{(T)} = U_{m \\times T} * D_{T \\times T} * V^{\\dagger}_{T \\times m}\n", + "$$\n", + "\n", + "并且对于一个本身秩 (rank) 为 $r$ 的矩阵 $M$, 误差随着使用奇异值的数量变多会越来越小。经典的奇异值算法可以保证:\n", + "\n", + "$$\n", + "\\lim_{T\\rightarrow r} ||M - M_{re}^{(T)}||^2_2 = 0\n", + "$$\n", + "\n", + "其中矩阵间的距离测量由 2-norm 来计算,\n", + "\n", + "$$\n", + "||M||_2 = \\sqrt{\\sum_{i,j} |M_{ij}|^2}\n", + "$$\n", + "\n", + "目前量子版本的奇异值分解还需要很长时间的优化,理论上只能保证上述误差不断减小。" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYIAAAEKCAYAAAAfGVI8AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/d3fzzAAAACXBIWXMAAAsTAAALEwEAmpwYAABFwElEQVR4nO3dd3iUZdbA4d9JJ6EEQu9FIJRAAlEEUYoKuiqiUnQtoO7ay+qC9Vtl13Xt6Np1VVREsYsFK1VAUCAgXUAC0mtCSULa+f54JoWQMiGTTMq5r2suZt6ZeedkEubM084jqooxxpiaK8DfARhjjPEvSwTGGFPDWSIwxpgazhKBMcbUcJYIjDGmhgvydwAnomHDhtq2bVt/h2GMMVXKkiVL9qpqo4LHq2QiaNu2LYsXL/Z3GMYYU6WIyObCjlvXkDHG1HCWCIwxpoazRGCMMTVclRwjMKYy2L17N+PGjWPt2rVkZ2f7OxxjAAgICCA6Oponn3ySxo0be/UcSwTGnKBx48YxaNAgXn/9dYKDg/0djjEAZGRkMHnyZMaNG8fbb7/t1XNqTNfQZwnbGPbIRyx6oA8XPPIxnyVs83dIpopbu3YtV1xxhSUBU6kEBwdz5ZVXsnbtWq+fUyNaBJ8lbOPeT1Zwn77HyYHrGHnkXe79JAKA4XEt/Bydqaqys7MtCZhKKTg4uFTdlTWiRfDEt+uonbGXkYFzCBBlZOBcamfs44lv1/k7NGOM8bsakQi2J6VyW9CnBJIFQABZ3Br0CduTUv0cmTFlExgYSGxsLN27d+eCCy4gKSnJb7HMnj2bBQsW+Ox8n332GatXr869/cADD/DDDz/47PwFff755zz66KNePTYlJYWoqCgOHjx4zPHhw4fz/vvvAy7+Hj16EB0dTffu3fnoo49yH7dw4UL69OlDbGwsXbp0YcKECSQmJtKyZcvjvsnHxsayaNEiJkyYQIsWLYiNjaVjx45cfPHFx7w/ZVEjEkFMvVRGBs4hWNwbHCpZjAycS/d6aX6OzNQknyVs47RHZ9Lunq847dGZPhmnqlWrFsuWLWPlypU0aNCAF154wQeRnpjiEkFmZmapz1cwEfzrX//irLPOOuH4SjJs2DDuuecerx4bHh7O0KFD+fTTT3OPJScnM2/ePC644AKWL1/OuHHjmDZtGmvXruWLL77g7rvvZsmSJQCMGTOGV199Nfd3N2rUKNq2bUvr1q358ccfc8+5du1aDh06RJ8+fQC44447WLZsGevXr2f06NEMHjyYPXv2lPlnrxGJ4Jlm3yMcuxNbANn8t9l3forI1DQ541TbklJRYFtSKvd+ssKnkxb69u3Ltm3ufBs3buScc86hd+/enH766bkDh7t27eKiiy6iZ8+e9OzZM/eDe+LEiXTv3p3u3bvzzDPPAJCYmEiXLl3461//Srdu3RgyZAipqa4V/eyzz9K1a1d69OjBpZdeSmJiIi+//DJPP/00sbGx/Pjjj4wdO5YbbriBPn36cNdddzFhwgSefPLJ3Hi7d+9OYmIiAG+//TY9evSgZ8+eXHnllSxYsIDPP/+c8ePHExsby8aNGxk7dmzut+oZM2YQFxdHTEwM11xzDUePHgVc+ZkHH3yQXr16ERMTU+iA6amnnsqqVatybw8cOJDFixfz5ptvcssttwDwxRdf0KdPH+Li4jjrrLPYtWvXcee57LLLmDp1au7tTz/9lKFDhxIeHs6TTz7JfffdR7t27QBo164d9913H0899RTgph43a9YMcK26rl27FnrOqVOncumllxb6+x49ejRDhgzh3XffLfT+0qgRg8Xt01aBHPuNJFQy3XFjfGT0Kz8VeV/CliTSs45t8qdmZDHhi1UMj2vB/iPp3PjOkmPuf//6vl6/dlZWFjNmzODaa68F4LrrruPll1+mY8eOLFq0iJtuuomZM2dy2223MWDAAD799FOysrI4fPgwS5YsYdKkSSxatAhVpU+fPgwYMID69euzfv163nvvPf73v/8xatQoPv74Y6644goeffRRNm3aRGhoKElJSURGRnLDDTdQu3Ztxo0bB8Drr7/O1q1bWbBgAYGBgUyYMKHQ2FetWsW///1vFixYQMOGDdm/fz8NGjRg2LBhnH/++YwYMeKYx6elpTF27FhmzJhBp06duOqqq3jppZf429/+BkDDhg1ZunQpL774Ik8++SSvvfbaMc8fPXo0H3zwAf/85z/ZsWMHO3bsID4+npUrV+Y+pn///ixcuBAR4bXXXuPxxx/P/RDPMXToUP7yl7+wb98+oqKimDp1am4iWbVqVe77kCM+Pp7nnnsOcN/sO3fuzMCBAznnnHMYM2YMYWFhjBo1itjYWJ577jmCgoJ4//33+fDDD4v8vffq1atUs4OKUiNaBNwwDyYkw4Rk0u/cyFGC+b72MHfcmApQMAnkSErJKNN5U1NTiY2NpWnTpuzatYuzzz6bw4cPs2DBAkaOHElsbCzXX389O3bsAGDmzJnceOONgPsmWq9ePebNm8dFF11EREQEtWvX5uKLL87tnmjXrh2xsbEA9O7dO/cbfI8ePbj88st55513CAoq+vvkyJEjCQwMLPZnmDlzJiNHjqRhw4YANGjQoNjHr1u3jnbt2tGpUyfAdbPMnTs39/6LL774uHjzGzVqVG7L4oMPPjgu0QBs3bqVoUOHEhMTwxNPPHFMCyJHSEgIw4YN46OPPmLv3r0kJCQwdOjQYmPP8cADD7B48eLcb/TnnHMOAE2aNKF79+7MmDGDZcuWERQURPfu3Ys8j6/2nK8RLYL8Quo2ZHbH8by0MoQ2uw7RqUkdf4dkqonivsGf9uhMthUyOaFFZC0AGkSElKoFkCNnjCAlJYWhQ4fywgsvMHbsWCIjI1m2bFmpz1dQaGho7vXAwMDcrqGvvvqKuXPn8sUXX/Dwww+zYsWKQp8fERGRez0oKOiYgdC0tPIZo8uJOTAwsNCxiRYtWhAVFcWvv/7K+++/z8svv3zcY2699VbuvPNOhg0bxuzZs4tszVx22WU89NBDqCoXXnhh7nTirl27smTJEnr27Jn72CVLlhAfH597u0OHDtx444389a9/pVGjRrkti5zuoSZNmnDZZZcV+7MmJCQcc84TVaEtAhGJFJGPRGStiKwRkb4i0kBEvheR9Z5/65d3HD2G38GqwGgmzU8s75cyBoDxQztTK/jYb8a1ggMZP7SzT84fHh7Os88+y1NPPUV4eDjt2rXL7VJQVZYvXw7AmWeeyUsvvQS47qTk5GROP/10PvvsM1JSUjhy5Aiffvopp59+epGvlZ2dzR9//MGgQYN47LHHSE5O5vDhw9SpU4dDhw4V+by2bduydOlSAJYuXcqmTZsAGDx4MB9++CH79u0DYP/+/QBFnq9z584kJiayYcMGACZPnsyAAQNK9X6NHj2axx9/nOTkZHr06HHc/cnJybRo4dYYvfXWW0WeZ+DAgaxfv54XXnjhmA/tcePG8cgjj+S2SBITE3nmmWcYP3484BJpzrf59evXExgYSGRkJOBaNNOnT+f9998vcnwA4OOPP+a7774rMVl4o6K7hv4LfKOq0UBPYA1wDzBDVTsCMzy3y1WDiBCu65JBw4TnOXAkvbxfzhiGx7XgkYtjaBFZC8G1BB65OManCxrj4uLo0aMH7733HlOmTOH111+nZ8+edOvWjWnTpgHw3//+l1mzZhETE0Pv3r1ZvXo1vXr1YuzYsZxyyin06dOHv/zlL8TFxRX5OllZWVxxxRXExMQQFxfHbbfdRmRkJBdccAGffvpp7mBxQZdccgn79++nW7duPP/887ldO926deP+++9nwIAB9OzZkzvvvBOASy+9lCeeeIK4uDg2btyYe56wsDAmTZrEyJEjiYmJISAggBtuuKFU79WIESOYOnUqo0aNKvT+CRMmMHLkSHr37p3bZVWYgIAARowYwb59+45JRrGxsTz22GNccMEFdOrUiU6dOvHSSy/RubNL/JMnT6Zz587ExsZy5ZVXMmXKlNwutMjISPr27UuTJk1o3779Ma+XMxjfsWNH3nnnHWbOnEmjRsftM1Nq4qs+phJfSKQesAxor/leVETWAQNVdYeINANmq2qxX5Pi4+O1rBvT7PrheZrMu5/vT5vK2WefW6ZzmZopPj7eNkgyXrnnnntYtGgR3377LSEhIRXymoX9fYrIElU9ri+pIlsE7YA9wCQRSRCR10QkAmiiqjs8j9kJNCnsySJynYgsFpHFvpg326T/lWQH1eLslK/KfC5jjCnOo48+yqxZsyosCZRWRSaCIKAX8JKqxgFHKNAN5GkpFNpEUdVXVTVeVeN90RQirB4BPUbCyo/JTjlQ9vMZY0wVVZGJYCuwVVUXeW5/hEsMuzxdQnj+3V1hEcVfAxkpTHr5cZ9NwzLGmKqmwhKBqu4E/hCRnP7/M4HVwOfAGM+xMcC0ioqJ5nHsanAyrcIzi5znbYwx1V1FryO4FZgiIiHA78DVuGT0gYhcC2wGCh/GLydNbv2eISIV+ZLGGFOpVGgiUNVlQGGrH86syDiOIYJmZ7Ny1XIat+lCk7phfgvFGGP8oWaUmCjB4R8eo9NHZzN19jJ/h2JMqVgZat8pTRlqcKWoL7/8cmJiYujevTv9+/fn8OHDDBo0iG+//faYxz7zzDPceOONJCYmUqtWLeLi4ujSpQunnHIKb775po9/ktKzRADU6XkhoZJB5tIppKSXvlyuMV47tBMmnQuHjq9meSKsDLXvlKYMNbjFeU2aNGHFihWsXLkyd+/qghVEwVURzVkB3KFDBxISElizZg1Tp07lmWeeYdKkST79WUrLEgFAk64cahzPRdnf8cmSP/wdjanO5jwOWxbCnMd8fmorQ12xZah37NiRW4YCXOmL0NBQRowYwVdffUV6enru+7h9+/ZCy3a0b9+eiRMn8uyzz5b06y1fqlrlLr1791Zfy142VfXBuvr3R5/RrKxsn5/fVD/H/R2+8afjL4tedfcdPaL66lmqEyJVH6zr/v3fWapL33H3H957/HO9EBERoaqqmZmZOmLECP36669VVXXw4MH622+/qarqwoULddCgQaqqOmrUKH366adzn5OUlKSLFy/W7t276+HDh/XQoUPatWtXXbp0qW7atEkDAwM1ISFBVVVHjhypkydPVlXVZs2aaVpamqqqHjhwQFVVH3zwQX3iiSdyYxszZoyed955mpmZWej93bp1002bNunKlSu1Y8eOumfPHlVV3bdvX+7zP/zww2PO9+GHH2pqaqq2bNlS161bp6qqV155Ze7P1KZNG3322WdVVfWFF17Qa6+99rj3bOLEifrAAw+oqur27du1U6dOqqo6adIkvfnmm1VVdf/+/Zqd7T4H/ve//+mdd9553HkSEhK0UaNGeuqpp+r999+f+36rqp533nn62WefqarqI488on//+99VVXXTpk3arVu3Y85z4MABDQsLO+78ZVXY5ySwWAv5TLUWgYd0vZCjIZH0Pfwdc9eXfeWyMcdJ3gI561VUIWlLmU9pZaj9V4Y6NjaW33//nfHjx7N//35OPvlk1qxZAxy7wUz+bqHCaCVYw1TjylAXKTiMgCs/5Zm3dtBufiIDOzf2d0Smqrm6mHIlRw9CWhJ5C+fV3T7J0+cdEVX884tgZaiLjrkiylDnJM6LL76YgIAApk+fTpcuXbjwwgu54447WLp0KSkpKfTu3bvIeBMSEujSpcuJ/bA+Yi2CfIJb9WJ0v07M/W0P63cVXU7XmFKb8zhogUWLmu2zsQIrQ13xZajnz5/PgQOuPE16ejqrV6+mTZs2gEsQgwYN4pprrim2NZCYmMi4ceO49dZbSxW/r1kiKGBM5HLeC3mYN+dvLPnBxnhr68+QVaDkeVa6O+4jVobae74oQ71x40YGDBiQ+z7Ex8dzySWX5N5/2WWXsXz58uMSwcaNG3Onj44aNYrbbruNq6++ulTx+1qFlaH2JV+UoS7Sqs/gwzF8FfMM513i31+OqdysDLWpzCprGeqqIfo8qN2E845+4+9IjDGmQlgiKCgwGOKuRH/7lrk/LyHDitEZY6o5SwSF6e2KoS77/Fm+X+2bFaCm+gkICCAjI8PfYRhznIyMDAICvP94t0RQmMjWcMZ4Th98PkO6FrphmjFER0czefJkSwamUsnIyGDy5MlER0d7/RwbLDbmBO3evZtx48axdu3aY+bHG+NPAQEBREdH8+STT9K48bHroYoaLLYFZcU5uJ3ZX3/ArLCz+OeF3f0djalkGjduzNtvv+3vMIwpM+saKk7COwxc8yA/LvqZ7Ump/o7GGGPKhSWC4sRdiUogowNm8PZPm/0djTHGlAtLBMWp2wyJ/hOXh8zlo0Ubba8CY0y1ZImgJPHXUjv7IP3S5/PJ0m3+jsYYY3zOEkFJ2g1AG3aiX719TJq/iezsqjfLyhhjimOJoCQBAcgN8wgd8g827jliexUYY6odSwTeCArlvJjmtK+dyRvzE/0djTHG+JQlAi+F/PQM07mZX377g8S9R/wdjjHG+IwlAm+1PpWwzENMP3MPbaLC/R2NMcb4jCUCb7XuC42iabfpfUTE39EYY4zPVGgiEJFEEVkhIstEZLHnWAMR+V5E1nv+rV+RMXlNBOKvge1LefHdD5k0f5O/IzLGGJ/wR4tgkKrG5it8dA8wQ1U7AjM8tyunHqMhqBZdt3/K3sNH/R2NMcb4RGUoOnchMNBz/S1gNnC3v4IpVq1IGP0OA5rHMjCi8H1MjTGmqqnoFoEC34nIEhG5znOsiaru8FzfCRS6AYCIXCcii0Vk8Z49fpzL3/EsxJME1u085L84jDHGRyo6EfRX1V7AucDNInJG/jvVbY5Q6NJdVX1VVeNVNb5Ro0YVEGox1n/PH2+MZegzc1i65YB/YzHGmDKq0ESgqts8/+4GPgVOAXaJSDMAz7+7KzKmE5K8lVZbPqV/2CYm2QIzY0wVV2GJQEQiRKROznVgCLAS+BwY43nYGGBaRcV0wmJGQkgd7opawPQVO9iRbHsVGGOqropsETQB5onIcuBn4CtV/QZ4FDhbRNYDZ3luV26htaHnaGKSZlBXD9leBcaYKq3CEoGq/q6qPT2Xbqr6sOf4PlU9U1U7qupZqrq/omIqk95XI1lHub9FAu8u2kJqepa/IzLGmBNSqkQgIvEiMtrTtZPT3VMZpqBWvKbdIe5K4rp3Izk1g08Stvo7ImOMOSFeJQIRaSIiC3FdOu+SN8VzIvBUOcVW+V34PO0HXEGPlvV4Y57tVWCMqZq8bRE8DewCooCUfMc/xA361lhy9BD3dNzGxj1H+HHDXn+HY4wxpeZtIjgTuF9VC06a3wi09m1IVczcx+m76Cb6NMpk7yErO2GMqXq8TQS1gPRCjjcC0nwXThXUawySncnUkzdwSe+W/o7GGGNKzdtEMBcYm++2ikggribQDF8HVaU07AhtT0eWvkl2VpaVnTDGVDneJoK7gL+KyPdAKG6AeDVwGnBvOcVWdcRfA0lb+OjDt7nwhXkkpRTWeDLGmMrJq6mfqrpaRGKAG4GjQBhuoPiFfAXjaq7o8yGiEWeHrydsxFAiQmvmjFpjTNXk9SeWqu4EHizHWKquoBC4aRH1I6IY5u9YjDGmlLxdR3CLiFxRyPErROQm34dVBUVEAZCWlsYLszYwY80uPwdkjDHe8XaM4G/AH4UcTwTu8FUwVd6C5wh9pQ+f/JLI87M2+DsaY4zxireJoCVQWGW1rZ77DECD9siBRO7vtIWELUm2V4ExpkrwNhHsBGILOd4LsOW0OToOhTrNOSP5C+qEBdleBcaYKsHbRPAu8KyInC0iwZ7LEOAZYEq5RVfVBAZB7zEEbZrJDTEBtleBMaZK8DYRPAjMB77F1RpKAb4GFgD/KJ/QqqheV4EEcmXIbFTV9iowxlR6XiUCVc1Q1cuAzsDlwJ+BaFW9VFUzyjPAKqducxj2HHX7XcPQbk1trwJjTKVXqv0IVHW9qn6gqh+q6vryCqrKi7scojpwTf92tleBMabS83pBmYiMxlUhbUyBBKKqto6qoM0LiN84g5gWg3hj3iYuO7k1AQHi76iMMeY4XiUCEXkCt5ZgFrAdsB1YSvLHIuTHJ3lo2BBS6kUjlgOMMZWUty2Cq4DLVPWj8gymWom9AmY+TOzuz6DXI/6OxhhjiuTtGEEAsKwc46h+ajeCrsNg2RT2Hkjin1+sYsNuK1FtjKl8vE0ErwLH1RoyJeh9NaQlE7ZuGh8u3srSLUn+jsgYY47jbddQJPBnETkb+BU4Zsqoqt7m47iqh7b9of1AaocFs+i+M608tTGmUvL2k6kreV1D0QXus4HjoojAVdMAiPAcSk7JoF54sP9iMsaYArzdmGaQr17Qs8XlYmCbqp4vIu2AqUAUsAS4UlWr1xZfWRmwew2PJATz+fLtzL1rEMGBpVrCYYwx5cYfn0a3A2vy3X4MeFpVTwIOANf6Iaby9e19MOlc+rYMYUdyGt+s3OnviIwxJpfXiUBEBonIqyLyjYjMzH8pxTlaAucBr3luCzAYyJmW+hYw3Ovoq4qYUZB+mDOOzqFtVDhvzN/k74iMMSaXtzuUjcUVmasDDAT2APVxZahXl+L1ngHuArI9t6OAJFXN9NzeCrQoIobrRGSxiCzes2dPKV6yEmgZD01iCFj8Blf3a2t7FRhjKhVvWwTjgFs8hecygHtVNQ54BzjszQlE5Hxgt6ouOZFAVfVVVY1X1fhGjRqdyCn8RwTir4ZdKxjVbJftVWCMqVS8TQTtgR88148CtT3XnwfGenmO04BhIpKIGxweDPwXiBSRnEHrlsA2L89XtfQYBSG1qbXhSy49uZXtVWCMqTS8TQT7cN1C4D6ou3uuRwG1vDmBqt6rqi1VtS1wKTBTVS/H1S8a4XnYGGCalzFVLaF14LrZcNa/uKpvW9urwBhTaXibCH4Ehniuf4DbrWwS8B7wfRljuBu4U0Q24BLL62U8X+XVsCMEBNCqfi3bq8AYU2l4u6DsFiDMc/0RIBPX1fMB8O/SvqiqzgZme67/DpxS2nNUWT//D1Z8xDWD32Xehr2s3nGQ3m3q+zsqY0wN5u2Csv35rmfj5v6bExEcDn8sJJ7VLLrvTMJDrOyEMca/vJ0+miUijQs5HiUi1rdRGt0ugrB6yJI3CQ8JQlVJSqleC6mNMVWLt2MERW2rEgrYp1hphIRDz8tg9TQ4vIcrXl/E7VOX+TsqY0wNVmy/hIjc6bmqwA0ikn/NQCBwOrC2nGKrvnpfDYtehmVTGB57CYG2haUxxo9K6qC+1fOvAH8B8ncDpQOJwA2+D6uaaxwNg/8B7Qcwsnkrf0djjKnhik0EqtoOQERmARerqtVF8JUzxuVePZiWwdSftzAqvhWR4SF+DMoYUxN5NUagqoMKJgEROUlEwop6jvHCzhXwy2tsO5DKf6av5b2f//B3RMaYGsjbWUP/EZExnusiIj8AvwE7RKRPeQZYra34EKbfRZeIw/TrEMXbPyWSkZVd8vOMMcaHvJ01dDmwznP9XKAncCrwNvBoOcRVM/QeC5oFSydzzWnt2JGcxrerbK8CY0zF8jYRNMGViAb4E/CBqv4MPAfElUdgNUKD9tBhMCx9i8GdGtAmKpw35tleBcaYilWaonNtPNeHADM814Moeo2B8Ub8NXBwGwEbvufqfm1ZuiWJBNurwBhTgbxNBB8D74rI90AD4FvP8VhgQznEVXN0OgcadoJDOxgR34o6obZXgTGmYnmbCO4EnsXtRna2qh7xHG8GvFQegdUYgcFw0yI4+VpqhwYx2vYqMMZUMG+nj2aq6lOqeruqJuQ7/rSqvlZ+4dUQAQGgCslbGdOvLdmqTLa9CowxFaTIBWUi0gtYpqrZnutFUtWlPo+spvn6Llj1Ga3uWMXfh3QmrlWkvyMyxtQQxa0sXgw0BXZ7riuFDwwrru6QKYuOQ+DnV2HdV9w86CI+S9jGaY/OZHtSKs0jazF+aGeGx7Xwd5TGmGqouETQDtiT77opTx0GQ73WsPgNPks/hbs//pWjmW5x2bakVO79ZAWAJQNjjM8VmQhUdXNh1005CQiE3mNg5kO8v20mRzMbHHN3akYWj3+z1hKBMcbnvC0x0V5E7hSR50XkORG5Q0SsleBrcVdCQBCnp3xX6N3bk9O4fvJipv68hV0H0yo4OGNMdVXiPoki8nfcPsWBuPECARoBj4nI3ar6dPmGWIPUaQJjvuCDdw9BcuZxd4eHBPLr1mS+XbULgG7N6zI4ujGDohvTs2Wk7WtgjDkhoqpF3ynSH5iDqyf0VM7exSISBYwDxgMDVHV+BcSaKz4+XhcvXlyRL1mhPkvYxr2frCA1I2/7h1rBgTxycQwXxjZn3a5DzFy7m1lrd7Nk8wGyFRpEhHD3OZ0ZfXJrP0ZujKnMRGSJqsYXPF5Si+BG4G1VvT//QVXdB9wrIs2Am4AKTQTV3XBm0bv1dC7d95dCZw1FN61LdNO63DTwJJJTMpizfg+z1+6mSV1XFXzNjoM8MG0lDw3vTnTTuv78UYwxVUBJieBU4Jpi7n8TeN1n0RgnNYlW275i/lXXw5znYcSbrtuoEPXCgxnWsznDejbPPZacmkFqRhYNItwmN9OWbWPh7/sZHN2Y006KIjykxB5BY0wNUlLX0BEgWlUL3TFFRFoB61Q1vJziK1R17xoiZT88Fe2qk+5d5/Y4Pn/iCZ/uhVkbeHHWBo6kZxESFMCp7aMY1LkRg6Mb0yYqwoeBG2Mqs6K6hkpKBNlAU1XdXcT9TYDtqlrigjLPbmZzgVBcS+QjVX3QM/toKhAFLAGuVNX04s5V7RMBwPtXwZpp7npQGNz+a5GtAm+kZ2bzS+L+3LGF3/e6clHtG0UwuHNjzurahFPbR/kicmNMJXWiYwQA54lIchH3RZYihqPAYFU9LCLBwDwR+RpX0O5pVZ0qIi8D12KF7HALtnOuZsOcx8rUKggJCuC0kxpy2kkN+cf5XUnce4RZ63Yzc+1u3v5pMxv2HM5NBN+s3EmvNpE0rmM7kRpTE3iTCEoaAyi6SZH/Qa7pcdhzM9hzUWAw8GfP8beACdT0RHBoJ6zPt5YgKx2WTYG6zaHvLRBc9g/otg0juLphO64+rR1Hjmay/4hrhO09fJQb3lnC38/uxK1ndiQlPZO1Ow/Z9FRjqrFiE4Gqelum2isiEojr/jkJeAHYCCSpas6k+a1AoUtnReQ64DqA1q2r+RTJOY+7VkB+2Rkw8yH45XUYMN4tPgsM9snLRYQGERHq/hSiIkL4+vbTqR/uBpp/XL+X6ycvoUFECAM7NWJgdGMGdGxEvfC81/4sYRtPfLvO6iIZU0UVO0ZQbi8qEgl8CvwDeFNVT/IcbwV8rardi3t+tR8jeLk/7Fxx/PH67SCiEWz9Geq3hQH3QM9LQcrvm3pyagaz17lxhTm/7eFASgaBAULv1vUZFN0YRXluxnpSM/ISV86aB0sGxlQuJzRYXJ5E5AEgFbgbNyCdKSJ9gQmqOrS451b7RFAcVVj/vWsdhNWDsV9W2EtnZSvL/khi1lo3trB6x8EiH9sishbz7xlcYbEZY0pWVCLwaddPCQE08rQEEJFawNnAGmAWMMLzsDHAtIqKqUoSgU5D4Lo5MOptdyx5K7x2Fqz7xiWKchIYIPRuU59xQzsz/fbTWXjvmUU+dnuS22EtLd/qaGNM5VSRK4uaAW95xgkCgA9U9UsRWQ1MFZF/AwnYAjXvBARAuKdC6cEdcGQPvDcaWp4Mg/8P2g8s9xCa1gujRWQttiUdv61m88haZGcr/R+bSZO6YZzaPopT20dxSrsG1Kvlm7ENY4xv+K1rqCxqdNdQUbIy3MyiOY/DwW0uEVz+MQSWb64vri7SOd2b8sqc3/np970s3ZJEemY2Iq5Y3qntoujbIYqT2zWgbpglBmMqQqUbIygLSwTFyEiDJW+6ZDDkIXds/yZoUH5Vw72ZNZSWkcWyP5L4aeM+Fv6+j4QtSaRnZRMg8J+LYrj0lNakZWSRkZVNHUsMxpSLMiUCEamPm98/CGhMgbEFVW3smzC9Y4mgFLYnwKuDoMsFMOh+aBzt74gAlxiWbjnAwt/386eYpkQ3rcv3q3dx/eTFTLu5PzEt67H/SDohQQHUDrXaSMb4QllWFgO8DXTDLfjahZeLyEwl0KA9DLgLfnoB1nwBPUbBwHvccT8KCw6kX4eG9OvQMPdY+0YR3DK4Ix2b1Abglbkbee3HTXRvUY9T2zegb/so4ts2sMRgjI952yI4hNt3YGn5h1QyaxGcgCP7YP4z8PP/3MrkO9dAcC1/R1WsZX8k8cPqXSz8fR/L/kgiM1sJDBBiWtSjbwc3+Bzfpn7uYjhjTPHK2jW0DPiLqlaKT19LBGVwaCfs+NVNQVWF+f+F2D9D7Qrt3Su1lPRMlmw+wMLf97Hw9/0s9ySG9g0jmDluIOD2YWgTFW5lto0pQlkTwQDg/3C7kq1UVb9ODrdE4CM7lrvxg6BQ6HM99Lstb0pqJXfkqEsMR45mcm5MM1SV+H//wIBOjZg4OhZV5aff9xHXqj61QvKK41o5DFOTlTURtADeB/oWdr83Zah9yRKBD+3dALMfgZUfQ2gdV9Su360QUqFbTJRZdrby44a9RNYKpmerSDbtPcKgJ2cTHCj0bBlJ3w5RZGUrb8zfRJqVwzA1VFkTwVygPvAyhQwWq+rHPorTK5YIysGuVTDrP67b6NbFrpWgWq51jMpTWkYWC3/fx0+erqSV25LJyi78b93KYZiaoqyJIAU4RVVXlkdwpWWJoBylJbsaRhlpMOlc6HkZ9B7jEkMVdigtg5gJ3xV6nwDvX9+X6St2cPuZHanv2eLTmOqmrLWGVgO2C3pNEFbP/ZuyF4LD4evx8FxvWPo2ZGUW/9xKrE5YMC0iC58l1TyyFhv3HObjpVsJC3a9nE98u5YhT8/h9qkJvDJnI3N/28Pew0crMmRjKoy3LYJzcAvK/g9YAWTkv19V95dHcEWxFkEFUYXfZ8HMf8O2JdCgA1w9Heo09XdkJ6S4chjD41qgqoinK+zjJVuZvmIHq3ccZEdyWu7jG9UJpWuzunRpVpduzetyQc/mFf5zGHOiyto1lH+XlPxPENzmYzZYXJ2pwrqvYe1XcOHzbtxgz2/QsGOVG0M4kVlDB46ks2bHQVZ7Lmt2HGLD7kO0iKzF7PGDAHj4q9XUCQvmtjM7AnA0M4vQoAr9b2FMicq6sniQj+MxVYkIRP/JXQCO7IVXB7pEMPgfcNKZcHgXfHQ1jHgT6jTxZ7TFGh7XotQzhOpHhNDvpIb0OylvFXR6Zja7D+W1FHYfOsrRTPd9SVXp+8hM6oQF5bYeujarS5fmdWleLyy31WFMZVFiiyBno3ngKlVdVyFRlcBaBH6WlQm/vg9zHoWkLdC6H9SKhN++gd5Xw/kT/R2hX2VkZfPKnI25rYfEfUdyt4moVyuYLs3q0KVZXYZ0bUrfDlHFnsvWPRhfOuEWgapmiEg7rL6QyREYBHGXQ8xIWPoWzH4MUva4+5ZNgQF3V+pWQXkLDgzglsEdc28fOZrJ2p2HPInhIKu3H2Tqz3/QsHYofTtEsffwUa54bRH3nBvNwM6NSU3PIjUji7m/7TlmTGNbUir3fuK2MLVkYHzJ266ht4C/AuPLMRZT1QSFwCl/dfsrL5sC2Zmg2fDWBRB9npt2Wr+tv6P0u4jQIHq3qU/vNvVzj2VlKxlZrivpcFomTeuF5Zbf/nH9Hq6bvIQAgYJLH1Izsnji23WWCIxPeTtY/CJwObAJWAIcyX+/qt5WLtEVwbqGKpFDO+G/PSEzr78cCcjbMvOksyD+Gug4pNw3yakuNu87wrerdvKf6WuLfMzjl/Tg3JimtneDKZWyriPoAiwFDgDtgZh8l+6+CtJUQXMed62A/AKCoMdo10W0ayVMvQwWPOuf+KqgNlERXHdGhyLXPQQI3PXxr7mlMr5btZNX524ku4iV08aUxKuvaKpqs4ZM4bb+DFnpxx7LSofdq+DiV+CM8W4QuUUvd9+6ryHhHTj5Wmg30O29bAo1fmjnQtc9/Oei7sS2rk+jOm6196x1e/hp416uO6MDABO/W0dmtnJy2wb0al2feuHWajDFK1VbXUTCgJNwA8cbVTWthKeY6u6GecXfHxgEXc7Pu52yH7b8BGu/hPrtIP5qiL0cIhoWfY4aKmccoKRZQ49cHENKet6q75XbDzL3tz28OHsjAJ2b1CG+bX13adOAlvVr2RRWcwxvxwiCgf8AtwAhuIVkR4HngPtVNaOYp/ucjRFUcZlH3W5pi9+AzfOhYWe4eVGVW5xWmaWmuz2iFyfu55fNB0jYfIBDR12yaFo3jDH92nLjQNeCyM5WAgLsva8Jyrqg7DHgMuAG3JoCgNOBR3DjDON8EaSpIYJCIWaEu+xe4wacRVyhu8kXQbfhboyhVqS/I62yaoUE0rdDVO46haxsZd3OQyzevJ/FiQeoE+b+6yenZND/sZn8a3g3LoprydHMLDKz1HZ9q2G8bRHsBK5R1ekFjp8HvKaqzcopvkJZi6Ca2v87fHQtbF/qCt51v8TNOMoZXzA+t/tgGs/P2sAlvVrSs1Uks9bu5i9vL6ZLszrEt2nAyW0bEN+2Pk3qhvk7VOMDZa01lArEFlxZLCLRQIKqVujmt5YIqrntCbB4Eqz4EDJS4LrZ0DzO31HVCBv3HGZawjZ+STxAwh8HcmcmtWpQi/g2DXLHGTo2rn1cd5Ktgq78ypoIFgJLVPXmAsdfwiWIQncuK/DYVsDbQBPcYPOrqvpfEWmA2/2sLZAIjFLVA8WdyxJBDZGW7GYZ9Rjtuo6+fwDSU9wAc5Nu/o6u2svIymb19oP8krifJZsP8EvigdxS3D/dO5hm9Wrx69Yk0jKy2XYghfs+XVlkZVdTOZQ1EZwBTAe2AQs9h08FmgPnqmoJU0dARJoBzVR1qYjUwS1MGw6MBfar6qMicg9QX1XvLu5clghqqC/vdFNPs45Cq1Ndt1HXCyHYui0qgqqyeV8KK7Yl55bfvnnKUpb9kQS4EhgFtYgMY/49Z1ZkmKYYZUoEnhM0B24Goj2H1gAvqur2EwxoGvC85zJQVXd4ksVsVe1c3HMtEdRgR/bB8nfdjKP9v0OfG+HcR/0dVY21/0g62w6kMuz5eUUWI2sTFU6zemE0q1eLpvXCiGlRjz/FuGHF5NQM6oYF2XTWClLWWUN4PvDv91EwbYE4YBHQRFV3eO7aies6Kuw51wHXAbRu3doXYZiqKCIK+t0Kp94MiXOhXit3fMsimP0f10ro/CcItEVUFaFBRAgNIkJoHlmr0BZB7dAgerSMZEdSKj9v2s+ug2kMim6cmwjOnjiHM7s05pGLe5Cdrfz9w+U0rhtKc0/SaFYvjKb1wmgYEWpTXMtRsYnA039fotLsUCYitYGPgb+p6sH83wRUVUWk0C8Wqvoq8Cq4FoG3r2eqqYAAaD8w73bKPti7AT64Cmo3gV5XQa8xENnKbyHWJEWtgv738O7HjBFkZ+sxj7ll8Em0axgBwKGjmSzevJ+dyWlkZB37Xzw4UGhS1yWGUfGtGBnfioysbGas2UXPVpE0q1f6+So2uJ2n2K4hz85kJX3oqqp61bLwLEz7EvhWVSd6jq3DuoaML2RnwfrvXbfR+u/cauU711qxuwriqw/W7Gxlf0o6O5LS2JGcys6DaexITmNnsrs9PLYFl57Smq0HUuj/2CweuySG0Se3ZuW2ZK6fvISmnlZEs7ru3+aRea2LRrVDCQoMKHHb0urqhMYIRGRAMec8B7gdyFTVEje2F/fV/y3cwPDf8h1/AtiXb7C4gareVdy5LBGYEiVtgT3roOPZkJ0Nk4dDuzMg7kpAq8RuaqZ46ZnZrN99iKZ1w4iqHcqG3Yd4cdZGlzQOuqSRM/01R4DAi5f34qEv1xTalVU/PJjHLulBRGgQtUICCQ8JJDw4iPBQdz0sKNAvXVS+SrJlHizOd6I44AncyuJXgIdUdY8Xz+sP/AisAHJ+O/fhxgk+AFoDm3HTR4vtarJEYErlyF746BrYNMdVRq3XEg5sduMJNXw3tepMVUlOzWCHpyWR06oYHteCs56ac8I7bX11W3+6Na/HtGXbeHHWRt6//lQiw0OYtmwbM9bsJiI0kFrBQYSHBFIrJJCIkEDCQ/ISS/+ODQkNCmT/kXRSM7KKrDKbw5etlzIPFnt2KXsYGAl8AnRV1Y3ePt8zxbSoVGrzy0z5iWgIYz53Ywg/PQ9LJrnjCZNdqeygUAgKs2mo1YyIEBkeQmR4CF2aHdtpUdTgduM6obw+5mRS0jNJycgi5WgWKemZpGZkkZKeRcrRzNxV1nXDgmnbMJyQIFdBd/fBo/y6NYmU9CxS07M4kp553MZCAMsfHEJoUCCvzNnImwsSWffvcwEY9+Fyvlm5Mzdh1AoOJCI0iJXbknP3w87h6w2KSkwEIhIFPICrMzQf6Keqv/jk1Y2pSA1PcpvmBIbklc6e8xjUqg+LXnab6ESfD52GQFg9/8ZqylVRg9v3/akLMS29+90Pim7MoOjGubf/ekZ7/npG+9zbqsrRzGxS07M8SSWTlPQsanvqOJ3foznRzerkPv70jg2pGxZMakYmR466xJOakXlcEsixvZBEdqJKGiO4H7c9ZSJwj6p+47NXLgPrGjInpLDd1ILCYMQkN7i8bjoc3gUBwdBpKIx+xyqiVmNVZdbQaY/OLGKxXi3m3zO4VOc60a6hh4BUYCtwk4jcVNiDVHVYqaIxxh8K201Ns2HDD3DBM3DeRNj6i9srISsjLwl8dhM07ORaCw1PqvCwTfkYHteiUn7wF1RU62X80GInV5ZKSYngbUqePmpM1VDUbmpbf3bXAwKgdR93yZGeArtXw7Ip8MOD0Cgaos+DHpdCo04VF7upsbzdoKgsSj1rqDKwriFT4ZL+cF1Ha7+ExPlwwX+h15VweI/blrPNabaa2VR6ZZ41ZEyNFtkK+lzvLin73YAzwOrPYPo4CIuETue41sJJZ0JIhD+jNaZULBEYU1rh+SqvxF4OdZrB2q/gt6/h16luU52/r3Uzj7KzXZeTMZWYJQJjyiIkHLqc7y5ZmW4P5h3L86afTv0zpB92A83Rf4JIK5hoKh/7qmKMrwQGQfsBcNptecda9HIrm7+5G56JgVfOgIQp/ovRmEJYIjCmPA24C25eCLcuhbP/BYGhbq0CQPoR+O4fsGWhK5hnjJ/YrCFjKpqqW6OweQG8NQyyMyCikdtHIfp816oICs17/KGdViTP+ERRs4asRWBMRctZqNamH9z1O1zyOrTtDys/hndHwt7f3P0Hd0DaQbcQbstCVw7DmHJgg8XG+FNYXYgZ4S6ZR91gc5Pu7r6Z/4blU4FstwJ62RRXJM9aBcbHrEVgTGURFAodBue1GOKvgcbReWUxMtNg0jn+i89UW5YIjKms6rWAfRuOPZa0BQ7tcusTpoyEeU/DPq+rwRtTKEsExlRWhRXJkwA3VnBkNxzZAz9MgOd6wUunwexHXaIwppQsERhTWRVXJK9OU7huNvxtBQx9BELruESQM9CctAW2LXUzlIwpgU0fNaa6OLQTwqNc8bsf/gnzJkK9VtDlAndp1QcCAv0dpfEjKzpnTHVXp2ne9X63QsOOsPpz+OV1WPgi1G/nFrZZ7SNTgCUCY6qj8AYQ+2d3OXrI7cB2aFdeEnjzfNda6DoM2g+y/ZprOEsExlR3oXWg+yV5tzPToW4LVzF1+bsQUhs6DoFTroM2ff0Xp/EbSwTG1DRBIXDxKy4hJM6FNV+4pHDSWS4RHNwBv8+GzudArfr+jtZUAEsExtRUQSHuw/+ks9x+zTmF7377Gr68AwKCoN0Z0GWY23CndmP/xmvKjY0aGWPcbKIgz65rvcbCX2ZC35th/yb48m8wsQukHnD3Z6YXdRZTRVVYi0BE3gDOB3aranfPsQbA+0BbIBEYpaoHKiomY0whAgKgZW93OeufsGsVbFuS10303mhIS/ZMSx0GUR3ynmuVUqukimwRvAkULJRyDzBDVTsCMzy3jTGVhQg07Q69x+Qd6zDYLVTLWdX8Yj9YOtndZ5VSq6QKSwSqOhfYX+DwhcBbnutvAcMrKh5jzAnqdytcNwv+thLOedRty5m637UGlr3jymIkTIYDVu6iqvD3YHETVd3hub4TsLakMVVFZCs49UZ3UYWv/p434JyVDs/GQrvTod0At54h/4I3U6lUmsFidbUuiqx3ISLXichiEVm8Z8+eCozMGFOiw7vcfgnZmcceT94OM/6ZN9CcOB9+/h/sXW91kCoRf7cIdolIM1XdISLNgN1FPVBVXwVeBVdrqKICNMZ4obBKqQGBrkVw9VduK05w6xUWvuCu12nutuVsNwB6jLbSF37k73f+cyBnFGoMMM2PsRhjTlRxlVJrN87bbGfow67e0flPQ6tT4Ldv3cByThJIeAfWfAmpSRUafk1XYdVHReQ9YCDQENgFPAh8BnwAtAY246aPFhxQPo5VHzWmmsjOhsM7oW5z11X03x6uhLYEQPM411qIPg9aHlcw05wAv1cfVdXLirjrzIqKwRhTyQQEuCQArtVwyxLYttiVuPh9Dix41u3l3DIesjLgp+eh7RnQPNZKavuQv8cIjDEmT1AItOnnLoPuc5VTM4+6+3atcmsXAELrQdv+0H6gq6BqM5LKxBKBMabyCq3jLuBaAePWw6a5rsWwaQ6s+woad3GJYNcq2PGrG4DOaWUYr1giMMZUHbUbQ8wIdwFXC6luC3d91Wcw93F3PaqjSwjtB0Knc9yubaZI/p41ZIwxJ65Bu7xieQPvhRvmwZCHoX5bWPYeTLvZDTwDrJ0OG2dBRmre8w/thEnnuk17ajBrERhjqoeAAGga4y79bnFVUg9syhtUnvkQ7F4NgaFu6mr7AbBzRV5tpPMn+jd+P7JEYIypnoJCoFHnvNvXfgebf3JjC7/Phpn/dq0FzXarouu1gk5DoFGXGre4rcLWEfiSrSMwxpTZpzfCig8hOwMCgt2/AOENPTWSzoBO50LdZv6N04eKWkdQs9KeMcaAGxtY9Uneh392BgSFwtBH3I5tWxa6Xdr+WOjuP5DoxhySt/kt5PJkXUPGmJqnsNpIqrBvg9vPWRX2bczbXGfdN/DN3e56gw6utdB+gJuRFFyrYmMvB5YIjDE1T3G1kcCtcm54Ut59p1znFrBtmuPWMaz4yO25cPdmd/+GH9zK5zanQVjdivkZfMgSgTGm5rlhXukeHxDgdmpr2t3t5ZyVCXt/g9Da7v75z7okIYGeGklnwElnuuRRBVgiMMaY0goMgiZd827/+QPY+otrLWya62ok7ViWlwh+eR2adIPmvfLWPVQilgiMMaasgsM8M41OB+6Ho4chZa+7L+0gTB8PmgXBEdCmr2sxRJ8PUR38GnYOSwTGGONrobXzuo3C6sL4DZA4L6/F8P0DEBzuEsGhXbB6mksOjTrn7d1QgSwRGGNMeQtv4Kqkdh3mbh/a6aarAmyeD1+Pd9drN3EJod0Z0PVCCKuXd45DO+Gjq2HEm3mzmXzEEoExxlS0/GWzu18MLXrltRY2zXUL3Tqc6RLBprkuCWycWW7lMCwRGGOMv9Vv6y69rspbw1DPU1U14R349f28xy6bAgPu9mmrwFYWG2NMZVJwDcPwl6DLhW5qKriFcHMe8+lLWiIwxpjK7MgeWP+tm3UEbuHbsik+LZ1ticAYYyqzQsth+LZVYInAGGMqs5LKYfiADRYbY0xlVtpyGCfAWgTGGFPDWSIwxpgazhKBMcbUcJYIjDGmhrNEYIwxNVyV3LxeRPYAm0/w6Q2BvT4Mp7xVpXgt1vJTleKtSrFC1Yq3rLG2UdVGBQ9WyURQFiKyWFXj/R2Ht6pSvBZr+alK8ValWKFqxVtesVrXkDHG1HCWCIwxpoariYngVX8HUEpVKV6LtfxUpXirUqxQteItl1hr3BiBMcaYY9XEFoExxph8LBEYY0wNV2MSgYi8ISK7RWSlv2MpiYi0EpFZIrJaRFaJyO3+jqk4IhImIj+LyHJPvP/0d0wlEZFAEUkQkS/9HUtJRCRRRFaIyDIRWezveIojIpEi8pGIrBWRNSLS198xFUVEOnve05zLQRH5m7/jKoqI3OH5/7VSRN4TkTCfnbumjBGIyBnAYeBtVe3u73iKIyLNgGaqulRE6gBLgOGqutrPoRVKRASIUNXDIhIMzANuV9WFfg6tSCJyJxAP1FXV8/0dT3FEJBGIV9VKv+hJRN4CflTV10QkBAhX1SQ/h1UiEQkEtgF9VPVEF6uWGxFpgft/1VVVU0XkA2C6qr7pi/PXmBaBqs4F9vs7Dm+o6g5VXeq5fghYA7Twb1RFU+ew52aw51Jpv2GISEvgPOA1f8dSnYhIPeAM4HUAVU2vCknA40xgY2VMAvkEAbVEJAgIB7b76sQ1JhFUVSLSFogDFvk5lGJ5ulqWAbuB71W1Msf7DHAXkF3C4yoLBb4TkSUicp2/gylGO2APMMnT7faaiET4OygvXQq85+8giqKq24AngS3ADiBZVb/z1fktEVRiIlIb+Bj4m6oe9Hc8xVHVLFWNBVoCp4hIpex+E5Hzgd2qusTfsZRCf1XtBZwL3Ozp5qyMgoBewEuqGgccAe7xb0gl83RhDQM+9HcsRRGR+sCFuGTbHIgQkSt8dX5LBJWUp6/9Y2CKqn7i73i85ekKmAWc4+dQinIaMMzT7z4VGCwi7/g3pOJ5vg2iqruBT4FT/BtRkbYCW/O1Bj/CJYbK7lxgqaru8ncgxTgL2KSqe1Q1A/gE6Oerk1siqIQ8g6+vA2tUdaK/4ymJiDQSkUjP9VrA2cBavwZVBFW9V1VbqmpbXHfATFX12TcrXxORCM+EATzdLEOASjnzTVV3An+ISGfPoTOBSjnBoYDLqMTdQh5bgFNFJNzz+XAmbuzQJ2pMIhCR94CfgM4islVErvV3TMU4DbgS9201Z2rbn/wdVDGaAbNE5FfgF9wYQaWflllFNAHmichy4GfgK1X9xs8xFedWYIrnbyEW+I9/wymeJ7mejfuGXWl5WlkfAUuBFbjPbp+Vm6gx00eNMcYUrsa0CIwxxhTOEoExxtRwlgiMMaaGs0RgjDE1nCUCY4yp4SwR1GAiMltEnvfTa6uIjPDHa3ujssfnC54qlhO8eNwsEbmqAkIq7LWL/T14qt5eUpExVUeWCKopzyKvFz0ljI+KyC4RmSEiZ+d72MXAvf6K0ddEpJfng+P0Iu5/X0QWVHRcxSnqg05E3qwMJbJF5DygFTAl37FET9wqIqmektPjPQudKtpDwKMiYp9lZWBvXvX1Ma4UwbVAJ+B84GsgKucBqrrfU920yhGRoIIfPJ6KrcuAawp5fBQwHKs4Wlq3A2+qalaB4//CLSTsgiuG9h/AHwXxpgN1cGUizAmyRFANeco9nA7co6ozVHWzqv6iqk+q6tR8jzuma8jzTe//ROQVzyYdW0VkfIFzdxKROSKSJiLrRORPInJYRMZ67m/r+aYYX+B5JTXxH/WcL9UTx+P5N94QkQmeroyxIrIROAoUVtnyNWCkp2Bffld4nvO+iJwjIj+KyAER2S8i34pIl2Ji8+pnEpEWIjLVc94DIvKViHQs6rylISIXi8ivnvdnv+d30CTf/ReIq06aJiKbRORhccXUcu5vLCLTPM/fLCLHJctCXrMRrsbNF4XcfUhVd6pqoqq+BvyKK3+R89wOntfbKSJHRGSpuIJ/+c9f4t9bITHdLSJ7ReRUcMUOccngspJ+HlM0SwTV02HPZZiUfhejO3BL2HsBjwGPi2eXKU/z+1MgEzgVGAs8CIT6IOYjuG/yXYCbcHWA7i/wmHbAn4GRQE8grZDzTAECgdEFjl8LvK+qR3AJ5Blci2kgkAx8kf+Ds7REJBxXbC8NGAD0xZUL/sFz3wkTkaa4Anlv4d6fM4DJ+e4fivu5nwe64d7HERxb3uFN4CTcB/tw4CqgbQkv3R+XPIusbSTOQE9cGfnuqo1rgZ6N+119DHwiItEFTlHk31shr/MkroTFgAKbHv2Me8/NiVJVu1TDC3AJbiOeNFyNpSdxuy/lf8xs4Pl8txOB9wo8Zj3wf57rQ3FJoEW++/vh6uWP9dxu67kdX+A8Cowo6nYh8d8AbMh3ewLug6aJFz/7O8CCfLdP9rxenyIeHwFk4co9HxefNz8T7sN3PZ6yLZ5jgcA+YFQxsRb6PuA+uL/0XO/leVybIs4xF/hHgWPDcV8GBNc1qMBp+e5v4/mZJxQT29+AzYUcT8QliMNAuufcqUC/En4vC3P+lrz5e8v3/owGJgG/FfYe4EpIZwNBFf3/rLpcrEVQTanqx7i65Rfgvpn1AxaKyH0lPPXXAre3A40916OB7eopi+zxCz7Y4EVERojIPE9XwmHgaaB1gYdtVe9KBb8G9M337fMaYKV6yiN7ui3eFZGNInIQ2IVrHRd8vdLojWuxHPJ0lR3GtTTqAx3KcF6A5cAPwEoR+VhEbvR02+R/7ftzXtfz2u/iElxT3Lf1bNw3ZwDU7cRV0g5XtSi81QUwEVdUbgCuJfRPVc0diBdXNfVxcftuH/DEFM/x73Fxf285nsS13Ppr4TuIpeISns/28K1pLBFUY6qapqrfq+q/VLUfrrT1hBK6QDIK3FZK93eSkxRyB3LF7a1QJE9/71TgW1ziigP+D7flZX5HvIxhDrABuEZcWezL8Gyf6PEl0Ai4Hujjeb1MoKj3xZufKQA3UB1b4NIJeKWYWA8B9Qo5HolLJKjrBx/iufyK6+ZaLyI98732Pwu8bg+gI27HsBylrTC5F5fICrNPVTeo6k+41uc4ERmU7/4ncV14/8Ali1hcIir4Hnvz9/Y9LqEVVYG3AZCmedulmlIK8ncApkKtxv3Ow3BN+tJaCzQXkeaqmvNtMp5j/+PmfPA0y3cstoTzngZsU9WHcg6ISJsTiA9weyiLyBu4GS9rcd9sJ3vOG4Vr2dykqrM8x3pR/P8Fb36mpbiEs1dLt0/vOtw3+txEJW4j9Z647pDcnwnXxfeTiPwLWIXrMlnuee1oVd1Q2AuIyFrc7+gUYIHnWGtci7E4CUAjEWmoqnuLepCqHhA36eBpEYnzxNofeNvTMsUzVtUB171TWtNxZaI/FBFV1bcK3N8d9x6YE2QtgmpIRKJEZKaIXCEiPUSknYiMxO3TO0NPfNvL73EfXG+JSE/PN/mJuG/TrkNXNRXXF3y3iHQTkX64b4fF+Q1oISKXi0h7EbmRss8CeQto6Hntz1R1n+f4Adw33b+KyEkiMgB42fMzFMrLn2kKrotpmogM8LznZ4jIUyXMHJqIa7ncLG5GViyuznwDz7+IyKme2TUnez7Ah+Hm9uds+vIv4M8i8i8R6S4i0Z6utsc98a8DvgFeEZG+ntd4E9elUpwE3B7U/Ut4HMCLQGdcKwDc7/QicWs7YnDjNifcdaNuf4uRwMty/OK203E/nzlBlgiqp8O4D67bcd0kq3AzSN7l+Nk0XlPVbOAi3Cyhn3Eftg/jkkD+vuScqYm/4LpF/q+E834BPIGbyfMrbqbJAycap+ec23HfJOuTb+2A52cYjes6WQm8gOu+OFrCKYv9mVQ1BTeb53fc3rdrce9PfVzyKSrO94CrPZfFuA+0psDp6nb8AtdFdBquS2s98BTwkKq+4znHt8B5wCDc7+Vn3F7BW/K91FhgEzATNx30XdxgbZE8XVJvAJcX9zjPY3fjWl0TPLPL7sQlkR9xY1QLPddPmCcZjMIltKvATdnFjX9NKu65pni2MY0pE08/9TLcjJqqtCG88YKINMa1PE5W1U3+jqcgEXkCqKeq/ljMVm3YGIEpFRG5CDdoux43rXIief3UpppR1d3iFp+1xrUoKpvdlNz1aEpgLQJTKp4m+f/h+qgP4NYi3OHltE5jTCVkicAYY2o4Gyw2xpgazhKBMcbUcJYIjDGmhrNEYIwxNZwlAmOMqeH+Hy6iDrJ8QcLXAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "singular_value = singular_value_list[-1]\n", + "err_subfull, err_local, err_SVD = [], [], []\n", + "U, D, V_dagger = np.linalg.svd(M, full_matrices=True)\n", + "\n", + "# 计算 2-norm 误差\n", + "for i in range(T):\n", + " lowrank_mat = np.matrix(U[:, :i]) * np.diag(D[:i])* np.matrix(V_dagger[:i, :])\n", + " recons_mat = np.matrix(U_learned[:, :i]) * np.diag(singular_value[:i])* np.matrix(V_dagger_learned[:i, :])\n", + " err_local.append(norm(lowrank_mat - recons_mat)) \n", + " err_subfull.append(norm(M_err - recons_mat))\n", + " err_SVD.append(norm(M_err- lowrank_mat))\n", + "\n", + "# 画图 \n", + "fig, ax = plt.subplots()\n", + "ax.plot(list(range(1, T+1)), err_subfull, \"o-.\", label = 'Reconstruction via VQSVD')\n", + "ax.plot(list(range(1, T+1)), err_SVD, \"^--\", label='Reconstruction via SVD')\n", + "plt.xlabel('Singular Value Used (Rank)', fontsize = 14)\n", + "plt.ylabel('Norm Distance', fontsize = 14)\n", + "leg = plt.legend(frameon=True)\n", + "leg.get_frame().set_edgecolor('k')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 案例2:图像压缩\n", + "\n", + "为了做图像处理,我们先引入必要的 package。" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "# 图像处理包 PIL\n", + "from PIL import Image\n", + "\n", + "# 打开提前准备好的图片\n", + "img = Image.open('./figures/MNIST_32.png')\n", + "imgmat = np.array(list(img.getdata(band=0)), float)\n", + "imgmat.shape = (img.size[1], img.size[0])\n", + "imgmat = np.matrix(imgmat)/255" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# 然后我们看看经典奇异值的分解效果\n", + "U, sigma, V = np.linalg.svd(imgmat)\n", + "\n", + "for i in range(5, 16, 5):\n", + " reconstimg = np.matrix(U[:, :i]) * np.diag(sigma[:i]) * np.matrix(V[:i, :])\n", + " plt.imshow(reconstimg, cmap='gray')\n", + " title = \"n = %s\" % i\n", + " plt.title(title)\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "# 然后我们再来看看量子版本的分解效果:\n", + "time_start = time.time()\n", + "\n", + "# 超参数设置\n", + "N = 5 # 量子比特数量\n", + "T = 8 # 设置想要学习的阶数\n", + "D = 80 # 量子神经网络的深度\n", + "ITR = 200 # 迭代次数\n", + "LR = 0.02 # 学习速率\n", + "SEED = 14 # 随机数种子\n", + "\n", + "# 设置等差的学习权重\n", + "weight = np.arange(2 * T, 0, -2).astype('complex128')\n", + "\n", + "\n", + "def Mat_generator():\n", + " imgmat = np.array(list(img.getdata(band=0)), float)\n", + " imgmat.shape = (img.size[1], img.size[0])\n", + " lenna = np.matrix(imgmat)\n", + " return lenna.astype('complex128')\n", + "\n", + "M_err = Mat_generator()\n", + "U, D, V_dagger = np.linalg.svd(Mat_generator(), full_matrices=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "# 设置电路参数\n", + "cir_depth = 80 # 电路深度\n", + "block_len = 1 # 每个模组的长度\n", + "theta_size = N * block_len * cir_depth # 网络参数 theta 的大小\n", + "\n", + "# 定义量子神经网络\n", + "def U_theta(theta):\n", + "\n", + " # 用 UAnsatz 初始化网络\n", + " cir = UAnsatz(N)\n", + " \n", + " # 搭建层级结构:\n", + " for layer_num in range(cir_depth):\n", + " \n", + " for which_qubit in range(N):\n", + " cir.ry(theta[block_len * layer_num * N + which_qubit], which_qubit)\n", + "\n", + " for which_qubit in range(1, N):\n", + " cir.cnot([which_qubit - 1, which_qubit])\n", + "\n", + " return cir.U" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [], + "source": [ + "class NET(fluid.dygraph.Layer):\n", + " \n", + " # 初始化长度为 theta_size 的可学习参数列表,并用 [0, 2*pi] 的均匀分布来填充初始值\n", + " def __init__(self, shape, param_attr=fluid.initializer.Uniform(low=0.0, high=2 * np.pi), dtype='float64'):\n", + " super(NET, self).__init__()\n", + " \n", + " # 创建用来学习 U 的参数 theta\n", + " self.theta = self.create_parameter(shape=shape, attr=param_attr, dtype=dtype, is_bias=False)\n", + " \n", + " # 创建用来学习 V_dagger 的参数 phi\n", + " self.phi = self.create_parameter(shape=shape, attr=param_attr, dtype=dtype, is_bias=False)\n", + " \n", + " # 我们需要将 Numpy array 转换成 Paddle 动态图模式中支持的 variable\n", + " self.M = fluid.dygraph.to_variable(Mat_generator())\n", + " self.weight = fluid.dygraph.to_variable(weight)\n", + "\n", + " # 定义损失函数和前向传播机制\n", + " def forward(self):\n", + " \n", + " # 获取量子神经网络的酉矩阵表示\n", + " U = U_theta(self.theta)\n", + " U_dagger = hermitian(U)\n", + " \n", + " \n", + " V = U_theta(self.phi)\n", + " V_dagger = hermitian(V)\n", + " \n", + " # 初始化损失函数和奇异值存储器\n", + " loss = 0 \n", + " singular_values = np.zeros(T)\n", + " \n", + " # 定义损失函数\n", + " for i in range(T):\n", + " loss -= self.weight.real[i] * matmul(U_dagger, matmul(self.M, V)).real[i][i]\n", + " singular_values[i] = (matmul(U_dagger, matmul(self.M, V)).real[i][i]).numpy()\n", + " \n", + " # 函数返回两个矩阵 U 和 V_dagger、 学习的奇异值以及损失函数 \n", + " return U, V_dagger, loss, singular_values" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100% |########################################################################|\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "主程序段总共运行了 871.9080681800842 秒\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# 记录优化中间过程\n", + "loss_list, singular_value_list = [], []\n", + "U_learned, V_dagger_learned = [], []\n", + "\n", + "\n", + "# 启动 Paddle 动态图模式\n", + "with fluid.dygraph.guard():\n", + " \n", + " net = NET([theta_size])\n", + " # 一般来说,我们利用Adam优化器来获得相对好的收敛,当然你可以改成SGD或者是RMS prop.\n", + " opt = fluid.optimizer.AdagradOptimizer(learning_rate=LR, parameter_list=net.parameters())\n", + " \n", + " # 优化循环\n", + " pbar = ProgressBar()\n", + " for itr in pbar(range(ITR)):\n", + " \n", + " # 前向传播计算损失函数\n", + " U, V_dagger, loss, singular_values = net()\n", + " \n", + " # 在动态图机制下,反向传播极小化损失函数\n", + " loss.backward()\n", + " opt.minimize(loss)\n", + " net.clear_gradients()\n", + "\n", + " # 记录优化中间结果\n", + " loss_list.append(loss[0][0].numpy())\n", + " singular_value_list.append(singular_values)\n", + "\n", + " # 记录最后学出的两个酉矩阵 \n", + " U_learned = U.real.numpy() + 1j * U.imag.numpy()\n", + " V_dagger_learned = V_dagger.real.numpy() + 1j*V_dagger.imag.numpy()\n", + "\n", + "singular_value = singular_value_list[-1]\n", + "mat = np.matrix(U_learned.real[:, :T]) * np.diag(singular_value[:T])* np.matrix(V_dagger_learned.real[:T, :])\n", + "\n", + "reconstimg = mat\n", + "plt.imshow(reconstimg, cmap='gray')\n", + "\n", + "time_span = time.time() - time_start \n", + "print('主程序段总共运行了', time_span, '秒')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 参考文献:\n", + "\n", + "[1] [Wang, X., Song, Z. & Wang, Y. Variational Quantum Singular Value Decomposition. arXiv:2006.02336 (2020).](https://arxiv.org/abs/2006.02336)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/tutorial/VQSVD/VQSVD_Tutorial_CN.pdf b/tutorial/VQSVD/VQSVD_Tutorial_CN.pdf new file mode 100644 index 0000000000000000000000000000000000000000..27070b52b4287a87d8b2b7a6aac996214d5266da Binary files /dev/null and b/tutorial/VQSVD/VQSVD_Tutorial_CN.pdf differ diff --git a/tutorial/VQSVD/figures/MNIST_32.png b/tutorial/VQSVD/figures/MNIST_32.png new file mode 100644 index 0000000000000000000000000000000000000000..66a312bf276a1d7922179d57a1fb4057b3ee33ed Binary files /dev/null and b/tutorial/VQSVD/figures/MNIST_32.png differ diff --git a/tutorial/VQSVD/figures/MNIST_7.png b/tutorial/VQSVD/figures/MNIST_7.png new file mode 100644 index 0000000000000000000000000000000000000000..5d5d483be1a85da2ab58fa331aceb5d517694078 Binary files /dev/null and b/tutorial/VQSVD/figures/MNIST_7.png differ