提交 9a5ad8a6 编写于 作者: Q Quleaf

update to v2.2.1

上级 9c11f358
...@@ -7,7 +7,7 @@ English | [简体中文](README_CN.md) ...@@ -7,7 +7,7 @@ English | [简体中文](README_CN.md)
- [Install PaddlePaddle](#install-paddlepaddle) - [Install PaddlePaddle](#install-paddlepaddle)
- [Install Paddle Quantum](#install-paddle-quantum) - [Install Paddle Quantum](#install-paddle-quantum)
- [Environment setup for Quantum Chemistry module](#environment_setup_for_quantum_chemistry_module) - [Environment setup for Quantum Chemistry module](#environment_setup_for_quantum_chemistry_module)
- [Run programs](#run-programs) - [Run Example](#run-example)
- [Introduction and developments](#introduction-and-developments) - [Introduction and developments](#introduction-and-developments)
- [Quick start](#quick-start) - [Quick start](#quick-start)
- [Tutorials](#tutorials) - [Tutorials](#tutorials)
...@@ -18,7 +18,7 @@ English | [简体中文](README_CN.md) ...@@ -18,7 +18,7 @@ English | [简体中文](README_CN.md)
- [Copyright and License](#copyright-and-license) - [Copyright and License](#copyright-and-license)
- [References](#references) - [References](#references)
[Paddle Quantum (量桨)](https://qml.baidu.com/) is a quantum machine learning (QML) toolkit developed based on Baidu PaddlePaddle. It provides a platform to construct and train quantum neural networks (QNNs) with easy-to-use QML development kits supporting combinatorial optimization, quantum chemistry and other cutting-edge quantum applications, making PaddlePaddle the first deep learning framework in China that supports quantum machine learning. [Paddle Quantum (量桨)](https://qml.baidu.com/) is the world's first cloud-integrated quantum machine learning platform based on Baidu PaddlePaddle. It supports the building and training of quantum neural networks, making PaddlePaddle the first deep learning framework in China. Paddle Quantum is feature-rich and easy to use. It provides comprehensive API documentation and tutorials help users get started right away.
<p align="center"> <p align="center">
<a href="https://qml.baidu.com/"> <a href="https://qml.baidu.com/">
...@@ -33,7 +33,7 @@ English | [简体中文](README_CN.md) ...@@ -33,7 +33,7 @@ English | [简体中文](README_CN.md)
</a> </a>
<!-- PyPI --> <!-- PyPI -->
<a href="https://pypi.org/project/paddle-quantum/"> <a href="https://pypi.org/project/paddle-quantum/">
<img src="https://img.shields.io/badge/pypi-v2.2.0-orange.svg?style=flat-square&logo=pypi"/> <img src="https://img.shields.io/badge/pypi-v2.2.1-orange.svg?style=flat-square&logo=pypi"/>
</a> </a>
<!-- Python --> <!-- Python -->
<a href="https://www.python.org/"> <a href="https://www.python.org/">
...@@ -55,7 +55,7 @@ Paddle Quantum aims at establishing a bridge between artificial intelligence (AI ...@@ -55,7 +55,7 @@ Paddle Quantum aims at establishing a bridge between artificial intelligence (AI
## Features ## Features
- Easy-to-use - Easy-to-use
- Many online learning resources (Nearly 40 tutorials) - Many online learning resources (Nearly 50 tutorials)
- High efficiency in building QNN with various QNN templates - High efficiency in building QNN with various QNN templates
- Automatic differentiation - Automatic differentiation
- Versatile - Versatile
...@@ -71,7 +71,7 @@ Paddle Quantum aims at establishing a bridge between artificial intelligence (AI ...@@ -71,7 +71,7 @@ Paddle Quantum aims at establishing a bridge between artificial intelligence (AI
### Install PaddlePaddle ### Install PaddlePaddle
This dependency will be automatically satisfied when users install Paddle Quantum. Please refer to [PaddlePaddle](https://www.paddlepaddle.org.cn/install/quick)'s official installation and configuration page. This project requires PaddlePaddle 2.2.0+. This dependency will be automatically satisfied when users install Paddle Quantum. Please refer to [PaddlePaddle](https://www.paddlepaddle.org.cn/install/quick)'s official installation and configuration page. This project requires PaddlePaddle 2.2.0 to 2.3.0.
### Install Paddle Quantum ### Install Paddle Quantum
...@@ -151,6 +151,7 @@ We provide tutorials covering quantum simulation, machine learning, combinatoria ...@@ -151,6 +151,7 @@ We provide tutorials covering quantum simulation, machine learning, combinatoria
8. [Hamiltonian Simulation with Product Formula](./tutorials/quantum_simulation/HamiltonianSimulation_EN.ipynb) 8. [Hamiltonian Simulation with Product Formula](./tutorials/quantum_simulation/HamiltonianSimulation_EN.ipynb)
9. [Simulate the Spin Dynamics on a Heisenberg Chain](./tutorials/quantum_simulation/SimulateHeisenberg_EN.ipynb) 9. [Simulate the Spin Dynamics on a Heisenberg Chain](./tutorials/quantum_simulation/SimulateHeisenberg_EN.ipynb)
10. [Distributed Variational Quantum Eigensolver Based on Schmidt Decomposition](./tutorials/quantum_simulation/DistributedVQE_EN.ipynb) 10. [Distributed Variational Quantum Eigensolver Based on Schmidt Decomposition](./tutorials/quantum_simulation/DistributedVQE_EN.ipynb)
11. [Quantum Signal Processing and Quantum Singular Value Transformation](./tutorials/quantum_simulation/QSP_and_QSVT_EN.ipynb)
- [Machine Learning](./tutorials/machine_learning) - [Machine Learning](./tutorials/machine_learning)
1. [Encoding Classical Data into Quantum States](./tutorials/machine_learning/DataEncoding_EN.ipynb) 1. [Encoding Classical Data into Quantum States](./tutorials/machine_learning/DataEncoding_EN.ipynb)
...@@ -160,6 +161,8 @@ We provide tutorials covering quantum simulation, machine learning, combinatoria ...@@ -160,6 +161,8 @@ We provide tutorials covering quantum simulation, machine learning, combinatoria
5. [Quantum Autoencoder](./tutorials/machine_learning/QAutoencoder_EN.ipynb) 5. [Quantum Autoencoder](./tutorials/machine_learning/QAutoencoder_EN.ipynb)
6. [Quantum GAN](./tutorials/machine_learning/QGAN_EN.ipynb) 6. [Quantum GAN](./tutorials/machine_learning/QGAN_EN.ipynb)
7. [Variational Quantum Singular Value Decomposition (VQSVD)](./tutorials/machine_learning/VQSVD_EN.ipynb) 7. [Variational Quantum Singular Value Decomposition (VQSVD)](./tutorials/machine_learning/VQSVD_EN.ipynb)
8. [Data Encoding Analysis](./tutorials/machine_learning/EncodingAnalysis_EN.ipynb)
9. [Quantum Neural Network Approximating Functions](./tutorials/machine_learning/QApproximating_EN.ipynb)
- [Combinatorial Optimization](./tutorials/combinatorial_optimization) - [Combinatorial Optimization](./tutorials/combinatorial_optimization)
1. [Quantum Approximation Optimization Algorithm (QAOA)](./tutorials/combinatorial_optimization/QAOA_EN.ipynb) 1. [Quantum Approximation Optimization Algorithm (QAOA)](./tutorials/combinatorial_optimization/QAOA_EN.ipynb)
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
- [Copyright and License](#copyright-and-license) - [Copyright and License](#copyright-and-license)
- [References](#references) - [References](#references)
[Paddle Quantum(量桨)](https://qml.baidu.com/)是基于百度飞桨开发的量子机器学习工具集,支持量子神经网络的搭建与训练,提供易用的量子机器学习开发套件与量子优化、量子化学等前沿量子应用工具集,使得百度飞桨也因此成为国内首个支持量子机器学习的深度学习框架 [Paddle Quantum(量桨)](https://qml.baidu.com/)是基于百度飞桨研发的全球首个云量一体的量子机器学习平台。量桨支持量子神经网络的搭建与训练等功能,使得百度飞桨也因此成为国内首个支持量子机器学习的深度学习框架。量桨具备轻松上手、功能丰富等特点,提供了完善的API文档和用例教程,使用户可以快速入门和上手
<p align="center"> <p align="center">
<a href="https://qml.baidu.com/"> <a href="https://qml.baidu.com/">
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
</a> </a>
<!-- PyPI --> <!-- PyPI -->
<a href="https://pypi.org/project/paddle-quantum/"> <a href="https://pypi.org/project/paddle-quantum/">
<img src="https://img.shields.io/badge/pypi-v2.2.0-orange.svg?style=flat-square&logo=pypi"/> <img src="https://img.shields.io/badge/pypi-v2.2.1-orange.svg?style=flat-square&logo=pypi"/>
</a> </a>
<!-- Python --> <!-- Python -->
<a href="https://www.python.org/"> <a href="https://www.python.org/">
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
## 特色 ## 特色
- 轻松上手 - 轻松上手
- 丰富的在线学习资源(近 40 篇教程案例) - 丰富的在线学习资源(近 50 篇教程案例)
- 通过模板高效搭建量子神经网络 - 通过模板高效搭建量子神经网络
- 自动微分框架 - 自动微分框架
- 功能丰富 - 功能丰富
...@@ -71,7 +71,7 @@ ...@@ -71,7 +71,7 @@
### 安装 PaddlePaddle ### 安装 PaddlePaddle
当用户安装 Paddle Quantum 时会自动下载安装这个关键依赖包。关于 PaddlePaddle 更全面的安装信息请参考 [PaddlePaddle](https://www.paddlepaddle.org.cn/install/quick) 安装配置页面。此项目需求 PaddlePaddle 2.2.0+ 当用户安装 Paddle Quantum 时会自动下载安装这个关键依赖包。关于 PaddlePaddle 更全面的安装信息请参考 [PaddlePaddle](https://www.paddlepaddle.org.cn/install/quick) 安装配置页面。此项目需求 PaddlePaddle 2.2.0 到 2.3.0
### 安装 Paddle Quantum ### 安装 Paddle Quantum
...@@ -80,6 +80,7 @@ ...@@ -80,6 +80,7 @@
```bash ```bash
pip install paddle-quantum pip install paddle-quantum
``` ```
用户也可以选择下载全部文件后进行本地安装, 用户也可以选择下载全部文件后进行本地安装,
```bash ```bash
...@@ -88,7 +89,6 @@ cd quantum ...@@ -88,7 +89,6 @@ cd quantum
pip install -e . pip install -e .
``` ```
### 量子化学模块的环境设置 ### 量子化学模块的环境设置
我们的量子化学模块是基于 `Psi4` 进行开发的,所以在运行量子化学模块之前需要先行安装该 Python 包。 我们的量子化学模块是基于 `Psi4` 进行开发的,所以在运行量子化学模块之前需要先行安装该 Python 包。
...@@ -160,6 +160,8 @@ Paddle Quantum(量桨)建立起了人工智能与量子计算的桥梁,为 ...@@ -160,6 +160,8 @@ Paddle Quantum(量桨)建立起了人工智能与量子计算的桥梁,为
8. [利用 Product Formula 模拟时间演化](./tutorials/quantum_simulation/HamiltonianSimulation_CN.ipynb) 8. [利用 Product Formula 模拟时间演化](./tutorials/quantum_simulation/HamiltonianSimulation_CN.ipynb)
9. [模拟一维海森堡链的自旋动力学](./tutorials/quantum_simulation/SimulateHeisenberg_CN.ipynb) 9. [模拟一维海森堡链的自旋动力学](./tutorials/quantum_simulation/SimulateHeisenberg_CN.ipynb)
10. [基于施密特分解的分布式变分量子本征求解器](./tutorials/quantum_simulation/DistributedVQE_CN.ipynb) 10. [基于施密特分解的分布式变分量子本征求解器](./tutorials/quantum_simulation/DistributedVQE_CN.ipynb)
11. [量子信号处理与量子奇异值变换](./tutorials/quantum_simulation/QSP_and_QSVT_CN.ipynb)
- [机器学习](./tutorials/machine_learning) - [机器学习](./tutorials/machine_learning)
1. [量子态编码经典数据](./tutorials/machine_learning/DataEncoding_CN.ipynb) 1. [量子态编码经典数据](./tutorials/machine_learning/DataEncoding_CN.ipynb)
...@@ -169,6 +171,8 @@ Paddle Quantum(量桨)建立起了人工智能与量子计算的桥梁,为 ...@@ -169,6 +171,8 @@ Paddle Quantum(量桨)建立起了人工智能与量子计算的桥梁,为
5. [量子变分自编码器(Quantum Autoencoder)](./tutorials/machine_learning/QAutoencoder_CN.ipynb) 5. [量子变分自编码器(Quantum Autoencoder)](./tutorials/machine_learning/QAutoencoder_CN.ipynb)
6. [量子生成对抗网络(Quantum GAN)](./tutorials/machine_learning/QGAN_CN.ipynb) 6. [量子生成对抗网络(Quantum GAN)](./tutorials/machine_learning/QGAN_CN.ipynb)
7. [变分量子奇异值分解(VQSVD)](./tutorials/machine_learning/VQSVD_CN.ipynb) 7. [变分量子奇异值分解(VQSVD)](./tutorials/machine_learning/VQSVD_CN.ipynb)
8. [数据编码分析](./tutorials/machine_learning/EncodingAnalysis_CN.ipynb)
9. [量子神经网络模拟函数](./tutorials/machine_learning/QApproximating_CN.ipynb)
- [组合优化](./tutorials/combinatorial_optimization) - [组合优化](./tutorials/combinatorial_optimization)
1. [量子近似优化算法(QAOA)](./tutorials/combinatorial_optimization/QAOA_CN.ipynb) 1. [量子近似优化算法(QAOA)](./tutorials/combinatorial_optimization/QAOA_CN.ipynb)
......
...@@ -22,7 +22,7 @@ copyright = '2022, Baidu Inc' ...@@ -22,7 +22,7 @@ copyright = '2022, Baidu Inc'
author = 'Baidu Inc' author = 'Baidu Inc'
# The full version, including alpha/beta/rc tags # The full version, including alpha/beta/rc tags
release = '2.2.0' release = '2.2.1'
# -- General configuration --------------------------------------------------- # -- General configuration ---------------------------------------------------
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
Paddle Quantum Paddle Quantum
======================= =======================
`Paddle Quantum <https://github.com/PaddlePaddle/Quantum>`__\ is a quantum machine learning toolkit developed based on Baidu PaddlePaddle. It provides a platform to construct and train quantum neural networks (QNN) with easy-to-use QML development kits supporting combinatorial optimization, quantum chemistry and other cutting-edge quantum applications, making PaddlePaddle the first deep learning framework in China that supports quantum machine learning. `Paddle Quantum <https://github.com/PaddlePaddle/Quantum>`__\ is the world's first cloud-integrated quantum machine learning platform based on Baidu PaddlePaddle. It supports the building and training of quantum neural networks, making PaddlePaddle the first deep learning framework in China. Paddle Quantum is feature-rich and easy to use. It provides comprehensive API documentation and tutorials help users get started right away.
.. figure:: https://release-data.cdn.bcebos.com/Paddle%20Quantum.png .. figure:: https://release-data.cdn.bcebos.com/Paddle%20Quantum.png
:target: https://github.com/PaddlePaddle/Quantum :target: https://github.com/PaddlePaddle/Quantum
...@@ -19,7 +19,7 @@ Features ...@@ -19,7 +19,7 @@ Features
- Easy-to-use - Easy-to-use
- Many online learning resources (Nearly 40 tutorials) - Many online learning resources (Nearly 50 tutorials)
- High efficiency in building QNN with various QNN templates - High efficiency in building QNN with various QNN templates
- Automatic differentiation - Automatic differentiation
...@@ -45,7 +45,7 @@ Install ...@@ -45,7 +45,7 @@ Install
Install PaddlePaddle Install PaddlePaddle
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~
This dependency will be automatically satisfied when users install Paddle Quantum. Please refer to `PaddlePaddle <https://www.paddlepaddle.org.cn/install/quick>`__ Install and configuration page. This project requires PaddlePaddle 2.2.0+. This dependency will be automatically satisfied when users install Paddle Quantum. Please refer to `PaddlePaddle <https://www.paddlepaddle.org.cn/install/quick>`__ Install and configuration page. This project requires PaddlePaddle 2.2.0 to 2.3.0.
.. _header-n19: .. _header-n19:
......
...@@ -113,7 +113,7 @@ msgid "" ...@@ -113,7 +113,7 @@ msgid ""
"This dependency will be automatically satisfied when users install Paddle" "This dependency will be automatically satisfied when users install Paddle"
" Quantum. Please refer to `PaddlePaddle " " Quantum. Please refer to `PaddlePaddle "
"<https://www.paddlepaddle.org.cn/install/quick>`__ Install and " "<https://www.paddlepaddle.org.cn/install/quick>`__ Install and "
"configuration page. This project requires PaddlePaddle 2.2.0+." "configuration page. This project requires PaddlePaddle 2.2.0 to 2.3.0."
msgstr "" msgstr ""
#: ../../source/introduction.rst:53 #: ../../source/introduction.rst:53
......
...@@ -22,3 +22,4 @@ ...@@ -22,3 +22,4 @@
paddle_quantum.shadow paddle_quantum.shadow
paddle_quantum.trotter paddle_quantum.trotter
paddle_quantum.visual paddle_quantum.visual
paddle_quantum.qsvt
\ No newline at end of file
paddle\_quantum.qsvt.qsp
============================
.. automodule:: paddle_quantum.qsvt.qsp
:members:
:undoc-members:
:show-inheritance:
\ No newline at end of file
paddle\_quantum.qsvt.qsp\_utils
===============================
.. automodule:: paddle_quantum.qsvt.qsp_utils
:members:
:undoc-members:
:show-inheritance:
\ No newline at end of file
paddle\_quantum.qsvt.qsvt
============================
.. automodule:: paddle_quantum.qsvt.qsvt
:members:
:undoc-members:
:show-inheritance:
\ No newline at end of file
paddle\_quantum.qsvt
============================
.. automodule:: paddle_quantum.qsvt
:members:
:undoc-members:
:show-inheritance:
.. rubric:: Submodules
.. toctree::
:maxdepth: 4
paddle_quantum.qsvt.qsp_utils
paddle_quantum.qsvt.qsp
paddle_quantum.qsvt.qsvt
\ No newline at end of file
...@@ -21,14 +21,14 @@ Tutorials ...@@ -21,14 +21,14 @@ Tutorials
--------- ---------
We provide use cases covering quantum simulation, machine learning, combinatorial optimization, local operations and classical communication (LOCC), and other popular quantum machine learning research topics. We provide use cases covering quantum simulation, machine learning, combinatorial optimization, local operations and classical communication (LOCC), and other popular quantum machine learning research topics.
Similar to the \ `quick start guide </quick-start/overview.html>`__\ , you can read each tutorial `online </tutorials/overview.html>`__ or download and run `Jupyter Notebooks <https://github.com/PaddlePaddle/Quantum/tree/master/tutorial>`__\ locally. Similar to the \ `quick start guide </quick-start/overview.html>`__\ , you can read each tutorial `online </tutorials/overview.html>`__ or download and run `Jupyter Notebooks <https://github.com/PaddlePaddle/Quantum/tree/master/tutorials>`__\ locally.
For interested developers, we recommend them to download Jupyter Notebooks and play around with it. Here are the tutorial categories: For interested developers, we recommend them to download Jupyter Notebooks and play around with it. Here are the tutorial categories:
- `Quantum Simulation <https://github.com/PaddlePaddle/Quantum/blob/master/tutorial/quantum_simulation>`__ - `Quantum Simulation <https://github.com/PaddlePaddle/Quantum/blob/master/tutorials/quantum_simulation>`__
- `Machine Learning <https://github.com/PaddlePaddle/Quantum/blob/master/tutorial/machine_learning>`__ - `Machine Learning <https://github.com/PaddlePaddle/Quantum/blob/master/tutorials/machine_learning>`__
- `Combinatorial Optimization <https://github.com/PaddlePaddle/Quantum/blob/master/tutorial/combinatorial_optimization>`__ - `Combinatorial Optimization <https://github.com/PaddlePaddle/Quantum/blob/master/tutorials/combinatorial_optimization>`__
- `LOCCNet <https://github.com/PaddlePaddle/Quantum/blob/master/tutorial/locc>`__ - `LOCCNet <https://github.com/PaddlePaddle/Quantum/blob/master/tutorials/locc>`__
- `QNN Research <https://github.com/PaddlePaddle/Quantum/blob/master/tutorial/qnn_research>`__ - `QNN Research <https://github.com/PaddlePaddle/Quantum/blob/master/tutorials/qnn_research>`__
With the latest LOCCNet module, Paddle Quantum can efficiently simulate distributed quantum information processing tasks. Interested readers can With the latest LOCCNet module, Paddle Quantum can efficiently simulate distributed quantum information processing tasks. Interested readers can
start with this `tutorial on LOCCNet </tutorials/loccnet/loccnet-framework.html>`__. In addition, Paddle Quantum supports quantum neural start with this `tutorial on LOCCNet </tutorials/loccnet/loccnet-framework.html>`__. In addition, Paddle Quantum supports quantum neural
......
...@@ -22,7 +22,7 @@ copyright = '2022, Baidu Inc' ...@@ -22,7 +22,7 @@ copyright = '2022, Baidu Inc'
author = 'Baidu Inc' author = 'Baidu Inc'
# The full version, including alpha/beta/rc tags # The full version, including alpha/beta/rc tags
release = '2.2.0' release = '2.2.1'
# -- General configuration --------------------------------------------------- # -- General configuration ---------------------------------------------------
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
Paddle Quantum(量桨) Paddle Quantum(量桨)
======================= =======================
`Paddle Quantum(量桨) <https://github.com/PaddlePaddle/Quantum>`__\ 是基于百度飞桨开发的量子机器学习工具集,支持量子神经网络的搭建与训练,提供易用的量子机器学习开发套件与量子优化、量子化学等前沿量子应用工具集,使得百度飞桨也因此成为国内首个支持量子机器学习的深度学习框架 `Paddle Quantum(量桨) <https://github.com/PaddlePaddle/Quantum>`__\ 是基于百度飞桨研发的全球首个云量一体的量子机器学习平台。量桨支持量子神经网络的搭建与训练等功能,使得百度飞桨也因此成为国内首个支持量子机器学习的深度学习框架。量桨具备轻松上手、功能丰富等特点,提供了完善的API文档和用例教程,使用户可以快速入门和上手
.. figure:: https://release-data.cdn.bcebos.com/Paddle%20Quantum.png .. figure:: https://release-data.cdn.bcebos.com/Paddle%20Quantum.png
:target: https://github.com/PaddlePaddle/Quantum :target: https://github.com/PaddlePaddle/Quantum
...@@ -19,7 +19,7 @@ Paddle Quantum(量桨) ...@@ -19,7 +19,7 @@ Paddle Quantum(量桨)
- 轻松上手 - 轻松上手
- 丰富的在线学习资源(近 40 篇教程案例) - 丰富的在线学习资源(近 50 篇教程案例)
- 通过模板高效搭建量子神经网络 - 通过模板高效搭建量子神经网络
- 自动微分框架 - 自动微分框架
...@@ -47,7 +47,7 @@ Paddle Quantum(量桨) ...@@ -47,7 +47,7 @@ Paddle Quantum(量桨)
当用户安装 Paddle Quantum 时会自动下载安装这个关键依赖包。关于 PaddlePaddle 更全面的安装信息请参考 当用户安装 Paddle Quantum 时会自动下载安装这个关键依赖包。关于 PaddlePaddle 更全面的安装信息请参考
`PaddlePaddle <https://www.paddlepaddle.org.cn/install/quick>`__ `PaddlePaddle <https://www.paddlepaddle.org.cn/install/quick>`__
安装配置页面。此项目需求 PaddlePaddle 2.2.0+ 安装配置页面。此项目需求 PaddlePaddle 2.2.0 到 2.3.0
.. _header-n19: .. _header-n19:
......
...@@ -22,3 +22,4 @@ ...@@ -22,3 +22,4 @@
paddle_quantum.shadow paddle_quantum.shadow
paddle_quantum.trotter paddle_quantum.trotter
paddle_quantum.visual paddle_quantum.visual
paddle_quantum.qsvt
...@@ -3,7 +3,7 @@ paddle\_quantum.ansatz.circuit ...@@ -3,7 +3,7 @@ paddle\_quantum.ansatz.circuit
量子电路类的功能实现。 量子电路类的功能实现。
.. py:class:: Circuit(num_qubits = None) .. py:class:: Circuit(num_qubits=None)
基类: :py:class:`paddle_quantum.ansatz.container.Sequential` 基类: :py:class:`paddle_quantum.ansatz.container.Sequential`
...@@ -28,7 +28,11 @@ paddle\_quantum.ansatz.circuit ...@@ -28,7 +28,11 @@ paddle\_quantum.ansatz.circuit
展平后的电路参数梯度。 展平后的电路参数梯度。
.. py:method:: update_param(theta, idx = None) .. py:property:: depth()
电路深度
.. py:method:: update_param(theta, idx=None)
替换单层或所有的电路参数。 替换单层或所有的电路参数。
...@@ -39,7 +43,11 @@ paddle\_quantum.ansatz.circuit ...@@ -39,7 +43,11 @@ paddle\_quantum.ansatz.circuit
:raises ValueError: 索引必须是整数或者 None。 :raises ValueError: 索引必须是整数或者 None。
.. py:method:: randomize_param(self, low = 0, high = 2 * pi) .. py:method:: transfer_static()
将该线路的所有参数的 ``stop_grdient`` 设为 ``True``
.. py:method:: randomize_param(low=0, high=2 * pi)
在 ``[low, high)`` 的范围内随机化电路参数 在 ``[low, high)`` 的范围内随机化电路参数
...@@ -48,7 +56,7 @@ paddle\_quantum.ansatz.circuit ...@@ -48,7 +56,7 @@ paddle\_quantum.ansatz.circuit
:param high: 随机参数的上界, 默认为 ``2*pi``。 :param high: 随机参数的上界, 默认为 ``2*pi``。
:type high: float, optional :type high: float, optional
.. py:method:: h(qubits_idx = 'full', num_qubits = None, depth = 1) .. py:method:: h(qubits_idx='full', num_qubits=None, depth=1)
添加一个单量子比特的 Hadamard 门。 添加一个单量子比特的 Hadamard 门。
...@@ -69,7 +77,7 @@ paddle\_quantum.ansatz.circuit ...@@ -69,7 +77,7 @@ paddle\_quantum.ansatz.circuit
:param depth: 层数,默认为 ``1``。 :param depth: 层数,默认为 ``1``。
:type depth: int, optional :type depth: int, optional
.. py:method:: s(qubits_idx = 'full', num_qubits = None, depth = 1) .. py:method:: s(qubits_idx='full', num_qubits=None, depth=1)
添加单量子比特 S 门。 添加单量子比特 S 门。
...@@ -90,7 +98,7 @@ paddle\_quantum.ansatz.circuit ...@@ -90,7 +98,7 @@ paddle\_quantum.ansatz.circuit
:param depth: 层数,默认为 ``1``。 :param depth: 层数,默认为 ``1``。
:type depth: int, optional :type depth: int, optional
.. py:method:: t(qubits_idx = 'full', num_qubits = None, depth = 1) .. py:method:: t(qubits_idx='full', num_qubits=None, depth=1)
添加单量子比特 T 门。 添加单量子比特 T 门。
...@@ -111,7 +119,7 @@ paddle\_quantum.ansatz.circuit ...@@ -111,7 +119,7 @@ paddle\_quantum.ansatz.circuit
:param depth: 层数,默认为 ``1``。 :param depth: 层数,默认为 ``1``。
:type depth: int, optional :type depth: int, optional
.. py:method:: x(qubits_idx = 'full', num_qubits = None, depth = 1) .. py:method:: x(qubits_idx='full', num_qubits=None, depth=1)
添加单量子比特 X 门。 添加单量子比特 X 门。
...@@ -701,7 +709,7 @@ paddle\_quantum.ansatz.circuit ...@@ -701,7 +709,7 @@ paddle\_quantum.ansatz.circuit
:param depth: 层数,默认为 ``1``。 :param depth: 层数,默认为 ``1``。
:type depth: int, optional :type depth: int, optional
.. py:method:: ccx(qubits_idx = 'cycle', num_qubits = None, depth = 1) .. py:method:: ccx(qubits_idx='cycle', num_qubits=None, depth=1)
添加 CCX 门。 添加 CCX 门。
...@@ -771,6 +779,8 @@ paddle\_quantum.ansatz.circuit ...@@ -771,6 +779,8 @@ paddle\_quantum.ansatz.circuit
:type num_qubits: int, optional :type num_qubits: int, optional
:param depth: 层数,默认为 ``1``。 :param depth: 层数,默认为 ``1``。
:type depth: int, optional :type depth: int, optional
:param gate_name: oracle 的名字,默认为 ``O``。
:type gate_name: str, optional
.. py:method:: control_oracle(oracle, qubits_idx, num_qubits=None, depth=1) .. py:method:: control_oracle(oracle, qubits_idx, num_qubits=None, depth=1)
...@@ -784,8 +794,28 @@ paddle\_quantum.ansatz.circuit ...@@ -784,8 +794,28 @@ paddle\_quantum.ansatz.circuit
:type num_qubits: int, optional :type num_qubits: int, optional
:param depth: 层数,默认为 ``1``。 :param depth: 层数,默认为 ``1``。
:type depth: int, optional :type depth: int, optional
:param gate_name: oracle 的名字,默认为 ``cO``。
:type gate_name: str, optional
.. py:method:: collapse(qubits_idx='full', num_qubits=None, desired_result=None, if_print=False, measure_basis='z')
添加一个坍缩算子
:param qubits_idx: 作用的量子比特的编号。
:type qubits_idx: Union[Iterable[Iterable[int]], Iterable[int], int]
:param num_qubits: 总共的量子比特数量,默认为 ``None``。
:type num_qubits: int, optional
:param desired_result: 期望的坍缩态(现只支持输入计算基),默认为 ``None`` (随机坍缩)。
:type desired_result: Union[int, str]
:param if_print: 是否要打印坍缩的信息,默认为 ``True``。
:type if_print: bool, optional
:param measure_basis: 要观测的测量基底,默认为 ``z``。
:type measure_basis: Union[Iterable[paddle.Tensor], str]
:raises NotImplementdError: 要观测的测量基底只能为 ``z``,其他测量基底会在之后推出。
:raises TypeError: 当 ``backend`` 为 ``unitary_matrix`` 时,无法获取输入态的概率。
.. py:method:: superposition_layer(qubits_idx = 'full', num_qubits = None, depth = 1) .. py:method:: superposition_layer(qubits_idx='full', num_qubits=None, depth=1)
添加一个 Hadamard 门组成的层。 添加一个 Hadamard 门组成的层。
...@@ -796,7 +826,7 @@ paddle\_quantum.ansatz.circuit ...@@ -796,7 +826,7 @@ paddle\_quantum.ansatz.circuit
:param depth: 层数,默认为 ``1``。 :param depth: 层数,默认为 ``1``。
:type depth: int, optional :type depth: int, optional
.. py:method:: weak_superposition_layer(qubits_idx = 'full', num_qubits = None, depth = 1) .. py:method:: weak_superposition_layer(qubits_idx='full', num_qubits=None, depth=1)
转角度为 :math:`\pi/4` 的 Ry 门组成的层。 转角度为 :math:`\pi/4` 的 Ry 门组成的层。
...@@ -807,7 +837,7 @@ paddle\_quantum.ansatz.circuit ...@@ -807,7 +837,7 @@ paddle\_quantum.ansatz.circuit
:param depth: 层数,默认为 ``1``。 :param depth: 层数,默认为 ``1``。
:type depth: int, optional :type depth: int, optional
.. py:method:: linear_entangled_layer(qubits_idx = 'full', num_qubits = None, depth = 1) .. py:method:: linear_entangled_layer(qubits_idx='full', num_qubits=None, depth=1)
包含 Ry 门、Rz 门,和 CNOT 门的线性纠缠层。 包含 Ry 门、Rz 门,和 CNOT 门的线性纠缠层。
...@@ -818,7 +848,7 @@ paddle\_quantum.ansatz.circuit ...@@ -818,7 +848,7 @@ paddle\_quantum.ansatz.circuit
:param depth: 层数,默认为 ``1``。 :param depth: 层数,默认为 ``1``。
:type depth: int, optional :type depth: int, optional
.. py:method:: real_entangled_layer(qubits_idx = 'full', num_qubits = None, depth = 1) .. py:method:: real_entangled_layer(qubits_idx='full', num_qubits=None, depth=1)
包含 Ry 门和 CNOT 门的强纠缠层。 包含 Ry 门和 CNOT 门的强纠缠层。
...@@ -829,7 +859,7 @@ paddle\_quantum.ansatz.circuit ...@@ -829,7 +859,7 @@ paddle\_quantum.ansatz.circuit
:param depth: 层数,默认为 ``1``。 :param depth: 层数,默认为 ``1``。
:type depth: int, optional :type depth: int, optional
.. py:method:: complex_entangled_layer(qubits_idx = 'full', num_qubits = None, depth = 1) .. py:method:: complex_entangled_layer(qubits_idx='full', num_qubits=None, depth=1)
包含 U3 门和 CNOT 门的强纠缠层。 包含 U3 门和 CNOT 门的强纠缠层。
...@@ -840,7 +870,7 @@ paddle\_quantum.ansatz.circuit ...@@ -840,7 +870,7 @@ paddle\_quantum.ansatz.circuit
:param depth: 层数,默认为 ``1``。 :param depth: 层数,默认为 ``1``。
:type depth: int, optional :type depth: int, optional
.. py:method:: real_block_layer(qubits_idx = 'full', num_qubits = None, depth = 1) .. py:method:: real_block_layer(qubits_idx='full', num_qubits=None, depth=1)
包含 Ry 门和 CNOT 门的弱纠缠层。 包含 Ry 门和 CNOT 门的弱纠缠层。
...@@ -851,7 +881,7 @@ paddle\_quantum.ansatz.circuit ...@@ -851,7 +881,7 @@ paddle\_quantum.ansatz.circuit
:param depth: 层数,默认为 ``1``。 :param depth: 层数,默认为 ``1``。
:type depth: int, optional :type depth: int, optional
.. py:method:: complex_block_layer(qubits_idx = 'full', num_qubits = None, depth = 1) .. py:method:: complex_block_layer(qubits_idx='full', num_qubits=None, depth=1)
包含 U3 门和 CNOT 门的弱纠缠层。 包含 U3 门和 CNOT 门的弱纠缠层。
...@@ -862,7 +892,7 @@ paddle\_quantum.ansatz.circuit ...@@ -862,7 +892,7 @@ paddle\_quantum.ansatz.circuit
:param depth: 层数,默认为 ``1``。 :param depth: 层数,默认为 ``1``。
:type depth: int, optional :type depth: int, optional
.. py:method:: bit_flip(prob, qubits_idx = 'full', num_qubits = None) .. py:method:: bit_flip(prob, qubits_idx='full', num_qubits=None)
添加比特反转信道。 添加比特反转信道。
...@@ -873,7 +903,7 @@ paddle\_quantum.ansatz.circuit ...@@ -873,7 +903,7 @@ paddle\_quantum.ansatz.circuit
:param num_qubits: 总的量子比特个数,默认为 ``None``。 :param num_qubits: 总的量子比特个数,默认为 ``None``。
:type num_qubits: int, optional :type num_qubits: int, optional
.. py:method:: phase_flip(prob, qubits_idx = 'full', num_qubits = None) .. py:method:: phase_flip(prob, qubits_idx='full', num_qubits=None)
添加相位反转信道。 添加相位反转信道。
...@@ -884,7 +914,7 @@ paddle\_quantum.ansatz.circuit ...@@ -884,7 +914,7 @@ paddle\_quantum.ansatz.circuit
:param num_qubits: 总的量子比特个数,默认为 ``None``。 :param num_qubits: 总的量子比特个数,默认为 ``None``。
:type num_qubits: int, optional :type num_qubits: int, optional
.. py:method:: bit_phase_flip(prob, qubits_idx = 'full', num_qubits = None) .. py:method:: bit_phase_flip(prob, qubits_idx='full', num_qubits=None)
添加比特相位反转信道。 添加比特相位反转信道。
...@@ -895,7 +925,7 @@ paddle\_quantum.ansatz.circuit ...@@ -895,7 +925,7 @@ paddle\_quantum.ansatz.circuit
:param num_qubits: 总的量子比特个数,默认为 ``None``。 :param num_qubits: 总的量子比特个数,默认为 ``None``。
:type num_qubits: int, optional :type num_qubits: int, optional
.. py:method:: amplitude_damping(gamma, qubits_idx = 'full', num_qubits = None) .. py:method:: amplitude_damping(gamma, qubits_idx='full', num_qubits=None)
添加振幅阻尼信道。 添加振幅阻尼信道。
...@@ -906,7 +936,7 @@ paddle\_quantum.ansatz.circuit ...@@ -906,7 +936,7 @@ paddle\_quantum.ansatz.circuit
:param num_qubits: 总的量子比特个数,默认为 ``None``。 :param num_qubits: 总的量子比特个数,默认为 ``None``。
:type num_qubits: int, optional :type num_qubits: int, optional
.. py:method:: generalized_amplitude_damping(gamma, qubits_idx = 'full', num_qubits = None) .. py:method:: generalized_amplitude_damping(gamma, qubits_idx='full', num_qubits=None)
添加广义振幅阻尼信道。 添加广义振幅阻尼信道。
...@@ -919,7 +949,7 @@ paddle\_quantum.ansatz.circuit ...@@ -919,7 +949,7 @@ paddle\_quantum.ansatz.circuit
:param num_qubits: 总的量子比特个数,默认为 ``None``。 :param num_qubits: 总的量子比特个数,默认为 ``None``。
:type num_qubits: int, optional :type num_qubits: int, optional
.. py:method:: phase_damping(gamma, qubits_idx = 'full', num_qubits = None) .. py:method:: phase_damping(gamma, qubits_idx='full', num_qubits=None)
添加相位阻尼信道。 添加相位阻尼信道。
...@@ -930,7 +960,7 @@ paddle\_quantum.ansatz.circuit ...@@ -930,7 +960,7 @@ paddle\_quantum.ansatz.circuit
:param num_qubits: 总的量子比特个数,默认为 ``None``。 :param num_qubits: 总的量子比特个数,默认为 ``None``。
:type num_qubits: int, optional :type num_qubits: int, optional
.. py:method:: depolarizing(prob, qubits_idx = 'full', num_qubits = None) .. py:method:: depolarizing(prob, qubits_idx='full', num_qubits=None)
添加去极化信道。 添加去极化信道。
...@@ -941,7 +971,7 @@ paddle\_quantum.ansatz.circuit ...@@ -941,7 +971,7 @@ paddle\_quantum.ansatz.circuit
:param num_qubits: 总的量子比特个数,默认为 ``None``。 :param num_qubits: 总的量子比特个数,默认为 ``None``。
:type num_qubits: int, optional :type num_qubits: int, optional
.. py:method:: pauli_channel(prob, qubits_idx = 'full', num_qubits = None) .. py:method:: pauli_channel(prob, qubits_idx='full', num_qubits=None)
添加泡利信道。 添加泡利信道。
...@@ -952,7 +982,7 @@ paddle\_quantum.ansatz.circuit ...@@ -952,7 +982,7 @@ paddle\_quantum.ansatz.circuit
:param num_qubits: 总的量子比特个数,默认为 ``None``。 :param num_qubits: 总的量子比特个数,默认为 ``None``。
:type num_qubits: int, optional :type num_qubits: int, optional
.. py:method:: reset_channel(prob, qubits_idx = 'full', num_qubits = None) .. py:method:: reset_channel(prob, qubits_idx='full', num_qubits=None)
添加重置信道。 添加重置信道。
...@@ -963,7 +993,7 @@ paddle\_quantum.ansatz.circuit ...@@ -963,7 +993,7 @@ paddle\_quantum.ansatz.circuit
:param num_qubits: 总的量子比特个数,默认为 ``None``。 :param num_qubits: 总的量子比特个数,默认为 ``None``。
:type num_qubits: int, optional :type num_qubits: int, optional
.. py:method:: thermal_relaxation(const_t, exec_time, qubits_idx = 'full', num_qubits = None) .. py:method:: thermal_relaxation(const_t, exec_time, qubits_idx='full', num_qubits=None)
添加热弛豫信道。 添加热弛豫信道。
...@@ -976,7 +1006,7 @@ paddle\_quantum.ansatz.circuit ...@@ -976,7 +1006,7 @@ paddle\_quantum.ansatz.circuit
:param num_qubits: 总的量子比特个数,默认为 ``None``。 :param num_qubits: 总的量子比特个数,默认为 ``None``。
:type num_qubits: int, optional :type num_qubits: int, optional
.. py:method:: kraus_repr(kraus_oper, qubits_idx, num_qubits = None) .. py:method:: kraus_repr(kraus_oper, qubits_idx, num_qubits=None)
添加一个 Kraus 表示的自定义量子信道。 添加一个 Kraus 表示的自定义量子信道。
...@@ -987,7 +1017,7 @@ paddle\_quantum.ansatz.circuit ...@@ -987,7 +1017,7 @@ paddle\_quantum.ansatz.circuit
:param num_qubits: 总的量子比特个数,默认为 ``None``。 :param num_qubits: 总的量子比特个数,默认为 ``None``。
:type num_qubits: int, optional :type num_qubits: int, optional
.. py:method:: unitary_matrix(num_qubits = None) .. py:method:: unitary_matrix(num_qubits=None)
电路的酉矩阵形式 电路的酉矩阵形式
......
...@@ -41,16 +41,18 @@ paddle\_quantum.ansatz.vans ...@@ -41,16 +41,18 @@ paddle\_quantum.ansatz.vans
.. py:function:: cir_decompose(cir) .. py:function:: cir_decompose(cir)
将电路中的 Layer 分解成量子门 将电路中的 Layer 分解成量子门, 如果需要的话可以把所有参数门的输入转为可训练参数
:param cir: 待分解电路 :param cir: 待分解电路
:type cir: Circuit :type cir: Circuit
:return: 分解后的电路。 :param trainable: 是否将分解后的参数量子门输入转为参数
:type trainable: bool, optional
:return: 分解后的电路
:rtype: Circuit :rtype: Circuit
.. note:: .. note::
函数不支持自定义门,如 oracle 和 control-oracle 量子电路稳定支持原生门,不支持 oracle 等其他自定义量子门
.. py:class:: VAns(n, loss_func, *loss_func_args, epsilon=0.1, insert_rate=2, iter=100, iter_out=10, LR =0.1, threshold=0.002, accept_wall=100, zero_init_state=True) .. py:class:: VAns(n, loss_func, *loss_func_args, epsilon=0.1, insert_rate=2, iter=100, iter_out=10, LR =0.1, threshold=0.002, accept_wall=100, zero_init_state=True)
......
...@@ -175,7 +175,7 @@ paddle\_quantum.dataset ...@@ -175,7 +175,7 @@ paddle\_quantum.dataset
:param dimension: 编码数据的维度。 :param dimension: 编码数据的维度。
:type dimension: int :type dimension: int
.. py:method:: encode(feature, encoding, num_qubits, return_state = True, full_return = False) .. py:method:: encode(feature, encoding, num_qubits, return_state=True, full_return=False)
用 ``num_qubits`` 的量子比特对 ``feature`` 进行编码 ``encoding``。 用 ``num_qubits`` 的量子比特对 ``feature`` 进行编码 ``encoding``。
......
...@@ -113,7 +113,7 @@ Fisher 信息的功能实现。 ...@@ -113,7 +113,7 @@ Fisher 信息的功能实现。
:return: 量子费舍信息矩阵的秩 :return: 量子费舍信息矩阵的秩
:rtype: int :rtype: int
.. py:class:: ClassicalFisher(model, num_thetas, num_inputs, model_type = 'quantum', **kwargs) .. py:class:: ClassicalFisher(model, num_thetas, num_inputs, model_type='quantum', **kwargs)
:param model: 经典或量子神经网络模型的实例 :param model: 经典或量子神经网络模型的实例
:type model: paddle.nn.Layer :type model: paddle.nn.Layer
...@@ -164,7 +164,7 @@ Fisher 信息的功能实现。 ...@@ -164,7 +164,7 @@ Fisher 信息的功能实现。
:rtype: Tuple[np.ndarray, float] :rtype: Tuple[np.ndarray, float]
.. py:method:: get_eff_dim(normalized_cfisher, list_num_samples, gamma = 1) .. py:method:: get_eff_dim(normalized_cfisher, list_num_samples, gamma=1)
计算经典的有效维数 计算经典的有效维数
......
...@@ -19,3 +19,38 @@ paddle\_quantum.gate.base ...@@ -19,3 +19,38 @@ paddle\_quantum.gate.base
参数名为"mylayer_0.w_n",其中 "w" 是参数的名称,"n" 为自动生成的具有唯一性的后缀。如果为 ``None``, 参数名为"mylayer_0.w_n",其中 "w" 是参数的名称,"n" 为自动生成的具有唯一性的后缀。如果为 ``None``,
前缀名将为小写的类名。默认为 ``None``。 前缀名将为小写的类名。默认为 ``None``。
:type name_scope: str, optional :type name_scope: str, optional
.. py:method:: gate_history_generation()
生成量子门的历史记录
.. py:class:: ParamGate
基类::py:class:`paddle_quantum.gate.base.Gate`
可参数化量子门的基类。
.. py:method:: theta_generation(param, param_shape)
规范可参数化量子门的输入,并根据输入决定是否要管理或者生成参数
:param param: 可参数化量子门的输入
:type param: Union[paddle.Tensor, float, List[float]]
:param param_shape: 输入的形状
:type param_shape: List[int]
.. note::
在以下情况 ``param`` 会被转为一个参数
- ``param`` 是 ``None``
在以下情况 ``param`` 会被记录为一个参数
- ``param`` 是 `ParamBase`
在以下情况 ``param`` 会保持不变
- ``param`` 是一个 `paddle.Tensor` 但不是 `ParamBase`
- ``param`` 是一个 `float` 或者 `List[float]`
.. py:method:: gate_history_generation()
生成可参数化量子门的历史记录
...@@ -3,7 +3,7 @@ paddle\_quantum.gate.custom ...@@ -3,7 +3,7 @@ paddle\_quantum.gate.custom
自定义量子门和受控量子门的功能实现。 自定义量子门和受控量子门的功能实现。
.. py:class:: Oracle(oracle, qubits_idx, num_qubits=None, depth=1) .. py:class:: Oracle(oracle, qubits_idx, num_qubits=None, depth=1, gate_name='O')
基类::py:class:`paddle_quantum.gate.base.Gate` 基类::py:class:`paddle_quantum.gate.base.Gate`
...@@ -17,8 +17,10 @@ paddle\_quantum.gate.custom ...@@ -17,8 +17,10 @@ paddle\_quantum.gate.custom
:type num_qubits: int, optional :type num_qubits: int, optional
:param depth: 层数,默认为 ``1``。 :param depth: 层数,默认为 ``1``。
:type depth: int, optional :type depth: int, optional
:param gate_name: oracle 的名字,默认为 ``O``。
:type gate_name: str, optional
.. py:class:: ControlOracle(oracle, qubits_idx, num_qubits=None, depth=1) .. py:class:: ControlOracle(oracle, qubits_idx, num_qubits=None, depth=1, gate_name='cO')
基类::py:class:`paddle_quantum.gate.base.Gate` 基类::py:class:`paddle_quantum.gate.base.Gate`
...@@ -32,3 +34,5 @@ paddle\_quantum.gate.custom ...@@ -32,3 +34,5 @@ paddle\_quantum.gate.custom
:type num_qubits: int, optional :type num_qubits: int, optional
:param depth: 层数,默认为 ``1``。 :param depth: 层数,默认为 ``1``。
:type depth: int, optional :type depth: int, optional
:param gate_name: oracle 的名字,默认为 ``cO``。
:type gate_name: str, optional
...@@ -124,3 +124,31 @@ paddle\_quantum.gate.layer ...@@ -124,3 +124,31 @@ paddle\_quantum.gate.layer
:type depth: int, optional :type depth: int, optional
.. py:class:: QAOALayer(Gate) .. py:class:: QAOALayer(Gate)
基类::py:class:`paddle_quantum.gate.base.Gate`
QAOA 驱动层
.. note::
仅支持 MaxCut 问题
:param edges: 图的边
:type edges: Iterable
:param nodes: 图的节点
:type nodes: Iterable
:param depth: 层数,默认为 ``1``。
:type depth: int, optional
.. py:class:: QAOALayer(Gate)
基类::py:class:`paddle_quantum.gate.base.Gate`
带权重的 QAOA 驱动层
:param edges: 带权重的图的边
:type edges: Dict[Tuple[int, int], float]
:param nodes: 带权重的图的节点
:type nodes: Dict[int, float]
:param depth: 层数,默认为 ``1``。
:type depth: int, optional
...@@ -129,7 +129,7 @@ paddle\_quantum.gate.multi\_qubit\_gate ...@@ -129,7 +129,7 @@ paddle\_quantum.gate.multi\_qubit\_gate
.. py:class:: CP(qubits_idx='cycle', num_qubits=None, depth=1, param=None, param_sharing=False) .. py:class:: CP(qubits_idx='cycle', num_qubits=None, depth=1, param=None, param_sharing=False)
基类::py:class:`paddle_quantum.gate.base.Gate` 基类::py:class:`paddle_quantum.gate.base.ParamGate`
受控 P 门。 受控 P 门。
...@@ -158,7 +158,7 @@ paddle\_quantum.gate.multi\_qubit\_gate ...@@ -158,7 +158,7 @@ paddle\_quantum.gate.multi\_qubit\_gate
.. py:class:: CRX(qubits_idx='cycle', num_qubits=None, depth=1, param=None, param_sharing=False) .. py:class:: CRX(qubits_idx='cycle', num_qubits=None, depth=1, param=None, param_sharing=False)
基类::py:class:`paddle_quantum.gate.base.Gate` 基类::py:class:`paddle_quantum.gate.base.ParamGate`
关于 x 轴的受控单量子比特旋转门。 关于 x 轴的受控单量子比特旋转门。
...@@ -191,7 +191,7 @@ paddle\_quantum.gate.multi\_qubit\_gate ...@@ -191,7 +191,7 @@ paddle\_quantum.gate.multi\_qubit\_gate
.. py:class:: CRY(qubits_idx='cycle', num_qubits=None, depth=1, param=None, param_sharing=False) .. py:class:: CRY(qubits_idx='cycle', num_qubits=None, depth=1, param=None, param_sharing=False)
基类::py:class:`paddle_quantum.gate.base.Gate` 基类::py:class:`paddle_quantum.gate.base.ParamGate`
关于 y 轴的受控单量子比特旋转门。 关于 y 轴的受控单量子比特旋转门。
...@@ -224,7 +224,7 @@ paddle\_quantum.gate.multi\_qubit\_gate ...@@ -224,7 +224,7 @@ paddle\_quantum.gate.multi\_qubit\_gate
.. py:class:: CRZ(qubits_idx='cycle', num_qubits=None, depth=1, param=None, param_sharing=False) .. py:class:: CRZ(qubits_idx='cycle', num_qubits=None, depth=1, param=None, param_sharing=False)
基类::py:class:`paddle_quantum.gate.base.Gate` 基类::py:class:`paddle_quantum.gate.base.ParamGate`
关于 z 轴的受控单量子比特旋转门。 关于 z 轴的受控单量子比特旋转门。
...@@ -257,7 +257,7 @@ paddle\_quantum.gate.multi\_qubit\_gate ...@@ -257,7 +257,7 @@ paddle\_quantum.gate.multi\_qubit\_gate
.. py:class:: CU(qubits_idx='cycle', num_qubits=None, depth=1, param=None, param_sharing=False) .. py:class:: CU(qubits_idx='cycle', num_qubits=None, depth=1, param=None, param_sharing=False)
基类::py:class:`paddle_quantum.gate.base.Gate` 基类::py:class:`paddle_quantum.gate.base.ParamGate`
受控单量子比特旋转门。 受控单量子比特旋转门。
...@@ -290,7 +290,7 @@ paddle\_quantum.gate.multi\_qubit\_gate ...@@ -290,7 +290,7 @@ paddle\_quantum.gate.multi\_qubit\_gate
.. py:class:: RXX(qubits_idx='cycle', num_qubits=None, depth=1, param=None, param_sharing=False) .. py:class:: RXX(qubits_idx='cycle', num_qubits=None, depth=1, param=None, param_sharing=False)
基类::py:class:`paddle_quantum.gate.base.Gate` 基类::py:class:`paddle_quantum.gate.base.ParamGate`
RXX 门。 RXX 门。
...@@ -322,7 +322,7 @@ paddle\_quantum.gate.multi\_qubit\_gate ...@@ -322,7 +322,7 @@ paddle\_quantum.gate.multi\_qubit\_gate
.. py:class:: RYY(qubits_idx='cycle', num_qubits=None, depth=1, param=None, param_sharing=False) .. py:class:: RYY(qubits_idx='cycle', num_qubits=None, depth=1, param=None, param_sharing=False)
基类::py:class:`paddle_quantum.gate.base.Gate` 基类::py:class:`paddle_quantum.gate.base.ParamGate`
RYY 门。 RYY 门。
...@@ -354,7 +354,7 @@ paddle\_quantum.gate.multi\_qubit\_gate ...@@ -354,7 +354,7 @@ paddle\_quantum.gate.multi\_qubit\_gate
.. py:class:: RZZ(qubits_idx='cycle', num_qubits=None, depth=1, param=None, param_sharing=False) .. py:class:: RZZ(qubits_idx='cycle', num_qubits=None, depth=1, param=None, param_sharing=False)
基类::py:class:`paddle_quantum.gate.base.Gate` 基类::py:class:`paddle_quantum.gate.base.ParamGate`
RZZ 门。 RZZ 门。
...@@ -474,7 +474,7 @@ paddle\_quantum.gate.multi\_qubit\_gate ...@@ -474,7 +474,7 @@ paddle\_quantum.gate.multi\_qubit\_gate
.. py:class:: UniversalTwoQubits(qubits_idx='cycle', num_qubits=None, depth=1, param=None, param_sharing=False) .. py:class:: UniversalTwoQubits(qubits_idx='cycle', num_qubits=None, depth=1, param=None, param_sharing=False)
基类::py:class:`paddle_quantum.gate.base.Gate` 基类::py:class:`paddle_quantum.gate.base.ParamGate`
两量子比特通用门,该通用门需要 15 个参数。 两量子比特通用门,该通用门需要 15 个参数。
...@@ -492,7 +492,7 @@ paddle\_quantum.gate.multi\_qubit\_gate ...@@ -492,7 +492,7 @@ paddle\_quantum.gate.multi\_qubit\_gate
.. py:class:: UniversalThreeQubits(qubits_idx='cycle', num_qubits=None, depth=1, param=None, param_sharing=False) .. py:class:: UniversalThreeQubits(qubits_idx='cycle', num_qubits=None, depth=1, param=None, param_sharing=False)
基类::py:class:`paddle_quantum.gate.base.Gate` 基类::py:class:`paddle_quantum.gate.base.ParamGate`
三量子比特通用门,该通用门需要 81 个参数。 三量子比特通用门,该通用门需要 81 个参数。
......
...@@ -140,7 +140,7 @@ paddle\_quantum.gate.single\_qubit\_gate ...@@ -140,7 +140,7 @@ paddle\_quantum.gate.single\_qubit\_gate
.. py:class:: P(qubits_idx='full', num_qubits=None, depth=1, param=None, param_sharing=False) .. py:class:: P(qubits_idx='full', num_qubits=None, depth=1, param=None, param_sharing=False)
基类::py:class:`paddle_quantum.gate.base.Gate` 基类::py:class:`paddle_quantum.gate.base.ParamGate`
单量子比特 P 门。 单量子比特 P 门。
...@@ -167,7 +167,7 @@ paddle\_quantum.gate.single\_qubit\_gate ...@@ -167,7 +167,7 @@ paddle\_quantum.gate.single\_qubit\_gate
.. py:class:: RX(qubits_idx='full', num_qubits=None, depth=1, param=None, param_sharing=False) .. py:class:: RX(qubits_idx='full', num_qubits=None, depth=1, param=None, param_sharing=False)
基类::py:class:`paddle_quantum.gate.base.Gate` 基类::py:class:`paddle_quantum.gate.base.ParamGate`
关于 x 轴的单量子比特旋转门。 关于 x 轴的单量子比特旋转门。
...@@ -194,7 +194,7 @@ paddle\_quantum.gate.single\_qubit\_gate ...@@ -194,7 +194,7 @@ paddle\_quantum.gate.single\_qubit\_gate
.. py:class:: RY(qubits_idx='full', num_qubits=None, depth=1, param=None, param_sharing=False) .. py:class:: RY(qubits_idx='full', num_qubits=None, depth=1, param=None, param_sharing=False)
基类::py:class:`paddle_quantum.gate.base.Gate` 基类::py:class:`paddle_quantum.gate.base.ParamGate`
关于 y 轴的单量子比特旋转门。 关于 y 轴的单量子比特旋转门。
...@@ -221,7 +221,7 @@ paddle\_quantum.gate.single\_qubit\_gate ...@@ -221,7 +221,7 @@ paddle\_quantum.gate.single\_qubit\_gate
.. py:class:: RZ(qubits_idx='full', num_qubits=None, depth=1, param=None, param_sharing=False) .. py:class:: RZ(qubits_idx='full', num_qubits=None, depth=1, param=None, param_sharing=False)
基类::py:class:`paddle_quantum.gate.base.Gate` 基类::py:class:`paddle_quantum.gate.base.ParamGate`
关于 z 轴的单量子比特旋转门。 关于 z 轴的单量子比特旋转门。
...@@ -248,7 +248,7 @@ paddle\_quantum.gate.single\_qubit\_gate ...@@ -248,7 +248,7 @@ paddle\_quantum.gate.single\_qubit\_gate
.. py:class:: U3(qubits_idx='full', num_qubits=None, depth=1, param=None, param_sharing=False) .. py:class:: U3(qubits_idx='full', num_qubits=None, depth=1, param=None, param_sharing=False)
基类::py:class:`paddle_quantum.gate.base.Gate` 基类::py:class:`paddle_quantum.gate.base.ParamGate`
单量子比特旋转门。 单量子比特旋转门。
......
...@@ -98,6 +98,8 @@ paddle\_quantum.gradtool ...@@ -98,6 +98,8 @@ paddle\_quantum.gradtool
:param \*args: 用于损失函数计算的额外参数列表。 :param \*args: 用于损失函数计算的额外参数列表。
:type \*args: Any :type \*args: Any
:raise Exception: 训练数据必须是 ``paddle.Tensor`` 类型
:return: 包含如下两个元素: :return: 包含如下两个元素:
- loss_list: 损失函数值随训练次数变化的列表。 - loss_list: 损失函数值随训练次数变化的列表。
- grad_list: 各参数梯度随训练次变化的列表。 - grad_list: 各参数梯度随训练次变化的列表。
...@@ -130,6 +132,8 @@ paddle\_quantum.gradtool ...@@ -130,6 +132,8 @@ paddle\_quantum.gradtool
:param \*args: 用于损失函数计算的额外参数列表。 :param \*args: 用于损失函数计算的额外参数列表。
:type \*args: Any :type \*args: Any
:raise Exception: 训练数据必须是 ``paddle.Tensor`` 类型
.. note:: .. note::
在本函数中提供了三种计算模式,``mode`` 分别可以选择 ``'single'``, ``'max'``, 以及 ``'random'``。 在本函数中提供了三种计算模式,``mode`` 分别可以选择 ``'single'``, ``'max'``, 以及 ``'random'``。
- mode='single': 表示计算电路中的每个可变参数梯度的平均值和方差。 - mode='single': 表示计算电路中的每个可变参数梯度的平均值和方差。
......
...@@ -75,7 +75,7 @@ paddle\_quantum.hamiltonian ...@@ -75,7 +75,7 @@ paddle\_quantum.hamiltonian
- pauli_words: 元素为每一项的泡利字符串,例如 'Z0, Z1, X3' 这一项的泡利字符串为 'ZZIX'。 - pauli_words: 元素为每一项的泡利字符串,例如 'Z0, Z1, X3' 这一项的泡利字符串为 'ZZIX'。
:rtype: Tuple[list] :rtype: Tuple[list]
.. py:method:: construct_h_matrix(qubit_num = None) .. py:method:: construct_h_matrix(qubit_num=None)
构建 Hamiltonian 在 Z 基底下的矩阵。 构建 Hamiltonian 在 Z 基底下的矩阵。
......
...@@ -27,7 +27,7 @@ paddle\_quantum.linalg ...@@ -27,7 +27,7 @@ paddle\_quantum.linalg
验证矩阵 ``P`` 是否为厄密矩阵 验证矩阵 ``P`` 是否为厄密矩阵
:param mat: 矩阵 :param mat: 厄密矩阵
:type mat: paddle.Tensor :type mat: paddle.Tensor
:param eps: 容错率 :param eps: 容错率
:type eps: float, optional :type eps: float, optional
...@@ -39,7 +39,7 @@ paddle\_quantum.linalg ...@@ -39,7 +39,7 @@ paddle\_quantum.linalg
验证矩阵 ``P`` 是否为映射算子 验证矩阵 ``P`` 是否为映射算子
:param mat: 矩阵 :param mat: 映射算子
:type mat: paddle.Tensor :type mat: paddle.Tensor
:param eps: 容错率 :param eps: 容错率
:type eps: float, optional :type eps: float, optional
...@@ -47,11 +47,11 @@ paddle\_quantum.linalg ...@@ -47,11 +47,11 @@ paddle\_quantum.linalg
:return: 决定是否 :math:`PP - P = 0` :return: 决定是否 :math:`PP - P = 0`
:rtype: bool :rtype: bool
.. py:function:: is_unitary(mat, eps = 1e-5) .. py:function:: is_unitary(mat, eps=1e-5)
验证矩阵 ``P`` 是否为酉矩阵 验证矩阵 ``P`` 是否为酉矩阵
:param mat: 矩阵 :param mat: 矩阵
:type mat: paddle.Tensor :type mat: paddle.Tensor
:param eps: 容错率 :param eps: 容错率
:type eps: float, optional :type eps: float, optional
...@@ -66,7 +66,7 @@ paddle\_quantum.linalg ...@@ -66,7 +66,7 @@ paddle\_quantum.linalg
:param num_qubits: 量子比特数 n :param num_qubits: 量子比特数 n
:type num_qubits: int :type num_qubits: int
:return: 一个 :math:`2^n \times 2^n` 厄密矩阵 :return: 一个 :math:`2^n \times 2^n` 厄密矩阵 (n 为量子比特数)
:rtype: paddle.Tensor :rtype: paddle.Tensor
.. py:function:: orthogonal_projection_random(num_qubits) .. py:function:: orthogonal_projection_random(num_qubits)
...@@ -76,37 +76,49 @@ paddle\_quantum.linalg ...@@ -76,37 +76,49 @@ paddle\_quantum.linalg
:param num_qubits: 量子比特数 n :param num_qubits: 量子比特数 n
:type num_qubits: int :type num_qubits: int
:return: 一个 :math:`2^n \times 2^n` 正交投影算子 :return: 一个 :math:`2^n \times 2^n` 正交投影算子 (n 为量子比特数)
:rtype: paddle.Tensor :rtype: paddle.Tensor
.. py:function:: unitary_hermitian_random(num_qubits) .. py:function:: density_matrix_random(num_qubits)
随机生成一个厄密酉矩阵 随机生成一个密度矩阵
:param num_qubits: 量子比特数 n :param num_qubits: 量子比特数 n
:type num_qubits: int :type num_qubits: int
:return: 一个 :math:`2^n \times 2^n` 厄密共轭酉矩阵 :return: 一个 :math:`2^n \times 2^n` 密度矩阵 (n 为量子比特数)
:rtype: paddle.Tensor :rtype: paddle.Tensor
.. py:function:: unitary_random_with_hermitian_block(num_qubits) .. py:function:: unitary_random(num_qubits)
随机生成一个左上半部分为厄密矩阵的酉矩阵 随机生成一个酉矩阵
:param num_qubits: 量子比特数 n :param num_qubits: 量子比特数 n
:type num_qubits: int :type num_qubits: int
:return: 一个左上半部分为厄密矩阵的 :math:`2^n \times 2^n` 酉矩阵 :return: 一个 :math:`2^n \times 2^n` 酉矩阵 (n 为量子比特数)
:rtype: paddle.Tensor :rtype: paddle.Tensor
.. py:function:: unitary_random(num_qubits) .. py:function:: unitary_hermitian_random(num_qubits)
随机生成一个酉矩阵 随机生成一个厄密酉矩阵
:param num_qubits: 量子比特数 n
:type num_qubits: int
:return: 一个 :math:`2^n \times 2^n` 厄密共轭酉矩阵 (n 为量子比特数)
:rtype: paddle.Tensor
.. py:function:: unitary_random_with_hermitian_block(num_qubits, is_unitary)
随机生成一个左上半部分为厄密矩阵的酉矩阵
:param num_qubits: 量子比特数 n :param num_qubits: 量子比特数 n
:type num_qubits: int :type num_qubits: int
:param is_unitary: 厄密矩阵块是否是酉矩阵的 1/2
:type is_unitary: bool
:return: 一个 :math:`2^n \times 2^n` 酉矩阵 :return: 一个左上半部分为厄密矩阵的 :math:`2^n \times 2^n` 酉矩阵 (n 为量子比特数)
:rtype: paddle.Tensor :rtype: paddle.Tensor
.. py:function:: haar_orthogonal(num_qubits) .. py:function:: haar_orthogonal(num_qubits)
...@@ -116,7 +128,7 @@ paddle\_quantum.linalg ...@@ -116,7 +128,7 @@ paddle\_quantum.linalg
:param num_qubits: 量子比特数 n :param num_qubits: 量子比特数 n
:type num_qubits: int :type num_qubits: int
:return: 一个 :math:`2^n \times 2^n` 正交矩阵 :return: 一个 :math:`2^n \times 2^n` 正交矩阵 (n 为量子比特数)
:rtype: paddle.Tensor :rtype: paddle.Tensor
.. py:function:: haar_unitary(num_qubits) .. py:function:: haar_unitary(num_qubits)
...@@ -126,7 +138,7 @@ paddle\_quantum.linalg ...@@ -126,7 +138,7 @@ paddle\_quantum.linalg
:param num_qubits: 量子比特数 n :param num_qubits: 量子比特数 n
:type num_qubits: int :type num_qubits: int
:return: 一个 :math:`2^n \times 2^n` 酉矩阵 :return: 一个 :math:`2^n \times 2^n` 酉矩阵 (n 为量子比特数)
:rtype: paddle.Tensor :rtype: paddle.Tensor
.. py:function:: haar_state_vector(num_qubits, is_real=False) .. py:function:: haar_state_vector(num_qubits, is_real=False)
...@@ -138,7 +150,7 @@ paddle\_quantum.linalg ...@@ -138,7 +150,7 @@ paddle\_quantum.linalg
:param is_real: 生成的态矢量是否为实数 :param is_real: 生成的态矢量是否为实数
:type is_real: bool, optional :type is_real: bool, optional
:return: 一个 :math:`2^n \times 1` 态矢量 :return: 一个 :math:`2^n \times 1` 态矢量 (n 为量子比特数)
:rtype: paddle.Tensor :rtype: paddle.Tensor
.. py:function:: haar_density_operator(num_qubits, rank=None, is_real=False) .. py:function:: haar_density_operator(num_qubits, rank=None, is_real=False)
...@@ -152,7 +164,7 @@ paddle\_quantum.linalg ...@@ -152,7 +164,7 @@ paddle\_quantum.linalg
:param is_real: 生成的态矢量是否为实数 :param is_real: 生成的态矢量是否为实数
:type is_real: bool, optional :type is_real: bool, optional
:return: 一个 :math:`2^n x 2^n` 密度矩阵 :return: 一个 :math:`2^n \times 2^n` 密度矩阵 (n 为量子比特数)
:rtype: paddle.Tensor :rtype: paddle.Tensor
.. py:function:: NKron(matrix_A, matrix_B, *args) .. py:function:: NKron(matrix_A, matrix_B, *args)
...@@ -160,11 +172,11 @@ paddle\_quantum.linalg ...@@ -160,11 +172,11 @@ paddle\_quantum.linalg
计算两个及以上的矩阵的克罗内克乘积 计算两个及以上的矩阵的克罗内克乘积
:param matrix_A: 矩阵 :param matrix_A: 矩阵
:type num_qubits: np.ndarray :type num_qubits: Union[np.ndarray, paddle.Tensor]
:param matrix_B: 矩阵 :param matrix_B: 矩阵
:type matrix_B: np.ndarray :type matrix_B: Union[np.ndarray, paddle.Tensor]
:param \*args: 更多矩阵 :param \*args: 更多矩阵
:type \*args: np.ndarray :type \*args: Union[np.ndarray, paddle.Tensor]
:return: 克罗内克乘积 :return: 克罗内克乘积
:rtype: np.ndarray :rtype: Union[np.ndarray, paddle.Tensor]
...@@ -49,5 +49,8 @@ paddle\_quantum.loss.measure ...@@ -49,5 +49,8 @@ paddle\_quantum.loss.measure
:type qubits_idx: Union[Iterable[int], int, str], optional :type qubits_idx: Union[Iterable[int], int, str], optional
:param desired_result: 指定要返回的测量结果的概率值。默认为 ``None``,返回所有测量结果的概率值。 :param desired_result: 指定要返回的测量结果的概率值。默认为 ``None``,返回所有测量结果的概率值。
:type desired_result: Union[Iterable[str], str], optional :type desired_result: Union[Iterable[str], str], optional
:raises NotImplementedError: 所指定的后端必须为量桨已经实现的后端
:raises NotImplementedError: ``qubits_idx`` 须为 ``Iterable`` 或 ``'full'``。
:raises NotImplementedError: 目前我们只支持在Z方向上测量。
:return: 测量结果所对应的概率值。 :return: 测量结果所对应的概率值。
:rtype: paddle.Tensor :rtype: paddle.Tensor
...@@ -35,6 +35,14 @@ paddle\_quantum.operator.operator ...@@ -35,6 +35,14 @@ paddle\_quantum.operator.operator
该类可以让你使用对量子态进行坍缩,坍缩到某一本征态。 该类可以让你使用对量子态进行坍缩,坍缩到某一本征态。
:param qubits_idx: 坍缩的量子比特编号,默认为 ``'full'``.
:type qubits_idx: Union[Iterable[int], int, str], optional
:param num_qubits: 总的量子比特数量,默认为 ``None``。
:type num_qubits: int, optional
:param desired_result: 想要坍缩到的特定结果。
:type desired_result: Union[int, str]
:param if_print: 是否打印坍缩后的量子态的信息,默认为 ``'False'``.
:type if_print: bool
:param measure_basis: 测量基底。量子态会坍缩到对应的本征态上。 :param measure_basis: 测量基底。量子态会坍缩到对应的本征态上。
:type measure_basis: Union[Iterable[paddle.Tensor], str] :type measure_basis: Union[Iterable[paddle.Tensor], str]
:raises NotImplementedError: 所输入的测量基底还没有实现。 :raises NotImplementedError: 所输入的测量基底还没有实现。
...@@ -45,8 +53,5 @@ paddle\_quantum.operator.operator ...@@ -45,8 +53,5 @@ paddle\_quantum.operator.operator
:param state: 输入的量子态,其将会被坍缩。 :param state: 输入的量子态,其将会被坍缩。
:type state: paddle_quantum.State :type state: paddle_quantum.State
:param desired_result: 想要坍缩到的特定结果。
:type desired_result: Union[int, str]
:raises NotImplementedError: 当前仅支持 z 基底。
:return: 坍缩后的量子态。 :return: 坍缩后的量子态。
:rtype: paddle_quantum.State :rtype: paddle_quantum.State
...@@ -3,7 +3,7 @@ paddle\_quantum.qchem.hardware\_efficient ...@@ -3,7 +3,7 @@ paddle\_quantum.qchem.hardware\_efficient
Hardware Efficient 电路模板。 Hardware Efficient 电路模板。
.. py:class:: HardwareEfficientModel(n_qubits, depth, theta = None) .. py:class:: HardwareEfficientModel(n_qubits, depth, theta=None)
基类: :py:class:`paddle_quantum.gate.base.Gate` 基类: :py:class:`paddle_quantum.gate.base.Gate`
......
...@@ -3,7 +3,7 @@ paddle\_quantum.qchem.loss ...@@ -3,7 +3,7 @@ paddle\_quantum.qchem.loss
量子化学中的损失函数。 量子化学中的损失函数。
.. py:class:: MolEnergyLoss(geometry, basis, multiplicity = 1, charge = 0) .. py:class:: MolEnergyLoss(geometry, basis, multiplicity=1, charge=0)
基类::py:class:`paddle_quantum.loss.ExpecVal` 基类::py:class:`paddle_quantum.loss.ExpecVal`
...@@ -18,7 +18,7 @@ paddle\_quantum.qchem.loss ...@@ -18,7 +18,7 @@ paddle\_quantum.qchem.loss
:param charge: 分子电荷量, 默认值为 ``0``。 :param charge: 分子电荷量, 默认值为 ``0``。
:type charge: int, optional :type charge: int, optional
.. py:class:: RHFEnergyLoss(geometry, basis, multiplicity = 1, charge = 0) .. py:class:: RHFEnergyLoss(geometry, basis, multiplicity=1, charge=0)
基类: :py:class:`paddle_quantum.Operator` 基类: :py:class:`paddle_quantum.Operator`
......
...@@ -3,6 +3,17 @@ paddle\_quantum.qchem.qchem ...@@ -3,6 +3,17 @@ paddle\_quantum.qchem.qchem
量子化学中的功能函数。 量子化学中的功能函数。
.. py:function:: qubitOperator_to_Hamiltonian(spin_h,tol)
将openfermion形式转化为量桨的哈密顿量形式。
:param spin_h: openfermion形式的哈密顿量。
:type spin_h: openfermion.ops.operators.qubit_operator.QubitOperator
:param tol: 阈值
:type tol: float, optional
:return: 返回转换成量桨形式的哈密顿量
:rtype: Hamiltonian
.. py:function:: geometry(structure, file) .. py:function:: geometry(structure, file)
读取分子几何信息。 读取分子几何信息。
......
...@@ -22,7 +22,7 @@ paddle\_quantum.qchem.slater\_determinant ...@@ -22,7 +22,7 @@ paddle\_quantum.qchem.slater\_determinant
:param theta: 给定旋转角度。 :param theta: 给定旋转角度。
:type theta: float :type theta: float
.. py:class:: RHFSlaterDeterminantModel(n_qubits, n_electrons, mo_coeff = None) .. py:class:: RHFSlaterDeterminantModel(n_qubits, n_electrons, mo_coeff=None)
基类::py:class:`paddle_quantum.gate.Gate` 基类::py:class:`paddle_quantum.gate.Gate`
......
...@@ -3,7 +3,7 @@ paddle\_quantum.qchem.uccsd ...@@ -3,7 +3,7 @@ paddle\_quantum.qchem.uccsd
UCCSD 电路模板。 UCCSD 电路模板。
.. py:class:: UCCSDModel(n_qubits, n_electrons, n_trotter_steps, single_excitation_amplitude = None, double_excitation_amplitude = None) .. py:class:: UCCSDModel(n_qubits, n_electrons, n_trotter_steps, single_excitation_amplitude=None, double_excitation_amplitude=None)
基类::py:class:`paddle_quantum.gate.Gate` 基类::py:class:`paddle_quantum.gate.Gate`
......
...@@ -3,12 +3,12 @@ paddle\_quantum.qinfo ...@@ -3,12 +3,12 @@ paddle\_quantum.qinfo
量子信息中的常用功能实现。 量子信息中的常用功能实现。
.. py:function:: partial_trace(rho_AB, dim1, dim2, A_or_B) .. py:function:: partial_trace(state, dim1, dim2, A_or_B)
计算量子态的偏迹。 计算量子态的偏迹。
:param rho_AB: 输入的量子态。 :param state: 输入的量子态。
:type rho_AB: paddle_quantum.State :type state: Union[paddle_quantum.State, paddle.Tensor]
:param dim1: 系统A的维数。 :param dim1: 系统A的维数。
:type dim1: int :type dim1: int
:param dim2: 系统B的维数。 :param dim2: 系统B的维数。
...@@ -19,12 +19,12 @@ paddle\_quantum.qinfo ...@@ -19,12 +19,12 @@ paddle\_quantum.qinfo
:return: 输入的量子态的偏迹。 :return: 输入的量子态的偏迹。
:rtype: paddle.Tensor :rtype: paddle.Tensor
.. py:function:: partial_trace_discontiguous(rho, preserve_qubits = None) .. py:function:: partial_trace_discontiguous(state, preserve_qubits=None)
计算量子态的偏迹,可选取任意子系统。 计算量子态的偏迹,可选取任意子系统。
:param rho: 输入的量子态。 :param state: 输入的量子态。
:type rho: paddle_quantum.State :type state: Union[paddle_quantum.State, paddle.Tensor]
:param preserve_qubits: 要保留的量子比特,默认为 None,表示全保留。 :param preserve_qubits: 要保留的量子比特,默认为 None,表示全保留。
:type preserve_qubits: list, optional :type preserve_qubits: list, optional
...@@ -40,9 +40,9 @@ paddle\_quantum.qinfo ...@@ -40,9 +40,9 @@ paddle\_quantum.qinfo
D(\rho, \sigma) = 1 / 2 * \text{tr}|\rho-\sigma| D(\rho, \sigma) = 1 / 2 * \text{tr}|\rho-\sigma|
:param rho: 量子态的密度矩阵形式。 :param rho: 量子态的密度矩阵形式。
:type rho: paddle_quantum.State :type rho: Union[paddle_quantum.State, paddle.Tensor]
:param sigma: 量子态的密度矩阵形式。 :param sigma: 量子态的密度矩阵形式。
:type sigma: paddle_quantum.State :type sigma: Union[paddle_quantum.State, paddle.Tensor]
:return: 输入的量子态之间的迹距离。 :return: 输入的量子态之间的迹距离。
:rtype: paddle.Tensor :rtype: paddle.Tensor
...@@ -56,9 +56,9 @@ paddle\_quantum.qinfo ...@@ -56,9 +56,9 @@ paddle\_quantum.qinfo
F(\rho, \sigma) = \text{tr}(\sqrt{\sqrt{\rho}\sigma\sqrt{\rho}}) F(\rho, \sigma) = \text{tr}(\sqrt{\sqrt{\rho}\sigma\sqrt{\rho}})
:param rho: 量子态的密度矩阵形式。 :param rho: 量子态的密度矩阵形式。
:type rho: paddle_quantum.State :type rho: Union[paddle_quantum.State, paddle.Tensor]
:param sigma: 量子态的密度矩阵形式。 :param sigma: 量子态的密度矩阵形式。
:type sigma: paddle_quantum.State :type sigma: Union[paddle_quantum.State, paddle.Tensor]
:return: 输入的量子态之间的保真度。 :return: 输入的量子态之间的保真度。
:rtype: paddle.Tensor :rtype: paddle.Tensor
...@@ -89,7 +89,7 @@ paddle\_quantum.qinfo ...@@ -89,7 +89,7 @@ paddle\_quantum.qinfo
P = \text{tr}(\rho^2) P = \text{tr}(\rho^2)
:param rho: 量子态的密度矩阵形式。 :param rho: 量子态的密度矩阵形式。
:type rho: paddle_quantum.State :type rho: Union[paddle_quantum.State, paddle.Tensor]
:return: 输入的量子态的纯度。 :return: 输入的量子态的纯度。
:rtype: paddle.Tensor :rtype: paddle.Tensor
...@@ -103,7 +103,7 @@ paddle\_quantum.qinfo ...@@ -103,7 +103,7 @@ paddle\_quantum.qinfo
S = -\text{tr}(\rho \log(\rho)) S = -\text{tr}(\rho \log(\rho))
:param rho: 量子态的密度矩阵形式。 :param rho: 量子态的密度矩阵形式。
:type rho: paddle_quantum.State :type rho: Union[paddle_quantum.State, paddle.Tensor]
:return: 输入的量子态的冯诺依曼熵。 :return: 输入的量子态的冯诺依曼熵。
:rtype: paddle.Tensor :rtype: paddle.Tensor
...@@ -117,14 +117,14 @@ paddle\_quantum.qinfo ...@@ -117,14 +117,14 @@ paddle\_quantum.qinfo
S(\rho \| \sigma)=\text{tr} \rho(\log \rho-\log \sigma) S(\rho \| \sigma)=\text{tr} \rho(\log \rho-\log \sigma)
:param rho: 量子态的密度矩阵形式 :param rho: 量子态的密度矩阵形式
:type rho: paddle_quantum.State :type rho: Union[paddle_quantum.State, paddle.Tensor]
:param sig: 量子态的密度矩阵形式 :param sig: 量子态的密度矩阵形式
:type sig: paddle_quantum.State :type sig: Union[paddle_quantum.State, paddle.Tensor]
:return: 输入的量子态之间的相对熵 :return: 输入的量子态之间的相对熵
:rtype: paddle.Tensor :rtype: paddle.Tensor
.. py:function:: random_pauli_str_generator(n, terms = 3) .. py:function:: random_pauli_str_generator(n, terms=3)
随机生成一个可观测量(observable)的列表( ``list`` )形式。 随机生成一个可观测量(observable)的列表( ``list`` )形式。
...@@ -158,12 +158,12 @@ paddle\_quantum.qinfo ...@@ -158,12 +158,12 @@ paddle\_quantum.qinfo
:return: 输入列表对应的可观测量的矩阵形式。 :return: 输入列表对应的可观测量的矩阵形式。
:rtype: paddle.Tensor :rtype: paddle.Tensor
.. py:function:: partial_transpose_2(density_op, sub_system = None) .. py:function:: partial_transpose_2(density_op, sub_system=None)
计算输入量子态的 partial transpose :math:`\rho^{T_A}`。 计算输入量子态的 partial transpose :math:`\rho^{T_A}`。
:param density_op: 量子态的密度矩阵形式。 :param density_op: 量子态的密度矩阵形式。
:type density_op: paddle_quantum.State :type density_op: Union[paddle_quantum.State, paddle.Tensor]
:param sub_system: 1或2,表示关于哪个子系统进行 partial transpose,默认为第二个。 :param sub_system: 1或2,表示关于哪个子系统进行 partial transpose,默认为第二个。
:type sub_system: int, optional :type sub_system: int, optional
...@@ -175,7 +175,7 @@ paddle\_quantum.qinfo ...@@ -175,7 +175,7 @@ paddle\_quantum.qinfo
计算输入量子态的 partial transpose :math:`\rho^{T_A}`。 计算输入量子态的 partial transpose :math:`\rho^{T_A}`。
:param density_op: 量子态的密度矩阵形式。 :param density_op: 量子态的密度矩阵形式。
:type density_op: paddle_quantum.State :type density_op: Union[paddle_quantum.State, paddle.Tensor]
:param n: 需要转置系统的量子比特数量。 :param n: 需要转置系统的量子比特数量。
:type n: int :type n: int
...@@ -187,7 +187,7 @@ paddle\_quantum.qinfo ...@@ -187,7 +187,7 @@ paddle\_quantum.qinfo
计算输入量子态的 Negativity :math:`N = ||\frac{\rho^{T_A}-1}{2}||`。 计算输入量子态的 Negativity :math:`N = ||\frac{\rho^{T_A}-1}{2}||`。
:param density_op: 量子态的密度矩阵形式。 :param density_op: 量子态的密度矩阵形式。
:type density_op: paddle_quantum.State :type density_op: Union[paddle_quantum.State, paddle.Tensor]
:return: 输入的量子态的 Negativity。 :return: 输入的量子态的 Negativity。
:rtype: paddle.Tensor :rtype: paddle.Tensor
...@@ -197,27 +197,27 @@ paddle\_quantum.qinfo ...@@ -197,27 +197,27 @@ paddle\_quantum.qinfo
计算输入量子态的 Logarithmic Negativity :math:`E_N = ||\rho^{T_A}||`。 计算输入量子态的 Logarithmic Negativity :math:`E_N = ||\rho^{T_A}||`。
:param density_op: 量子态的密度矩阵形式。 :param density_op: 量子态的密度矩阵形式。
:type density_op: paddle_quantum.State :type density_op: Union[paddle_quantum.State, paddle.Tensor]
:return: 输入的量子态的 Logarithmic Negativity。 :return: 输入的量子态的 Logarithmic Negativity。
:rtype: paddle.Tensor :rtype: paddle.Tensor
.. py:function:: is_ppt(density_op: paddle_quantum.State) .. py:function:: is_ppt(density_op)
计算输入量子态是否满足 PPT 条件。 计算输入量子态是否满足 PPT 条件。
:param density_op: 量子态的密度矩阵形式。 :param density_op: 量子态的密度矩阵形式。
:type density_op: paddle_quantum.State :type density_op: Union[paddle_quantum.State, paddle.Tensor]
:return: 输入的量子态是否满足 PPT 条件。 :return: 输入的量子态是否满足 PPT 条件。
:rtype: bool :rtype: bool
.. py:function:: schmidt_decompose(psi, sys_A = None) .. py:function:: schmidt_decompose(psi, sys_A=None)
计算输入量子态的施密特分解 :math:`\lvert\psi\rangle=\sum_ic_i\lvert i_A\rangle\otimes\lvert i_B \rangle`。 计算输入量子态的施密特分解 :math:`\lvert\psi\rangle=\sum_ic_i\lvert i_A\rangle\otimes\lvert i_B \rangle`。
:param psi: 量子态的向量形式,形状为(2**n)。 :param psi: 量子态的向量形式,形状为(2**n)。
:type psi: paddle_quantum.State :type psi: Union[paddle_quantum.State, paddle.Tensor]
:param sys_A: 包含在子系统 A 中的 qubit 下标(其余 qubit 包含在子系统B中),默认为量子态 :math:`\lvert \psi\rangle` 的前半数 qubit。 :param sys_A: 包含在子系统 A 中的 qubit 下标(其余 qubit 包含在子系统B中),默认为量子态 :math:`\lvert \psi\rangle` 的前半数 qubit。
:type sys_A: List[int], optional :type sys_A: List[int], optional
...@@ -240,7 +240,7 @@ paddle\_quantum.qinfo ...@@ -240,7 +240,7 @@ paddle\_quantum.qinfo
:return: 编码得到的密度矩阵。 :return: 编码得到的密度矩阵。
:rtype: paddle_quantum.State :rtype: paddle_quantum.State
.. py:function:: shadow_trace(state, hamiltonian, sample_shots, method = 'CS') .. py:function:: shadow_trace(state, hamiltonian, sample_shots, method='CS')
估计可观测量 :math:`H` 的期望值 :math:`\text{trace}(H\rho)`。 估计可观测量 :math:`H` 的期望值 :math:`\text{trace}(H\rho)`。
...@@ -253,5 +253,27 @@ paddle\_quantum.qinfo ...@@ -253,5 +253,27 @@ paddle\_quantum.qinfo
:param method: 使用 shadow 来进行估计的方法,可选 "CS"、"LBCS"、"APS" 三种方法,默认为 ``CS``。 :param method: 使用 shadow 来进行估计的方法,可选 "CS"、"LBCS"、"APS" 三种方法,默认为 ``CS``。
:type method: str, optional :type method: str, optional
:raises ValueError: 输入的哈密顿量 (Hamiltonian) 形式不合法
:return: 估计可观测量 :math:`H` 的期望值。 :return: 估计可观测量 :math:`H` 的期望值。
:rtype: float :rtype: float
.. py:function:: tensor_product(state_a, state_b, *args)
计算输入的量子态(至少两个)的直积形式, 输出将自动返回 State 实例
:param state_a: 量子态A
:type state_a: Union[State, paddle.Tensor]
:param state_b: 量子态B
:type state_b: Union[State, paddle.Tensor]
:param args: 其他量子态
:type args: Union[State, paddle.Tensor]
:raises NotImplementedError: 当前只接收输入类型为 State 或 paddle.Tensor
.. note::
使用的 backend 必须为 DensityMatrix
:return: 输入量子态的直积
:rtype: State
\ No newline at end of file
paddle\_quantum.qsvt.qsp
============================
量子信号处理相关类与函数,具体参考论文 https://arxiv.org/abs/1806.01838
.. py:function:: signal_unitary(signal_x)
实现论文中的信号矩阵 :math:`W(x)`
:param signal_x: 输入信号,区间为[-1, 1]
:type signal_x: float
:return: matrix :math:`W(x=\text{signal_x})`
:rtype: ndarray
.. py:function:: poly_parity_verification(poly_p, k, error)
对输入多项式进行奇偶校验,判断 :math:`P` 奇偶性是否为 (k mod 2),详见论文定理 3 中的条件 2.
:param poly_p: 多项式 :math:`P(x)`
:type poly_p: Polynomial
:param k: 项数 k
:type k: int
:param error: 误差阈值,默认为 `1e-6`.
:type error: float
:return: poly_p 奇偶性是否为 (k mod 2)
:rtype: bool
.. py:function:: normalization_verification(poly_p, poly_q, trials, error)
归一化验证,判断多项式 :math:`P(x)` 和 :math:`Q(x)` 是否满足归一化条件,详见论文定理 3 中的条件 3
:param poly_p: 多项式 :math:`P(x)`
:type poly_p: Polynomial
:param poly_q: 多项式 :math:`Q(x)`
:type poly_q: Polynomial
:param trials: 验证次数,默认为 `10`
:type trials: int
:param error: 误差阈值,默认为 `1e-2`.
:type error: float
:return: 多项式是否满足归一化条件 :math:`|P|^2 + (1 - x^2)|Q|^2 = 1`
:rtype: bool
.. py:function:: angle_phi_verification(phi, poly_p, poly_p_hat, poly_q_hat, trials, error)
验证角度 :math:`\phi` 是否满足论文中的等式 6
:param phi: 旋转角 :math:`\phi`
:type phi: float
:param poly_p: 多项式 :math:`P(x)`
:type poly_p: Polynomial
:param poly_q: 多项式 :math:`Q(x)`
:type poly_q: Polynomial
:param poly_p_hat: 多项式 :math:`\tilde{P}(x)`
:type poly_p: Polynomial
:param poly_q_hat: 多项式 :math:`\tilde{Q}(x)`
:type poly_q: Polynomial
:param trials: 验证次数,默认为 `10`
:type trials: int
:param error: 误差阈值,默认为 `1e-2`.
:type error: float
:return: 角度 :math:`\phi` 是否满足论文中的等式 6.
:rtype: bool
.. py:function:: processing_unitary(list_matrices, signal_x)
构造量子信号处理矩阵 :math:`W_\Phi(x)`,详见论文中的等式 1
:param list_matrices: 一个包含信号处理矩阵的数组
:type list_matrices: List[ndarray]
:param signal_x: 输入信号 x,范围为 [-1, 1]
:type signal_x: float
:return: 量子信号处理矩阵 :math:`W_\Phi(x)`
:rtype: ndarray
.. py:function:: Phi_verification(list_phi, poly_p, trials, error)
验证完整的角度 :math:`\Phi`
:param list_phi: 包含所有角度 :math:`\phi` 的数组
:type list_phi: ndarray
:param poly_p: 多项式 :math:`P(x)`
:type poly_p: Polynomial
:param trials: 验证次数,默认为 `100`
:type trials: trials
:param error: 误差阈值,默认为 `1e-6`
:type error: float
:return: 角度 :math:`\Phi` 是否使得 :math:`W_\Phi(x)` 为 :math:`P(x)` 的块编码
:rtype: bool
.. py:function:: update_polynomial(poly_p, poly_q, phi)
计算 :math:`P, Q` 经过一层量子信号处理后的多项式 :math:`\tilde{P}, \tilde{Q}`
:param poly_p: 多项式 :math:`P(x)`
:type poly_p: Polynomial
:param poly_q: 多项式 :math:`Q(x)`
:type poly_q: Polynomial
:param phi: 量子信号处理的旋转角 :math:`\phi`
:type phi: float
:return: 更新之后的多项式 :math:`\tilde{P}(x), \tilde{Q}(x)`
:rtype: Tuple[Polynomial, Polynomial]
.. py:function:: alg_find_Phi(poly_p, poly_q, length)
计算角度 :math:`\Phi` 的算法
:param poly_p: 多项式 :math:`P(x)`
:type poly_p: Polynomial
:param poly_q: 多项式 :math:`Q(x)`
:type poly_q: Polynomial
:param length: 返回角度的个数,即量子信号处理的层数
:type length: int
:return: 包含角度的数组 :math:`\Phi`
:rtype: ndarray
.. py:function:: poly_A_hat_generation(poly_p)
计算多项式 :math:`\hat{A}(y) = 1 - P(x)P^*(x)`,其中 :math:`y = x^2`
:param poly_p: 多项式 :math:`P(x)`
:type poly_p: Polynomial
:return: 多项式 :math:`\hat{A}(y)`
:rtype: Polynomial
.. py:function:: poly_A_hat_decomposition(A_hat, error)
通过求根的方式分解多项式 :math:`\hat{A}(y)`
:param poly_p: 多项式 :math:`P(x)`
:type poly_p: Polynomial
:param error: 误差阈值,默认为 `0.001`
:type error: float
:return: 多项式 :math:`\hat{A}(y)` 的最高项系数以及根
:rtype: Tuple[float, List[float]]
.. py:function:: poly_Q_generation(leading_coef, roots, parity)
根据多项式 :math:`\hat{A}(y)` 的分解,构造多项式 :math:`Q(x)`
:param leading_coef: 多项式 :math:`\hat{A}(y)` 的最高项系数
:type leading_coef: float
:param roots: 多项式 :math:`\hat{A}(y)` 的根
:type roots: List[float]
:param parity: 多项式 :math:`Q(x)` 的奇偶性
:type parity: int
:return: 多项式 :math:`Q(x)`
:rtype: Polynomial
.. py:function:: alg_find_Q(poly_p, k)
根据多项式 :math:`P(x)` 构造多项式 :math:`Q(x)` 的算法
:param poly_p: 多项式 :math:`P(x)`
:type poly_p: Polynomial
:param k: 多项式 :math:`Q(x)` 的项数
:type k: int
:return: 多项式 :math:`Q(x)`
:rtype: Polynomial
.. py:function:: quantum_signal_processing(poly_p, length)
量子信号处理函数,找到一组角度 :math:`\Phi` 使得量子信号处理算子 :math:`W_\Phi(x)` 是一个多项式 :math:`P(x)` 的块编码
:param poly_p: 多项式 :math:`P(x)`
:type poly_p: Polynomial
:param length: 角度的个数,即量子信号处理的层数,默认 `None` 为多项式 :math:`P(x)` 的度
:type length: int
:return: 角度 :math:`\Phi`
:rtype: ndarray
.. py:function:: reflection_based_quantum_signal_processing(P)
基于反射的量子信号处理函数,找到一组角度 :math:`\Phi` 使得量子信号处理算子 :math:`W_\Phi(x)` 是一个多项式 :math:`P(x)` 的块编码,详见论文引理 8
:param poly_p: 多项式 :math:`P(x)`
:type poly_p: Polynomial
:return: 角度 :math:`\Phi`
:rtype: ndarray
.. py:class:: ScalarQSP
基类: :py:class:`object`
基于量子信号处理的类
:param poly_p: 多项式 :math:`P(x)`
:type poly_p: Polynomial
:param length: 角度的个数,即量子信号处理的层数,默认 `None` 为多项式 :math:`P(x)` 的度
:type length: int
.. py:method:: block_encoding(signal_x)
构造一个量子信号处理的电路,即实现多项式 :math:`P(x)` 的块编码电路
:param signal_x: 输入的信号 x
:type signal_x: float
:return: 量子信号处理的电路
:rtype: Circuit
.. py:method:: block_encoding_matrix(signal_x)
构造一个量子信号处理的矩阵,即实现多项式 :math:`P(x)` 的块编码矩阵
:param signal_x: 输入的信号 x
:type signal_x: float
:return: 量子信号处理的矩阵
:rtype: paddle.Tensor
paddle\_quantum.qsvt.qsp\_utils
===============================
量子信号处理相关工具函数包
.. py:function:: random_odd_poly_generation(degree, odd)
生成一个随机的满足量子信号处理要求的多项式
:param degree: 多项式的度
:type degree: int
:param odd: 多项式的奇偶性,输入 `True` 则为奇函数, `False` 则为偶函数
:type odd: bool
:return: 一个随机生成的多项式
:rtype: Polynomial
.. py:function:: clean_small_error(array)
清除相对较小的项,以提升计算精度
:param array: 目标数组
:type array: ndarray
:return: 经过清除后的数组
:rtype: ndarray
.. py:function:: poly_norm(poly, p=1)
计算一个多项式的 p 范数
:param poly: 目标多项式
:type poly: Polynomial
:param p: p 范数,默认为 `1`,输入 `0` 则是无穷范数
:type p: Optional[int]
:return: 目标多项式的 p 范数
:rtype: float
.. py:function:: poly_real(poly)
取一个多项式的实部
:param poly: 目标多项式
:type poly: Polynomial
:return: 目标多项式的实部
:rtype: Polynomial
.. py:function:: poly_imag(poly)
取一个多项式的实部
:param poly: 目标多项式
:type poly: Polynomial
:return: 目标多项式的虚部
:rtype: Polynomial
.. py:function:: poly_matrix(poly, matrix_A)
计算一个矩阵的多项式 poly(matrix_A)
:param poly: 输入多项式
:type poly: Polynomial
:param matrix_A: 输入矩阵
:type matrix_A: paddle.Tensor
:return: 矩阵的多项式结果 poly(matrix_A)
:rtype: paddle.Tensor
.. py:function:: exp_matrix(t, matrix_A)
计算矩阵指数 :math:`e^{itA}`
:param t: 演化时间
:type t: float
:param matrix_A: 目标矩阵 A
:type matrix_A: paddle.Tensor
:return: 矩阵指数 :math:`e^{itA}`
:rtype: paddle.Tensor
\ No newline at end of file
paddle\_quantum.qsvt.qsvt
============================
量子奇异值变换
.. py:function:: block_encoding_projector(num_qubits, num_projected_qubits)
生成块编码的投影算子
:param num_qubits: 量子比特数量
:type num_qubits: int
:param num_projected_qubits: 被投影的量子比特数量,默认为 `num_qubits - 1`
:type num_projected_qubits: int
:return: 投影算子 :math:`|0\rangle\langle0| \otimes I`
:rtype: paddle.Tensor
.. py:function:: qubitization(proj, phi)
单比特化操作,生成等同于 :math:`e^{i \phi (2P - I)}` 的电路
:param proj: 正交投影算子 :math:`P`
:type proj: paddle.Tensor
:param phi: 角度 :math:`\phi`
:type phi: paddle.Tensor
:return: :math:`e^{i \phi (2P - I)}` 的电路
:rtype: Circuit
.. py:class:: QSVT
基类: :py:class:`object`
:param poly_p: 多项式 :math:`P(x)`
:type poly_p: Polynomial
:param oracle: 酉算子 :math:`U`,为一个厄米特矩阵 :math:`X` 的块编码
:type oracle: paddle.Tensor
:param m: 厄米特矩阵 :math:`X` 的系统量子比特数量,默认为酉算子 :math:`U` 量子比特数量 - 1
.. py:method:: block_encoding_matrix()
构造一个对于厄米特矩阵 :math:`X` 的量子奇异值变换矩阵,即实现多项式 :math:`P(X)` 的块编码矩阵
:return: 量子奇异值变换矩阵
:rtype: paddle.Tensor
.. py:method:: block_encoding_circuit()
构造一个对于厄米特矩阵 :math:`X` 的量子奇异值变换电路,即实现多项式 :math:`P(X)` 的块编码电路
:return: 量子奇异值变换电路
:rtype: Circuit
.. py:method:: block_encoding_unitary()
返回一个对于厄米特矩阵 :math:`X` 的量子奇异值变换电路的酉矩阵形式,用于验证正确性
:return: 量子奇异值变换电路的酉矩阵
:rtype: paddle.Tensor
\ No newline at end of file
paddle\_quantum.qsvt
============================
量子奇异值变换模块
.. rubric:: Submodules
.. toctree::
:maxdepth: 4
paddle_quantum.qsvt.qsp_utils
paddle_quantum.qsvt.qsp
paddle_quantum.qsvt.qsvt
\ No newline at end of file
...@@ -20,5 +20,8 @@ paddle\_quantum.shadow ...@@ -20,5 +20,8 @@ paddle\_quantum.shadow
:param method: 进行随机采样的方法,有 ``'CS'`` 、 ``'LBCS'`` 、 ``'APS'`` 三种方法,默认为 ``'CS'``。 :param method: 进行随机采样的方法,有 ``'CS'`` 、 ``'LBCS'`` 、 ``'APS'`` 三种方法,默认为 ``'CS'``。
:type method: str, optional :type method: str, optional
:raises ValueError: 输入的哈密顿量 (Hamiltonian) 形式不合法
:raises NotImplementedError: 输入 ``state`` 的 ``backend`` 必须是 ``StateVector`` 或 ``DensityMatrix``
:return: 随机选择的泡利测量基和测量结果,形状为 ``(sample_shots, 2)`` 的list。 :return: 随机选择的泡利测量基和测量结果,形状为 ``(sample_shots, 2)`` 的list。
:rtype: list :rtype: list
...@@ -77,6 +77,8 @@ paddle\_quantum.state.common ...@@ -77,6 +77,8 @@ paddle\_quantum.state.common
p_{1}|\Phi^{+}\rangle\langle\Phi^{+}|+p_{2}| \Psi^{+}\rangle\langle\Psi^{+}|+p_{3}| \Phi^{-}\rangle\langle\Phi^{-}| + p_{1}|\Phi^{+}\rangle\langle\Phi^{+}|+p_{2}| \Psi^{+}\rangle\langle\Psi^{+}|+p_{3}| \Phi^{-}\rangle\langle\Phi^{-}| +
p_{4}|\Psi^{-}\rangle\langle\Psi^{-}| p_{4}|\Psi^{-}\rangle\langle\Psi^{-}|
:param prob: 各个贝尔态的概率。
:type: List[float]
:raises Exception: 当后端为态矢量时,所输入量子态应该为纯态。 :raises Exception: 当后端为态矢量时,所输入量子态应该为纯态。
:raises NotImplementedError: 所指定的后端必须为量桨已经实现的后端。 :raises NotImplementedError: 所指定的后端必须为量桨已经实现的后端。
...@@ -123,7 +125,7 @@ paddle\_quantum.state.common ...@@ -123,7 +125,7 @@ paddle\_quantum.state.common
:param num_qubits: 量子态所包含的量子比特数。 :param num_qubits: 量子态所包含的量子比特数。
:type num_qubits: int :type num_qubits: int
:raises Exception: 所指定的后端必须为态矢量 :raises Exception: 当后端为态矢量时,所输入量子态应该为纯态
:raises NotImplementedError: 所指定的后端必须为量桨已经实现的后端。 :raises NotImplementedError: 所指定的后端必须为量桨已经实现的后端。
:returns: 生成的 GHZ-state。 :returns: 生成的 GHZ-state。
:rtype: paddle_quantum.State :rtype: paddle_quantum.State
......
...@@ -17,6 +17,17 @@ paddle\_quantum.state.state ...@@ -17,6 +17,17 @@ paddle\_quantum.state.state
:type backend: paddle_quantum.Backend, optional :type backend: paddle_quantum.Backend, optional
:param dtype: 量子态的数据类型。默认为 None,使用全局的默认数据类型。 :param dtype: 量子态的数据类型。默认为 None,使用全局的默认数据类型。
:type dtype: str, optional :type dtype: str, optional
:raises Exception: 所输入的量子态维度不正确。
:raises NotImplementedError: 所指定的后端必须为量桨已经实现的后端。
.. py:property:: ket()
得到量子态的列向量形式。
.. py:property:: bra()
得到量子态的行向量形式。
.. py:method:: numpy() .. py:method:: numpy()
...@@ -55,6 +66,7 @@ paddle\_quantum.state.state ...@@ -55,6 +66,7 @@ paddle\_quantum.state.state
:type hamiltonian: paddle_quantum.Hamiltonian :type hamiltonian: paddle_quantum.Hamiltonian
:param shots: 测量次数。 :param shots: 测量次数。
:type shots: int :type shots: int
:raises NotImplementedError: 所指定的后端必须为量桨已经实现的后端。
:return: 该量子态关于可观测量的期望值。 :return: 该量子态关于可观测量的期望值。
:rtype: float :rtype: float
...@@ -69,5 +81,8 @@ paddle\_quantum.state.state ...@@ -69,5 +81,8 @@ paddle\_quantum.state.state
:type qubits_idx: Union[Iterable[int], int], optional :type qubits_idx: Union[Iterable[int], int], optional
:param plot: 是否画图。默认为 Flase,表示不画图。 :param plot: 是否画图。默认为 Flase,表示不画图。
:type plot: bool, optional :type plot: bool, optional
:raises Exception: 测量的次数必须大于0。
:raises NotImplementedError: 所指定的后端必须为量桨已经实现的后端。
:raises NotImplementedError: 输入的量子比特下标有误。
:return: 测量结果。 :return: 测量结果。
:rtype: dict :rtype: dict
...@@ -4,7 +4,7 @@ paddle\_quantum.trotter ...@@ -4,7 +4,7 @@ paddle\_quantum.trotter
Trotter 哈密顿量时间演化的功能实现。 Trotter 哈密顿量时间演化的功能实现。
.. py:function:: construct_trotter_circuit(circuit, hamiltonian, tau, steps, method = 'suzuki', order = 1, grouping = None, coefficient = None, permutation = None) .. py:function:: construct_trotter_circuit(circuit, hamiltonian, tau, steps, method='suzuki', order=1, grouping=None, coefficient=None, permutation=None)
向 circuit 的后面添加 trotter 时间演化电路,即给定一个系统的哈密顿量 H,该电路可以模拟系统的时间演化 :math:`U_{cir} e^{-iHt}`。 向 circuit 的后面添加 trotter 时间演化电路,即给定一个系统的哈密顿量 H,该电路可以模拟系统的时间演化 :math:`U_{cir} e^{-iHt}`。
...@@ -27,6 +27,11 @@ Trotter 哈密顿量时间演化的功能实现。 ...@@ -27,6 +27,11 @@ Trotter 哈密顿量时间演化的功能实现。
:param permutation: 自定义哈密顿量的排列方式,默认为 ``None``,仅在 ``method='custom'`` 时有效。 :param permutation: 自定义哈密顿量的排列方式,默认为 ``None``,仅在 ``method='custom'`` 时有效。
:type permutation: np.ndarray, optional :type permutation: np.ndarray, optional
:raises ValueError: Trotter-Suzuki 分解的阶数 ``order`` 必须为 ``1``, ``2``, 或 ``2k``, 其中 ``k`` 是一个整数
:raises ValueError: ``permutation`` 和 ``coefficient`` 的形状不一致
:raises ValueError: 重排策略 ``grouping`` 的方法不支持, 仅支持 ``'xyz'``, ``'even_odd'``
:raises ValueError: 搭建时间演化电路的方法 ``method`` 不支持, 仅支持 ``'suzuki'``, ``'custom'``
.. Hint:: .. Hint::
想知道该函数是如何模拟的?更多信息请移步至量桨官网教程: https://qml.baidu.com/tutorials/overview.html. 想知道该函数是如何模拟的?更多信息请移步至量桨官网教程: https://qml.baidu.com/tutorials/overview.html.
...@@ -91,7 +96,7 @@ Trotter 哈密顿量时间演化的功能实现。 ...@@ -91,7 +96,7 @@ Trotter 哈密顿量时间演化的功能实现。
:return: 系数数组。 :return: 系数数组。
:rtype: np.ndarray :rtype: np.ndarray
.. py:function:: get_1d_heisenberg_hamiltonian(length, j_x = 1.0, j_y = 1.0, j_z = 1.0, h_z = 0.0, periodic_boundary_condition = True) .. py:function:: get_1d_heisenberg_hamiltonian(length, j_x=1.0, j_y=1.0, j_z=1.0, h_z=0.0, periodic_boundary_condition=True)
生成一个一维海森堡链的哈密顿量。 生成一个一维海森堡链的哈密顿量。
......
...@@ -73,4 +73,7 @@ paddle\_quantum.visual ...@@ -73,4 +73,7 @@ paddle\_quantum.visual
:param density_matrix: 多量子比特的量子态的状态向量或者密度矩阵,要求量子数大于 1。 :param density_matrix: 多量子比特的量子态的状态向量或者密度矩阵,要求量子数大于 1。
:type density_matrix: paddle_quantum.State :type density_matrix: paddle_quantum.State
:param size: 条宽度,在 0 到 1 之间,默认为 ``0.3``。 :param size: 条宽度,在 0 到 1 之间,默认为 ``0.3``。
:type size: float, optional :type size: float, optional
\ No newline at end of file
:raises TypeError: 要求输入的 ``density_matrix`` 类型为 ``numpy.ndarray``, ``paddle.Tensor``, 或者 ``paddle_quantum.State``
:raises ValueError: 要求输入的 ``density_matrix`` 是一个方阵
\ No newline at end of file
...@@ -23,14 +23,14 @@ Paddle Quantum。目前支持网页阅览和\ `下载运行 Jupyter Notebook <ht ...@@ -23,14 +23,14 @@ Paddle Quantum。目前支持网页阅览和\ `下载运行 Jupyter Notebook <ht
-------- --------
我们提供了涵盖量子模拟、机器学习、组合优化、本地操作与经典通讯(local operations and classical communication, LOCC)、量子神经网络等多个领域的案例供大家学习。与\ `入门手册 </quick-start/overview.html>`__\ 类似,每个教程目前支持 我们提供了涵盖量子模拟、机器学习、组合优化、本地操作与经典通讯(local operations and classical communication, LOCC)、量子神经网络等多个领域的案例供大家学习。与\ `入门手册 </quick-start/overview.html>`__\ 类似,每个教程目前支持
\ `网页阅览 </tutorials/overview.html>`__\ 和\ `下载运行 Jupyter Notebook <https://github.com/PaddlePaddle/Quantum/tree/master/tutorial>`__\ 两种方式。我们推荐用户下载 Notebook \ `网页阅览 </tutorials/overview.html>`__\ 和\ `下载运行 Jupyter Notebook <https://github.com/PaddlePaddle/Quantum/tree/master/tutorials>`__\ 两种方式。我们推荐用户下载 Notebook
后,本地运行进行实践。 后,本地运行进行实践。
- `量子模拟 <https://github.com/PaddlePaddle/Quantum/blob/master/tutorial/quantum_simulation>`__ - `量子模拟 <https://github.com/PaddlePaddle/Quantum/blob/master/tutorials/quantum_simulation>`__
- `机器学习 <https://github.com/PaddlePaddle/Quantum/blob/master/tutorial/machine_learning>`__ - `机器学习 <https://github.com/PaddlePaddle/Quantum/blob/master/tutorials/machine_learning>`__
- `组合优化 <https://github.com/PaddlePaddle/Quantum/blob/master/tutorial/combinatorial_optimization>`__ - `组合优化 <https://github.com/PaddlePaddle/Quantum/blob/master/tutorials/combinatorial_optimization>`__
- `LOCCNet <https://github.com/PaddlePaddle/Quantum/blob/master/tutorial/locc>`__ - `LOCCNet <https://github.com/PaddlePaddle/Quantum/blob/master/tutorials/locc>`__
- `量子神经网络研究 <https://github.com/PaddlePaddle/Quantum/blob/master/tutorial/qnn_research>`__ - `量子神经网络研究 <https://github.com/PaddlePaddle/Quantum/blob/master/tutorials/qnn_research>`__
随着 LOCCNet 模组的推出,量桨现已支持分布式量子信息处理任务的高效模拟和开发。感兴趣的读者请参见 `教程 </tutorials/loccnet/loccnet-framework.html>`__。 随着 LOCCNet 模组的推出,量桨现已支持分布式量子信息处理任务的高效模拟和开发。感兴趣的读者请参见 `教程 </tutorials/loccnet/loccnet-framework.html>`__。
Paddle Quantum 也支持在 GPU Paddle Quantum 也支持在 GPU
......
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 使用量桨通过量易伏平台连接量子计算机\n",
"\n",
"*Copyright (c) 2022 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.*\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 简介\n",
"\n",
"在量子机器学习中,我们最终想实现的目标就是使用量子计算机和经典计算机进行混合计算,从而实现高效的量子机器学习(Quantum Machine Learning, QML)算法。在量桨(Paddle Quantum)中,我们也支持通过[量易伏](https://quantum-hub.baidu.com/)这一云原生量子计算平台连接到真实的量子计算机,从而使用量子计算机实现 QML 算法。\n",
"\n",
"### 量易伏简介\n",
"\n",
"量易伏是由百度研究院量子计算研究所推出的云原生量子计算平台。量易伏很好地实现了量子计算和云计算的深度融合。量易包含本地模拟器、云端模拟器和云端量子计算机等多种使用方式。其中,云端模拟器和云端量子计算机的使用需要消耗量易伏点数,用户需要在[量易伏网站](https://quantum-hub.baidu.com/)上进行注册后可获得量易伏账号和点数。量易伏包含 QComposer、PyOnline、YunOnline、QCompute SDK 等多种调用方式。其中 QCompute SDK 允许我们通过 Python 进行调用。在 Python 环境中,需要输入账号对应的 token 来进行调用,token 可在 https://quantum-hub.baidu.com/token 上进行查看。如果想使用云端模拟器或云端量子计算机,需要消耗量易伏点数,点数可在 https://quantum-hub.baidu.com/feedback 页面发送邮件进行获取。\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 量桨连接量易伏\n",
"\n",
"目前量桨已经支持连接量易伏,只需要使用 `paddle_quantum.set_backend('quleaf')` 即可将量桨的后端设置为量易伏。除此之外,我们还需要设置量易伏的模拟方式。如果使用云端算力,则还需要输入 token。因此,完整的设置代码如下:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import paddle_quantum\n",
"from QCompute import BackendName\n",
"paddle_quantum.set_backend('quleaf')\n",
"paddle_quantum.backend.quleaf.set_quleaf_backend(BackendName.LocalBaiduSim2)\n",
"# 如果使用本地模拟器,则可以不需要输入 token\n",
"# paddle_quantum.backend.quleaf.set_quleaf_token('your token')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"在 `set_quleaf_backend()` 函数中可以设置量易伏的后端模拟方式。\n",
"\n",
"目前,量易伏的所有后端如下:\n",
"\n",
"| 类型 | 量子端 | 说明 |\n",
"| :--: | :--: | :--: |\n",
"| 本地 | LocalBaiduSim2 | 使用 Python 编写的 Sim2 本地版 |\n",
"| 云端 | CloudBaiduSim2Water | 使用 C++ 编写的多实例 Sim2 模拟器云版 |\n",
"| 云端 | CloudBaiduSim2Earth | 使用 Python 编写的单一实例高配置 Sim2 模拟器云版 |\n",
"| 云端 | CloudBaiduSim2Thunder | 使用 C++ 编写的单一实例高配置 Sim2 模拟器云版 |\n",
"| 云端 | CloudBaiduSim2Wind | 使用 C++ 编写的单一实例 Sim2 模拟器云版(支持稀疏模式) |\n",
"| 云端 | CloudBaiduSim2Heaven | 使用 C++ 编写的单一实例集群 Sim2 模拟器云版 |\n",
"| 云端 | CloudBaiduSim2Lake | 使用 C++ 编写的单一实例 Sim2 模拟器云版 (支持 GPU 运算) |\n",
"| 云端 | CloudAerAtBD | 开源 Aer(C++ 版) 模拟器云版 |\n",
"| 云端 | CloudIoPCAS | 来自中科院物理所的 10 比特量子真机 (VIP) |\n",
"\n",
"其中,`LocalBaiduSim2` 为本地模拟器,不需要输入 token,不会使用量易伏点数;`CloudIoPCAS` 为真实的量子计算机,云端的量子计算机和模拟器都需要输入token,且会消耗量易伏点数。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 贝尔态制备\n",
"\n",
"这里,我们以制备贝尔态为例测试量桨是否成功连接量易伏。在执行了上面的代码之后,用户就已经将量桨设置为了量易伏模式。"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"num_qubits = 2\n",
"init_state = paddle_quantum.state.zero_state(num_qubits)\n",
"circuit = paddle_quantum.ansatz.Circuit(num_qubits)\n",
"circuit.h(0)\n",
"circuit.cnot([0, 1])\n",
"bell_state = circuit(init_state)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"如果上面的代码能成功运行,则说明可以连接到量易伏,并执行量子电路。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## VQE 例子\n",
"\n",
"这里,我们以VQE为例来介绍如何通过量桨调用量易伏算力。我们使用量易伏的本地模拟器来进行 demo 演示。"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The iter is 0, loss is -0.32104.\n",
"The iter is 10, loss is -0.49239.\n",
"The iter is 20, loss is -0.80956.\n",
"The iter is 30, loss is -1.07812.\n",
"The iter is 40, loss is -1.09850.\n",
"The iter is 50, loss is -1.13334.\n",
"The iter is 60, loss is -1.13445.\n",
"The iter is 70, loss is -1.13492.\n",
"The theoretical value is -1.137283834485513.\n"
]
}
],
"source": [
"import paddle\n",
"\n",
"# 定义哈密顿量\n",
"hamiltonian_list = [\n",
" [-0.0970662686176252, 'I'],\n",
" [-0.04530261550868938, 'X0, X1, Y2, Y3'],\n",
" [0.04530261550868938, 'X0, Y1, Y2, X3'],\n",
" [0.04530261550868938, 'Y0, X1, X2, Y3'],\n",
" [-0.04530261550868938, 'Y0, Y1, X2, X3'],\n",
" [0.1714128263940238, 'Z0'],\n",
" [0.16868898168693292, 'Z0, Z1'],\n",
" [0.12062523481381847, 'Z0, Z2'],\n",
" [0.1659278503225078, 'Z0, Z3'],\n",
" [0.17141282639402383, 'Z1'],\n",
" [0.1659278503225078, 'Z1, Z2'],\n",
" [0.12062523481381847, 'Z1, Z3'],\n",
" [-0.22343153674664024, 'Z2'],\n",
" [0.17441287610651632, 'Z2, Z3'],\n",
" [-0.2234315367466403, 'Z3'],\n",
"]\n",
"\n",
"# 定义电路\n",
"num_qubits = 4\n",
"circuit = paddle_quantum.ansatz.Circuit(num_qubits)\n",
"circuit.ry('full')\n",
"circuit.cnot('cycle')\n",
"circuit.ry('full')\n",
"circuit.cnot('cycle')\n",
"circuit.ry('full')\n",
"# print(circuit)\n",
"\n",
"# 定义初态和优化器\n",
"init_state = paddle_quantum.state.zero_state(num_qubits)\n",
"optimizer = paddle.optimizer.Adam(learning_rate=0.1, parameters=circuit.parameters())\n",
"hamiltonian = paddle_quantum.Hamiltonian(hamiltonian_list)\n",
"loss_func = paddle_quantum.loss.ExpecVal(hamiltonian, shots=10000)\n",
"# 进行迭代训练\n",
"num_itr = 80\n",
"for itr in range(0, num_itr):\n",
" state = circuit(init_state)\n",
" loss = loss_func(state)\n",
" loss.backward()\n",
" optimizer.minimize(loss)\n",
" optimizer.clear_grad()\n",
" if itr % 10 == 0:\n",
" print(f\"The iter is {itr:3d}, loss is {loss.item():3.5f}.\")\n",
"print(\"The theoretical value is -1.137283834485513.\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"由上面的训练过程可以看出,我们可以通过量桨调用量易伏来实现量子机器学习算法。\n",
"\n",
"最后,我们总结一下量桨调用量易伏的用法。只需要在程序的最开头部分设置量桨和量易伏的 backend 就行。需要注意的是,很多函数都不支持在量易伏上运行,只有量子电路相关的功能才支持。"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.8.0 ('paddle-quantum-dev')",
"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.0"
},
"orig_nbformat": 4,
"vscode": {
"interpreter": {
"hash": "9043b12ec77a531919bc05f05830335d23baf822720cbea14b03018197d26545"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Using Paddle Quantum to connect to a quantum computer via the QuLeaf platform\n",
"\n",
"*Copyright (c) 2022 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.*\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Introduction\n",
"\n",
"In quantum machine learning, the ultimate goal we want to achieve is to use quantum computers and classical computers for hybrid computation, thus enabling efficient Quantum Machine Learning (QML) algorithms. In Paddle Quantum, we also support the implementation of QML algorithms using quantum computers by connecting to a real quantum computer via [QuLeaf](https://quantum-hub.baidu.com/), a cloud-native quantum computing platform.\n",
"\n",
"### Introduction to QuLeaf\n",
"\n",
"QuLeaf is a cloud-native quantum computing platform launched by the Institute of Quantum Computing of Baidu Research Institute. QuLeaf is a good implementation of the deep integration of quantum computing and cloud computing. QuLeaf includes various usage methods such as local simulator, cloud-based simulator and cloud-based quantum computer. Among them, the use of cloud simulator and cloud quantum computer needs to consume QuLeaf credits, users need to register on the [QuLeaf website](https://quantum-hub.baidu.com/) to get QuLeaf account and points. QuLeaf includes QComposer, PyOnline, YunOnline, QCompute SDK and so on. Among them, QCompute SDK allows us to make calls through Python. In the Python environment, you need to enter the token corresponding to your account to make the call, and the token can be viewed at https://quantum-hub.baidu.com/token. If you want to use the cloud-based simulator or cloud-based quantum computer, you need to consume QuLeaf credits which can be obtained in the https://quantum-hub.baidu.com/feedback."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Paddle Quantum calls QuLeaf\n",
"\n",
"In the paddle quantum, we already support the backend implementation of QuLeaf. Just use `paddle_quantum.set_backend('quleaf')` to set the backend of the quantum paddle to QuLeaf. In addition to that, we need to set the simulation method of the QuLeaf. If we use cloud computation power, we also need to enter the token, so the complete setup code is as follows."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import paddle_quantum\n",
"from QCompute import BackendName\n",
"paddle_quantum.set_backend('quleaf')\n",
"paddle_quantum.backend.quleaf.set_quleaf_backend(BackendName.LocalBaiduSim2)\n",
"# If you are using the local simulator, you don't need to enter your token\n",
"# paddle_quantum.backend.quleaf.set_quleaf_token('your token')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `set_quleaf_backend()` function allows you to set the quantum end of QuLeaf.\n",
"\n",
"Currently, all the end the QuLeaf are as follows:\n",
"\n",
"| Type | Quantum End | Description |\n",
"| :---: | :---: | :---: |\n",
"| Local | LocalBaiduSim2 | Sim2, local simulator, Python version |\n",
"| Cloud | CloudBaiduSim2Water | Sim2, Cloud Simulator, C++ Version, Dockerd Multiple Instances |\n",
"| Cloud | CloudBaiduSim2Earth | Sim2, Cloud Simulator, Python Version, Single Instance, high-performance |\n",
"| Cloud | CloudBaiduSim2Thunder | Sim2, Cloud Simulator, C++ Version, Single Instance, high-performance |\n",
"| Cloud | CloudBaiduSim2Wind | Sim2, Cloud Simulator, C++ Version, Single Instance, Sparse |\n",
"| Cloud | CloudBaiduSim2Heaven | Sim2, Cloud Simulator, C++ Version, Single Instance, Cluster |\n",
"| Cloud | CloudBaiduSim2Lake | Sim2, Cloud Simulator, C++ Version, Single Instance, GPU |\n",
"| Cloud | CloudAerAtBD | Aer, Cloud Simulator, C++ Version (open-source) |\n",
"| Cloud | CloudIoPCAS | QPU 10-qubit quantum computer, from the Institute of Physics CAS |\n",
"\n",
"In this case, `LocalBaiduSim2` is a local simulator, which does not require token input and does not consume the QuLeaf credits; `CloudIoPCAS` is a real quantum computer, both the quantum computer and the simulator in the cloud require token input and consume the QuLeaf credits."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Bell state preparation\n",
"\n",
"Here, we test whether the paddle quantum is successfully connected to the QuLeaf by the example of the Bell state preparation. After executing the above code, the user has already set the paddle quantum to the QuLeaf mode."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"num_qubits = 2\n",
"init_state = paddle_quantum.state.zero_state(num_qubits)\n",
"circuit = paddle_quantum.ansatz.Circuit(num_qubits)\n",
"circuit.h(0)\n",
"circuit.cnot([0, 1])\n",
"bell_state = circuit(init_state)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If the above code runs successfully, it means that it can connect to the QuLeaf and execute the quantum circuit."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## VQE Example\n",
"\n",
"Here, we use VQE as an example to show how to use the QuLeaf through the paddle quantum. We use QuLeaf's local simulator for the demo."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The iter is 0, loss is 0.15198.\n",
"The iter is 10, loss is -0.76691.\n",
"The iter is 20, loss is -0.91600.\n",
"The iter is 30, loss is -0.97056.\n",
"The iter is 40, loss is -1.05518.\n",
"The iter is 50, loss is -1.13397.\n",
"The iter is 60, loss is -1.12326.\n",
"The iter is 70, loss is -1.13693.\n",
"The theoretical value is -1.137283834485513.\n"
]
}
],
"source": [
"import paddle\n",
"\n",
"# define the hamiltonian\n",
"hamiltonian_list = [\n",
" [-0.0970662686176252, 'I'],\n",
" [-0.04530261550868938, 'X0, X1, Y2, Y3'],\n",
" [0.04530261550868938, 'X0, Y1, Y2, X3'],\n",
" [0.04530261550868938, 'Y0, X1, X2, Y3'],\n",
" [-0.04530261550868938, 'Y0, Y1, X2, X3'],\n",
" [0.1714128263940238, 'Z0'],\n",
" [0.16868898168693292, 'Z0, Z1'],\n",
" [0.12062523481381847, 'Z0, Z2'],\n",
" [0.1659278503225078, 'Z0, Z3'],\n",
" [0.17141282639402383, 'Z1'],\n",
" [0.1659278503225078, 'Z1, Z2'],\n",
" [0.12062523481381847, 'Z1, Z3'],\n",
" [-0.22343153674664024, 'Z2'],\n",
" [0.17441287610651632, 'Z2, Z3'],\n",
" [-0.2234315367466403, 'Z3'],\n",
"]\n",
"\n",
"# define the quantum circuit\n",
"num_qubits = 4\n",
"circuit = paddle_quantum.ansatz.Circuit(num_qubits)\n",
"circuit.ry('full')\n",
"circuit.cnot('cycle')\n",
"circuit.ry('full')\n",
"circuit.cnot('cycle')\n",
"circuit.ry('full')\n",
"# print(circuit)\n",
"\n",
"# define the initial quantum state and the optimizer\n",
"init_state = paddle_quantum.state.zero_state(num_qubits)\n",
"optimizer = paddle.optimizer.Adam(learning_rate=0.1, parameters=circuit.parameters())\n",
"hamiltonian = paddle_quantum.Hamiltonian(hamiltonian_list)\n",
"loss_func = paddle_quantum.loss.ExpecVal(hamiltonian, shots=10000)\n",
"# iterative training\n",
"num_itr = 80\n",
"for itr in range(0, num_itr):\n",
" state = circuit(init_state)\n",
" loss = loss_func(state)\n",
" loss.backward()\n",
" optimizer.minimize(loss)\n",
" optimizer.clear_grad()\n",
" if itr % 10 == 0:\n",
" print(f\"The iter is {itr:3d}, loss is {loss.item():3.5f}.\")\n",
"print(\"The theoretical value is -1.137283834485513.\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"From the above training process, we can see that we can implement quantum machine learning algorithm by paddle quantum calling the QuLeaf.\n",
"\n",
"Finally, let's summarize the usage of the paddle quantum to call the QuLeaf. All you need to do is to set the backend of the paddle quantum and the QuLeaf in the beginning of the program. Note that many functions are not supported on the QuLeaf, only quantum circuit related functions are supported."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.8.0 ('paddle-quantum-dev')",
"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.0"
},
"orig_nbformat": 4,
"vscode": {
"interpreter": {
"hash": "9043b12ec77a531919bc05f05830335d23baf822720cbea14b03018197d26545"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
...@@ -35,7 +35,8 @@ options = { ...@@ -35,7 +35,8 @@ options = {
} }
def main(n=4): if __name__ == "__main__":
n = 4
paddle.seed(SEED) paddle.seed(SEED)
p = 4 # number of layers in the circuit p = 4 # number of layers in the circuit
...@@ -73,7 +74,3 @@ def main(n=4): ...@@ -73,7 +74,3 @@ def main(n=4):
ax.margins(0.20) ax.margins(0.20)
plt.axis("off") plt.axis("off")
plt.show() plt.show()
if __name__ == "__main__":
main()
...@@ -33,13 +33,13 @@ __all__ = [ ...@@ -33,13 +33,13 @@ __all__ = [
def maxcut_hamiltonian(E): def maxcut_hamiltonian(E):
r"""生成最大割问题对应的哈密顿量。 r"""Generate the Hamiltonian for Max-Cut problem
Args: Args:
E (list): 图的边 E (list): Edges of the graph
Returns: Returns:
list: 生成的哈密顿量的列表形式 list: list form of the generated Hamiltonian
""" """
H_D_list = [] H_D_list = []
for (u, v) in E: for (u, v) in E:
...@@ -49,22 +49,22 @@ def maxcut_hamiltonian(E): ...@@ -49,22 +49,22 @@ def maxcut_hamiltonian(E):
def find_cut(G, p, ITR, LR, print_loss=False, shots=0, plot=False): def find_cut(G, p, ITR, LR, print_loss=False, shots=0, plot=False):
r"""运行 QAOA 寻找最大割问题的近似解。 r"""Find the approximated solution of given Max-Cut problem via QAOA
Args: Args:
G (NetworkX graph): G (NetworkX graph): Graph
p (int): QAOA 电路的层数 p (int): depth of the QAOA circuit
ITR (int): 梯度下降优化参数的迭代次数 ITR (int): maximum iteration times for optimization
LR (float): Adam 优化器的学习率 LR (float): learning rate of the Adam optimizer
print_loss (bool, optional): 优化过程中是否输出损失函数的值,默认为 ``False``,即不输出 print_loss (bool, optional): whether print the loss value during optimization. Defaults to ``False``, not print
shots (int, optional): QAOA 电路最终输出的量子态的测量次数,默认 0,则返回测量结果的精确概率分布 shots (int, optional): measurement times at the final output of QAOA circuit, Defaults to ``0``, exact probability distribution
plot (bool, optional): 是否绘制测量结果图,默认为 ``False`` ,即不绘制 plot (bool, optional): whether plot the result of measurement, Defaults to ``False``, not plot
Returns: Returns:
tuple: tuple containing: tuple: tuple containing:
string: 寻找到的近似解 string: approximated solution
dict: 所有测量结果和其对应的出现次数 dict: measurement results and their frequencies
""" """
V = list(G.nodes()) V = list(G.nodes())
# Map nodes' labels to integers from 0 to |V|-1 # Map nodes' labels to integers from 0 to |V|-1
......
...@@ -34,13 +34,15 @@ __all__ = [ ...@@ -34,13 +34,15 @@ __all__ = [
def tsp_hamiltonian(g, A, n): def tsp_hamiltonian(g, A, n):
""" r"""This is to construct Hamiltonia H_C
This is to construct Hamiltonia H_C
Args: Args:
G: the graph to solve g: the graph to solve
A: the penality parameter
n: the number of vertices of graph g
Returns: Returns:
Hamiltonian list Hamiltonian with list form
Hamiltonian H_C
""" """
H_C_list1 = [] H_C_list1 = []
for i in range(n - 1): for i in range(n - 1):
...@@ -93,8 +95,7 @@ def tsp_hamiltonian(g, A, n): ...@@ -93,8 +95,7 @@ def tsp_hamiltonian(g, A, n):
def solve_tsp(g, A, p=2, ITR=120, LR=0.4, print_loss=False, shots=0): def solve_tsp(g, A, p=2, ITR=120, LR=0.4, print_loss=False, shots=0):
""" r"""This is the core function to solve the TSP.
This is the core function to solve the TSP.
Args: Args:
g: the graph to solve g: the graph to solve
...@@ -102,6 +103,9 @@ def solve_tsp(g, A, p=2, ITR=120, LR=0.4, print_loss=False, shots=0): ...@@ -102,6 +103,9 @@ def solve_tsp(g, A, p=2, ITR=120, LR=0.4, print_loss=False, shots=0):
p: number of layers of blocks in the complex entangled circuit (default value p=2) p: number of layers of blocks in the complex entangled circuit (default value p=2)
ITR: number of iteration steps for the complex entangled circuit (default value ITR=120) ITR: number of iteration steps for the complex entangled circuit (default value ITR=120)
LR: learning rate for the gradient-based optimization method (default value LR=0.4) LR: learning rate for the gradient-based optimization method (default value LR=0.4)
print_loss (bool, optional): whether print the loss value during optimization. Defaults to ``False``, not print
shots (int, optional): measurement times at the final output of QAOA circuit, Defaults to ``0``, exact probability distribution
Returns: Returns:
string representation for the optimized walk for the salesman string representation for the optimized walk for the salesman
""" """
......
...@@ -23,10 +23,15 @@ __all__ = ["H_generator"] ...@@ -23,10 +23,15 @@ __all__ = ["H_generator"]
def H_generator(N): def H_generator(N):
r"""Generate a Hamiltonian with trivial descriptions
Args:
N: Number of Pauli strings
Returns:
A Hamiltonian
""" """
Generate a Hamiltonian with trivial descriptions
Returns: A Hamiltonian
"""
# Generate the Pauli string representing a random Hamiltonian # Generate the Pauli string representing a random Hamiltonian
hamiltonian = random_pauli_str_generator(N, terms=10) hamiltonian = random_pauli_str_generator(N, terms=10)
print("Random Hamiltonian in Pauli string format = \n", hamiltonian) print("Random Hamiltonian in Pauli string format = \n", hamiltonian)
......
...@@ -37,6 +37,16 @@ __all__ = [ ...@@ -37,6 +37,16 @@ __all__ = [
def loss_func(U, H): def loss_func(U, H):
r"""Compute the loss function of SSVQE
Args:
H: Hamiltonian
U: unitary of the circuit
Returns: Tutle: inlcuding following elements
- loss function
- loss components
"""
# Calculate loss function # Calculate loss function
loss_struct = paddle.real(matmul(matmul(dagger(U), H), U)) loss_struct = paddle.real(matmul(matmul(dagger(U), H), U))
# Use computational basis to calculate each expectation value, which is the same # Use computational basis to calculate each expectation value, which is the same
...@@ -55,13 +65,16 @@ def loss_func(U, H): ...@@ -55,13 +65,16 @@ def loss_func(U, H):
def Paddle_SSVQE(H, N=2, ITR=50, LR=0.3): def Paddle_SSVQE(H, N=2, ITR=50, LR=0.3):
r""" r"""Paddle_SSVQE
Paddle_SSVQE
:param H: Hamiltonian Args:
:param N: Number of qubits/Width of QNN H: Hamiltonian
:param ITR: Number of iterations N: Number of qubits/Width of QNN
:param LR: Learning rate ITR: Number of iterations
:return: First several smallest eigenvalues of the Hamiltonian LR: Learning rate
Returns:
First several smallest eigenvalues of the Hamiltonian
""" """
# We need to convert Numpy array to variable supported in PaddlePaddle # We need to convert Numpy array to variable supported in PaddlePaddle
...@@ -89,7 +102,7 @@ def Paddle_SSVQE(H, N=2, ITR=50, LR=0.3): ...@@ -89,7 +102,7 @@ def Paddle_SSVQE(H, N=2, ITR=50, LR=0.3):
return loss_components return loss_components
def main(): if __name__ == '__main__':
paddle.seed(SEED) paddle.seed(SEED)
N = 2 N = 2
H = H_generator(N) H = H_generator(N)
...@@ -127,6 +140,3 @@ def main(): ...@@ -127,6 +140,3 @@ def main():
output_ordinalvalue(i), numpy.linalg.eigh(H)[0][i]) output_ordinalvalue(i), numpy.linalg.eigh(H)[0][i])
) )
if __name__ == '__main__':
main()
...@@ -20,9 +20,10 @@ main ...@@ -20,9 +20,10 @@ main
import numpy import numpy
from paddle_quantum.SSVQE.HGenerator import H_generator from paddle_quantum.SSVQE.HGenerator import H_generator
from paddle_quantum.SSVQE.Paddle_SSVQE import Paddle_SSVQE from paddle_quantum.SSVQE.Paddle_SSVQE import Paddle_SSVQE
def main(): if __name__ == '__main__':
N = 2 N = 2
H = H_generator(N) H = H_generator(N)
...@@ -38,8 +39,4 @@ def main(): ...@@ -38,8 +39,4 @@ def main():
print('The theoretical 2nd excited state energy: ', numpy.linalg.eigh(H)[0][2]) 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 estimated 3rd excited state energy is: ', loss_components[3].numpy())
print('The theoretical 3rd excited state energy: ', numpy.linalg.eigh(H)[0][3]) print('The theoretical 3rd excited state energy: ', numpy.linalg.eigh(H)[0][3])
\ No newline at end of file
if __name__ == '__main__':
main()
...@@ -37,14 +37,14 @@ __all__ = [ ...@@ -37,14 +37,14 @@ __all__ = [
def Paddle_VQE(Hamiltonian, N, D=2, ITR=80, LR=0.2): def Paddle_VQE(Hamiltonian, N, D=2, ITR=80, LR=0.2):
r""" r"""Main Learning network using dynamic graph
Main Learning network using dynamic graph
:param Hamiltonian: Hamiltonian Args:
:param N: Width of QNN Hamiltonian: Hamiltonian
:param D: Depth of QNN N: Width of QNN
:param ITR: Number of iterations D: Depth of QNN. Defaults to 2.
:param LR: Learning rate ITR: Number of iterations. Defaults to 80.
:return: No return LR: Learning rate. Defaults to 0.2.
""" """
# Determine the dimensions of network # Determine the dimensions of network
...@@ -86,7 +86,7 @@ def Paddle_VQE(Hamiltonian, N, D=2, ITR=80, LR=0.2): ...@@ -86,7 +86,7 @@ def Paddle_VQE(Hamiltonian, N, D=2, ITR=80, LR=0.2):
savez("./output/summary_data", iter=summary_iter, energy=summary_loss) savez("./output/summary_data", iter=summary_iter, energy=summary_loss)
def main(): if __name__ == '__main__':
# Read data from built-in function or xyz file depending on OS # Read data from built-in function or xyz file depending on OS
sysStr = platform.system() sysStr = platform.system()
...@@ -110,7 +110,3 @@ def main(): ...@@ -110,7 +110,3 @@ def main():
Paddle_VQE(hamiltonian, N) Paddle_VQE(hamiltonian, N)
benchmark_result() benchmark_result()
if __name__ == '__main__':
main()
...@@ -29,10 +29,15 @@ __all__ = [ ...@@ -29,10 +29,15 @@ __all__ = [
] ]
def Hamiltonian_str_convert(qubit_op): def _Hamiltonian_str_convert(qubit_op):
''' r"""Convert provided Hamiltonian information to Pauli string
Convert provided Hamiltonian information to Pauli string
''' Args:
qubit_op: instance of ``QubitOperator`` class defined in ``openfermion``
Returns:
H_info for Hamiltonian
"""
info_dic = qubit_op.terms info_dic = qubit_op.terms
def process_tuple(tup): def process_tuple(tup):
...@@ -55,10 +60,21 @@ def Hamiltonian_str_convert(qubit_op): ...@@ -55,10 +60,21 @@ def Hamiltonian_str_convert(qubit_op):
def calc_H_rho_from_qubit_operator(qubit_op, n_qubits): def calc_H_rho_from_qubit_operator(qubit_op, n_qubits):
r"""Generate a Hamiltonian from QubitOperator
Args:
qubit_op: instance of ``QubitOperator`` class defined in ``openfermion``
n_qubits: number of qubits
Raises:
Exception: unrecognized basis
Returns:
Tuple:
- H: (2**n, 2**n) complex128 array, as the Hamiltonian (n == n_qubits)
- rho: (2**n, 2**n) complex128 array, as the density matrix (n == n_qubits)
""" """
Generate a Hamiltonian from QubitOperator
Returns: H, rho
"""
# const # const
beta = 1 beta = 1
...@@ -94,9 +110,20 @@ def calc_H_rho_from_qubit_operator(qubit_op, n_qubits): ...@@ -94,9 +110,20 @@ def calc_H_rho_from_qubit_operator(qubit_op, n_qubits):
def read_calc_H(geo_fn, multiplicity=1, charge=0): def read_calc_H(geo_fn, multiplicity=1, charge=0):
""" r"""Read and calc the H and rho
Read and calc the H and rho
Returns: H,rho matrix Args:
geo_fn (str): geometry filename
multiplicity (int, optional): used in openfermionpyscf, Defaults to 1.
charge (int, optional): used in openfermionpyscf, Defaults to 0.
Raises:
Exception: filename should be a string
Returns:
Tuple:
- H: the Hamiltonian
- nqubit: qubit 的个数
""" """
if not isinstance(geo_fn, str): # geo_fn = 'h2.xyz' if not isinstance(geo_fn, str): # geo_fn = 'h2.xyz'
...@@ -117,19 +144,11 @@ def read_calc_H(geo_fn, multiplicity=1, charge=0): ...@@ -117,19 +144,11 @@ def read_calc_H(geo_fn, multiplicity=1, charge=0):
qubit_op = openfermion.transforms.jordan_wigner(molecular_hamiltonian) qubit_op = openfermion.transforms.jordan_wigner(molecular_hamiltonian)
# calc H # calc H
Hamiltonian = Hamiltonian_str_convert(qubit_op) Hamiltonian = _Hamiltonian_str_convert(qubit_op)
return Hamiltonian, molecular_hamiltonian.n_qubits return Hamiltonian, molecular_hamiltonian.n_qubits
def main(): if __name__ == '__main__':
"""
The main function
"""
filename = 'h2.xyz' filename = 'h2.xyz'
H, N = read_calc_H(geo_fn=filename) H, N = read_calc_H(geo_fn=filename)
print('H', H) print('H', H)
if __name__ == '__main__':
main()
...@@ -27,9 +27,12 @@ __all__ = [ ...@@ -27,9 +27,12 @@ __all__ = [
def H_generator(): def H_generator():
""" r"""Generate a Hamiltonian with trivial descriptions
Generate a Hamiltonian with trivial descriptions
:return: a Hamiltonian, 'mat' Returns:
Tuple: including following elements
- H: the Hamiltonian
- rho: density matrix
""" """
beta = 1 beta = 1
...@@ -47,9 +50,12 @@ def H_generator(): ...@@ -47,9 +50,12 @@ def H_generator():
def H2_generator(): def H2_generator():
""" r"""Generate a Hamiltonian with trivial descriptions
Generate a Hamiltonian with trivial descriptions
Returns: A Hamiltonian, 'mat' Returns:
tuple contains
- H: Hamiltonian, a list of Pauli string
- N: the number of qubits
""" """
beta = 1 beta = 1
......
...@@ -23,11 +23,8 @@ from paddle_quantum.VQE.benchmark import benchmark_result ...@@ -23,11 +23,8 @@ from paddle_quantum.VQE.benchmark import benchmark_result
from paddle_quantum.VQE.chemistrysub import H2_generator from paddle_quantum.VQE.chemistrysub import H2_generator
def main(): if __name__ == '__main__':
""" #Main Learning network using dynamic graph
Main Learning network using dynamic graph
:return: Plot or No return
"""
# Read data from built-in function or xyz file depending on OS # Read data from built-in function or xyz file depending on OS
sysStr = platform.system() sysStr = platform.system()
...@@ -51,7 +48,3 @@ def main(): ...@@ -51,7 +48,3 @@ def main():
Paddle_VQE(hamiltonian, N) Paddle_VQE(hamiltonian, N)
benchmark_result() benchmark_result()
if __name__ == '__main__':
main()
...@@ -27,6 +27,14 @@ __all__ = ["generate_rho_sigma", ] ...@@ -27,6 +27,14 @@ __all__ = ["generate_rho_sigma", ]
def generate_rho_sigma(): def generate_rho_sigma():
r"""Generate two quantum states with specific eigenvalues
Returns: Tuple: including following elements
- rho
- sigma
"""
numpy.random.seed(SEED) numpy.random.seed(SEED)
V = scipy.stats.unitary_group.rvs(4) # Generate a random unitary matrix V = scipy.stats.unitary_group.rvs(4) # Generate a random unitary matrix
D = numpy.diag([0.5, 0.3, 0.1, 0.1]) # Input the spectrum of the target state rho D = numpy.diag([0.5, 0.3, 0.1, 0.1]) # Input the spectrum of the target state rho
......
...@@ -36,6 +36,18 @@ __all__ = [ ...@@ -36,6 +36,18 @@ __all__ = [
def loss_func(U, rho, sigma): def loss_func(U, rho, sigma):
r"""Compute the loss function of VQSD
Args:
rho: Quantum state to be diagonalized
sigma: Quantum state sigma
U: Unitary of the circuit
Returns:
Overlap between sigma and the state that is generated by applying the unitary to rho
"""
# rho_tilda is the quantum state obtained by acting U on rho, which is U*rho*U^dagger # rho_tilda is the quantum state obtained by acting U on rho, which is U*rho*U^dagger
rho_tilde = matmul(matmul(U, rho), dagger(U)) rho_tilde = matmul(matmul(U, rho), dagger(U))
...@@ -46,14 +58,17 @@ def loss_func(U, rho, sigma): ...@@ -46,14 +58,17 @@ def loss_func(U, rho, sigma):
def Paddle_VQSD(rho, sigma, N=2, ITR=50, LR=0.2): def Paddle_VQSD(rho, sigma, N=2, ITR=50, LR=0.2):
r""" r"""Paddle_VQSD
Paddle_VQSD
:param rho: Quantum state to be diagonalized Args:
:param sigma: Quantum state sigma rho: Quantum state to be diagonalized
:param N: Width of QNN sigma: Quantum state sigma
:param ITR: Number of iterations N: Width of QNN
:param LR: Learning rate ITR: Number of iterations
:return: Diagonalized quantum state after optimization LR: Learning rate
Returns:
Diagonalized quantum state after optimization
""" """
rho = paddle.to_tensor(rho, dtype=paddle_quantum.get_dtype()) rho = paddle.to_tensor(rho, dtype=paddle_quantum.get_dtype())
sigma = paddle.to_tensor(sigma, dtype=paddle_quantum.get_dtype()) sigma = paddle.to_tensor(sigma, dtype=paddle_quantum.get_dtype())
...@@ -82,9 +97,9 @@ def Paddle_VQSD(rho, sigma, N=2, ITR=50, LR=0.2): ...@@ -82,9 +97,9 @@ def Paddle_VQSD(rho, sigma, N=2, ITR=50, LR=0.2):
return rho_tilde_np return rho_tilde_np
def main(): if __name__ == '__main__':
D = [0.5, 0.3, 0.1, 0.1] D = [0.5, 0.3, 0.1, 0.1]
rho, sigma = generate_rho_sigma() rho, sigma = generate_rho_sigma()
...@@ -93,7 +108,3 @@ def main(): ...@@ -93,7 +108,3 @@ def main():
print("The estimated spectrum is:", 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) print('The target spectrum is:', D)
if __name__ == '__main__':
main()
...@@ -20,9 +20,10 @@ Main ...@@ -20,9 +20,10 @@ Main
import numpy import numpy
from paddle_quantum.VQSD.HGenerator import generate_rho_sigma from paddle_quantum.VQSD.HGenerator import generate_rho_sigma
from paddle_quantum.VQSD.Paddle_VQSD import Paddle_VQSD from paddle_quantum.VQSD.Paddle_VQSD import Paddle_VQSD
def main(): if __name__ == '__main__':
""" """
main main
""" """
...@@ -34,7 +35,3 @@ def main(): ...@@ -34,7 +35,3 @@ def main():
print("The estimated spectrum is:", 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) print('The target spectrum is:', D)
if __name__ == '__main__':
main()
...@@ -16,7 +16,8 @@ ...@@ -16,7 +16,8 @@
r""" r"""
Paddle Quantum Library. Paddle Quantum Library.
""" """
import os
os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"]="python"
from .backend import Backend from .backend import Backend
from .state import State from .state import State
from .base import Operator from .base import Operator
...@@ -32,7 +33,6 @@ from . import locc ...@@ -32,7 +33,6 @@ from . import locc
from . import loss from . import loss
from . import mbqc from . import mbqc
from . import operator from . import operator
from . import qchem
from . import base from . import base
from . import dataset from . import dataset
from . import finance from . import finance
...@@ -46,4 +46,4 @@ from . import trotter ...@@ -46,4 +46,4 @@ from . import trotter
from . import visual from . import visual
name = 'paddle_quantum' name = 'paddle_quantum'
__version__ = '2.2.0' __version__ = '2.2.1'
...@@ -17,6 +17,7 @@ r""" ...@@ -17,6 +17,7 @@ r"""
The source file of the Circuit class. The source file of the Circuit class.
""" """
import warnings
import paddle import paddle
from .container import Sequential from .container import Sequential
from ..gate import Gate, H, S, T, X, Y, Z, P, RX, RY, RZ, U3 from ..gate import Gate, H, S, T, X, Y, Z, P, RX, RY, RZ, U3
...@@ -31,7 +32,9 @@ from ..gate import QAOALayer ...@@ -31,7 +32,9 @@ from ..gate import QAOALayer
from ..gate import AmplitudeEncoding from ..gate import AmplitudeEncoding
from ..channel import BitFlip, PhaseFlip, BitPhaseFlip, AmplitudeDamping, GeneralizedAmplitudeDamping, PhaseDamping from ..channel import BitFlip, PhaseFlip, BitPhaseFlip, AmplitudeDamping, GeneralizedAmplitudeDamping, PhaseDamping
from ..channel import Depolarizing, PauliChannel, ResetChannel, ThermalRelaxation, KrausRepr from ..channel import Depolarizing, PauliChannel, ResetChannel, ThermalRelaxation, KrausRepr
from ..intrinsic import _get_float_dtype
from ..state import zero_state from ..state import zero_state
from ..operator import Collapse
from typing import Union, Iterable, Optional, Dict, List, Tuple from typing import Union, Iterable, Optional, Dict, List, Tuple
from paddle_quantum import State, get_backend, get_dtype, Backend from paddle_quantum import State, get_backend, get_dtype, Backend
from math import pi from math import pi
...@@ -44,14 +47,14 @@ class Circuit(Sequential): ...@@ -44,14 +47,14 @@ class Circuit(Sequential):
Args: Args:
num_qubits: Number of qubits. Defaults to None. num_qubits: Number of qubits. Defaults to None.
""" """
def __init__(self, num_qubits: Optional[int] = None): def __init__(self, num_qubits: Optional[int] = None):
super().__init__() super().__init__()
self.__num_qubits = num_qubits self.__num_qubits = num_qubits
# whether the circuit is a dynamic quantum circuit # whether the circuit is a dynamic quantum circuit
self.__isdynamic = True if num_qubits is None else False self.__isdynamic = True if num_qubits is None else False
# alias for ccx # alias for ccx
self.toffoli = self.ccx self.toffoli = self.ccx
...@@ -66,18 +69,20 @@ class Circuit(Sequential): ...@@ -66,18 +69,20 @@ class Circuit(Sequential):
r"""Whether the circuit is dynamic r"""Whether the circuit is dynamic
""" """
return self.__dynamic return self.__dynamic
@num_qubits.setter @num_qubits.setter
def num_qubits(self, value: int) -> None: def num_qubits(self, value: int) -> None:
assert isinstance(value, int) assert isinstance(value, int)
self.__num_qubits = value self.__num_qubits = value
@property @property
def param(self) -> paddle.Tensor: def param(self) -> paddle.Tensor:
r"""Flattened parameters in the circuit. r"""Flattened parameters in the circuit.
""" """
if len(self.parameters()) == 0:
return []
return paddle.concat([paddle.flatten(param) for param in self.parameters()]) return paddle.concat([paddle.flatten(param) for param in self.parameters()])
@property @property
def grad(self) -> np.ndarray: def grad(self) -> np.ndarray:
r"""Gradients with respect to the flattened parameters. r"""Gradients with respect to the flattened parameters.
...@@ -88,20 +93,32 @@ class Circuit(Sequential): ...@@ -88,20 +93,32 @@ class Circuit(Sequential):
' otherwise check where the gradient chain is broken' ' otherwise check where the gradient chain is broken'
grad_list.append(paddle.flatten(param.grad)) grad_list.append(paddle.flatten(param.grad))
return paddle.concat(grad_list).numpy() return paddle.concat(grad_list).numpy()
def update_param(self, theta: Union[paddle.Tensor, np.ndarray, float], idx: Optional[int] = None) -> None: @property
def depth(self) -> int:
r"""(current) Depth of this Circuit
"""
qubit_depth = [len(qubit_gates) for qubit_gates in self.qubit_history]
return max(qubit_depth)
def update_param(self, theta: Union[paddle.Tensor, np.ndarray, float], idx: int = None) -> None:
r"""Replace parameters of all/one layer(s) by ``theta``. r"""Replace parameters of all/one layer(s) by ``theta``.
Args: Args:
theta: New parameters theta: New parameters
idx: Index of replacement. Defaults to None, refering to all layers. idx: Index of replacement. Defaults to None, referring to all layers.
""" """
if not isinstance(theta, paddle.Tensor): if not isinstance(theta, paddle.Tensor):
theta = paddle.to_tensor(theta, dtype='float32') theta = paddle.to_tensor(theta, dtype='float32')
theta = paddle.flatten(theta) theta = paddle.flatten(theta)
backend_dtype = _get_float_dtype(get_dtype())
if backend_dtype != 'float32':
warnings.warn(
f"\ndtype of parameters will be float32 instead of {backend_dtype}", UserWarning)
if idx is None: if idx is None:
assert self.param.shape == theta.shape, "the shapen of input paramters is not correct" assert self.param.shape == theta.shape, "the shape of input parameters is not correct"
for layer in self.sublayers(): for layer in self.sublayers():
for name, _ in layer.named_parameters(): for name, _ in layer.named_parameters():
param = getattr(layer, name) param = getattr(layer, name)
...@@ -109,9 +126,8 @@ class Circuit(Sequential): ...@@ -109,9 +126,8 @@ class Circuit(Sequential):
param = paddle.create_parameter( param = paddle.create_parameter(
shape=param.shape, shape=param.shape,
dtype=param.dtype, dtype='float32',
default_initializer=paddle.nn.initializer.Assign( default_initializer=paddle.nn.initializer.Assign(theta[:num_param].reshape(param.shape)),
theta[:num_param].reshape(param.shape)),
) )
setattr(layer, 'theta', param) setattr(layer, 'theta', param)
...@@ -119,21 +135,19 @@ class Circuit(Sequential): ...@@ -119,21 +135,19 @@ class Circuit(Sequential):
return return
theta = theta[num_param:] theta = theta[num_param:]
elif isinstance(idx, int): elif isinstance(idx, int):
assert idx < len(self.sublayers( assert idx < len(self.sublayers()), "the index is out of range, expect below " + str(len(self.sublayers()))
)), "the index is out of range, expect below " + str(len(self.sublayers()))
layer = self.sublayers()[idx] layer = self.sublayers()[idx]
assert theta.shape == paddle.concat([paddle.flatten(param) for param in layer.parameters()]).shape, \ assert theta.shape == paddle.concat([paddle.flatten(param) for param in layer.parameters()]).shape, \
"the shape of input paramters is not correct," "the shape of input parameters is not correct,"
for name, _ in layer.named_parameters(): for name, _ in layer.named_parameters():
param = getattr(layer, name) param = getattr(layer, name)
num_param = int(paddle.numel(param)) num_param = int(paddle.numel(param))
param = paddle.create_parameter( param = paddle.create_parameter(
shape=param.shape, shape=param.shape,
dtype=param.dtype, dtype='float32',
default_initializer=paddle.nn.initializer.Assign( default_initializer=paddle.nn.initializer.Assign(theta[:num_param].reshape(param.shape)),
theta[:num_param].reshape(param.shape)),
) )
setattr(layer, 'theta', param) setattr(layer, 'theta', param)
...@@ -142,10 +156,20 @@ class Circuit(Sequential): ...@@ -142,10 +156,20 @@ class Circuit(Sequential):
theta = theta[num_param:] theta = theta[num_param:]
else: else:
raise ValueError("idx must be an integer or None") raise ValueError("idx must be an integer or None")
def randomize_param(self, low: float = 0, high: Optional[float] = 2 * pi) -> None: def transfer_static(self) -> None:
r""" set ``stop_gradient`` of all parameters of the circuit as ``True``
"""
for layer in self.sublayers():
for name, _ in layer.named_parameters():
param = getattr(layer, name)
param.stop_gradient = True
setattr(layer, 'theta', param)
def randomize_param(self, low: float = 0, high: Optional[float] = 2 * pi) -> None:
r"""Randomize parameters of the circuit in a range from low to high. r"""Randomize parameters of the circuit in a range from low to high.
Args: Args:
low: Lower bound. low: Lower bound.
high: Upper bound. high: Upper bound.
...@@ -157,14 +181,13 @@ class Circuit(Sequential): ...@@ -157,14 +181,13 @@ class Circuit(Sequential):
param = paddle.create_parameter( param = paddle.create_parameter(
shape=param.shape, shape=param.shape,
dtype=param.dtype, dtype=param.dtype,
default_initializer=paddle.nn.initializer.Uniform( default_initializer=paddle.nn.initializer.Uniform(low=low, high=high),
low=low, high=high),
) )
setattr(layer, 'theta', param) setattr(layer, 'theta', param)
def __num_qubits_update(self, qubits_idx: Union[Iterable[int], int, str]) -> None: def __num_qubits_update(self, qubits_idx: Union[Iterable[int], int, str]) -> None:
r"""Update ``self.num_qubits`` according to ``qubits_idx``, or report error. r"""Update ``self.num_qubits`` according to ``qubits_idx``, or report error.
Args: Args:
qubits_idx: Input qubit indices of a quantum gate. qubits_idx: Input qubit indices of a quantum gate.
""" """
...@@ -183,8 +206,7 @@ class Circuit(Sequential): ...@@ -183,8 +206,7 @@ class Circuit(Sequential):
return return
assert max_idx + 1 <= num_qubits or self.__isdynamic, \ assert max_idx + 1 <= num_qubits or self.__isdynamic, \
"The circuit is not a dynamic quantum circuit. Invalid input qubit idx: " + \ f"The circuit is not a dynamic quantum circuit. Invalid input qubit idx: {max_idx} num_qubit: {self.__num_qubits}"
str(max_idx) + " num_qubit: " + str(self.__num_qubits)
self.__num_qubits = int(max(max_idx + 1, num_qubits)) self.__num_qubits = int(max(max_idx + 1, num_qubits))
def h( def h(
...@@ -208,8 +230,7 @@ class Circuit(Sequential): ...@@ -208,8 +230,7 @@ class Circuit(Sequential):
depth: Number of layers. Defaults to 1. depth: Number of layers. Defaults to 1.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append( self.append(H(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth))
H(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth))
def s( def s(
self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1 self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1
...@@ -266,10 +287,10 @@ class Circuit(Sequential): ...@@ -266,10 +287,10 @@ class Circuit(Sequential):
The matrix form of such a gate is: The matrix form of such a gate is:
.. math:: .. math::
X = \begin{bmatrix} X = \begin{bmatrix}
0 & 1 \\ 0 & 1 \\
1 & 0 1 & 0
\end{bmatrix} \end{bmatrix}
Args: Args:
qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'full'. qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'full'.
...@@ -290,9 +311,9 @@ class Circuit(Sequential): ...@@ -290,9 +311,9 @@ class Circuit(Sequential):
.. math:: .. math::
Y = \begin{bmatrix} Y = \begin{bmatrix}
0 & -i \\ 0 & -i \\
i & 0 i & 0
\end{bmatrix} \end{bmatrix}
Args: Args:
qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'full'. qubits_idx: Indices of the qubits on which the gates are applied. Defaults to 'full'.
...@@ -349,8 +370,8 @@ class Circuit(Sequential): ...@@ -349,8 +370,8 @@ class Circuit(Sequential):
param_sharing: Whether gates in the same layer share a parameter. Defaults to False. param_sharing: Whether gates in the same layer share a parameter. Defaults to False.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(P(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, self.append(
depth, param, param_sharing)) P(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing))
def rx( def rx(
self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1, self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1,
...@@ -375,8 +396,8 @@ class Circuit(Sequential): ...@@ -375,8 +396,8 @@ class Circuit(Sequential):
param_sharing: Whether gates in the same layer share a parameter. Defaults to False. param_sharing: Whether gates in the same layer share a parameter. Defaults to False.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(RX(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, self.append(RX(qubits_idx, self.num_qubits if num_qubits is None else num_qubits,
depth, param, param_sharing)) depth, param, param_sharing))
def ry( def ry(
self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1, self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1,
...@@ -401,8 +422,8 @@ class Circuit(Sequential): ...@@ -401,8 +422,8 @@ class Circuit(Sequential):
param_sharing: Whether gates in the same layer share a parameter. Defaults to False. param_sharing: Whether gates in the same layer share a parameter. Defaults to False.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(RY(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, self.append(RY(qubits_idx, self.num_qubits if num_qubits is None else num_qubits,
depth, param, param_sharing)) depth, param, param_sharing))
def rz( def rz(
self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1, self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1,
...@@ -427,8 +448,8 @@ class Circuit(Sequential): ...@@ -427,8 +448,8 @@ class Circuit(Sequential):
param_sharing: Whether gates in the same layer share a parameter. Defaults to False. param_sharing: Whether gates in the same layer share a parameter. Defaults to False.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(RZ(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, self.append(RZ(qubits_idx, self.num_qubits if num_qubits is None else num_qubits,
depth, param, param_sharing)) depth, param, param_sharing))
def u3( def u3(
self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None, depth: int = 1, self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None, depth: int = 1,
...@@ -456,8 +477,8 @@ class Circuit(Sequential): ...@@ -456,8 +477,8 @@ class Circuit(Sequential):
param_sharing: Whether gates in the same layer share a parameter. Defaults to False. param_sharing: Whether gates in the same layer share a parameter. Defaults to False.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(U3(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, self.append(U3(qubits_idx, self.num_qubits if num_qubits is None else num_qubits,
depth, param, param_sharing)) depth, param, param_sharing))
def cnot( def cnot(
self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1 self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1
...@@ -616,8 +637,8 @@ class Circuit(Sequential): ...@@ -616,8 +637,8 @@ class Circuit(Sequential):
param_sharing: Whether gates in the same layer share a parameter. Defaults to False. param_sharing: Whether gates in the same layer share a parameter. Defaults to False.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(CP(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, self.append(CP(qubits_idx, self.num_qubits if num_qubits is None else num_qubits,
depth, param, param_sharing)) depth, param, param_sharing))
def crx( def crx(
self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1, self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1,
...@@ -648,8 +669,8 @@ class Circuit(Sequential): ...@@ -648,8 +669,8 @@ class Circuit(Sequential):
param_sharing: Whether gates in the same layer share a parameter. Defaults to False. param_sharing: Whether gates in the same layer share a parameter. Defaults to False.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(CRX( self.append(CRX(qubits_idx, self.num_qubits if num_qubits is None else num_qubits,
qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) depth, param, param_sharing))
def cry( def cry(
self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1, self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1,
...@@ -680,8 +701,8 @@ class Circuit(Sequential): ...@@ -680,8 +701,8 @@ class Circuit(Sequential):
param_sharing: Whether gates in the same layer share a parameter. Defaults to False. param_sharing: Whether gates in the same layer share a parameter. Defaults to False.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(CRY( self.append(CRY(qubits_idx, self.num_qubits if num_qubits is None else num_qubits,
qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) depth, param, param_sharing))
def crz( def crz(
self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1, self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1,
...@@ -712,8 +733,8 @@ class Circuit(Sequential): ...@@ -712,8 +733,8 @@ class Circuit(Sequential):
param_sharing: Whether gates in the same layer share a parameter. Defaults to False. param_sharing: Whether gates in the same layer share a parameter. Defaults to False.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(CRZ( self.append(CRZ(qubits_idx, self.num_qubits if num_qubits is None else num_qubits,
qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) depth, param, param_sharing))
def cu( def cu(
self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1, self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1,
...@@ -744,8 +765,8 @@ class Circuit(Sequential): ...@@ -744,8 +765,8 @@ class Circuit(Sequential):
param_sharing: Whether gates in the same layer share a parameter. Defaults to False. param_sharing: Whether gates in the same layer share a parameter. Defaults to False.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(CU(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, self.append(CU(qubits_idx, self.num_qubits if num_qubits is None else num_qubits,
depth, param, param_sharing)) depth, param, param_sharing))
def rxx( def rxx(
self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1, self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1,
...@@ -775,8 +796,8 @@ class Circuit(Sequential): ...@@ -775,8 +796,8 @@ class Circuit(Sequential):
param_sharing: Whether gates in the same layer share a parameter. Defaults to False. param_sharing: Whether gates in the same layer share a parameter. Defaults to False.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(RXX( self.append(RXX(qubits_idx, self.num_qubits if num_qubits is None else num_qubits,
qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) depth, param, param_sharing))
def ryy( def ryy(
self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1, self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1,
...@@ -806,8 +827,8 @@ class Circuit(Sequential): ...@@ -806,8 +827,8 @@ class Circuit(Sequential):
param_sharing: Whether gates in the same layer share a parameter. Defaults to False. param_sharing: Whether gates in the same layer share a parameter. Defaults to False.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(RYY( self.append(RYY(qubits_idx, self.num_qubits if num_qubits is None else num_qubits,
qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) depth, param, param_sharing))
def rzz( def rzz(
self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1, self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1,
...@@ -837,8 +858,8 @@ class Circuit(Sequential): ...@@ -837,8 +858,8 @@ class Circuit(Sequential):
param_sharing: Whether gates in the same layer share a parameter. Defaults to False. param_sharing: Whether gates in the same layer share a parameter. Defaults to False.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(RZZ( self.append(RZZ(qubits_idx, self.num_qubits if num_qubits is None else num_qubits,
qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) depth, param, param_sharing))
def ms( def ms(
self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1 self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1
...@@ -928,8 +949,8 @@ class Circuit(Sequential): ...@@ -928,8 +949,8 @@ class Circuit(Sequential):
depth: Number of layers. Defaults to 1. depth: Number of layers. Defaults to 1.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(Toffoli( self.append(
qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) Toffoli(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth))
def universal_two_qubits( def universal_two_qubits(
self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1, self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1,
...@@ -945,8 +966,8 @@ class Circuit(Sequential): ...@@ -945,8 +966,8 @@ class Circuit(Sequential):
param_sharing: Whether gates in the same layer share a parameter. Defaults to False. param_sharing: Whether gates in the same layer share a parameter. Defaults to False.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(UniversalTwoQubits( self.append(UniversalTwoQubits(qubits_idx, self.num_qubits if num_qubits is None else num_qubits,
qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) depth, param, param_sharing))
def universal_three_qubits( def universal_three_qubits(
self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1, self, qubits_idx: Union[Iterable[int], str] = 'cycle', num_qubits: int = None, depth: int = 1,
...@@ -965,12 +986,12 @@ class Circuit(Sequential): ...@@ -965,12 +986,12 @@ class Circuit(Sequential):
ValueError: The ``param`` must be paddle.Tensor or float. ValueError: The ``param`` must be paddle.Tensor or float.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(UniversalThreeQubits( self.append(UniversalThreeQubits(qubits_idx, self.num_qubits if num_qubits is None else num_qubits,
qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth, param, param_sharing)) depth, param, param_sharing))
def oracle( def oracle(
self, oracle: paddle.tensor, qubits_idx: Union[Iterable[Iterable[int]], Iterable[int], int], self, oracle: paddle.tensor, qubits_idx: Union[Iterable[Iterable[int]], Iterable[int], int],
num_qubits: int = None, depth: int = 1 num_qubits: int = None, depth: int = 1, gate_name: str = 'O'
) -> None: ) -> None:
"""Add an oracle gate. """Add an oracle gate.
...@@ -979,16 +1000,15 @@ class Circuit(Sequential): ...@@ -979,16 +1000,15 @@ class Circuit(Sequential):
qubits_idx: Indices of the qubits on which the gates are applied. qubits_idx: Indices of the qubits on which the gates are applied.
num_qubits: Total number of qubits. Defaults to None. num_qubits: Total number of qubits. Defaults to None.
depth: Number of layers. Defaults to 1. depth: Number of layers. Defaults to 1.
gate_name: name of this oracle
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(Oracle(oracle, qubits_idx, self.append(Oracle(oracle, qubits_idx,
self.num_qubits if num_qubits is None else num_qubits, depth)) self.num_qubits if num_qubits is None else num_qubits, depth, gate_name))
def control_oracle( def control_oracle(
self, oracle: paddle.Tensor, self, oracle: paddle.Tensor, qubits_idx: Union[Iterable[Iterable[int]], Iterable[int]],
# num_control_qubits: int, controlled_value: 'str', num_qubits: int = None, depth: int = 1, gate_name: str = 'cO'
qubits_idx: Union[Iterable[Iterable[int]], Iterable[int]],
num_qubits: int = None, depth: int = 1
) -> None: ) -> None:
"""Add a controlled oracle gate. """Add a controlled oracle gate.
...@@ -997,10 +1017,33 @@ class Circuit(Sequential): ...@@ -997,10 +1017,33 @@ class Circuit(Sequential):
qubits_idx: Indices of the qubits on which the gates are applied. qubits_idx: Indices of the qubits on which the gates are applied.
num_qubits: Total number of qubits. Defaults to None. num_qubits: Total number of qubits. Defaults to None.
depth: Number of layers. Defaults to 1. depth: Number of layers. Defaults to 1.
gate_name: name of this oracle
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(ControlOracle(oracle, qubits_idx, self.append(ControlOracle(oracle, qubits_idx,
self.num_qubits if num_qubits is None else num_qubits, depth)) self.num_qubits if num_qubits is None else num_qubits, depth, gate_name))
def collapse(self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None,
desired_result: Union[int, str] = None, if_print: bool = False,
measure_basis: Union[Iterable[paddle.Tensor], str] = 'z') -> None:
r"""
Args:
qubits_idx: list of qubits to be collapsed. Defaults to ``'full'``.
num_qubits: Total number of qubits. Defaults to ``None``.
desired_result: The desired result you want to collapse. Defaults to ``None`` meaning randomly choose one.
if_print: whether print the information about the collapsed state. Defaults to ``False``.
measure_basis: The basis of the measurement. The quantum state will collapse to the corresponding eigenstate.
Raises:
NotImplementedError: If the basis of measurement is not z. Other bases will be implemented in future.
TypeError: cannot get probability of state when the backend is unitary_matrix.
Note:
When desired_result is `None`, Collapse does not support gradient calculation
"""
self.__num_qubits_update(qubits_idx)
self.append(Collapse(qubits_idx, self.num_qubits if num_qubits is None else num_qubits,
desired_result, if_print, measure_basis))
def superposition_layer( def superposition_layer(
self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1 self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1
...@@ -1013,9 +1056,9 @@ class Circuit(Sequential): ...@@ -1013,9 +1056,9 @@ class Circuit(Sequential):
depth: Number of layers. Defaults to 1. depth: Number of layers. Defaults to 1.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(SuperpositionLayer( self.append(
qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) SuperpositionLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth))
def weak_superposition_layer( def weak_superposition_layer(
self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1 self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1
) -> None: ) -> None:
...@@ -1027,9 +1070,9 @@ class Circuit(Sequential): ...@@ -1027,9 +1070,9 @@ class Circuit(Sequential):
depth: Number of layers. Defaults to 1. depth: Number of layers. Defaults to 1.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(WeakSuperpositionLayer( self.append(
qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) WeakSuperpositionLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth))
def linear_entangled_layer( def linear_entangled_layer(
self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1 self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1
) -> None: ) -> None:
...@@ -1041,8 +1084,8 @@ class Circuit(Sequential): ...@@ -1041,8 +1084,8 @@ class Circuit(Sequential):
depth: Number of layers. Defaults to 1. depth: Number of layers. Defaults to 1.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(LinearEntangledLayer( self.append(
qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) LinearEntangledLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth))
def real_entangled_layer( def real_entangled_layer(
self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1 self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1
...@@ -1055,8 +1098,8 @@ class Circuit(Sequential): ...@@ -1055,8 +1098,8 @@ class Circuit(Sequential):
depth: Number of layers. Defaults to 1. depth: Number of layers. Defaults to 1.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(RealEntangledLayer( self.append(
qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) RealEntangledLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth))
def complex_entangled_layer( def complex_entangled_layer(
self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1 self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1
...@@ -1069,8 +1112,8 @@ class Circuit(Sequential): ...@@ -1069,8 +1112,8 @@ class Circuit(Sequential):
depth: Number of layers. Defaults to 1. depth: Number of layers. Defaults to 1.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(ComplexEntangledLayer( self.append(
qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) ComplexEntangledLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth))
def real_block_layer( def real_block_layer(
self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1 self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1
...@@ -1083,9 +1126,9 @@ class Circuit(Sequential): ...@@ -1083,9 +1126,9 @@ class Circuit(Sequential):
depth: Number of layers. Defaults to 1. depth: Number of layers. Defaults to 1.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(RealBlockLayer( self.append(
qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) RealBlockLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth))
def complex_block_layer( def complex_block_layer(
self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1 self, qubits_idx: Iterable[int] = 'full', num_qubits: int = None, depth: int = 1
) -> None: ) -> None:
...@@ -1097,8 +1140,8 @@ class Circuit(Sequential): ...@@ -1097,8 +1140,8 @@ class Circuit(Sequential):
depth: Number of layers. Defaults to 1. depth: Number of layers. Defaults to 1.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(ComplexBlockLayer( self.append(
qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth)) ComplexBlockLayer(qubits_idx, self.num_qubits if num_qubits is None else num_qubits, depth))
def bit_flip( def bit_flip(
self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
...@@ -1111,9 +1154,9 @@ class Circuit(Sequential): ...@@ -1111,9 +1154,9 @@ class Circuit(Sequential):
num_qubits: Total number of qubits. Defaults to None. num_qubits: Total number of qubits. Defaults to None.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(BitFlip(prob, qubits_idx, self.append(BitFlip(prob, qubits_idx,
self.num_qubits if num_qubits is None else num_qubits)) self.num_qubits if num_qubits is None else num_qubits))
def phase_flip( def phase_flip(
self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
) -> None: ) -> None:
...@@ -1125,9 +1168,9 @@ class Circuit(Sequential): ...@@ -1125,9 +1168,9 @@ class Circuit(Sequential):
num_qubits: Total number of qubits. Defaults to None. num_qubits: Total number of qubits. Defaults to None.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(PhaseFlip(prob, qubits_idx, self.append(PhaseFlip(prob, qubits_idx,
self.num_qubits if num_qubits is None else num_qubits)) self.num_qubits if num_qubits is None else num_qubits))
def bit_phase_flip( def bit_phase_flip(
self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
) -> None: ) -> None:
...@@ -1139,9 +1182,9 @@ class Circuit(Sequential): ...@@ -1139,9 +1182,9 @@ class Circuit(Sequential):
num_qubits: Total number of qubits. Defaults to None. num_qubits: Total number of qubits. Defaults to None.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(BitPhaseFlip(prob, qubits_idx, self.append(BitPhaseFlip(prob, qubits_idx,
self.num_qubits if num_qubits is None else num_qubits)) self.num_qubits if num_qubits is None else num_qubits))
def amplitude_damping( def amplitude_damping(
self, gamma: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None self, gamma: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
) -> None: ) -> None:
...@@ -1153,24 +1196,24 @@ class Circuit(Sequential): ...@@ -1153,24 +1196,24 @@ class Circuit(Sequential):
num_qubits: Total number of qubits. Defaults to None. num_qubits: Total number of qubits. Defaults to None.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(AmplitudeDamping(gamma, qubits_idx, self.append(AmplitudeDamping(gamma, qubits_idx,
self.num_qubits if num_qubits is None else num_qubits)) self.num_qubits if num_qubits is None else num_qubits))
#TODO: change bug
def generalized_amplitude_damping( def generalized_amplitude_damping(
self, gamma: Union[paddle.Tensor, float], prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None self, gamma: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
) -> None: ) -> None:
r"""Add generalized amplitude damping channels. r"""Add generalized amplitude damping channels.
Args: Args:
gamma: Damping probability. gamma: Damping probability.
prob: Excitation probability.
qubits_idx: Indices of the qubits on which the channels are applied. Defaults to 'full'. qubits_idx: Indices of the qubits on which the channels are applied. Defaults to 'full'.
num_qubits: Total number of qubits. Defaults to None. num_qubits: Total number of qubits. Defaults to None.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(GeneralizedAmplitudeDamping(gamma, prob, qubits_idx, self.append(GeneralizedAmplitudeDamping(gamma, qubits_idx,
self.num_qubits if num_qubits is None else num_qubits)) self.num_qubits if num_qubits is None else num_qubits))
def phase_damping( def phase_damping(
self, gamma: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None self, gamma: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
) -> None: ) -> None:
...@@ -1182,8 +1225,8 @@ class Circuit(Sequential): ...@@ -1182,8 +1225,8 @@ class Circuit(Sequential):
num_qubits: Total number of qubits. Defaults to None. num_qubits: Total number of qubits. Defaults to None.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(PhaseDamping(gamma, qubits_idx, self.append(PhaseDamping(gamma, qubits_idx,
self.num_qubits if num_qubits is None else num_qubits)) self.num_qubits if num_qubits is None else num_qubits))
def depolarizing( def depolarizing(
self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
...@@ -1196,8 +1239,8 @@ class Circuit(Sequential): ...@@ -1196,8 +1239,8 @@ class Circuit(Sequential):
num_qubits: Total number of qubits. Defaults to None. num_qubits: Total number of qubits. Defaults to None.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(Depolarizing(prob, qubits_idx, self.append(Depolarizing(prob, qubits_idx,
self.num_qubits if num_qubits is None else num_qubits)) self.num_qubits if num_qubits is None else num_qubits))
def pauli_channel( def pauli_channel(
self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
...@@ -1210,9 +1253,9 @@ class Circuit(Sequential): ...@@ -1210,9 +1253,9 @@ class Circuit(Sequential):
num_qubits: Total number of qubits. Defaults to None. num_qubits: Total number of qubits. Defaults to None.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(PauliChannel(prob, qubits_idx, self.append(PauliChannel(prob, qubits_idx,
self.num_qubits if num_qubits is None else num_qubits)) self.num_qubits if num_qubits is None else num_qubits))
def reset_channel( def reset_channel(
self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None self, prob: Union[paddle.Tensor, float], qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
) -> None: ) -> None:
...@@ -1224,11 +1267,11 @@ class Circuit(Sequential): ...@@ -1224,11 +1267,11 @@ class Circuit(Sequential):
num_qubits: Total number of qubits. Defaults to None. num_qubits: Total number of qubits. Defaults to None.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(ResetChannel(prob, qubits_idx, self.append(ResetChannel(prob, qubits_idx,
self.num_qubits if num_qubits is None else num_qubits)) self.num_qubits if num_qubits is None else num_qubits))
def thermal_relaxation( def thermal_relaxation(
self, const_t: Union[paddle.Tensor, Iterable[float]], exec_time: Union[paddle.Tensor, float], self, const_t: Union[paddle.Tensor, Iterable[float]], exec_time: Union[paddle.Tensor, float],
qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
) -> None: ) -> None:
r"""Add thermal relaxation channels. r"""Add thermal relaxation channels.
...@@ -1240,8 +1283,8 @@ class Circuit(Sequential): ...@@ -1240,8 +1283,8 @@ class Circuit(Sequential):
num_qubits: Total number of qubits. Defaults to None. num_qubits: Total number of qubits. Defaults to None.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(ThermalRelaxation(const_t, exec_time, qubits_idx, self.append(ThermalRelaxation(const_t, exec_time, qubits_idx,
self.num_qubits if num_qubits is None else num_qubits)) self.num_qubits if num_qubits is None else num_qubits))
def kraus_repr( def kraus_repr(
self, kraus_oper: Iterable[paddle.Tensor], self, kraus_oper: Iterable[paddle.Tensor],
...@@ -1256,16 +1299,16 @@ class Circuit(Sequential): ...@@ -1256,16 +1299,16 @@ class Circuit(Sequential):
num_qubits: Total number of qubits. Defaults to None. num_qubits: Total number of qubits. Defaults to None.
""" """
self.__num_qubits_update(qubits_idx) self.__num_qubits_update(qubits_idx)
self.append(KrausRepr(kraus_oper, qubits_idx, self.append(KrausRepr(kraus_oper, qubits_idx,
self.num_qubits if num_qubits is None else num_qubits)) self.num_qubits if num_qubits is None else num_qubits))
def qaoa_layer(self, edges: Iterable, nodes: Iterable, depth: Optional[int] = 1) -> None: def qaoa_layer(self, edges: Iterable, nodes: Iterable, depth: Optional[int] = 1) -> None:
# TODO: see qaoa layer in layer.py # TODO: see qaoa layer in layer.py
self.__num_qubits_update(edges) self.__num_qubits_update(edges)
self.__num_qubits_update(nodes) self.__num_qubits_update(nodes)
self.append(QAOALayer(edges, nodes, depth)) self.append(QAOALayer(edges, nodes, depth))
def unitary_matrix(self, num_qubits: Optional[int] = None) -> paddle.Tensor: def unitary_matrix(self, num_qubits: Optional[int] = None) -> paddle.Tensor:
r"""Get the unitary matrix form of the circuit. r"""Get the unitary matrix form of the circuit.
Args: Args:
...@@ -1278,11 +1321,11 @@ class Circuit(Sequential): ...@@ -1278,11 +1321,11 @@ class Circuit(Sequential):
num_qubits = self.__num_qubits num_qubits = self.__num_qubits
else: else:
assert num_qubits >= self.__num_qubits assert num_qubits >= self.__num_qubits
backend = get_backend() backend = get_backend()
self.to(backend=Backend.UnitaryMatrix) self.to(backend=Backend.UnitaryMatrix)
unitary = State(paddle.eye( unitary = State(paddle.eye(2 ** num_qubits).cast(get_dtype()),
2 ** num_qubits).cast(get_dtype()), backend=Backend.UnitaryMatrix) backend=Backend.UnitaryMatrix)
for layer in self._sub_layers.values(): for layer in self._sub_layers.values():
unitary = layer(unitary) unitary = layer(unitary)
self.to(backend=backend) self.to(backend=backend)
...@@ -1294,188 +1337,15 @@ class Circuit(Sequential): ...@@ -1294,188 +1337,15 @@ class Circuit(Sequential):
Returns: Returns:
history of quantum gates of circuit history of quantum gates of circuit
""" """
def get_gate_name(gate: Gate) -> str:
r""" return the corresponding string of gate
Returns:
gate name
"""
if isinstance(gate, H):
return 'h'
if isinstance(gate, S):
return 's'
if isinstance(gate, T):
return 't'
if isinstance(gate, X):
return 'x'
if isinstance(gate, Y):
return 'y'
if isinstance(gate, Z):
return 'z'
if isinstance(gate, U3):
return 'u'
if isinstance(gate, P):
return 'p'
if isinstance(gate, RX):
return 'rx'
if isinstance(gate, RY):
return 'ry'
if isinstance(gate, RZ):
return 'rz'
if isinstance(gate, CNOT):
return 'cnot'
if isinstance(gate, SWAP):
return 'swap'
if isinstance(gate, CX):
return 'cnot'
if isinstance(gate, CY):
return 'cy'
if isinstance(gate, CZ):
return 'cz'
if isinstance(gate, RXX):
return 'rxx'
if isinstance(gate, RYY):
return 'ryy'
if isinstance(gate, RZZ):
return 'rzz'
if isinstance(gate, CP):
return 'cp'
if isinstance(gate, CRX):
return 'crx'
if isinstance(gate, CRY):
return 'cry'
if isinstance(gate, CRZ):
return 'crz'
if isinstance(gate, CU):
return 'cu'
if isinstance(gate, MS):
return 'ms'
if isinstance(gate, CSWAP):
return 'cswap'
if isinstance(gate, Toffoli):
return 'ccx'
raise ValueError("The gate is not a simple quantum gate.")
not_implemented_gate = [
AmplitudeEncoding,
Oracle,
ControlOracle,
UniversalTwoQubits,
UniversalThreeQubits,
RealBlockLayer,
ComplexBlockLayer,
]
single_qubit_gate = {'h', 's', 't', 'x',
'y', 'z', 'p', 'rx', 'ry', 'rz', 'u'}
gate_history = [] gate_history = []
qubit_max_idx = 0
for gate in self.sublayers(): for gate in self.sublayers():
if any((isinstance(gate, gate_class) for gate_class in not_implemented_gate)): if gate.gate_name is None:
raise NotImplementedError( raise NotImplementedError(f"Gate {type(gate)} has no gate name and hence cannot be recorded into history.")
f"Not support to print the {type(gate)}.")
if isinstance(gate, SuperpositionLayer):
for depth_idx in range(0, gate.depth):
for qubit_idx in gate.qubits_idx:
gate_info = {
'gate': 'h', 'which_qubits': [qubit_idx], 'theta': None
}
gate_history.append(gate_info)
elif isinstance(gate, WeakSuperpositionLayer):
for depth_idx in range(0, gate.depth):
for qubit_idx in gate.qubits_idx:
gate_info = {
'gate': 'ry', 'which_qubits': [qubit_idx],
'theta': paddle.to_tensor([pi / 4])
}
gate_history.append(gate_info)
elif isinstance(gate, LinearEntangledLayer):
for depth_idx in range(0, gate.depth):
for idx, qubit_idx in enumerate(gate.qubits_idx):
gate_info = {
'gate': 'ry', 'which_qubits': [qubit_idx],
'theta': gate.parameters()[0][depth_idx][idx][0]
}
gate_history.append(gate_info)
for idx in range(0, len(gate.qubits_idx) - 1):
gate_info = {
'gate': 'cnot', 'which_qubits': [gate.qubits_idx[idx], gate.qubits_idx[idx + 1]],
'theta': None
}
gate_history.append(gate_info)
for idx, qubit_idx in enumerate(gate.qubits_idx):
gate_info = {
'gate': 'rz', 'which_qubits': [qubit_idx],
'theta': gate.parameters()[0][depth_idx][idx][1]
}
gate_history.append(gate_info)
for idx in range(0, len(gate.qubits_idx) - 1):
gate_info = {
'gate': 'cnot', 'which_qubits': [gate.qubits_idx[idx], gate.qubits_idx[idx + 1]],
'theta': None
}
gate_history.append(gate_info)
elif isinstance(gate, RealEntangledLayer):
for depth_idx in range(0, gate.depth):
for idx, qubit_idx in enumerate(gate.qubits_idx):
gate_info = {
'gate': 'ry', 'which_qubits': [qubit_idx],
'theta': gate.parameters()[0][depth_idx][idx]
}
gate_history.append(gate_info)
for idx in range(0, len(gate.qubits_idx)):
gate_info = {
'gate': 'cnot',
'which_qubits': [
gate.qubits_idx[idx],
gate.qubits_idx[(idx + 1) %
len(gate.qubits_idx)]
],
'theta': None
}
gate_history.append(gate_info)
elif isinstance(gate, ComplexEntangledLayer):
for depth_idx in range(0, gate.depth):
for idx, qubit_idx in enumerate(gate.qubits_idx):
gate_info = {
'gate': 'u', 'which_qubits': [qubit_idx], 'theta': None
}
gate_history.append(gate_info)
for idx in range(0, len(gate.qubits_idx)):
gate_info = {
'gate': 'cnot',
'which_qubits': [
gate.qubits_idx[idx],
gate.qubits_idx[(idx + 1) %
len(gate.qubits_idx)]
],
'theta': None
}
gate_history.append(gate_info)
else: else:
gate_name = get_gate_name(gate) gate.gate_history_generation()
if gate_name in single_qubit_gate: gate_history.extend(gate.gate_history)
for depth_idx in range(0, gate.depth):
for idx, qubit_idx in enumerate(gate.qubits_idx):
gate_info = {'gate': gate_name,
'which_qubits': [qubit_idx]}
param = None
if gate_name in {'rx', 'ry', 'rz'}:
param = gate.theta[depth_idx][idx]
gate_info['theta'] = param
gate_history.append(gate_info)
else:
for depth_idx in range(0, gate.depth):
for idx, qubit_idx in enumerate(gate.qubits_idx):
gate_info = {'gate': gate_name,
'which_qubits': qubit_idx}
param = None
if gate_name in {'cp', 'crx', 'cry', 'crz', 'rxx', 'ryy', 'rzz'}:
param = gate.parameters()[0][depth_idx][idx]
gate_info['theta'] = param
gate_history.append(gate_info)
return gate_history return gate_history
def __count_history(self, history): def __count_history(self, history):
...@@ -1492,7 +1362,7 @@ class Circuit(Sequential): ...@@ -1492,7 +1362,7 @@ class Circuit(Sequential):
for current_gate in history: for current_gate in history:
# Single-qubit gates with no params to print # Single-qubit gates with no params to print
if current_gate['gate'] in {'h', 's', 't', 'x', 'y', 'z', 'u', 'sdg', 'tdg'}: if current_gate['gate'] in {'h', 's', 't', 'x', 'y', 'z', 'u', 'sdg', 'tdg'}:
curr_qubit = current_gate['which_qubits'][0] curr_qubit = current_gate['which_qubits']
gate.append(qubit[curr_qubit]) gate.append(qubit[curr_qubit])
qubit[curr_qubit] = qubit[curr_qubit] + 1 qubit[curr_qubit] = qubit[curr_qubit] + 1
# A new section is added # A new section is added
...@@ -1501,7 +1371,7 @@ class Circuit(Sequential): ...@@ -1501,7 +1371,7 @@ class Circuit(Sequential):
qubit_max = qubit[curr_qubit] qubit_max = qubit[curr_qubit]
# Gates with params to print # Gates with params to print
elif current_gate['gate'] in {'p', 'rx', 'ry', 'rz'}: elif current_gate['gate'] in {'p', 'rx', 'ry', 'rz'}:
curr_qubit = current_gate['which_qubits'][0] curr_qubit = current_gate['which_qubits']
gate.append(qubit[curr_qubit]) gate.append(qubit[curr_qubit])
if length[qubit[curr_qubit]] == 5: if length[qubit[curr_qubit]] == 5:
length[qubit[curr_qubit]] = 13 length[qubit[curr_qubit]] = 13
...@@ -1512,8 +1382,8 @@ class Circuit(Sequential): ...@@ -1512,8 +1382,8 @@ class Circuit(Sequential):
# Two-qubit gates or Three-qubit gates # Two-qubit gates or Three-qubit gates
elif ( elif (
current_gate['gate'] in { current_gate['gate'] in {
'cnot', 'swap', 'rxx', 'ryy', 'rzz', 'ms', 'cnot', 'swap', 'rxx', 'ryy', 'rzz', 'ms',
'cy', 'cz', 'cu', 'cp', 'crx', 'cry', 'crz'} or 'cy', 'cz', 'cu', 'cp', 'crx', 'cry', 'crz'} or
current_gate['gate'] in {'cswap', 'ccx'} current_gate['gate'] in {'cswap', 'ccx'}
): ):
a = max(current_gate['which_qubits']) a = max(current_gate['which_qubits'])
...@@ -1530,14 +1400,14 @@ class Circuit(Sequential): ...@@ -1530,14 +1400,14 @@ class Circuit(Sequential):
qubit_max = ind + 1 qubit_max = ind + 1
return length, gate return length, gate
@property @property
def qubit_history(self) -> List[List[Tuple[Dict[str, Union[str, List[int], paddle.Tensor]], int]]]: def qubit_history(self) -> List[List[Tuple[Dict[str, Union[str, List[int], paddle.Tensor]], int]]]:
r""" gate information on each qubit r""" gate information on each qubit
Returns: Returns:
list of gate history on each qubit list of gate history on each qubit
Note: Note:
The entry ``qubit_history[i][j][0/1]`` returns the gate information / gate index of the j-th gate The entry ``qubit_history[i][j][0/1]`` returns the gate information / gate index of the j-th gate
applied on the i-th qubit. applied on the i-th qubit.
...@@ -1548,8 +1418,11 @@ class Circuit(Sequential): ...@@ -1548,8 +1418,11 @@ class Circuit(Sequential):
history_qubit.append(history_i) history_qubit.append(history_i)
for idx, i in enumerate(self.gate_history): for idx, i in enumerate(self.gate_history):
qubits = i["which_qubits"] qubits = i["which_qubits"]
for j in qubits: if not isinstance(qubits, Iterable):
history_qubit[j].append([i, idx]) history_qubit[qubits].append([i, idx])
else:
for j in qubits:
history_qubit[j].append([i, idx])
return history_qubit return history_qubit
def __str__(self) -> str: def __str__(self) -> str:
...@@ -1559,7 +1432,7 @@ class Circuit(Sequential): ...@@ -1559,7 +1432,7 @@ class Circuit(Sequential):
# Ignore the unused section # Ignore the unused section
total_length = sum(length) - 5 total_length = sum(length) - 5
print_list = [['-' if i % 2 == 0 else ' '] * print_list = [['-' if i % 2 == 0 else ' '] *
total_length for i in range(num_qubits * 2)] total_length for i in range(num_qubits * 2)]
for i, current_gate in enumerate(history): for i, current_gate in enumerate(history):
...@@ -1567,22 +1440,21 @@ class Circuit(Sequential): ...@@ -1567,22 +1440,21 @@ class Circuit(Sequential):
# Calculate starting position ind of current gate # Calculate starting position ind of current gate
sec = gate[i] sec = gate[i]
ind = sum(length[:sec]) ind = sum(length[:sec])
print_list[current_gate['which_qubits'][0] * 2][ind + print_list[current_gate['which_qubits'] * 2][ind + length[sec] // 2] = current_gate['gate'].upper()
length[sec] // 2] = current_gate['gate'].upper()
elif current_gate['gate'] in {'sdg'}: elif current_gate['gate'] in {'sdg'}:
sec = gate[i] sec = gate[i]
ind = sum(length[:sec]) ind = sum(length[:sec])
print_list[current_gate['which_qubits'][0] * 2][ print_list[current_gate['which_qubits'] * 2][
ind + length[sec] // 2 - 1: ind + length[sec] // 2 + 2] = current_gate['gate'].upper() ind + length[sec] // 2 - 1: ind + length[sec] // 2 + 2] = current_gate['gate'].upper()
elif current_gate['gate'] in {'tdg'}: elif current_gate['gate'] in {'tdg'}:
sec = gate[i] sec = gate[i]
ind = sum(length[:sec]) ind = sum(length[:sec])
print_list[current_gate['which_qubits'][0] * 2][ print_list[current_gate['which_qubits'] * 2][
ind + length[sec] // 2 - 1: ind + length[sec] // 2 + 2] = current_gate['gate'].upper() ind + length[sec] // 2 - 1: ind + length[sec] // 2 + 2] = current_gate['gate'].upper()
elif current_gate['gate'] in {'p', 'rx', 'ry', 'rz'}: elif current_gate['gate'] in {'p', 'rx', 'ry', 'rz'}:
sec = gate[i] sec = gate[i]
ind = sum(length[:sec]) ind = sum(length[:sec])
line = current_gate['which_qubits'][0] * 2 line = current_gate['which_qubits'] * 2
# param = self.__param[current_gate['theta'][2 if current_gate['gate'] == 'rz' else 0]] # param = self.__param[current_gate['theta'][2 if current_gate['gate'] == 'rz' else 0]]
param = current_gate['theta'] param = current_gate['theta']
if current_gate['gate'] == 'p': if current_gate['gate'] == 'p':
...@@ -1592,8 +1464,7 @@ class Circuit(Sequential): ...@@ -1592,8 +1464,7 @@ class Circuit(Sequential):
print_list[line][ind + 2] = 'R' print_list[line][ind + 2] = 'R'
print_list[line][ind + 3] = current_gate['gate'][1] print_list[line][ind + 3] = current_gate['gate'][1]
print_list[line][ind + 4] = '(' print_list[line][ind + 4] = '('
print_list[line][ind + 5: ind + print_list[line][ind + 5: ind + 10] = format(float(param.numpy()), '.3f')[:5]
10] = format(float(param.numpy()), '.3f')[:5]
print_list[line][ind + 10] = ')' print_list[line][ind + 10] = ')'
# Two-qubit gates # Two-qubit gates
elif current_gate['gate'] in {'cnot', 'swap', 'rxx', 'ryy', 'rzz', 'ms', 'cz', 'cy', elif current_gate['gate'] in {'cnot', 'swap', 'rxx', 'ryy', 'rzz', 'ms', 'cz', 'cy',
...@@ -1604,11 +1475,9 @@ class Circuit(Sequential): ...@@ -1604,11 +1475,9 @@ class Circuit(Sequential):
tqubit = current_gate['which_qubits'][1] tqubit = current_gate['which_qubits'][1]
if current_gate['gate'] in {'cnot', 'swap', 'cy', 'cz', 'cu'}: if current_gate['gate'] in {'cnot', 'swap', 'cy', 'cz', 'cu'}:
print_list[cqubit * 2][ind + length[sec] // 2] = \ print_list[cqubit * 2][ind + length[sec] // 2] = \
'*' if current_gate['gate'] in {'cnot', '*' if current_gate['gate'] in {'cnot', 'cy', 'cz', 'cu'} else 'x'
'cy', 'cz', 'cu'} else 'x'
print_list[tqubit * 2][ind + length[sec] // 2] = \ print_list[tqubit * 2][ind + length[sec] // 2] = \
'x' if current_gate['gate'] in { 'x' if current_gate['gate'] in {'swap', 'cnot'} else current_gate['gate'][1]
'swap', 'cnot'} else current_gate['gate'][1]
elif current_gate['gate'] == 'ms': elif current_gate['gate'] == 'ms':
for qubit in {cqubit, tqubit}: for qubit in {cqubit, tqubit}:
print_list[qubit * 2][ind + length[sec] // 2 - 1] = 'M' print_list[qubit * 2][ind + length[sec] // 2 - 1] = 'M'
...@@ -1619,11 +1488,9 @@ class Circuit(Sequential): ...@@ -1619,11 +1488,9 @@ class Circuit(Sequential):
param = current_gate['theta'] param = current_gate['theta']
for line in {cqubit * 2, tqubit * 2}: for line in {cqubit * 2, tqubit * 2}:
print_list[line][ind + 2] = 'R' print_list[line][ind + 2] = 'R'
print_list[line][ind + 3: ind + print_list[line][ind + 3: ind + 5] = current_gate['gate'][1:3].lower()
5] = current_gate['gate'][1:3].lower()
print_list[line][ind + 5] = '(' print_list[line][ind + 5] = '('
print_list[line][ind + 6: ind + print_list[line][ind + 6: ind + 10] = format(float(param.numpy()), '.2f')[:4]
10] = format(float(param.numpy()), '.2f')[:4]
print_list[line][ind + 10] = ')' print_list[line][ind + 10] = ')'
elif current_gate['gate'] in {'crx', 'cry', 'crz'}: elif current_gate['gate'] in {'crx', 'cry', 'crz'}:
# param = self.__param[current_gate['theta'][2 if current_gate['gate'] == 'crz' else 0]] # param = self.__param[current_gate['theta'][2 if current_gate['gate'] == 'crz' else 0]]
...@@ -1632,8 +1499,7 @@ class Circuit(Sequential): ...@@ -1632,8 +1499,7 @@ class Circuit(Sequential):
print_list[tqubit * 2][ind + 2] = 'R' print_list[tqubit * 2][ind + 2] = 'R'
print_list[tqubit * 2][ind + 3] = current_gate['gate'][2] print_list[tqubit * 2][ind + 3] = current_gate['gate'][2]
print_list[tqubit * 2][ind + 4] = '(' print_list[tqubit * 2][ind + 4] = '('
print_list[tqubit * 2][ind + 5: ind + print_list[tqubit * 2][ind + 5: ind + 10] = format(float(param.numpy()), '.3f')[:5]
10] = format(float(param.numpy()), '.3f')[:5]
print_list[tqubit * 2][ind + 10] = ')' print_list[tqubit * 2][ind + 10] = ')'
start_line = min(cqubit, tqubit) start_line = min(cqubit, tqubit)
end_line = max(cqubit, tqubit) end_line = max(cqubit, tqubit)
...@@ -1668,24 +1534,30 @@ class Circuit(Sequential): ...@@ -1668,24 +1534,30 @@ class Circuit(Sequential):
print_list[cqubit1 * 2][ind + length[sec] // 2] = '*' print_list[cqubit1 * 2][ind + length[sec] // 2] = '*'
print_list[cqubit2 * 2][ind + length[sec] // 2] = '*' print_list[cqubit2 * 2][ind + length[sec] // 2] = '*'
print_list[tqubit * 2][ind + length[sec] // 2] = 'X' print_list[tqubit * 2][ind + length[sec] // 2] = 'X'
else:
raise NotImplementedError(f"Not support to print the gate {current_gate['gate']}.")
print_list = list(map(''.join, print_list)) print_list = list(map(''.join, print_list))
return_str = '\n'.join(print_list) return_str = '\n'.join(print_list)
return return_str return return_str
def forward(self, state: Optional[State] = None) -> State: def forward(self, state: Optional[State] = None) -> State:
r""" forward the input r""" forward the input
Args: Args:
state: initial state state: initial state
Returns: Returns:
output quantum state output quantum state
""" """
assert self.__num_qubits is not None, "Information about num_qubits is required before running the circuit"
if state is None: if state is None:
assert self.__num_qubits is not None, "Information about num_qubits is required before running the circuit"
state = zero_state(self.__num_qubits, self.backend, self.dtype) state = zero_state(self.__num_qubits, self.backend, self.dtype)
else:
assert self.__num_qubits == state.num_qubits, \
f"num_qubits does not agree: expected {self.__num_qubits}, received {state.num_qubits}"
return super().forward(state) return super().forward(state)
...@@ -18,7 +18,6 @@ The source file of the Sequential class. ...@@ -18,7 +18,6 @@ The source file of the Sequential class.
""" """
import collections import collections
from sre_parse import State
from paddle_quantum import Operator from paddle_quantum import Operator
from typing import Optional, Union, Iterable, Any, List from typing import Optional, Union, Iterable, Any, List
......
...@@ -82,7 +82,7 @@ class Inserter: ...@@ -82,7 +82,7 @@ class Inserter:
# add one qubit gates rz_rx_rz to the circuit # add one qubit gates rz_rx_rz to the circuit
cir.insert(insert_ind, RZ([index], param=theta[0])) cir.insert(insert_ind, RZ([index], param=theta[0]))
cir.insert(insert_ind + 1, RX([index], param=theta[1])) cir.insert(insert_ind + 1, RX([index], param=theta[1]))
cir.insert(insert_ind + 2, RX([index], param=theta[2])) cir.insert(insert_ind + 2, RZ([index], param=theta[2]))
else: else:
# add two qubit gates to the circuit # add two qubit gates to the circuit
# obtain which two qubits to insert # obtain which two qubits to insert
...@@ -121,9 +121,9 @@ class Inserter: ...@@ -121,9 +121,9 @@ class Inserter:
cir.insert(insert_ind + 1, RZ([qubit_i], param=theta[0])) cir.insert(insert_ind + 1, RZ([qubit_i], param=theta[0]))
cir.insert(insert_ind + 2, RX([qubit_i], param=theta[1])) cir.insert(insert_ind + 2, RX([qubit_i], param=theta[1]))
cir.insert(insert_ind + 3, RZ([qubit_i], param=theta[2])) cir.insert(insert_ind + 3, RZ([qubit_i], param=theta[2]))
cir.insert(insert_ind + 4, RX([qubit_i], param=theta[3])) cir.insert(insert_ind + 4, RX([qubit_j], param=theta[3]))
cir.insert(insert_ind + 5, RZ([qubit_i], param=theta[4])) cir.insert(insert_ind + 5, RZ([qubit_j], param=theta[4]))
cir.insert(insert_ind + 6, RX([qubit_i], param=theta[5])) cir.insert(insert_ind + 6, RX([qubit_j], param=theta[5]))
cir.insert(insert_ind + 7, CNOT([qubit_i, qubit_j])) cir.insert(insert_ind + 7, CNOT([qubit_i, qubit_j]))
return cir return cir
...@@ -145,7 +145,7 @@ class Inserter: ...@@ -145,7 +145,7 @@ class Inserter:
for gate_info in history: for gate_info in history:
qubits_idx = gate_info["which_qubits"] qubits_idx = gate_info["which_qubits"]
if gate_info["gate"] == "rz" or gate_info["gate"] == "rx": if gate_info["gate"] == "rz" or gate_info["gate"] == "rx":
qubit_ind = qubits_idx[0] qubit_ind = qubits_idx
count_gates[qubit_ind] += 1 count_gates[qubit_ind] += 1
elif gate_info["gate"] == "cnot": elif gate_info["gate"] == "cnot":
qubit_i = min(qubits_idx[0], qubits_idx[1]) qubit_i = min(qubits_idx[0], qubits_idx[1])
...@@ -712,11 +712,12 @@ class Simplifier: ...@@ -712,11 +712,12 @@ class Simplifier:
return cir return cir
def cir_decompose(cir: Circuit) -> Circuit: def cir_decompose(cir: Circuit, trainable: Optional[bool] = False) -> Circuit:
r"""Decompose all layers of circuit into gates. r"""Decompose all layers of circuit into gates, and make all parameterized gates trainable if needed
Args: Args:
cir: Target quantum circuit. cir: Target quantum circuit.
trainable: whether the decomposed parameterized gates are trainable
Returns: Returns:
A quantum circuit with same structure and parameters but all layers are decomposed into Gates. A quantum circuit with same structure and parameters but all layers are decomposed into Gates.
...@@ -730,13 +731,17 @@ def cir_decompose(cir: Circuit) -> Circuit: ...@@ -730,13 +731,17 @@ def cir_decompose(cir: Circuit) -> Circuit:
gate_name = gate_info['gate'] gate_name = gate_info['gate']
qubits_idx = gate_info['which_qubits'] qubits_idx = gate_info['which_qubits']
param = gate_info['theta'] param = gate_info['theta']
# get gate function # get gate function
if param is None: if param is None:
getattr(new_cir, gate_name)(qubits_idx) getattr(new_cir, gate_name)(qubits_idx)
else: continue
getattr(new_cir, gate_name)(qubits_idx, param=param)
if trainable:
param = param.reshape([1] + param.shape)
param = paddle.create_parameter(
shape=param.shape, dtype=param.dtype,
default_initializer=paddle.nn.initializer.Assign(param))
getattr(new_cir, gate_name)(qubits_idx, param=param)
return new_cir return new_cir
...@@ -831,7 +836,7 @@ class VAns: ...@@ -831,7 +836,7 @@ class VAns:
self.loss = itr_loss self.loss = itr_loss
else: # insert + simplification else: # insert + simplification
# Insert # Insert
new_cir = cir_decompose(self.cir) new_cir = cir_decompose(self.cir, trainable=True)
new_cir = Inserter.insert_identities( new_cir = Inserter.insert_identities(
new_cir, self.insert_rate, self.epsilon) new_cir, self.insert_rate, self.epsilon)
...@@ -874,6 +879,7 @@ class VAns: ...@@ -874,6 +879,7 @@ class VAns:
Returns: Returns:
Optimized loss. Optimized loss.
""" """
cir = cir_decompose(cir, trainable=True)
opt = paddle.optimizer.Adam( opt = paddle.optimizer.Adam(
learning_rate=self.LR, parameters=cir.parameters()) learning_rate=self.LR, parameters=cir.parameters())
......
...@@ -20,6 +20,7 @@ The module that contains various backends. ...@@ -20,6 +20,7 @@ The module that contains various backends.
from . import state_vector from . import state_vector
from . import density_matrix from . import density_matrix
from . import quleaf from . import quleaf
from . import unitary_matrix
import enum import enum
......
...@@ -63,6 +63,7 @@ def set_quleaf_token(token: str) -> None: ...@@ -63,6 +63,7 @@ def set_quleaf_token(token: str) -> None:
""" """
global TOKEN global TOKEN
TOKEN = token TOKEN = token
QCompute.Define.hubToken = token
def get_quleaf_token() -> str: def get_quleaf_token() -> str:
......
...@@ -46,7 +46,7 @@ class BitFlip(Channel): ...@@ -46,7 +46,7 @@ class BitFlip(Channel):
qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
): ):
super().__init__() super().__init__()
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
if isinstance(prob, float): if isinstance(prob, float):
self.prob = paddle.to_tensor(prob) self.prob = paddle.to_tensor(prob)
else: else:
...@@ -80,7 +80,7 @@ class PhaseFlip(Channel): ...@@ -80,7 +80,7 @@ class PhaseFlip(Channel):
qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
): ):
super().__init__() super().__init__()
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
if isinstance(prob, float): if isinstance(prob, float):
self.prob = paddle.to_tensor(prob) self.prob = paddle.to_tensor(prob)
else: else:
...@@ -114,7 +114,7 @@ class BitPhaseFlip(Channel): ...@@ -114,7 +114,7 @@ class BitPhaseFlip(Channel):
qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
): ):
super().__init__() super().__init__()
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
if isinstance(prob, float): if isinstance(prob, float):
self.prob = paddle.to_tensor(prob) self.prob = paddle.to_tensor(prob)
else: else:
...@@ -156,7 +156,7 @@ class AmplitudeDamping(Channel): ...@@ -156,7 +156,7 @@ class AmplitudeDamping(Channel):
qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
): ):
super().__init__() super().__init__()
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
if isinstance(gamma, float): if isinstance(gamma, float):
self.gamma = paddle.to_tensor(gamma) self.gamma = paddle.to_tensor(gamma)
else: else:
...@@ -197,7 +197,7 @@ class GeneralizedAmplitudeDamping(Channel): ...@@ -197,7 +197,7 @@ class GeneralizedAmplitudeDamping(Channel):
qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
): ):
super().__init__() super().__init__()
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
if isinstance(prob, float): if isinstance(prob, float):
self.prob = paddle.to_tensor(prob) self.prob = paddle.to_tensor(prob)
else: else:
...@@ -244,7 +244,7 @@ class PhaseDamping(Channel): ...@@ -244,7 +244,7 @@ class PhaseDamping(Channel):
qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
): ):
super().__init__() super().__init__()
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
if isinstance(gamma, float): if isinstance(gamma, float):
self.gamma = paddle.to_tensor(gamma) self.gamma = paddle.to_tensor(gamma)
else: else:
...@@ -280,7 +280,7 @@ class Depolarizing(Channel): ...@@ -280,7 +280,7 @@ class Depolarizing(Channel):
qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
): ):
super().__init__() super().__init__()
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
if isinstance(prob, float): if isinstance(prob, float):
self.prob = paddle.to_tensor(prob) self.prob = paddle.to_tensor(prob)
else: else:
...@@ -311,7 +311,7 @@ class PauliChannel(Channel): ...@@ -311,7 +311,7 @@ class PauliChannel(Channel):
qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
): ):
super().__init__() super().__init__()
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
if isinstance(prob, Iterable): if isinstance(prob, Iterable):
self.prob = paddle.to_tensor(prob) self.prob = paddle.to_tensor(prob)
else: else:
...@@ -369,7 +369,7 @@ class ResetChannel(Channel): ...@@ -369,7 +369,7 @@ class ResetChannel(Channel):
qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
): ):
super().__init__() super().__init__()
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
if isinstance(prob, Iterable): if isinstance(prob, Iterable):
self.prob = paddle.to_tensor(prob) self.prob = paddle.to_tensor(prob)
else: else:
...@@ -402,7 +402,7 @@ class ThermalRelaxation(Channel): ...@@ -402,7 +402,7 @@ class ThermalRelaxation(Channel):
qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None
): ):
super().__init__() super().__init__()
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
if isinstance(const_t, float): if isinstance(const_t, float):
self.const_t = paddle.to_tensor(const_t) self.const_t = paddle.to_tensor(const_t)
else: else:
......
...@@ -45,7 +45,7 @@ class KrausRepr(Channel): ...@@ -45,7 +45,7 @@ class KrausRepr(Channel):
assert 2 ** num_acted_qubits == kraus_oper[0].shape[0], "The length of oracle should be integer power of 2." assert 2 ** num_acted_qubits == kraus_oper[0].shape[0], "The length of oracle should be integer power of 2."
self.kraus_oper = kraus_oper self.kraus_oper = kraus_oper
is_single_qubit = True if num_acted_qubits == 1 else False is_single_qubit = True if num_acted_qubits == 1 else False
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit, num_acted_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits)
def forward(self, state: 'paddle_quantum.State') -> 'paddle_quantum.State': def forward(self, state: 'paddle_quantum.State') -> 'paddle_quantum.State':
for qubits_idx in self.qubits_idx: for qubits_idx in self.qubits_idx:
......
...@@ -27,6 +27,8 @@ import paddle.vision.transforms as transform ...@@ -27,6 +27,8 @@ import paddle.vision.transforms as transform
from sklearn.model_selection import train_test_split from sklearn.model_selection import train_test_split
from sklearn import datasets from sklearn import datasets
from paddle_quantum.gate import RY, RZ, U3, CNOT, IQPEncoding, AmplitudeEncoding from paddle_quantum.gate import RY, RZ, U3, CNOT, IQPEncoding, AmplitudeEncoding
from .base import get_dtype
from .intrinsic import _get_float_dtype
__all__ = [ __all__ = [
"Dataset", "Dataset",
...@@ -117,14 +119,15 @@ class Dataset(object): ...@@ -117,14 +119,15 @@ class Dataset(object):
""" """
quantum_states = classical_data.copy() quantum_states = classical_data.copy()
quantum_circuits = classical_data.copy() quantum_circuits = classical_data.copy()
float_dtype = _get_float_dtype(get_dtype())
if encoding == AMPLITUDE_ENCODING: if encoding == AMPLITUDE_ENCODING:
# Not support to return circuit in amplitude encoding # Not support to return circuit in amplitude encoding
if return_state is False or split_circuit is True: if return_state is False or split_circuit is True:
raise Exception("Not support to return circuit in amplitude encoding") raise Exception("Not support to return circuit in amplitude encoding")
for i in range(len(classical_data)): for i in range(len(classical_data)):
x = paddle.to_tensor(_normalize(classical_data[i])) x = paddle.to_tensor(_normalize(classical_data[i]), dtype=float_dtype)
if is_image: if is_image:
x = paddle.to_tensor(_normalize_image(classical_data[i])) x = paddle.to_tensor(_normalize_image(classical_data[i]), dtype=float_dtype)
circuit = AmplitudeEncoding(qubits_idx='full', num_qubits=num_qubits) circuit = AmplitudeEncoding(qubits_idx='full', num_qubits=num_qubits)
state = circuit(x) state = circuit(x)
quantum_states[i] = state.data.numpy() quantum_states[i] = state.data.numpy()
...@@ -133,9 +136,9 @@ class Dataset(object): ...@@ -133,9 +136,9 @@ class Dataset(object):
for i in range(len(classical_data)): for i in range(len(classical_data)):
one_block_param = 1 * num_qubits one_block_param = 1 * num_qubits
depth = int(can_describe_dimension / one_block_param) depth = int(can_describe_dimension / one_block_param)
param = paddle.to_tensor(_normalize(classical_data[i])) param = paddle.to_tensor(_normalize(classical_data[i]), dtype=float_dtype)
if is_image: if is_image:
param = paddle.to_tensor(_normalize_image(classical_data[i])) param = paddle.to_tensor(_normalize_image(classical_data[i]), dtype=float_dtype)
param = paddle.reshape(param, (depth, num_qubits, 1)) param = paddle.reshape(param, (depth, num_qubits, 1))
which_qubits = list(range(num_qubits)) which_qubits = list(range(num_qubits))
if split_circuit: if split_circuit:
...@@ -158,9 +161,9 @@ class Dataset(object): ...@@ -158,9 +161,9 @@ class Dataset(object):
for i in range(len(classical_data)): for i in range(len(classical_data)):
one_block_param = 1 * num_qubits one_block_param = 1 * num_qubits
depth = int(can_describe_dimension / one_block_param) depth = int(can_describe_dimension / one_block_param)
param = paddle.to_tensor(_normalize(classical_data[i])) param = paddle.to_tensor(_normalize(classical_data[i]), dtype=float_dtype)
if is_image: if is_image:
param = paddle.to_tensor(_normalize_image(classical_data[i])) param = paddle.to_tensor(_normalize_image(classical_data[i]), dtype=float_dtype)
param = paddle.reshape(param, (depth, num_qubits)) param = paddle.reshape(param, (depth, num_qubits))
if split_circuit: if split_circuit:
quantum_circuits[i] = [] quantum_circuits[i] = []
...@@ -190,9 +193,9 @@ class Dataset(object): ...@@ -190,9 +193,9 @@ class Dataset(object):
for i in range(len(classical_data)): for i in range(len(classical_data)):
one_block_param = 3 * num_qubits one_block_param = 3 * num_qubits
depth = int(can_describe_dimension / one_block_param) depth = int(can_describe_dimension / one_block_param)
param = paddle.to_tensor(_normalize(classical_data[i])) param = paddle.to_tensor(_normalize(classical_data[i]), dtype=float_dtype)
if is_image: if is_image:
param = paddle.to_tensor(_normalize_image(classical_data[i])) param = paddle.to_tensor(_normalize_image(classical_data[i]), dtype=float_dtype)
param = paddle.reshape(param, (depth, num_qubits, 3)) param = paddle.reshape(param, (depth, num_qubits, 3))
which_qubits = list(range(num_qubits)) which_qubits = list(range(num_qubits))
if split_circuit: if split_circuit:
...@@ -219,9 +222,9 @@ class Dataset(object): ...@@ -219,9 +222,9 @@ class Dataset(object):
for i in range(len(classical_data)): for i in range(len(classical_data)):
one_block_param = 2 * num_qubits one_block_param = 2 * num_qubits
depth = int(can_describe_dimension / one_block_param) depth = int(can_describe_dimension / one_block_param)
param = paddle.to_tensor(_normalize(classical_data[i])) param = paddle.to_tensor(_normalize(classical_data[i]), dtype=float_dtype)
if is_image: if is_image:
param = paddle.to_tensor(_normalize_image(classical_data[i])) param = paddle.to_tensor(_normalize_image(classical_data[i]), dtype=float_dtype)
param = paddle.reshape(param, (depth, num_qubits, 2)) param = paddle.reshape(param, (depth, num_qubits, 2))
which_qubits = [k for k in range(num_qubits)] which_qubits = [k for k in range(num_qubits)]
if split_circuit: if split_circuit:
...@@ -256,9 +259,9 @@ class Dataset(object): ...@@ -256,9 +259,9 @@ class Dataset(object):
for i in range(len(classical_data)): for i in range(len(classical_data)):
one_block_param = 1 * num_qubits one_block_param = 1 * num_qubits
depth = int(can_describe_dimension / one_block_param) depth = int(can_describe_dimension / one_block_param)
param = paddle.to_tensor(_normalize(classical_data[i])) param = paddle.to_tensor(_normalize(classical_data[i]), dtype=float_dtype)
if is_image: if is_image:
param = paddle.to_tensor(_normalize_image(classical_data[i])) param = paddle.to_tensor(_normalize_image(classical_data[i]), dtype=float_dtype)
param = paddle.reshape(param, (depth, num_qubits, 1)) param = paddle.reshape(param, (depth, num_qubits, 1))
which_qubits = [k for k in range(num_qubits)] which_qubits = [k for k in range(num_qubits)]
if split_circuit: if split_circuit:
...@@ -287,9 +290,9 @@ class Dataset(object): ...@@ -287,9 +290,9 @@ class Dataset(object):
for i in range(len(classical_data)): for i in range(len(classical_data)):
one_block_param = 3 * num_qubits one_block_param = 3 * num_qubits
depth = int(can_describe_dimension / one_block_param) depth = int(can_describe_dimension / one_block_param)
param = paddle.to_tensor(_normalize(classical_data[i])) param = paddle.to_tensor(_normalize(classical_data[i]), dtype=float_dtype)
if is_image: if is_image:
param = paddle.to_tensor(_normalize_image(classical_data[i])) param = paddle.to_tensor(_normalize_image(classical_data[i]), dtype=float_dtype)
param = paddle.reshape(param, (depth, num_qubits, 3)) param = paddle.reshape(param, (depth, num_qubits, 3))
which_qubits = [k for k in range(num_qubits)] which_qubits = [k for k in range(num_qubits)]
if split_circuit: if split_circuit:
...@@ -463,7 +466,7 @@ class VisionDataset(Dataset): ...@@ -463,7 +466,7 @@ class VisionDataset(Dataset):
new_size = int(np.sqrt(self.dimension)) new_size = int(np.sqrt(self.dimension))
cur_image = transform.resize(cur_image.reshape((self.figure_size, self.figure_size)), cur_image = transform.resize(cur_image.reshape((self.figure_size, self.figure_size)),
(new_size, new_size)) (new_size, new_size))
self.classical_image_vectors[i] = cur_image.reshape(-1).astype(np.float64) # now it is one-dimension self.classical_image_vectors[i] = cur_image.reshape(-1) # now it is one-dimension
if self.can_describe_dimension < len(self.classical_image_vectors[i]): if self.can_describe_dimension < len(self.classical_image_vectors[i]):
self.classical_image_vectors[i] = self.classical_image_vectors[i][:self.can_describe_dimension] self.classical_image_vectors[i] = self.classical_image_vectors[i][:self.can_describe_dimension]
...@@ -476,14 +479,14 @@ class VisionDataset(Dataset): ...@@ -476,14 +479,14 @@ class VisionDataset(Dataset):
elif downscaling_method == DOWNSCALINGMETHOD_PCA: elif downscaling_method == DOWNSCALINGMETHOD_PCA:
for i in range(len(self.classical_image_vectors)): for i in range(len(self.classical_image_vectors)):
_, s, _ = np.linalg.svd(self.classical_image_vectors[i].reshape((self.figure_size, self.figure_size))) _, s, _ = np.linalg.svd(self.classical_image_vectors[i].reshape((self.figure_size, self.figure_size)))
s = s[:self.dimension].astype(np.float64) s = s[:self.dimension]
if self.can_describe_dimension > self.dimension: if self.can_describe_dimension > self.dimension:
self.classical_image_vectors[i] = np.append(s, np.array( self.classical_image_vectors[i] = np.append(s, np.array(
[0.0] * (self.can_describe_dimension - self.dimension))) [0.0] * (self.can_describe_dimension - self.dimension)))
else: else:
self.classical_image_vectors[i] = s[:self.can_describe_dimension] self.classical_image_vectors[i] = s[:self.can_describe_dimension]
# Step 4: Encode the data, which must be of float64 type(needed in paddle quantum) # Step 4: Encode the data
self.quantum_image_states, self.quantum_image_circuits = self.data2circuit( self.quantum_image_states, self.quantum_image_circuits = self.data2circuit(
self.classical_image_vectors, encoding, num_qubits, self.can_describe_dimension, split_circuit, self.classical_image_vectors, encoding, num_qubits, self.can_describe_dimension, split_circuit,
return_state, is_image=True) return_state, is_image=True)
...@@ -714,14 +717,13 @@ class SimpleDataset(Dataset): ...@@ -714,14 +717,13 @@ class SimpleDataset(Dataset):
# The second step: fill the vector to ``can_describe_dimension`` using zero # The second step: fill the vector to ``can_describe_dimension`` using zero
for i in range(len(self.feature)): for i in range(len(self.feature)):
self.feature[i] = self.feature[i].reshape(-1).astype( self.feature[i] = self.feature[i].reshape(-1) # now self.images[i] is a numpy with (new_size*new_size,1) shape
np.float64) # now self.images[i] is a numpy with (new_size*new_size,1) shape
self.feature[i] = np.append( self.feature[i] = np.append(
self.feature[i], self.feature[i],
np.array([0.0] * (self.can_describe_dimension - self.dimension)) np.array([0.0] * (self.can_describe_dimension - self.dimension))
) # now self.images[i] is filled to ``self.can_describe_dimension`` ) # now self.images[i] is filled to ``self.can_describe_dimension``
# Step 3: Encode the data, which must be of float64 type(needed in paddle quantum) # Step 3: Encode the data
self.quantum_states, self.quantum_circuits = self.data2circuit( self.quantum_states, self.quantum_circuits = self.data2circuit(
self.feature, encoding, num_qubits, self.can_describe_dimension, False, # split_circuit=False self.feature, encoding, num_qubits, self.can_describe_dimension, False, # split_circuit=False
return_state return_state
...@@ -800,7 +802,7 @@ class BreastCancer(SimpleDataset): ...@@ -800,7 +802,7 @@ class BreastCancer(SimpleDataset):
""" """
def __init__(self, encoding: str, num_qubits: int, test_rate: Optional[float] =0.2, def __init__(self, encoding: str, num_qubits: int, test_rate: Optional[float] = 0.2,
return_state: Optional[bool] = True, seed: Optional[int] = 0) -> None: return_state: Optional[bool] = True, seed: Optional[int] = 0) -> None:
SimpleDataset.__init__(self, dimension=30) # The dimension is 30 SimpleDataset.__init__(self, dimension=30) # The dimension is 30
self.dimension = 30 self.dimension = 30
......
...@@ -18,7 +18,7 @@ The module of the quantum gates. ...@@ -18,7 +18,7 @@ The module of the quantum gates.
""" """
from . import functional from . import functional
from .base import Gate from .base import Gate, ParamGate
from .clifford import Clifford, compose_clifford_circuit from .clifford import Clifford, compose_clifford_circuit
from .single_qubit_gate import H, S, T, X, Y, Z, P, RX, RY, RZ, U3 from .single_qubit_gate import H, S, T, X, Y, Z, P, RX, RY, RZ, U3
from .multi_qubit_gate import CNOT, CX, CY, CZ, SWAP from .multi_qubit_gate import CNOT, CX, CY, CZ, SWAP
......
...@@ -17,11 +17,15 @@ r""" ...@@ -17,11 +17,15 @@ r"""
The source file of the basic class for the quantum gates. The source file of the basic class for the quantum gates.
""" """
import paddle
import paddle_quantum import paddle_quantum
from typing import Union, List, Iterable
from ..intrinsic import _get_float_dtype
from math import pi
class Gate(paddle_quantum.Operator): class Gate(paddle_quantum.Operator):
r"""Basis class for quantum gates. r"""Base class for quantum gates.
Args: Args:
depth: Number of layers. Defaults to 1. depth: Number of layers. Defaults to 1.
...@@ -36,6 +40,7 @@ class Gate(paddle_quantum.Operator): ...@@ -36,6 +40,7 @@ class Gate(paddle_quantum.Operator):
): ):
super().__init__(backend, dtype, name_scope) super().__init__(backend, dtype, name_scope)
self.depth = depth self.depth = depth
self.gate_name = None
def forward(self, *inputs, **kwargs): def forward(self, *inputs, **kwargs):
raise NotImplementedError raise NotImplementedError
...@@ -47,3 +52,75 @@ class Gate(paddle_quantum.Operator): ...@@ -47,3 +52,75 @@ class Gate(paddle_quantum.Operator):
value.backend = paddle_quantum.get_backend() if self.backend is None else self.backend value.backend = paddle_quantum.get_backend() if self.backend is None else self.backend
if value.dtype is None: if value.dtype is None:
value.dtype = paddle_quantum.get_dtype() if self.dtype is None else self.dtype value.dtype = paddle_quantum.get_dtype() if self.dtype is None else self.dtype
def gate_history_generation(self) -> None:
r""" determine self.gate_history
"""
gate_history = []
for _ in range(0, self.depth):
for qubit_idx in self.qubits_idx:
gate_info = {'gate': self.gate_name, 'which_qubits': qubit_idx, 'theta': None}
gate_history.append(gate_info)
self.gate_history = gate_history
class ParamGate(Gate):
r""" Base class for quantum parameterized gates
"""
def theta_generation(self, param: Union[paddle.Tensor, float, List[float]], param_shape: List[int]) -> None:
""" determine self.theta, and create parameter if necessary
Args:
param: input theta
param_shape: shape for theta
Note:
in the following cases ``param`` will be transformed to a parameter:
- ``param`` is None
in the following cases ``param`` will be added to the parameter list:
- ``param`` is ParamBase
in the following cases ``param`` will keep unchange:
- ``param`` is a Tensor but not a parameter
- ``param`` is a float or list of floats
"""
float_dtype = _get_float_dtype(self.dtype)
if param is None:
theta = self.create_parameter(
shape=param_shape, dtype=float_dtype,
default_initializer=paddle.nn.initializer.Uniform(low=0, high=2 * pi)
)
self.add_parameter('theta', theta)
elif isinstance(param, paddle.fluid.framework.ParamBase):
assert param.shape == param_shape, "received: " + str(param.shape) + " expect: " + str(param_shape)
self.add_parameter('theta', param)
elif isinstance(param, paddle.Tensor):
param = param.reshape(param_shape)
self.theta = param
elif isinstance(param, float):
self.theta = paddle.ones(param_shape, dtype=float_dtype) * param
else: # when param is a list of float
self.theta = paddle.to_tensor(param, dtype=float_dtype).reshape(param_shape)
def gate_history_generation(self) -> None:
r""" determine self.gate_history when gate is parameterized
"""
gate_history = []
for depth_idx in range(0, self.depth):
for idx, qubit_idx in enumerate(self.qubits_idx):
if self.param_sharing:
param = self.theta[depth_idx]
else:
param = self.theta[depth_idx][idx]
gate_info = {'gate': self.gate_name, 'which_qubits': qubit_idx, 'theta': param}
gate_history.append(gate_info)
self.gate_history = gate_history
...@@ -38,13 +38,16 @@ class Oracle(Gate): ...@@ -38,13 +38,16 @@ class Oracle(Gate):
""" """
def __init__( def __init__(
self, oracle: paddle.Tensor, qubits_idx: Union[Iterable[Iterable[int]], Iterable[int], int], self, oracle: paddle.Tensor, qubits_idx: Union[Iterable[Iterable[int]], Iterable[int], int],
num_qubits: int = None, depth: int = 1 num_qubits: int = None, depth: int = 1, gate_name: str = 'O'
): ):
super().__init__(depth) super().__init__(depth)
oracle = oracle.cast(paddle_quantum.get_dtype())
assert is_unitary(oracle), "the input oracle must be a unitary matrix" assert is_unitary(oracle), "the input oracle must be a unitary matrix"
num_acted_qubits = int(math.log2(oracle.shape[0])) num_acted_qubits = int(math.log2(oracle.shape[0]))
self.oracle = paddle.cast(oracle, paddle_quantum.get_dtype()) self.oracle = paddle.cast(oracle, paddle_quantum.get_dtype())
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, False, num_acted_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits)
self.gate_name = gate_name
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
for _ in range(0, self.depth): for _ in range(0, self.depth):
...@@ -63,23 +66,25 @@ class ControlOracle(Gate): ...@@ -63,23 +66,25 @@ class ControlOracle(Gate):
depth: Number of layers. Defaults to ``1``. depth: Number of layers. Defaults to ``1``.
""" """
def __init__( def __init__(
self, oracle: paddle.Tensor, self, oracle: paddle.Tensor, qubits_idx: Union[Iterable[Iterable[int]], Iterable[int]],
# num_control_qubits: int, controlled_value: 'str', num_qubits: int = None, depth: int = 1, gate_name: str = 'cO'
qubits_idx: Union[Iterable[Iterable[int]], Iterable[int]], ) -> None:
num_qubits: int = None, depth: int = 1
):
super().__init__(depth) super().__init__(depth)
complex_dtype = paddle_quantum.get_dtype()
oracle = oracle.cast(complex_dtype)
assert is_unitary(oracle), "the input oracle must be a unitary matrix" assert is_unitary(oracle), "the input oracle must be a unitary matrix"
num_acted_qubits = int(math.log2(oracle.shape[0])) num_acted_qubits = int(math.log2(oracle.shape[0]))
# 暂时只支持单控制位 # 暂时只支持单控制位
oracle = ( oracle = (
paddle.kron(paddle.to_tensor([[1.0, 0], [0, 0]]), paddle.eye(2 ** num_acted_qubits)) + paddle.kron(paddle.to_tensor([[1.0, 0], [0, 0]], dtype=complex_dtype), paddle.eye(2 ** num_acted_qubits)) +
paddle.kron(paddle.to_tensor([[0.0, 0], [0, 1]]), oracle) paddle.kron(paddle.to_tensor([[0.0, 0], [0, 1]], dtype=complex_dtype), oracle)
) )
num_acted_qubits = num_acted_qubits + 1 num_acted_qubits = num_acted_qubits + 1
self.oracle = paddle.cast(oracle, paddle_quantum.get_dtype()) self.oracle = oracle
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, False, num_acted_qubits) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits)
self.gate_name = gate_name
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
for _ in range(0, self.depth): for _ in range(0, self.depth):
......
...@@ -41,17 +41,26 @@ class BasisEncoding(Gate): ...@@ -41,17 +41,26 @@ class BasisEncoding(Gate):
) -> None: ) -> None:
super().__init__() super().__init__()
self.num_qubits = num_qubits self.num_qubits = num_qubits
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.gate_name = 'BasisEnc'
def forward(self, feature: paddle.Tensor, def forward(self, feature: paddle.Tensor, state: 'paddle_quantum.State' = None) -> 'paddle_quantum.State':
state: Optional[paddle_quantum.State] = None, inverse: Optional[bool] = False) -> paddle_quantum.State:
feature = paddle.cast(feature, 'int32')
if state is None: if state is None:
state = paddle_quantum.state.zero_state(self.num_qubits) state = paddle_quantum.state.zero_state(self.num_qubits)
feature = paddle.cast(feature, 'int32')
gate_history = []
for idx, element in enumerate(feature): for idx, element in enumerate(feature):
if element: if element:
state = functional.x(state, self.qubits_idx[idx], self.dtype, self.backend) state = functional.x(state, self.qubits_idx[idx], self.dtype, self.backend)
gate_history.append({'gate': 'x', 'which_qubits': self.qubits_idx[idx], 'theta': None})
self.gate_history = gate_history
return state return state
def gate_history_generation(self) -> None:
if self.gate_history is None:
raise RuntimeError("you must forward the encoding to receive the gate history")
pass
class AmplitudeEncoding(Gate): class AmplitudeEncoding(Gate):
...@@ -68,9 +77,10 @@ class AmplitudeEncoding(Gate): ...@@ -68,9 +77,10 @@ class AmplitudeEncoding(Gate):
num_qubits = max(qubits_idx) num_qubits = max(qubits_idx)
super().__init__() super().__init__()
self.num_qubits = num_qubits self.num_qubits = num_qubits
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.gate_name = 'AmpEnc'
def forward(self, feature: paddle.Tensor) -> paddle_quantum.State: def forward(self, feature: paddle.Tensor) -> 'paddle_quantum.State':
def calc_location(location_of_bits_list): def calc_location(location_of_bits_list):
if len(location_of_bits_list) <= 1: if len(location_of_bits_list) <= 1:
result_list = [0, location_of_bits_list[0]] result_list = [0, location_of_bits_list[0]]
...@@ -133,21 +143,28 @@ class AngleEncoding(Gate): ...@@ -133,21 +143,28 @@ class AngleEncoding(Gate):
num_qubits = max(qubits_idx) num_qubits = max(qubits_idx)
super().__init__() super().__init__()
self.num_qubits = num_qubits self.num_qubits = num_qubits
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
if encoding_gate == 'rx': if encoding_gate == 'rx':
self.encoding_gate = functional.rx self.encoding_gate = functional.rx
elif encoding_gate == 'ry': elif encoding_gate == 'ry':
self.encoding_gate = functional.ry self.encoding_gate = functional.ry
elif encoding_gate == 'rz': elif encoding_gate == 'rz':
self.encoding_gate = functional.rz self.encoding_gate = functional.rz
self.encoding_gate_name = encoding_gate
feature = paddle.cast(feature, _get_float_dtype(paddle_quantum.get_dtype())) feature = paddle.cast(feature, _get_float_dtype(paddle_quantum.get_dtype()))
feature = paddle.flatten(feature) feature = paddle.flatten(feature)
self.feature = feature self.feature = feature
self.gate_name = 'AngleEnc'
def forward( def forward(
self, state: paddle_quantum.State, invert: Optional[bool] = False self, state: 'paddle_quantum.State' = None, invert: bool = False
) -> paddle_quantum.State: ) -> 'paddle_quantum.State':
gate_history = []
if state is None:
state = paddle_quantum.state.zero_state(self.num_qubits)
if invert: if invert:
feature = -1 * self.feature feature = -1 * self.feature
else: else:
...@@ -157,7 +174,14 @@ class AngleEncoding(Gate): ...@@ -157,7 +174,14 @@ class AngleEncoding(Gate):
state, element[0], self.qubits_idx[idx], state, element[0], self.qubits_idx[idx],
dtype=self.dtype, backend=self.backend dtype=self.dtype, backend=self.backend
) )
gate_history.append({'gate': self.encoding_gate_name, 'which_qubits': self.qubits_idx[idx], 'theta': element[0]})
self.gate_history = gate_history
return state return state
def gate_history_generation(self) -> None:
if self.gate_history is None:
raise RuntimeError("you must forward the encoding to receive the gate history")
pass
class IQPEncoding(Gate): class IQPEncoding(Gate):
...@@ -181,10 +205,15 @@ class IQPEncoding(Gate): ...@@ -181,10 +205,15 @@ class IQPEncoding(Gate):
feature = paddle.cast(feature, _get_float_dtype(paddle_quantum.get_dtype())) feature = paddle.cast(feature, _get_float_dtype(paddle_quantum.get_dtype()))
feature = paddle.flatten(feature) feature = paddle.flatten(feature)
self.feature = feature self.feature = feature
self.gate_name = 'IQPEnc'
def forward( def forward(
self, state: paddle_quantum.State, invert: Optional[bool] = False self, state: paddle_quantum.State = None, invert: Optional[bool] = False
) -> paddle_quantum.State: ) -> paddle_quantum.State:
gate_history = []
if state is None:
state = paddle_quantum.state.zero_state(self.num_qubits)
for _ in range(0, self.num_repeat): for _ in range(0, self.num_repeat):
if invert: if invert:
for qubits_idx in self.qubits_idx: for qubits_idx in self.qubits_idx:
...@@ -194,15 +223,28 @@ class IQPEncoding(Gate): ...@@ -194,15 +223,28 @@ class IQPEncoding(Gate):
dtype=self.dtype, backend=self.backend dtype=self.dtype, backend=self.backend
) )
state = functional.cnot(state, qubits_idx, dtype=self.dtype, backend=self.backend) state = functional.cnot(state, qubits_idx, dtype=self.dtype, backend=self.backend)
gate_history.append({'gate': 'cnot', 'which_qubits': qubits_idx, 'theta': None})
gate_history.append({'gate': 'rz', 'which_qubits': qubits_idx[1],
'theta': -self.feature[qubits_idx[0]] * self.feature[qubits_idx[1]]})
gate_history.append({'gate': 'cnot', 'which_qubits': qubits_idx, 'theta': None})
for idx in range(0, self.feature.size): for idx in range(0, self.feature.size):
state = functional.rz(state, -self.feature[idx], idx, dtype=self.dtype, backend=self.backend) state = functional.rz(state, -self.feature[idx], idx, dtype=self.dtype, backend=self.backend)
gate_history.append({'gate': 'rz', 'which_qubits': idx, 'theta': -self.feature[idx]})
for idx in range(0, self.feature.size): for idx in range(0, self.feature.size):
state = functional.h(state, idx, dtype=self.dtype, backend=self.backend) state = functional.h(state, idx, dtype=self.dtype, backend=self.backend)
gate_history.append({'gate': 'h', 'which_qubits': idx, 'theta': None})
else: else:
for idx in range(0, self.feature.size): for idx in range(0, self.feature.size):
state = functional.h(state, idx, dtype=self.dtype, backend=self.backend) state = functional.h(state, idx, dtype=self.dtype, backend=self.backend)
gate_history.append({'gate': 'h', 'which_qubits': idx, 'theta': None})
for idx in range(0, self.feature.size): for idx in range(0, self.feature.size):
state = functional.rz(state, self.feature[idx], idx, dtype=self.dtype, backend=self.backend) state = functional.rz(state, self.feature[idx], idx, dtype=self.dtype, backend=self.backend)
gate_history.append({'gate': 'rz', 'which_qubits': idx, 'theta': self.feature[idx]})
for qubits_idx in self.qubits_idx: for qubits_idx in self.qubits_idx:
state = functional.cnot(state, qubits_idx, dtype=self.dtype, backend=self.backend) state = functional.cnot(state, qubits_idx, dtype=self.dtype, backend=self.backend)
state = functional.rz( state = functional.rz(
...@@ -210,4 +252,16 @@ class IQPEncoding(Gate): ...@@ -210,4 +252,16 @@ class IQPEncoding(Gate):
dtype=self.dtype, backend=self.backend dtype=self.dtype, backend=self.backend
) )
state = functional.cnot(state, qubits_idx, dtype=self.dtype, backend=self.backend) state = functional.cnot(state, qubits_idx, dtype=self.dtype, backend=self.backend)
gate_history.append({'gate': 'cnot', 'which_qubits': qubits_idx, 'theta': None})
gate_history.append({'gate': 'rz', 'which_qubits': qubits_idx[1],
'theta': self.feature[qubits_idx[0]] * self.feature[qubits_idx[1]]})
gate_history.append({'gate': 'cnot', 'which_qubits': qubits_idx, 'theta': None})
self.gate_history = gate_history
return state return state
def gate_history_generation(self) -> None:
if self.gate_history is None:
raise RuntimeError("you must forward the encoding to receive the gate history")
pass
\ No newline at end of file
...@@ -85,7 +85,7 @@ def t(state: paddle_quantum.State, qubit_idx: int, dtype: str, backend: paddle_q ...@@ -85,7 +85,7 @@ def t(state: paddle_quantum.State, qubit_idx: int, dtype: str, backend: paddle_q
""" """
gate = [ gate = [
[1, 0], [1, 0],
[0, math.cos(math.pi / 4) - math.sin(math.pi / 4) * 1j], [0, math.cos(math.pi / 4) + math.sin(math.pi / 4) * 1j],
] ]
gate = paddle.to_tensor(gate, dtype=dtype) gate = paddle.to_tensor(gate, dtype=dtype)
state_data = simulation(state, gate, qubit_idx, state.num_qubits, backend) state_data = simulation(state, gate, qubit_idx, state.num_qubits, backend)
......
...@@ -24,7 +24,7 @@ import paddle_quantum ...@@ -24,7 +24,7 @@ import paddle_quantum
from paddle_quantum.gate import functional from paddle_quantum.gate import functional
from paddle_quantum.intrinsic import _get_float_dtype from paddle_quantum.intrinsic import _get_float_dtype
from .base import Gate from .base import Gate
from typing import Iterable, List, Optional, Union from typing import Iterable, List, Union, Tuple, Dict
def qubits_idx_filter(qubits_idx: Union[Iterable[int], str], num_qubits: int) -> List[Iterable[int]]: def qubits_idx_filter(qubits_idx: Union[Iterable[int], str], num_qubits: int) -> List[Iterable[int]]:
...@@ -64,12 +64,22 @@ class SuperpositionLayer(Gate): ...@@ -64,12 +64,22 @@ class SuperpositionLayer(Gate):
): ):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = qubits_idx_filter(qubits_idx, num_qubits) self.qubits_idx = qubits_idx_filter(qubits_idx, num_qubits)
self.gate_name = 'SupLayer'
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
for _ in range(0, self.depth): for _ in range(0, self.depth):
for qubit_idx in self.qubits_idx: for qubit_idx in self.qubits_idx:
state = functional.h(state, qubit_idx, self.dtype, self.backend) state = functional.h(state, qubit_idx, self.dtype, self.backend)
return state return state
def gate_history_generation(self):
gate_history = []
for _ in range(0, self.depth):
for qubit_idx in self.qubits_idx:
gate_info = {'gate': 'h', 'which_qubits': qubit_idx, 'theta': None}
gate_history.append(gate_info)
self.gate_history = gate_history
class WeakSuperpositionLayer(Gate): class WeakSuperpositionLayer(Gate):
...@@ -85,6 +95,7 @@ class WeakSuperpositionLayer(Gate): ...@@ -85,6 +95,7 @@ class WeakSuperpositionLayer(Gate):
): ):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = qubits_idx_filter(qubits_idx, num_qubits) self.qubits_idx = qubits_idx_filter(qubits_idx, num_qubits)
self.gate_name = 'WSupLayer'
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
theta = paddle.to_tensor([np.pi / 4]) theta = paddle.to_tensor([np.pi / 4])
...@@ -92,6 +103,14 @@ class WeakSuperpositionLayer(Gate): ...@@ -92,6 +103,14 @@ class WeakSuperpositionLayer(Gate):
for qubit_idx in self.qubits_idx: for qubit_idx in self.qubits_idx:
state = functional.ry(state, theta, qubit_idx, self.dtype, self.backend) state = functional.ry(state, theta, qubit_idx, self.dtype, self.backend)
return state return state
def gate_history_generation(self):
gate_history = []
for _ in range(0, self.depth):
for qubit_idx in self.qubits_idx:
gate_info = {'gate': 'ry', 'which_qubits': qubit_idx, 'theta': paddle.to_tensor([np.pi / 4])}
gate_history.append(gate_info)
self.gate_history = gate_history
class LinearEntangledLayer(Gate): class LinearEntangledLayer(Gate):
...@@ -117,6 +136,7 @@ class LinearEntangledLayer(Gate): ...@@ -117,6 +136,7 @@ class LinearEntangledLayer(Gate):
default_initializer=initializer default_initializer=initializer
) )
self.add_parameter('theta', theta) self.add_parameter('theta', theta)
self.gate_name = 'LEntLayer'
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
for depth_idx in range(0, self.depth): for depth_idx in range(0, self.depth):
...@@ -133,6 +153,23 @@ class LinearEntangledLayer(Gate): ...@@ -133,6 +153,23 @@ class LinearEntangledLayer(Gate):
state = functional.cnot( state = functional.cnot(
state, [self.qubits_idx[idx], self.qubits_idx[idx + 1]], self.dtype, self.backend) state, [self.qubits_idx[idx], self.qubits_idx[idx + 1]], self.dtype, self.backend)
return state return state
def gate_history_generation(self):
gate_history = []
qubits_idx = self.qubits_idx
for depth_idx in range(0, self.depth):
for idx, qubit_idx in enumerate(qubits_idx):
gate_history.append({'gate': 'ry', 'which_qubits': qubit_idx, 'theta': self.theta[depth_idx][idx][0]})
for idx in range(0, len(qubits_idx) - 1):
gate_history.append({'gate': 'cnot', 'which_qubits': [qubits_idx[idx], qubits_idx[idx + 1]], 'theta': None})
for idx, qubit_idx in enumerate(qubits_idx):
gate_history.append({'gate': 'rz', 'which_qubits': qubit_idx, 'theta': self.theta[depth_idx][idx][1]})
for idx in range(0, len(qubits_idx) - 1):
gate_history.append({'gate': 'cnot', 'which_qubits': [qubits_idx[idx], qubits_idx[idx + 1]], 'theta': None})
self.gate_history = gate_history
class RealEntangledLayer(Gate): class RealEntangledLayer(Gate):
...@@ -163,6 +200,7 @@ class RealEntangledLayer(Gate): ...@@ -163,6 +200,7 @@ class RealEntangledLayer(Gate):
default_initializer=initializer default_initializer=initializer
) )
self.add_parameter('theta', theta) self.add_parameter('theta', theta)
self.gate_name = 'REntLayer'
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
for depth_idx in range(0, self.depth): for depth_idx in range(0, self.depth):
...@@ -174,6 +212,21 @@ class RealEntangledLayer(Gate): ...@@ -174,6 +212,21 @@ class RealEntangledLayer(Gate):
state, [self.qubits_idx[qubit_idx], self.qubits_idx[(qubit_idx + 1) % len(self.qubits_idx)]], state, [self.qubits_idx[qubit_idx], self.qubits_idx[(qubit_idx + 1) % len(self.qubits_idx)]],
self.dtype, self.backend) self.dtype, self.backend)
return state return state
def gate_history_generation(self):
gate_history = []
qubits_idx = self.qubits_idx
for depth_idx in range(0, self.depth):
for idx, qubit_idx in enumerate(qubits_idx):
gate_history.append({'gate': 'ry', 'which_qubits': qubit_idx, 'theta': self.theta[depth_idx][idx]})
for idx in range(0, len(qubits_idx)):
gate_history.append({
'gate': 'cnot',
'which_qubits': [qubits_idx[idx], qubits_idx[(idx + 1) % len(qubits_idx)]],
'theta': None
})
self.gate_history = gate_history
class ComplexEntangledLayer(Gate): class ComplexEntangledLayer(Gate):
...@@ -203,6 +256,7 @@ class ComplexEntangledLayer(Gate): ...@@ -203,6 +256,7 @@ class ComplexEntangledLayer(Gate):
default_initializer=initializer default_initializer=initializer
) )
self.add_parameter('theta', theta) self.add_parameter('theta', theta)
self.gate_name = 'CEntLayer'
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
for depth_idx in range(0, self.depth): for depth_idx in range(0, self.depth):
...@@ -214,6 +268,21 @@ class ComplexEntangledLayer(Gate): ...@@ -214,6 +268,21 @@ class ComplexEntangledLayer(Gate):
state, [self.qubits_idx[qubit_idx], self.qubits_idx[(qubit_idx + 1) % len(self.qubits_idx)]], state, [self.qubits_idx[qubit_idx], self.qubits_idx[(qubit_idx + 1) % len(self.qubits_idx)]],
self.dtype, self.backend) self.dtype, self.backend)
return state return state
def gate_history_generation(self):
gate_history = []
qubits_idx = self.qubits_idx
for depth_idx in range(0, self.depth):
for idx, qubit_idx in enumerate(qubits_idx):
gate_history.append({'gate': 'u', 'which_qubits': qubit_idx, 'theta': self.theta[depth_idx][idx]})
for idx in range(0, len(qubits_idx)):
gate_history.append({
'gate': 'cnot',
'which_qubits': [qubits_idx[idx], qubits_idx[(idx + 1) % len(qubits_idx)]],
'theta': None
})
self.gate_history = gate_history
class RealBlockLayer(Gate): class RealBlockLayer(Gate):
...@@ -244,22 +313,34 @@ class RealBlockLayer(Gate): ...@@ -244,22 +313,34 @@ class RealBlockLayer(Gate):
default_initializer=initializer default_initializer=initializer
) )
self.add_parameter('theta', theta) self.add_parameter('theta', theta)
self.gate_name = 'RBLayer'
def __add_real_block(self, theta: paddle.Tensor, position: List[int]) -> None: def __add_real_block(self, theta: paddle.Tensor, position: List[int]) -> None:
assert len(theta) == 4, 'the length of theta is not right' assert len(theta) == 4, 'the length of theta is not right'
position[0] = self.qubits_idx[position[0]] position[0] = self.qubits_idx[position[0]]
position[1] = self.qubits_idx[position[1]] position[1] = self.qubits_idx[position[1]]
if self.count_history:
gate_history = []
gate_history.append({'gate': 'ry', 'which_qubits': position[0], 'theta': theta[0]})
gate_history.append({'gate': 'ry', 'which_qubits': position[1], 'theta': theta[1]})
gate_history.append({'gate': 'cnot', 'which_qubits': [position[0], position[1]], 'theta': None})
gate_history.append({'gate': 'ry', 'which_qubits': position[0], 'theta': theta[2]})
gate_history.append({'gate': 'ry', 'which_qubits': position[1], 'theta': theta[3]})
self.gate_history.extend(gate_history)
else:
state = functional.ry(self.state, theta[0], position[0], self.dtype, self.backend)
state = functional.ry(state, theta[1], position[1], self.dtype, self.backend)
state = functional.ry(self.state, theta[0], position[0], self.dtype, self.backend) state = functional.cnot(state, [position[0], position[1]], self.dtype, self.backend)
state = functional.ry(state, theta[1], position[1], self.dtype, self.backend)
state = functional.cnot(state, [position[0], position[1]], self.dtype, self.backend)
state = functional.ry(state, theta[2], position[0], self.dtype, self.backend) state = functional.ry(state, theta[2], position[0], self.dtype, self.backend)
state = functional.ry(state, theta[3], position[1], self.dtype, self.backend) state = functional.ry(state, theta[3], position[1], self.dtype, self.backend)
self.state = state self.state = state
def __add_real_layer(self, theta: paddle.Tensor, position: List) -> None: def __add_real_layer(self, theta: paddle.Tensor, position: List) -> None:
assert theta.shape[1] == 4 and theta.shape[0] == (position[1] - position[0] + 1) / 2, \ assert theta.shape[1] == 4 and theta.shape[0] == (position[1] - position[0] + 1) / 2, \
...@@ -282,6 +363,21 @@ class RealBlockLayer(Gate): ...@@ -282,6 +363,21 @@ class RealBlockLayer(Gate):
self.__add_real_layer(self.theta[depth_idx, int((n - 1) / 2):], [1, n - 1]) self.__add_real_layer(self.theta[depth_idx, int((n - 1) / 2):], [1, n - 1])
return self.state return self.state
def gate_history_generation(self):
self.gate_history = []
self.count_history = True
n = len(self.qubits_idx)
if n % 2 == 0:
for depth_idx in range(self.depth):
self.__add_real_layer(self.theta[depth_idx, :int(n / 2)], [0, n - 1])
self.__add_real_layer(self.theta[depth_idx, int(n / 2):], [1, n - 2]) if n > 2 else None
else:
for depth_idx in range(self.depth):
self.__add_real_layer(self.theta[depth_idx, :int((n - 1) / 2)], [0, n - 2])
self.__add_real_layer(self.theta[depth_idx, int((n - 1) / 2):], [1, n - 1])
self.count_history = False
class ComplexBlockLayer(Gate): class ComplexBlockLayer(Gate):
...@@ -295,8 +391,7 @@ class ComplexBlockLayer(Gate): ...@@ -295,8 +391,7 @@ class ComplexBlockLayer(Gate):
num_qubits: Total number of qubits. Defaults to ``None``. num_qubits: Total number of qubits. Defaults to ``None``.
depth: Number of layers. Defaults to ``1``. depth: Number of layers. Defaults to ``1``.
""" """
def __init__(self, qubits_idx: Optional[Union[Iterable[int], str]] = 'full', def __init__(self, qubits_idx: Union[Iterable[int], str] = 'full', num_qubits: int = None, depth: int = 1):
num_qubits: Optional[int] = None, depth: Optional[int] = 1) -> None:
super().__init__(depth) super().__init__(depth)
self.qubits_idx = qubits_idx_filter(qubits_idx, num_qubits) self.qubits_idx = qubits_idx_filter(qubits_idx, num_qubits)
assert len(self.qubits_idx) > 1, 'you need at least 2 qubits' assert len(self.qubits_idx) > 1, 'you need at least 2 qubits'
...@@ -311,23 +406,36 @@ class ComplexBlockLayer(Gate): ...@@ -311,23 +406,36 @@ class ComplexBlockLayer(Gate):
default_initializer=initializer default_initializer=initializer
) )
self.add_parameter('theta', theta) self.add_parameter('theta', theta)
self.state = None self.gate_name = 'CBLayer'
def __add_complex_block(self, theta: paddle.Tensor, position: List[int]) -> None: def __add_complex_block(self, theta: paddle.Tensor, position: List[int]) -> None:
assert len(theta) == 12, 'the length of theta is not right' assert len(theta) == 12, 'the length of theta is not right'
position[0] = self.qubits_idx[position[0]] position[0] = self.qubits_idx[position[0]]
position[1] = self.qubits_idx[position[1]] position[1] = self.qubits_idx[position[1]]
if self.count_history:
gate_history = []
gate_history.append({'gate': 'u', 'which_qubits': position[0], 'theta': theta[0:3]})
gate_history.append({'gate': 'u', 'which_qubits': position[1], 'theta': theta[3:6]})
gate_history.append({'gate': 'cnot', 'which_qubits': [position[0], position[1]], 'theta': None})
gate_history.append({'gate': 'u', 'which_qubits': position[0], 'theta': theta[6:9]})
gate_history.append({'gate': 'u', 'which_qubits': position[1], 'theta': theta[9:12]})
self.gate_history.extend(gate_history)
else:
state = functional.u3(self.state, [theta[0], theta[1], theta[2]], position[0], self.dtype, self.backend) state = functional.u3(self.state, theta[0:3], position[0], self.dtype, self.backend)
state = functional.u3(state, [theta[3], theta[4], theta[5]], position[1], self.dtype, self.backend) state = functional.u3(state, theta[3:6], position[1], self.dtype, self.backend)
state = functional.cnot(state, [position[0], position[1]], self.dtype, self.backend)
state = functional.u3(state, [theta[6], theta[7], theta[8]], position[0], self.dtype, self.backend) state = functional.cnot(state, [position[0], position[1]], self.dtype, self.backend)
state = functional.u3(state, [theta[9], theta[10], theta[11]], position[1], self.dtype, self.backend)
self.state = state state = functional.u3(state, theta[6:9], position[0], self.dtype, self.backend)
state = functional.u3(state, theta[9:12], position[1], self.dtype, self.backend)
self.state = state
def __add_complex_layer(self, theta: paddle.Tensor, position: List[int]) -> None: def __add_complex_layer(self, theta: paddle.Tensor, position: List[int]) -> None:
assert theta.shape[1] == 12 and theta.shape[0] == (position[1] - position[0] + 1) / 2, \ assert theta.shape[1] == 12 and theta.shape[0] == (position[1] - position[0] + 1) / 2, \
...@@ -351,37 +459,126 @@ class ComplexBlockLayer(Gate): ...@@ -351,37 +459,126 @@ class ComplexBlockLayer(Gate):
self.__add_complex_layer(self.theta[depth_idx, (num_acted_qubits - 1) // 2:], [1, num_acted_qubits - 1]) self.__add_complex_layer(self.theta[depth_idx, (num_acted_qubits - 1) // 2:], [1, num_acted_qubits - 1])
return self.state return self.state
def gate_history_generation(self):
self.gate_history = []
self.count_history = True
n = len(self.qubits_idx)
if n % 2 == 0:
for depth_idx in range(self.depth):
self.__add_complex_layer(self.theta[depth_idx, :int(n / 2)], [0, n - 1])
self.__add_complex_layer(self.theta[depth_idx, int(n / 2):], [1, n - 2]) if n > 2 else None
else:
for depth_idx in range(self.depth):
self.__add_complex_layer(self.theta[depth_idx, :int((n - 1) / 2)], [0, n - 2])
self.__add_complex_layer(self.theta[depth_idx, int((n - 1) / 2):], [1, n - 1])
self.count_history = False
class QAOALayer(Gate): class QAOALayer(Gate):
r""" QAOA driving layers
Note:
this layer only works for MAXCUT problem
Args:
edges: edges of the graph
nodes: nodes of the graph
depth: depth of layer
"""
# TODO: only maxcut now # TODO: only maxcut now
def __init__( def __init__(
self, edges: Iterable, nodes: Iterable, depth: int = 1 self, edges: Iterable, nodes: Iterable, depth: int = 1
): ):
super().__init__(depth) super().__init__(depth)
float_dtype = _get_float_dtype(self.dtype) float_dtype = _get_float_dtype(self.dtype)
initializer = paddle.nn.initializer.Uniform(low=0, high=2 * math.pi)
self.edges = edges self.edges = edges
self.nodes = nodes self.nodes = nodes
gamma = self.create_parameter(
shape=[self.depth], theta = self.create_parameter(
shape=[self.depth * 2],
dtype=float_dtype, dtype=float_dtype,
default_initializer=initializer default_initializer=paddle.nn.initializer.Uniform(low=0, high=2 * math.pi)
) )
beta = self.create_parameter( self.add_parameter('theta', theta)
shape=[self.depth], self.gate_name = 'QAOALayer'
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
gamma = self.theta[:self.depth]
beta = self.theta[self.depth:]
for depth_idx in range(0, self.depth):
for node0, node1 in self.edges:
state = functional.cnot(state, [node0, node1], self.dtype, self.backend)
state = functional.rz(state, gamma[depth_idx], node1, self.dtype, self.backend)
state = functional.cnot(state, [node0, node1], self.dtype, self.backend)
for node in self.nodes:
state = functional.rx(state, beta[depth_idx], node, self.dtype, self.backend)
return state
def gate_history_generation(self):
gate_history = []
gamma = self.theta[:self.depth]
beta = self.theta[self.depth:]
for depth_idx in range(0, self.depth):
for node0, node1 in self.edges:
gate_history.append({'gate': 'cnot', 'which_qubits': [node0, node1], 'theta': None})
gate_history.append({'gate': 'rz', 'which_qubits': node1, 'theta': gamma[depth_idx]})
gate_history.append({'gate': 'cnot', 'which_qubits': [node0, node1], 'theta': None})
for node in self.nodes:
gate_history.append({'gate': 'rx', 'which_qubits': node, 'theta': beta[depth_idx]})
class QAOALayerWeighted(Gate):
r""" QAOA driving layers with weights
Args:
edges: edges of the graph with weights
nodes: nodes of the graph with weights
depth: depth of layer
"""
def __init__(
self, edges: Dict[Tuple[int, int], float], nodes: Dict[int, float], depth: int = 1
) -> None:
super().__init__(depth)
float_dtype = _get_float_dtype(self.dtype)
self.edges = edges.keys()
self.nodes = nodes.keys()
self.edges_full = edges
self.nodes_full = nodes
theta = self.create_parameter(
shape=[self.depth * 2],
dtype=float_dtype, dtype=float_dtype,
default_initializer=initializer default_initializer=paddle.nn.initializer.Uniform(low=0, high=2 * math.pi)
) )
self.add_parameter('gamma', gamma) self.add_parameter('theta', theta)
self.add_parameter('beta', beta) self.gate_history_generation()
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
gamma = self.theta[:self.depth]
beta = self.theta[self.depth:]
for depth_idx in range(0, self.depth): for depth_idx in range(0, self.depth):
for node0, node1 in self.edges: for node0, node1 in self.edges:
state = functional.cnot(state, [node0, node1], self.dtype, self.backend) state = functional.cnot(state, [node0, node1], self.dtype, self.backend)
state = functional.rz(state, self.gamma[depth_idx], node1, self.dtype, self.backend) state = functional.rz(state, gamma[depth_idx] * self.edges_full[(node0, node1)], node1, self.dtype, self.backend)
state = functional.cnot(state, [node0, node1], self.dtype, self.backend) state = functional.cnot(state, [node0, node1], self.dtype, self.backend)
for node in self.nodes: for node in self.nodes:
state = functional.rx(state, self.beta[depth_idx], node, self.dtype, self.backend) state = functional.rz(state, gamma[depth_idx] * self.nodes_full[node], node, self.dtype, self.backend)
state = functional.rx(state, beta[depth_idx], node, self.dtype, self.backend)
return state return state
def gate_history_generation(self):
gate_history = []
gamma = self.theta[:self.depth]
beta = self.theta[self.depth:]
for depth_idx in range(0, self.depth):
for node0, node1 in self.edges:
gate_history.append({'gate': 'cnot', 'which_qubits': [node0, node1], 'theta': None})
gate_history.append({'gate': 'rz', 'which_qubits': node1, 'theta': gamma[depth_idx]})
gate_history.append({'gate': 'cnot', 'which_qubits': [node0, node1], 'theta': None})
for node in self.nodes:
gate_history.append({'gate': 'rx', 'which_qubits': node, 'theta': beta[depth_idx]})
\ No newline at end of file
...@@ -22,7 +22,7 @@ import math ...@@ -22,7 +22,7 @@ import math
import paddle import paddle
import paddle_quantum import paddle_quantum
from . import functional from . import functional
from .base import Gate from .base import Gate, ParamGate
from ..backend import Backend from ..backend import Backend
from ..intrinsic import _format_qubits_idx, _get_float_dtype from ..intrinsic import _format_qubits_idx, _get_float_dtype
from typing import Optional, Union, Iterable from typing import Optional, Union, Iterable
...@@ -53,7 +53,8 @@ class CNOT(Gate): ...@@ -53,7 +53,8 @@ class CNOT(Gate):
""" """
def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle', num_qubits: Optional[int] = None, depth: Optional[int] = 1): def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle', num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=False, num_acted_qubits=2) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2)
self.gate_name = 'cnot'
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -80,7 +81,8 @@ class CX(Gate): ...@@ -80,7 +81,8 @@ class CX(Gate):
def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle', def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle',
num_qubits: Optional[int] = None, depth: Optional[int] = 1): num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=False, num_acted_qubits=2) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2)
self.gate_name = 'cnot'
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -122,7 +124,8 @@ class CY(Gate): ...@@ -122,7 +124,8 @@ class CY(Gate):
def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle', def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle',
num_qubits: Optional[int] = None, depth: Optional[int] = 1): num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=False, num_acted_qubits=2) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2)
self.gate_name = 'cy'
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -164,7 +167,8 @@ class CZ(Gate): ...@@ -164,7 +167,8 @@ class CZ(Gate):
def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle', def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle',
num_qubits: Optional[int] = None, depth: Optional[int] = 1): num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=False, num_acted_qubits=2) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2)
self.gate_name = 'cz'
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -176,7 +180,7 @@ class CZ(Gate): ...@@ -176,7 +180,7 @@ class CZ(Gate):
return state return state
for _ in range(0, self.depth): for _ in range(0, self.depth):
for qubits_idx in self.qubits_idx: for qubits_idx in self.qubits_idx:
state = functional.cz(state, qubits_idx, self.dtype, self.backend) state = functional.cz(state, qubits_idx, self.dtype, self.backend)
return state return state
...@@ -205,7 +209,8 @@ class SWAP(Gate): ...@@ -205,7 +209,8 @@ class SWAP(Gate):
def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle', def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle',
num_qubits: Optional[int] = None, depth: Optional[int] = 1): num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=False, num_acted_qubits=2) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2)
self.gate_name = 'swap'
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -221,7 +226,7 @@ class SWAP(Gate): ...@@ -221,7 +226,7 @@ class SWAP(Gate):
return state return state
class CP(Gate): class CP(ParamGate):
r"""A collection of controlled P gates. r"""A collection of controlled P gates.
For a 2-qubit quantum circuit, when ``qubits_idx`` is ``[0, 1]``, the matrix form of such a gate is: For a 2-qubit quantum circuit, when ``qubits_idx`` is ``[0, 1]``, the matrix form of such a gate is:
...@@ -250,29 +255,12 @@ class CP(Gate): ...@@ -250,29 +255,12 @@ class CP(Gate):
param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False
): ):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=False, num_acted_qubits=2) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2)
self.param_sharing = param_sharing self.param_sharing = param_sharing
float_dtype = _get_float_dtype(self.dtype)
param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)]
if param_sharing: self.theta_generation(param, param_shape)
param_shape = [1] self.gate_name = 'cp'
else:
param_shape = len(self.qubits_idx)
param_shape = [self.depth, param_shape]
if param is None:
initializer = paddle.nn.initializer.Uniform(low=0, high=2 * math.pi)
else:
if isinstance(param, float):
initializer = paddle.nn.initializer.Constant(param)
elif isinstance(param, paddle.Tensor):
initializer = paddle.nn.initializer.Assign(param.reshape(param_shape))
else:
raise ValueError("The param must be paddle.Tensor or float.")
theta = self.create_parameter(
shape=param_shape, dtype=float_dtype,
default_initializer=initializer
)
self.add_parameter('theta', theta)
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -288,7 +276,7 @@ class CP(Gate): ...@@ -288,7 +276,7 @@ class CP(Gate):
return state return state
class CRX(Gate): class CRX(ParamGate):
r"""A collection of controlled rotation gates about the x-axis. r"""A collection of controlled rotation gates about the x-axis.
For a 2-qubit quantum circuit, when ``qubits_idx`` is ``[0, 1]``, the matrix form of such a gate is: For a 2-qubit quantum circuit, when ``qubits_idx`` is ``[0, 1]``, the matrix form of such a gate is:
...@@ -321,29 +309,12 @@ class CRX(Gate): ...@@ -321,29 +309,12 @@ class CRX(Gate):
param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False
): ):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=False, num_acted_qubits=2) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2)
self.param_sharing = param_sharing self.param_sharing = param_sharing
float_dtype = _get_float_dtype(self.dtype)
param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)]
if param_sharing: self.theta_generation(param, param_shape)
param_shape = [1] self.gate_name = 'crx'
else:
param_shape = len(self.qubits_idx)
param_shape = [self.depth, param_shape]
if param is None:
initializer = paddle.nn.initializer.Uniform(low=0, high=2 * math.pi)
else:
if isinstance(param, float):
initializer = paddle.nn.initializer.Constant(param)
elif isinstance(param, paddle.Tensor):
initializer = paddle.nn.initializer.Assign(param.reshape(param_shape))
else:
raise ValueError("The param must be paddle.Tensor or float.")
theta = self.create_parameter(
shape=param_shape, dtype=float_dtype,
default_initializer=initializer
)
self.add_parameter('theta', theta)
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -375,7 +346,7 @@ class CRX(Gate): ...@@ -375,7 +346,7 @@ class CRX(Gate):
return state return state
class CRY(Gate): class CRY(ParamGate):
r"""A collection of controlled rotation gates about the y-axis. r"""A collection of controlled rotation gates about the y-axis.
For a 2-qubit quantum circuit, when ``qubits_idx`` is ``[0, 1]``, the matrix form of such a gate is: For a 2-qubit quantum circuit, when ``qubits_idx`` is ``[0, 1]``, the matrix form of such a gate is:
...@@ -408,29 +379,12 @@ class CRY(Gate): ...@@ -408,29 +379,12 @@ class CRY(Gate):
param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False
): ):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=False, num_acted_qubits=2) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2)
self.param_sharing = param_sharing self.param_sharing = param_sharing
float_dtype = _get_float_dtype(self.dtype)
param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)]
if param_sharing: self.theta_generation(param, param_shape)
param_shape = [1] self.gate_name = 'cry'
else:
param_shape = len(self.qubits_idx)
param_shape = [self.depth, param_shape]
if param is None:
initializer = paddle.nn.initializer.Uniform(low=0, high=2 * math.pi)
else:
if isinstance(param, float):
initializer = paddle.nn.initializer.Constant(param)
elif isinstance(param, paddle.Tensor):
initializer = paddle.nn.initializer.Assign(param.reshape(param_shape))
else:
raise ValueError("The param must be paddle.Tensor or float.")
theta = self.create_parameter(
shape=param_shape, dtype=float_dtype,
default_initializer=initializer
)
self.add_parameter('theta', theta)
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -462,7 +416,7 @@ class CRY(Gate): ...@@ -462,7 +416,7 @@ class CRY(Gate):
return state return state
class CRZ(Gate): class CRZ(ParamGate):
r"""A collection of controlled rotation gates about the z-axis. r"""A collection of controlled rotation gates about the z-axis.
For a 2-qubit quantum circuit, when ``qubits_idx`` is ``[0, 1]``, the matrix form of such a gate is: For a 2-qubit quantum circuit, when ``qubits_idx`` is ``[0, 1]``, the matrix form of such a gate is:
...@@ -495,29 +449,12 @@ class CRZ(Gate): ...@@ -495,29 +449,12 @@ class CRZ(Gate):
param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False
): ):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=False, num_acted_qubits=2) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2)
self.param_sharing = param_sharing self.param_sharing = param_sharing
float_dtype = _get_float_dtype(self.dtype)
param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)]
if param_sharing: self.theta_generation(param, param_shape)
param_shape = [1] self.gate_name = 'crz'
else:
param_shape = len(self.qubits_idx)
param_shape = [self.depth, param_shape]
if param is None:
initializer = paddle.nn.initializer.Uniform(low=0, high=2 * math.pi)
else:
if isinstance(param, float):
initializer = paddle.nn.initializer.Constant(param)
elif isinstance(param, paddle.Tensor):
initializer = paddle.nn.initializer.Assign(param.reshape(param_shape))
else:
raise ValueError("The param must be paddle.Tensor or float.")
theta = self.create_parameter(
shape=param_shape, dtype=float_dtype,
default_initializer=initializer
)
self.add_parameter('theta', theta)
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -549,7 +486,7 @@ class CRZ(Gate): ...@@ -549,7 +486,7 @@ class CRZ(Gate):
return state return state
class CU(Gate): class CU(ParamGate):
r"""A collection of controlled single-qubit rotation gates. r"""A collection of controlled single-qubit rotation gates.
For a 2-qubit quantum circuit, when ``qubits_idx`` is ``[0, 1]``, the matrix form of such a gate is: For a 2-qubit quantum circuit, when ``qubits_idx`` is ``[0, 1]``, the matrix form of such a gate is:
...@@ -582,29 +519,15 @@ class CU(Gate): ...@@ -582,29 +519,15 @@ class CU(Gate):
param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False
): ):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=False, num_acted_qubits=2) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2)
self.param_sharing = param_sharing self.param_sharing = param_sharing
float_dtype = _get_float_dtype(self.dtype)
if param_sharing: if param_sharing:
param_shape = [3] param_shape = [depth, 3]
else:
param_shape = [len(self.qubits_idx), 3]
param_shape = [self.depth] + param_shape
if param is None:
initializer = paddle.nn.initializer.Uniform(low=0, high=2 * math.pi)
else: else:
if isinstance(param, float): param_shape = [depth, len(self.qubits_idx), 3]
initializer = paddle.nn.initializer.Constant(param) self.theta_generation(param, param_shape)
elif isinstance(param, paddle.Tensor): self.gate_name = 'cu'
initializer = paddle.nn.initializer.Assign(param.reshape(param_shape))
else:
raise ValueError("The param must be paddle.Tensor or float.")
theta = self.create_parameter(
shape=param_shape, dtype=float_dtype,
default_initializer=initializer
)
self.add_parameter('theta', theta)
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -636,7 +559,7 @@ class CU(Gate): ...@@ -636,7 +559,7 @@ class CU(Gate):
return state return state
class RXX(Gate): class RXX(ParamGate):
r"""A collection of RXX gates. r"""A collection of RXX gates.
The matrix form of such a gate is: The matrix form of such a gate is:
...@@ -668,29 +591,12 @@ class RXX(Gate): ...@@ -668,29 +591,12 @@ class RXX(Gate):
param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False
): ):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=False, num_acted_qubits=2) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2)
self.param_sharing = param_sharing self.param_sharing = param_sharing
float_dtype = _get_float_dtype(self.dtype)
param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)]
if param_sharing: self.theta_generation(param, param_shape)
param_shape = [1] self.gate_name = 'rxx'
else:
param_shape = len(self.qubits_idx)
param_shape = [self.depth, param_shape]
if param is None:
initializer = paddle.nn.initializer.Uniform(low=0, high=2 * math.pi)
else:
if isinstance(param, float):
initializer = paddle.nn.initializer.Constant(param)
elif isinstance(param, paddle.Tensor):
initializer = paddle.nn.initializer.Assign(param.reshape(param_shape))
else:
raise ValueError("The param must be paddle.Tensor or float.")
theta = self.create_parameter(
shape=param_shape, dtype=float_dtype,
default_initializer=initializer
)
self.add_parameter('theta', theta)
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -706,7 +612,7 @@ class RXX(Gate): ...@@ -706,7 +612,7 @@ class RXX(Gate):
return state return state
class RYY(Gate): class RYY(ParamGate):
r"""A collection of RYY gates. r"""A collection of RYY gates.
The matrix form of such a gate is: The matrix form of such a gate is:
...@@ -738,29 +644,12 @@ class RYY(Gate): ...@@ -738,29 +644,12 @@ class RYY(Gate):
param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False
): ):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=False, num_acted_qubits=2) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2)
self.param_sharing = param_sharing self.param_sharing = param_sharing
float_dtype = _get_float_dtype(self.dtype)
param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)]
if param_sharing: self.theta_generation(param, param_shape)
param_shape = [1] self.gate_name = 'ryy'
else:
param_shape = len(self.qubits_idx)
param_shape = [self.depth, param_shape]
if param is None:
initializer = paddle.nn.initializer.Uniform(low=0, high=2 * math.pi)
else:
if isinstance(param, float):
initializer = paddle.nn.initializer.Constant(param)
elif isinstance(param, paddle.Tensor):
initializer = paddle.nn.initializer.Assign(param.reshape(param_shape))
else:
raise ValueError("The param must be paddle.Tensor or float.")
theta = self.create_parameter(
shape=param_shape, dtype=float_dtype,
default_initializer=initializer
)
self.add_parameter('theta', theta)
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -776,7 +665,7 @@ class RYY(Gate): ...@@ -776,7 +665,7 @@ class RYY(Gate):
return state return state
class RZZ(Gate): class RZZ(ParamGate):
r"""A collection of RZZ gates. r"""A collection of RZZ gates.
The matrix form of such a gate is: The matrix form of such a gate is:
...@@ -808,29 +697,12 @@ class RZZ(Gate): ...@@ -808,29 +697,12 @@ class RZZ(Gate):
param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False
): ):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=False, num_acted_qubits=2) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2)
self.param_sharing = param_sharing self.param_sharing = param_sharing
float_dtype = _get_float_dtype(self.dtype)
param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)]
if param_sharing: self.theta_generation(param, param_shape)
param_shape = [1] self.gate_name = 'rzz'
else:
param_shape = len(self.qubits_idx)
param_shape = [self.depth, param_shape]
if param is None:
initializer = paddle.nn.initializer.Uniform(low=0, high=2 * math.pi)
else:
if isinstance(param, float):
initializer = paddle.nn.initializer.Constant(param)
elif isinstance(param, paddle.Tensor):
initializer = paddle.nn.initializer.Assign(param.reshape(param_shape))
else:
raise ValueError("The param must be paddle.Tensor or float.")
theta = self.create_parameter(
shape=param_shape, dtype=float_dtype,
default_initializer=initializer
)
self.add_parameter('theta', theta)
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -870,14 +742,15 @@ class MS(Gate): ...@@ -870,14 +742,15 @@ class MS(Gate):
""" """
def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle', num_qubits: Optional[int] = None, depth: Optional[int] = 1): def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle', num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=False, num_acted_qubits=2) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2)
self.gate_name = 'ms'
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
raise NotImplementedError raise NotImplementedError
for _ in range(0, self.depth): for _ in range(0, self.depth):
for qubits_idx in self.qubits_idx: for qubits_idx in self.qubits_idx:
functional.ms(state, qubits_idx, self.dtype, self.backend) state = functional.ms(state, qubits_idx, self.dtype, self.backend)
return state return state
...@@ -909,7 +782,8 @@ class CSWAP(Gate): ...@@ -909,7 +782,8 @@ class CSWAP(Gate):
""" """
def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle', num_qubits: Optional[int] = None, depth: Optional[int] = 1): def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle', num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=False, num_acted_qubits=3) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=3)
self.gate_name = 'cswap'
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -921,7 +795,7 @@ class CSWAP(Gate): ...@@ -921,7 +795,7 @@ class CSWAP(Gate):
return state return state
for _ in range(0, self.depth): for _ in range(0, self.depth):
for qubits_idx in self.qubits_idx: for qubits_idx in self.qubits_idx:
functional.cswap(state, qubits_idx, self.dtype, self.backend) state = functional.cswap(state, qubits_idx, self.dtype, self.backend)
return state return state
...@@ -952,7 +826,8 @@ class Toffoli(Gate): ...@@ -952,7 +826,8 @@ class Toffoli(Gate):
""" """
def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle', num_qubits: Optional[int] = None, depth: Optional[int] = 1): def __init__(self, qubits_idx: Optional[Union[Iterable, str]] = 'cycle', num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=False, num_acted_qubits=3) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=3)
self.gate_name = 'ccx'
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -964,11 +839,11 @@ class Toffoli(Gate): ...@@ -964,11 +839,11 @@ class Toffoli(Gate):
return state return state
for _ in range(0, self.depth): for _ in range(0, self.depth):
for qubits_idx in self.qubits_idx: for qubits_idx in self.qubits_idx:
functional.toffoli(state, qubits_idx, self.dtype, self.backend) state = functional.toffoli(state, qubits_idx, self.dtype, self.backend)
return state return state
class UniversalTwoQubits(Gate): class UniversalTwoQubits(ParamGate):
r"""A collection of universal two-qubit gates. One of such a gate requires 15 parameters. r"""A collection of universal two-qubit gates. One of such a gate requires 15 parameters.
Args: Args:
...@@ -986,29 +861,15 @@ class UniversalTwoQubits(Gate): ...@@ -986,29 +861,15 @@ class UniversalTwoQubits(Gate):
param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False
): ):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=False, num_acted_qubits=2) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=2)
self.param_sharing = param_sharing self.param_sharing = param_sharing
float_dtype = _get_float_dtype(self.dtype)
if param_sharing: if param_sharing:
param_shape = [15] param_shape = [depth, 15]
else: else:
param_shape = [len(self.qubits_idx), 15] param_shape = [depth, len(self.qubits_idx), 15]
param_shape = [self.depth] + param_shape self.theta_generation(param, param_shape)
if param is None: self.gate_name = 'uni2'
initializer = paddle.nn.initializer.Uniform(low=0, high=2 * math.pi)
else:
if isinstance(param, float):
initializer = paddle.nn.initializer.Constant(param)
elif isinstance(param, paddle.Tensor):
initializer = paddle.nn.initializer.Assign(param.reshape(param_shape))
else:
raise ValueError("The param must be paddle.Tensor or float.")
theta = self.create_parameter(
shape=param_shape, dtype=float_dtype,
default_initializer=initializer
)
self.add_parameter('theta', theta)
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -1025,7 +886,7 @@ class UniversalTwoQubits(Gate): ...@@ -1025,7 +886,7 @@ class UniversalTwoQubits(Gate):
return state return state
class UniversalThreeQubits(Gate): class UniversalThreeQubits(ParamGate):
r"""A collection of universal three-qubit gates. One of such a gate requires 81 parameters. r"""A collection of universal three-qubit gates. One of such a gate requires 81 parameters.
Args: Args:
...@@ -1043,29 +904,15 @@ class UniversalThreeQubits(Gate): ...@@ -1043,29 +904,15 @@ class UniversalThreeQubits(Gate):
param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False
): ):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=False, num_acted_qubits=3) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, num_acted_qubits=3)
self.param_sharing = param_sharing self.param_sharing = param_sharing
float_dtype = _get_float_dtype(self.dtype)
if param_sharing: if param_sharing:
param_shape = [81] param_shape = [depth, 81]
else: else:
param_shape = [len(self.qubits_idx), 81] param_shape = [depth, len(self.qubits_idx), 81]
param_shape = [self.depth] + param_shape self.theta_generation(param, param_shape)
if param is None: self.gate_name = 'uni3'
initializer = paddle.nn.initializer.Uniform(low=0, high=2 * math.pi)
else:
if isinstance(param, float):
initializer = paddle.nn.initializer.Constant(param)
elif isinstance(param, paddle.Tensor):
initializer = paddle.nn.initializer.Assign(param.reshape(param_shape))
else:
raise ValueError("The param must be paddle.Tensor or float.")
theta = self.create_parameter(
shape=param_shape, dtype=float_dtype,
default_initializer=initializer
)
self.add_parameter('theta', theta)
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
......
...@@ -23,10 +23,10 @@ import numpy as np ...@@ -23,10 +23,10 @@ import numpy as np
import paddle.nn import paddle.nn
import paddle_quantum import paddle_quantum
from . import functional from . import functional
from .base import Gate from .base import Gate, ParamGate
from ..backend import Backend from ..backend import Backend
from paddle_quantum.intrinsic import _format_qubits_idx, _get_float_dtype from paddle_quantum.intrinsic import _format_qubits_idx, _get_float_dtype
from typing import Optional, Union, Iterable from typing import Optional, List, Union, Iterable
class H(Gate): class H(Gate):
...@@ -49,7 +49,8 @@ class H(Gate): ...@@ -49,7 +49,8 @@ class H(Gate):
""" """
def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1): def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.gate_name = 'h'
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -85,7 +86,8 @@ class S(Gate): ...@@ -85,7 +86,8 @@ class S(Gate):
""" """
def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1): def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.gate_name = 's'
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -121,7 +123,8 @@ class T(Gate): ...@@ -121,7 +123,8 @@ class T(Gate):
""" """
def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1): def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.gate_name = 't'
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -156,8 +159,9 @@ class X(Gate): ...@@ -156,8 +159,9 @@ class X(Gate):
""" """
def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1): def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.gate_name = 'x'
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
state.gate_history.append({ state.gate_history.append({
...@@ -191,7 +195,8 @@ class Y(Gate): ...@@ -191,7 +195,8 @@ class Y(Gate):
""" """
def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1): def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.gate_name = 'y'
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -226,7 +231,8 @@ class Z(Gate): ...@@ -226,7 +231,8 @@ class Z(Gate):
""" """
def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1): def __init__(self, qubits_idx: Optional[Union[Iterable, int, str]] = 'full', num_qubits: Optional[int] = None, depth: Optional[int] = 1):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.gate_name = 'z'
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -242,7 +248,7 @@ class Z(Gate): ...@@ -242,7 +248,7 @@ class Z(Gate):
return state return state
class P(Gate): class P(ParamGate):
r"""A collection of single-qubit P gates. r"""A collection of single-qubit P gates.
The matrix form of such a gate is: The matrix form of such a gate is:
...@@ -269,29 +275,12 @@ class P(Gate): ...@@ -269,29 +275,12 @@ class P(Gate):
param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False
): ):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.param_sharing = param_sharing self.param_sharing = param_sharing
float_dtype = _get_float_dtype(self.dtype)
param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)]
if param_sharing: self.theta_generation(param, param_shape)
param_shape = [1] self.gate_name = 'p'
else:
param_shape = list(np.shape(self.qubits_idx))
param_shape = [self.depth] + param_shape
if param is None:
initializer = paddle.nn.initializer.Uniform(low=0, high=2 * math.pi)
else:
if isinstance(param, float):
initializer = paddle.nn.initializer.Constant(param)
elif isinstance(param, paddle.Tensor):
initializer = paddle.nn.initializer.Assign(param.reshape(param_shape))
else:
raise ValueError("The param must be paddle.Tensor or float.")
theta = self.create_parameter(
shape=param_shape, dtype=float_dtype,
default_initializer=initializer
)
self.add_parameter('theta', theta)
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -307,7 +296,7 @@ class P(Gate): ...@@ -307,7 +296,7 @@ class P(Gate):
return state return state
class RX(Gate): class RX(ParamGate):
r"""A collection of single-qubit rotation gates about the x-axis. r"""A collection of single-qubit rotation gates about the x-axis.
The matrix form of such a gate is: The matrix form of such a gate is:
...@@ -334,31 +323,12 @@ class RX(Gate): ...@@ -334,31 +323,12 @@ class RX(Gate):
param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False
): ):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.param_sharing = param_sharing self.param_sharing = param_sharing
float_dtype = _get_float_dtype(self.dtype)
param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)]
if param_sharing: self.theta_generation(param, param_shape)
param_shape = [1] self.gate_name = 'rx'
else:
param_shape = list(np.shape(self.qubits_idx))
param_shape = [self.depth] + param_shape
if param is None:
theta = self.create_parameter(
shape=param_shape, dtype=float_dtype,
default_initializer=paddle.nn.initializer.Uniform(low=0, high=2 * math.pi)
)
self.add_parameter('theta', theta)
elif isinstance(param, paddle.Tensor):
theta = self.create_parameter(
shape=param_shape, dtype=float_dtype,
default_initializer=paddle.nn.initializer.Assign(param.reshape(param_shape))
)
self.add_parameter('theta', theta)
elif isinstance(param, float):
self.theta = paddle.ones(param_shape, dtype=float_dtype) * param
else:
raise ValueError("The param must be paddle.Tensor or float.")
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -390,7 +360,7 @@ class RX(Gate): ...@@ -390,7 +360,7 @@ class RX(Gate):
return state return state
class RY(Gate): class RY(ParamGate):
r"""A collection of single-qubit rotation gates about the y-axis. r"""A collection of single-qubit rotation gates about the y-axis.
The matrix form of such a gate is: The matrix form of such a gate is:
...@@ -417,31 +387,12 @@ class RY(Gate): ...@@ -417,31 +387,12 @@ class RY(Gate):
param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False
): ):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.param_sharing = param_sharing self.param_sharing = param_sharing
float_dtype = _get_float_dtype(self.dtype)
param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)]
if param_sharing: self.theta_generation(param, param_shape)
param_shape = [1] self.gate_name = 'ry'
else:
param_shape = list(np.shape(self.qubits_idx))
param_shape = [self.depth] + param_shape
if param is None:
theta = self.create_parameter(
shape=param_shape, dtype=float_dtype,
default_initializer=paddle.nn.initializer.Uniform(low=0, high=2 * math.pi)
)
self.add_parameter('theta', theta)
elif isinstance(param, paddle.Tensor):
theta = self.create_parameter(
shape=param_shape, dtype=float_dtype,
default_initializer=paddle.nn.initializer.Assign(param.reshape(param_shape))
)
self.add_parameter('theta', theta)
elif isinstance(param, float):
self.theta = paddle.ones(param_shape, dtype=float_dtype) * param
else:
raise ValueError("The param must be paddle.Tensor or float.")
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -473,7 +424,7 @@ class RY(Gate): ...@@ -473,7 +424,7 @@ class RY(Gate):
return state return state
class RZ(Gate): class RZ(ParamGate):
r"""A collection of single-qubit rotation gates about the z-axis. r"""A collection of single-qubit rotation gates about the z-axis.
The matrix form of such a gate is: The matrix form of such a gate is:
...@@ -500,31 +451,12 @@ class RZ(Gate): ...@@ -500,31 +451,12 @@ class RZ(Gate):
param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False param: Optional[Union[paddle.Tensor, float]] = None, param_sharing: Optional[bool] = False
): ):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.param_sharing = param_sharing self.param_sharing = param_sharing
float_dtype = _get_float_dtype(self.dtype)
param_shape = [depth, 1 if param_sharing else len(self.qubits_idx)]
if param_sharing: self.theta_generation(param, param_shape)
param_shape = [1] self.gate_name = 'rz'
else:
param_shape = list(np.shape(self.qubits_idx))
param_shape = [self.depth] + param_shape
if param is None:
theta = self.create_parameter(
shape=param_shape, dtype=float_dtype,
default_initializer=paddle.nn.initializer.Uniform(low=0, high=2 * math.pi)
)
self.add_parameter('theta', theta)
elif isinstance(param, paddle.Tensor):
theta = self.create_parameter(
shape=param_shape, dtype=float_dtype,
default_initializer=paddle.nn.initializer.Assign(param.reshape(param_shape))
)
self.add_parameter('theta', theta)
elif isinstance(param, float):
self.theta = paddle.ones(param_shape, dtype=float_dtype) * param
else:
raise ValueError("The param must be paddle.Tensor or float.")
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
...@@ -556,7 +488,7 @@ class RZ(Gate): ...@@ -556,7 +488,7 @@ class RZ(Gate):
return state return state
class U3(Gate): class U3(ParamGate):
r"""A collection of single-qubit rotation gates. r"""A collection of single-qubit rotation gates.
The matrix form of such a gate is: The matrix form of such a gate is:
...@@ -586,29 +518,15 @@ class U3(Gate): ...@@ -586,29 +518,15 @@ class U3(Gate):
param: Optional[Union[paddle.Tensor, Iterable[float]]] = None, param_sharing: Optional[bool] = False param: Optional[Union[paddle.Tensor, Iterable[float]]] = None, param_sharing: Optional[bool] = False
): ):
super().__init__(depth) super().__init__(depth)
self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits, is_single_qubit_gate=True) self.qubits_idx = _format_qubits_idx(qubits_idx, num_qubits)
self.param_sharing = param_sharing self.param_sharing = param_sharing
float_dtype = _get_float_dtype(self.dtype)
if param_sharing: if param_sharing:
param_shape = [3] param_shape = [depth, 3]
else: else:
param_shape = list(np.shape(self.qubits_idx)) + [3] param_shape = [depth, len(self.qubits_idx), 3]
param_shape = [self.depth] + param_shape self.theta_generation(param, param_shape)
if param is None: self.gate_name = 'u'
initializer = paddle.nn.initializer.Uniform(low=0, high=2 * math.pi)
else:
if isinstance(param, float):
initializer = paddle.nn.initializer.Constant(param)
elif isinstance(param, paddle.Tensor):
initializer = paddle.nn.initializer.Assign(param.reshape(param_shape))
else:
raise ValueError("The param must be paddle.Tensor or float.")
theta = self.create_parameter(
shape=param_shape, dtype=float_dtype,
default_initializer=initializer
)
self.add_parameter('theta', theta)
def forward(self, state: paddle_quantum.State) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf and state.backend == Backend.QuLeaf:
......
...@@ -238,6 +238,9 @@ def plot_supervised_loss_grad(circuit: Circuit, loss_func: Callable[[Circuit, An ...@@ -238,6 +238,9 @@ def plot_supervised_loss_grad(circuit: Circuit, loss_func: Callable[[Circuit, An
TRAIN_Y: Label set. TRAIN_Y: Label set.
*args: Parameters for ``loss_func`` other than ``circuit``. *args: Parameters for ``loss_func`` other than ``circuit``.
Raises:
Exception: Training data should be paddle.Tensor type
Returns: Returns:
Contains the following two elements. Contains the following two elements.
- loss_list: A list of losses for each iteration. - loss_list: A list of losses for each iteration.
...@@ -304,6 +307,9 @@ def random_sample_supervised(circuit: Circuit, loss_func: Callable[[Circuit, Any ...@@ -304,6 +307,9 @@ def random_sample_supervised(circuit: Circuit, loss_func: Callable[[Circuit, Any
- In single mode, we calculate the mean and variance of gradients of every trainable parameters. - In single mode, we calculate the mean and variance of gradients of every trainable parameters.
- In max mode, we calculate the mean and variance of maximum gradients of for every trainable parameters. - In max mode, we calculate the mean and variance of maximum gradients of for every trainable parameters.
- In random mode, we calculate the mean and variance of data randomly extracted from gradients of every trainable parameters. - In random mode, we calculate the mean and variance of data randomly extracted from gradients of every trainable parameters.
Raises:
Exception: Training data should be paddle.Tensor type
Returns: Returns:
Contains the following two elements. Contains the following two elements.
......
...@@ -136,7 +136,7 @@ class Hamiltonian: ...@@ -136,7 +136,7 @@ class Hamiltonian:
@property @property
def pauli_words(self) -> list: def pauli_words(self) -> list:
r"""The Pauli word of each termi.e. ``['ZIZ', 'IIX']``. r"""The Pauli word of each term, i.e. ``['ZIZ', 'IIX']``.
""" """
if self.__update_flag: if self.__update_flag:
self.__decompose() self.__decompose()
...@@ -146,7 +146,7 @@ class Hamiltonian: ...@@ -146,7 +146,7 @@ class Hamiltonian:
@property @property
def pauli_words_r(self) -> list: def pauli_words_r(self) -> list:
r"""A list of Pauli word (exclude I)i.e. ``['ZXZZ', 'Z', 'X']``. r"""A list of Pauli word (exclude I), i.e. ``['ZXZZ', 'Z', 'X']``.
""" """
if self.__update_flag: if self.__update_flag:
self.__decompose() self.__decompose()
...@@ -209,11 +209,14 @@ class Hamiltonian: ...@@ -209,11 +209,14 @@ class Hamiltonian:
return self.__nqubits return self.__nqubits
def __decompose(self): def __decompose(self):
r"""将哈密顿量分解为不同的形式 r"""decompose the Hamiltonian into vairious forms
Raises:
Exception: Operators should be defined with a string composed of Pauli operators followed by qubit index on which it act, separated with ",". i.e. "Z0, X1"
Notes: Notes:
这是一个内部函数,你不需要直接使用它 This is an intrinsic function, user do not need to call this directly
这是一个比较基础的函数,它负责将输入的 Pauli string 拆分为不同的形式并存储在内部变量中 This is a fundamental function, it decomposes the input Pauli string into different forms and stores them into private variables.
""" """
self.__pauli_words = [] self.__pauli_words = []
self.__pauli_words_r = [] self.__pauli_words_r = []
...@@ -261,10 +264,10 @@ class Hamiltonian: ...@@ -261,10 +264,10 @@ class Hamiltonian:
self.__update_flag = False self.__update_flag = False
def __compress(self): def __compress(self):
r""" 对同类项进行合并。 r"""combine like terms
Notes: Notes:
这是一个内部函数,你不需要直接使用它 This is an intrinsic function, user do not need to call this directly
""" """
if self.__update_flag: if self.__update_flag:
self.__decompose() self.__decompose()
...@@ -388,14 +391,14 @@ class SpinOps: ...@@ -388,14 +391,14 @@ class SpinOps:
return self.__sigx_p return self.__sigx_p
def __direct_prod_op(self, spin_op, spin_index): def __direct_prod_op(self, spin_op, spin_index):
r"""直积,得到第 n 个自旋(量子比特)上的自旋算符 r"""get spin operators on n-th spin (qubit) with direct product
Args: Args:
spin_op: 单体自旋算符 spin_op: single body spin operator
spin_index: 标记第 n 个自旋(量子比特) spin_index: on which spin (qubit)
Returns: Returns:
scipy.sparse or np.ndarray: 直积后的自旋算符,其数据类型取决于 self.__use_sparse scipy.sparse or np.ndarray: spin operator with direct product form. The data type is specified by self.__use_sparse
""" """
s_p = copy.copy(spin_op) s_p = copy.copy(spin_op)
for i in range(self.size): for i in range(self.size):
......
...@@ -32,9 +32,9 @@ def _one(dtype): ...@@ -32,9 +32,9 @@ def _one(dtype):
def _format_qubits_idx( def _format_qubits_idx(
qubits_idx: Union[Iterable[Iterable[int]], Iterable[int], int, str], qubits_idx: Union[Iterable[Iterable[int]], Iterable[int], int, str],
num_qubits: int, is_single_qubit_gate: bool, num_acted_qubits: int = 0 num_qubits: int, num_acted_qubits: int = 1
) -> Union[List[List[int]], List[int]]: ) -> Union[List[List[int]], List[int]]:
if is_single_qubit_gate: if num_acted_qubits == 1:
if qubits_idx == 'full': if qubits_idx == 'full':
qubits_idx = list(range(0, num_qubits)) qubits_idx = list(range(0, num_qubits))
elif qubits_idx == 'even': elif qubits_idx == 'even':
...@@ -48,13 +48,15 @@ def _format_qubits_idx( ...@@ -48,13 +48,15 @@ def _format_qubits_idx(
else: else:
if qubits_idx == 'cycle': if qubits_idx == 'cycle':
qubits_idx = [] qubits_idx = []
for idx in range(0, num_qubits - 1): for idx in range(0, num_qubits - num_acted_qubits):
qubits_idx.append([idx, idx + 1]) qubits_idx.append([i for i in range(idx, idx + num_acted_qubits)])
qubits_idx.append([num_qubits - 1, 0]) for idx in range(num_qubits - num_acted_qubits, num_qubits):
qubits_idx.append([i for i in range(idx, num_qubits)] +
[i for i in range(idx + num_acted_qubits - num_qubits)])
elif qubits_idx == 'linear': elif qubits_idx == 'linear':
qubits_idx = [] qubits_idx = []
for idx in range(0, num_qubits - 1): for idx in range(0, num_qubits - num_acted_qubits):
qubits_idx.append([idx, idx + 1]) qubits_idx.append([i for i in range(idx, idx + num_acted_qubits)])
elif len(np.shape(qubits_idx)) == 1 and len(qubits_idx) == num_acted_qubits: elif len(np.shape(qubits_idx)) == 1 and len(qubits_idx) == num_acted_qubits:
qubits_idx = [list(qubits_idx)] qubits_idx = [list(qubits_idx)]
elif len(np.shape(qubits_idx)) == 2 and all((len(indices) == num_acted_qubits for indices in qubits_idx)): elif len(np.shape(qubits_idx)) == 2 and all((len(indices) == num_acted_qubits for indices in qubits_idx)):
......
...@@ -17,14 +17,16 @@ r""" ...@@ -17,14 +17,16 @@ r"""
The common linear algorithm in paddle quantum. The common linear algorithm in paddle quantum.
""" """
from typing import Optional from typing import Optional, Union
import paddle import paddle
import math import math
import numpy as np import numpy as np
import scipy import scipy
from scipy.stats import unitary_group
from functools import reduce from functools import reduce
import paddle_quantum import paddle_quantum
from paddle_quantum.intrinsic import _get_float_dtype
def abs_norm(mat: paddle.Tensor) -> float: def abs_norm(mat: paddle.Tensor) -> float:
...@@ -37,7 +39,7 @@ def abs_norm(mat: paddle.Tensor) -> float: ...@@ -37,7 +39,7 @@ def abs_norm(mat: paddle.Tensor) -> float:
norm of mat norm of mat
""" """
mat = mat.cast('complex64') mat = mat.cast(paddle_quantum.get_dtype())
return paddle.norm(paddle.abs(mat)).item() return paddle.norm(paddle.abs(mat)).item()
...@@ -58,7 +60,7 @@ def is_hermitian(mat: paddle.Tensor, eps: Optional[float] = 1e-6) -> bool: ...@@ -58,7 +60,7 @@ def is_hermitian(mat: paddle.Tensor, eps: Optional[float] = 1e-6) -> bool:
r""" verify whether mat ``P`` is Hermitian r""" verify whether mat ``P`` is Hermitian
Args: Args:
mat: matrix mat: hermitian candidate
eps: tolerance of error eps: tolerance of error
Returns: Returns:
...@@ -67,7 +69,7 @@ def is_hermitian(mat: paddle.Tensor, eps: Optional[float] = 1e-6) -> bool: ...@@ -67,7 +69,7 @@ def is_hermitian(mat: paddle.Tensor, eps: Optional[float] = 1e-6) -> bool:
""" """
shape = mat.shape shape = mat.shape
if len(shape) != 2 or shape[0] != shape[1] or math.log2(shape[0]) != math.ceil(math.log2(shape[0])): if len(shape) != 2 or shape[0] != shape[1] or math.log2(shape[0]) != math.ceil(math.log2(shape[0])):
# not a mat / not a square mat / shape is not in form 2^n x 2^n # not a mat / not a square mat / shape is not in form 2^num_qubits x 2^num_qubits
return False return False
return abs_norm(mat - dagger(mat)) < eps return abs_norm(mat - dagger(mat)) < eps
...@@ -76,7 +78,7 @@ def is_projector(mat: paddle.Tensor, eps: Optional[float] = 1e-6) -> bool: ...@@ -76,7 +78,7 @@ def is_projector(mat: paddle.Tensor, eps: Optional[float] = 1e-6) -> bool:
r""" verify whether mat ``P`` is a projector r""" verify whether mat ``P`` is a projector
Args: Args:
mat: matrix mat: projector candidate
eps: tolerance of error eps: tolerance of error
Returns: Returns:
...@@ -85,7 +87,7 @@ def is_projector(mat: paddle.Tensor, eps: Optional[float] = 1e-6) -> bool: ...@@ -85,7 +87,7 @@ def is_projector(mat: paddle.Tensor, eps: Optional[float] = 1e-6) -> bool:
""" """
shape = mat.shape shape = mat.shape
if len(shape) != 2 or shape[0] != shape[1] or math.log2(shape[0]) != math.ceil(math.log2(shape[0])): if len(shape) != 2 or shape[0] != shape[1] or math.log2(shape[0]) != math.ceil(math.log2(shape[0])):
# not a mat / not a square mat / shape is not in form 2^n x 2^n # not a mat / not a square mat / shape is not in form 2^num_qubits x 2^num_qubits
return False return False
return abs_norm(mat @ mat - mat) < eps return abs_norm(mat @ mat - mat) < eps
...@@ -94,7 +96,7 @@ def is_unitary(mat: paddle.Tensor, eps: Optional[float] = 1e-5) -> bool: ...@@ -94,7 +96,7 @@ def is_unitary(mat: paddle.Tensor, eps: Optional[float] = 1e-5) -> bool:
r""" verify whether mat ``P`` is a unitary r""" verify whether mat ``P`` is a unitary
Args: Args:
mat: matrix mat: unitary candidate
eps: tolerance of error eps: tolerance of error
Returns: Returns:
...@@ -103,52 +105,87 @@ def is_unitary(mat: paddle.Tensor, eps: Optional[float] = 1e-5) -> bool: ...@@ -103,52 +105,87 @@ def is_unitary(mat: paddle.Tensor, eps: Optional[float] = 1e-5) -> bool:
""" """
shape = mat.shape shape = mat.shape
if len(shape) != 2 or shape[0] != shape[1] or math.log2(shape[0]) != math.ceil(math.log2(shape[0])): if len(shape) != 2 or shape[0] != shape[1] or math.log2(shape[0]) != math.ceil(math.log2(shape[0])):
# not a mat / not a square mat / shape is not in form 2^n x 2^n # not a mat / not a square mat / shape is not in form 2^num_qubits x 2^num_qubits
return False return False
return abs_norm(mat @ dagger(mat) - paddle.cast(paddle.eye(shape[0]), mat.dtype)) < eps return abs_norm(mat @ dagger(mat) - paddle.cast(paddle.eye(shape[0]), mat.dtype)) < eps
def hermitian_random(num_qubits: int) -> paddle.Tensor: def hermitian_random(num_qubits: int) -> paddle.Tensor:
r"""randomly generate a :math:`2^n \times 2^n` hermitian matrix r"""randomly generate a :math:`2^num_qubits \times 2^num_qubits` hermitian matrix
Args: Args:
num_qubits: log2(dimension) num_qubits: log2(dimension)
Returns: Returns:
a :math:`2^n \times 2^n` hermitian matrix a :math:`2^num_qubits \times 2^num_qubits` hermitian matrix
""" """
assert num_qubits > 0 assert num_qubits > 0
n = 2 ** num_qubits n = 2 ** num_qubits
vec = paddle.randn([n, n]) + 1j * paddle.randn([n, n])
float_dtype = _get_float_dtype(paddle_quantum.get_dtype())
vec = paddle.randn([n, n], dtype=float_dtype) + 1j * paddle.randn([n, n], dtype=float_dtype)
mat = vec @ dagger(vec) mat = vec @ dagger(vec)
return mat / paddle.trace(mat) return mat / paddle.trace(mat)
def orthogonal_projection_random(num_qubits: int) -> paddle.Tensor: def orthogonal_projection_random(num_qubits: int) -> paddle.Tensor:
r"""randomly generate a :math:`2^n \times 2^n` rank-1 orthogonal projector r"""randomly generate a :math:`2^num_qubits \times 2^num_qubits` rank-1 orthogonal projector
Args: Args:
num_qubits: log2(dimension) num_qubits: log2(dimension)
Returns: Returns:
a :math:`2^n \times 2^n` orthogonal projector and its eigenstate a 2^num_qubits x 2^num_qubits orthogonal projector
""" """
assert num_qubits > 0 assert num_qubits > 0
n = 2 ** num_qubits n = 2 ** num_qubits
vec = paddle.randn([n, 1]) + 1j * paddle.randn([n, 1]) float_dtype = _get_float_dtype(paddle_quantum.get_dtype())
vec = paddle.randn([n, 1], dtype=float_dtype) + 1j * paddle.randn([n, 1], dtype=float_dtype)
mat = vec @ dagger(vec) mat = vec @ dagger(vec)
return mat / paddle.trace(mat) return mat / paddle.trace(mat)
def density_matrix_random(num_qubits: int) -> paddle.Tensor:
r""" randomly generate an num_qubits-qubit state in density matrix form
Args:
num_qubits: number of qubits
Returns:
a 2^num_qubits x 2^num_qubits density matrix
"""
float_dtype = _get_float_dtype(paddle_quantum.get_dtype())
real = paddle.rand([2 ** num_qubits, 2 ** num_qubits], dtype=float_dtype)
imag = paddle.rand([2 ** num_qubits, 2 ** num_qubits], dtype=float_dtype)
M = real + 1j * imag
M = M @ dagger(M)
rho = M / paddle.trace(M)
return rho
def unitary_random(num_qubits: int) -> paddle.Tensor:
r"""randomly generate a :math:`2^num_qubits \times 2^num_qubits` unitary
Args:
num_qubits: :math:`\log_{2}(dimension)`
Returns:
a :math:`2^num_qubits \times 2^num_qubits` unitary matrix
"""
return paddle.to_tensor(unitary_group.rvs(2 ** num_qubits), dtype=paddle_quantum.get_dtype())
def unitary_hermitian_random(num_qubits: int) -> paddle.Tensor: def unitary_hermitian_random(num_qubits: int) -> paddle.Tensor:
r"""randomly generate a :math:`2^n \times 2^n` hermitian unitary r"""randomly generate a :math:`2^num_qubits \times 2^num_qubits` hermitian unitary
Args: Args:
num_qubits: :math:`\log_{2}(dimension)` num_qubits: :math:`\log_{2}(dimension)`
Returns: Returns:
a :math:`2^n \times 2^n` hermitian unitary matrix a :math:`2^num_qubits \times 2^num_qubits` hermitian unitary matrix
""" """
proj_mat = orthogonal_projection_random(num_qubits) proj_mat = orthogonal_projection_random(num_qubits)
...@@ -156,39 +193,29 @@ def unitary_hermitian_random(num_qubits: int) -> paddle.Tensor: ...@@ -156,39 +193,29 @@ def unitary_hermitian_random(num_qubits: int) -> paddle.Tensor:
return (2 + 0j) * proj_mat - id_mat return (2 + 0j) * proj_mat - id_mat
def unitary_random_with_hermitian_block(num_qubits: int) -> paddle.Tensor: def unitary_random_with_hermitian_block(num_qubits: int, is_unitary: bool = False) -> paddle.Tensor:
r"""randomly generate a unitary :math:`2^n \times 2^n` matrix that is a block encoding of a :math:`2^{n/2} \times 2^{n/2}` Hermitian matrix r"""randomly generate a unitary :math:`2^num_qubits \times 2^num_qubits` matrix that is a block encoding of a :math:`2^{num_qubits/2} \times 2^{num_qubits/2}` Hermitian matrix
Args: Args:
num_qubits: :math:`\log_{2}(dimension)` num_qubits: :math:`\log_{2}(dimension)`
is_unitary: whether the hermitian block is a unitary divided by 2 (for tutorial only)
Returns: Returns:
a :math:`2^n \times 2^n` unitary matrix that its upper-left block is a Hermitian matrix a :math:`2^num_qubits \times 2^num_qubits` unitary matrix that its upper-left block is a Hermitian matrix
""" """
assert num_qubits > 0 assert num_qubits > 0
dtype = paddle_quantum.get_dtype()
mat0 = hermitian_random(num_qubits - 1).numpy() if is_unitary:
mat0 = unitary_hermitian_random(num_qubits - 1).numpy() / 2
else:
mat0 = hermitian_random(num_qubits - 1).numpy()
id_mat = np.eye(2 ** (num_qubits - 1)) id_mat = np.eye(2 ** (num_qubits - 1))
mat1 = 1j * scipy.linalg.sqrtm(id_mat - np.matmul(mat0, mat0)) mat1 = 1j * scipy.linalg.sqrtm(id_mat - np.matmul(mat0, mat0))
mat = np.block([[mat0, mat1], [mat1, mat0]]) mat = np.block([[mat0, mat1], [mat1, mat0]])
return paddle.to_tensor(mat, dtype=dtype) return paddle.to_tensor(mat, dtype=paddle_quantum.get_dtype())
def unitary_random(num_qubits: int) -> paddle.Tensor:
r"""randomly generate a :math:`2^n \times 2^n` unitary
Args:
num_qubits: :math:`\log_{2}(dimension)`
Returns:
a :math:`2^n \times 2^n` unitary matrix
"""
unitary = scipy.stats.unitary_group(2 ** num_qubits)
return paddle.to_tensor(unitary)
def haar_orthogonal(num_qubits: int) -> paddle.Tensor: def haar_orthogonal(num_qubits: int) -> paddle.Tensor:
...@@ -198,7 +225,7 @@ def haar_orthogonal(num_qubits: int) -> paddle.Tensor: ...@@ -198,7 +225,7 @@ def haar_orthogonal(num_qubits: int) -> paddle.Tensor:
num_qubits: number of qubits num_qubits: number of qubits
Returns: Returns:
a :math:`2^n \times 2^n` orthogonal matrix a :math:`2^num_qubits \times 2^num_qubits` orthogonal matrix
""" """
# Matrix dimension # Matrix dimension
...@@ -210,7 +237,7 @@ def haar_orthogonal(num_qubits: int) -> paddle.Tensor: ...@@ -210,7 +237,7 @@ def haar_orthogonal(num_qubits: int) -> paddle.Tensor:
# Step 3: make the decomposition unique # Step 3: make the decomposition unique
mat_lambda = np.diag(mat_r) / abs(np.diag(mat_r)) mat_lambda = np.diag(mat_r) / abs(np.diag(mat_r))
mat_u = mat_q @ np.diag(mat_lambda) mat_u = mat_q @ np.diag(mat_lambda)
return paddle.to_tensor(mat_u) return paddle.to_tensor(mat_u, dtype=paddle_quantum.get_dtype())
def haar_unitary(num_qubits: int) -> paddle.Tensor: def haar_unitary(num_qubits: int) -> paddle.Tensor:
...@@ -220,7 +247,7 @@ def haar_unitary(num_qubits: int) -> paddle.Tensor: ...@@ -220,7 +247,7 @@ def haar_unitary(num_qubits: int) -> paddle.Tensor:
num_qubits: number of qubits num_qubits: number of qubits
Returns: Returns:
a :math:`2^n \times 2^n` unitary a :math:`2^num_qubits \times 2^num_qubits` unitary
""" """
# Matrix dimension # Matrix dimension
...@@ -232,7 +259,7 @@ def haar_unitary(num_qubits: int) -> paddle.Tensor: ...@@ -232,7 +259,7 @@ def haar_unitary(num_qubits: int) -> paddle.Tensor:
# Step 3: make the decomposition unique # Step 3: make the decomposition unique
mat_lambda = np.diag(mat_r) / np.abs(np.diag(mat_r)) mat_lambda = np.diag(mat_r) / np.abs(np.diag(mat_r))
mat_u = mat_q @ np.diag(mat_lambda) mat_u = mat_q @ np.diag(mat_lambda)
return paddle.to_tensor(mat_u) return paddle.to_tensor(mat_u, dtype=paddle_quantum.get_dtype())
def haar_state_vector(num_qubits: int, is_real: Optional[bool] = False) -> paddle.Tensor: def haar_state_vector(num_qubits: int, is_real: Optional[bool] = False) -> paddle.Tensor:
...@@ -243,7 +270,7 @@ def haar_state_vector(num_qubits: int, is_real: Optional[bool] = False) -> paddl ...@@ -243,7 +270,7 @@ def haar_state_vector(num_qubits: int, is_real: Optional[bool] = False) -> paddl
is_real: whether the vector is real, default to be False is_real: whether the vector is real, default to be False
Returns: Returns:
a :math:`2^n \times 1` state vector a :math:`2^num_qubits \times 1` state vector
""" """
# Vector dimension # Vector dimension
...@@ -259,7 +286,7 @@ def haar_state_vector(num_qubits: int, is_real: Optional[bool] = False) -> paddl ...@@ -259,7 +286,7 @@ def haar_state_vector(num_qubits: int, is_real: Optional[bool] = False) -> paddl
# Perform u onto |0>, i.e., the first column of u # Perform u onto |0>, i.e., the first column of u
phi = unitary[:, 0] phi = unitary[:, 0]
return paddle.to_tensor(phi) return paddle.to_tensor(phi, dtype=paddle_quantum.get_dtype())
def haar_density_operator(num_qubits: int, rank: Optional[int] = None, is_real: Optional[bool] = False) -> paddle.Tensor: def haar_density_operator(num_qubits: int, rank: Optional[int] = None, is_real: Optional[bool] = False) -> paddle.Tensor:
...@@ -271,7 +298,7 @@ def haar_density_operator(num_qubits: int, rank: Optional[int] = None, is_real: ...@@ -271,7 +298,7 @@ def haar_density_operator(num_qubits: int, rank: Optional[int] = None, is_real:
is_real: whether the density matrix is real, default to be False is_real: whether the density matrix is real, default to be False
Returns: Returns:
a :math:`2^n \times 2^n` density matrix a :math:`2^num_qubits \times 2^num_qubits` density matrix
""" """
dim = 2 ** num_qubits dim = 2 ** num_qubits
rank = rank if rank is not None else dim rank = rank if rank is not None else dim
...@@ -283,18 +310,40 @@ def haar_density_operator(num_qubits: int, rank: Optional[int] = None, is_real: ...@@ -283,18 +310,40 @@ def haar_density_operator(num_qubits: int, rank: Optional[int] = None, is_real:
ginibre_matrix = np.random.randn(dim, rank) + 1j * np.random.randn(dim, rank) ginibre_matrix = np.random.randn(dim, rank) + 1j * np.random.randn(dim, rank)
rho = ginibre_matrix @ ginibre_matrix.conj().T rho = ginibre_matrix @ ginibre_matrix.conj().T
rho = rho / np.trace(rho) rho = rho / np.trace(rho)
return paddle.to_tensor(rho / np.trace(rho)) return paddle.to_tensor(rho / np.trace(rho), dtype=paddle_quantum.get_dtype())
def NKron(matrix_A: np.ndarray, matrix_B: np.ndarray, *args: np.ndarray) -> np.ndarray: def NKron(
matrix_A: Union[paddle.Tensor, np.ndarray],
matrix_B: Union[paddle.Tensor, np.ndarray],
*args: Union[paddle.Tensor, np.ndarray]
) -> Union[paddle.Tensor, np.ndarray]:
r""" calculate Kronecker product of at least two matrices r""" calculate Kronecker product of at least two matrices
Args: Args:
matrix_A: matrix matrix_A: matrix, as paddle.Tensor or numpy.ndarray
matrix_B: matrix matrix_B: matrix, as paddle.Tensor or numpy.ndarray
*args: other matrices *args: other matrices, as paddle.Tensor or numpy.ndarray
Returns: Returns:
Kronecker product of matrices Kronecker product of matrices, determined by input type of matrix_A
.. 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)
Note:
result should be A \otimes B \otimes C
""" """
return reduce(lambda result, index: np.kron(result, index), args, np.kron(matrix_A, matrix_B), ) is_ndarray = False
\ No newline at end of file if isinstance(matrix_A, np.ndarray):
is_ndarray = True
if not is_ndarray:
return reduce(lambda result, index: paddle.kron(result, index), args, paddle.kron(matrix_A, matrix_B), )
else:
return reduce(lambda result, index: np.kron(result, index), args, np.kron(matrix_A, matrix_B), )
...@@ -18,6 +18,7 @@ The source file of the LoccAnsatz class. ...@@ -18,6 +18,7 @@ The source file of the LoccAnsatz class.
""" """
import collections import collections
from matplotlib import docstring
import paddle import paddle
import paddle_quantum import paddle_quantum
from ..gate import H, S, T, X, Y, Z, P, RX, RY, RZ, U3 from ..gate import H, S, T, X, Y, Z, P, RX, RY, RZ, U3
...@@ -40,26 +41,29 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -40,26 +41,29 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
Args: Args:
party: The owner of this circuit. party: The owner of this circuit.
""" """
def __init__(self, party: LoccParty): def __init__(self, party: LoccParty):
super().__init__() super().__init__()
self.party = party self.party = party
self.num_local_qubits = len(self.party) self.num_local_qubits = len(self.party)
def __transform_qubits_idx(self, oper): def __transform_qubits_idx(self, oper):
if hasattr(oper, 'qubits_idx'): if hasattr(oper, "qubits_idx"):
if isinstance(oper.qubits_idx[0], Iterable): if isinstance(oper.qubits_idx[0], Iterable):
oper.qubits_idx = [ oper.qubits_idx = [
[self.party[qubit_idx] for qubit_idx in qubits_idx] for qubits_idx in oper.qubits_idx [self.party[qubit_idx] for qubit_idx in qubits_idx]
for qubits_idx in oper.qubits_idx
] ]
else: else:
oper.qubits_idx = [self.party[qubit_idx] for qubit_idx in oper.qubits_idx] oper.qubits_idx = [
self.party[qubit_idx] for qubit_idx in oper.qubits_idx
]
def append(self, operator: Union[Iterable, paddle_quantum.Operator]) -> None:
r"""Append an operator.
def append(self, operator: Union[Iterable, paddle_quantum.Operator]):
r""" Append an operator.
Args: Args:
operator: operator with a name or just an operator. operator: operator with a name or just an operator.
""" """
if isinstance(operator, Iterable): if isinstance(operator, Iterable):
name, oper = operator name, oper = operator
...@@ -70,12 +74,11 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -70,12 +74,11 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
idx = len(self._sub_layers) idx = len(self._sub_layers)
self.add_sublayer(str(idx), operator) self.add_sublayer(str(idx), operator)
def extend(self, operators: List[Operator]): def extend(self, operators: List[Operator]) -> None:
r""" Append a list of operators. r"""Append a list of operators.
Args: Args:
operator: List of operators. operators: List of operators.
""" """
if len(operators) > 0 and isinstance(operators[0], (list, tuple)): if len(operators) > 0 and isinstance(operators[0], (list, tuple)):
for name, oper in operators: for name, oper in operators:
...@@ -87,13 +90,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -87,13 +90,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.__transform_qubits_idx(oper) self.__transform_qubits_idx(oper)
self.add_sublayer(str(idx + origin_len), oper) self.add_sublayer(str(idx + origin_len), oper)
def insert(self, index: int, operator: Operator): def insert(self, index: int, operator: Operator) -> None:
r""" Insert an operator at index ``index``. r"""Insert an operator at index ``index``.
Args: Args:
index: Index to be inserted. index: Index to be inserted.
operator: An operator. operator: An operator.
""" """
new_operators = collections.OrderedDict() new_operators = collections.OrderedDict()
for idx, name in enumerate(self._sub_layers): for idx, name in enumerate(self._sub_layers):
...@@ -113,7 +115,7 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -113,7 +115,7 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
new_operators[name] = self._sub_layers[name] new_operators[name] = self._sub_layers[name]
self._sub_layers = new_operators self._sub_layers = new_operators
def pop(self, operator: Operator): def pop(self, operator: Operator) -> None:
r"""Remove the matched operator. r"""Remove the matched operator.
Args: Args:
...@@ -133,21 +135,23 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -133,21 +135,23 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self._sub_layers = new_operators self._sub_layers = new_operators
def forward(self, state: LoccState) -> LoccState: def forward(self, state: LoccState) -> LoccState:
r""" Forward the input. r"""Forward the input.
Args: Args:
state: Initial state. state: Initial state.
Returns: Returns:
Output state. Output state.
"""
"""
for layer in self._sub_layers.values(): for layer in self._sub_layers.values():
state = layer(state) state = layer(state)
return state return state
def h( def h(
self, qubits_idx: Union[Iterable, int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1 self,
qubits_idx: Union[Iterable, int, str] = "full",
num_qubits: Optional[int] = None,
depth: int = 1,
) -> None: ) -> None:
r"""Add single-qubit Hadamard gates. r"""Add single-qubit Hadamard gates.
...@@ -162,7 +166,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -162,7 +166,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def s( def s(
self, qubits_idx: Union[Iterable, int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1 self,
qubits_idx: Union[Iterable, int, str] = "full",
num_qubits: Optional[int] = None,
depth: int = 1,
) -> None: ) -> None:
r"""Add single-qubit S gates. r"""Add single-qubit S gates.
...@@ -177,7 +184,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -177,7 +184,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def t( def t(
self, qubits_idx: Union[Iterable, int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1 self,
qubits_idx: Union[Iterable, int, str] = "full",
num_qubits: Optional[int] = None,
depth: int = 1,
) -> None: ) -> None:
r"""Add single-qubit T gates. r"""Add single-qubit T gates.
...@@ -192,7 +202,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -192,7 +202,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def x( def x(
self, qubits_idx: Union[Iterable, int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1 self,
qubits_idx: Union[Iterable, int, str] = "full",
num_qubits: Optional[int] = None,
depth: int = 1,
) -> None: ) -> None:
r"""Add single-qubit X gates. r"""Add single-qubit X gates.
...@@ -207,7 +220,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -207,7 +220,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def y( def y(
self, qubits_idx: Union[Iterable, int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1 self,
qubits_idx: Union[Iterable, int, str] = "full",
num_qubits: Optional[int] = None,
depth: int = 1,
) -> None: ) -> None:
r"""Add single-qubit Y gates. r"""Add single-qubit Y gates.
...@@ -222,7 +238,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -222,7 +238,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def z( def z(
self, qubits_idx: Union[Iterable, int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1 self,
qubits_idx: Union[Iterable, int, str] = "full",
num_qubits: Optional[int] = None,
depth: int = 1,
) -> None: ) -> None:
r"""Add single-qubit Z gates. r"""Add single-qubit Z gates.
...@@ -237,8 +256,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -237,8 +256,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def p( def p(
self, qubits_idx: Union[Iterable, int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1, self,
param: Union[paddle.Tensor, float] = None, param_sharing: bool = False qubits_idx: Union[Iterable, int, str] = "full",
num_qubits: Optional[int] = None,
depth: int = 1,
param: Union[paddle.Tensor, float] = None,
param_sharing: bool = False,
) -> None: ) -> None:
r"""Add single-qubit P gates. r"""Add single-qubit P gates.
...@@ -255,8 +278,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -255,8 +278,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def rx( def rx(
self, qubits_idx: Union[Iterable, int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1, self,
param: Union[paddle.Tensor, float] = None, param_sharing: bool = False qubits_idx: Union[Iterable, int, str] = "full",
num_qubits: Optional[int] = None,
depth: int = 1,
param: Union[paddle.Tensor, float] = None,
param_sharing: bool = False,
) -> None: ) -> None:
r"""Add single-qubit rotation gates about the x-axis. r"""Add single-qubit rotation gates about the x-axis.
...@@ -273,8 +300,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -273,8 +300,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def ry( def ry(
self, qubits_idx: Union[Iterable, int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1, self,
param: Union[paddle.Tensor, float] = None, param_sharing: bool = False qubits_idx: Union[Iterable, int, str] = "full",
num_qubits: Optional[int] = None,
depth: int = 1,
param: Union[paddle.Tensor, float] = None,
param_sharing: bool = False,
) -> None: ) -> None:
r"""Add single-qubit rotation gates about the y-axis. r"""Add single-qubit rotation gates about the y-axis.
...@@ -291,8 +322,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -291,8 +322,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def rz( def rz(
self, qubits_idx: Union[Iterable, int, str] = 'full', num_qubits: Optional[int] = None, depth: int = 1, self,
param: Union[paddle.Tensor, float] = None, param_sharing: bool = False qubits_idx: Union[Iterable, int, str] = "full",
num_qubits: Optional[int] = None,
depth: int = 1,
param: Union[paddle.Tensor, float] = None,
param_sharing: bool = False,
) -> None: ) -> None:
r"""Add single-qubit rotation gates about the z-axis. r"""Add single-qubit rotation gates about the z-axis.
...@@ -309,8 +344,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -309,8 +344,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def u3( def u3(
self, qubits_idx: Union[Iterable, int, str] = 'full', num_qubits: int = None, depth: int = 1, self,
param: Union[paddle.Tensor, Iterable[float]] = None, param_sharing: bool = False qubits_idx: Union[Iterable, int, str] = "full",
num_qubits: int = None,
depth: int = 1,
param: Union[paddle.Tensor, Iterable[float]] = None,
param_sharing: bool = False,
) -> None: ) -> None:
r"""Add single-qubit rotation gates. r"""Add single-qubit rotation gates.
...@@ -327,7 +366,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -327,7 +366,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def cnot( def cnot(
self, qubits_idx: Union[Iterable, str] = 'cycle', num_qubits: int = None, depth: int = 1 self,
qubits_idx: Union[Iterable, str] = "cycle",
num_qubits: int = None,
depth: int = 1,
) -> None: ) -> None:
r"""Add CNOT gates. r"""Add CNOT gates.
...@@ -342,7 +384,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -342,7 +384,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def cx( def cx(
self, qubits_idx: Union[Iterable, str] = 'cycle', num_qubits: int = None, depth: int = 1 self,
qubits_idx: Union[Iterable, str] = "cycle",
num_qubits: int = None,
depth: int = 1,
) -> None: ) -> None:
r"""Same as cnot. r"""Same as cnot.
...@@ -357,7 +402,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -357,7 +402,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def cy( def cy(
self, qubits_idx: Union[Iterable, str] = 'cycle', num_qubits: int = None, depth: int = 1 self,
qubits_idx: Union[Iterable, str] = "cycle",
num_qubits: int = None,
depth: int = 1,
) -> None: ) -> None:
r"""Add controlled Y gates. r"""Add controlled Y gates.
...@@ -372,7 +420,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -372,7 +420,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def cz( def cz(
self, qubits_idx: Union[Iterable, str] = 'cycle', num_qubits: int = None, depth: int = 1 self,
qubits_idx: Union[Iterable, str] = "cycle",
num_qubits: int = None,
depth: int = 1,
) -> None: ) -> None:
r"""Add controlled Z gates. r"""Add controlled Z gates.
...@@ -387,7 +438,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -387,7 +438,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def swap( def swap(
self, qubits_idx: Union[Iterable, str] = 'cycle', num_qubits: int = None, depth: int = 1 self,
qubits_idx: Union[Iterable, str] = "cycle",
num_qubits: int = None,
depth: int = 1,
) -> None: ) -> None:
r"""Add SWAP gates. r"""Add SWAP gates.
...@@ -402,8 +456,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -402,8 +456,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def cp( def cp(
self, qubits_idx: Union[Iterable, str] = 'cycle', num_qubits: int = None, depth: int = 1, self,
param: Union[paddle.Tensor, float] = None, param_sharing: bool = False qubits_idx: Union[Iterable, str] = "cycle",
num_qubits: int = None,
depth: int = 1,
param: Union[paddle.Tensor, float] = None,
param_sharing: bool = False,
) -> None: ) -> None:
r"""Add controlled P gates. r"""Add controlled P gates.
...@@ -420,8 +478,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -420,8 +478,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def crx( def crx(
self, qubits_idx: Union[Iterable, str] = 'cycle', num_qubits: int = None, depth: int = 1, self,
param: Union[paddle.Tensor, float] = None, param_sharing: bool = False qubits_idx: Union[Iterable, str] = "cycle",
num_qubits: int = None,
depth: int = 1,
param: Union[paddle.Tensor, float] = None,
param_sharing: bool = False,
) -> None: ) -> None:
r"""Add controlled rotation gates about the x-axis. r"""Add controlled rotation gates about the x-axis.
...@@ -438,8 +500,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -438,8 +500,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def cry( def cry(
self, qubits_idx: Union[Iterable, str] = 'cycle', num_qubits: int = None, depth: int = 1, self,
param: Union[paddle.Tensor, float] = None, param_sharing: bool = False qubits_idx: Union[Iterable, str] = "cycle",
num_qubits: int = None,
depth: int = 1,
param: Union[paddle.Tensor, float] = None,
param_sharing: bool = False,
) -> None: ) -> None:
r"""Add controlled rotation gates about the y-axis. r"""Add controlled rotation gates about the y-axis.
...@@ -456,8 +522,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -456,8 +522,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def crz( def crz(
self, qubits_idx: Union[Iterable, str] = 'cycle', num_qubits: int = None, depth: int = 1, self,
param: Union[paddle.Tensor, float] = None, param_sharing: bool = False qubits_idx: Union[Iterable, str] = "cycle",
num_qubits: int = None,
depth: int = 1,
param: Union[paddle.Tensor, float] = None,
param_sharing: bool = False,
) -> None: ) -> None:
r"""Add controlled rotation gates about the z-axis. r"""Add controlled rotation gates about the z-axis.
...@@ -474,8 +544,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -474,8 +544,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def cu( def cu(
self, qubits_idx: Union[Iterable, str] = 'cycle', num_qubits: int = None, depth: int = 1, self,
param: Union[paddle.Tensor, float] = None, param_sharing: bool = False qubits_idx: Union[Iterable, str] = "cycle",
num_qubits: int = None,
depth: int = 1,
param: Union[paddle.Tensor, float] = None,
param_sharing: bool = False,
) -> None: ) -> None:
r"""Add controlled single-qubit rotation gates. r"""Add controlled single-qubit rotation gates.
...@@ -492,8 +566,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -492,8 +566,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def rxx( def rxx(
self, qubits_idx: Union[Iterable, str] = 'cycle', num_qubits: int = None, depth: int = 1, self,
param: Union[paddle.Tensor, float] = None, param_sharing: bool = False qubits_idx: Union[Iterable, str] = "cycle",
num_qubits: int = None,
depth: int = 1,
param: Union[paddle.Tensor, float] = None,
param_sharing: bool = False,
) -> None: ) -> None:
r"""Add RXX gates. r"""Add RXX gates.
...@@ -510,8 +588,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -510,8 +588,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def ryy( def ryy(
self, qubits_idx: Union[Iterable, str] = 'cycle', num_qubits: int = None, depth: int = 1, self,
param: Union[paddle.Tensor, float] = None, param_sharing: bool = False qubits_idx: Union[Iterable, str] = "cycle",
num_qubits: int = None,
depth: int = 1,
param: Union[paddle.Tensor, float] = None,
param_sharing: bool = False,
) -> None: ) -> None:
r"""Add RYY gates. r"""Add RYY gates.
...@@ -528,8 +610,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -528,8 +610,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def rzz( def rzz(
self, qubits_idx: Union[Iterable, str] = 'cycle', num_qubits: int = None, depth: int = 1, self,
param: Union[paddle.Tensor, float] = None, param_sharing: bool = False qubits_idx: Union[Iterable, str] = "cycle",
num_qubits: int = None,
depth: int = 1,
param: Union[paddle.Tensor, float] = None,
param_sharing: bool = False,
) -> None: ) -> None:
r"""Add RZZ gates. r"""Add RZZ gates.
...@@ -546,7 +632,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -546,7 +632,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def ms( def ms(
self, qubits_idx: Union[Iterable, str] = 'cycle', num_qubits: int = None, depth: int = 1 self,
qubits_idx: Union[Iterable, str] = "cycle",
num_qubits: int = None,
depth: int = 1,
) -> None: ) -> None:
r"""Add Mølmer-Sørensen (MS) gates. r"""Add Mølmer-Sørensen (MS) gates.
...@@ -561,7 +650,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -561,7 +650,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def cswap( def cswap(
self, qubits_idx: Union[Iterable, str] = 'cycle', num_qubits: int = None, depth: int = 1 self,
qubits_idx: Union[Iterable, str] = "cycle",
num_qubits: int = None,
depth: int = 1,
) -> None: ) -> None:
r"""Add CSWAP (Fredkin) gates. r"""Add CSWAP (Fredkin) gates.
...@@ -576,7 +668,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -576,7 +668,10 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def ccx( def ccx(
self, qubits_idx: Union[Iterable, str] = 'cycle', num_qubits: int = None, depth: int = 1 self,
qubits_idx: Union[Iterable, str] = "cycle",
num_qubits: int = None,
depth: int = 1,
) -> None: ) -> None:
r"""Add CCX gates. r"""Add CCX gates.
...@@ -595,8 +690,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -595,8 +690,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def universal_two_qubits( def universal_two_qubits(
self, qubits_idx: Union[Iterable, str] = 'cycle', num_qubits: int = None, depth: int = 1, self,
param: Union[paddle.Tensor, float] = None, param_sharing: bool = False qubits_idx: Union[Iterable, str] = "cycle",
num_qubits: int = None,
depth: int = 1,
param: Union[paddle.Tensor, float] = None,
param_sharing: bool = False,
) -> None: ) -> None:
r"""Add universal two-qubit gates. One of such a gate requires 15 parameters. r"""Add universal two-qubit gates. One of such a gate requires 15 parameters.
...@@ -613,8 +712,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -613,8 +712,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def universal_three_qubits( def universal_three_qubits(
self, qubits_idx: Union[Iterable, str] = 'cycle', num_qubits: int = None, depth: int = 1, self,
param: Union[paddle.Tensor, float] = None, param_sharing: bool = False qubits_idx: Union[Iterable, str] = "cycle",
num_qubits: int = None,
depth: int = 1,
param: Union[paddle.Tensor, float] = None,
param_sharing: bool = False,
) -> None: ) -> None:
r"""Add universal three-qubit gates. One of such a gate requires 81 parameters. r"""Add universal three-qubit gates. One of such a gate requires 81 parameters.
...@@ -631,8 +734,11 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -631,8 +734,11 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def oracle( def oracle(
self, oracle: paddle.Tensor, qubits_idx: Union[Iterable[Iterable[int]], Iterable[int], int], self,
num_qubits: int = None, depth: int = 1 oracle: paddle.Tensor,
qubits_idx: Union[Iterable[Iterable[int]], Iterable[int], int],
num_qubits: int = None,
depth: int = 1,
) -> None: ) -> None:
r"""Add an oracle gate. r"""Add an oracle gate.
...@@ -648,10 +754,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit): ...@@ -648,10 +754,12 @@ class LoccAnsatz(paddle_quantum.ansatz.Circuit):
self.append(oper) self.append(oper)
def control_oracle( def control_oracle(
self, oracle: paddle.Tensor, self,
# num_control_qubits: int, controlled_value: 'str', oracle: paddle.Tensor,
qubits_idx: Union[Iterable[Iterable[int]], Iterable[int]], # num_control_qubits: int, controlled_value: 'str',
num_qubits: int = None, depth: int = 1 qubits_idx: Union[Iterable[Iterable[int]], Iterable[int]],
num_qubits: int = None,
depth: int = 1,
) -> None: ) -> None:
"""Add a controlled oracle gate. """Add a controlled oracle gate.
......
...@@ -29,8 +29,8 @@ from typing import Optional, Union, Iterable, List ...@@ -29,8 +29,8 @@ from typing import Optional, Union, Iterable, List
class LoccNet(paddle.nn.Layer): class LoccNet(paddle.nn.Layer):
r"""Used to design LOCC protocols and perform training or verification. r"""Used to design LOCC protocols and perform training or verification."""
"""
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.parties_by_number = [] self.parties_by_number = []
...@@ -54,15 +54,14 @@ class LoccNet(paddle.nn.Layer): ...@@ -54,15 +54,14 @@ class LoccNet(paddle.nn.Layer):
self.init_status.num_qubits += state.num_qubits self.init_status.num_qubits += state.num_qubits
for idx, (party_id, qubit_id) in enumerate(qubits_idx): for idx, (party_id, qubit_id) in enumerate(qubits_idx):
if isinstance(party_id, str): if isinstance(party_id, str):
self.parties_by_name[party_id][qubit_id] = (temp_len + idx) self.parties_by_name[party_id][qubit_id] = temp_len + idx
elif isinstance(party_id, int): elif isinstance(party_id, int):
self.parties_by_number[party_id][qubit_id] = (temp_len + idx) self.parties_by_number[party_id][qubit_id] = temp_len + idx
else: else:
raise ValueError raise ValueError
def __partial_trace(self, rho_AB, dim1, dim2, A_or_B): def __partial_trace(self, rho_AB: paddle.Tensor, dim1: int, dim2: int, A_or_B: int):
r"""TODO To be checked. r"""TODO To be checked."""
"""
if A_or_B == 2: if A_or_B == 2:
dim1, dim2 = dim2, dim1 dim1, dim2 = dim2, dim1
idty_np = np.identity(dim2) idty_np = np.identity(dim2)
...@@ -82,8 +81,8 @@ class LoccNet(paddle.nn.Layer): ...@@ -82,8 +81,8 @@ class LoccNet(paddle.nn.Layer):
res, res,
paddle.matmul( paddle.matmul(
paddle.matmul(row_tmp, rho_AB), paddle.matmul(row_tmp, rho_AB),
paddle.transpose(row_tmp_conj, perm=[1, 0]) paddle.transpose(row_tmp_conj, perm=[1, 0]),
) ),
) )
if A_or_B == 2: if A_or_B == 2:
row_tmp = paddle.kron(idty_B, bra_j) row_tmp = paddle.kron(idty_B, bra_j)
...@@ -93,13 +92,16 @@ class LoccNet(paddle.nn.Layer): ...@@ -93,13 +92,16 @@ class LoccNet(paddle.nn.Layer):
res, res,
paddle.matmul( paddle.matmul(
paddle.matmul(row_tmp, rho_AB), paddle.matmul(row_tmp, rho_AB),
paddle.transpose(row_tmp_conj, perm=[1, 0]) paddle.transpose(row_tmp_conj, perm=[1, 0]),
) ),
) )
return res return res
def partial_state( def partial_state(
self, state: Union[List[LoccState], LoccState], qubits_idx: Iterable, is_desired: bool = True self,
state: Union[List[LoccState], LoccState],
qubits_idx: Iterable,
is_desired: bool = True,
) -> Union[List[LoccState], LoccState]: ) -> Union[List[LoccState], LoccState]:
r"""Get the quantum state of the qubits of interest. r"""Get the quantum state of the qubits of interest.
...@@ -149,17 +151,17 @@ class LoccNet(paddle.nn.Layer): ...@@ -149,17 +151,17 @@ class LoccNet(paddle.nn.Layer):
swapped[target_seq[next_idx]] = True swapped[target_seq[next_idx]] = True
swap_list.append((next_idx, target_seq[next_idx])) swap_list.append((next_idx, target_seq[next_idx]))
next_idx = target_seq[next_idx] next_idx = target_seq[next_idx]
cir = paddle_quantum.ansatz.Circuit() cir = paddle_quantum.ansatz.Circuit(n)
for a, b in swap_list: for a, b in swap_list:
cir.swap([a, b]) cir.swap([a, b])
if isinstance(state, LoccState): if isinstance(state, LoccState):
state = cir(state) state = cir(state)
if is_desired: if is_desired:
state_data = self.__partial_trace(state.data, 2 ** m, 2 ** (n - m), 2) state_data = self.__partial_trace(state.data, 2**m, 2 ** (n - m), 2)
else: else:
state_data = self.__partial_trace(state.data, 2 ** m, 2 ** (n - m), 1) state_data = self.__partial_trace(state.data, 2**m, 2 ** (n - m), 1)
new_state = state.clone() new_state = state.clone()
new_state.data = state_data new_state.data = state_data
new_state.num_qubits = int(math.log2(state_data.shape[-1])) new_state.num_qubits = int(math.log2(state_data.shape[-1]))
...@@ -168,9 +170,13 @@ class LoccNet(paddle.nn.Layer): ...@@ -168,9 +170,13 @@ class LoccNet(paddle.nn.Layer):
for each_state in state: for each_state in state:
each_state = cir(each_state) each_state = cir(each_state)
if is_desired: if is_desired:
state_data = self.__partial_trace(each_state.data, 2 ** m, 2 ** (n - m), 2) state_data = self.__partial_trace(
each_state.data, 2**m, 2 ** (n - m), 2
)
else: else:
state_data = self.__partial_trace(each_state.data, 2 ** m, 2 ** (n - m), 1) state_data = self.__partial_trace(
each_state.data, 2**m, 2 ** (n - m), 1
)
_state = each_state.clone() _state = each_state.clone()
_state.data = state_data _state.data = state_data
_state.num_qubits = int(math.log2(state_data.shape[-1])) _state.num_qubits = int(math.log2(state_data.shape[-1]))
...@@ -182,7 +188,10 @@ class LoccNet(paddle.nn.Layer): ...@@ -182,7 +188,10 @@ class LoccNet(paddle.nn.Layer):
return new_state return new_state
def reset_state( def reset_state(
self, status: Union[List[LoccState], LoccState], state: paddle_quantum.State, which_qubits: Iterable self,
status: Union[List[LoccState], LoccState],
state: paddle_quantum.State,
which_qubits: Iterable,
) -> Union[List[LoccState], LoccState]: ) -> Union[List[LoccState], LoccState]:
r"""Reset the quantum state of the qubits of interest. r"""Reset the quantum state of the qubits of interest.
...@@ -234,18 +243,18 @@ class LoccNet(paddle.nn.Layer): ...@@ -234,18 +243,18 @@ class LoccNet(paddle.nn.Layer):
swap_list.append((next_idx, target_seq[next_idx])) swap_list.append((next_idx, target_seq[next_idx]))
next_idx = target_seq[next_idx] next_idx = target_seq[next_idx]
cir0 = paddle_quantum.ansatz.Circuit() cir0 = paddle_quantum.ansatz.Circuit(n)
for a, b in swap_list: for a, b in swap_list:
cir0.swap([a, b]) cir0.swap([a, b])
cir1 = paddle_quantum.ansatz.Circuit() cir1 = paddle_quantum.ansatz.Circuit(n)
swap_list.reverse() swap_list.reverse()
for a, b in swap_list: for a, b in swap_list:
cir1.swap([a, b]) cir1.swap([a, b])
if isinstance(status, LoccState): if isinstance(status, LoccState):
_state = cir0(status) _state = cir0(status)
state_data = self.__partial_trace(_state.data, 2 ** m, 2 ** (n - m), 1) state_data = self.__partial_trace(_state.data, 2**m, 2 ** (n - m), 1)
state_data = paddle.kron(state.data, state_data) state_data = paddle.kron(state.data, state_data)
_state = _state.clone() _state = _state.clone()
_state.data = state_data _state.data = state_data
...@@ -255,7 +264,7 @@ class LoccNet(paddle.nn.Layer): ...@@ -255,7 +264,7 @@ class LoccNet(paddle.nn.Layer):
new_status = [] new_status = []
for each_status in status: for each_status in status:
_state = cir0(each_status) _state = cir0(each_status)
state_data = self.__partial_trace(_state.data, 2 ** m, 2 ** (n - m), 1) state_data = self.__partial_trace(_state.data, 2**m, 2 ** (n - m), 1)
state_data = paddle.kron(state.data, state_data) state_data = paddle.kron(state.data, state_data)
_state = _state.clone() _state = _state.clone()
_state.data = state_data _state.data = state_data
...@@ -268,7 +277,9 @@ class LoccNet(paddle.nn.Layer): ...@@ -268,7 +277,9 @@ class LoccNet(paddle.nn.Layer):
return new_status return new_status
def add_new_party(self, qubits_number: int, party_name: Optional[str] = None) -> Union[int, str]: def add_new_party(
self, qubits_number: int, party_name: Optional[str] = None
) -> Union[int, str]:
r"""Add a new LOCC party. r"""Add a new LOCC party.
Args: Args:
...@@ -320,30 +331,41 @@ class LoccNet(paddle.nn.Layer): ...@@ -320,30 +331,41 @@ class LoccNet(paddle.nn.Layer):
else: else:
raise ValueError raise ValueError
def __measure_parameterized(self, state_data, which_qubits, result_desired, theta): def __measure_parameterized(
r"""TODO 进行参数化的测量。 self,
state_data: paddle.Tensor,
which_qubits: Iterable,
result_desired: str,
theta: paddle.Tensor,
):
r"""TODO Do parameterized measurement。
Args: Args:
state_data (Tensor): 输入的量子态 state_data (Tensor): The input quantum state
which_qubits (list): 测量作用的量子比特编号 which_qubits (list): The indices of qubits to be measured
result_desired (str): 期望得到的测量结果 result_desired (str): The desired result
theta (Tensor): 测量运算的参数 theta (Tensor): The parameters of measurement
Returns: Returns:
Tensor: 测量坍塌后的量子态 Tensor: The quantum state collapsed after measurement
Tensor: 测量坍塌得到的概率 Tensor: The probability of collapsing to the resulting state
str: 测量得到的结果 str: The result of measurement
""" """
n = self.get_num_qubits() n = self.get_num_qubits()
assert len(which_qubits) == len(result_desired), \ assert len(which_qubits) == len(
"the length of qubits wanted to be measured and the result desired should be same" result_desired
), "the length of qubits wanted to be measured and the result desired should be same"
op_list = [paddle.to_tensor(np.eye(2), dtype=paddle_quantum.get_dtype())] * n op_list = [paddle.to_tensor(np.eye(2), dtype=paddle_quantum.get_dtype())] * n
for idx in range(0, len(which_qubits)): for idx in range(0, len(which_qubits)):
i = which_qubits[idx] i = which_qubits[idx]
ele = result_desired[idx] ele = result_desired[idx]
if int(ele) == 0: if int(ele) == 0:
basis0 = paddle.to_tensor(np.array([[1, 0], [0, 0]]), dtype=paddle_quantum.get_dtype()) basis0 = paddle.to_tensor(
basis1 = paddle.to_tensor(np.array([[0, 0], [0, 1]]), dtype=paddle_quantum.get_dtype()) np.array([[1, 0], [0, 0]]), dtype=paddle_quantum.get_dtype()
)
basis1 = paddle.to_tensor(
np.array([[0, 0], [0, 1]]), dtype=paddle_quantum.get_dtype()
)
rho0 = paddle.multiply(basis0, paddle.cos(theta[idx])) rho0 = paddle.multiply(basis0, paddle.cos(theta[idx]))
rho1 = paddle.multiply(basis1, paddle.sin(theta[idx])) rho1 = paddle.multiply(basis1, paddle.sin(theta[idx]))
rho = paddle.add(rho0, rho1) rho = paddle.add(rho0, rho1)
...@@ -351,8 +373,12 @@ class LoccNet(paddle.nn.Layer): ...@@ -351,8 +373,12 @@ class LoccNet(paddle.nn.Layer):
elif int(ele) == 1: elif int(ele) == 1:
# rho = diag(concat([cos(theta[idx]), sin(theta[idx])])) # rho = diag(concat([cos(theta[idx]), sin(theta[idx])]))
# rho = paddle.to_tensor(rho, zeros((2, 2), dtype="float64")) # rho = paddle.to_tensor(rho, zeros((2, 2), dtype="float64"))
basis0 = paddle.to_tensor(np.array([[1, 0], [0, 0]]), dtype=paddle_quantum.get_dtype()) basis0 = paddle.to_tensor(
basis1 = paddle.to_tensor(np.array([[0, 0], [0, 1]]), dtype=paddle_quantum.get_dtype()) np.array([[1, 0], [0, 0]]), dtype=paddle_quantum.get_dtype()
)
basis1 = paddle.to_tensor(
np.array([[0, 0], [0, 1]]), dtype=paddle_quantum.get_dtype()
)
rho0 = paddle.multiply(basis0, paddle.sin(theta[idx])) rho0 = paddle.multiply(basis0, paddle.sin(theta[idx]))
rho1 = paddle.multiply(basis1, paddle.cos(theta[idx])) rho1 = paddle.multiply(basis1, paddle.cos(theta[idx]))
rho = paddle.add(rho0, rho1) rho = paddle.add(rho0, rho1)
...@@ -366,31 +392,40 @@ class LoccNet(paddle.nn.Layer): ...@@ -366,31 +392,40 @@ class LoccNet(paddle.nn.Layer):
measure_operator = paddle.kron(measure_operator, op_list[idx]) measure_operator = paddle.kron(measure_operator, op_list[idx])
state_measured = paddle.matmul( state_measured = paddle.matmul(
paddle.matmul(measure_operator, state_data), paddle.matmul(measure_operator, state_data),
paddle_quantum.linalg.dagger(measure_operator) paddle_quantum.linalg.dagger(measure_operator),
)
prob = paddle.real(
paddle.trace(
paddle.matmul(
paddle.matmul(
paddle_quantum.linalg.dagger(measure_operator), measure_operator
),
state_data,
)
)
) )
prob = paddle.real(paddle.trace(paddle.matmul(
paddle.matmul(paddle_quantum.linalg.dagger(measure_operator), measure_operator),
state_data
)))
state_measured = paddle.divide(state_measured, prob) state_measured = paddle.divide(state_measured, prob)
return state_measured, prob, result_desired return state_measured, prob, result_desired
def __measure_parameterless(self, state, which_qubits, result_desired): def __measure_parameterless(
r"""TODO 进行 01 测量。 self, state: paddle.Tensor, which_qubits: Iterable, result_desired: str
):
r"""TODO Do 01 measurement。
Args: Args:
state (Tensor): 输入的量子态 state (Tensor): The input quantum state
which_qubits (list): 测量作用的量子比特编号 which_qubits (list): The indices of qubits to be measured
result_desired (str): 期望得到的测量结果 result_desired (str): The desired result
Returns: Returns:
Tensor: 测量坍塌后的量子态 Tensor: The quantum state after measurement
Tensor: 测量坍塌得到的概率 Tensor: The probability of collapsing to the resulting state
str: 测量得到的结果 str: The result of measurement
""" """
n = self.get_num_qubits() n = self.get_num_qubits()
assert len(which_qubits) == len(result_desired), \ assert len(which_qubits) == len(
"the length of qubits wanted to be measured and the result desired should be same" result_desired
), "the length of qubits wanted to be measured and the result desired should be same"
op_list = [np.eye(2)] * n op_list = [np.eye(2)] * n
for i, ele in zip(which_qubits, result_desired): for i, ele in zip(which_qubits, result_desired):
k = int(ele) k = int(ele)
...@@ -398,23 +433,36 @@ class LoccNet(paddle.nn.Layer): ...@@ -398,23 +433,36 @@ class LoccNet(paddle.nn.Layer):
rho[int(k), int(k)] = 1 rho[int(k), int(k)] = 1
op_list[i] = rho op_list[i] = rho
if n > 1: if n > 1:
measure_operator = paddle.to_tensor(functools.reduce(np.kron, op_list), dtype=paddle_quantum.get_dtype()) measure_operator = paddle.to_tensor(
functools.reduce(np.kron, op_list), dtype=paddle_quantum.get_dtype()
)
else: else:
measure_operator = paddle.to_tensor(op_list[0], dtype=paddle_quantum.get_dtype()) measure_operator = paddle.to_tensor(
op_list[0], dtype=paddle_quantum.get_dtype()
)
state_measured = paddle.matmul( state_measured = paddle.matmul(
paddle.matmul(measure_operator, state), paddle.matmul(measure_operator, state),
paddle_quantum.linalg.dagger(measure_operator) paddle_quantum.linalg.dagger(measure_operator),
)
prob = paddle.real(
paddle.trace(
paddle.matmul(
paddle.matmul(
paddle_quantum.linalg.dagger(measure_operator), measure_operator
),
state,
)
)
) )
prob = paddle.real(paddle.trace(paddle.matmul(
paddle.matmul(paddle_quantum.linalg.dagger(measure_operator), measure_operator),
state
)))
state_measured = paddle.divide(state_measured, prob) state_measured = paddle.divide(state_measured, prob)
return state_measured, prob, result_desired return state_measured, prob, result_desired
def measure( def measure(
self, status: Union[List[LoccState], LoccState], which_qubits: Iterable, self,
results_desired: Union[List[str], str], theta: paddle.Tensor = None status: Union[List[LoccState], LoccState],
which_qubits: Iterable,
results_desired: Union[List[str], str],
theta: paddle.Tensor = None,
) -> Union[List[LoccState], LoccState]: ) -> Union[List[LoccState], LoccState]:
r"""Perform 0-1 measurement or parameterized measurement on an LOCC state. r"""Perform 0-1 measurement or parameterized measurement on an LOCC state.
...@@ -455,9 +503,13 @@ class LoccNet(paddle.nn.Layer): ...@@ -455,9 +503,13 @@ class LoccNet(paddle.nn.Layer):
new_status = [] new_status = []
for result_desired in results_desired: for result_desired in results_desired:
if theta is None: if theta is None:
result_measured = self.__measure_parameterless(status.data, qubits_list, result_desired) result_measured = self.__measure_parameterless(
status.data, qubits_list, result_desired
)
else: else:
result_measured = self.__measure_parameterized(status.data, qubits_list, result_desired, theta) result_measured = self.__measure_parameterized(
status.data, qubits_list, result_desired, theta
)
state_data, prob, res = result_measured state_data, prob, res = result_measured
_state = status.clone() _state = status.clone()
_state.data = state_data _state.data = state_data
...@@ -474,9 +526,13 @@ class LoccNet(paddle.nn.Layer): ...@@ -474,9 +526,13 @@ class LoccNet(paddle.nn.Layer):
prior_prob = each_status.prob prior_prob = each_status.prob
for result_desired in results_desired: for result_desired in results_desired:
if theta is None: if theta is None:
result_measured = self.__measure_parameterless(each_status.state, qubits_list, result_desired) result_measured = self.__measure_parameterless(
each_status.state, qubits_list, result_desired
)
else: else:
result_measured = self.__measure_parameterized(each_status.state, qubits_list, result_desired, theta) result_measured = self.__measure_parameterized(
each_status.state, qubits_list, result_desired, theta
)
state_data, prob, res = result_measured state_data, prob, res = result_measured
_state = each_status.clone() _state = each_status.clone()
_state.data = state_data _state.data = state_data
...@@ -493,7 +549,7 @@ class LoccNet(paddle.nn.Layer): ...@@ -493,7 +549,7 @@ class LoccNet(paddle.nn.Layer):
r"""Get the number of the qubits in this LOCCNet. r"""Get the number of the qubits in this LOCCNet.
Returns: Returns:
Number of qubits in LOCCNet. The number of qubits in LOCCNet.
""" """
num_qubits = 0 num_qubits = 0
for party in self.parties_by_number: for party in self.parties_by_number:
......
...@@ -40,14 +40,22 @@ class LoccState(paddle_quantum.State): ...@@ -40,14 +40,22 @@ class LoccState(paddle_quantum.State):
backend: Backend of Paddle Quantum. Defaults to ``None``. backend: Backend of Paddle Quantum. Defaults to ``None``.
dtype: Type of data. Defaults to ``None``. dtype: Type of data. Defaults to ``None``.
""" """
def __init__( def __init__(
self, data: paddle.Tensor = None, prob: paddle.Tensor = None, measured_result: str = None, num_qubits: Optional[int] = None, self,
backend: Optional[paddle_quantum.Backend] = None, dtype: Optional[str] = None data: paddle.Tensor = None,
prob: paddle.Tensor = None,
measured_result: str = None,
num_qubits: Optional[int] = None,
backend: Optional[paddle_quantum.Backend] = None,
dtype: Optional[str] = None,
): ):
if data is None and prob is None and measured_result is None: if data is None and prob is None and measured_result is None:
self.data = paddle.to_tensor([1], dtype=paddle_quantum.get_dtype()) self.data = paddle.to_tensor([1], dtype=paddle_quantum.get_dtype())
self.prob = paddle.to_tensor([1], dtype=_get_float_dtype(paddle_quantum.get_dtype())) self.prob = paddle.to_tensor(
self.measured_result = '' [1], dtype=_get_float_dtype(paddle_quantum.get_dtype())
)
self.measured_result = ""
self.num_qubits = 0 self.num_qubits = 0
else: else:
self.data = data self.data = data
...@@ -62,13 +70,20 @@ class LoccState(paddle_quantum.State): ...@@ -62,13 +70,20 @@ class LoccState(paddle_quantum.State):
self.backend = backend self.backend = backend
self.dtype = dtype if dtype is not None else paddle_quantum.get_dtype() self.dtype = dtype if dtype is not None else paddle_quantum.get_dtype()
def clone(self) -> 'LoccState': def clone(self) -> "LoccState":
r"""Create a copy of the current object. r"""Create a copy of the current object.
Returns: Returns:
A copy of the current object. A copy of the current object.
""" """
return LoccState(self.data, self.prob, self.measured_result, self.num_qubits, self.backend, self.dtype) return LoccState(
self.data,
self.prob,
self.measured_result,
self.num_qubits,
self.backend,
self.dtype,
)
def __getitem__(self, item): def __getitem__(self, item):
if item == 0: if item == 0:
...@@ -83,13 +98,15 @@ class LoccState(paddle_quantum.State): ...@@ -83,13 +98,15 @@ class LoccState(paddle_quantum.State):
return ( return (
f"state: {self.data.numpy()}\n" f"state: {self.data.numpy()}\n"
f"prob: {self.prob.numpy()[0]}\n" f"prob: {self.prob.numpy()[0]}\n"
f"measured_result: {self.measured_result}") f"measured_result: {self.measured_result}"
)
def __str__(self): def __str__(self):
return ( return (
f"state: {self.data.numpy()}\n" f"state: {self.data.numpy()}\n"
f"prob: {self.prob.numpy()[0]}\n" f"prob: {self.prob.numpy()[0]}\n"
f"measured_result: {self.measured_result}") f"measured_result: {self.measured_result}"
)
LoccStatus = LoccState LoccStatus = LoccState
...@@ -21,6 +21,7 @@ import paddle ...@@ -21,6 +21,7 @@ import paddle
import paddle_quantum import paddle_quantum
from ..backend import quleaf from ..backend import quleaf
from ..backend import Backend from ..backend import Backend
from ..intrinsic import _get_float_dtype
from typing import Optional, Union, Iterable from typing import Optional, Union, Iterable
...@@ -90,8 +91,12 @@ class ExpecVal(paddle_quantum.Operator): ...@@ -90,8 +91,12 @@ class ExpecVal(paddle_quantum.Operator):
The expectation value. If the backend is QuLeaf, it is computed by sampling. The expectation value. If the backend is QuLeaf, it is computed by sampling.
""" """
if self.backend == Backend.QuLeaf: if self.backend == Backend.QuLeaf:
if len(state.param_list) > 0:
param = paddle.concat(state.param_list)
else:
param = paddle.to_tensor(state.param_list)
expec_val = quleaf.ExpecValOp.apply( expec_val = quleaf.ExpecValOp.apply(
paddle.concat(state.param_list), param,
state, self.hamiltonian, self.shots state, self.hamiltonian, self.shots
) )
return expec_val return expec_val
...@@ -154,26 +159,32 @@ class Measure(paddle_quantum.Operator): ...@@ -154,26 +159,32 @@ class Measure(paddle_quantum.Operator):
qubits_idx: The index of the qubits to be measured. Defaults to ``'full'`` which means measure all the qubits. qubits_idx: The index of the qubits to be measured. Defaults to ``'full'`` which means measure all the qubits.
desired_result: Specify the results of the measurement to return. Defaults to ``None`` which means return the probability of all the results. desired_result: Specify the results of the measurement to return. Defaults to ``None`` which means return the probability of all the results.
Raises:
NotImplementedError: The backend is wrong or not supported.
NotImplementedError: The qubit index is wrong or not supported.
NotImplementedError: Currently we just support the z basis.
Returns: Returns:
The probability of the measurement. The probability of the measurement.
""" """
float_dtype = _get_float_dtype(paddle_quantum.get_dtype())
num_qubits = state.num_qubits num_qubits = state.num_qubits
if self.measure_basis == 'z': if self.measure_basis == 'z':
if state.backend == paddle_quantum.Backend.StateVector: if state.backend == paddle_quantum.Backend.StateVector:
prob_amplitude = paddle.multiply(paddle.conj(state.data), state.data).real() prob_amplitude = paddle.multiply(paddle.conj(state.data), state.data).real()
elif state.backend == paddle_quantum.Backend.DensityMatrix: elif state.backend == paddle_quantum.Backend.DensityMatrix:
prob_amplitude = paddle.zeros([2 ** num_qubits]) prob_amplitude = paddle.zeros([2 ** num_qubits], dtype=float_dtype)
for idx in range(0, 2 ** num_qubits): for idx in range(0, 2 ** num_qubits):
prob_amplitude[idx] += state.data[idx, idx].real() prob_amplitude[idx] += state.data[idx, idx].real()
else: else:
raise NotImplementedError raise NotImplementedError("The backend is wrong or not supported.")
if isinstance(qubits_idx, int): if isinstance(qubits_idx, int):
qubits_idx = [qubits_idx] qubits_idx = [qubits_idx]
if isinstance(qubits_idx, Iterable) and all((isinstance(idx, int) for idx in qubits_idx)): if isinstance(qubits_idx, Iterable) and all((isinstance(idx, int) for idx in qubits_idx)):
qubits_idx = list(qubits_idx) qubits_idx = list(qubits_idx)
measured_num = len(qubits_idx) measured_num = len(qubits_idx)
prob_array = paddle.zeros([2 ** measured_num]) prob_array = paddle.zeros([2 ** measured_num], dtype=float_dtype)
for idx in range(0, 2 ** num_qubits): for idx in range(0, 2 ** num_qubits):
binary = bin(idx)[2:] binary = bin(idx)[2:]
binary = '0' * (num_qubits - len(binary)) + binary binary = '0' * (num_qubits - len(binary)) + binary
...@@ -184,11 +195,14 @@ class Measure(paddle_quantum.Operator): ...@@ -184,11 +195,14 @@ class Measure(paddle_quantum.Operator):
elif qubits_idx == 'full': elif qubits_idx == 'full':
prob_array = prob_amplitude prob_array = prob_amplitude
else: else:
raise NotImplementedError raise NotImplementedError("The qubit index is wrong or not supported.")
prob_array = prob_array / paddle.sum(prob_array) # normalize calculation error
if desired_result is None: if desired_result is None:
return prob_array return prob_array
if isinstance(desired_result, str): if isinstance(desired_result, str):
desired_result = [desired_result] desired_result = [desired_result]
return paddle.concat([prob_array[int(res, base=2)] for res in desired_result]) prob_array = paddle.concat([prob_array[int(res, base=2)] for res in desired_result])
return prob_array
else: else:
raise NotImplementedError raise NotImplementedError("Currently we just support the z basis.")
...@@ -17,11 +17,17 @@ r""" ...@@ -17,11 +17,17 @@ r"""
The source file of the class for the special quantum operator. The source file of the class for the special quantum operator.
""" """
import random import numpy as np
import paddle import paddle
import paddle_quantum import paddle_quantum
import warnings
from ..base import Operator from ..base import Operator
from typing import Union, Iterable from typing import Union, Iterable
from ..intrinsic import _format_qubits_idx, _get_float_dtype
from ..backend import Backend
from ..backend import state_vector, density_matrix, unitary_matrix
from ..linalg import abs_norm
from ..qinfo import partial_trace_discontiguous
class ResetState(Operator): class ResetState(Operator):
...@@ -58,70 +64,108 @@ class Collapse(Operator): ...@@ -58,70 +64,108 @@ class Collapse(Operator):
r"""The class to compute the collapse of the quantum state. r"""The class to compute the collapse of the quantum state.
Args: Args:
qubits_idx: list of qubits to be collapsed. Defaults to ``'full'``.
num_qubits: Total number of qubits. Defaults to ``None``.
desired_result: The desired result you want to collapse. Defaults to ``None`` meaning randomly choose one.
if_print: whether print the information about the collapsed state. Defaults to ``False``.
measure_basis: The basis of the measurement. The quantum state will collapse to the corresponding eigenstate. measure_basis: The basis of the measurement. The quantum state will collapse to the corresponding eigenstate.
Raises: Raises:
NotImplementedError: If the basis of measurement is not z. Other bases will be implemented soon. NotImplementedError: If the basis of measurement is not z. Other bases will be implemented in future.
Note:
When desired_result is `None`, Collapse does not support gradient calculation
""" """
def __init__(self, measure_basis: Union[Iterable[paddle.Tensor], str]): def __init__(self, qubits_idx: Union[Iterable[int], int, str] = 'full', num_qubits: int = None,
desired_result: Union[int, str] = None, if_print: bool = False,
measure_basis: Union[Iterable[paddle.Tensor], str] = 'z'):
super().__init__() super().__init__()
self.measure_basis = [] self.measure_basis = []
# the qubit indices must be sorted
self.qubits_idx = sorted(_format_qubits_idx(qubits_idx, num_qubits))
self.desired_result = desired_result
self.if_print = if_print
if measure_basis == 'z' or measure_basis == 'computational_basis': if measure_basis == 'z' or measure_basis == 'computational_basis':
basis0 = paddle.to_tensor([[1.0, 0], [0, 0]]) self.measure_basis = 'z'
basis1 = paddle.to_tensor([[0.0, 0], [0, 1]])
self.measure_basis.append(basis0)
self.measure_basis.append(basis1)
else: else:
raise NotImplementedError raise NotImplementedError
def forward(self, state: paddle_quantum.State, desired_result: Union[int, str]) -> paddle_quantum.State: def forward(self, state: paddle_quantum.State) -> paddle_quantum.State:
r"""Compute the collapse of the input state. r"""Compute the collapse of the input state.
Args: Args:
state: The input state, which will be collapsed. state: The input state, which will be collapsed
desired_result: The desired result you want to collapse.
Raises:
NotImplementedError: Currently we just support the z basis.
Returns: Returns:
The collapsed quantum state. The collapsed quantum state.
""" """
if self.backend == paddle_quantum.Backend.StateVector: complex_dtype = paddle_quantum.get_dtype()
if desired_result == 'random': float_dtype = _get_float_dtype(complex_dtype)
prob_list = []
idx_list = list(range(0, len(self.measure_basis))) num_qubits = state.num_qubits
for idx in idx_list: backend = state.backend
measure_op = self.measure_basis[idx] num_acted_qubits = len(self.qubits_idx)
state = paddle.unsqueeze(state.data, axis=1) desired_result = self.desired_result
_prob = paddle.matmul(measure_op, state.data) desired_result = int(desired_result, 2) if isinstance(desired_result, str) else desired_result
prob = paddle.matmul(paddle.conj(paddle.t(_prob)), _prob).item()
prob_list.append(prob) # when backend is unitary
desired_result = random.choices(idx_list, prob_list) if backend == Backend.UnitaryMatrix:
measure_op = self.measure_basis[desired_result] assert isinstance(desired_result, int), "desired_result cannot be None in unitary_matrix backend"
state = paddle.unsqueeze(state.data, axis=1) warnings.warn(
_prob = paddle.matmul(measure_op, state.data) "the unitary_matrix of a circuit containing Collapse operator is no longer a unitary"
prob = paddle.matmul(paddle.conj(paddle.t(_prob)), _prob) )
prob = paddle.reshape(prob, [1])
state = paddle.matmul(measure_op, state) / paddle.sqrt(prob) # determine local projector
measured_state = paddle_quantum.State(state, backend=self.backend) local_projector = paddle.zeros([2 ** num_acted_qubits, 2 ** num_acted_qubits])
elif self.backend == paddle_quantum.Backend.DensityMatrix: local_projector[desired_result, desired_result] += 1
if desired_result == 'random': local_projector = local_projector.cast(complex_dtype)
prob_list = []
idx_list = list(range(0, len(self.measure_basis))) projected_state = unitary_matrix.unitary_transformation(state.data, local_projector, self.qubits_idx, num_qubits)
for idx in idx_list: return paddle_quantum.State(projected_state, backend=Backend.UnitaryMatrix)
measure_op = self.measure_basis[idx]
state = paddle.unsqueeze(state.data, axis=1) # retrieve prob_amplitude
measure_op_dagger = paddle.conj(paddle.t(measure_op)) if backend == Backend.StateVector:
prob = paddle.trace(paddle.matmul(paddle.matmul(measure_op_dagger, measure_op), state)).item() rho = state.ket @ state.bra
prob_list.append(prob)
desired_result = random.choices(idx_list, prob_list)
measure_op = self.measure_basis[desired_result]
state = state.data
measure_op_dagger = paddle.conj(paddle.t(measure_op))
prob = paddle.trace(paddle.matmul(paddle.matmul(measure_op_dagger, measure_op), state))
state = paddle.matmul(paddle.matmul(measure_op, state), measure_op_dagger) / prob
measured_state = paddle_quantum.State(state)
else: else:
raise NotImplementedError rho = state.data
return measured_state rho = partial_trace_discontiguous(rho, self.qubits_idx)
prob_amplitude = paddle.zeros([2 ** num_acted_qubits], dtype=float_dtype)
for idx in range(0, 2 ** num_acted_qubits):
prob_amplitude[idx] += rho[idx, idx].real()
prob_amplitude /= paddle.sum(prob_amplitude)
if desired_result is None:
# randomly choose desired_result
desired_result = np.random.choice([i for i in range(2 ** num_acted_qubits)], p=prob_amplitude)
else:
# check whether the state can collapse to desired_result
assert prob_amplitude[desired_result] > 1e-20, ("it is infeasible for the state in qubits " +
f"{self.qubits_idx} to collapse to state |{desired_result_str}>")
# retrieve the binary version of desired result
desired_result_str = bin(desired_result)[2:]
assert num_acted_qubits >= len(desired_result_str), "the desired_result is too large"
for _ in range(num_acted_qubits - len(desired_result_str)):
desired_result_str = '0' + desired_result_str
# whether print the collapsed result
if self.if_print:
# retrieve binary representation
prob = prob_amplitude[desired_result].item()
print(f"qubits {self.qubits_idx} collapse to the state |{desired_result_str}> with probability {prob}")
# determine projector according to the desired result
local_projector = paddle.zeros([2 ** num_acted_qubits, 2 ** num_acted_qubits])
local_projector[desired_result, desired_result] += 1
local_projector = local_projector.cast(complex_dtype)
# apply the local projector and normalize it
if backend == Backend.StateVector:
projected_state = state_vector.unitary_transformation(state.data, local_projector, self.qubits_idx, num_qubits)
return paddle_quantum.State(projected_state / (abs_norm(projected_state) + 0j))
else:
projected_state = density_matrix.unitary_transformation(state.data, local_projector, self.qubits_idx, num_qubits)
return paddle_quantum.State(projected_state / paddle.trace(projected_state))
...@@ -65,6 +65,9 @@ class RHFEnergyLoss(pq.Operator): ...@@ -65,6 +65,9 @@ class RHFEnergyLoss(pq.Operator):
basis: chemical basis, e.g. "sto-3g". basis: chemical basis, e.g. "sto-3g".
multiplicity: spin multiplicity. multiplicity: spin multiplicity.
charge: charge of the molecule. charge: charge of the molecule.
Raises:
ModuleNotFoundError: `hartree fock` method needs pyscf being installed, please run `pip install -U pyscf`.
""" """
def __init__( def __init__(
......
...@@ -31,11 +31,8 @@ __all__ = ["RHFSlaterDeterminantModel"] ...@@ -31,11 +31,8 @@ __all__ = ["RHFSlaterDeterminantModel"]
class GivensRotationBlock(pq.gate.Gate): class GivensRotationBlock(pq.gate.Gate):
r"""This is a two-qubit gate performs the Givens rotation. r"""This is a two-qubit gate performs the Givens rotation.
.. math:: .. math:
U(\theta)=e^{-i\frac{\theta}{2}(Y\otimes X-X\otimes Y)}
\begin{align}
U(\theta)=e^{-i\frac{\theta}{2}(Y\otimes X-X\otimes Y)}
\end{align}
Args: Args:
pindex, qindex qubits where Givens rotation gate acts on. pindex, qindex qubits where Givens rotation gate acts on.
......
...@@ -23,20 +23,22 @@ import re ...@@ -23,20 +23,22 @@ import re
import numpy as np import numpy as np
from scipy.linalg import logm, sqrtm from scipy.linalg import logm, sqrtm
import paddle import paddle
from paddle import kron from paddle import kron, matmul, transpose
from paddle import matmul from .state import State
from paddle import transpose from .base import get_dtype
from paddle_quantum.intrinsic import _get_float_dtype from .intrinsic import _get_float_dtype
from .linalg import dagger, is_unitary, NKron
from .backend import Backend
import matplotlib.image import matplotlib.image
from paddle_quantum.linalg import dagger, is_unitary, NKron from typing import Optional, Tuple, List, Union
from typing import Optional, Tuple, List
def partial_trace(rho_AB: paddle_quantum.State, dim1: int, dim2: int, A_or_B: int) -> paddle.Tensor: def partial_trace(state: Union[State, paddle.Tensor],
dim1: int, dim2: int, A_or_B: int) -> Union[State, paddle.Tensor]:
r"""Calculate the partial trace of the quantum state. r"""Calculate the partial trace of the quantum state.
Args: Args:
rho_AB: Input quantum state. state: Input quantum state.
dim1: The dimension of system A. dim1: The dimension of system A.
dim2: The dimension of system B. dim2: The dimension of system B.
A_or_B: 1 or 2. 1 means to calculate partial trace on system A; 2 means to calculate partial trace on system B. A_or_B: 1 or 2. 1 means to calculate partial trace on system A; 2 means to calculate partial trace on system B.
...@@ -47,15 +49,17 @@ def partial_trace(rho_AB: paddle_quantum.State, dim1: int, dim2: int, A_or_B: in ...@@ -47,15 +49,17 @@ def partial_trace(rho_AB: paddle_quantum.State, dim1: int, dim2: int, A_or_B: in
if A_or_B == 2: if A_or_B == 2:
dim1, dim2 = dim2, dim1 dim1, dim2 = dim2, dim1
rho_AB = rho_AB.data is_State = False
complex_dtype = paddle_quantum.get_dtype() if isinstance(state, State):
float_dtype = _get_float_dtype(complex_dtype) is_State = True
backend = state.backend
idty_np = np.identity(dim2).astype(complex_dtype) rho_AB = state.data if backend != Backend.StateVector else state.ket @ state.bra
idty_B = paddle.to_tensor(idty_np) else:
rho_AB = state
complex_dtype = rho_AB.dtype
zero_np = np.zeros([dim2, dim2], complex_dtype) idty_B = paddle.eye(dim2).cast(complex_dtype)
res = paddle.to_tensor(zero_np) res = paddle.zeros([dim2, dim2]).cast(complex_dtype)
for dim_j in range(dim1): for dim_j in range(dim1):
row_top = paddle.zeros([1, dim_j]) row_top = paddle.zeros([1, dim_j])
...@@ -67,73 +71,87 @@ def partial_trace(rho_AB: paddle_quantum.State, dim1: int, dim2: int, A_or_B: in ...@@ -67,73 +71,87 @@ def partial_trace(rho_AB: paddle_quantum.State, dim1: int, dim2: int, A_or_B: in
if A_or_B == 1: if A_or_B == 1:
row_tmp = kron(bra_j, idty_B) row_tmp = kron(bra_j, idty_B)
row_tmp_conj = paddle.conj(row_tmp) row_tmp_conj = paddle.conj(row_tmp)
res = paddle.add(res, paddle.matmul(paddle.matmul(row_tmp, rho_AB), paddle.transpose(row_tmp_conj, perm=[1, 0]), ), ) res += (row_tmp @ rho_AB) @ paddle.transpose(row_tmp_conj, perm=[1, 0])
if A_or_B == 2: if A_or_B == 2:
row_tmp = kron(idty_B, bra_j) row_tmp = kron(idty_B, bra_j)
row_tmp_conj = paddle.conj(row_tmp) row_tmp_conj = paddle.conj(row_tmp)
res = paddle.add(res, paddle.matmul(paddle.matmul(row_tmp, rho_AB), paddle.transpose(row_tmp_conj, perm=[1, 0]), ), ) res += (row_tmp @ rho_AB) @ paddle.transpose(row_tmp_conj, perm=[1, 0])
if is_State:
if backend == Backend.StateVector:
eigval, eigvec = paddle.linalg.eig(res)
res = eigvec[:, paddle.argmax(paddle.real(eigval))]
return State(res, backend=backend)
return res return res
def partial_trace_discontiguous(rho: paddle_quantum.State, preserve_qubits: Optional[list] = None) -> paddle.Tensor: def partial_trace_discontiguous(state: Union[State, paddle.Tensor],
preserve_qubits: list=None) -> Union[State, paddle.Tensor]:
r"""Calculate the partial trace of the quantum state with arbitrarily selected subsystem r"""Calculate the partial trace of the quantum state with arbitrarily selected subsystem
Args: Args:
rho: Input quantum state. state: Input quantum state.
preserve_qubits: Remaining qubits, default is None, indicate all qubits remain. preserve_qubits: Remaining qubits, default is None, indicate all qubits remain.
Returns: Returns:
Partial trace of the quantum state with arbitrarily selected subsystem. Partial trace of the quantum state with arbitrarily selected subsystem.
""" """
if type(rho) == paddle_quantum.State: is_State = False
rho = rho.data if isinstance(state, State):
complex_dtype = paddle_quantum.get_dtype() is_State = True
float_dtype = _get_float_dtype(complex_dtype) backend = rho.backend
rho = state.data if backend != Backend.StateVector else state.ket @ state.bra
else:
rho = state
complex_dtype = rho.dtype
if preserve_qubits is None: if preserve_qubits is None:
return rho return rho
else:
n = int(math.log2(rho.size) // 2) n = int(math.log2(rho.size) // 2)
num_preserve = len(preserve_qubits) num_preserve = len(preserve_qubits)
shape = paddle.ones((n + 1,)) shape = paddle.ones((n + 1,))
shape = 2 * shape shape = 2 * shape
shape[n] = 2 ** n shape[n] = 2 ** n
shape = paddle.cast(shape, "int32") shape = paddle.cast(shape, "int32")
identity = paddle.eye(2 ** n) identity = paddle.eye(2 ** n)
identity = paddle.reshape(identity, shape=shape) identity = paddle.reshape(identity, shape=shape)
discard = list() discard = list()
for idx in range(0, n): for idx in range(0, n):
if idx not in preserve_qubits: if idx not in preserve_qubits:
discard.append(idx) discard.append(idx)
addition = [n] addition = [n]
preserve_qubits.sort() preserve_qubits.sort()
preserve_qubits = paddle.to_tensor(preserve_qubits) preserve_qubits = paddle.to_tensor(preserve_qubits)
discard = paddle.to_tensor(discard) discard = paddle.to_tensor(discard)
addition = paddle.to_tensor(addition) addition = paddle.to_tensor(addition)
permute = paddle.concat([discard, preserve_qubits, addition]) permute = paddle.concat([discard, preserve_qubits, addition])
identity = paddle.transpose(identity, perm=permute) identity = paddle.transpose(identity, perm=permute)
identity = paddle.reshape(identity, (2 ** n, 2 ** n)) identity = paddle.reshape(identity, (2 ** n, 2 ** n))
result = np.zeros((2 ** num_preserve, 2 ** num_preserve), dtype=complex_dtype) result = np.zeros((2 ** num_preserve, 2 ** num_preserve))
result = paddle.to_tensor(result) result = paddle.to_tensor(result, dtype=complex_dtype)
for i in range(0, 2 ** (n - num_preserve)): for i in range(0, 2 ** (n - num_preserve)):
bra = identity[i * 2 ** num_preserve:(i + 1) * 2 ** num_preserve, :] bra = identity[i * 2 ** num_preserve:(i + 1) * 2 ** num_preserve, :]
result = result + matmul(matmul(bra, rho), transpose(bra, perm=[1, 0])) result = result + matmul(matmul(bra, rho), transpose(bra, perm=[1, 0]))
return result if is_State:
if backend == Backend.StateVector:
eigval, eigvec = paddle.linalg.eig(result)
def trace_distance(rho: paddle_quantum.State, sigma: paddle_quantum.State) -> paddle.Tensor: result = eigvec[:, paddle.argmax(paddle.real(eigval))]
r"""Calculate the trace distance between two quantum states. return State(result, backend=backend)
return result
def trace_distance(rho: Union[State, paddle.Tensor], sigma: Union[State, paddle.Tensor]) -> paddle.Tensor:
r"""Calculate the fidelity of two quantum states.
.. math:: .. math::
D(\rho, \sigma) = 1 / 2 * \text{tr}|\rho-\sigma| D(\rho, \sigma) = 1 / 2 * \text{tr}|\rho-\sigma|
Args: Args:
...@@ -141,16 +159,17 @@ def trace_distance(rho: paddle_quantum.State, sigma: paddle_quantum.State) -> pa ...@@ -141,16 +159,17 @@ def trace_distance(rho: paddle_quantum.State, sigma: paddle_quantum.State) -> pa
sigma: Density matrix form of the quantum state. sigma: Density matrix form of the quantum state.
Returns: Returns:
Trace distance between the input quantum states. The fidelity between the input quantum states.
""" """
assert rho.data.shape == sigma.data.shape, 'The shape of two quantum states are different' if isinstance(rho, State):
A = rho.data.numpy() - sigma.data.numpy() rho = rho.data
distance = 1 / 2 * np.sum(np.abs(np.linalg.eigvals(A))) sigma = sigma.data
assert rho.shape == sigma.shape, 'The shape of two quantum states are different'
return paddle.to_tensor(distance) eigval, eigvec = paddle.linalg.eig(rho - sigma)
return 0.5 * paddle.sum(paddle.abs(eigval))
def state_fidelity(rho: paddle_quantum.State, sigma: paddle_quantum.State) -> paddle.Tensor: def state_fidelity(rho: Union[State, paddle.Tensor], sigma: Union[State, paddle.Tensor]) -> paddle.Tensor:
r"""Calculate the fidelity of two quantum states. r"""Calculate the fidelity of two quantum states.
.. math:: .. math::
...@@ -163,8 +182,12 @@ def state_fidelity(rho: paddle_quantum.State, sigma: paddle_quantum.State) -> pa ...@@ -163,8 +182,12 @@ def state_fidelity(rho: paddle_quantum.State, sigma: paddle_quantum.State) -> pa
Returns: Returns:
The fidelity between the input quantum states. The fidelity between the input quantum states.
""" """
rho = rho.data.numpy()
sigma = sigma.data.numpy() if isinstance(rho, State):
rho = rho.data
sigma = sigma.data
rho = rho.numpy()
sigma = sigma.numpy()
assert rho.shape == sigma.shape, 'The shape of two quantum states are different' assert rho.shape == sigma.shape, 'The shape of two quantum states are different'
fidelity = np.trace(sqrtm(sqrtm(rho) @ sigma @ sqrtm(rho))).real fidelity = np.trace(sqrtm(sqrtm(rho) @ sigma @ sqrtm(rho))).real
...@@ -188,18 +211,15 @@ def gate_fidelity(U: paddle.Tensor, V: paddle.Tensor) -> paddle.Tensor: ...@@ -188,18 +211,15 @@ def gate_fidelity(U: paddle.Tensor, V: paddle.Tensor) -> paddle.Tensor:
fidelity between gates fidelity between gates
""" """
complex_dtype = paddle_quantum.get_dtype() complex_dtype = U.dtype
U = paddle.to_tensor(U, dtype=complex_dtype) V = paddle.cast(V, dtype=complex_dtype)
V = paddle.to_tensor(V, dtype=complex_dtype) assert U.shape == V.shape, 'The shape of two matrices are different'
# assert is_unitary(U), "U is not a unitary"
# assert is_unitary(V), "V is not a unitary"
assert U.shape == V.shape, 'The shape of two unitary matrices are different'
fidelity = paddle.abs(paddle.trace(U @ dagger(V))) / U.shape[0] fidelity = paddle.abs(paddle.trace(U @ dagger(V))) / U.shape[0]
return fidelity return fidelity
def purity(rho: paddle_quantum.State) -> paddle.Tensor: def purity(rho: Union[State, paddle.Tensor]) -> paddle.Tensor:
r"""Calculate the purity of a quantum state. r"""Calculate the purity of a quantum state.
.. math:: .. math::
...@@ -212,13 +232,14 @@ def purity(rho: paddle_quantum.State) -> paddle.Tensor: ...@@ -212,13 +232,14 @@ def purity(rho: paddle_quantum.State) -> paddle.Tensor:
Returns: Returns:
The purity of the input quantum state. The purity of the input quantum state.
""" """
rho = rho.data if isinstance(rho, State):
rho = rho.data
gamma = paddle.trace(paddle.matmul(rho, rho)) gamma = paddle.trace(paddle.matmul(rho, rho))
return gamma.real() return gamma.real()
def von_neumann_entropy(rho: paddle_quantum.State) -> paddle.Tensor: def von_neumann_entropy(rho: Union[State, paddle.Tensor]) -> paddle.Tensor:
r"""Calculate the von Neumann entropy of a quantum state. r"""Calculate the von Neumann entropy of a quantum state.
.. math:: .. math::
...@@ -231,7 +252,9 @@ def von_neumann_entropy(rho: paddle_quantum.State) -> paddle.Tensor: ...@@ -231,7 +252,9 @@ def von_neumann_entropy(rho: paddle_quantum.State) -> paddle.Tensor:
Returns: Returns:
The von Neumann entropy of the input quantum state. The von Neumann entropy of the input quantum state.
""" """
rho = rho.data.numpy() if isinstance(rho, State):
rho = rho.data
rho = rho.numpy()
rho_eigenvalues = np.real(np.linalg.eigvals(rho)) rho_eigenvalues = np.real(np.linalg.eigvals(rho))
entropy = 0 entropy = 0
for eigenvalue in rho_eigenvalues: for eigenvalue in rho_eigenvalues:
...@@ -242,7 +265,7 @@ def von_neumann_entropy(rho: paddle_quantum.State) -> paddle.Tensor: ...@@ -242,7 +265,7 @@ def von_neumann_entropy(rho: paddle_quantum.State) -> paddle.Tensor:
return paddle.to_tensor(entropy) return paddle.to_tensor(entropy)
def relative_entropy(rho: paddle_quantum.State, sig: paddle_quantum.State) -> paddle.Tensor: def relative_entropy(rho: Union[State, paddle.Tensor], sig: Union[State, paddle.Tensor]) -> paddle.Tensor:
r"""Calculate the relative entropy of two quantum states. r"""Calculate the relative entropy of two quantum states.
.. math:: .. math::
...@@ -257,8 +280,11 @@ def relative_entropy(rho: paddle_quantum.State, sig: paddle_quantum.State) -> pa ...@@ -257,8 +280,11 @@ def relative_entropy(rho: paddle_quantum.State, sig: paddle_quantum.State) -> pa
Returns: Returns:
Relative entropy between input quantum states. Relative entropy between input quantum states.
""" """
rho = rho.data.numpy() if isinstance(rho, State):
sig = sig.data.numpy() rho = rho.data
sig = sig.data
rho = rho.numpy()
sig = sig.numpy()
assert rho.shape == sig.shape, 'The shape of two quantum states are different' assert rho.shape == sig.shape, 'The shape of two quantum states are different'
res = np.trace(rho @ logm(rho) - rho @ logm(sig)) res = np.trace(rho @ logm(rho) - rho @ logm(sig))
return paddle.to_tensor(res.real) return paddle.to_tensor(res.real)
...@@ -276,7 +302,7 @@ def random_pauli_str_generator(n: int, terms: Optional[int] = 3) -> List: ...@@ -276,7 +302,7 @@ def random_pauli_str_generator(n: int, terms: Optional[int] = 3) -> List:
terms: Number of terms in the observable. Defaults to 3. terms: Number of terms in the observable. Defaults to 3.
Returns: Returns:
The randomly generated observables list form. The randomly generated observable's list form.
""" """
pauli_str = [] pauli_str = []
for sublen in np.random.randint(1, high=n + 1, size=terms): for sublen in np.random.randint(1, high=n + 1, size=terms):
...@@ -332,12 +358,15 @@ def pauli_str_to_matrix(pauli_str: list, n: int) -> paddle.Tensor: ...@@ -332,12 +358,15 @@ def pauli_str_to_matrix(pauli_str: list, n: int) -> paddle.Tensor:
if len(op_str) == 1: if len(op_str) == 1:
matrices.append(coeff * sub_matrices[0]) matrices.append(coeff * sub_matrices[0])
else: else:
matrices.append(coeff * NKron(sub_matrices[0], sub_matrices[1], *sub_matrices[2:])) mat = sub_matrices[0]
for idx in range(1, len(sub_matrices)):
mat = np.kron(mat, sub_matrices[idx])
matrices.append(coeff * mat)
return paddle.to_tensor(sum(matrices), dtype=paddle_quantum.get_dtype()) return paddle.to_tensor(sum(matrices), dtype=get_dtype())
def partial_transpose_2(density_op: paddle_quantum.State, sub_system: Optional[int] = None) -> paddle.Tensor: def partial_transpose_2(density_op: Union[State, paddle.Tensor], sub_system: int = None) -> paddle.Tensor:
r"""Calculate the partial transpose :math:`\rho^{T_A}` of the input quantum state. r"""Calculate the partial transpose :math:`\rho^{T_A}` of the input quantum state.
Args: Args:
...@@ -350,7 +379,10 @@ def partial_transpose_2(density_op: paddle_quantum.State, sub_system: Optional[i ...@@ -350,7 +379,10 @@ def partial_transpose_2(density_op: paddle_quantum.State, sub_system: Optional[i
sys_idx = 2 if sub_system is None else 1 sys_idx = 2 if sub_system is None else 1
# Copy the density matrix and not corrupt the original one # Copy the density matrix and not corrupt the original one
density_op = density_op.data.numpy() if isinstance(density_op, State):
density_op = density_op.data
complex_dtype = density_op.dtype
density_op = density_op.numpy()
transposed_density_op = np.copy(density_op) transposed_density_op = np.copy(density_op)
if sys_idx == 2: if sys_idx == 2:
for j in [0, 2]: for j in [0, 2]:
...@@ -360,10 +392,10 @@ def partial_transpose_2(density_op: paddle_quantum.State, sub_system: Optional[i ...@@ -360,10 +392,10 @@ def partial_transpose_2(density_op: paddle_quantum.State, sub_system: Optional[i
transposed_density_op[2:4, 0:2] = density_op[0:2, 2:4] transposed_density_op[2:4, 0:2] = density_op[0:2, 2:4]
transposed_density_op[0:2, 2:4] = density_op[2:4, 0:2] transposed_density_op[0:2, 2:4] = density_op[2:4, 0:2]
return paddle.to_tensor(transposed_density_op) return paddle.to_tensor(transposed_density_op, dtype=complex_dtype)
def partial_transpose(density_op: paddle_quantum.State, n: int) -> paddle.Tensor: def partial_transpose(density_op: Union[State, paddle.Tensor], n: int) -> paddle.Tensor:
r"""Calculate the partial transpose :math:`\rho^{T_A}` of the input quantum state. r"""Calculate the partial transpose :math:`\rho^{T_A}` of the input quantum state.
Args: Args:
...@@ -374,16 +406,19 @@ def partial_transpose(density_op: paddle_quantum.State, n: int) -> paddle.Tensor ...@@ -374,16 +406,19 @@ def partial_transpose(density_op: paddle_quantum.State, n: int) -> paddle.Tensor
The partial transpose of the input quantum state. The partial transpose of the input quantum state.
""" """
# Copy the density matrix and not corrupt the original one # Copy the density matrix and not corrupt the original one
density_op = density_op.data.numpy() if isinstance(density_op, State):
density_op = density_op.data
complex_dtype = density_op.dtype
density_op = density_op.numpy()
transposed_density_op = np.copy(density_op) transposed_density_op = np.copy(density_op)
for j in range(0, 2 ** n, 2): for j in range(0, 2 ** n, 2):
for i in range(0, 2 ** n, 2): for i in range(0, 2 ** n, 2):
transposed_density_op[i:i + 2, j:j + 2] = density_op[i:i + 2, j:j + 2].transpose() transposed_density_op[i:i + 2, j:j + 2] = density_op[i:i + 2, j:j + 2].transpose()
return paddle.to_tensor(transposed_density_op) return paddle.to_tensor(transposed_density_op, dtype=complex_dtype)
def negativity(density_op: paddle_quantum.State) -> paddle.Tensor: def negativity(density_op: Union[State, paddle.Tensor]) -> paddle.Tensor:
r"""Compute the Negativity :math:`N = ||\frac{\rho^{T_A}-1}{2}||` of the input quantum state. r"""Compute the Negativity :math:`N = ||\frac{\rho^{T_A}-1}{2}||` of the input quantum state.
Args: Args:
...@@ -394,6 +429,9 @@ def negativity(density_op: paddle_quantum.State) -> paddle.Tensor: ...@@ -394,6 +429,9 @@ def negativity(density_op: paddle_quantum.State) -> paddle.Tensor:
""" """
# Implement the partial transpose # Implement the partial transpose
density_op_T = partial_transpose_2(density_op) density_op_T = partial_transpose_2(density_op)
if isinstance(density_op_T, State):
density_op_T = density_op_T.data
density_op_T = density_op_T.numpy()
# Calculate through the equivalent expression N = sum(abs(\lambda_i)) when \lambda_i<0 # Calculate through the equivalent expression N = sum(abs(\lambda_i)) when \lambda_i<0
n = 0.0 n = 0.0
...@@ -401,10 +439,10 @@ def negativity(density_op: paddle_quantum.State) -> paddle.Tensor: ...@@ -401,10 +439,10 @@ def negativity(density_op: paddle_quantum.State) -> paddle.Tensor:
for val in eigen_val: for val in eigen_val:
if val < 0: if val < 0:
n = n + np.abs(val) n = n + np.abs(val)
return paddle.to_tensor(n) return paddle.to_tensor(n, dtype=_get_float_dtype(paddle_quantum.get_dtype()))
def logarithmic_negativity(density_op: paddle_quantum.State) -> paddle.Tensor: def logarithmic_negativity(density_op: Union[State, paddle.Tensor]) -> paddle.Tensor:
r"""Calculate the Logarithmic Negativity :math:`E_N = ||\rho^{T_A}||` of the input quantum state. r"""Calculate the Logarithmic Negativity :math:`E_N = ||\rho^{T_A}||` of the input quantum state.
Args: Args:
...@@ -421,7 +459,7 @@ def logarithmic_negativity(density_op: paddle_quantum.State) -> paddle.Tensor: ...@@ -421,7 +459,7 @@ def logarithmic_negativity(density_op: paddle_quantum.State) -> paddle.Tensor:
return log2_n return log2_n
def is_ppt(density_op: paddle_quantum.State) -> bool: def is_ppt(density_op: Union[State, paddle.Tensor]) -> bool:
r"""Check if the input quantum state is PPT. r"""Check if the input quantum state is PPT.
Args: Args:
...@@ -439,11 +477,12 @@ def is_ppt(density_op: paddle_quantum.State) -> bool: ...@@ -439,11 +477,12 @@ def is_ppt(density_op: paddle_quantum.State) -> bool:
return ppt return ppt
def schmidt_decompose(psi: paddle_quantum.State, sys_A: Optional[List[int]] = None) -> Tuple[paddle.Tensor]: def schmidt_decompose(psi: Union[State, paddle.Tensor],
sys_A: List[int]=None) -> Tuple[paddle.Tensor, paddle.Tensor, paddle.Tensor]:
r"""Calculate the Schmidt decomposition of a quantum state :math:`\lvert\psi\rangle=\sum_ic_i\lvert i_A\rangle\otimes\lvert i_B \rangle`. r"""Calculate the Schmidt decomposition of a quantum state :math:`\lvert\psi\rangle=\sum_ic_i\lvert i_A\rangle\otimes\lvert i_B \rangle`.
Args: Args:
psi: State vector form of the quantum state, with shape(2**n) psi: State vector form of the quantum state, with shape (2**n)
sys_A: Qubit indices to be included in subsystem A (other qubits are included in subsystem B), default are the first half qubits of :math:`\lvert \psi\rangle` sys_A: Qubit indices to be included in subsystem A (other qubits are included in subsystem B), default are the first half qubits of :math:`\lvert \psi\rangle`
Returns: Returns:
...@@ -453,7 +492,10 @@ def schmidt_decompose(psi: paddle_quantum.State, sys_A: Optional[List[int]] = No ...@@ -453,7 +492,10 @@ def schmidt_decompose(psi: paddle_quantum.State, sys_A: Optional[List[int]] = No
* A high dimensional array composed of bases for subsystem A :math:`\lvert i_A\rangle`, with shape ``(k, 2**m, 1)`` * A high dimensional array composed of bases for subsystem A :math:`\lvert i_A\rangle`, with shape ``(k, 2**m, 1)``
* A high dimensional array composed of bases for subsystem B :math:`\lvert i_B\rangle` , with shape ``(k, 2**m, 1)`` * A high dimensional array composed of bases for subsystem B :math:`\lvert i_B\rangle` , with shape ``(k, 2**m, 1)``
""" """
psi = psi.data.numpy() if isinstance(psi, State):
psi = psi.data
psi = psi.numpy()
complex_dtype = psi.dtype
assert psi.ndim == 1, 'Psi must be a one dimensional vector.' assert psi.ndim == 1, 'Psi must be a one dimensional vector.'
assert np.log2(psi.size).is_integer(), 'The number of amplitutes must be an integral power of 2.' assert np.log2(psi.size).is_integer(), 'The number of amplitutes must be an integral power of 2.'
...@@ -465,20 +507,20 @@ def schmidt_decompose(psi: paddle_quantum.State, sys_A: Optional[List[int]] = No ...@@ -465,20 +507,20 @@ def schmidt_decompose(psi: paddle_quantum.State, sys_A: Optional[List[int]] = No
# Permute qubit indices # Permute qubit indices
psi = psi.reshape([2] * tot_qu).transpose(sys_A + sys_B) psi = psi.reshape([2] * tot_qu).transpose(sys_A + sys_B)
# construct amplitute matrix # construct amplitude matrix
amp_mtr = psi.reshape([2**len(sys_A), 2**len(sys_B)]) amp_mtr = psi.reshape([2**len(sys_A), 2**len(sys_B)])
# Standard process to obtain schmidt decomposition # Standard process to obtain schmidt decomposition
u, c, v = np.linalg.svd(amp_mtr) u, c, v = np.linalg.svd(amp_mtr)
k = np.count_nonzero(c > 1e-13) k = np.count_nonzero(c > 1e-13)
c = c[:k] c = paddle.to_tensor(c[:k], dtype=complex_dtype)
u = u.T[:k].reshape([k, -1, 1]) u = paddle.to_tensor(u.T[:k].reshape([k, -1, 1]), dtype=complex_dtype)
v = v[:k].reshape([k, -1, 1]) v = paddle.to_tensor(v[:k].reshape([k, -1, 1]), dtype=complex_dtype)
return paddle.to_tensor(c), paddle.to_tensor(u), paddle.to_tensor(v) return c, u, v
def image_to_density_matrix(image_filepath: str) -> paddle_quantum.State: def image_to_density_matrix(image_filepath: str) -> State:
r"""Encode image to density matrix r"""Encode image to density matrix
Args: Args:
...@@ -498,10 +540,10 @@ def image_to_density_matrix(image_filepath: str) -> paddle_quantum.State: ...@@ -498,10 +540,10 @@ def image_to_density_matrix(image_filepath: str) -> paddle_quantum.State:
# Density matrix whose trace is 1 # Density matrix whose trace is 1
rho = image_matrix@image_matrix.T rho = image_matrix@image_matrix.T
rho = rho/np.trace(rho) rho = rho/np.trace(rho)
return paddle_quantum.State(paddle.to_tensor(rho), backend=paddle_quantum.Backend.DensityMatrix) return State(paddle.to_tensor(rho), backend=paddle_quantum.Backend.DensityMatrix)
def shadow_trace(state: 'paddle_quantum.State', hamiltonian: paddle_quantum.Hamiltonian, def shadow_trace(state: 'State', hamiltonian: paddle_quantum.Hamiltonian,
sample_shots: int, method: Optional[str] = 'CS') -> float: sample_shots: int, method: Optional[str] = 'CS') -> float:
r"""Estimate the expectation value :math:`\text{trace}(H\rho)` of an observable :math:`H`. r"""Estimate the expectation value :math:`\text{trace}(H\rho)` of an observable :math:`H`.
...@@ -510,6 +552,9 @@ def shadow_trace(state: 'paddle_quantum.State', hamiltonian: paddle_quantum.Hami ...@@ -510,6 +552,9 @@ def shadow_trace(state: 'paddle_quantum.State', hamiltonian: paddle_quantum.Hami
hamiltonian: Observable. hamiltonian: Observable.
sample_shots: Number of samples. sample_shots: Number of samples.
method: Method used to , which should be one of “CS”, “LBCS”, and “APS”. Default is “CS”. method: Method used to , which should be one of “CS”, “LBCS”, and “APS”. Default is “CS”.
Raises:
ValueError: Hamiltonian has a bad form
Returns: Returns:
The estimated expectation value for the hamiltonian. The estimated expectation value for the hamiltonian.
...@@ -639,3 +684,47 @@ def shadow_trace(state: 'paddle_quantum.State', hamiltonian: paddle_quantum.Hami ...@@ -639,3 +684,47 @@ def shadow_trace(state: 'paddle_quantum.State', hamiltonian: paddle_quantum.Hami
trace_estimation = estimation trace_estimation = estimation
return trace_estimation return trace_estimation
def tensor_product(state_a: Union[State, paddle.Tensor], state_b: Union[State, paddle.Tensor], *args: Union[State, paddle.Tensor]) -> State:
r"""calculate tensor product (kronecker product) between at least two state. This function automatically returns State instance
Args:
state_a: State
state_b: State
*args: other states
Raises:
NotImplementedError: only accept state or tensor instances
Returns:
tensor product of input states
Note:
the backend should be density matrix.
"""
if isinstance(state_a, State):
data_a = state_a.data
elif isinstance(state_a, paddle.Tensor):
data_a = state_a
else:
raise NotImplementedError
if isinstance(state_b, State):
data_b = state_b.data
elif isinstance(state_b, paddle.Tensor):
data_b = state_b
else:
raise NotImplementedError
addis = []
for st in args:
if isinstance(st, State):
addis.append(st.data)
elif isinstance(st, paddle.Tensor):
addis.append(st)
else:
raise NotImplementedError
res = paddle_quantum.linalg.NKron(data_a, data_b, *addis)
return State(res)
\ No newline at end of file
# !/usr/bin/env python3
# Copyright (c) 2021 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.
r"""
The module of quantum singular value transformation.
"""
from .qsp_utils import poly_matrix
from .qsp import ScalarQSP, Phi_verification, reflection_based_quantum_signal_processing
from .qsvt import QSVT
# !/usr/bin/env python3
# Copyright (c) 2022 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 as np
from math import pi, acos
import paddle
from paddle_quantum.ansatz.circuit import Circuit
from numpy.polynomial.polynomial import Polynomial, polytrim
from typing import List, Tuple
from .qsp_utils import clean_small_error
"""
Libraries for Quantum Signal Processing
referring to paper https://arxiv.org/abs/1806.01838
"""
# ----------------------------- belows are support and test functions ------------------------
def signal_unitary(signal_x: float) -> np.ndarray:
r"""signal unitary :math:`W(x)` in paper https://arxiv.org/abs/1806.01838
Args:
signal_x: variable x in [-1, 1]
Returns:
matrix W(x = x)
"""
assert -1 <= signal_x <= 1, "x must be in domain [-1, 1]"
return np.array([[signal_x, 1j * np.sqrt(1 - signal_x ** 2)],
[1j * np.sqrt(1 - signal_x ** 2), signal_x]])
def poly_parity_verification(poly_p: Polynomial, k: int, error: float = 1e-6) -> bool:
r"""verify whether :math:`P` has parity-(k mod 2), i.e., the second condition of theorem 3 holds
Args:
poly_p: polynomial :math:`P(x)`
k: parameter that determine parity
error: tolerated error, defaults to `1e-6`
Returns:
determine whether :math:`P` has parity-(k mod 2)
"""
parity = k % 2
P_coef = poly_p.coef
for i in range(poly_p.degree()):
if i % 2 != parity:
if np.abs(P_coef[i]) > error:
return False
P_coef[i] = 0 # this element should be 0
return True
def normalization_verification(poly_p: Polynomial, poly_q: Polynomial, trials: int = 10, error: float = 1e-2) -> bool:
r"""verify whether polynomials :math:`P(x)` and :math:`Q(x)` satisfy the normalization condition :math:`|P|^2 + (1 - x^2)|Q|^2 = 1`, i.e., the third condition of Theorem 3.
Args:
poly_p: polynomial :math:`P(x)`
poly_q: polynomial :math:`Q(x)`
trials: number of tests, defaults to `10`
error: tolerated error, defaults to `1e-2`
Returns:
determine whether :math:`|P|^2 + (1 - x^2)|Q|^2 = 1`
"""
P_conj = Polynomial(np.conj(poly_p.coef))
Q_conj = Polynomial(np.conj(poly_q.coef))
test_poly = poly_p * P_conj + Polynomial([1, 0, -1]) * poly_q * Q_conj
for _ in range(trials):
x = np.random.rand() * 2 - 1 # sample x from [-1, 1]
y = test_poly(x)
if abs(np.real(y) - 1) > error or abs(np.imag(y)) > error:
print(np.real(y) - 1)
return False
return True
def angle_phi_verification(phi: float, poly_p: Polynomial, poly_p_hat: Polynomial, poly_q_hat: Polynomial,
trials: int = 10, error: float = 0.01) -> bool:
r"""verify :math:`\phi` during the iteration of finding :math:`\Phi`
Args:
phi: rotation angle :math:`\phi`
poly_p: polynomial :math:`P(x)`
poly_p_hat: updated polynomial :math:`\hat{P}`
poly_q_hat: updated polynomial :math:`\hat{Q}`
trials: number of tests, defaults to `10`
error: tolerated error, defaults to `0.01`
Returns:
determine whether the equation (6) in paper https://arxiv.org/abs/1806.01838 holds
"""
def block_encoding_new_p(x):
return np.array([[poly_p_hat(x), 1j * poly_q_hat(x) * np.sqrt(1 - x ** 2)],
[1j * np.conj(poly_q_hat(x)) * np.sqrt(1 - x ** 2), np.conj(poly_p_hat(x))]])
rz = np.array([[np.exp(1j * phi), 0],
[0, np.exp(-1j * phi)]])
for _ in range(trials):
x = np.random.rand() * 2 - 1 # sample x from [-1, 1]
matrix = np.matmul(np.matmul(block_encoding_new_p(x), signal_unitary(x)), rz)
y = matrix[0, 0]
if np.abs(y - poly_p(x)) > error:
print(y)
print(poly_p(x))
return False
return True
def processing_unitary(list_matrices: List[np.ndarray], signal_x: float) -> np.ndarray:
r"""processing unitary :math:`W_\Phi(x)`, see equation 1 in paper https://arxiv.org/abs/1806.01838
Args:
list_matrices: array of phi's matrices
signal_x: input signal x in [-1, 1]
Returns:
matrix :math:`W_\Phi(x)`
"""
assert -1 <= signal_x <= 1, "x must be in domain [-1, 1]"
W = signal_unitary(signal_x)
M = list_matrices[0]
for i in range(1, len(list_matrices)):
M = np.matmul(M, np.matmul(W, list_matrices[i]))
return M
def Phi_verification(list_phi: np.ndarray, poly_p: Polynomial, trials: int = 100, error: float = 1e-6) -> bool:
r"""verify the final :math:`\Phi`
Args:
list_phi: array of :math:`\phi`'s
poly_p: polynomial :math:`P(x)`
trials: number of tests, defaults to `100`
error: tolerated error, defaults to `1e-6`
Returns:
determine whether :math:`W_\Phi(x)` is a block encoding of :math:`P(x)`
"""
def rz(theta: float) -> np.ndarray:
return np.array([[np.exp(1j * theta), 0],
[0, np.exp(-1j * theta)]])
matrix_phi = list(map(rz, list_phi))
for _ in range(trials):
x = np.random.rand() * 2 - 1 # sample x from [-1, 1]
y = processing_unitary(matrix_phi, x)[0, 0]
if np.abs(y - poly_p(x)) > error:
print(y - poly_p(x), error)
return False
return True
# ----------------------------- belows are Phi-generation functions ------------------------
def update_polynomial(poly_p: Polynomial, poly_q: Polynomial, phi: float) -> Tuple[Polynomial, Polynomial]:
r"""update :math:`P, Q` by given phi according to proof in theorem 3
Args:
poly_p: polynomial :math:`P(x)`
poly_q: polynomial :math:`Q(x)`
phi: derived :math:`phi`
Returns:
updated :math:`P(x), Q(x)`
"""
poly_1 = Polynomial([0, 1]) # x
poly_2 = Polynomial([1, 0, -1]) # 1 - x^2
# P = e^{-i phi} x P + e^{i phi} (1 - x^2) Q
# Q = e^{i phi} x Q - e^{-i phi} P
P_new = np.exp(-1j * phi) * poly_1 * poly_p + np.exp(1j * phi) * poly_2 * poly_q
Q_new = np.exp(1j * phi) * poly_1 * poly_q - np.exp(-1j * phi) * poly_p
# clean the error that is lower than 0.001
P_new = Polynomial(polytrim(P_new.coef, 0.001))
Q_new = Polynomial(polytrim(Q_new.coef, 0.001))
# clean the error further,
P_new.coef = clean_small_error(P_new.coef)
Q_new.coef = clean_small_error(Q_new.coef)
if P_new.degree() >= poly_p.degree() and np.abs(P_new.coef[-1]) < np.abs(P_new.coef[-3]):
P_new.coef = np.delete(np.delete(P_new.coef, -1), -1)
if Q_new.degree() >= poly_q.degree() > 0 and np.abs(Q_new.coef[-1]) < np.abs(Q_new.coef[-3]):
Q_new.coef = np.delete(np.delete(Q_new.coef, -1), -1)
# used for debug, can be removed in formal version
assert P_new.degree() < poly_p.degree(), print(P_new, '\n', poly_p)
assert Q_new.degree() < poly_q.degree() or poly_q.degree() == 0, print(Q_new, '\n', poly_q)
assert poly_parity_verification(P_new, poly_p.degree() - 1)
assert poly_parity_verification(Q_new, poly_q.degree() - 1)
assert normalization_verification(P_new, Q_new), print(P_new, '\n', Q_new)
assert angle_phi_verification(phi, poly_p, P_new, Q_new)
return P_new, Q_new
def alg_find_Phi(poly_p: Polynomial, poly_q: Polynomial, length: int) -> np.ndarray:
r"""The algorithm of finding phi's by theorem 3
Args:
poly_p: polynomial :math:`P(x)`
poly_q: polynomial :math:`Q(x)`
length: length of returned array
Returns:
array of phi's
"""
n = poly_p.degree()
m = poly_q.degree()
# condition check for theorem 3
assert n <= length, "the condition for P's degree is not satisfied"
assert m <= max(0, length - 1), "the condition for Q's degree is not satisfied"
assert poly_parity_verification(poly_p, length), "the condition for P's parity is not satisfied"
assert poly_parity_verification(poly_q, length - 1), "the condition for Q's parity is not satisfied"
assert normalization_verification(poly_p, poly_q), "the third equation for P, Q is not satisfied"
i = length
Phi = np.zeros([length + 1])
while n > 0:
# assign phi
Phi[i] = np.log(poly_p.coef[n] / poly_q.coef[m]) * -1j / 2
if Phi[i] == 0:
Phi[i] = np.pi
# update step
poly_p, poly_q = update_polynomial(poly_p, poly_q, Phi[i])
n = poly_p.degree()
m = poly_q.degree()
i = i - 1
for j in range(1, i):
Phi[j] = (-1) ** (j - 1) * pi / 2
Phi[0] = -1j * np.log(poly_p.coef[0])
return Phi
# ----------------------------- belows are Q-generation functions ------------------------
def poly_A_hat_generation(poly_p: Polynomial) -> Polynomial:
r"""function for :math:`\hat{A}` generation
Args:
poly_p: polynomial :math:`P(x)`
Returns:
polynomial :math:`\hat{A}(y) = 1 - P(x)P^*(x)`, with :math:`y = x^2`
"""
P_conj = Polynomial(np.conj(poly_p.coef))
A = 1 - poly_p * P_conj
A_coef = A.coef
coef = [A_coef[0]]
for i in range(1, poly_p.degree() + 1):
coef.append(A_coef[2 * i])
return Polynomial(np.array(coef))
def poly_A_hat_decomposition(A_hat: Polynomial, error: float = 0.001) -> Tuple[float, List[float]]:
r"""function for :math:`\hat{A}` decomposition
Args:
A_hat: polynomial :math:`\hat{A}(x)`
error: tolerated error, defaults to `0.001`
Returns:
Tuple: including the following elements
- leading coefficient of :math:`\hat{A}`
- list of roots of :math:`\hat{A}` such that there exist no two roots that are complex conjugates
"""
leading_coef = A_hat.coef[A_hat.degree()]
# remove one 1 and 0 (if k is even) from this list
roots = [i for i in A_hat.roots() if not ((np.abs(np.real(i) - 1) < error
and np.abs(np.imag(i)) < error) or np.abs(i) < error)]
# Note that root function in numpy return roots in complex conjugate pairs
# Now elements in roots are all in pairs
output_roots = [roots[i] for i in range(len(roots)) if i % 2 == 0]
return leading_coef, output_roots
def poly_Q_generation(leading_coef: float, roots: List[float], parity: int) -> Polynomial:
r"""function for polynomial :math:`Q` generation
Args:
leading_coef: leading coefficient of :math:`\hat{A}`
roots: filtered list of roots of :math:`\hat{A}`
parity: parity that affects decomposition
Returns:
polynomial :math:`Q`
"""
a = np.sqrt(-1 * leading_coef)
poly_q = Polynomial([a])
for i in range(len(roots)):
poly_q = poly_q * Polynomial([-1 * roots[i], 0, 1])
if parity % 2 == 0:
poly_q = poly_q * Polynomial([0, 1])
return poly_q
return poly_q
def alg_find_Q(poly_p: Polynomial, k: int) -> Polynomial:
r"""The algorithm of finding :math:`Q` by theorem 4 in paper https://arxiv.org/abs/1806.01838
Args:
poly_p: polynomial :math:`P(x)`
k: length of returned array
Returns:
polynomial :math:`Q(x)`
"""
n = poly_p.degree()
# condition check for theorem 3
assert n <= k, "the condition for P's degree is not satisfied"
assert poly_parity_verification(poly_p, k), "the condition for P's parity is not satisfied"
A_hat = poly_A_hat_generation(poly_p)
leading_coef, roots = poly_A_hat_decomposition(A_hat)
poly_q = poly_Q_generation(leading_coef, roots, k)
return poly_q
# ----------------------------- belows are final functions ------------------------
def quantum_signal_processing(poly_p: Polynomial, length: int = None) -> np.ndarray:
r""" Compute :math:`\Phi` that transfer a block encoding of x to a block encoding of :math:`P(x)` by :math:`W_\Phi(x)`
Args:
poly_p: polynomial :math:`P(x)`
length: length of returned array, defaults to `None` to be the degree of :math:`P(x)`
Returns:
array of :math:`\phi`'s
"""
if length is None:
length = poly_p.degree()
Q = alg_find_Q(poly_p, length)
Phi = alg_find_Phi(poly_p, Q, length)
assert Phi_verification(Phi, poly_p)
return Phi
def reflection_based_quantum_signal_processing(P: Polynomial) -> np.ndarray:
r""" Reflection-based quantum signal processing, compute Phi that transfer a block encoding of x to a block encoding of :math:`P(x)` with :math:`R_\Phi(x)`. Refer to Corollary 8 in the paper.
Args:
P: polynomial :math:`P(x)`
Returns:
array of :math:`phi`'s
"""
Phi = quantum_signal_processing(P)
k = P.degree()
Phi_new = np.zeros([k])
Phi_new[0] = Phi[0] + Phi[k] + (k - 1) * np.pi / 2
for i in range(1, k):
Phi_new[i] = Phi[i] - np.pi / 2
# assertion
phi_sum = 0
phi_alternate = 0
for i in range(k):
phi_sum += Phi_new[i]
phi_alternate += ((-1) ** i) * Phi_new[i]
assert np.abs(P(1) - np.exp(1j * phi_sum)) < 10 ** (-8)
assert np.abs(P(-1) - ((-1) ** k) * np.exp(1j * phi_sum)) < 10 ** (-8)
if k % 2 == 0:
assert np.abs(P(0) - np.exp(-1j * phi_alternate)) < 10 ** (-8)
return Phi_new
# ----------------------------- below is the class for QSP ------------------------
class ScalarQSP(object):
def __init__(self, poly_p: Polynomial, length: int = None) -> None:
r"""Initialize a class that is used for QSP in single qubit
Args:
poly_p: Polynomial P(x)
k: length of array Phi - 1
"""
if length is None:
length = poly_p.degree()
self.poly_p = poly_p
self.poly_q = alg_find_Q(poly_p, length)
self.Phi = paddle.to_tensor(quantum_signal_processing(poly_p, length))
def block_encoding(self, signal_x: float) -> Circuit:
r"""generate a block encoding of :math:`P(x)` by quantum circuit
Args:
x: input parameter
Returns:
a quantum circuit of unitary that is the block encoding of :math:`P(x)`
"""
assert -1 <= signal_x <= 1, "x must be in domain [-1, 1]"
Phi = self.Phi
cir = Circuit()
signal_x = -2 * acos(signal_x)
for i in range(1, len(Phi)):
cir.rz(0, param=-2 * Phi[-i])
cir.rx(0, param=signal_x)
cir.rz(0, param=-2 * Phi[0])
return cir
def block_encoding_matrix(self, signal_x: float) -> paddle.Tensor:
r"""generate a block encoding of :math:`P(x)` for verification
Args:
x: input parameter
Returns:
a block encoding unitary of :math:`P(x)`
"""
assert -1 <= signal_x <= 1, "x must be in domain [-1, 1]"
matrix = np.array([[self.poly_p(signal_x), 1j * self.poly_q(signal_x) * np.sqrt(1 - signal_x ** 2)],
[1j * np.conj(self.poly_q(signal_x)) * np.sqrt(1 - signal_x ** 2), np.conj(self.poly_p(signal_x))]])
return paddle.to_tensor(matrix)
\ No newline at end of file
# !/usr/bin/env python3
# Copyright (c) 2022 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.
from numpy.polynomial.polynomial import Polynomial
import numpy as np
from typing import Tuple, Optional
import paddle
from scipy.linalg import expm
r"""
Tools for Polynomial & Tensor in qsvt Modules
"""
# ----------------------------- belows are polynomial tools ------------------------
def clean_small_error(array: np.ndarray) -> np.ndarray:
r"""clean relatively small quantity
Args:
array: target array
Returns:
cleaned array
"""
def compare_and_clean(a: float, b: float) -> Tuple[float, float]:
r"""discard tiny or relatively tiny real or imaginary parts of elements"""
if a == 0 or b == 0:
return a, b
a_abs = np.abs(a)
b_abs = np.abs(b)
abs_error = 10 ** (-2)
rel_error = 10 ** (-4)
if a_abs < abs_error and a_abs / b_abs < rel_error:
return 0, b
if b_abs < abs_error and b_abs / a_abs < rel_error:
return a, 0
return a, b
for i in range(len(array)):
real, imag = compare_and_clean(np.real(array[i]), np.imag(array[i]))
array[i] = real + 1j * imag
return array
def poly_norm(poly: Polynomial, p: Optional[int] = 1) -> float:
r"""calculate the p-norm of a polynomial
Args:
poly: the target polynomial
p: order of norm, 0 means to be infinity
Returns:
p-norm of the target polynomial
"""
coef = poly.coef
if p == 0:
return np.max(coef)
coef_pow = list(map(lambda x: np.abs(x) ** p, coef))
return np.power(np.sum(coef_pow), 1 / p)
def poly_real(poly: Polynomial) -> Polynomial:
r"""return the real part of a polynomial
Args:
poly: the target polynomial
Returns:
the real part of poly
"""
return Polynomial(np.real(poly.coef))
def poly_imag(poly: Polynomial) -> Polynomial:
r"""return the imaginary part of the polynomial
Args:
poly: the target polynomial
Returns:
the imaginary part of poly
"""
return Polynomial(np.imag(poly.coef))
# ----------------------------- belows are polynomial and tensor tools ------------------------
def poly_matrix(poly: Polynomial, matrix_A: paddle.Tensor) -> paddle.Tensor:
r"""calculate the polynomial of a matrix, poly(matrix_A)
Args:
poly: the polynomial
matrix_A: the matrix
Returns:
poly(matrix_A)
"""
coef = paddle.to_tensor(poly.coef)
k = poly.degree()
N = matrix_A.shape[0]
I = paddle.eye(N)
matrix = paddle.cast(paddle.zeros([N, N]), "complex128")
matrix_temp = I
for i in range(0, k + 1):
for j in range(i):
matrix_temp = matrix_temp @ matrix_A
matrix += coef[i] * matrix_temp
matrix_temp = I
return matrix
def exp_matrix(t: float, matrix_A: paddle.Tensor) -> paddle.Tensor:
r"""calculate :math:`e^{itA}`
Args:
t: time constant
A: the target matrix
Returns:
:math:`e^{itA}`
"""
matrix_A = matrix_A.cast("complex128").numpy()
return paddle.to_tensor(expm(1j * t * matrix_A), dtype="complex128")
# !/usr/bin/env python3
# Copyright (c) 2022 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.
from paddle_quantum.ansatz.circuit import Circuit
from paddle_quantum import set_dtype
from paddle_quantum.linalg import is_hermitian, is_projector, is_unitary, dagger
from paddle_quantum.qinfo import partial_trace
import paddle
from math import log2, ceil
from scipy.linalg import expm
from numpy.polynomial.polynomial import Polynomial
from .qsp import reflection_based_quantum_signal_processing
"""
Libraries for Quantum Singular Value Transformations
referring to paper https://arxiv.org/abs/1806.01838
"""
def block_encoding_projector(
num_qubits: int, num_projected_qubits: int = None
) -> paddle.Tensor:
r"""Generate a projector that is used for simple block encoding
Args:
num_qubits: number of total qubits
num_projected_qubits: number of projected qubits, default to be `num_qubits - 1`
Returns:
:math:`\ket{0}\bra{0} \otimes I`
"""
if num_projected_qubits is None:
num_projected_qubits = num_qubits - 1
m, n = num_projected_qubits, num_qubits
small_I = paddle.eye(2**m)
ket_0 = paddle.zeros([2 ** (n - m), 2 ** (n - m)])
ket_0[0, 0] += 1
return paddle.kron(ket_0, small_I)
def qubitization(proj: paddle.Tensor, phi: paddle.Tensor) -> Circuit:
r"""generate quantum circuit that is equivalent to :math:`e^{i \phi (2P - I)}`
Args:
proj: orthogonal projector
phi: angle parameter
Returns:
a quantum circuit that is equivalent to e^{i \phi (2P - I)}.
"""
assert is_hermitian(proj) and is_projector(proj)
# preparation
n = ceil(log2(proj.shape[0]))
dtype = "complex64"
X = paddle.to_tensor([[0.0, 1.0], [1.0, 0.0]], dtype=dtype)
# define registers
system_register = [i for i in range(n)]
aux_register = [n]
CPX_matrix = paddle.kron(proj, X) + paddle.kron(
paddle.eye(2**n) - proj, paddle.eye(2)
)
cir = Circuit()
cir.oracle(CPX_matrix, system_register + aux_register)
cir.rz(aux_register, param=2 * phi)
cir.oracle(CPX_matrix, system_register + aux_register)
return cir
class QSVT(object):
def __init__(
self, poly_p: Polynomial, oracle: paddle.Tensor, m: int = None
) -> None:
r"""Initialize a class that is used for QSP in multiple qubits
Args:
poly_p: polynomial :math:`P(x)`
oracle: unitary :math:`U` which is a block encoding of a Hermitian :math:`X`
m: log2(dimension of :math:`X`), default to be n - 1
"""
shape = oracle.shape
N = shape[0]
n = int(log2(N))
if m is None:
m = n - 1
assert is_unitary(oracle) # assert U is a 2^n x 2^n unitary
assert is_hermitian(
oracle[0 : 2**m, 0 : 2**m]
) # assert the matrix block encoded by U is Hermitian
self.I = paddle.eye(N)
self.n = n
self.U = oracle
# find A
assert m <= N # make sure A is a block encoding of U
# determine V
self.V = block_encoding_projector(n, m)
# determine phi
self.Phi = paddle.to_tensor(
reflection_based_quantum_signal_processing(poly_p), dtype="float32"
)
def block_encoding_matrix(self) -> paddle.Tensor:
r"""provide the matrix of a block encoding of :math:`P(X)`
Returns:
block encoding of :math:`P(X)` in matrix form
"""
k = len(self.Phi)
matrix = self.I
Vz = 2 * self.V - self.I
for i in range(k):
VRz = paddle.to_tensor(expm((Vz * 1j * self.Phi[i]).numpy()))
if i % 2 != k % 2:
matrix = matrix @ VRz @ self.U
else:
matrix = matrix @ VRz @ dagger(self.U)
return matrix
def block_encoding_circuit(self) -> Circuit:
r"""generate a block encoding of :math:`P(X)` by quantum circuit
Returns:
a quantum circuit of unitary that is the block encoding of :math:`P(X)`
"""
set_dtype("complex64")
Phi = paddle.cast(self.Phi, dtype="float32")
U = self.U
system_register = [i for i in range(self.n)]
cir = Circuit(self.n + 1)
k = len(self.Phi)
for i in range(k):
if i % 2 == 0:
cir.oracle(U, system_register)
else:
cir.oracle(dagger(U), system_register)
cir.extend(qubitization(self.V, Phi[-i - 1]))
return cir
def block_encoding_unitary(self) -> paddle.Tensor:
r"""generate the unitary of above circuit for verification
Returns:
a block encoding unitary of :math:`P(X)`
"""
U = self.block_encoding_circuit().unitary_matrix()
zero_state = paddle.zeros([2**1, 2**1])
zero_state[0, 0] += 1
U = U @ paddle.kron(paddle.eye(2**self.n), zero_state)
return partial_trace(U, 2**self.n, 2**1, A_or_B=2)
...@@ -43,6 +43,11 @@ def shadow_sample( ...@@ -43,6 +43,11 @@ def shadow_sample(
mode: Representation form of the input quantum state. mode: Representation form of the input quantum state.
hamiltonian: A ``Hamiltonian`` object representing the observable to be measured. Defaults to ``None``. hamiltonian: A ``Hamiltonian`` object representing the observable to be measured. Defaults to ``None``.
method: Method for sampling random Pauli operators, which should be one of ``'CS'``, ``'LBCS'``, and ``'APS'``. Defaults to ``'CS'``. method: Method for sampling random Pauli operators, which should be one of ``'CS'``, ``'LBCS'``, and ``'APS'``. Defaults to ``'CS'``.
Raises:
ValueError: Hamiltonian has a bad form
NotImplementedError: The backend of ``state`` should be ``StateVector`` or ``DensityMatrix``
Returns: Returns:
Randomly chosen Pauli operators and their corresponding measurement result in a list of shape ``(sample_shots, 2)``. Randomly chosen Pauli operators and their corresponding measurement result in a list of shape ``(sample_shots, 2)``.
......
...@@ -172,7 +172,7 @@ def bell_diagonal_state(prob: List[float]) -> State: ...@@ -172,7 +172,7 @@ def bell_diagonal_state(prob: List[float]) -> State:
prob: The prob of each bell state. prob: The prob of each bell state.
Raises: Raises:
Exception: The state should bu a pure state if the backend is state_vector. Exception: The state should be a pure state if the backend is state_vector.
NotImplementedError: If the backend is wrong or not implemented. NotImplementedError: If the backend is wrong or not implemented.
Returns: Returns:
...@@ -325,7 +325,7 @@ def completely_mixed_computational(num_qubits: int) -> State: ...@@ -325,7 +325,7 @@ def completely_mixed_computational(num_qubits: int) -> State:
num_qubits: The number of qubits contained in the quantum state. num_qubits: The number of qubits contained in the quantum state.
Raises: Raises:
Exception: The state should bu a pure state if the backend is state_vector. Exception: The state should be a pure state if the backend is state_vector.
NotImplementedError: If the backend is wrong or not implemented. NotImplementedError: If the backend is wrong or not implemented.
Returns: Returns:
...@@ -365,7 +365,7 @@ def r_state(prob: float) -> State: ...@@ -365,7 +365,7 @@ def r_state(prob: float) -> State:
prob: The parameter of the R-state to be generated. It should be in :math:`[0,1]` . prob: The parameter of the R-state to be generated. It should be in :math:`[0,1]` .
Raises: Raises:
Exception: The state should bu a pure state if the backend is state_vector. Exception: The state should be a pure state if the backend is state_vector.
NotImplementedError: If the backend is wrong or not implemented. NotImplementedError: If the backend is wrong or not implemented.
Returns: Returns:
...@@ -410,7 +410,7 @@ def s_state(prob: float) -> State: ...@@ -410,7 +410,7 @@ def s_state(prob: float) -> State:
prob: The parameter of the S-state to be generated. It should be in :math:`[0,1]` . prob: The parameter of the S-state to be generated. It should be in :math:`[0,1]` .
Raises: Raises:
Exception: The state should bu a pure state if the backend is state_vector. Exception: The state should be a pure state if the backend is state_vector.
NotImplementedError: If the backend is wrong or not implemented. NotImplementedError: If the backend is wrong or not implemented.
Returns: Returns:
...@@ -455,7 +455,7 @@ def isotropic_state(num_qubits: int, prob: float) -> State: ...@@ -455,7 +455,7 @@ def isotropic_state(num_qubits: int, prob: float) -> State:
prob: The parameter of the isotropic state to be generated. It should be in :math:`[0,1]` . prob: The parameter of the isotropic state to be generated. It should be in :math:`[0,1]` .
Raises: Raises:
Exception: The state should bu a pure state if the backend is state_vector. Exception: The state should be a pure state if the backend is state_vector.
NotImplementedError: If the backend is wrong or not implemented. NotImplementedError: If the backend is wrong or not implemented.
Returns: Returns:
......
...@@ -39,7 +39,10 @@ class State(object): ...@@ -39,7 +39,10 @@ class State(object):
num_qubits: The number of qubits contained in the quantum state. Defaults to ``None``, which means it will be inferred by the data. num_qubits: The number of qubits contained in the quantum state. Defaults to ``None``, which means it will be inferred by the data.
backend: Used to specify the backend used. Defaults to ``None``, which means to use the default backend. backend: Used to specify the backend used. Defaults to ``None``, which means to use the default backend.
dtype: Used to specify the data dtype of the data. Defaults to ``None``, which means to use the default data type. dtype: Used to specify the data dtype of the data. Defaults to ``None``, which means to use the default data type.
Raises:
Exception: The shape of the data is not correct.
NotImplementedError: If the backend is wrong or not implemented.
""" """
def __init__( def __init__(
self, data: Union[paddle.Tensor, np.ndarray, QCompute.QEnv], num_qubits: Optional[int] = None, self, data: Union[paddle.Tensor, np.ndarray, QCompute.QEnv], num_qubits: Optional[int] = None,
...@@ -55,6 +58,8 @@ class State(object): ...@@ -55,6 +58,8 @@ class State(object):
data = paddle.cast(data, dtype) data = paddle.cast(data, dtype)
self.dtype = dtype if dtype is not None else paddle_quantum.get_dtype() self.dtype = dtype if dtype is not None else paddle_quantum.get_dtype()
if self.backend == Backend.StateVector: if self.backend == Backend.StateVector:
if data.shape[-1] == 1:
data = paddle.squeeze(data)
self.data = data self.data = data
if num_qubits is not None: if num_qubits is not None:
if data.shape[-1] != 2 ** num_qubits: if data.shape[-1] != 2 ** num_qubits:
...@@ -91,6 +96,33 @@ class State(object): ...@@ -91,6 +96,33 @@ class State(object):
raise NotImplementedError raise NotImplementedError
self.num_qubits = num_qubits self.num_qubits = num_qubits
@property
def ket(self) -> paddle.Tensor:
r""" return the ket form in state_vector backend
Returns:
ket form of the state
"""
if self.backend != Backend.StateVector:
raise Exception("the backend must be in state_vector to raise the ket form of state")
return self.data.reshape([2 ** self.num_qubits, 1])
@property
def bra(self) -> paddle.Tensor:
r""" return the bra form in state_vector backend
Returns:
bra form of the state
"""
if self.backend != Backend.StateVector:
raise Exception("the backend must be in state_vector to raise the bra form of state")
return paddle.conj(self.data.reshape([1, 2 ** self.num_qubits]))
def numpy(self) -> np.ndarray: def numpy(self) -> np.ndarray:
r"""get the data in numpy. r"""get the data in numpy.
...@@ -132,6 +164,9 @@ class State(object): ...@@ -132,6 +164,9 @@ class State(object):
hamiltonian: Input observable. hamiltonian: Input observable.
shots: Number of measurement shots. shots: Number of measurement shots.
Raises:
NotImplementedError: If the backend is wrong or not implemented.
Returns: Returns:
The expectation value of the input observable for the quantum state. The expectation value of the input observable for the quantum state.
""" """
...@@ -148,14 +183,6 @@ class State(object): ...@@ -148,14 +183,6 @@ class State(object):
[1 / math.sqrt(2), -1j / math.sqrt(2)], [1 / math.sqrt(2), -1j / math.sqrt(2)],
[1 / math.sqrt(2), 1j / math.sqrt(2)], [1 / math.sqrt(2), 1j / math.sqrt(2)],
], dtype=self.dtype) ], dtype=self.dtype)
# gate_for_x = paddle.to_tensor([
# [1 / math.sqrt(2), 1j / math.sqrt(2)],
# [1j / math.sqrt(2), 1 / math.sqrt(2)],
# ], dtype=self.dtype)
# gate_for_y = paddle.to_tensor([
# [1 / math.sqrt(2), -1j / math.sqrt(2)],
# [-1j / math.sqrt(2), 1 / math.sqrt(2)],
# ], dtype=self.dtype)
if self.backend == Backend.StateVector: if self.backend == Backend.StateVector:
simulator = state_vector.unitary_transformation simulator = state_vector.unitary_transformation
elif self.backend == Backend.DensityMatrix: elif self.backend == Backend.DensityMatrix:
...@@ -210,11 +237,26 @@ class State(object): ...@@ -210,11 +237,26 @@ class State(object):
qubits_idx: The index of the qubit to be measured. Defaults to ``None``, which means all qubits are measured. qubits_idx: The index of the qubit to be measured. Defaults to ``None``, which means all qubits are measured.
plot: Whether to draw the measurement result plot. Defaults to ``False`` which means no plot. plot: Whether to draw the measurement result plot. Defaults to ``False`` which means no plot.
Raises:
Exception: The number of shots should be greater than 0.
NotImplementedError: If the backend is wrong or not implemented.
NotImplementedError: The qubit index is wrong or not supported.
Returns: Returns:
Measurement results Measurement results
""" """
if self.backend == paddle_quantum.Backend.StateVector: if self.backend == paddle_quantum.Backend.QuLeaf:
if shots == 0:
raise Exception("The quleaf server requires the number of shots to be greater than 0.")
state_data = self.data
QCompute.MeasureZ(*state_data.Q.toListPair())
result = state_data.commit(shots, fetchMeasure=True)['counts']
result = {''.join(reversed(key)): value for key, value in result.items()}
if qubits_idx is not None:
# new_result = [(self.__process_string(key, qubits_idx), value) for key, value in result.items()]
# result = self.__process_similiar(new_result)
pass
elif self.backend == paddle_quantum.Backend.StateVector:
prob_amplitude = paddle.multiply(paddle.conj(self.data), self.data).real() prob_amplitude = paddle.multiply(paddle.conj(self.data), self.data).real()
elif self.backend == paddle_quantum.Backend.DensityMatrix: elif self.backend == paddle_quantum.Backend.DensityMatrix:
prob_amplitude = paddle.zeros([2 ** self.num_qubits]) prob_amplitude = paddle.zeros([2 ** self.num_qubits])
......
...@@ -63,6 +63,12 @@ def construct_trotter_circuit( ...@@ -63,6 +63,12 @@ def construct_trotter_circuit(
and ``'even_odd'`` grouping methods. Defaults to None. and ``'even_odd'`` grouping methods. Defaults to None.
coefficient: Custom coefficients corresponding to terms of the Hamiltonian. Only works for ``method='custom'``. Defaults to None. coefficient: Custom coefficients corresponding to terms of the Hamiltonian. Only works for ``method='custom'``. Defaults to None.
permutation: Custom permutation of the Hamiltonian. Only works for ``method='custom'``. Defaults to None. permutation: Custom permutation of the Hamiltonian. Only works for ``method='custom'``. Defaults to None.
Raises:
ValueError: The order of the trotter-suzuki decomposition should be either 1, 2 or 2k (k an integer)
ValueError: Shape of the permutation and coefficient array don\'t match
ValueError: Grouping method ``grouping`` is not supported, valid key words: 'xyz', 'even_odd'
ValueError: The method ``method`` is not supported, valid method keywords: 'suzuki', 'custom'
Hint: Hint:
For a more detailed explanation of how this function works, users may refer to the tutorials on Paddle Quantum's website: https://qml.baidu.com/tutorials/overview.html. For a more detailed explanation of how this function works, users may refer to the tutorials on Paddle Quantum's website: https://qml.baidu.com/tutorials/overview.html.
...@@ -155,7 +161,7 @@ def construct_trotter_circuit( ...@@ -155,7 +161,7 @@ def construct_trotter_circuit(
def __get_suzuki_num(order): def __get_suzuki_num(order):
r"""计算阶数为 order 的 suzuki product formula 的 trotter 数。 r"""compute the Trotter number of the suzuki product formula with the order of ``order``
""" """
if order == 1 or order == 2: if order == 1 or order == 2:
n_suzuki = order n_suzuki = order
...@@ -169,32 +175,34 @@ def __get_suzuki_num(order): ...@@ -169,32 +175,34 @@ def __get_suzuki_num(order):
def __sort_pauli_word(pauli_word, site): def __sort_pauli_word(pauli_word, site):
r"""将 pauli_word 按照 site 的大小进行排列,并同时返回排序后的 pauli_word 和 site。 r"""reordering the ``pauli_word`` by the value of ``site``, return the new pauli_word and site after sort.
Note: Note:
这是一个内部函数,一般你不需要直接使用它。 This is an intrinsic function, user do not need to call this directly
""" """
sort_index = np.argsort(np.array(site)) sort_index = np.argsort(np.array(site))
return ''.join(np.array(list(pauli_word))[sort_index].tolist()), np.array(site)[sort_index] return ''.join(np.array(list(pauli_word))[sort_index].tolist()), np.array(site)[sort_index]
def _add_trotter_block(circuit, tau, grouped_hamiltonian, order): def _add_trotter_block(circuit, tau, grouped_hamiltonian, order):
r"""添加一个 trotter 块,i.e. :math:`e^{-iH\tau}`,并使用 Trotter-Suzuki 分解对其进行展开。 r"""add a Trotter block, i.e. :math:`e^{-iH\tau}`, use Trotter-Suzuki decomposition to expand it.
Args: Args:
circuit (Circuit): 需要添加 trotter 块的电路 circuit: target circuit to add the Trotter block
tau (float or tensor): 该 trotter 块的演化时间 tau: evolution time of this Trotter block
grouped_hamiltonian (list): 一个由 Hamiltonian 对象组成的列表,该函数会默认该列表中的哈密顿量为 Trotter-Suzuki 展开的基本项 grouped_hamiltonian: list of Hamiltonian objects, this function uses these as the basic terms of Trotter-Suzuki expansion by default
order (int): Trotter-Suzuki 展开的阶数 order: The order of Trotter-Suzuki expansing
Note (关于 grouped_hamiltonian 的使用方法): Note:
以二阶的 trotter-suzki 分解 S2(t) 为例,若 grouped_hamiltonian = [H_1, H_2],则会按照 About how to use grouped_hamiltonian:
(H_1, t/2)(H_2, t/2)(H_2, t/2)(H_1, t/2) 的方法进行添加 trotter 电路 For example, consider Trotter-Suzuki decomposition of the second order S2(t), if grouped_hamiltonian = [H_1, H_2], it will add Trotter circuit
特别地,若用户没有预先对 Hamiltonian 进行 grouping 的话,传入一个单个的 Hamiltonian 对象,则该函数会按照该 Hamiltonian with (H_1, t/2)(H_2, t/2)(H_2, t/2)(H_1, t/2). Specifically, if user does not pre-grouping the Hamiltonians and put a single Hamiltonian object,
的顺序进行正则(canonical)的分解:依然以二阶 trotter 为例,若传入单个 H,则添加 (H[0:-1:1], t/2)(H[-1:0:-1], t/2) 的电路 this function will make canonical decomposition according to the order of this Hamiltonian: for second order, if put a single Hamiltonian H,
the circuit will be added with (H[0:-1:1], t/2)(H[-1:0:-1], t/2)
Warning: Warning:
本函数一般情况下为内部函数,不会对输入的合法性进行检测和尝试修正。推荐使用 construct_trotter_circuit() 来构建时间演化电路 This function is usually an intrinsic function, it does not check or correct the input.
To build time evolution circuit, function ``construct_trotter_circuit()`` is recommanded
""" """
if order == 1: if order == 1:
__add_first_order_trotter_block(circuit, tau, grouped_hamiltonian) __add_first_order_trotter_block(circuit, tau, grouped_hamiltonian)
...@@ -206,18 +214,19 @@ def _add_trotter_block(circuit, tau, grouped_hamiltonian, order): ...@@ -206,18 +214,19 @@ def _add_trotter_block(circuit, tau, grouped_hamiltonian, order):
def _add_custom_block(circuit, tau, grouped_hamiltonian, custom_coefficients, permutation): def _add_custom_block(circuit, tau, grouped_hamiltonian, custom_coefficients, permutation):
r""" 添加一个自定义形式的 trotter 块 r"""Add a custom Trotter block
Args: Args:
circuit (Circuit)): 需要添加 trotter 块的电路 circuit: target circuit to add the Trotter block
tau (float or tensor): 该 trotter 块的演化时间 tau: evolution time of this Trotter block
grouped_hamiltonian (list): 一个由 Hamiltonian 对象组成的列表,该函数会默认该列表中的哈密顿量为 trotter-suzuki 展开的基本项 grouped_hamiltonian: list of Hamiltonian objects, this function uses these as the basic terms of Trotter-Suzuki expansion by default
order (int): trotter-suzuki 展开的阶数 order: The order of Trotter-Suzuki expansing
permutation (np.ndarray): 自定义置换 permutation: custom permutation
custom_coefficients (np.ndarray or Tensor): 自定义系数 custom_coefficients: custom coefficients
Warning: Warning:
本函数一般情况下为内部函数,不会对输入的合法性进行检测和尝试修正。推荐使用 construct_trotter_circuit() 来构建时间演化电路 This function is usually an intrinsic function, it does not check or correct the input.
To build time evolution circuit, function ``construct_trotter_circuit()`` is recommanded
""" """
# combine the grouped hamiltonian into one single hamiltonian # combine the grouped hamiltonian into one single hamiltonian
...@@ -235,10 +244,10 @@ def _add_custom_block(circuit, tau, grouped_hamiltonian, custom_coefficients, pe ...@@ -235,10 +244,10 @@ def _add_custom_block(circuit, tau, grouped_hamiltonian, custom_coefficients, pe
def __add_first_order_trotter_block(circuit, tau, grouped_hamiltonian, reverse=False, optimization=False): def __add_first_order_trotter_block(circuit, tau, grouped_hamiltonian, reverse=False, optimization=False):
r""" 添加一阶 trotter-suzuki 分解的时间演化块 r"""Add a time evolution block of the first order Trotter-Suzuki decompositon
Notes: Notes:
这是一个内部函数,你不需要使用它 This is an intrinsic function, user do not need to call this directly
""" """
if not reverse: if not reverse:
for hamiltonian in grouped_hamiltonian: for hamiltonian in grouped_hamiltonian:
...@@ -338,20 +347,20 @@ def optimal_circuit(circuit: paddle_quantum.ansatz.Circuit, theta: Union[paddle. ...@@ -338,20 +347,20 @@ def optimal_circuit(circuit: paddle_quantum.ansatz.Circuit, theta: Union[paddle.
def __add_second_order_trotter_block(circuit, tau, grouped_hamiltonian): def __add_second_order_trotter_block(circuit, tau, grouped_hamiltonian):
r""" 添加二阶 trotter-suzuki 分解的时间演化块 r"""Add a time evolution block of the second order Trotter-Suzuki decompositon
Notes: Notes:
这是一个内部函数,你不需要使用它 This is an intrinsic function, user do not need to call this directly
""" """
__add_first_order_trotter_block(circuit, tau / 2, grouped_hamiltonian) __add_first_order_trotter_block(circuit, tau / 2, grouped_hamiltonian)
__add_first_order_trotter_block(circuit, tau / 2, grouped_hamiltonian, reverse=True) __add_first_order_trotter_block(circuit, tau / 2, grouped_hamiltonian, reverse=True)
def __add_higher_order_trotter_block(circuit, tau, grouped_hamiltonian, order): def __add_higher_order_trotter_block(circuit, tau, grouped_hamiltonian, order):
r""" 添加高阶(2k 阶) trotter-suzuki 分解的时间演化块 r"""Add a time evolution block of the higher order (2k) Trotter-Suzuki decompositon
Notes: Notes:
这是一个内部函数,你不需要使用它 This is an intrinsic function, user do not need to call this directly
""" """
assert order % 2 == 0 assert order % 2 == 0
p_values = get_suzuki_p_values(order) p_values = get_suzuki_p_values(order)
...@@ -423,13 +432,13 @@ def add_n_pauli_gate( ...@@ -423,13 +432,13 @@ def add_n_pauli_gate(
def __group_hamiltonian_xyz(hamiltonian): def __group_hamiltonian_xyz(hamiltonian):
r""" 将哈密顿量拆分成 X、Y、Z 以及剩余项四个部分,并返回由他们组成的列表 r"""Decompose the Hamiltonian as X, Y, Z, and remainder term, return the list of them.
Args: Args:
hamiltonian (Hamiltonian): Paddle Quantum 中的 Hamiltonian 类 hamiltonian: Hamiltonian class in Paddle Quantum
Notes: Notes:
X、Y、Z 项分别指的是该项的 Pauli word 只含有 X、Y、Z,例如 'XXXY' 就会被分类到剩余项 X, (Y, Z) means the terms whose Pauli word only include X, (Y, Z). For example, 'XXXY' would be a remainder term.
""" """
grouped_hamiltonian = [] grouped_hamiltonian = []
coeffs, pauli_words, sites = hamiltonian.decompose_with_sites() coeffs, pauli_words, sites = hamiltonian.decompose_with_sites()
...@@ -455,14 +464,16 @@ def __group_hamiltonian_xyz(hamiltonian): ...@@ -455,14 +464,16 @@ def __group_hamiltonian_xyz(hamiltonian):
def __group_hamiltonian_even_odd(hamiltonian): def __group_hamiltonian_even_odd(hamiltonian):
r""" 将哈密顿量拆分为奇数项和偶数项两部分 r"""Decompose the Hamiltonian into odd and even parts.
Args: Args:
hamiltonian (Hamiltonian): hamiltonian (Hamiltonian): Hamiltonian class in Paddle Quantum
Warning: Warning:
注意该分解方法并不能保证拆分后的奇数项和偶数项内部一定相互对易,因此不正确的使用该方法反而会增加 trotter 误差。 Note this decomposition cannot make sure the mutual commutativity among odd terms or even terms. Use this method incorrectly would cause larger
请在使用该方法前检查哈密顿量是否为可以进行奇偶分解:例如一维最近邻相互作用系统的哈密顿量可以进行奇偶分解 Trotter error.
Please check whether Hamiltonian could be odd-even decomposed before you call this method. For example, 1-D Hamiltonian with nearest neighbor
interaction could be odd-even decomposed.
""" """
grouped_hamiltonian = [] grouped_hamiltonian = []
coeffs, pauli_words, sites = hamiltonian.decompose_with_sites() coeffs, pauli_words, sites = hamiltonian.decompose_with_sites()
......
...@@ -497,6 +497,10 @@ def plot_density_matrix_graph(density_matrix: paddle_quantum.State, size: Option ...@@ -497,6 +497,10 @@ def plot_density_matrix_graph(density_matrix: paddle_quantum.State, size: Option
Args: Args:
density_matrix: The state vector or density matrix of quantum state with multi qubits, requiring the number of qubits greater than 1 density_matrix: The state vector or density matrix of quantum state with multi qubits, requiring the number of qubits greater than 1
size: Bar width, between 0 and 1, default is ``0.3``. size: Bar width, between 0 and 1, default is ``0.3``.
Raises:
TypeError: Expected density_matrix to be np.ndarray or paddle.Tensor or paddle_quantum.State
ValueError: Expected density matrix dim0 equal to dim1
""" """
if not isinstance( if not isinstance(
density_matrix, (np.ndarray, paddle.Tensor, paddle_quantum.State) density_matrix, (np.ndarray, paddle.Tensor, paddle_quantum.State)
......
...@@ -24,7 +24,7 @@ with open("README.md", "r", encoding="utf-8") as fh: ...@@ -24,7 +24,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
setuptools.setup( setuptools.setup(
name='paddle-quantum', name='paddle-quantum',
version='2.2.0', version='2.2.1',
author='Institute for Quantum Computing, Baidu INC.', author='Institute for Quantum Computing, Baidu INC.',
author_email='qml@baidu.com', author_email='qml@baidu.com',
description='Paddle Quantum is a quantum machine learning (QML) toolkit developed based on Baidu PaddlePaddle.', description='Paddle Quantum is a quantum machine learning (QML) toolkit developed based on Baidu PaddlePaddle.',
...@@ -44,6 +44,7 @@ setuptools.setup( ...@@ -44,6 +44,7 @@ setuptools.setup(
'paddle_quantum.operator', 'paddle_quantum.operator',
'paddle_quantum.state', 'paddle_quantum.state',
'paddle_quantum.qchem', 'paddle_quantum.qchem',
'paddle_quantum.qsvt',
'paddle_quantum.mbqc', 'paddle_quantum.mbqc',
'paddle_quantum.GIBBS', 'paddle_quantum.GIBBS',
'paddle_quantum.GIBBS.example', 'paddle_quantum.GIBBS.example',
...@@ -78,10 +79,11 @@ setuptools.setup( ...@@ -78,10 +79,11 @@ setuptools.setup(
'paddle_quantum.mbqc.VQSVD.example': ['*.txt'], 'paddle_quantum.mbqc.VQSVD.example': ['*.txt'],
}, },
install_requires=[ install_requires=[
'paddlepaddle>=2.2.0', 'paddlepaddle<=2.3.0',
'qcompute', 'scipy>=1.8.1',
'scipy', 'protobuf<=3.20.1',
'networkx>=2.5', 'networkx>=2.5',
'qcompute',
'matplotlib>=3.3.0', 'matplotlib>=3.3.0',
'tqdm', 'tqdm',
'openfermion', 'openfermion',
......
...@@ -565,7 +565,7 @@ ...@@ -565,7 +565,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3", "display_name": "Python 3.8.13 ('new_pq')",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
...@@ -593,6 +593,11 @@ ...@@ -593,6 +593,11 @@
"toc_position": {}, "toc_position": {},
"toc_section_display": true, "toc_section_display": true,
"toc_window_display": false "toc_window_display": false
},
"vscode": {
"interpreter": {
"hash": "58b83104798ee1b81625bc6249d4d66f2bacd4a7d411a9b7a27bac0b4765adf2"
}
} }
}, },
"nbformat": 4, "nbformat": 4,
......
...@@ -574,7 +574,7 @@ ...@@ -574,7 +574,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3", "display_name": "Python 3.8.13 ('new_pq')",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
...@@ -602,6 +602,11 @@ ...@@ -602,6 +602,11 @@
"toc_position": {}, "toc_position": {},
"toc_section_display": true, "toc_section_display": true,
"toc_window_display": false "toc_window_display": false
},
"vscode": {
"interpreter": {
"hash": "58b83104798ee1b81625bc6249d4d66f2bacd4a7d411a9b7a27bac0b4765adf2"
}
} }
}, },
"nbformat": 4, "nbformat": 4,
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
"\\end{align*}\n", "\\end{align*}\n",
"$$\n", "$$\n",
"\n", "\n",
"$A$ 和 $B$ 代表的是共享纠缠对的双方 Alice 和 Bob。根据贝尔对角态(Bell-digonal state)的定义,在贝尔态作为基底的密度矩阵可以表示为如下对角形式,\n", "$A$ 和 $B$ 代表的是共享纠缠对的双方 Alice 和 Bob。根据贝尔对角态(Bell-diagonal state)的定义,在贝尔态作为基底的密度矩阵可以表示为如下对角形式,\n",
"\n", "\n",
"$$\n", "$$\n",
"\\rho_{\\text{diag}} = p_1 | \\Phi^+\\rangle \\langle \\Phi^+ | + p_2 | \\Psi^+\\rangle \\langle \\Psi^+ | + \n", "\\rho_{\\text{diag}} = p_1 | \\Phi^+\\rangle \\langle \\Phi^+ | + p_2 | \\Psi^+\\rangle \\langle \\Psi^+ | + \n",
...@@ -324,7 +324,7 @@ ...@@ -324,7 +324,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3 (ipykernel)", "display_name": "Python 3",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
...@@ -338,7 +338,7 @@ ...@@ -338,7 +338,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.13" "version": "3.8.3"
}, },
"toc": { "toc": {
"base_numbering": 1, "base_numbering": 1,
......
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 数据编码分析\n",
"\n",
"*Copyright (c) 2022 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 概览\n",
"\n",
"目前很多研究表明变分量子算法(Variational Quantum Algorithms, VQA)有望在近期量子设备上展现出量子优势,其中备受关注的是用其解决监督学习任务。VQA 通常也被称为参数化量子电路 (Parameterized Quantum Circuit, PQC),在本教程中将其分为数据编码电路和量子神经网络两部分。数据编码电路将经典信息编码为量子态。编码后量子态的质量直接影响了后续的分类效果。从核函数的角度分析数据编码,不同的编码方式就对应不同的核函数,其对量子态的分类起着决定性作用 [1,2]。从统计学习理论的角度考察数据编码,它通常决定了算法的表达能力和泛化能力 [3,4]。 因此,我们有必要对数据编码方式进行系统的分析。最近文献 [5] 得到了一些进展,从量子信息的角度严格的分析了数据编码电路的宽度和深度对编码后量子态的影响。\n",
"\n",
"接下来本教程围绕文献 [5] 展开,主要分为原理和 Paddle Quantum 实现两部分:先介绍基本概念与主要结论,然后给出具体的实现方案。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 原理"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 基本概念\n",
"\n",
"图 1 是将经典数据编码为量子态的流程图。假设经典数据是分布 $D$ 的独立同分布采样,每个经典数据为 $\\pmb x$, 通过数据编码电路后变成了量子态 $\\rho(\\pmb x)$。这里引入分布 $D$ 上的**平均量子态**的概念,即 \n",
"\n",
"$$\n",
"\\bar{\\rho}:=\\mathbf{E}[\\rho(\\pmb x)]. \\tag{1}\n",
"$$\n",
"\n",
"在给定 $M$ 条数据组成的经典数据集 $S$ 时,我们通常使用平均值 \n",
"\n",
"$$\n",
"\\bar{\\rho}:=\\frac{1}{M}\\sum_{j=1}^M\\rho(\\pmb x_j) \\tag{2}\n",
"$$ \n",
"\n",
"近似 $S$ 的平均量子态。\n",
"\n",
"![illustration](figures/EncodingAnalysis-fig-illustration.png \"图 1:经典数据编码为量子态的流程图。\")\n",
"\n",
"有很多方法可以衡量量子态之间的距离,常用的有迹距离、保真度、**Petz-Rényi 散度**等等。在本教程中使用 Petz-Rényi 散度作为不同量子态之间距离的度量。具体的,量子态 $\\rho_0$ 和 $\\rho_1$ 的Petz-Rényi 散度定义为\n",
"\n",
"$$\n",
"D_2(\\rho_0||\\rho_1)=logTr[\\rho_0^2\\rho_1^{-1}]. \\tag{3}\n",
"$$\n",
"\n",
"$D_2$ 的取值越小表示两个量子态越接近。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 主要结论\n",
"\n",
"接下来我们借助 Petz-Rényi 散度这一度量工具考察编码后的平均量子态与最大混合态之间的距离。不难想到这个距离应该与经典数据集本身的性质和数据编码方式有关。具体的,假设经典数据集的每个特征维度满足一定的独立性且标准差至少为 $\\sigma$, 那么对于如图 2 所示的编码电路(宽度和深度分别为 $n$ 和 $D$)有如下不等式成立\n",
"\n",
"$$\n",
"D_2(\\bar{\\rho}||I/2^n)\\leq log(1+(2^n-1)2^{-D\\sigma^2}), \\tag{4}\n",
"$$\n",
"\n",
"其中 $I/2^n$ 为 $n$ 比特的最大混合态,$I$ 为单位矩阵。\n",
"\n",
"![encoding-u3](figures/EncodingAnalysis-fig-u3_circuit.png \"图 2:更一般的数据编码电路。其中 Etg 表示控制非门和 CZ 门的任意组合。\")\n",
"\n",
"更严格的定理描述和证明可以参考文献 [5],在本教程中我们主要体会这一结论意味着什么以及有那些启发:\n",
"\n",
"* 上述结论意味着随着电路深度增加,编码后的平均量子态以指数的速度趋向于最大混合态。例如一个二分类的数据集,0类和1类的平均量子态最终都趋向于最大混合态,那么从量子信息的角度看将无法区分这两类的平均量子态,也即从平均意义下无法区分经典数据特征。\n",
"\n",
"* 当经典数据特征维度很高时(例如图片数据)使用角度编码可能不是一个合适的选择,因为这很容易导致很深的数据编码电路,而加宽电路又会遇到贫瘠高原问题,这严重限制了 VQA 的能力。因此经典数据在输入给电路之前需要对其做一些降维操作。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Paddle Quantum 实现 \n",
"\n",
"这一节中主要使用 Paddle Quantum 在 MNIST 数据集上完成两个实验:\n",
"- 考察平均量子态和最大混合态之间的 Petz-Rényi 散度随着数据编码电路的深度的变化趋势;\n",
"- 考察随着数据编码电路的加深分类准确率的变化。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 首先导入相关的包"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"# 导入 numpy、paddle 和 paddle_quantum\n",
"import numpy as np\n",
"import paddle\n",
"import paddle_quantum\n",
"\n",
"# 构建量子电路\n",
"from paddle_quantum.ansatz import Circuit\n",
"\n",
"# 一些用到的函数\n",
"from numpy import pi as PI\n",
"from paddle import matmul, transpose, reshape, real, argmax, cast, mean, concat, real\n",
"from paddle_quantum.qinfo import pauli_str_to_matrix \n",
"from paddle_quantum.linalg import dagger\n",
"import paddle.nn.functional as F\n",
"\n",
"# 数据集工具包\n",
"from paddle_quantum.dataset import MNIST\n",
"\n",
"# 作图与计算时间\n",
"from matplotlib import pyplot as plt\n",
"from pylab import axes\n",
"import time"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 参数化量子电路\n",
"\n",
"![encoding-ry](figures/EncodingAnalysis-fig-ry_circuit.png \"图 3:参数化量子电路。\")\n",
"\n",
"图 3 中左侧红色框为数据编码电路,是图 2 的一个特例,右侧蓝色框为量子神经网络。其中数据编码电路由 $R_y$ 旋转门和控制非门组成,具体的电路深度 $D$ 由数据的特征维度决定。例如在本教程中将使用 MNIST 数据集,由于图片被降采样为 16 维的特征向量,因此在设计实验时选择量子比特数为 8、6、4、3、2 ,则默认情况下对应的电路深度为 2、3、4、6、8, 大于 16 维的位置用 0 填充,即作用 $R_y(0)$. 量子神经网络部分由单比特的通用门 $U3$ 和控制非门构成。具体的电路深度 $L$ 可以自由设置。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 将经典数据编码为量子态\n",
"\n",
"这里需要量桨提供的数据集处理工具 `dataset`。使用图 2 所示的数据编码电路将 MNIST 数据中的经典数据编码为量子态并保存起来,以便将其输入给量子神经网络进行训练。"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"train_data, test_data = [], []\n",
"\n",
"# 二分类的手写数字类别\n",
"classes = [3,6]\n",
"\n",
"training_data_num = 1000\n",
"testing_data_num = 200\n",
"qubit_num_list = [8, 6, 4, 3, 2]\n",
"\n",
"# 使用不同的宽度和深度的电路编码经典数据并保存下来\n",
"for qubit_num in qubit_num_list:\n",
" \n",
" # 训练数据集\n",
" train_dataset = MNIST(mode='train', encoding='real_entangled_encoding', num_qubits=qubit_num,\n",
" classes=classes,\n",
" data_num=training_data_num,\n",
" downscaling_method='resize', target_dimension=16,\n",
" need_relabel=True, return_state=True)\n",
"\n",
" # 验证数据集\n",
" val_dataset = MNIST(mode='test', encoding='real_entangled_encoding', num_qubits=qubit_num,\n",
" classes=classes,\n",
" data_num=testing_data_num,\n",
" downscaling_method='resize', target_dimension=16,\n",
" need_relabel=True, return_state=True)\n",
"\n",
" # 获取数据集的输入和标签\n",
" train_x, train_y = train_dataset.quantum_image_states, train_dataset.labels\n",
" test_x, test_y = val_dataset.quantum_image_states, val_dataset.labels\n",
" train_data.append((train_x, train_y))\n",
" test_data.append((test_x, test_y))"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[1000, 256]\n",
"(1000,)\n",
"[200, 256]\n",
"(200,)\n"
]
}
],
"source": [
"print(train_data[0][0].shape)\n",
"print(train_data[0][1].shape)\n",
"print(test_data[0][0].shape)\n",
"print(test_data[0][1].shape)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 构建量子神经网络"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"# 搭建整个优化流程图\n",
"class Net(paddle.nn.Layer):\n",
" \"\"\"\n",
" 创建模型训练网络\n",
" \"\"\"\n",
" def __init__(self, n, depth):\n",
" # 初始化部分,通过n, depth给出初始电路\n",
" super(Net, self).__init__()\n",
" self.n = n\n",
" self.depth = depth\n",
" \n",
" self.circuit = Circuit(n)\n",
" # 先搭建广义的旋转层\n",
" for i in range(n):\n",
" self.circuit.rz(qubits_idx=i)\n",
" self.circuit.ry(qubits_idx=i)\n",
" self.circuit.rz(qubits_idx=i)\n",
"\n",
" # 默认深度为 depth = 1\n",
" # 对每一层搭建电路\n",
" for d in range(3, depth + 3):\n",
" # 搭建纠缠层\n",
" for i in range(n-1):\n",
" self.circuit.cnot(qubits_idx=[i, i + 1])\n",
" self.circuit.cnot(qubits_idx=[n-1, 0])\n",
" # 对每一个量子比特搭建Ry\n",
" for i in range(n):\n",
" self.circuit.ry(qubits_idx=i)\n",
"\n",
" # 定义前向传播机制、计算损失函数 和交叉验证正确率\n",
" def forward(self, state_in, label):\n",
" \"\"\"\n",
" 输入:state_in:输入量子态,shape: [-1, 1, 2^n] -- 此教程中为[BATCH, 1, 2^n]\n",
" label:输入量子态对应标签,shape: [-1, 1]\n",
" 计算损失函数:交叉熵损失函数\n",
" \"\"\"\n",
" # 按照随机初始化的参数 theta \n",
" Utheta = self.circuit.unitary_matrix()\n",
"\n",
" # 因为 Utheta是学习到的,我们这里用行向量运算来提速而不会影响训练效果\n",
" state_out = matmul(state_in, Utheta) # 维度 [-1, 1, 2 ** n]\n",
"\n",
" # 测量得到泡利 Z 算符的期望值 <Z>\n",
" Ob1 = paddle.to_tensor(pauli_str_to_matrix([[1.0, 'z0']], self.n))\n",
" E_Ob1 = matmul(matmul(state_out, Ob1), transpose(paddle.conj(state_out), perm=[0, 2, 1]))\n",
" E_Ob1_re = reshape(real(E_Ob1), [-1, 1])\n",
"\n",
" Ob2 = paddle.to_tensor(pauli_str_to_matrix([[1.0, 'x0']], self.n))\n",
" E_Ob2 = matmul(matmul(state_out, Ob2), transpose(paddle.conj(state_out), perm=[0, 2, 1]))\n",
" E_Ob2_re = reshape(real(E_Ob2), [-1, 1])\n",
"\n",
" outputs = concat([E_Ob1_re, E_Ob2_re], axis=-1)\n",
"\n",
" # 计算损失函数和准确率\n",
" loss = F.cross_entropy(outputs, label)\n",
" # validation accuracy\n",
" acc = mean(cast(argmax(outputs, axis=-1) == label, \"float32\"))\n",
" \n",
" return loss, acc"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"# 定义一个分类器\n",
"def QClassifier(train_x, train_y, test_x, test_y, N, D, EPOCH, LR, BATCH, seed=0):\n",
" \"\"\"\n",
" 量子二分类器\n",
" \"\"\"\n",
" train_y = paddle.to_tensor(train_y, dtype=\"int64\")\n",
" test_y = paddle.to_tensor(test_y, dtype=\"int64\")\n",
"\n",
" N_train, in_dim = train_x.shape\n",
" \n",
" # 定义优化图,Net是用户定义的量子神经网络\n",
" paddle.seed(0)\n",
" net = Net(n=N, depth=D)\n",
"\n",
" # 一般来说,我们利用Adam优化器来获得相对好的收敛\n",
" # 当然你可以改成SGD或者是RMSprop\n",
" opt = paddle.optimizer.Adam(learning_rate=LR, parameters=net.parameters())\n",
"\n",
" # 优化循环\n",
" for ep in range(EPOCH):\n",
" for itr in range(N_train // BATCH):\n",
" input_state = train_x[itr * BATCH:(itr + 1) * BATCH]\n",
" input_state = reshape(input_state, [-1, 1, 2 ** N])\n",
" label = train_y[itr * BATCH:(itr + 1) * BATCH]\n",
"\n",
" test_input_state = reshape(test_x, [-1, 1, 2 ** N])\n",
"\n",
" # 前向传播计算损失函数\n",
" train_loss, train_acc = net(state_in=input_state, label=label)\n",
"\n",
" if itr % 3 == 0:\n",
" # 计算测试集上的正确率\n",
" loss_useless, test_acc = net(state_in=test_input_state, label=test_y)\n",
" print(\"epoch:\", ep, \"iter:\", itr,\n",
" \"train loss: %.4f\" % train_loss.numpy(),\n",
" \"train acc: %.4f\" % train_acc,\n",
" \"test acc: %.4f\" % test_acc)\n",
"\n",
" # 反向传播极小化损失函数\n",
" train_loss.backward()\n",
" opt.minimize(train_loss)\n",
" opt.clear_grad()\n",
" \n",
" # 返回测试集的分类准确率\n",
" _, test_acc = net(state_in=test_input_state, label=test_y) \n",
" \n",
" return test_acc.numpy()"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"***************************** qubit num : 8 *****************************\n",
"epoch: 0 iter: 0 train loss: 0.6913 train acc: 0.5200 test acc: 0.4250\n",
"epoch: 0 iter: 3 train loss: 0.6836 train acc: 0.6500 test acc: 0.5200\n",
"epoch: 1 iter: 0 train loss: 0.6759 train acc: 0.6850 test acc: 0.5550\n",
"epoch: 1 iter: 3 train loss: 0.6709 train acc: 0.6950 test acc: 0.5650\n",
"epoch: 2 iter: 0 train loss: 0.6651 train acc: 0.6650 test acc: 0.5700\n",
"epoch: 2 iter: 3 train loss: 0.6621 train acc: 0.7050 test acc: 0.6050\n",
"epoch: 3 iter: 0 train loss: 0.6589 train acc: 0.6900 test acc: 0.6000\n",
"epoch: 3 iter: 3 train loss: 0.6597 train acc: 0.7050 test acc: 0.6250\n",
"epoch: 4 iter: 0 train loss: 0.6563 train acc: 0.7150 test acc: 0.6550\n",
"epoch: 4 iter: 3 train loss: 0.6566 train acc: 0.7250 test acc: 0.6700\n",
"***************************** qubit num : 6 *****************************\n",
"epoch: 0 iter: 0 train loss: 0.6966 train acc: 0.4900 test acc: 0.5250\n",
"epoch: 0 iter: 3 train loss: 0.6938 train acc: 0.4850 test acc: 0.5450\n",
"epoch: 1 iter: 0 train loss: 0.6884 train acc: 0.5350 test acc: 0.5450\n",
"epoch: 1 iter: 3 train loss: 0.6862 train acc: 0.5400 test acc: 0.5700\n",
"epoch: 2 iter: 0 train loss: 0.6775 train acc: 0.5750 test acc: 0.5850\n",
"epoch: 2 iter: 3 train loss: 0.6744 train acc: 0.6100 test acc: 0.6000\n",
"epoch: 3 iter: 0 train loss: 0.6642 train acc: 0.6350 test acc: 0.5950\n",
"epoch: 3 iter: 3 train loss: 0.6615 train acc: 0.6450 test acc: 0.6200\n",
"epoch: 4 iter: 0 train loss: 0.6526 train acc: 0.6900 test acc: 0.6300\n",
"epoch: 4 iter: 3 train loss: 0.6560 train acc: 0.6250 test acc: 0.6350\n",
"***************************** qubit num : 4 *****************************\n",
"epoch: 0 iter: 0 train loss: 0.7081 train acc: 0.4650 test acc: 0.5350\n",
"epoch: 0 iter: 3 train loss: 0.6994 train acc: 0.4950 test acc: 0.5900\n",
"epoch: 1 iter: 0 train loss: 0.6902 train acc: 0.5450 test acc: 0.5750\n",
"epoch: 1 iter: 3 train loss: 0.6942 train acc: 0.5150 test acc: 0.5800\n",
"epoch: 2 iter: 0 train loss: 0.6869 train acc: 0.6100 test acc: 0.5850\n",
"epoch: 2 iter: 3 train loss: 0.6923 train acc: 0.5150 test acc: 0.6000\n",
"epoch: 3 iter: 0 train loss: 0.6825 train acc: 0.5700 test acc: 0.6050\n",
"epoch: 3 iter: 3 train loss: 0.6917 train acc: 0.5200 test acc: 0.5950\n",
"epoch: 4 iter: 0 train loss: 0.6776 train acc: 0.5850 test acc: 0.5800\n",
"epoch: 4 iter: 3 train loss: 0.6901 train acc: 0.5450 test acc: 0.6050\n",
"***************************** qubit num : 3 *****************************\n",
"epoch: 0 iter: 0 train loss: 0.7104 train acc: 0.4550 test acc: 0.5000\n",
"epoch: 0 iter: 3 train loss: 0.6931 train acc: 0.4950 test acc: 0.4900\n",
"epoch: 1 iter: 0 train loss: 0.6928 train acc: 0.4600 test acc: 0.4700\n",
"epoch: 1 iter: 3 train loss: 0.6968 train acc: 0.4800 test acc: 0.4800\n",
"epoch: 2 iter: 0 train loss: 0.6964 train acc: 0.5100 test acc: 0.4750\n",
"epoch: 2 iter: 3 train loss: 0.7004 train acc: 0.5150 test acc: 0.4600\n",
"epoch: 3 iter: 0 train loss: 0.6961 train acc: 0.5050 test acc: 0.4800\n",
"epoch: 3 iter: 3 train loss: 0.6957 train acc: 0.5300 test acc: 0.4850\n",
"epoch: 4 iter: 0 train loss: 0.6938 train acc: 0.5250 test acc: 0.5150\n",
"epoch: 4 iter: 3 train loss: 0.6919 train acc: 0.5150 test acc: 0.5250\n",
"***************************** qubit num : 2 *****************************\n",
"epoch: 0 iter: 0 train loss: 0.7031 train acc: 0.5450 test acc: 0.4800\n",
"epoch: 0 iter: 3 train loss: 0.6960 train acc: 0.5350 test acc: 0.4550\n",
"epoch: 1 iter: 0 train loss: 0.6932 train acc: 0.5100 test acc: 0.5100\n",
"epoch: 1 iter: 3 train loss: 0.6930 train acc: 0.5250 test acc: 0.5200\n",
"epoch: 2 iter: 0 train loss: 0.6930 train acc: 0.5150 test acc: 0.4750\n",
"epoch: 2 iter: 3 train loss: 0.6933 train acc: 0.5050 test acc: 0.4600\n",
"epoch: 3 iter: 0 train loss: 0.6925 train acc: 0.5400 test acc: 0.4600\n",
"epoch: 3 iter: 3 train loss: 0.6909 train acc: 0.5350 test acc: 0.4700\n",
"epoch: 4 iter: 0 train loss: 0.6939 train acc: 0.5450 test acc: 0.4750\n",
"epoch: 4 iter: 3 train loss: 0.6853 train acc: 0.5600 test acc: 0.4750\n",
"主程序段总共运行了 125.62448906898499 秒\n"
]
}
],
"source": [
"time_start = time.time()\n",
"\n",
"acc_list = []\n",
"\n",
"for i in range(5):\n",
" print('***************************** qubit num : %s *****************************'%qubit_num_list[i])\n",
" train_x, train_y = train_data[i]\n",
" test_x, test_y = test_data[i]\n",
"\n",
" acc = QClassifier(\n",
" train_x,\n",
" train_y,\n",
" test_x,\n",
" test_y,\n",
" N=qubit_num_list[i], # 所需的量子比特数量\n",
" D=qubit_num_list[i] + 2, # 采用的电路深度\n",
" EPOCH=5, # 训练 epoch 轮数\n",
" LR=0.05, # 设置学习速率\n",
" BATCH=200, # 训练时 batch 的大小\n",
" seed=0\n",
" )\n",
" acc_list.append(acc) \n",
"\n",
"time_span = time.time() - time_start\n",
"print('主程序段总共运行了', time_span, '秒')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 考察平均量子态与最大混合态的 Petz-Rényi 散度"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[1.9842463368536747, 0.5256437464833253, 0.1964853652901484, 0.05162865627740749, 0.022790754263846934]\n",
"[1.8628793706046225, 0.6064395532199834, 0.1529884926031612, 0.04173701231534178, 0.009023512622560221]\n"
]
},
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"fig = plt.figure(1)\n",
"ax = axes([0.15, 0.15, 0.8, 0.8])\n",
"\n",
"\n",
"def average_encode_state(train_x):\n",
" d1, d2 = train_x.shape\n",
" density_matrices = np.reshape(train_x, [d1, d2, 1]) @ np.reshape(np.conj(train_x), [d1, 1, d2])\n",
" return np.mean(density_matrices, axis=0)\n",
"\n",
"\n",
"depth_list = [2, 3, 4, 6, 8]\n",
"\n",
"Q_2Renyi_D3_list = []\n",
"Q_2Renyi_D6_list = []\n",
"\n",
"for i,q in enumerate(qubit_num_list):\n",
" train_x, train_y = train_data[i]\n",
" train_x = train_x.numpy()\n",
" \n",
" train_x0 = train_x[train_y == 0]\n",
" train_x1 = train_x[train_y == 1]\n",
" average_state0 = average_encode_state(train_x0.real)\n",
" average_state1 = average_encode_state(train_x1.real)\n",
"# print(average_state)\n",
" Q_2Renyi_D3 = np.log2(np.trace(average_state0 @ average_state0) * 2 ** q)\n",
" Q_2Renyi_D6 = np.log2(np.trace(average_state1 @ average_state1) * 2 ** q)\n",
"# bound = np.sum(np.sqrt(S0)) ** 2 / 2 ** num_qubits\n",
" Q_2Renyi_D3_list.append(Q_2Renyi_D3)\n",
" Q_2Renyi_D6_list.append(Q_2Renyi_D6)\n",
"\n",
"print(Q_2Renyi_D3_list)\n",
"print(Q_2Renyi_D6_list)\n",
"\n",
"\n",
"func3, = ax.plot(depth_list, Q_2Renyi_D3_list, linewidth=1.5,\n",
" marker=\"<\",\n",
" linestyle=\":\",\n",
" color=\"r\"\n",
" )\n",
"\n",
"func6, = ax.plot(depth_list, Q_2Renyi_D6_list, linewidth=1.5,\n",
" marker=\"o\",\n",
" linestyle=\"-.\",\n",
" color=\"g\"\n",
" )\n",
"\n",
"plt.xticks(fontsize=22)\n",
"plt.yticks(fontsize=22)\n",
"\n",
"plt.xlabel(\"Depth\", fontsize=22)\n",
"plt.ylabel(r\"$D_2(\\overline{\\rho} || I/2^n)$\", fontsize=22)\n",
"ax.semilogy()\n",
"ax.legend(handles=[func3, func6],\n",
" labels=[\"Digit 3\", \"Digit 6\"],\n",
" loc=\"best\",\n",
" fontsize=18)\n",
"ax.grid(axis=\"y\")\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 考察分类准确率受到的影响"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[array([0.675], dtype=float32), array([0.61], dtype=float32), array([0.585], dtype=float32), array([0.535], dtype=float32), array([0.475], dtype=float32)]\n"
]
},
{
"data": {
"image/png": "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1IjI+fJDmTJlOOuss45LbgADBw6kU6dOpKWl8emnJ9aSkS+88AJLliyhZ8+ePPHEE1mSG0DZsmU57bTTohKbJTgTs7ZsgU6dYGuw9dpjxIEDB3jkkUdo164d5cuXp2TJkjRs2JABAwbwwQcfhHSOP/74gyeffJLu3bvToEEDSpQoQVJSEm3btmXSpEmkpaX5fd2XX37JgAEDqFWrFgkJCSQlJdGoUSOGDBly3JftoUOHmDhxIq1bt6ZMmTIZ9f/atWvHnXfeyaFDWdcEzn4Pznev7O677wbg3nvvzWjTuXNnrrzySkSEiRMnBvycTz/9NCLCJZdcctx5s9+DW7hwYca5U1NTGT9+PE2aNKFEiRJUrVqVSy+9lN9++y3ge7355pu0b9+eMmXKUKFCBc4//3wWLVqU5byR4quAn5iYGPJrUlNTefXVVxk8eDCNGzembNmylCpVimbNmjF27Fh27tzp93WZ75V98skndO3alaSkJEqVKkXbtm2ZPXt2wPf89ddfueyyy6hWrRolS5akWbNmPPTQQwH/feXkmWeeAeDmm2/O1evzlaraI58ebdq00aJg1apVUXnfa65RjYtz21i2YcMGbdy4sQJapkwZ7d69uw4cOFDbt2+vpUuX1k6dOmVp36lTJwV0wYIFWfa/+uqrCmjt2rW1S5cuOmjQIO3cubMmJiYqoBdddJGmp6dnec3HH3+sCQkJCmirVq10wIAB2qdPH01OTtaEhAS9+uqrM9qmpaXpueeeq4AmJSXphRdeqIMHD9Zzzz1Xa9WqpYBu2bIly/kBdV8TzqJFi3T48OHaokULBbRFixY6fPhwHT58uE6YMEGXL1+ugNavX1/T0tL8/nk1adJEAV24cGHGvpdeekkBHT58eJa2CxYsUEDbt2+vXbt21TJlymiPHj20T58+Wq1aNQW0Tp06umvXruPeZ/z48QqoiGiHDh108ODBevrpp2t8fLzedNNNChz3d5NbH3zwgSYkJGipUqX0999/D/l1GzduVEArVKig7dq100suuUS7d++ulStXVkAbNmyof/zxx3Gvq1evngJ65513qojomWeeqYMGDdJWrVplfOaZM2ce97offvgh49x16tTRgQMH6vnnn6/FixfXiy++OOO869evDyn+zZs3K6Dx8fF68OBBXbNmjd533306cuRIHTdunM6dO/e4f7OhCOc7B0jRAN/BUU8CRflxIiW4Tp2Of0ya5I4dOOD/+EsvueN//JF1f1yc+5eZ/REXl7Xd7Nnu9T/+6P/8n3ziji9ffvyxSElLS8v4Urnooot0586dWY7v3btX582bl2VfoAS3atUq/eKLL457j82bN2vLli0V0Ndffz3LsS5duiig06dPP+51f/75p6akpGQ8/+yzzxTQ1q1b6/79+7O0TU9P18WLF+uBAwey7M+e4HzuvvtuBfTuu+8+7tjZZ5+tgM6ZM+e4Y/Pnz1dAmzdvnmV/TgkO0OTkZN22bVvGsd27d2vr1q0V0AceeCDL61JSUjQuLk4TEhJ07ty5WY49+eSTGefMbYK79dZbdfjw4TpgwICMZF+2bFl96623wjrP3r17dfbs2XrkyJEs+//66y+9/PLLFdBRo0Yd9zpfIipevPhxn+/+++9XQBs1anTc63x/XsOGDdPDhw9n7P/++++1SpUqGX8uoSa4jz76SAGtWrWqPvbYY1qsWLGMc/ge7du3z/L3FopIJTjrojQx58wzoWpVKF7cPS9Rwj0/66zoxuXP7NmzWb58OfXr1+e1116jQoUKWY6XLVuWrl27hnSupk2bcpafD1mjRg0eeughAGbNmpXl2LZt2wC44IILjntdpUqVaNOmzXFtzznnHEqXLp2lrYjQoUOHiBTVHjNmDADPPvvscccmTZoEwLXXXhvWOUWEKVOmULVq1Yx9SUlJjB07FoD58+cf9z7p6elcdtlldO/ePcux66+/3u+fczjefPNNXn75ZWbOnMm3335L5cqVeemll+jbt29Y5ylbtiy9evUiISEhy/6SJUvyzDPPUKxYMd58882Arx8zZsxxn+/WW28lKSmJdevWZem+XbRoEd988w1JSUk8/fTTFPf9BwOaN2/OXXfdFVbsQEYX6s6dO7n55psZMGAAq1atYu/evXz66ac0bdqUJUuWMGDAgLDPHQk2itJERLApXaVKBT9eufLxx6+5BiZPdsntyBHo1w/8fF8C0Lhx8PO3bBn8eF58+OGHAAwdOpSSJUvm+XxHjx7l008/ZenSpWzdupVDhw6hquzbtw+An376KUv7M888k1WrVjFkyBDuuOMO2rZt63cQBEDr1q2Jj4/nxRdf5JRTTqFfv35Uq1YtzzFnd/HFF1OrVi0++ugjfvnlFxo2bAi4qQWzZ8+mbNmyDBs2LKxz1q1b1+9ABd8AmM2bN2fZ/9lnnwEwZMgQv+cbPHgwy5YtCyuGzNatWwfA7t27Wb16NQ899BD9+/dn0KBBTJs2LeDfQSDLly9n/vz5bNiwgQMHDrjuNaB48eL88ccf7Nq167hfngB69ux53L7ixYvTsGFDli9fzubNmzOmbvj+THr27Ol3tOqwYcO4/vrrw4rbVxT56NGjnH322UyfPj3jWJcuXfj444855ZRT+Pzzz1mwYAFdunQJ6/x5ZQnOxKRt22DUKBg50iW6LVuiHZF/v/76K4DfkYbh+umnn+jTp0/AofngJhtnNmHCBL799lvmzp3L3LlzKVWqFMnJyZx77rkMGzYsI7kAnHTSSTz++OPccsstjB49mtGjR9OwYUPat2/PRRddRN++fcP+YvanWLFiXHPNNdx55508//zzGVefkydP5ujRo1x22WWULVs2rHMGml9Xrlw5gOMGx2zatAmAevXq+X1doP3hKl++PO3atePtt9+md+/evP7667Rr1y7kRLF//36GDh0adFAIuL93fwkunD+X33//HYAGDRoE/CxJSUns2bMnpNiBLH+PV1111XHHa9euTY8ePZg1a1ZUEpx1UZqY9NZbMGkStGjhtm+9Fe2I/Itk5fP+/fuzevVqevfuzeLFi9mxYwdHjx5FVVmzZg1Axm/2PtWrVyclJYX58+czbtw42rRpw7Jly7jnnnto3LgxU6ZMydJ+zJgx/Prrrzz33HMMHTqUtLQ0pk2bxoABA0hOTj4ugebWyJEjSUxMZMqUKRw+fJjU1FReeOEFIPzuSSDXyzUF+vvJj+Wfhg8fDhC0SzG72267jdmzZ9OsWTNmz57N5s2bOXLkSMY9JN+0iex/7z7RXsYqc7IMlDh9+7dGYTi0JThj8sD3G7QvAeXWjz/+yMqVK6latSpvvfUWHTp0oGLFihlXVL4uMX/i4uI499xzmTBhAp9//jk7duxg4sSJHD16lNGjRx+XtKpXr86oUaOYNm0aGzZsYMWKFZx22mmsWLEi6PD+cFSpUoWBAweyY8cO3njjDd5++222bNlC586dadasWUTeI5iaNWsCx66ws8uPicdVqlQBXMWTUM2cORNwE8V79epFjRo1Mu7HHThwIKJJoVatWkDgz7579+6wrt4AGjdunHE/d8eOHX7b/Pnnn4CbR1jQLMEZkwfdunUDYNq0acd1k4XDd7O+Zs2afrsJw6kWUrp0acaOHUvt2rU5dOhQjsm3RYsW3HDDDQB8++23YUQdXObBJr4BJ6NHj47Y+YPp2LEjAK+99prf46+//nrE39M35zCcye++v/c6deocd2z69OkBr9xyo1OnTgC89957fq/Uc1ORJiEhIeM+YPaBPuDm+X3++ecAJCcnh33+vLIEZ0weXHTRRbRs2ZINGzYwdOjQ434D3rdvn9//+NmdfPLJxMXF8f3332d8Ifi89NJLAb+oH3nkkYyqIpmlpKSwZcsW4uLiMr48P/30Uz744AOOHj2apW1aWlrGZPRI3ZsC94XWtm1bli1bxmeffUbNmjXp06dPxM4fzOjRoxERXn75ZT755JMsxyZNmsTSpUvDPuf06dP55ptvjtuflpbGK6+8woMPPgi47tlQ+e7dZh9xmpKSwm233RZ2jMGcc845tGzZkt27d3PDDTeQmpqacWz16tXcf//9uTrvbbfdRlxcHJMnT+ajjz7K2J+WlsbYsWP5+eefqVWrVtgjTCMi0PwBe9g8OJ9oTfQuLH755Rdt1KhRxlyoCy64QAcNGqQdOnQIa6L3ddddp4DGxcVply5ddPDgwXrqqacqoLfddpsCWq9evSyvSUpKUkCbNm2qF198sQ4ePFjPPvtsjYuLU0DHjRuX0fbxxx/PmOTdpUsXHTJkiPbp00dr1KihgFavXl03bNiQ5fzkYh5cZtOnT884xz333BOwXU7z4ALNV1u/fr3fPxdV1fvuuy9j0vPZZ5+tQ4YM0RYtWmhcXJzecMMNCuh5550XNP7Mhg8fnjFBukePHjp06FA9//zzMybJx8XFBf2M/sycOTPjz6dFixY6aNAg7dixo8bFxemQIUMCTrzOaUJ2oH9jK1eu1IoVKyqgdevW1YEDB2q3bt1yPdHb56mnnlIRURHRs846S/v166cNGzbM+Pe2ZMmSsM5XZCd6A0OARcAeYD+QAowG4sI4R33fP5oQHh0DnKM78DGwE/gL+B64A0gMNQ5LcCeOvXv36vjx47V169ZapkwZLVmypDZo0EAHDhyoH374YZa2gb580tLSdPLkydqqVSstXbq0li9fXrt27apz584N+EU+bdo0HT58uDZv3lwrVKigJUqU0AYNGuhFF12kH330UZa269at07vvvlu7dOmiderU0cTERK1UqZK2atVK7733Xt2+fftxnyuvCW779u0KaEJCgm7evDlgu/xIcKqqM2bM0LZt22qpUqU0KSlJu3btqgsWLMioGjN48OCg8We2ePFiHTNmjCYnJ2u1atU0ISFBS5curU2bNtWrrrpKv/7665DPldmCBQu0S5cuWrFiRS1durS2bNlSn3rqKU1LS4t4glN1v5BdeumlWqVKFU1MTNTGjRvr+PHjNTU1NdcJzvc5evTooZUqVdKEhAStW7eujhw5MlfnKpIJDpjk/Yc6CLwHvA3s9fa9FWqSAyoDU4M8vvTOuRco7ef1t3rHjwLzgJnAdm/fUqBUKHFYgjMnuieeeEIBveSSS6IdShZXXHGFAvrII49EOxTjR6QSXMzMgxORfsC1wFbcVdVab381YAHQFxgDPJnTuVT1T2BEkPfyVb99XVUPZDuWDEzEXbWdq6rLvP1lgPeBjsB44KYwPp4xJ5y9e/fyyCOPANEpxPvTTz9RtWpVypcvn7FPVZk6dSovvfQSiYmJDB48uMDjMgUnZhIc4LujOtaX3ABUdZuIXAMsBMaJyNOqmp7bNxGRWkA37+mLfpqMAwR40JfcvDj2i8jlwFrgWhG5V1V35zYOY4qqhx9+OGOwzO+//86AAQPyXBorN1555RUefvhhWrVqRZ06dTh48CCrVq1i/fr1xMXF8fTTT2dMJzBFU0wkOBGpDbQBjuC6A7NQ1c9EZBNQC2gLLMnD243AjR79IXMC8+IoDviK+h03ZlZVfxGRpUAH4EJgevY2xpzo3n//fT777DOqVKnCVVddxaOPPhqVOC688EJ+/vlnli1bxg8//MDhw4epUqUK/fv358Ybb6RDhw5RicsUnJhIcEArb/uDqh4M0OYrXIJrRd4THPi/emsMlAJ2qurPQeLo4MVhCc6YbBbmV+HPMLVv35727dtHOwwTRSHPgxOR7jm3yjVfjRf/ZQccX1ls//VgQiAinYBGuCvFV4PEEXgFxQjEYYwxJv+FcwX3gYisA54DXorw/SdfDZcDQdrs97bhVWnN6gpvO9sbiBLxOERkJDASoFq1ajHz22xeJCUlZVSzN8aY/Hbo0KGIfHeGk+CW47rlHgEeEJHXgGdV9fip/TFIRMoB/b2nU4K1zQtVnQxMBkhOTtbOnTvn11sVmNWrV4dd/d0YY3KrRIkStGrVKueGOQi5i1JV2wDtcIMv4nBXQ1+JyFIRudQboJFbvqui0kHa+K6ucnspMQh3f+134KMAbQoijkLJTTcxxpj8FcnvmrBqUarqMlW9DKiNG9b/G3AW8DLwu4hMEJHcFLPb4G2DvdZXjXRDkDbB+LonpwaZZuA7t/9FliITR6ETHx+fpW6dMcbkl9TU1IisSwi5LLasqjtU9UGgIdAbd0VUEVcBZJ2IvCsi54dxyuXetrmIBFoW+YxsbUMmIs1wiViBl4I0/RFXRaWiiJwUoM2ZuY2jsCpbtmzE1gkzxphg9u7dG7FbInlaTcCrlPIeMAB4FDdBOh7oBcwVkZWhjL5U1Y3AN0Bx71xZeKMfa+OqnIRfBhyu9LYLVPWXIHEcAeZ6T4f6iaMhrpv2CK6qyQmhYsWK7Nq1iz///DNjMUZjjIkUVeXIkSP8+eef7Nq1i4oVK0bkvHmaByciTXDltYYB5XBXSJ94j0uBFsD7InKZqua02NAE3CTvB0Vkiaqu896jKuBbS2Ji5u5FEbkOuA740us69RdjghcL+J/7lt1EXFmwsSLyoap+6Z2nDG5wShxucM3uEM5VJCQmJlK3bl127tzJhg0bSEtLi3ZIxpgiJj4+nrJly1K3bl0SExMjcs6wE5yIxOMSwLVAJ9xV235cEnpGVX2rKz4qIn1xk6Fvw09lkMxUdZaIPAdcA6wUkXlAKtAVlzzfAZ7J9rLKuMnZwZa97QlUBXbjCjYHpapficg44EFgiYh86r22k3eeZbhVBU4oiYmJ1KhRgxo1akQ7FGOMCUnICU5EauLmd/0dqIFLbGtxSWeqqh43qlBV3/YKG/cM5T1U9VoRWYxbHqcTrrvzR9yV03O5rEHpG1wyXVVDWnJZVR8Ske+Af+Du/ZUAfgGeAh5R1cO5iMMYY0wBklDvp4jIEVzCAfgQeFpVPwzhdf8BrlDVE2718OTkZE1JSYl2GMYYU2SJyNeqmuzvWDhJ5yDuCqaxqvYIJbl5bsXKWhljjClg4dyDq5l97bRQqOpO3KrYxhhjTIEJp5JJ2MnNGGOMiZZwVhO4QEQ+FZEuQdqc67U5LzLhGWOMMbkTzj24y4Fk4Msgbb7EjTockYeYjDHGmDwLJ8G1Ab4N1lWpqvuBFbiyWMYYY0zUhJPgagAbQ2i3Eaieu3CMMcaYyAgnwR0GkkJolwRYLSdjjDFRFU6CWw2cLSIBk5y3qOjZwE95DexEt2ULdOoEW4MVITPGGBNQOAnuLaAsMEVEjquE6S14OgW3IOibkQnvxHX//bB4Mdx3X7QjMcaYwimcUl2lcEvanIxb7PO/uDqR4AoeXwrUB9YBrW3eXO5KdZUsCYf8VMwsUQIOHoxQYMYYU0REpFSXqv4FnA98iyu9dQfwqve409v3LdDNklvu/fILDBniEh1AYiIMHQrr10c3LmOMKWzCWi5HVX8TkTa4Vby7A/Vwa8D9hlvV+1211TDzpEYNKFcODh8GEbfduxeq27hUY4wJS9jrwXkJ7F3vYfLBtm0wahQMHAh9+8IHH8DSpdCuXbQjM8aYwiNPK3qb/PFWpmVZf/gBzjkHLrwQPvsMTj89enEZY0xhcsKt0VbYVK8O8+bB2WdDtWrRjsYYYwqPsBKciBQXkX+KyDIR2SUiaQEeR/Mr4BNRvXowZ45LcKmpbo6cMcaY4ELuohSREsAC4ExAcmqel6BMYFde6e7HLVpkA0+MMSaYcK7gbsYVUf4QOAV4BTeCMhFoDkwADgHjVdW6PvPJNde4K7jzzoOdtoysMcYEFE4i6g/sBQar6jpcckNVU1V1tareAVwM3C4igyIfqgE3kvLdd2HtWujeHfbti3ZExhgTm8JJcCcDy1R1r/dcAUQk3tdAVT8EvgKui1iE5jhdu8LMmfDNN24SuDHGmOOFM00gDtiR6bmvcFT5bPt/BnrkLSyTk1694LXXoEGDaEdijDGxKZwruM1AzUzPf/e22Wdm1ce7ujP5a8AASPYqsM2ZA2m2SJExxmQIJ8F9jyuq7PM5brTkPSJSFkBEBgPtgFURi9DkaMkS6N0bRo6E9PRoR2OMMbEhnAQ3F6gmIp0BVPV/wFLgHGCHiOwApuGu3h6JbJgmmPbt4a67YMoU+Mc/wKqBGmNMePfgpgM/4JbK8ekLvAhcAFQAduGmCbwdqQBNaO691xVlfuIJSEqCe+6JdkTGGBNd4SyXs19V/6eqmzLt266qvYByQC2giqo+lpeARGSIiCwSkT0isl9EUkRktIjkam6diMSLyCgR+VxEdojIIRHZKCJzRKSXn/ZTRUSDPH709z7RJgKPPQZXXOGS3YoV0Y7IGGOiK5xKJtcDf6nqf7If89aK+yuvwYjIJOBa3ITx+UAq0BV4BugqIv1VNeS7TCJSCde1egawE9elegCoA/wN2AbMCfDy/+EWb80uZgtlxcXB5Mlw6aXQsmW0ozHGmOgKp4vyMVyyOC7BRYKI9MMlt61AR1Vd6+2vhisR1hcYAzwZ4vnigNm45PYkME5VD2U6XhY34jOQ/6jq1LA/SJTFx0OXLu7n+fPhzz/dsjvGGHOiCafb7w8gP+tm3OZtx/qSG4CqbgOu8Z6OC6Or8iqgPfCeqt6YObl5592nqivzGnSsUoWHHnJXc3MCXaMaY0wRFk6CW4y7Goo4EakNtAGOADOzH1fVz4BNQHWgbYin9VVTydM9wcJKxFU7adnSzZf79NNoR2SMMQUrnAR3H1BbRO4VkUivFtDK2/6gqgcDtPkqW9uARKQGcCqQBiwVkVNE5C4R+beITBCR7iF8hi4i8piITBaR+0WkW24HukRLuXLw4YfQqJGbJ/fFF9GOyBhjCk449+BaAa8CdwL9ReRd4FeOlezKQlVfCePcvoJTvwZp81u2tsGc5m134Lo3HyLrZx0HLBGRvqq6PcA5LvOzb5WIDCpMXZuVKsEnn7hVwV97DdqGev1rjDGFXDgJbipuErcATYEmObQPJ8GV8bYHgrTZ723LhnC+ipm2jwGvAffjyoslA5Nw9+dmAp2yvXYF8DUwD5dUywGtgfFAC2CeiLTOPF0iMxEZCYwEqFatGgsXLgwh3Pz3yCMJlCuXSoyEY4wx+S6cBOdb/60w8HUlFgMWq+qQTMcWiMj5wE9ARxHpoqoLfAdV9Yls5zoAvC8inwCf4e4B3kaAFRNUdTIwGSA5OVk7d+6c908TQevXu7lyL78MdetGOxpjjMk/ISc4VR2Rj3H4rs5KB2nju8oLZSRn5jYvZD+oqr+LyPu4Ne664KYhBKWqR0RkAvAucGEIMcSkvXth+XK35I6tCm6MKcpiZdDEBm9bL0ibOtnaBrM+wM/+2oTzFe+rYlIrjNfElBYt4IMPYPNmOP98WxXcGFN0xUqCW+5tm4tIyQBtzsjWNpg1HLufVylAm8redn+A4/74zhXOa2JO+/ZuVfA1a+DCC21VcGNM0RROqS5/owoDCmcUpapuFJFvcIM5BpBtgIqIdAJq46qcLA3hfKki8h4wEFfq651s50sAOnpPU0KNE7jE234VtFUh8Le/wYwZ8OCDkJoa7WiMMSbyRENcW0VE0gltkIkAqqrxYQUi0h83qnErcI6qrvP2V8XdI2sG3KiqT2Z6zXW4wR5fqupl2c7XAvgGOAr0VtWPvP3xwMPATbjJ4yf75t6JSEtcIp2rqmmZzlUMuAE33SAO6O47XzDJycmakhJO/ix46emuhuXhw26bkBDtiIwxJnQi8rWqJvs7FolRlHG4e2etcYNE3gH2hBkjqjpLRJ7DzVtbKSLzOFZsuZx33meyvawybhHWrX7O962I3IirQzlXRL7ETRNoBTT0YhyQbWJ5feBtYKd3Rbkd1y15Gm4183Tg1lCSW2ERF+dWAu/dGypWhGnTXD1LY4wp7CI2itK70noFaISbYxY2Vb1WRBYDo3Hz0+JxAzumAM+Fs5KAd76nRWQlcAtueH9r3GoAk4EJqroh20u+xSXEM3FXjOfgkvrvwEvAJFX9OjefLZbFx7suy1tvhTJl3IoEEa9VY4wxBSzkLsqQTiZSEVgLvKSqt0TsxIVUYeiizOzOO2H8eLj5ZnjkEUtyxpjYF6kuyhyp6k4R+Qroh7tqMoXI/fe7eXKPPQZVq8LYsdGOyBhjci+iCc5zBKiRD+c1+UwEnnjCDTS54IJoR2OMMXkT0XlwIlId6IBbO84UQnFx8OijcPrpbk25FSuiHZExxuROOPPgOgY5XAZXfHk0UB5X3NgUclOnwpVXulUIbFVwY0xhE04X5UJyngcnuEojd+Y2IBM7Bg6El15yq4KXKQM9ekQ7ImOMCV04Ce5zAie4I7hJ0/OBGapqtTGKgFKlYM4cV5i5f3+YOxdibHEEY4wJKJx5cJ3zMQ4To5KS3KrgnTpBnz7wyy9uQrgxxsS6/BhFaYqYypXdquBffWXJzRhTeMTKagImxtWsCRdd5H7+8ENYuza68RhjTE5CTnAicp2IpIlIzyBtenptro5MeCbWHDzoRlb+7W+wcWO0ozHGmMDCuYLrgys+/H6QNh/g5sBdnIeYTAwrWdINPNm92yW5bduiHZExxvgXToJrAnyvQYpXesWQVwJN8xqYiV2tW7tVwX//Hbp1g127oh2RMcYcL5wEVwUI5ff17UDV3IVjCosOHeCdd2D1ajdXzhhjYk04oyh3A3VDaFcb2J+raEyhct55kJICp54a7UiMMeZ44VzBfQO0FZGTAzXwjrXDVTMxJ4DTTnNFmn/6CUaNglSb4m+MiRHhJLiXcFd874pIk+wHRaQxbtXteK+tOYEsXgz//jeMGOFWCDfGmGgLp5LJDBEZCvQCVorIUtxq2wCNcat4xwPvq+r0iEdqYtoVV8Aff8C4ca5u5fPP24KpxpjoCreSSX/gYWAUcLb38EkFngX+GZnQTGEzdizs2QMTJkDZsvDww5bkjDHRE1aC84oo3ygi44FzgXq4Asy/AZ+qqq0Dd4IbP96tCv7FF3D4MJQoEe2IjDEnqlzVovQS2RsRjsUUASLw1FPHklt6ultE1RhjCpp99ZiIi4tzFU/27YNzz7V5csaY6AinFuUQEflFRLoFadPdazMgMuGZwqx4cUhMhL//HWbOhC1b3LI7W7dGOzJjzIkgnCu4wUASsCBImwVAeWBoHmIyRURiIrz1FrRvD0OHukS3eDHcd1+0IzPGnAjCSXCnA9+p6pFADVT1MPAt0CKvgZmioXRpt45caqqrX5meDs895+7VlSwZ7eiMMUVZOAmuGrA5hHabvbbGALB+PVx8McTHu+elSkH37vD001b5xBiTf8JJcAcIrYhyFeBw7sLJuNe3SET2iMh+EUkRkdEikqsBMSISLyKjRORzEdkhIodEZKOIzBGRXgUVx4msRg2oWhVU3cjKQ4fg11/hqqugbl246y747bdoR2mMKWrC+bL+DuggIgGvzkSkOm7y9/e5CUZEJgH/BZKBRcAnwCnAM8CscJOLiFQClgLPAc29n98FNgJ/Ay4qiDiMWzdu1Cg3P27UKGjc2K0r16aNmzvXoIFLeMYYEynhzIN7DeiE+4K/SFV3Zj4oIhWBGUCi1zYsItIPuBbYCnRU1bXe/mq4wSt9gTHAkyGeLw6YDZzhvWacqh7KdLwsUD+/4zDOW28d+3nSpGM/9+wJGzbACy9ApUpu39GjrvtyyBCoZp3dxphckiDrl2ZtKFIM+BxoC+zFJY/MtSgvAsoBXwLneFVPQg9EJAVoAwxX1VeyHesELMQlnVrewqo5ne9q4HngPVUN2BWZn3EkJydrSkpKqG9tPIsXwznnQEIC9O0L11zjphdY2S9jTHYi8rWqJvs9FmqC805UHpgK9PZ2+V7s++qZA4xQ1bDWeBaR2rhuwyNAeVU96KfN70AtoIOqLgnhnCuBU4FzVTXY1IZ8i8MSXO79+CNMngxTp7oVwxs3hk8+gTp1oh2ZMSaWBEtw4dai3A30EZEWQHey1qL8SFVX5DLGVt72B39JxfMVLrG0AoImFhGpgUtuacBSETkFGIhbjHUn8JkXb/bsHtE4TO41aQKPPebuz82Y4e7X1arljs2c6QannHmmXdUZYwLLbS3Kb3Hz3fwSkVNVNZyBJg287a9B2vjG2TUI0sbnNG+7A7gGeIisn3UcsERE+qrq9nyMw+RRyZIwfLh7gBuJOXasm3rQqpUbsDJkiFuixxhjMstVgvNHRJKAIcAVuKubcM7t+3o6EKTNfm9bNoTzVcy0fQw36OV+4HfcyMhJuPXrZuIGzkQsDhEZCYwEqFatGgsXLgwhXBOOp5+OZ968asyeXZOrry7DTTcdZcyYtXTvvi3aoRljYkieE5yIdMUltT5ACdz9uKN5PW8e+YbxFwMWq+qQTMcWiMj5wE9ARxHpEuo9ulCo6mRgMrh7cJ07d47UqU0mPXq4LswvvoDnnitG795Nad++KWvWuMop/fvbUj3GnOhyO3m6rojcLSK/AB8Dg4CSwDfAjbh7VOHwXRWVDtLGd3W1L4TzZW7zQvaDqvo78L73tEs+xmHykQi0awevvOLqXQL8978wbJi7X3fLLbB2bXRjNMZETzirCRQXkcEi8gnwC/Av3Dwy323+pqp6hqo+lYuFTzd423pB2vjGz20I0sZnfYCf/bWpno9xmAJ2770wfz507QpPPgmnnOKmGoQxWNgYU0TkmOBEpI1X2WMLMA3oCqTjpgT0BZYBqOqaPMSx3Ns2F5FAJXjPyNY2mDUcu49WKUCbyt52f6Z9kY7DFDARtwbdjBmu/NcDD8Dpp7v9qm4x1o0box2lMaYgBExwInKDiKzATdy+BqgArAb+CdRW1T6q+i4RuN+mqhtx3ZvFgePWkvMmWNfGTbBeGsL5UoH3vKdd/ZwvAejoPU3J9LqIxmGiq0YNuOMOd1UHsGYN3Hgj1K8PvXvD3LmQlhbNCI0x+SnYFdzjuOH2e3AVQc5S1VNV9dFsQ+sjZYK3fVBEGvl2ikhV4Fnv6cTM1UNE5DoR+VFEslQcyXS+dGBk5kVaRSQeeBA4CdgEvJ3XOEzh0KSJm14wbhx8+SVceCE0agTf56pyqjEm1oVyD64Yrr5kYn4GoqqzcEWRqwMrvWr/bwFrgWbAO7hix5lVxpUJq+vnfN/iBrwkAHNF5AsRmYUbPXkTLnEPyD6hO5dxmEKiXj03efy331w35umnw0knuWPvvw8LF9r9OmOKimAJ7mrc/bUywAjgMxFZKyK3eyWtIk5Vr8WtBv4Nbn5aN2AdcB3QT1XD6lBS1aeBc4EPgEa4EmPFcMP4W6qq327GSMdhYk/x4jBgALz77rGFV++/H7p0gWbN3ACVXWEVnDPGxJoca1GKSBPgSuBS3EKmiuv6m4+rS3kz0EZV4/M10kLIalEWLgcPuqu655938+tKlHBXezffHO3IjDGBBKtFmWMXpar+qKr/xA2u6IMbvKHA+bg109p4b9I+UgEbEw2+smBLl8Ly5TBiBJx8sju2eTP8+9+wP9OY2y1b3CoHW7dGJVxjTA5CngenqmmqOltVL8Ilu7G45XLEeywSkZ9F5J7MgzOMKYxatoTnnoNe3kJLb7/t6l7WrAnXXgvffee6NBcvhvvui2qoxpgAwloux+8JRNriujAvwdVnVEBVNWJ1Lgsr66IsOlRdt+Xzz7vKKf6UKOG6OY0xBSdPXZQ5UdUvVPUq3KjDEcBijlU3MaZI8JUFe/llN62gVSso5v0KV6oUnHGGq6BijIkdeU5wPqp6UFVfUdVOwMmROq8xsaZ5czjrLEhPd1dthw5BSgp06ADnnQdvvgmpYa1nb4zJDxFLcJmp6i/5cV5jYsW2be6e3BdfuG337u6e3Jo1biWDunXd/TljTPTk+R6cCczuwZ140tJcCbAXXoAXX4TKlWHBAndvrls3iLfJNMZEVL7egzPGHBMfDz17ugnklb1y3o8/7tava9QIJkxwV3/GmPxnCc6YfDZrFrzxBjRoALffDnXquCLQxpj8ZQnOmHxWvDhccgl8+imsXg3XXQeNG7tj+/ZZWTBj8oslOGMKUJMm8NhjcNll7vmHH7olfGrWhMsvh2XLrNizMZFiCc6YKBowAL75xpUImzkT2raFNm1g795oR2ZM4RdyghORNBF5MYR2L4hInhdBNeZE0aqVq5CyebMrD9ayJZQr5469/DKsXBnV8IwptMK5gvPVnAy1rTEmDOXKuTl1U6a45wcPwg03uDXrOnSAadPcpHJjTGjyo4uyDGB1HIzJo5Il4eef4dFH4Y8/YNgwqFUL3nsv2pEZUzhELMGJSJyINMctMPp7pM5rzImsUiW3Ht2PP8K8eW5B1iZN3LEvv7SyYMYEEzTBeffd0kTEt4L18Mz7sh1PBb4DKgNv53PcxpxQ4uKga1c3p66RtxjV5MmuLFi9evCvf8HGjdGN0ZhYk9MVnGR6aLbn2R9HgV+BJ4C78idcY4zPv/8Nc+ZA69bwwANQv767h2eMcYKu2aaqGQlQRNKBqap6Rb5HZYzJka8sWM+e8Ouvrv5l1aruWGoqPP00DB0K1apFN05joiWce3D3Au/kUxzGmDyoV89dxV1/vXv+v//BP/7hyoINGgQLF9oEcnPiCTnBqeq9qjo7P4MxxkRG587HyoJ9/LEbnNKsGWzaFO3IjCk44Uz0Li4iVUWkRLb9ZUTkARGZIyJPi0idyIdpjAmXryzYpk1uwvjpp0ONGu7YG2+4UZh2VWeKspDXgxOR+4HbgbNVdam3Lw5IAVpwbHL3ZqCFqu6IfLiFi60HZ2JRejo0bOju27Vq5QamDBkCZcpEOzJjwhep9eC6Apt8yc3TF2gJfA/8HTc9oCZgY7mMiVFxcfDdd64sWFoaXH21K/b86qvRjsyYyAonwdUH1mTbdxFu+sClqjoFGABswSU+Y0yM8pUFW7ECliyBvn3hlFPcsdWrXbKzsmCmsAsnwVUEsq9F3B74VVVXAqhqOrAMqBuZ8Iwx+UkE2rVz9+jOOsvtmzbNLedTqxbccgusXRvdGI3JrXASXCqQ5HsiIlWBhsDibO3+wtWjzBURGSIii0Rkj4jsF5EUERnt3e8L5zz3iIgGefj9/VREpubwuh9z+9mMKQweeADmz4dzz3WLsZ5yClx8sQ1IMYVP0Ine2fwEdBCREqp6COiH657MnuBqANtzE4yITAKuBQ4B83FJtSvwDNBVRPp7V4nh+BZY4Wd/ThX8/ges87N/S5jvb0yhIuKS27nnwpYtbnWDtDS3XxWeeAL69YO61k9jYlw4CW4m8H/A5yKyGDeo5AiZJn+LSDzQGvg63EBEpB8uuW0FOqrqWm9/NWAB7r7eGODJME/9jqreE248wH9UdWouXmdMkVGjBtxxx7Hnq1a5CeS33AI9erj7eN26uaoqxsSacLr9HsclmmTgRqAkcIuqZr5aOx/Xjfl5LmK5zduO9SU3AFXdBlzjPR0XblelMSZymjeHX36BcePcPLoePVzx51Wroh2ZMccLp5LJYeBvQCfgEqCxqk7K1uwQcBMQ1oBjEakNtMFdEc70896fAZuA6kDbcM5tjIms+vVh/Hj47TeYMcOtQH7SSe7Y7NlWFszEjnC6KFE3K3xRkOMLcFd54WrlbX9Q1YMB2nwF1PLaLgnj3K1F5EGgArATN8rzfVU9ksPruojI6bgBM9tw9xo/ycU9QGOKpOLFYcAA9/C57z74+mto3Nh1Xw4fDhUqRC9Gc2ILK8FlJiKNgCrADlX9KY9xNPC2vwZp81u2tqHq5T0y+11ELvWuDAO5zM++VSIyyDctwhiT1aJF7qru+efhppvgtttgwgS48cZoR2ZORGElOBEphivXNRq3sCnAy8AV3vGh3rGRqvp9GKf2TSs4EKTNfm9bNsRz/oy7rzcXWA8UB04D7sZ1s34gIu1U9btsr1uBGyQzD5dUy+EGzozHlSSbJyKtVdVv2VoRGQmMBKhWrRoLFy4MMVxjioZ69VxSW7u2DHPm1OTw4T9ZuHAn27cnsmxZRf72t+2ULJmW84mMyStVDemBS4YfA2nAYVx5rnRgSqY29b19d4d6Xu91t+OmHEwL0ma81+bf4Zw7wLlmeed6L4zXFAeWeq97JpTXtGnTRo0xzhNPqIJq2bKq116r+t130Y7IFAVAigb4Dg5nROJ1uEEm84H6qnqqn2S5ATd37PwwzgvHrs5KB2nju8rbF+a5/bnP254nIgmhvEDdPbsJ3tMLIxCDMSeU6693ZcH69IEXX3SrG3Tq5ObYGZMfwklww4AdwCWqGmyy82og3CVzNnjbekHa+M65IUibUPmqkRTnWFdrOK+rFYEYjDmh+MqCvfKKW8Ln0UchOfnYHLpnn7WyYCaywrkH1xhYqKq7c2i3Dzf4JBzLvW1zESmp/kdSnpGtbV5UyvTz/oCtAr8unNcYY7KpVAluvvnY861b3UCU1FT429/cCMzevSEhpP4VY/wL5wpOcffXclITNx8u9BOrbgS+wV1RDch+XEQ6AbVxVU6WZj+eC5d42zWqGk6Xp+91X0UgBmOMp3p1tz7d/ffDmjXQv78rBbYknAlBxmQTToJbD7QIVklEREoCp+O6KcPlu7/1oDcFwXfOqsCz3tOJmmkemohcJyI/isgr2eKo6xVtTsy2X0RkWKb3ejzb8ZYi0tMrOZZ5fzER+Qdwvb/XGWPyrkYNuPNOWL8e5sxxqxs0aeKOzZ8PH3xg9+tMeMLpopyNG3b/D+DhAG1uxU2ofjfcQFR1log8hyvLtVJE5nGs2HI5XM3LZ7K9rDKu63Rrtv0Vgf8Cz4vIN7hVxssCzTk2j+4ZVf13ttfVxy3autN73XZct+RpuCvTdOBWVf0o3M9njAlNfDz07OkePo895hJc/fowciRccQVUqxa1EE0hEexqbIqIXJFp12O4RDJRRKaLyMXe/soicoGITAH+hZs79iy5oKrXAkNx3ZWdgG64UZnXAf1UNdTf3zbikvDXwElAH+A83Od9A+iqqmP8vO5bXDHnNUAz3IoJnXBLAL0EnKmqgZK7MSafvP22m0DesCHcfjvUqQN33RXtqEysEw1QNE5E0oGpqnpFpn2n4a7O6uPuyWV5CS6x9NDwJnkXWcnJyZqSkhLtMIwpUtasgcmToU0bGDIE9uyBqVPdIq1WFuzEIyJfq2qyv2NhVeZXV6KqGa5ayfu4e20/4ebG/QNoZsnNGJOfGjd2UwyGDHHP5851IzBr1oTLL3erHPh+b9+yxc2125r9JoY5IYS99IyqHlLV51S1t6qeqqpNVfV8VX1cVYOV2jLGmIgbNAiWL4cRI2DWLDc4pU0b2L/fjcpcvNgVgTYnnrC6KE14rIvSmIK1bx/8979w3XX+R1yWKAEHA61XYgqliHVRGmNMLCtb1k0S37jRdWGWLHnsWOPGsDQSs2hNoZHTNIH+ItI5F+dVVT0pF68zxpg8q1EDypWDw4chMdFt16xx3ZdXXulGYNaoEe0oTX7LKcGV4ViR43DYer7GmKjats1dzY0c6UZdrlvnlvJ5+WW49VbXJj0d4qwfq8jK6R7ch8CDuTmxBl9M9IRg9+CMiT179kBSkvu5d283teD2210Xpil8gt2Dy+kKbqslKmNMUeJLbmlp0KiRW3182jQYOBDuuAOaN49ufCZy7OLcGHNCio93JcA2bIBbboHZs+HUU13FFFM0WIIzxpzQqlaFBx90qxncfTec7y3XvHgxfGXrhhRqluCMMQa3Rt0990D58u75nXfCmWfCBRfYsj2FlSU4Y4zxY/ZsmDABUlKgQwfo2tXm0RU2AROcqsZZFRNjzImqXDkYN87do3v0UVi1ClZ7K12mpR2rd2lil13BGWNMEKVLw803wy+/wLBhbt8zz0C7dvD++5boYpklOGOMCUHJkpCQ4H6uWtWtUNCzJyQnu/Xq0tOjG585niU4Y4wJ0+DBsHYtTJkCe/fCxRfD3/8e7ahMdpbgjDEmFxIS3Ppzq1e7ieKXX+72b9vmnh89Gt34jCU4Y4zJk2LFYOhQOOcc9/zll929uqZN3RVeamp04zuRWYIzxpgIuuUWd0+uXDm3csHJJ8MLL0Q7qhOTJThjjImguDjo08fNn3vvPaheHebNO3bcui4LjiU4Y4zJByLQo4ebHD5litv3ww9uyZ5HH4UDB6Ib34nAEpwxxuQjETeXDtxUgqZNXTdm/fquUsrevVENr0izBGeMMQXktNNcd+X//ufmz91+OzRrBkeORDuyoskSnDHGFLD27WHuXPjyS3jgAShe3FVEefZZ2LEj2tEVHZbgjDEmSs44A0aMcD+vXAnXXefu0Y0dC9u3RzW0IsESnDHGxIDTT3dJrndveOQRd4/upptgz55oR1Z4xVyCE5EhIrJIRPaIyH4RSRGR0SISVqwico+IaJDHoYKIwxhjQtW8OUyf7qqjXHIJzJrlui/B7tPlRrFoB5CZiEwCrgUOAfOBVKAr8AzQVUT6q2q4JU2/BVb42R+wvkA+xWGMMSE55RSYOhX++ssVeU5NdVd4HTu6JXwaNox2hIVDzCQ4EemHSypbgY6qutbbXw1YAPQFxgBPhnnqd1T1nhiIwxhjwlKqlNsePOgWXP3Pf9ycuksvdSMwTzkluvHFuljqbrvN2471JRUAVd0GXOM9HVcAXYSxEocxxgCu7NekSbB+PYwZAzNmuPl0X34Z7chiW0x8SYtIbaANcASYmf24qn4GbAKqA22LehzGGONPzZrw+OMu0U2Y4ObSAcycCStWRDW0mBQTCQ5o5W1/UNWDAdp8la1tqFqLyIMiMllEJopIXxEpHoU4jDEmIqpVg1tvdXUv09Lgn/+EVq3cCMyvvsr59SeKWElwDbztr0Ha/Jatbah6AbcCVwFjgbeAn0WkUwHHYYwxERcf767e7rsPFi+GM8+ECy5wUw5OdLEyyKSMtw1WfnS/ty0b4jl/xt1PmwusB4oDpwF3A52AD0Sknap+F8k4RGQkMBKgWrVqLFy4MMRwjTEm9845B1q3jufdd2sxY0ZtFi1ayY4d+zhyREhIUESiHWHBi5UEF3Gq+qqf3QuABSIyC+gH/B/QM8LvOxmYDJCcnKydO3eO5OmNMSaoHj3gqaegRIk2APz977BmDdx1F5x3HidUoouVLkrfVVHpIG18V1f7IvB+93nb80QkIYpxGGNMxJUoceznM86ADRugWzdo1w7ef9/VvTwRxEqC2+Bt6wVpUydb27z40dsWBypHMQ5jjMlXV18N69bB88/Dtm3QsyeMHx/tqApGrCS45d62uYiUDNDmjGxt86JSpp/3Z/q5oOMwxph8l5joEt1PPx2bKA7w9dfwxhtuJGZRFBMJTlU3At/grqgGZD/ujXisjasusjQCb3mJt12jqhldjVGIwxhjCkxCAlx+uSvkDPDCCzBoEJx6KkybBkePRjW8iIuJBOeZ4G0fFJFGvp0iUhV41ns6MXMNSBG5TkR+FJFXMp9IROp6xZITs+0XERmW6b0ej0QcxhhTGE2a5K7gEhJg2DBo0gRefz3aUUVOzCQ4VZ0FPIerErJSROaIyFvAWqAZ8A6u2HFmlYHGQN1s+ysC/wX+EJGFIjJdRObgpg68ApQEnlHVf0coDmOMKXTi492qBStWwNtvQ1ISrPUKFKanw+HDUQ0vz2ImwQGo6rXAUFw3YSegG7AOuA7op6qh9hRvBB4GvgZOAvoA5+E+7xtAV1UdUwBxGGNMzIuLgz59ICXFLbYK8OabcNJJ8PTTrthzYSR6oowXjYLk5GRNSUmJdhjGGBO2JUvc0jyLFrnSYP/8J4waBaWDTaKKAhH5WlWT/R2LqSs4Y4wxsaF9e/j8c1i40A1CueUWN1G8MCmylUyMMcbkXadO7rFkCez3JlX99ZerljJqFJQvH9XwgrIrOGOMMTlq3x7OP9/9/PHHcNttUK8e3Hkn7NgR3dgCsQRnjDEmLH36wPLlLuGNH+8S3a23QmpqtCPLyhKcMcaYsLVs6RZa/f57tw7dsmVQzLvpdSDYeiwFyBKcMcaYXGveHKZPh3nz3EoFW7dC7dowejT89lvOr89PluCMMcbkWUKmdVkGDHBlwBo1gquugl9+iU5MNg8uH4nIHwRfHTwnlYE/IxROrChqn8k+T+wrap/JPk9W9VS1ir8DluBimIikBJrAWFgVtc9knyf2FbXPZJ8ndNZFaYwxpkiyBGeMMaZIsgQX2yZHO4B8UNQ+k32e2FfUPpN9nhDZPThjjDFFkl3BGWOMKZIswRljjCmSLMHFCBFJEJGuIvKoiKSIyF4ROSIim0Rkloh0jnaM4RKRMSIyQ0RWi8gOEUkVkT9EZJ6IXCoiEu0Y80pE/k9E1HvcEu14wiUiUzPF7+/xY7RjzA0RKSkit4rIVyKyW0T+EpH1IjJTRDpEO75QiUjnHP5+Mj/qRjveUIlIbRF5WkTWiMhBETkkImtF5HkRaRip97HlcmJHJ+AT7+etwOfAAaAZ0A/oJyL3q+q/ohRfbowFqgLfA0twn6cecC7QFegvIheranr0Qsw9ETkDuBVQoLAn6//hVq3PbktBB5JXItIA+BhohIt/AXAU92+vD/At7vMWBluBl4McPxNoCvwMbCyQiPJIRFoBnwLlgd+Bj7xDycDVwFAR6aaqS/L8Zqpqjxh44L70ZwHn+Dk2EPcfVIEu0Y41jM90NlDaz/7muP+4Clwe7Thz+dkSgVXAJuBt77PcEu24cvE5pnqxj4h2LBH6PKVxiTod9wtWfLbjlYBToh1nBD/vKu/v7/ZoxxJGzEu8mCcDCZn2JwAvese+jcR7WRdljFDVT1W1v6ou8nPsDdwXEcClBRpYHqjqYlU9rq64qv4ATPKeFrI1gjPch/vNeRSwJ8qxmGPuBE4CJqnqg6qalvmgqu5Q1Z+iE1pkiUg73L/BNI59P8Q0ESkBtPOe3q2qGQvseD/f6T09XURK5fX9LMEVHsu9be2oRhE5R73t4ahGkQsichbwD2C6qs6JdjzGEZHiwFXe08eiGUsBucLbfqiqm6MaSejSOPZ/P5gDwMG8vpndgys8Tva2he6eSHbePZJR3tPZ0YwlXN5voC8DO4EbohxOJHURkdOBMsA2YDHwiRau+6NtcF2Qm1R1vYi0Bvri7gNvAz5W1cXRDDBSvKubgd7TF6MZSzhUNVVE5gPdgHtFZLTvKk5EEoD7vaYvqtdvmReW4AoBEakOjPCevhnFUHJFRC7HDaJJwF2Btsf1Hvyfqr4dzdhyYTzQGBikqkWpovtlfvatEpFBqrqywKPJndO87SYReQR3lZ3ZXSLyDnCpv67zQmYAUBbYDrwX5VjCdS3wIe5q+wIRSfH2nwFUAJ7ADd7KM+uijHEiUgyYBiQB8wtpl1gHYDgwBOjo7buLY7+tFQoi0h64EXjHuy9aFKwArseN1i0D1AR64kYaNgPmiUitqEUXnorethUuuT2BG0lZAbgINyCoD/BsFGKLNF/35CuZ72MVBqr6C+6X3Lm4X3j7eI9auEEziyL2maI9osYeOY44+g9uVNFvQPVox5PHz1IS96X5MHAE9+VaM9pxhRH7T8AuoEa2Y1MppKMog3ze4sBS73M9E+14Qoz5di9eBV71czwZN7oyHTgp2vHm4XM2yvQ5m0Y7nlzE3x43ivonoDduPbjKuF9C1nmf61+ReC+7gothIvIkcCXuH0NXVd0a5ZDyRFUPquoqVf0ncBvQAngmymGF6v9w90FvVtVCfx80J6p6BJjgPb0wmrGEYV+mn1/IflBVU4CvcXMWOxVUUPnAd/W2VFVXRzWSMIlIeeAdXPdqd1Wdrap/eo93ge64wSV3icjJgc8UGktwMUpEHsV1Hf2BS25roxxSpE31tr28m8uxri/uN//hIrIw8wP3nxLgGm/ff6IWZWT5qpgUli7K9QF+9temej7Hki9EJJ5j90sLzeCSTHoAVYAv1HVVZqGq64BluPEhnfP6ZjbIJAaJyEPAzcAO4G+quirKIeWHXbjhwsVw9062RTeckMQR/Df/ht6jfIFEk/8qedv9UY0idMsz/VwJ/5U9KnvbwvKZsuuG+4VjP1AY7wP7yokFmzu629tWDNImJHYFF2NEZCLwT1wCOE9Vv4tySPmlIy657QZifjSiqtZXVfH34FgppX96+1pGMdRIusTbfhXVKEKkqptwv/2DKwWXhYhUAFp7T1OyHy8krvS2M1S1MCZp33y9Nv56brx9bbynga7CQ2YJLoaIyAO48kK7ccltefBXxC4ROVtEenqjQLMf68Cx7pUXNVu1CVMwRKSl93cUn21/MRH5B66LHODxgo8u18Z729tFJNm305u/+BxuNPLXuAE0hYqIVAZ6eU8LY/ckuJGTf+Gu5B4XkUTfAe/np4A6uF/wP/J7hjBYF2WMEJHewB3e03XAmADF9n9U1YkFFljuNQJeAnaLyDe4gTJlcWWUmnlt3sdNFzDRUR9XR3On93e0Hde1dxpuukA6cKuq5vmLpqCo6hzv/vU/gCUi8gWuq/9M3GfaBAxWbzhfITMMN5f0R41EIeIoUNXtInItLkGPBvp6//bAXbnVwFU3ukJV81wCzxJc7Mjc35zsPfz5DCgMCe4z3Dy3c3CjD9vjRq9txU1Wn6aq70QtOgNurtuTuC//Zri/K8VVeH8JV8/x6+iFlzuqeouILAGuw82JK4WbZvMYMFFV/4hmfHlwubedEtUo8khVXxaRlbg5pedwrB7tJlzieyxS4w6kcP4iY4wxxgRn9+CMMcYUSZbgjDHGFEmW4IwxxhRJluCMMcYUSZbgjDHGFEmW4IwxxhRJluCMMcYUSTbR25gYIyIbgHqZdilwAFfCbQ2uNuRrhaVOqYgogFe305gCY1dwxsSuj3CFnF/B1fBbh6twMw74VkRmi0hUl30RkXtEREXknmjGYYw/dgVnTOyaqKoLM+8QkThcwd3HvO1nItJeVXdEIT5jYppdwRlTiKhqurfycTLuiu4U4NHoRmVMbLIEZ0whpKq7cMVqAS7N3lUpIpVE5AERWSki+0XkgIh8IyI3BViHa6rX1TjCW0bnHRH5U0QOisjXInK5n9cocLf39G7v9Rqsy1JEBorIUi+mfSIyX0TOzsufhTGBWIIzpvD6ANgJxANdfDtF5DTgO9zyS+WBhbjVHerhujbnikjxAOc8C7dW2qnAJ8ASoAUwRUSeytb2ZdyKBHjblzM9VmQ/sYjcB0wHjuCWSvodOBeYLyLtQv3QxoTKEpwxhZS3pplvLa3mACJSEngXt/bZbUADVe2pqhfili2ah1vt+vYApx0FTAYaq+pgVe0KdAD24dYovDDT+48A3vGevqOqIzI93uF4o4EzVbWTqg70Yn4BKA7cl4s/AmOCsgRnTOH2p7et5G1HAA2AGao6UVWP+hqq6k5gOJAKjBb/K+puwi1ympbpdcs4tqr3TXmI9e7M68upajrHFrw9x1/XqTF5YQnOmMLN93843dv6rrBm+musqpuBtUBl3BVddrNU9bCf/a9627NFJLejr9/zE882YBeQyLEkbUxEWIIzpnCr7G13etuG3nZmtkEfGQ/c6t0AVfycb32A9/kNl0RLkPtE9FuA/Xu9bYlcntcYv2wenDGFlNfF2Mp7utLbxnvb9znWfRlIgc6d87okjSkwluCMKbx6ABVw99QWevs2Ao2B51T1/Vycs36A/XVxPT6HKODEaExuWRelMYWQiFTg2MCPV1R1u/fzXG87IJen7h9gCsFQb/u/zANXcEP+wX5ZNjHIEpwxhYiIxIlIb1zB5UbAj8A/MzWZjLuKG+7ViSzl5xwNROTSAG9RG5jolQTztT8DuNl7+mS29pu8bdOwP4wx+UzcVBpjTKzItJrAR8BWb3cJ3KCQ1rjJ2+DmoF2d6erN9/rTcCMW6+IGn3wHbAbK4hJRI2CZqrbN9JqpuCkEzwOX45JkiveenXBXaM+q6uhs71Ud+BkoBSzyfk4DZqvqbK9N0NUEMn3eBqq6Icc/IGNCZN0KxsSubt4283I5XwNfAtNV9Xt/L1LVlSJyOnAtcBEuKbYH/sAlrteAWQHecxlu8vW93vuXxA1geRZ40c97bRWRnsC/cANezgYEV6Vkdlif1pgIsys4Y0zmK7jLVXVqdKMxJjLsHpwxxpgiyRKcMcaYIskSnDHGmCLJ7sEZY4wpkuwKzhhjTJFkCc4YY0yRZAnOGGNMkWQJzhhjTJFkCc4YY0yR9P97ZP+oLEgSxAAAAABJRU5ErkJggg==",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"print(acc_list)\n",
"\n",
"fig = plt.figure(1)\n",
"ax = axes([0.15, 0.15, 0.8, 0.8])\n",
"\n",
"func36_acc, = ax.plot(depth_list, acc_list, linewidth=1.5,\n",
" marker=\"*\",\n",
" linestyle=\"--\",\n",
" color=\"b\"\n",
" )\n",
"\n",
"plt.xticks(fontsize=22)\n",
"plt.yticks(fontsize=22)\n",
"plt.ylim(0.48, 0.75)\n",
"plt.xlabel(\"Depth\", fontsize=22)\n",
"plt.ylabel(r\"Test Accuracy\", fontsize=22)\n",
"ax.legend(handles=[func36_acc,],\n",
" labels=[\"classifying 3 and 6\"],\n",
" loc=\"best\", fontsize=22)\n",
"ax.grid(axis=\"y\")\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 总结\n",
"\n",
"从实验结果上看每个类别的平均量子态确实随着数据编码电路的加深以指数的速度趋向于最大混合态,从而导致了最终量子神经网络分类准确率下降。在文献 [5] 的帮助下我们逐渐清楚了角度编码存在的一些局限性,同时也体会到了设计一种能够解决实际问题的(通常数据特征维度很高)数据编码策略是迫切需要的,当然也极富挑战性。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"## 参考文献\n",
"\n",
"[1] Schuld, M. \"Quantum machine learning models are kernel methods.\" [arXiv preprint arXiv:2101.11020.(2021)](https://arxiv.org/abs/2101.11020)\n",
"\n",
"[2] Lloyd, Seth, et al. \"Quantum embeddings for machine learning.\" [arXiv preprint arXiv:2001.03622 (2020).](https://arxiv.org/pdf/2001.03622.pdf)\n",
"\n",
"[3] Caro, Matthias C., et al. \"Encoding-dependent generalization bounds for parametrized quantum circuits.\" [Quantum 5 (2021): 582.](https://quantum-journal.org/papers/q-2021-11-17-582/)\n",
"\n",
"[4] Leonardo Banchi, et al. \"Generalization in quantum machine learning: A quantum information standpoint.\" [PRX Quantum 2.4 (2021): 040321.](https://journals.aps.org/prxquantum/abstract/10.1103/PRXQuantum.2.040321)\n",
"\n",
"[5] Li, Guangxi, et al. \"Concentration of Data Encoding in Parameterized Quantum Circuits.\" [arXiv preprint arXiv:2206.08273 (2022).](https://arxiv.org/pdf/2206.08273.pdf)\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.8.13 ('new_pq_dev')",
"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.13"
},
"vscode": {
"interpreter": {
"hash": "ea8ffbee42045ec282b7cb9811c8c332764b47a134b1aefa35055ac6f62b46f4"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Data Encoding Analysis\n",
"\n",
"*Copyright (c) 2022 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Overview\n",
"\n",
"Many studies are now exhibiting the promise of Variational Quantum Algorithms (VQA) in demonstrating quantum advantages on near-term quantum devices, with particular emphasis on using them to solve supervised learning tasks. VQA is also known as a Parameterized Quantum Circuit (PQC), which is divided into two main parts in this tutorial: the data encoding circuit and the quantum neural network. The data encoding circuit converts classical data into quantum states, and the quality of the quantum states has a direct impact on the classification results. When data encoding is analyzed through the perspective of kernel functions, different encoding methods correspond to different kernel functions, which play a critical role in the classification of quantum states [1,2]. It often determines the expressiveness and generalization ability of the method by analyzing data encoding from the standpoint of statistical learning theory [3,4]. Therefore, it is necessary to conduct a systematic analysis of data encoding. From the standpoint of quantum information, literature [5] critically analyzes the effect of the width and depth of the data encoding circuit on the quantum state.\n",
"\n",
"The next part of the tutorial is organized around the literature [5] and is divided into two parts: theory and Paddle Quantum implementation. The fundamental concepts and main conclusions are introduced first, followed by specific implementation solutions."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Theory"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Fundamental Concepts\n",
"\n",
"Figure 1 shows the flowchart for encoding classical data into quantum states. Assume that the classical data are independent identically distributed samples of distribution $D$, each classical data is $\\pmb x$, which becomes a quantum state $\\rho(\\pmb x)$ after passing through the data encoding circuit. Here the concept of **average quantum state** over the distribution $D$ is introduced, i.e. \n",
"\n",
"$$\n",
"\\bar{\\rho}:=\\mathbf{E}[\\rho(\\pmb x)]. \\tag{1}\n",
"$$\n",
"\n",
"Given a classical data set $S$ consisting of $M$ data, we usually use the average value to approximate the average quantum state of $S$\n",
"\n",
"$$\n",
"\\bar{\\rho}:=\\frac{1}{M}\\sum_{j=1}^M\\rho(\\pmb x_j). \\tag{2}\n",
"$$\n",
"\n",
"\n",
"![illustration](figures/EncodingAnalysis-fig-illustration.png \"Figure 1: Flowchart for encoding classical data into quantum states.\")\n",
"\n",
"There are various methods for measuring the distance between quantum states, including trace distance, fidelity, **Petz-Rényi divergence**, and others. In this tutorial, the Petz-Rényi divergence is used as a measure of the distance between different quantum states. Specifically, the Petz-Rényi divergence of the quantum states $\\rho_0$ and $\\rho_1$ is defined as\n",
"\n",
"$$\n",
"D_2(\\rho_0||\\rho_1)=logTr[\\rho_0^2\\rho_1^{-1}], \\tag{3}\n",
"$$\n",
"\n",
"the closer the two quantum states are, the smaller the value of $D_2$."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Main Result\n",
"\n",
"The Petz-Rényi divergence metric is then used to calculate the distance between the encoding average quantum state and the maximum mixed state. It is not difficult to believe that this distance is related to the property of the classical dataset and how the data are encoded. Assume that each feature dimension of the classical dataset satisfies certain independence and has a standard deviation of at least $\\sigma$. The following inequality then holds for the encoding circuit depicted in figure 2 (with width and depth $n$ and $D$, respectively)\n",
"\n",
"$$\n",
"D_2(\\bar{\\rho}||I/2^n)\\leq log(1+(2^n-1)2^{-D\\sigma^2}), \\tag{4}\n",
"$$\n",
"\n",
"where $I/2^n$ is the maximum mixed state of $n$ bits and $I$ is the unit matrix.\n",
"\n",
"![encoding-u3](figures/EncodingAnalysis-fig-u3_circuit.png \" Figure 2: General data encoding circuits. Etg denotes any combination of control non-gates and CZ gates.\")\n",
"\n",
"A more rigorous description and proof of the theorem can be found in [5], and the focus of this tutorial will be on what this conclusion implies and illuminates.\n",
"\n",
"* This means that the average quantum state converges to the maximum mixed state at an exponential rate as the circuit depth increases. For example, if the average quantum states of both classes 0 and 1 eventually converge to the maximum mixed state, it will be impossible to distinguish the average quantum states of these two classes from the standpoint of quantum information, i.e., it will be impossible to distinguish classical data features in an average sense.\n",
"\n",
"* When the classical data features have a high dimensionality (e.g., picture data), angle encoding may not be the best option. This is due to the fact that it can easily lead to very deep encoding circuits, and widening circuits may run into the barren plateau problem, which severely limits VQA's capability. As a result, some dimensionality reduction operations must be performed on the traditional data before it is fed into the circuit."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Paddle Quantum Implementation\n",
"\n",
"This section focuses on two experiments using Paddle Quantum on the MNIST dataset: \n",
"- Exploring the trend of the Petz-Rényi divergence between the average quantum state and the maximum mixed state with the depth of the data encoding circuit\n",
"- Investigate changes in classification accuracy as the data encoding circuit becomes deeper."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### First import the relevant packages"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"# import numpy, paddle, and paddle_quantum\n",
"import numpy as np\n",
"import paddle\n",
"import paddle_quantum\n",
"\n",
"# import circuit module\n",
"from paddle_quantum.ansatz import Circuit\n",
"\n",
"# import some function\n",
"from numpy import pi as PI\n",
"from paddle import matmul, transpose, reshape, real, argmax, cast, mean, concat, real\n",
"from paddle_quantum.qinfo import pauli_str_to_matrix \n",
"from paddle_quantum.linalg import dagger\n",
"import paddle.nn.functional as F\n",
"\n",
"# dataset tool\n",
"from paddle_quantum.dataset import MNIST\n",
"\n",
"# plot and time module\n",
"from matplotlib import pyplot as plt\n",
"from pylab import axes\n",
"import time"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Parameterized Quantum Circuit\n",
"\n",
"![encoding-ry](figures/EncodingAnalysis-fig-ry_circuit.png \"Figure 3: Parameterized quantum circuit.\")\n",
"\n",
"The red box on the left in Figure 3 is the data encoding circuit, a special case of Figure 2, and the blue box on the right is the quantum neural network. The data encoding circuit here is made up of $R_y$ and CNOT, and the specific circuit depth $D$ is determined by the data feature dimension. The MNIST dataset will be used in this tutorial and the images are downscaled to 16-dimensional feature vectors. The number of quantum bits is chosen to be 8, 6, 4, 3, 2, and the corresponding circuit depths are 2, 3, 4, 6, 8. The positions larger than 16 dimensions will be filled with 0, i.e., $R_y(0)$. The quantum neural network part consists of the single-bit universal gate $U3$ and CNOT.\n",
"The specific circuit depth $L$ can be set freely."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Encoding classical Data into quantum states\n",
"\n",
"Here you need the dataset tool `dataset` provided by Paddle Quantum. The MNIST data are encoded into quantum states using the data encoding circuit shown in Figure 2 before being fed into the quantum neural network for training."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"train_data, test_data = [], []\n",
"\n",
"# Binary classification task \n",
"classes = [3,6]\n",
"\n",
"training_data_num = 1000\n",
"testing_data_num = 200\n",
"qubit_num_list = [8, 6, 4, 3, 2]\n",
"\n",
"# Encode classical data using circuits of different widths and depths and save them\n",
"for qubit_num in qubit_num_list:\n",
" \n",
" # training dataset\n",
" train_dataset = MNIST(mode='train', encoding='real_entangled_encoding', num_qubits=qubit_num,\n",
" classes=classes,\n",
" data_num=training_data_num,\n",
" downscaling_method='resize', target_dimension=16,\n",
" need_relabel=True, return_state=True)\n",
"\n",
" # validation dataset\n",
" val_dataset = MNIST(mode='test', encoding='real_entangled_encoding', num_qubits=qubit_num,\n",
" classes=classes,\n",
" data_num=testing_data_num,\n",
" downscaling_method='resize', target_dimension=16,\n",
" need_relabel=True, return_state=True)\n",
"\n",
" # x and y\n",
" train_x, train_y = train_dataset.quantum_image_states, train_dataset.labels\n",
" test_x, test_y = val_dataset.quantum_image_states, val_dataset.labels\n",
" train_data.append((train_x, train_y))\n",
" test_data.append((test_x, test_y))"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[1000, 256]\n",
"(1000,)\n",
"[200, 256]\n",
"(200,)\n"
]
}
],
"source": [
"print(train_data[0][0].shape)\n",
"print(train_data[0][1].shape)\n",
"print(test_data[0][0].shape)\n",
"print(test_data[0][1].shape)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Building Quantum Neural Network"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"# Construct model\n",
"class Net(paddle.nn.Layer):\n",
" \"\"\"\n",
" construct network\n",
" \"\"\"\n",
" def __init__(self, n, depth):\n",
" # Initialize the circuit: n, depth\n",
" super(Net, self).__init__()\n",
" self.n = n\n",
" self.depth = depth\n",
" \n",
" self.circuit = Circuit(n)\n",
" # Add layers of rotation gates\n",
" for i in range(n):\n",
" self.circuit.rz(qubits_idx=i)\n",
" self.circuit.ry(qubits_idx=i)\n",
" self.circuit.rz(qubits_idx=i)\n",
"\n",
" # default depth = 1\n",
" # Add layers of entanglement\n",
" for d in range(3, depth + 3):\n",
" for i in range(n-1):\n",
" self.circuit.cnot(qubits_idx=[i, i + 1])\n",
" self.circuit.cnot(qubits_idx=[n-1, 0])\n",
" for i in range(n):\n",
" self.circuit.ry(qubits_idx=i)\n",
"\n",
" # Define forward propagation mechanism, and then calculate loss function and cross-validation accuracy\n",
" def forward(self, state_in, label):\n",
" \"\"\"\n",
" Input: \n",
" state_in: input quantum state, shape: [-1, 1, 2^n] -- Here is [BATCH, 1, 2^n]\n",
" label: labels of input quantum state, shape: [-1, 1]\n",
" Loss function:\n",
" The cross entropy loss \n",
" \"\"\"\n",
" # Initialize theta \n",
" Utheta = self.circuit.unitary_matrix()\n",
"\n",
" # row vector operations here to speed up \n",
" state_out = matmul(state_in, Utheta) # shape [-1, 1, 2 ** n]\n",
"\n",
" # Measure the expected value of the pauli Z operator <Z>\n",
" Ob1 = paddle.to_tensor(pauli_str_to_matrix([[1.0, 'z0']], self.n))\n",
" E_Ob1 = matmul(matmul(state_out, Ob1), transpose(paddle.conj(state_out), perm=[0, 2, 1]))\n",
" E_Ob1_re = reshape(real(E_Ob1), [-1, 1])\n",
"\n",
" Ob2 = paddle.to_tensor(pauli_str_to_matrix([[1.0, 'x0']], self.n))\n",
" E_Ob2 = matmul(matmul(state_out, Ob2), transpose(paddle.conj(state_out), perm=[0, 2, 1]))\n",
" E_Ob2_re = reshape(real(E_Ob2), [-1, 1])\n",
"\n",
" outputs = concat([E_Ob1_re, E_Ob2_re], axis=-1)\n",
" \n",
" # Calculate loss and accuracy\n",
" loss = F.cross_entropy(outputs, label)\n",
" acc = mean(cast(argmax(outputs, axis=-1) == label, \"float32\"))\n",
" \n",
" return loss, acc"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"# Define a classifier\n",
"def QClassifier(train_x, train_y, test_x, test_y, N, D, EPOCH, LR, BATCH, seed=0):\n",
" \"\"\"\n",
" Quantum binary classifier\n",
" \"\"\"\n",
" train_y = paddle.to_tensor(train_y, dtype=\"int64\")\n",
" test_y = paddle.to_tensor(test_y, dtype=\"int64\")\n",
"\n",
" N_train, in_dim = train_x.shape\n",
" \n",
" # Initialize the neural network\n",
" paddle.seed(0)\n",
" net = Net(n=N, depth=D)\n",
"\n",
" # Generally speaking, we use Adam optimizer to obtain relatively good convergence,\n",
" # You can change it to SGD or RMS prop.\n",
" opt = paddle.optimizer.Adam(learning_rate=LR, parameters=net.parameters())\n",
"\n",
" # Optimization loop\n",
" for ep in range(EPOCH):\n",
" for itr in range(N_train // BATCH):\n",
" input_state = train_x[itr * BATCH:(itr + 1) * BATCH]\n",
" input_state = reshape(input_state, [-1, 1, 2 ** N])\n",
" label = train_y[itr * BATCH:(itr + 1) * BATCH]\n",
"\n",
" test_input_state = reshape(test_x, [-1, 1, 2 ** N])\n",
"\n",
" # Forward propagation to calculate loss and accuracy\n",
" train_loss, train_acc = net(state_in=input_state, label=label)\n",
"\n",
" if itr % 3 == 0:\n",
" # Compute test accuracy and loss\n",
" loss_useless, test_acc = net(state_in=test_input_state, label=test_y)\n",
" print(\"epoch:\", ep, \"iter:\", itr,\n",
" \"train loss: %.4f\" % train_loss.numpy(),\n",
" \"train acc: %.4f\" % train_acc,\n",
" \"test acc: %.4f\" % test_acc)\n",
"\n",
" # Use back propagation to minimize the loss function\n",
" train_loss.backward()\n",
" opt.minimize(train_loss)\n",
" opt.clear_grad()\n",
" \n",
" # Compute test accuracy and loss\n",
" _, test_acc = net(state_in=test_input_state, label=test_y) \n",
" \n",
" return test_acc.numpy()"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"***************************** qubit num : 8 *****************************\n",
"epoch: 0 iter: 0 train loss: 0.6913 train acc: 0.5200 test acc: 0.4250\n",
"epoch: 0 iter: 3 train loss: 0.6836 train acc: 0.6500 test acc: 0.5200\n",
"epoch: 1 iter: 0 train loss: 0.6759 train acc: 0.6850 test acc: 0.5550\n",
"epoch: 1 iter: 3 train loss: 0.6709 train acc: 0.6950 test acc: 0.5650\n",
"epoch: 2 iter: 0 train loss: 0.6651 train acc: 0.6650 test acc: 0.5700\n",
"epoch: 2 iter: 3 train loss: 0.6621 train acc: 0.7050 test acc: 0.6050\n",
"epoch: 3 iter: 0 train loss: 0.6589 train acc: 0.6900 test acc: 0.6000\n",
"epoch: 3 iter: 3 train loss: 0.6597 train acc: 0.7050 test acc: 0.6250\n",
"epoch: 4 iter: 0 train loss: 0.6563 train acc: 0.7150 test acc: 0.6550\n",
"epoch: 4 iter: 3 train loss: 0.6566 train acc: 0.7250 test acc: 0.6700\n",
"***************************** qubit num : 6 *****************************\n",
"epoch: 0 iter: 0 train loss: 0.6966 train acc: 0.4900 test acc: 0.5250\n",
"epoch: 0 iter: 3 train loss: 0.6938 train acc: 0.4850 test acc: 0.5450\n",
"epoch: 1 iter: 0 train loss: 0.6884 train acc: 0.5350 test acc: 0.5450\n",
"epoch: 1 iter: 3 train loss: 0.6862 train acc: 0.5400 test acc: 0.5700\n",
"epoch: 2 iter: 0 train loss: 0.6775 train acc: 0.5750 test acc: 0.5850\n",
"epoch: 2 iter: 3 train loss: 0.6744 train acc: 0.6100 test acc: 0.6000\n",
"epoch: 3 iter: 0 train loss: 0.6642 train acc: 0.6350 test acc: 0.5950\n",
"epoch: 3 iter: 3 train loss: 0.6615 train acc: 0.6450 test acc: 0.6200\n",
"epoch: 4 iter: 0 train loss: 0.6526 train acc: 0.6900 test acc: 0.6300\n",
"epoch: 4 iter: 3 train loss: 0.6560 train acc: 0.6250 test acc: 0.6350\n",
"***************************** qubit num : 4 *****************************\n",
"epoch: 0 iter: 0 train loss: 0.7081 train acc: 0.4650 test acc: 0.5350\n",
"epoch: 0 iter: 3 train loss: 0.6994 train acc: 0.4950 test acc: 0.5900\n",
"epoch: 1 iter: 0 train loss: 0.6902 train acc: 0.5450 test acc: 0.5750\n",
"epoch: 1 iter: 3 train loss: 0.6942 train acc: 0.5150 test acc: 0.5800\n",
"epoch: 2 iter: 0 train loss: 0.6869 train acc: 0.6100 test acc: 0.5850\n",
"epoch: 2 iter: 3 train loss: 0.6923 train acc: 0.5150 test acc: 0.6000\n",
"epoch: 3 iter: 0 train loss: 0.6825 train acc: 0.5700 test acc: 0.6050\n",
"epoch: 3 iter: 3 train loss: 0.6917 train acc: 0.5200 test acc: 0.5950\n",
"epoch: 4 iter: 0 train loss: 0.6776 train acc: 0.5850 test acc: 0.5800\n",
"epoch: 4 iter: 3 train loss: 0.6901 train acc: 0.5450 test acc: 0.6050\n",
"***************************** qubit num : 3 *****************************\n",
"epoch: 0 iter: 0 train loss: 0.7104 train acc: 0.4550 test acc: 0.5000\n",
"epoch: 0 iter: 3 train loss: 0.6931 train acc: 0.4950 test acc: 0.4900\n",
"epoch: 1 iter: 0 train loss: 0.6928 train acc: 0.4600 test acc: 0.4700\n",
"epoch: 1 iter: 3 train loss: 0.6968 train acc: 0.4800 test acc: 0.4800\n",
"epoch: 2 iter: 0 train loss: 0.6964 train acc: 0.5100 test acc: 0.4750\n",
"epoch: 2 iter: 3 train loss: 0.7004 train acc: 0.5150 test acc: 0.4600\n",
"epoch: 3 iter: 0 train loss: 0.6961 train acc: 0.5050 test acc: 0.4800\n",
"epoch: 3 iter: 3 train loss: 0.6957 train acc: 0.5300 test acc: 0.4850\n",
"epoch: 4 iter: 0 train loss: 0.6938 train acc: 0.5250 test acc: 0.5150\n",
"epoch: 4 iter: 3 train loss: 0.6919 train acc: 0.5150 test acc: 0.5250\n",
"***************************** qubit num : 2 *****************************\n",
"epoch: 0 iter: 0 train loss: 0.7031 train acc: 0.5450 test acc: 0.4800\n",
"epoch: 0 iter: 3 train loss: 0.6960 train acc: 0.5350 test acc: 0.4550\n",
"epoch: 1 iter: 0 train loss: 0.6932 train acc: 0.5100 test acc: 0.5100\n",
"epoch: 1 iter: 3 train loss: 0.6930 train acc: 0.5250 test acc: 0.5200\n",
"epoch: 2 iter: 0 train loss: 0.6930 train acc: 0.5150 test acc: 0.4750\n",
"epoch: 2 iter: 3 train loss: 0.6933 train acc: 0.5050 test acc: 0.4600\n",
"epoch: 3 iter: 0 train loss: 0.6925 train acc: 0.5400 test acc: 0.4600\n",
"epoch: 3 iter: 3 train loss: 0.6909 train acc: 0.5350 test acc: 0.4700\n",
"epoch: 4 iter: 0 train loss: 0.6939 train acc: 0.5450 test acc: 0.4750\n",
"epoch: 4 iter: 3 train loss: 0.6853 train acc: 0.5600 test acc: 0.4750\n",
"time used: 184.6011986732483 s\n"
]
}
],
"source": [
"time_start = time.time()\n",
"\n",
"acc_list = []\n",
"\n",
"for i in range(5):\n",
" print('***************************** qubit num : %s *****************************'%qubit_num_list[i])\n",
" train_x, train_y = train_data[i]\n",
" test_x, test_y = test_data[i]\n",
"\n",
" acc = QClassifier(\n",
" train_x, # training data x\n",
" train_y, # training data label\n",
" test_x, # test data x\n",
" test_y, # test data label\n",
" N=qubit_num_list[i], # Number of qubits\n",
" D=qubit_num_list[i] + 2, # Circuit depth\n",
" EPOCH=5, # Number of training epochs\n",
" LR=0.05, # Learning rate\n",
" BATCH=200, # Batch size\n",
" seed=0\n",
" )\n",
" acc_list.append(acc) \n",
"\n",
"time_span = time.time() - time_start\n",
"print('time used:', time_span, 's')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### The Petz-Rényi divergence of the average quantum state and the maximum mixed state"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[1.9842463368536747, 0.5256437464833253, 0.1964853652901484, 0.05162865627740749, 0.022790754263846934]\n",
"[1.8628793706046225, 0.6064395532199834, 0.1529884926031612, 0.04173701231534178, 0.009023512622560221]\n"
]
},
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"fig = plt.figure(1)\n",
"ax = axes([0.15, 0.15, 0.8, 0.8])\n",
"\n",
"\n",
"def average_encode_state(train_x):\n",
" d1, d2 = train_x.shape\n",
" density_matrices = np.reshape(train_x, [d1, d2, 1]) @ np.reshape(np.conj(train_x), [d1, 1, d2])\n",
" return np.mean(density_matrices, axis=0)\n",
"\n",
"\n",
"depth_list = [2, 3, 4, 6, 8]\n",
"\n",
"Q_2Renyi_D3_list = []\n",
"Q_2Renyi_D6_list = []\n",
"\n",
"for i,q in enumerate(qubit_num_list):\n",
" train_x, train_y = train_data[i]\n",
" train_x = train_x.numpy()\n",
" \n",
" train_x0 = train_x[train_y == 0]\n",
" train_x1 = train_x[train_y == 1]\n",
" average_state0 = average_encode_state(train_x0.real)\n",
" average_state1 = average_encode_state(train_x1.real)\n",
"# print(average_state)\n",
" Q_2Renyi_D3 = np.log2(np.trace(average_state0 @ average_state0) * 2 ** q)\n",
" Q_2Renyi_D6 = np.log2(np.trace(average_state1 @ average_state1) * 2 ** q)\n",
"# bound = np.sum(np.sqrt(S0)) ** 2 / 2 ** num_qubits\n",
" Q_2Renyi_D3_list.append(Q_2Renyi_D3)\n",
" Q_2Renyi_D6_list.append(Q_2Renyi_D6)\n",
"\n",
"print(Q_2Renyi_D3_list)\n",
"print(Q_2Renyi_D6_list)\n",
"\n",
"\n",
"func3, = ax.plot(depth_list, Q_2Renyi_D3_list, linewidth=1.5,\n",
" marker=\"<\",\n",
" linestyle=\":\",\n",
" color=\"r\"\n",
" )\n",
"\n",
"func6, = ax.plot(depth_list, Q_2Renyi_D6_list, linewidth=1.5,\n",
" marker=\"o\",\n",
" linestyle=\"-.\",\n",
" color=\"g\"\n",
" )\n",
"\n",
"plt.xticks(fontsize=22)\n",
"plt.yticks(fontsize=22)\n",
"\n",
"plt.xlabel(\"Depth\", fontsize=22)\n",
"plt.ylabel(r\"$D_2(\\overline{\\rho} || I/2^n)$\", fontsize=22)\n",
"ax.semilogy()\n",
"ax.legend(handles=[func3, func6],\n",
" labels=[\"Digit 3\", \"Digit 6\"],\n",
" loc=\"best\",\n",
" fontsize=18)\n",
"ax.grid(axis=\"y\")\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Investigating the effect on classification accuracy"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[array([0.675], dtype=float32), array([0.61], dtype=float32), array([0.585], dtype=float32), array([0.535], dtype=float32), array([0.475], dtype=float32)]\n"
]
},
{
"data": {
"image/png": "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1IjI+fJDmTJlOOuss45LbgADBw6kU6dOpKWl8emnJ9aSkS+88AJLliyhZ8+ePPHEE1mSG0DZsmU57bTTohKbJTgTs7ZsgU6dYGuw9dpjxIEDB3jkkUdo164d5cuXp2TJkjRs2JABAwbwwQcfhHSOP/74gyeffJLu3bvToEEDSpQoQVJSEm3btmXSpEmkpaX5fd2XX37JgAEDqFWrFgkJCSQlJdGoUSOGDBly3JftoUOHmDhxIq1bt6ZMmTIZ9f/atWvHnXfeyaFDWdcEzn4Pznev7O677wbg3nvvzWjTuXNnrrzySkSEiRMnBvycTz/9NCLCJZdcctx5s9+DW7hwYca5U1NTGT9+PE2aNKFEiRJUrVqVSy+9lN9++y3ge7355pu0b9+eMmXKUKFCBc4//3wWLVqU5byR4quAn5iYGPJrUlNTefXVVxk8eDCNGzembNmylCpVimbNmjF27Fh27tzp93WZ75V98skndO3alaSkJEqVKkXbtm2ZPXt2wPf89ddfueyyy6hWrRolS5akWbNmPPTQQwH/feXkmWeeAeDmm2/O1evzlaraI58ebdq00aJg1apVUXnfa65RjYtz21i2YcMGbdy4sQJapkwZ7d69uw4cOFDbt2+vpUuX1k6dOmVp36lTJwV0wYIFWfa/+uqrCmjt2rW1S5cuOmjQIO3cubMmJiYqoBdddJGmp6dnec3HH3+sCQkJCmirVq10wIAB2qdPH01OTtaEhAS9+uqrM9qmpaXpueeeq4AmJSXphRdeqIMHD9Zzzz1Xa9WqpYBu2bIly/kBdV8TzqJFi3T48OHaokULBbRFixY6fPhwHT58uE6YMEGXL1+ugNavX1/T0tL8/nk1adJEAV24cGHGvpdeekkBHT58eJa2CxYsUEDbt2+vXbt21TJlymiPHj20T58+Wq1aNQW0Tp06umvXruPeZ/z48QqoiGiHDh108ODBevrpp2t8fLzedNNNChz3d5NbH3zwgSYkJGipUqX0999/D/l1GzduVEArVKig7dq100suuUS7d++ulStXVkAbNmyof/zxx3Gvq1evngJ65513qojomWeeqYMGDdJWrVplfOaZM2ce97offvgh49x16tTRgQMH6vnnn6/FixfXiy++OOO869evDyn+zZs3K6Dx8fF68OBBXbNmjd533306cuRIHTdunM6dO/e4f7OhCOc7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",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"print(acc_list)\n",
"\n",
"fig = plt.figure(1)\n",
"ax = axes([0.15, 0.15, 0.8, 0.8])\n",
"\n",
"func36_acc, = ax.plot(depth_list, acc_list, linewidth=1.5,\n",
" marker=\"*\",\n",
" linestyle=\"--\",\n",
" color=\"b\"\n",
" )\n",
"\n",
"plt.xticks(fontsize=22)\n",
"plt.yticks(fontsize=22)\n",
"plt.ylim(0.48, 0.75)\n",
"plt.xlabel(\"Depth\", fontsize=22)\n",
"plt.ylabel(r\"Test Accuracy\", fontsize=22)\n",
"ax.legend(handles=[func36_acc,],\n",
" labels=[\"classifying 3 and 6\"],\n",
" loc=\"best\", fontsize=22)\n",
"ax.grid(axis=\"y\")\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Conclusion\n",
"\n",
"According to the experimental results, the average quantum state of each category tends to the maximum mixed state at an exponential rate as the data encoding circuit deepens. As a result, the final quantum neural network's classification accuracy decreases. With the help of the literature [5], we have come to understand some of the limitations of angle coding. It is also acknowledged that designing a data coding strategy capable of solving real-world problems (often with high dimensionality of data features) is both urgent and difficult."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"## References\n",
"\n",
"[1] Schuld, M. \"Quantum machine learning models are kernel methods.\" [arXiv preprint arXiv:2101.11020.(2021)](https://arxiv.org/abs/2101.11020)\n",
"\n",
"[2] Lloyd, Seth, et al. \"Quantum embeddings for machine learning.\" [arXiv preprint arXiv:2001.03622 (2020).](https://arxiv.org/pdf/2001.03622.pdf)\n",
"\n",
"[3] Caro, Matthias C., et al. \"Encoding-dependent generalization bounds for parametrized quantum circuits.\" [Quantum 5 (2021): 582.](https://quantum-journal.org/papers/q-2021-11-17-582/)\n",
"\n",
"[4] Leonardo Banchi, et al. \"Generalization in quantum machine learning: A quantum information standpoint.\" [PRX Quantum 2.4 (2021): 040321.](https://journals.aps.org/prxquantum/abstract/10.1103/PRXQuantum.2.040321)\n",
"\n",
"[5] Li, Guangxi, et al. \"Concentration of Data Encoding in Parameterized Quantum Circuits.\" [arXiv preprint arXiv:2206.08273 (2022).](https://arxiv.org/pdf/2206.08273.pdf)\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.8.13 ('new_pq')",
"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.13"
},
"vscode": {
"interpreter": {
"hash": "58b83104798ee1b81625bc6249d4d66f2bacd4a7d411a9b7a27bac0b4765adf2"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 量子神经网络模拟函数\n",
"\n",
"*Copyright (c) 2022 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 概览\n",
"量子神经网络(Quantum Neural Network, QNN)由参数化量子电路组成,通过训练电路中的参数从而最小化目标损失函数,是一种常用的量子机器学习模型。和机器学习中的神经网络(Neural Network, NN)模型类似,QNN 的表达能力可以通过其能近似的函数来刻画。经典神经网络中的通用近似定理(Universal Approximation Theorem, UAT)描述了多层人工神经网络近似任意函数的能力。一些研究将 QNN 与傅里叶级数联系起来,发现多比特的 QNN 也有类似的通用近似性质(Universal Approximation Property, UAP)[1],然而单比特 QNN 的表达能力仍旧是一个开放问题。我们从量子信号处理(Quantum Signal Processing, QSP)中获取灵感,建立了单比特 QNN 和单元傅里叶级数的一一对应关系,从而证明了单比特 QNN 的表达能力,解决了这个开放问题。在这篇教程中,我们将展示使用单比特 QNN 模拟任意目标函数。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 单比特 QNN 模拟任意偶函数\n",
"我们使用 data re-uploading 形式的单比特 QNN,由数据编码门和可训练门交替组成,其中数据编码门和可训练门均从三种泡利旋转门$R_X,R_Y,R_Z$ 中选择。令 QNN 作用的初始态为 $|0\\rangle$,我们定义 QNN 的输出为对某个可观测量 $M$ 的测量,即\n",
"\n",
"$$\n",
"f_U(x) = \\langle 0| U^\\dagger M U |0\\rangle, \\tag{1}\n",
"$$\n",
"\n",
"其中 $x$ 为数据输入,$U$ 表示该 QNN。\n",
"\n",
"首先我们考虑最简单的情况,选择 $R_Z$ 为编码门,$R_Y$ 为可训练门,定义一种如下的 QNN:\n",
"\n",
"$$\n",
"U^{\\mathit{YZY}}_{\\mathbf{\\theta}, L}(x) = R_Y(\\theta_0) \\sum_{j=1}^LR_Z(x)R_Y(\\theta_j), \\tag{2}\n",
"$$\n",
"\n",
"其中 $\\mathbf{\\theta} := (\\theta_0, \\ldots, \\theta_L)$ 为可训练参数,$L$ 为 QNN 的层数。\n",
"\n",
"我们证明了这种单比特 QNN 可以表示傅里叶级数\n",
"\n",
"$$\n",
"\\langle 0|U^{\\mathit{YZY}}_{\\mathbf{\\theta}, L}(x) |0\\rangle = a_0 + \\sum_{j=1}^{L}a_j\\cos(nx), \\tag{3}\n",
"$$\n",
"\n",
"当选取可观测量为泡利算子 $Z$ 时,该 QNN 的输出也就能近似模拟任意平方可积的偶函数 $f: [-\\pi, \\pi] \\to [-1, 1]$。\n",
"\n",
"接下来我们使用量桨实现单比特 QNN 模拟偶函数的实验来验证以上结果。我们先通过下面几行代码引入必要的包。"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import paddle\n",
"import numpy as np\n",
"import paddle_quantum\n",
"from paddle_quantum.ansatz import Circuit\n",
"from paddle_quantum.hamiltonian import Hamiltonian\n",
"from paddle_quantum.loss import ExpecVal\n",
"import matplotlib.pyplot as plt\n",
"import brewer2mpl\n",
"import matplotlib\n",
"# 设置后端为态矢量模式\n",
"paddle_quantum.set_backend(\"state_vector\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"我们首先构造 QNN 对应的参数化量子电路,由数据编码门 $R_Z$ 和可训练门 $R_Y$ 组成。 "
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"# 构造 YZY 结构的参数化量子电路\n",
"def U_YZY(train_block, w_theta, x):\n",
" cir = Circuit(1)\n",
" for i in range(train_block):\n",
" cir.ry(0, param=w_theta[i])\n",
" cir.rz(0, param=x) # 输入数据\n",
" cir.ry(0, param=w_theta[-1])\n",
" return cir"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"选定模拟的目标函数为阻尼振荡函数 $f(x) = \\sin(5x)/5x$,我们需要从目标函数中采样获得数据点用于 QNN 的训练。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 定义目标函数\n",
"def target_func(x):\n",
" return np.sin(5 * x) / (5 * x)\n",
"\n",
"# 随机采样获取关于目标函数的数据点\n",
"def get_data():\n",
" x_plot = np.arange(0, np.pi, np.pi/1000)\n",
" y_plot = np.sin(5*x_plot) / (5*x_plot)\n",
" \n",
" np.random.seed(0)\n",
" x_all = np.random.uniform(0, np.pi, 300)\n",
" \n",
" y_all = np.sin(5*x_all) / (5*x_all)\n",
"\n",
" x_train, y_train = x_all[:200], y_all[:200]\n",
" x_test, y_test = x_all[200:], y_all[200:]\n",
"\n",
" return x_train, y_train, x_test, y_test, x_plot, y_plot\n",
" \n",
"# 采样获取训练集和测试集\n",
"x_train, y_train, x_test, y_test, x_plot, y_plot = get_data()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"接下来定义 QNN 训练模型以及训练相关的函数。"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"class QNN(paddle.nn.Layer):\n",
" def __init__(self, \n",
" train_block, # 电路的层数为 L\n",
" SEED=0,\n",
" dtype='float64'):\n",
" super(QNN, self).__init__()\n",
" self.train_block = train_block\n",
" paddle.seed(SEED)\n",
" # 初始化可训练参数\n",
" self.w_theta = self.create_parameter(\n",
" shape=[(train_block+1)],\n",
" default_initializer=paddle.nn.initializer.Uniform(0.0, 2 * np.pi),\n",
" dtype=dtype,\n",
" is_bias=False)\n",
"\n",
"\n",
" def forward(self, x):\n",
" \"\"\"\n",
" Forward propagation\n",
" \"\"\"\n",
" predict = []\n",
" H = Hamiltonian([(1.0, \"z0\")])\n",
" out_func = ExpecVal(H)\n",
" x = paddle.to_tensor(x, dtype='float64')\n",
" if len(x.shape) == 1: # 对于 1 维数据进行处理\n",
" x = x.reshape((-1, 1))\n",
" for i in range(x.shape[0]):\n",
" cir = U_YZY(self.train_block, self.w_theta, x[i])\n",
" # 运行量子电路\n",
" out_state = cir()\n",
" predict.append(out_func(out_state))\n",
" return paddle.concat(predict).reshape((-1,)), cir\n",
"\n",
"\n",
"# 训练函数\n",
"def train_qnn(x, y, train_block, LR, ITR, SEED, BATCHSIZE=20):\n",
" model = QNN(train_block, SEED)\n",
" opt = paddle.optimizer.Adam(learning_rate=LR, parameters=model.parameters())\n",
" loss_list = []\n",
" x = paddle.to_tensor(x, dtype='float64')\n",
" y = paddle.to_tensor(y, dtype='float64')\n",
" for ep in range(1, ITR + 1):\n",
" # 对数据进行分批训练\n",
" for itr in range(len(x) // BATCHSIZE):\n",
" x_batch = x[itr * BATCHSIZE:(itr + 1) * BATCHSIZE]\n",
" y_batch = y[itr * BATCHSIZE:(itr + 1) * BATCHSIZE]\n",
" # 运行定义的量子神经网络\n",
" predict, cir = model(x_batch)\n",
" avg_loss = paddle.mean((predict - y_batch) ** 2)\n",
" loss_list.append(avg_loss.numpy())\n",
" # 计算梯度并进行优化\n",
" avg_loss.backward()\n",
" opt.minimize(avg_loss)\n",
" opt.clear_grad()\n",
" if (itr+1) % 5 == 0:\n",
" print(\"qnn:epoch:\", ep,\"qnn:iter:\", (itr+1), \" train loss:\", \"%.8f\" % avg_loss.numpy())\n",
"\n",
" return model, loss_list"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"我们使用层数为 10 的 QNN 来模拟目标函数,在训练前还需要设定一些有关优化器的超参数。"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"c:\\Users\\yuzhan01\\Miniconda3\\envs\\pq_new\\lib\\site-packages\\paddle\\tensor\\creation.py:125: DeprecationWarning: `np.object` is a deprecated alias for the builtin `object`. To silence this warning, use `object` by itself. Doing this will not modify any behavior and is safe. \n",
"Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations\n",
" if data.dtype == np.object:\n",
"c:\\Users\\yuzhan01\\Miniconda3\\envs\\pq_new\\lib\\site-packages\\paddle\\fluid\\framework.py:1104: DeprecationWarning: `np.bool` is a deprecated alias for the builtin `bool`. To silence this warning, use `bool` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.bool_` here.\n",
"Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations\n",
" elif dtype == np.bool:\n",
"c:\\Users\\yuzhan01\\Miniconda3\\envs\\pq_new\\lib\\site-packages\\paddle\\fluid\\dygraph\\math_op_patch.py:276: UserWarning: The dtype of left and right variables are not the same, left dtype is paddle.float32, but right dtype is paddle.float64, the right dtype will convert to paddle.float32\n",
" warnings.warn(\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"qnn:epoch: 1 qnn:iter: 5 train loss: 0.12315345\n",
"qnn:epoch: 1 qnn:iter: 10 train loss: 0.06922857\n",
"qnn:epoch: 2 qnn:iter: 5 train loss: 0.02042443\n",
"qnn:epoch: 2 qnn:iter: 10 train loss: 0.04707706\n",
"qnn:epoch: 3 qnn:iter: 5 train loss: 0.01874223\n",
"qnn:epoch: 3 qnn:iter: 10 train loss: 0.01295448\n",
"qnn:epoch: 4 qnn:iter: 5 train loss: 0.00991240\n",
"qnn:epoch: 4 qnn:iter: 10 train loss: 0.00303511\n",
"qnn:epoch: 5 qnn:iter: 5 train loss: 0.00157935\n",
"qnn:epoch: 5 qnn:iter: 10 train loss: 0.00089821\n",
"qnn:epoch: 6 qnn:iter: 5 train loss: 0.00046386\n",
"qnn:epoch: 6 qnn:iter: 10 train loss: 0.00054655\n",
"qnn:epoch: 7 qnn:iter: 5 train loss: 0.00059435\n",
"qnn:epoch: 7 qnn:iter: 10 train loss: 0.00022313\n",
"qnn:epoch: 8 qnn:iter: 5 train loss: 0.00028409\n",
"qnn:epoch: 8 qnn:iter: 10 train loss: 0.00017835\n",
"qnn:epoch: 9 qnn:iter: 5 train loss: 0.00017996\n",
"qnn:epoch: 9 qnn:iter: 10 train loss: 0.00018871\n",
"qnn:epoch: 10 qnn:iter: 5 train loss: 0.00016455\n",
"qnn:epoch: 10 qnn:iter: 10 train loss: 0.00012700\n"
]
}
],
"source": [
"SEED = 4096\n",
"QITR = 10\n",
"QLR = 0.1\n",
"train_block = 10\n",
"modelL10, loss_listL10 = train_qnn(x_train, y_train, train_block=train_block, LR=QLR, ITR=QITR, SEED=SEED)\n",
"predictL10 = modelL10(x_test)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"训练完成后,以图像形式展示函数模拟的结果。"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAbQAAAEZCAYAAAD/ttB2AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAABTo0lEQVR4nO3de3zO9fvA8de1s83MYQcMm/Mh51ZCMoqkIlLRmZBKopMOSgcdqUhRIkUq/XTyVUhOhcKcmcOcz2bYwc679/79ce++m9ls7HBv967n43E/Zu/P6bp33+7rfn/eJzHGoJRSSpV1Lo4OQCmllCoKmtCUUko5BU1oSimlnIImNKWUUk5BE5pSSimn4OboAMorf39/Exoa6ugwlFKqTNmwYUOMMSYgt22a0BwkNDSUiIgIR4ehlFJliogcymub3nJUSinlFDShKaWUcgqa0JRSSjkFp05oIlJDRBaJiM7vpZRSTs5pO4WISB/gIyD9Co51B14F7gIygHjgeWPMqlz2HQkMzdovA3jDGPPLFQeuVDmQmZnJ0aNHSUxMdHQoqpRxd3cnMDCQSpUqXfaxTpvQgBeAbsDLQIPLPHYy0BXoaIw5LSKDgSUi0t4Ys9m2k4i8ADwLtDPG7BORbsDvItLLGLOwSJ6FUk4oJiYGEaFx48a4uDj1jSJ1GYwxJCcnc+zYMYDLTmrO/E7qaIyJutyDRKQx1hrXu8aY0wDGmOnAfuCtbPtVBl4Bphhj9mXttwT4A5hQ6OiVcmKxsbEEBQVpMlMXEBG8vb0JDg4mOjr6so932neTMSbjCg/tAwiwPEf5MqC7iFTM+r0H4J3Hfs1EpMkVXj9fb02eRa9HRvPGpJlEbN2FLgGkyhqLxYK7u7ujw1ClVIUKFUhPv+zWIqe+5XilWgKZwOEc5Qew/r2aAeuy9rOV59zPdp5d2TeIyFCstT/q1KlzxQEeOnaCI8ejOXI8mp8X/UXbZg14cegAGjSud8XnVKqkiYijQ1Cl1JW+N5y2hlYI/kCSMcaSozw+62e1bPsBJOSzn50xZpoxJswYExYQkOvMLQVyfVhLOoa1wMPd+n1kY+ReBox4g3kz513xOZVSqqzThFZwBf3KUOxfOx/sdwufvPk0C2d9wG1+fogxZLgIb/3wGx9P+oqM89pzTKkr0aRJE8LDwwkPD6d69eoEBQXZf2/SpNhaEQpl3rx5tGrVirZt2/LKK6+U6LV79uzJihUrSvSal6IJ7WIxgLeIuOYo9836eSbbftnL89qv2HifPkf/7fuZMnoYFS2ZAMxctJK3wx8gZW/OO6ZKqfxUr16dFStWsGLFCnr06EG3bt3sv1evXr3E4wkNDc03YYwaNYqpU6eyfv36QjVl5Oe1117j4YcfvqDs+++/p3PnzsV2zculCe1iW7H+XWrnKK+LdZzZzmz7AYTmsl/27cUmetJs/B+5k+u6XMfH4R2oCogx+KWlc2Lc1OK+vFJO55133rmibY509OhRatasiaurK0OGDCnRa1eqVKlUtYWW+4QmIkEikv3v8DNggPAcu3YB/jDG2NrMFgFJeewXaYzZRTFKiTpEwop1+A/uB0DLJ+/nhRPneOzkObp7eJH471atpSl1mdq3b3/JbdOmTaNDhw507dqVG2+8kcjISADWrVtH69atCQ0NZfz48XTo0MH+QR8REUFYWBgdO3bkscce4/rrr6dJkybMnz8fgMWLF9O+fXvCw8O5/fbbOX78OAADBw7k5MmTjBw5kvDwcDZs2HBBPGlpaYSHhwPQv39/Bg4cyPPPP0/lypX56quvAHj00Ufx8vKy1/Ief/xxKleuzCuvvMJdd91F48aNeemlly447wcffMB1111Hly5d6NmzJxs3bmTu3Ll89dVXLFq0iPDwcN566y3Gjx9P9erVee211+zHLlq0iA4dOnDDDTdw8803s3fvXgCmTZtGaGgo/fv359FHH6Vt27b07NmTlJSUy3+RLqFc93IUkY7AX8A04DEAY8xuEZkGvCgiC4wxMSIyCKgP3G871hgTKyJvAs+IyCxjzH4RuQm4GehV3LHbameuvj4AuPr60HLgnYQs/AuP2jU4//cGToybSt2vSue3SqVymr9kFfOXXDQZzwUa16vDc8Putf++e99hxn/+bb7nnv7+C4WOD6wDf5cvX46npycrVqzg0Ucf5e+//+baa69l4sSJdO/enTZt2vDcc8/x7LPPkpaWRp8+fXj//fcZMGAAmzdvJiwsjOnTp9OrVy8OHDhAv379iIiIoHHjxnz66ac8+OCD/Pnnn8ycOZPly5czceJEe+LKzsPDgxUrViAifP/999jWV1y3bp19n88//5zFixfbf58yZQqRkZFs3LiRBQsWcPLkSerUqcPw4cOpWbMm3377LTNnzmTd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",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"matplotlib.rcParams[\"font.family\"] = \"serif\"\n",
"matplotlib.rcParams[\"mathtext.fontset\"] = \"cm\"\n",
"bmap = brewer2mpl.get_map(\"Set1\", \"qualitative\", 7)\n",
"colors = bmap.mpl_colors\n",
"\n",
"plt.plot(x_plot, y_plot, color=\"#304860\", ls=\"--\", lw=2.5, label=\"Target function\")\n",
"\n",
"plt.scatter(\n",
" x_test,\n",
" predictL10[0].numpy(),\n",
" s=40,\n",
" marker=\"^\",\n",
" facecolor=\"white\",\n",
" color=\"#D1193E\",\n",
" label=\"QNN L=10\",\n",
" )\n",
"plt.xlabel(r\"$x$\", fontdict={\"size\": 22})\n",
"plt.ylabel(r\"$f(x)$\", fontdict={\"size\": 22})\n",
"plt.xticks(fontsize=10)\n",
"plt.yticks(fontsize=10)\n",
"plt.legend(prop={\"size\": 12})\n",
"plt.text(0, -0.2, r\"(a)\", fontsize=16)\n",
"plt.tick_params(labelsize=16)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"从结果可以看出,使用层数为 10 的 QNN 能以较高精度近似目标函数,验证了关于 QNN 表达能力的理论结果。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 单比特 QNN 模拟任意函数\n",
"既然 $U^{\\mathit{YZY}}_{\\mathbf{\\theta}, L}$ 可以模拟任意偶函数,那么有没有办法可以做出一些修改,使得 QNN 可以模拟任意的函数呢?实际上,我们只需要在 $U^{\\mathit{YZY}}_{\\mathbf{\\theta}, L}$ 对应的仅含 $\\cos$ 项的傅里叶级数的基础上,引入 $\\sin$ 项即可得到完整的傅里叶级数。具体的,我们需要加入额外的可训练门 $R_Z$,定义一种如下的 QNN:\n",
"\n",
"$$\n",
"U^{\\mathit{WZW}}_{\\mathbf{\\theta},\\mathbf{\\phi}, L}(x) = R_Y(\\theta_0)R_Z(\\phi_0) \\sum_{j=1}^L R_Z(x) R_Y(\\theta_j)R_Z(\\phi_j), \\tag{4}\n",
"$$\n",
"\n",
"其中 $\\mathbf{\\theta} := (\\theta_0, \\ldots, \\theta_L)$ 和 $\\mathbf{\\phi} := (\\phi_0, \\ldots, \\phi_L)$ 为可训练参数,$L$ 为 QNN 的层数。我们证明了这种单比特 QNN 可以表示傅里叶级数\n",
"\n",
"$$\n",
"\\langle 0|U^{\\mathit{WZW}}_{\\mathbf{\\theta},\\mathbf{\\phi}, L}(x) |0\\rangle = a_0 + \\sum_{j=1}^{L}(a_j\\cos(nx)+ b_j\\sin(nx)), \\tag{5}\n",
"$$\n",
"\n",
"从而能够近似模拟任意平方可积的函数 $f: [-\\pi, \\pi] \\to [-1, 1]$。\n",
"\n",
"接下来我们使用量桨实现单比特 QNN 模拟方波函数的实验来验证以上结果。首先构造 $U^{\\mathit{WZW}}_{\\mathbf{\\theta},\\mathbf{\\phi}, L}$ 形式的 QNN 对应的参数化量子电路。"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"def U_WZW(train_block, w_theta, x):\n",
" cir = Circuit(1)\n",
" for i in range(train_block):\n",
" cir.rz(0, param=w_theta[i][1])\n",
" cir.ry(0, param=w_theta[i][0])\n",
" cir.rz(0, param=x) # 输入数据\n",
" cir.rz(0, param=w_theta[-1][1])\n",
" cir.ry(0, param=w_theta[-1][0])\n",
" return cir"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"与前一章节类似,我们需要定义模拟的目标函数,并从目标函数中采样获得数据点用于 QNN 的训练。"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"def square_wave(trunk):\n",
" x_train = np.linspace(0, 20, 400)\n",
" x_test = np.linspace(0.02, 30, 150)\n",
"\n",
" def func(x):\n",
" cof = 0\n",
" for i in range(1, trunk+1, 2):\n",
" cof = cof + 4*np.sin(i*x)/(i*np.pi)\n",
" y_max = max(cof)\n",
" cof /= y_max\n",
" return cof\n",
" \n",
" y_train = func(x_train)\n",
" y_test = func(x_test)\n",
"\n",
" return x_train, y_train, x_test, y_test\n",
"\n",
"x_train, y_train, x_test, y_test = square_wave(10000)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"接下来根据 QNN 结构定义其训练模型以及训练相关的函数。"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"class QNN(paddle.nn.Layer):\n",
" def __init__(self, \n",
" train_block, # 电路的层数为 L\n",
" SEED=0,\n",
" dtype='float64'):\n",
" super(QNN, self).__init__()\n",
" self.train_block = train_block\n",
" paddle.seed(SEED)\n",
" # 初始化可训练参数\n",
" self.w_theta = self.create_parameter(\n",
" shape=[(train_block+1), 2],\n",
" default_initializer=paddle.nn.initializer.Uniform(0.0, 2*np.pi),\n",
" dtype=dtype,\n",
" is_bias=False)\n",
"\n",
"\n",
" def forward(self, x):\n",
" \"\"\"\n",
" Forward propagation\n",
" \"\"\"\n",
" predict = []\n",
" H = Hamiltonian([(1.0, \"z0\")])\n",
" out_func = ExpecVal(H)\n",
" x = paddle.to_tensor(x, dtype='float64')\n",
" if len(x.shape) == 1: # 对于 1 维数据进行处理\n",
" x = x.reshape((-1, 1))\n",
" for i in range(x.shape[0]):\n",
" cir = U_WZW(self.train_block, self.w_theta, x[i])\n",
" # 运行量子电路\n",
" out_state = cir()\n",
" predict.append(out_func(out_state))\n",
" return paddle.concat(predict).reshape((-1,)), cir\n",
"\n",
"\n",
"# 定义训练函数\n",
"def train_qnn(x, y, train_block, LR, ITR, SEED, BATCHSIZE=40):\n",
" model = QNN(train_block, SEED)\n",
" opt = paddle.optimizer.Adam(learning_rate=LR, parameters=model.parameters())\n",
" loss_list = []\n",
" x = paddle.to_tensor(x, dtype='float64')\n",
" y = paddle.to_tensor(y, dtype='float64')\n",
" for ep in range(1, ITR + 1):\n",
" # 对数据进行分批训练\n",
" for itr in range(len(x) // BATCHSIZE):\n",
" x_batch = x[itr * BATCHSIZE:(itr + 1) * BATCHSIZE]\n",
" y_batch = y[itr * BATCHSIZE:(itr + 1) * BATCHSIZE]\n",
" # 运行上面定义的量子神经网络\n",
" predict, cir = model(x_batch)\n",
" avg_loss = paddle.mean((predict - y_batch) ** 2)\n",
" loss_list.append(avg_loss.numpy())\n",
" # 计算梯度并进行优化\n",
" avg_loss.backward()\n",
" opt.minimize(avg_loss)\n",
" opt.clear_grad()\n",
" if (itr+1) % 5 == 0:\n",
" print(\"qnn:epoch:\", ep,\"qnn:iter:\", (itr+1), \" train loss:\", \"%.8f\" % avg_loss.numpy())\n",
"\n",
" return model, loss_list"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"我们使用层数为 45 的 QNN 来模拟该方波函数,注意这里使用的层数与目标函数的傅里叶截断误差有关,通常来说对于越复杂的函数使用的层数越多,使用的层数越多模拟的效果越好。"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"c:\\Users\\yuzhan01\\Miniconda3\\envs\\pq_new\\lib\\site-packages\\paddle\\tensor\\creation.py:125: DeprecationWarning: `np.object` is a deprecated alias for the builtin `object`. To silence this warning, use `object` by itself. Doing this will not modify any behavior and is safe. \n",
"Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations\n",
" if data.dtype == np.object:\n",
"c:\\Users\\yuzhan01\\Miniconda3\\envs\\pq_new\\lib\\site-packages\\paddle\\fluid\\framework.py:1104: DeprecationWarning: `np.bool` is a deprecated alias for the builtin `bool`. To silence this warning, use `bool` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.bool_` here.\n",
"Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations\n",
" elif dtype == np.bool:\n",
"c:\\Users\\yuzhan01\\Miniconda3\\envs\\pq_new\\lib\\site-packages\\paddle\\fluid\\dygraph\\math_op_patch.py:276: UserWarning: The dtype of left and right variables are not the same, left dtype is paddle.float32, but right dtype is paddle.float64, the right dtype will convert to paddle.float32\n",
" warnings.warn(\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"qnn:epoch: 1 qnn:iter: 5 train loss: 1.35621011\n",
"qnn:epoch: 1 qnn:iter: 10 train loss: 1.06700826\n",
"qnn:epoch: 2 qnn:iter: 5 train loss: 1.59428942\n",
"qnn:epoch: 2 qnn:iter: 10 train loss: 0.32698074\n",
"qnn:epoch: 3 qnn:iter: 5 train loss: 0.30190033\n",
"qnn:epoch: 3 qnn:iter: 10 train loss: 0.09284516\n",
"qnn:epoch: 4 qnn:iter: 5 train loss: 0.11605076\n",
"qnn:epoch: 4 qnn:iter: 10 train loss: 0.06084419\n",
"qnn:epoch: 5 qnn:iter: 5 train loss: 0.10283329\n",
"qnn:epoch: 5 qnn:iter: 10 train loss: 0.07899086\n",
"qnn:epoch: 6 qnn:iter: 5 train loss: 0.06403162\n",
"qnn:epoch: 6 qnn:iter: 10 train loss: 0.05624062\n",
"qnn:epoch: 7 qnn:iter: 5 train loss: 0.05701165\n",
"qnn:epoch: 7 qnn:iter: 10 train loss: 0.05501990\n",
"qnn:epoch: 8 qnn:iter: 5 train loss: 0.05415571\n",
"qnn:epoch: 8 qnn:iter: 10 train loss: 0.05919911\n",
"qnn:epoch: 9 qnn:iter: 5 train loss: 0.05508716\n",
"qnn:epoch: 9 qnn:iter: 10 train loss: 0.05666707\n",
"qnn:epoch: 10 qnn:iter: 5 train loss: 0.05592950\n",
"qnn:epoch: 10 qnn:iter: 10 train loss: 0.05661748\n"
]
}
],
"source": [
"SEED = 2\n",
"QITR = 10\n",
"QLR = 0.1\n",
"train_block = 45\n",
"modelL45, loss_listL45 = train_qnn(x_train, y_train, train_block=train_block, LR=QLR, ITR=QITR, SEED=SEED)\n",
"predictL45 = modelL45(x_test)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"最后我们作图展示函数模拟的结果。"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 720x432 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"fig, ax1 = plt.subplots(1, 1,figsize=(10,6))\n",
"ax1.plot(x_train, y_train, label='Target function', color='#000181', lw=2, linestyle='--')\n",
"ax1.plot(x_test, predictL45[0].numpy(), label='QNN L=45', color='#AE2D68', lw=2,linestyle='-')\n",
"ax1.axvline(20, alpha=0.7,ls='--',c='#280659')\n",
"ax1.set_xlabel(r'$x$', fontdict={'size':22})\n",
"ax1.set_ylabel(r'$f(x)$', fontdict={'size':22})\n",
"plt.tick_params(labelsize=16)\n",
"ax1.legend(prop={'size': 12})\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"由结果可以看出,即使是方波这种对于经典 NN 而言难以模拟的函数,使用 QNN 也可以达到良好的近似效果,验证了 QNN 的表达能力。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"## 参考文献\n",
"\n",
"[1] Schuld, Maria, Ryan Sweke, and Johannes Jakob Meyer. \"Effect of data encoding on the expressive power of variational quantum-machine-learning models.\" [Physical Review A 103.3 (2021): 032430.](https://doi.org/10.1103/PhysRevA.103.032430)\n",
"\n",
"[2] Yu, Zhan, et al. \"Power and limitations of single-qubit native quantum neural networks.\" [arXiv preprint arXiv:2205.07848 (2022).](https://doi.org/10.48550/arXiv.2205.07848)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.8.13 ('newpq')",
"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.13"
},
"vscode": {
"interpreter": {
"hash": "de7cd855378eaab3f20deac830fabedba9ee866c40f7d3ccd0ebe0daf90b4a28"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Quantum Neural Network Approximating Functions\n",
"\n",
"*Copyright (c) 2022 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Overview\n",
"Quantum neural network (QNN) is a common quantum machine learning model that consists of parameterized quantum circuits. By tuning parameters of quantum circuits, a QNN is able to minimize an objective function of interest. Similar to the Neural Network (NN) model in machine learning, the expressivity of QNN is characterized by the function classes that it can approximate. The Universal Approximation Theorem (UAT) in machine learning theory describes the ability of multi-layer NNs to approximate any function. In recent times, the universal approximation property (UAP) of multi-qubit QNN models has been investigated by correlating QNN to Fourier series [1]. However, the expressivity of single-qubit QNNs remains an open problem. In our recent paper [2], we prove that single-qubit QNNs can approximate any univariate function, by exploring connections to quantum signal processing, which solve this open problem. In this tutorial, we demonstrate how to use single-qubit QNNs to approximate any target function."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Single-qubit QNN approximating any even function\n",
"We use data re-uploading single-qubit QNNs that consist of interleaved data encoding gates and trainable gates. Both the data encoding gates and trainable gates are selected from the Pauli rotation gates $\\{ R_X,R_Y,R_Z \\}$. Let the initial state be $|0\\rangle$,we define the output of the QNN be the measurement results of some observables $M$,\n",
"\n",
"$$\n",
"f_U(x) = \\langle 0| U^\\dagger M U |0\\rangle, \\tag{1}\n",
"$$\n",
"\n",
"where $x$ is the input data and $U$ denotes the QNN.\n",
"\n",
"First, let us consider the simplest case, i.e. choosing $R_Z$ as the data encoding gates and $R_Y$ as the trainable gate. We define the single-qubit QNN as follow,\n",
"\n",
"$$\n",
"U^{\\mathit{YZY}}_{\\mathbf{\\theta}, L}(x) = R_Y(\\theta_0) \\sum_{j=1}^LR_Z(x)R_Y(\\theta_j), \\tag{2}\n",
"$$\n",
"\n",
"where $\\mathbf{\\theta} := (\\theta_0, \\ldots, \\theta_L)$ is the set of trainable parameters and $L$ denotes the number of layers.\n",
"\n",
"We prove that a single-qubit QNN $U^{\\mathit{YZY}}_{\\mathbf{\\theta}, L}(x)$ can represent Fourier series\n",
"\n",
"$$\n",
"\\langle 0|U^{\\mathit{YZY}}_{\\mathbf{\\theta}, L}(x) |0\\rangle = a_0 + \\sum_{j=1}^{L}a_j\\cos(nx). \\tag{3}\n",
"$$\n",
"When choosing the observable as the Pauli operator $Z$,the output of this QNN can approximate any square-integrable even function $f: [-\\pi, \\pi] \\to [-1, 1]$.\n",
"\n",
"Now we numerically simulate the single-qubit QNN approximation on Paddle Quantum to verify the results. First we import the required packages."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"import paddle\n",
"import numpy as np\n",
"import paddle_quantum\n",
"from paddle_quantum.ansatz import Circuit\n",
"from paddle_quantum.hamiltonian import Hamiltonian\n",
"from paddle_quantum.loss import ExpecVal\n",
"import matplotlib.pyplot as plt\n",
"import brewer2mpl\n",
"import matplotlib\n",
"# set the backend to state vector mode\n",
"paddle_quantum.set_backend(\"state_vector\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We define a function to construct the corresponding QNN, consisting of interleaved data encoding gates $R_Z$ and trainable gates $R_Y$."
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"# Construct the parameterized quantum circuit in YZY structure.\n",
"def U_YZY(train_block, w_theta, x):\n",
" cir = Circuit(1)\n",
" for i in range(train_block):\n",
" cir.ry(0, param=w_theta[i])\n",
" cir.rz(0, param=x) # input data\n",
" cir.ry(0, param=w_theta[-1])\n",
" return cir"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let a damping function $f(x) = \\sin(5x)/5x$ be the target function, and we need to sample data points used for training."
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"C:\\Users\\yuzhan01\\AppData\\Local\\Temp\\ipykernel_26936\\3212922392.py:8: RuntimeWarning: invalid value encountered in true_divide\n",
" y_plot = np.sin(5*x_plot) / (5*x_plot)\n"
]
}
],
"source": [
"# Define the target function\n",
"def target_func(x):\n",
" return np.sin(5 * x) / (5 * x)\n",
"\n",
"# Randomly sample data points from the target function.\n",
"def get_data():\n",
" x_plot = np.arange(0, np.pi, np.pi/1000)\n",
" y_plot = np.sin(5*x_plot) / (5*x_plot)\n",
" \n",
" np.random.seed(0)\n",
" x_all = np.random.uniform(0, np.pi, 300)\n",
" \n",
" y_all = np.sin(5*x_all) / (5*x_all)\n",
"\n",
" x_train, y_train = x_all[:200], y_all[:200]\n",
" x_test, y_test = x_all[200:], y_all[200:]\n",
"\n",
" return x_train, y_train, x_test, y_test, x_plot, y_plot\n",
" \n",
"# Get the training set and test set\n",
"x_train, y_train, x_test, y_test, x_plot, y_plot = get_data()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next we define the QNN training model and a training function."
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [],
"source": [
"class QNN(paddle.nn.Layer):\n",
" def __init__(self, \n",
" train_block, # L layer\n",
" SEED=0,\n",
" dtype='float64'):\n",
" super(QNN, self).__init__()\n",
" self.train_block = train_block\n",
" paddle.seed(SEED)\n",
" # initiate trainable parameter \n",
" self.w_theta = self.create_parameter(\n",
" shape=[(train_block+1)],\n",
" default_initializer=paddle.nn.initializer.Uniform(0.0, 2 * np.pi),\n",
" dtype=dtype,\n",
" is_bias=False)\n",
"\n",
"\n",
" def forward(self, x):\n",
" \"\"\"\n",
" Forward propagation\n",
" \"\"\"\n",
" predict = []\n",
" H = Hamiltonian([(1.0, \"z0\")])\n",
" out_func = ExpecVal(H)\n",
" x = paddle.to_tensor(x, dtype='float64')\n",
" if len(x.shape) == 1: # 1-dimension data\n",
" x = x.reshape((-1, 1))\n",
" for i in range(x.shape[0]):\n",
" cir = U_YZY(self.train_block, self.w_theta, x[i])\n",
" # Run the quantum circuit\n",
" out_state = cir()\n",
" predict.append(out_func(out_state))\n",
" return paddle.concat(predict).reshape((-1,)), cir\n",
"\n",
"\n",
"# Training\n",
"def train_qnn(x, y, train_block, LR, ITR, SEED, BATCHSIZE=20):\n",
" model = QNN(train_block, SEED)\n",
" opt = paddle.optimizer.Adam(learning_rate=LR, parameters=model.parameters())\n",
" loss_list = []\n",
" x = paddle.to_tensor(x, dtype='float64')\n",
" y = paddle.to_tensor(y, dtype='float64')\n",
" for ep in range(1, ITR + 1):\n",
" # Select batch of data\n",
" for itr in range(len(x) // BATCHSIZE):\n",
" x_batch = x[itr * BATCHSIZE:(itr + 1) * BATCHSIZE]\n",
" y_batch = y[itr * BATCHSIZE:(itr + 1) * BATCHSIZE]\n",
" # Run the network defined above\n",
" predict, cir = model(x_batch)\n",
" avg_loss = paddle.mean((predict - y_batch) ** 2)\n",
" loss_list.append(avg_loss.numpy())\n",
" # Calculate the gradient and optimize\n",
" avg_loss.backward()\n",
" opt.minimize(avg_loss)\n",
" opt.clear_grad()\n",
" if (itr+1) % 5 == 0:\n",
" print(\"qnn:epoch:\", ep,\"qnn:iter:\", (itr+1), \" train loss:\", \"%.8f\" % avg_loss.numpy())\n",
"\n",
" return model, loss_list"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We use a 10-layer QNN to approximate the target function. Before training, we need to set some hyper-parameters for the optimizer."
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"c:\\Users\\yuzhan01\\Miniconda3\\envs\\pq_new\\lib\\site-packages\\paddle\\tensor\\creation.py:125: DeprecationWarning: `np.object` is a deprecated alias for the builtin `object`. To silence this warning, use `object` by itself. Doing this will not modify any behavior and is safe. \n",
"Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations\n",
" if data.dtype == np.object:\n",
"c:\\Users\\yuzhan01\\Miniconda3\\envs\\pq_new\\lib\\site-packages\\paddle\\fluid\\framework.py:1104: DeprecationWarning: `np.bool` is a deprecated alias for the builtin `bool`. To silence this warning, use `bool` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.bool_` here.\n",
"Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations\n",
" elif dtype == np.bool:\n",
"c:\\Users\\yuzhan01\\Miniconda3\\envs\\pq_new\\lib\\site-packages\\paddle\\fluid\\dygraph\\math_op_patch.py:276: UserWarning: The dtype of left and right variables are not the same, left dtype is paddle.float32, but right dtype is paddle.float64, the right dtype will convert to paddle.float32\n",
" warnings.warn(\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"qnn:epoch: 1 qnn:iter: 5 train loss: 0.12315345\n",
"qnn:epoch: 1 qnn:iter: 10 train loss: 0.06922857\n",
"qnn:epoch: 2 qnn:iter: 5 train loss: 0.02042443\n",
"qnn:epoch: 2 qnn:iter: 10 train loss: 0.04707706\n",
"qnn:epoch: 3 qnn:iter: 5 train loss: 0.01874223\n",
"qnn:epoch: 3 qnn:iter: 10 train loss: 0.01295448\n",
"qnn:epoch: 4 qnn:iter: 5 train loss: 0.00991240\n",
"qnn:epoch: 4 qnn:iter: 10 train loss: 0.00303511\n",
"qnn:epoch: 5 qnn:iter: 5 train loss: 0.00157935\n",
"qnn:epoch: 5 qnn:iter: 10 train loss: 0.00089821\n",
"qnn:epoch: 6 qnn:iter: 5 train loss: 0.00046386\n",
"qnn:epoch: 6 qnn:iter: 10 train loss: 0.00054655\n",
"qnn:epoch: 7 qnn:iter: 5 train loss: 0.00059435\n",
"qnn:epoch: 7 qnn:iter: 10 train loss: 0.00022313\n",
"qnn:epoch: 8 qnn:iter: 5 train loss: 0.00028409\n",
"qnn:epoch: 8 qnn:iter: 10 train loss: 0.00017835\n",
"qnn:epoch: 9 qnn:iter: 5 train loss: 0.00017996\n",
"qnn:epoch: 9 qnn:iter: 10 train loss: 0.00018871\n",
"qnn:epoch: 10 qnn:iter: 5 train loss: 0.00016455\n",
"qnn:epoch: 10 qnn:iter: 10 train loss: 0.00012700\n"
]
}
],
"source": [
"SEED = 4096\n",
"QITR = 10\n",
"QLR = 0.1\n",
"train_block = 10\n",
"modelL10, loss_listL10 = train_qnn(x_train, y_train, train_block=train_block, LR=QLR, ITR=QITR, SEED=SEED)\n",
"predictL10 = modelL10(x_test)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"After training, we plot the approximation result."
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAbQAAAEZCAYAAAD/ttB2AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAABTo0lEQVR4nO3de3zO9fvA8de1s83MYQcMm/Mh51ZCMoqkIlLRmZBKopMOSgcdqUhRIkUq/XTyVUhOhcKcmcOcz2bYwc679/79ce++m9ls7HBv967n43E/Zu/P6bp33+7rfn/eJzHGoJRSSpV1Lo4OQCmllCoKmtCUUko5BU1oSimlnIImNKWUUk5BE5pSSimn4OboAMorf39/Exoa6ugwlFKqTNmwYUOMMSYgt22a0BwkNDSUiIgIR4ehlFJliogcymub3nJUSinlFDShKaWUcgqa0JRSSjkFp05oIlJDRBaJiM7vpZRSTs5pO4WISB/gIyD9Co51B14F7gIygHjgeWPMqlz2HQkMzdovA3jDGPPLFQeuVDmQmZnJ0aNHSUxMdHQoqpRxd3cnMDCQSpUqXfaxTpvQgBeAbsDLQIPLPHYy0BXoaIw5LSKDgSUi0t4Ys9m2k4i8ADwLtDPG7BORbsDvItLLGLOwSJ6FUk4oJiYGEaFx48a4uDj1jSJ1GYwxJCcnc+zYMYDLTmrO/E7qaIyJutyDRKQx1hrXu8aY0wDGmOnAfuCtbPtVBl4Bphhj9mXttwT4A5hQ6OiVcmKxsbEEBQVpMlMXEBG8vb0JDg4mOjr6so932neTMSbjCg/tAwiwPEf5MqC7iFTM+r0H4J3Hfs1EpMkVXj9fb02eRa9HRvPGpJlEbN2FLgGkyhqLxYK7u7ujw1ClVIUKFUhPv+zWIqe+5XilWgKZwOEc5Qew/r2aAeuy9rOV59zPdp5d2TeIyFCstT/q1KlzxQEeOnaCI8ejOXI8mp8X/UXbZg14cegAGjSud8XnVKqkiYijQ1Cl1JW+N5y2hlYI/kCSMcaSozw+62e1bPsBJOSzn50xZpoxJswYExYQkOvMLQVyfVhLOoa1wMPd+n1kY+ReBox4g3kz513xOZVSqqzThFZwBf3KUOxfOx/sdwufvPk0C2d9wG1+fogxZLgIb/3wGx9P+oqM89pzTKkr0aRJE8LDwwkPD6d69eoEBQXZf2/SpNhaEQpl3rx5tGrVirZt2/LKK6+U6LV79uzJihUrSvSal6IJ7WIxgLeIuOYo9836eSbbftnL89qv2HifPkf/7fuZMnoYFS2ZAMxctJK3wx8gZW/OO6ZKqfxUr16dFStWsGLFCnr06EG3bt3sv1evXr3E4wkNDc03YYwaNYqpU6eyfv36QjVl5Oe1117j4YcfvqDs+++/p3PnzsV2zculCe1iW7H+XWrnKK+LdZzZzmz7AYTmsl/27cUmetJs/B+5k+u6XMfH4R2oCogx+KWlc2Lc1OK+vFJO55133rmibY509OhRatasiaurK0OGDCnRa1eqVKlUtYWW+4QmIkEikv3v8DNggPAcu3YB/jDG2NrMFgFJeewXaYzZRTFKiTpEwop1+A/uB0DLJ+/nhRPneOzkObp7eJH471atpSl1mdq3b3/JbdOmTaNDhw507dqVG2+8kcjISADWrVtH69atCQ0NZfz48XTo0MH+QR8REUFYWBgdO3bkscce4/rrr6dJkybMnz8fgMWLF9O+fXvCw8O5/fbbOX78OAADBw7k5MmTjBw5kvDwcDZs2HBBPGlpaYSHhwPQv39/Bg4cyPPPP0/lypX56quvAHj00Ufx8vKy1/Ief/xxKleuzCuvvMJdd91F48aNeemlly447wcffMB1111Hly5d6NmzJxs3bmTu3Ll89dVXLFq0iPDwcN566y3Gjx9P9erVee211+zHLlq0iA4dOnDDDTdw8803s3fvXgCmTZtGaGgo/fv359FHH6Vt27b07NmTlJSUy3+RLqFc93IUkY7AX8A04DEAY8xuEZkGvCgiC4wxMSIyCKgP3G871hgTKyJvAs+IyCxjzH4RuQm4GehV3LHbameuvj4AuPr60HLgnYQs/AuP2jU4//cGToybSt2vSue3SqVymr9kFfOXXDQZzwUa16vDc8Putf++e99hxn/+bb7nnv7+C4WOD6wDf5cvX46npycrVqzg0Ucf5e+//+baa69l4sSJdO/enTZt2vDcc8/x7LPPkpaWRp8+fXj//fcZMGAAmzdvJiwsjOnTp9OrVy8OHDhAv379iIiIoHHjxnz66ac8+OCD/Pnnn8ycOZPly5czceJEe+LKzsPDgxUrViAifP/999jWV1y3bp19n88//5zFixfbf58yZQqRkZFs3LiRBQsWcPLkSerUqcPw4cOpWbMm3377LTNnzmTd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",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"matplotlib.rcParams[\"font.family\"] = \"serif\"\n",
"matplotlib.rcParams[\"mathtext.fontset\"] = \"cm\"\n",
"bmap = brewer2mpl.get_map(\"Set1\", \"qualitative\", 7)\n",
"colors = bmap.mpl_colors\n",
"\n",
"plt.plot(x_plot, y_plot, color=\"#304860\", ls=\"--\", lw=2.5, label=\"Target function\")\n",
"\n",
"plt.scatter(\n",
" x_test,\n",
" predictL10[0].numpy(),\n",
" s=40,\n",
" marker=\"^\",\n",
" facecolor=\"white\",\n",
" color=\"#D1193E\",\n",
" label=\"QNN L=10\",\n",
" )\n",
"plt.xlabel(r\"$x$\", fontdict={\"size\": 22})\n",
"plt.ylabel(r\"$f(x)$\", fontdict={\"size\": 22})\n",
"plt.xticks(fontsize=10)\n",
"plt.yticks(fontsize=10)\n",
"plt.legend(prop={\"size\": 12})\n",
"plt.text(0, -0.2, r\"(a)\", fontsize=16)\n",
"plt.tick_params(labelsize=16)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"From the result, we can see that using a 10-layer QNN with YZY structure is able to approximate the target function in a high precision, which verifies the theoretical result on the expressivity of QNNs."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Single-qubit QNN approximating any function\n",
"\n",
"Now that $U^{\\mathit{YZY}}_{\\mathbf{\\theta}, L}$ can approximate any even function, can we make some changes so that it can approximate any function?\n",
"Actually, we just need to introduce $\\sin$ terms to complete the Fourier series, by adding an extra trainable gate $R_Z$ in each layer. We define a new QNN as follow:\n",
"\n",
"$$\n",
"U^{\\mathit{WZW}}_{\\mathbf{\\theta},\\mathbf{\\phi}, L}(x) = R_Y(\\theta_0)R_Z(\\phi_0) \\sum_{j=1}^L R_Z(x) R_Y(\\theta_j)R_Z(\\phi_j), \\tag{4}\n",
"$$\n",
"\n",
"where $\\mathbf{\\theta} := (\\theta_0, \\ldots, \\theta_L)$ and $\\mathbf{\\phi} := (\\phi_0, \\ldots, \\phi_L)$ are trainable parameters,$L$ denotes the number of layers. We proved that this QNN can represent the Fourier series,\n",
"\n",
"$$\n",
"\\langle 0|U^{\\mathit{WZW}}_{\\mathbf{\\theta},\\mathbf{\\phi}, L}(x) |0\\rangle = a_0 + \\sum_{j=1}^{L}(a_j\\cos(nx)+ b_j\\sin(nx)), \\tag{5}\n",
"$$\n",
"\n",
"and it can approximate any square-integrable function $f: [-\\pi, \\pi] \\to [-1, 1]$.\n",
"\n",
"Then we use the single-qubit QNN to approximate a square-wave function on Paddle Quantum to verify the results. First define a function to construct the corresponding QNN $U^{\\mathit{WZW}}_{\\mathbf{\\theta},\\mathbf{\\phi}, L}$."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"def U_WZW(train_block, w_theta, x):\n",
" cir = Circuit(1)\n",
" for i in range(train_block):\n",
" cir.rz(0, param=w_theta[i][1])\n",
" cir.ry(0, param=w_theta[i][0])\n",
" cir.rz(0, param=x) # input data\n",
" cir.rz(0, param=w_theta[-1][1])\n",
" cir.ry(0, param=w_theta[-1][0])\n",
" return cir"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Same as in previous section, we need to define the target function and sample data points used for training."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"def square_wave(trunk):\n",
" x_train = np.linspace(0, 20, 400)\n",
" x_test = np.linspace(0.02, 30, 150)\n",
"\n",
" def func(x):\n",
" cof = 0\n",
" for i in range(1, trunk+1, 2):\n",
" cof = cof + 4*np.sin(i*x)/(i*np.pi)\n",
" y_max = max(cof)\n",
" cof /= y_max\n",
" return cof\n",
" \n",
" y_train = func(x_train)\n",
" y_test = func(x_test)\n",
"\n",
" return x_train, y_train, x_test, y_test\n",
"\n",
"x_train, y_train, x_test, y_test = square_wave(10000)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next we define the QNN training model and a training function."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"class QNN(paddle.nn.Layer):\n",
" def __init__(self, \n",
" train_block, # L layer\n",
" SEED=0,\n",
" dtype='float64'):\n",
" super(QNN, self).__init__()\n",
" self.train_block = train_block\n",
" paddle.seed(SEED)\n",
" # initiate trainable parameter \n",
" self.w_theta = self.create_parameter(\n",
" shape=[(train_block+1), 2],\n",
" default_initializer=paddle.nn.initializer.Uniform(0.0, 2*np.pi),\n",
" dtype=dtype,\n",
" is_bias=False)\n",
"\n",
"\n",
" def forward(self, x):\n",
" \"\"\"\n",
" Forward propagation\n",
" \"\"\"\n",
" predict = []\n",
" H = Hamiltonian([(1.0, \"z0\")])\n",
" out_func = ExpecVal(H)\n",
" x = paddle.to_tensor(x, dtype='float64')\n",
" if len(x.shape) == 1: # 1-dimension data\n",
" x = x.reshape((-1, 1))\n",
" for i in range(x.shape[0]):\n",
" cir = U_WZW(self.train_block, self.w_theta, x[i])\n",
" # Run the quantum circuit\n",
" out_state = cir()\n",
" predict.append(out_func(out_state))\n",
" return paddle.concat(predict).reshape((-1,)), cir\n",
"\n",
"\n",
"# Training\n",
"def train_qnn(x, y, train_block, LR, ITR, SEED, BATCHSIZE=40):\n",
" model = QNN(train_block, SEED)\n",
" opt = paddle.optimizer.Adam(learning_rate=LR, parameters=model.parameters())\n",
" loss_list = []\n",
" x = paddle.to_tensor(x, dtype='float64')\n",
" y = paddle.to_tensor(y, dtype='float64')\n",
" for ep in range(1, ITR + 1):\n",
" # Select batch of data\n",
" for itr in range(len(x) // BATCHSIZE):\n",
" x_batch = x[itr * BATCHSIZE:(itr + 1) * BATCHSIZE]\n",
" y_batch = y[itr * BATCHSIZE:(itr + 1) * BATCHSIZE]\n",
" # Run the network defined above\n",
" predict, cir = model(x_batch)\n",
" avg_loss = paddle.mean((predict - y_batch) ** 2)\n",
" loss_list.append(avg_loss.numpy())\n",
" # Calculate the gradient and optimize\n",
" avg_loss.backward()\n",
" opt.minimize(avg_loss)\n",
" opt.clear_grad()\n",
" if (itr+1) % 5 == 0:\n",
" print(\"qnn:epoch:\", ep,\"qnn:iter:\", (itr+1), \" train loss:\", \"%.8f\" % avg_loss.numpy())\n",
"\n",
" return model, loss_list"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We use a 45-layer QNN to approximate the square-wave function. Note that the number of layers required for precise approximate is related to the truncation error for Fourier series. Usually more layers leads to better approximation results."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"c:\\Users\\yuzhan01\\Miniconda3\\envs\\pq_new\\lib\\site-packages\\paddle\\tensor\\creation.py:125: DeprecationWarning: `np.object` is a deprecated alias for the builtin `object`. To silence this warning, use `object` by itself. Doing this will not modify any behavior and is safe. \n",
"Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations\n",
" if data.dtype == np.object:\n",
"c:\\Users\\yuzhan01\\Miniconda3\\envs\\pq_new\\lib\\site-packages\\paddle\\fluid\\framework.py:1104: DeprecationWarning: `np.bool` is a deprecated alias for the builtin `bool`. To silence this warning, use `bool` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.bool_` here.\n",
"Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations\n",
" elif dtype == np.bool:\n",
"c:\\Users\\yuzhan01\\Miniconda3\\envs\\pq_new\\lib\\site-packages\\paddle\\fluid\\dygraph\\math_op_patch.py:276: UserWarning: The dtype of left and right variables are not the same, left dtype is paddle.float32, but right dtype is paddle.float64, the right dtype will convert to paddle.float32\n",
" warnings.warn(\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"qnn:epoch: 1 qnn:iter: 5 train loss: 1.35621011\n",
"qnn:epoch: 1 qnn:iter: 10 train loss: 1.06700826\n",
"qnn:epoch: 2 qnn:iter: 5 train loss: 1.59428942\n",
"qnn:epoch: 2 qnn:iter: 10 train loss: 0.32698074\n",
"qnn:epoch: 3 qnn:iter: 5 train loss: 0.30190033\n",
"qnn:epoch: 3 qnn:iter: 10 train loss: 0.09284516\n",
"qnn:epoch: 4 qnn:iter: 5 train loss: 0.11605076\n",
"qnn:epoch: 4 qnn:iter: 10 train loss: 0.06084419\n",
"qnn:epoch: 5 qnn:iter: 5 train loss: 0.10283329\n",
"qnn:epoch: 5 qnn:iter: 10 train loss: 0.07899086\n",
"qnn:epoch: 6 qnn:iter: 5 train loss: 0.06403162\n",
"qnn:epoch: 6 qnn:iter: 10 train loss: 0.05624062\n",
"qnn:epoch: 7 qnn:iter: 5 train loss: 0.05701165\n",
"qnn:epoch: 7 qnn:iter: 10 train loss: 0.05501990\n",
"qnn:epoch: 8 qnn:iter: 5 train loss: 0.05415571\n",
"qnn:epoch: 8 qnn:iter: 10 train loss: 0.05919911\n",
"qnn:epoch: 9 qnn:iter: 5 train loss: 0.05508716\n",
"qnn:epoch: 9 qnn:iter: 10 train loss: 0.05666707\n",
"qnn:epoch: 10 qnn:iter: 5 train loss: 0.05592950\n",
"qnn:epoch: 10 qnn:iter: 10 train loss: 0.05661748\n"
]
}
],
"source": [
"SEED = 2\n",
"QITR = 10\n",
"QLR = 0.1\n",
"train_block = 45\n",
"modelL45, loss_listL45 = train_qnn(x_train, y_train, train_block=train_block, LR=QLR, ITR=QITR, SEED=SEED)\n",
"predictL45 = modelL45(x_test)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Then we plot to show the approximation results."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 720x432 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
},
{
"data": {
"text/plain": [
"<Figure size 432x288 with 0 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, ax1 = plt.subplots(1, 1,figsize=(10,6))\n",
"ax1.plot(x_train, y_train, label='Target function', color='#000181', lw=2, linestyle='--')\n",
"ax1.plot(x_test, predictL45[0].numpy(), label='QNN L=45', color='#AE2D68', lw=2,linestyle='-')\n",
"ax1.axvline(20, alpha=0.7,ls='--',c='#280659')\n",
"ax1.set_xlabel(r'$x$', fontdict={'size':22})\n",
"ax1.set_ylabel(r'$f(x)$', fontdict={'size':22})\n",
"plt.tick_params(labelsize=16)\n",
"ax1.legend(prop={'size': 12})\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The single qubit QNN can approximate the target square-wave function that is arguably hard to approximate by classical NNs, which verifies the theoretical results."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"## Reference\n",
"\n",
"[1] Schuld, Maria, Ryan Sweke, and Johannes Jakob Meyer. \"Effect of data encoding on the expressive power of variational quantum-machine-learning models.\" [Physical Review A 103.3 (2021): 032430.](https://doi.org/10.1103/PhysRevA.103.032430)\n",
"\n",
"[2] Yu, Zhan, et al. \"Power and limitations of single-qubit native quantum neural networks.\" [arXiv preprint arXiv:2205.07848 (2022).](https://doi.org/10.48550/arXiv.2205.07848)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.8.13 ('newpq')",
"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.13"
},
"vscode": {
"interpreter": {
"hash": "de7cd855378eaab3f20deac830fabedba9ee866c40f7d3ccd0ebe0daf90b4a28"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
...@@ -97,7 +97,7 @@ ...@@ -97,7 +97,7 @@
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"def rand_circuit(theta, target, num_qubits):\n", "def rand_circuit(target, num_qubits, theta=None):\n",
" # 初始化量子电路\n", " # 初始化量子电路\n",
" cir = Circuit(num_qubits)\n", " cir = Circuit(num_qubits)\n",
" \n", " \n",
...@@ -108,11 +108,11 @@ ...@@ -108,11 +108,11 @@
" # target是一个随机的数组,用来帮助我们抽取随机的单比特门 \n", " # target是一个随机的数组,用来帮助我们抽取随机的单比特门 \n",
" for i in range(num_qubits):\n", " for i in range(num_qubits):\n",
" if target[i] == 0:\n", " if target[i] == 0:\n",
" cir.rz(i, param=theta[i])\n", " cir.rz(i, param=theta[i] if theta is not None else theta)\n",
" elif target[i] == 1:\n", " elif target[i] == 1:\n",
" cir.ry(i, param=theta[i])\n", " cir.ry(i, param=theta[i] if theta is not None else theta)\n",
" else:\n", " else:\n",
" cir.rx(i, param=theta[i])\n", " cir.rx(i, param=theta[i] if theta is not None else theta)\n",
" \n", " \n",
" # ============== 第二层 ==============\n", " # ============== 第二层 ==============\n",
" # 搭建两两相邻的 CZ 门\n", " # 搭建两两相邻的 CZ 门\n",
...@@ -197,7 +197,7 @@ ...@@ -197,7 +197,7 @@
"class manual_gradient(paddle.nn.Layer):\n", "class manual_gradient(paddle.nn.Layer):\n",
" \n", " \n",
" # 初始化一个可学习参数列表,并用 [0, 2*pi] 的均匀分布来填充初始值\n", " # 初始化一个可学习参数列表,并用 [0, 2*pi] 的均匀分布来填充初始值\n",
" def __init__(self, shape, param_attr=paddle.nn.initializer.Uniform(low=0.0, high=2 * pi), dtype='float32'):\n", " def __init__(self, shape, param_attr=paddle.nn.initializer.Uniform(low=0.0, high=2*pi), dtype='float32'):\n",
" super(manual_gradient, self).__init__()\n", " super(manual_gradient, self).__init__()\n",
" \n", " \n",
" # 将 Numpy array 转换成 Paddle 中的 Tensor\n", " # 将 Numpy array 转换成 Paddle 中的 Tensor\n",
...@@ -207,7 +207,7 @@ ...@@ -207,7 +207,7 @@
" def forward(self):\n", " def forward(self):\n",
" \n", " \n",
" # 初始化三个 theta 参数列表\n", " # 初始化三个 theta 参数列表\n",
" theta_np = np.random.uniform(low=0., high= 2 * pi, size=(THETA_SIZE))\n", " theta_np = np.random.uniform(low=0., high=2*pi, size=(THETA_SIZE))\n",
" theta_plus_np = np.copy(theta_np) \n", " theta_plus_np = np.copy(theta_np) \n",
" theta_minus_np = np.copy(theta_np) \n", " theta_minus_np = np.copy(theta_np) \n",
" \n", " \n",
...@@ -222,8 +222,8 @@ ...@@ -222,8 +222,8 @@
" # 生成随机目标,在 rand_circuit 中随机选取电路门\n", " # 生成随机目标,在 rand_circuit 中随机选取电路门\n",
" target = np.random.choice(3, N) \n", " target = np.random.choice(3, N) \n",
" \n", " \n",
" U_plus = rand_circuit(theta_plus, target, N).unitary_matrix()\n", " U_plus = rand_circuit(target, N, theta_plus).unitary_matrix()\n",
" U_minus = rand_circuit(theta_minus, target, N).unitary_matrix()\n", " U_minus = rand_circuit(target, N, theta_minus).unitary_matrix()\n",
"\n", "\n",
" # 计算解析梯度\n", " # 计算解析梯度\n",
" grad = paddle.real((dagger(U_plus) @ self.H @ U_plus)[0][0] - (dagger(U_minus) @ self.H @ U_minus)[0][0])/2 \n", " grad = paddle.real((dagger(U_plus) @ self.H @ U_plus)[0][0] - (dagger(U_minus) @ self.H @ U_minus)[0][0])/2 \n",
...@@ -513,9 +513,7 @@ ...@@ -513,9 +513,7 @@
"# paddle.seed(SEED)\n", "# paddle.seed(SEED)\n",
"target = np.random.choice(3, N)\n", "target = np.random.choice(3, N)\n",
"# 在 0 - 2*Pi 间随机生成各参数值\n", "# 在 0 - 2*Pi 间随机生成各参数值\n",
"theta = paddle.rand([THETA_SIZE]).astype('float32') * 2 * pi\n", "cir = rand_circuit(target, N)\n",
"theta.stop_gradient = False\n",
"cir = rand_circuit(theta, target, N)\n",
"print(cir)\n", "print(cir)\n",
"# 随机生成哈密顿量\n", "# 随机生成哈密顿量\n",
"H_l = Hamiltonian(random_pauli_str_generator(N, terms=7))\n", "H_l = Hamiltonian(random_pauli_str_generator(N, terms=7))\n",
...@@ -583,7 +581,7 @@ ...@@ -583,7 +581,7 @@
} }
], ],
"source": [ "source": [
"grad_mean_list, grad_variance_list = random_sample(cir, loss_func, samples, H_l, mode='single', param=0) " "grad_mean_list, grad_variance_list = random_sample(cir, loss_func, samples, H_l, mode='single', param=0)"
] ]
}, },
{ {
...@@ -871,13 +869,11 @@ ...@@ -871,13 +869,11 @@
" THETA_SIZE = N \n", " THETA_SIZE = N \n",
" target = np.random.choice(3, N)\n", " target = np.random.choice(3, N)\n",
" # 在 0 - 2*Pi 间随机生成各参数值\n", " # 在 0 - 2*Pi 间随机生成各参数值\n",
" theta = paddle.rand([THETA_SIZE]).astype('float32') * 2 * pi\n", " cir = rand_circuit(target, N)\n",
" theta.stop_gradient = False\n",
" cir = rand_circuit(theta, target, N)\n",
" \n", " \n",
" H_l = Hamiltonian(random_pauli_str_generator(N, terms=10))\n", " H_l = Hamiltonian(random_pauli_str_generator(N, terms=10))\n",
" \n", " \n",
" grad_mean_list, grad_variance_list = random_sample(cir, loss_func, samples, H_l, mode='max') \n", " grad_mean_list, grad_variance_list = random_sample(cir, loss_func, samples, H_l, mode='max')\n",
" # 记录采样信息\n", " # 记录采样信息\n",
" means.append(grad_mean_list)\n", " means.append(grad_mean_list)\n",
" variances.append(grad_variance_list)\n" " variances.append(grad_variance_list)\n"
...@@ -1187,7 +1183,7 @@ ...@@ -1187,7 +1183,7 @@
} }
], ],
"source": [ "source": [
"loss,grad = plot_supervised_loss_grad(circuit, loss_func, N=qubit_num, EPOCH = EPOCH, LR = lr,BATCH = BATCH, TRAIN_X=train_x, TRAIN_Y=train_y)" "loss,grad = plot_supervised_loss_grad(circuit, loss_func, N=qubit_num, EPOCH=EPOCH, LR=lr,BATCH=BATCH, TRAIN_X=train_x, TRAIN_Y=train_y)"
] ]
}, },
{ {
...@@ -1276,11 +1272,8 @@ ...@@ -1276,11 +1272,8 @@
} }
], ],
"metadata": { "metadata": {
"interpreter": {
"hash": "2c4627ff4a417355d61049564f513ba5a6282bdfd3c46a5a6bb293c3faa304ed"
},
"kernelspec": { "kernelspec": {
"display_name": "Python 3.8.13 ('pd_dep')", "display_name": "Python 3.8.13 ('pq')",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
...@@ -1337,6 +1330,11 @@ ...@@ -1337,6 +1330,11 @@
"_Feature" "_Feature"
], ],
"window_display": false "window_display": false
},
"vscode": {
"interpreter": {
"hash": "1e82098cfee7be27b5e385e3f85fe91d734d6114f7d09dccafdaad2c23171c3e"
}
} }
}, },
"nbformat": 4, "nbformat": 4,
......
...@@ -96,7 +96,7 @@ ...@@ -96,7 +96,7 @@
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"def rand_circuit(theta, target, num_qubits):\n", "def rand_circuit(target, num_qubits, theta=None):\n",
" # Initialize the quantum circuit\n", " # Initialize the quantum circuit\n",
" cir = Circuit(num_qubits)\n", " cir = Circuit(num_qubits)\n",
" \n", " \n",
...@@ -107,11 +107,11 @@ ...@@ -107,11 +107,11 @@
" # Fixed-angle Ry rotation gates \n", " # Fixed-angle Ry rotation gates \n",
" for i in range(num_qubits):\n", " for i in range(num_qubits):\n",
" if target[i] == 0:\n", " if target[i] == 0:\n",
" cir.rz(i, param=theta[i])\n", " cir.rz(i, param=theta[i] if theta is not None else theta)\n",
" elif target[i] == 1:\n", " elif target[i] == 1:\n",
" cir.ry(i, param=theta[i])\n", " cir.ry(i, param=theta[i] if theta is not None else theta)\n",
" else:\n", " else:\n",
" cir.rx(i, param=theta[i])\n", " cir.rx(i, param=theta[i] if theta is not None else theta)\n",
" \n", " \n",
" # ============== Second layer ==============\n", " # ============== Second layer ==============\n",
" # Build adjacent CZ gates\n", " # Build adjacent CZ gates\n",
...@@ -195,7 +195,7 @@ ...@@ -195,7 +195,7 @@
"class manual_gradient(paddle.nn.Layer):\n", "class manual_gradient(paddle.nn.Layer):\n",
" \n", " \n",
" # Initialize a list of learnable parameters and fill the initial value with a uniform distribution of [0, 2*pi]\n", " # Initialize a list of learnable parameters and fill the initial value with a uniform distribution of [0, 2*pi]\n",
" def __init__(self, shape, param_attr=paddle.nn.initializer.Uniform(low=0.0, high=2 * pi), dtype='float32'):\n", " def __init__(self, shape, param_attr=paddle.nn.initializer.Uniform(low=0.0, high=2*pi), dtype='float32'):\n",
" super(manual_gradient, self).__init__()\n", " super(manual_gradient, self).__init__()\n",
" \n", " \n",
" # Convert Numpy array to Tensor in PaddlePaddle\n", " # Convert Numpy array to Tensor in PaddlePaddle\n",
...@@ -205,7 +205,7 @@ ...@@ -205,7 +205,7 @@
" def forward(self):\n", " def forward(self):\n",
" \n", " \n",
" # Initialize three theta parameter lists\n", " # Initialize three theta parameter lists\n",
" theta_np = np.random.uniform(low=0., high= 2 * pi, size=(THETA_SIZE))\n", " theta_np = np.random.uniform(low=0., high=2*pi, size=(THETA_SIZE))\n",
" theta_plus_np = np.copy(theta_np) \n", " theta_plus_np = np.copy(theta_np) \n",
" theta_minus_np = np.copy(theta_np) \n", " theta_minus_np = np.copy(theta_np) \n",
" \n", " \n",
...@@ -220,8 +220,8 @@ ...@@ -220,8 +220,8 @@
" # Generate random targets, randomly select circuit gates in rand_circuit\n", " # Generate random targets, randomly select circuit gates in rand_circuit\n",
" target = np.random.choice(3, N) \n", " target = np.random.choice(3, N) \n",
" \n", " \n",
" U_plus = rand_circuit(theta_plus, target, N).unitary_matrix()\n", " U_plus = rand_circuit(target, N, theta_plus).unitary_matrix()\n",
" U_minus = rand_circuit(theta_minus, target, N).unitary_matrix()\n", " U_minus = rand_circuit(target, N, theta_minus).unitary_matrix()\n",
"\n", "\n",
" # Calculate the analytical gradient\n", " # Calculate the analytical gradient\n",
" grad = paddle.real((dagger(U_plus) @ self.H @ U_plus)[0][0] - (dagger(U_minus) @ self.H @ U_minus)[0][0])/2 \n", " grad = paddle.real((dagger(U_plus) @ self.H @ U_plus)[0][0] - (dagger(U_minus) @ self.H @ U_minus)[0][0])/2 \n",
...@@ -469,7 +469,7 @@ ...@@ -469,7 +469,7 @@
"outputs": [], "outputs": [],
"source": [ "source": [
"# Hyper parameter settings\n", "# Hyper parameter settings\n",
"np.random.seed(42) # Fixed Numpy random seed\n", "#np.random.seed(1) # Fixed Numpy random seed\n",
"N = 2 # Set the number of qubits\n", "N = 2 # Set the number of qubits\n",
"samples = 300 # Set the number of sampled random network structures\n", "samples = 300 # Set the number of sampled random network structures\n",
"THETA_SIZE = N # Set the size of the parameter theta\n", "THETA_SIZE = N # Set the size of the parameter theta\n",
...@@ -511,9 +511,7 @@ ...@@ -511,9 +511,7 @@
"# paddle.seed(SEED)\n", "# paddle.seed(SEED)\n",
"target = np.random.choice(3, N)\n", "target = np.random.choice(3, N)\n",
"# Random generate parameters between 0 and 2*Pi \n", "# Random generate parameters between 0 and 2*Pi \n",
"theta = paddle.rand([THETA_SIZE]).astype('float32') * 2 * pi\n", "cir = rand_circuit(target, N)\n",
"theta.stop_gradient = False\n",
"cir = rand_circuit(theta, target, N)\n",
"print(cir)\n", "print(cir)\n",
"# Random generate Hamiltonian information, in Pauli string format\n", "# Random generate Hamiltonian information, in Pauli string format\n",
"H_l = Hamiltonian(random_pauli_str_generator(N, terms=7))\n", "H_l = Hamiltonian(random_pauli_str_generator(N, terms=7))\n",
...@@ -582,7 +580,7 @@ ...@@ -582,7 +580,7 @@
} }
], ],
"source": [ "source": [
"grad_mean_list, grad_variance_list = random_sample(cir, loss_func, samples, H_l, mode='single', param=0) " "grad_mean_list, grad_variance_list = random_sample(cir, loss_func, samples, H_l, mode='single', param=0)"
] ]
}, },
{ {
...@@ -850,13 +848,11 @@ ...@@ -850,13 +848,11 @@
" THETA_SIZE = N \n", " THETA_SIZE = N \n",
" target = np.random.choice(3, N)\n", " target = np.random.choice(3, N)\n",
" # Generate a value from 0 to 2PI\n", " # Generate a value from 0 to 2PI\n",
" theta = paddle.rand([THETA_SIZE]).astype('float32') * 2 * pi\n", " cir = rand_circuit(target, N)\n",
" theta.stop_gradient = False\n",
" cir = rand_circuit(theta, target, N)\n",
" \n", " \n",
" H_l = Hamiltonian(random_pauli_str_generator(N, terms=10))\n", " H_l = Hamiltonian(random_pauli_str_generator(N, terms=10))\n",
" \n", " \n",
" grad_mean_list, grad_variance_list = random_sample(cir, loss_func, samples, H_l, mode='max') \n", " grad_mean_list, grad_variance_list = random_sample(cir, loss_func, samples, H_l, mode='max')\n",
" # Record sampling information\n", " # Record sampling information\n",
" means.append(grad_mean_list)\n", " means.append(grad_mean_list)\n",
" variances.append(grad_variance_list)" " variances.append(grad_variance_list)"
...@@ -1165,7 +1161,7 @@ ...@@ -1165,7 +1161,7 @@
} }
], ],
"source": [ "source": [
"loss,grad = plot_supervised_loss_grad(circuit, loss_func, N=qubit_num, EPOCH = EPOCH, LR = lr,BATCH = BATCH, TRAIN_X=train_x, TRAIN_Y=train_y)" "loss,grad = plot_supervised_loss_grad(circuit, loss_func, N=qubit_num, EPOCH=EPOCH, LR=lr,BATCH=BATCH, TRAIN_X=train_x, TRAIN_Y=train_y)"
] ]
}, },
{ {
...@@ -1247,11 +1243,8 @@ ...@@ -1247,11 +1243,8 @@
} }
], ],
"metadata": { "metadata": {
"interpreter": {
"hash": "2c4627ff4a417355d61049564f513ba5a6282bdfd3c46a5a6bb293c3faa304ed"
},
"kernelspec": { "kernelspec": {
"display_name": "Python 3.8.13 ('pd_dep')", "display_name": "Python 3.8.13 ('pq')",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
...@@ -1308,6 +1301,11 @@ ...@@ -1308,6 +1301,11 @@
"_Feature" "_Feature"
], ],
"window_display": false "window_display": false
},
"vscode": {
"interpreter": {
"hash": "1e82098cfee7be27b5e385e3f85fe91d734d6114f7d09dccafdaad2c23171c3e"
}
} }
}, },
"nbformat": 4, "nbformat": 4,
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"# VAns可变结构电路" "# VAns: 可变结构电路"
] ]
}, },
{ {
...@@ -45,11 +45,11 @@ ...@@ -45,11 +45,11 @@
"1. 准备一个简单的初始电路,使用经典优化器对电路参数进行调整,最小化目标损失函数,记录最优值。\n", "1. 准备一个简单的初始电路,使用经典优化器对电路参数进行调整,最小化目标损失函数,记录最优值。\n",
"2. 从一个集合中随机选择量子门模块(如下图所示,各量子门模块仅由 $R_y$ 门、$R_z$ 门,和 $CNOT$ 门组成),随机选择插入模块所作用的量子比特,并将模块添加到电路末尾。添加模块中的量子门的参数都初始化为 $0$ ,这样添加的模块一开始等同于 $I$。\n", "2. 从一个集合中随机选择量子门模块(如下图所示,各量子门模块仅由 $R_y$ 门、$R_z$ 门,和 $CNOT$ 门组成),随机选择插入模块所作用的量子比特,并将模块添加到电路末尾。添加模块中的量子门的参数都初始化为 $0$ ,这样添加的模块一开始等同于 $I$。\n",
"\n", "\n",
"![Inserting Blocks](./figures/vans-fig-blocks.png)\n", "![Inserting Blocks](blocks.png)\n",
"\n", "\n",
"3. 根据下图中的规则化简电路,对化简后的电路进行优化,更新其参数,获得损失函数的当前最优值。比较当前损失函数最优值与上一个记录的最优值,根据设定的阈值决定是否接受新电路。若接受新电路,则继续遍历电路中的量子门,并删除不会降低损失的门,将精简过的电路记为当前电路,并记录其最优损失。若拒绝则直接回到第2步。\n", "3. 根据下图中的规则化简电路,对化简后的电路进行优化,更新其参数,获得损失函数的当前最优值。比较当前损失函数最优值与上一个记录的最优值,根据设定的阈值决定是否接受新电路。若接受新电路,则继续遍历电路中的量子门,并删除不会降低损失的门,将精简过的电路记为当前电路,并记录其最优损失。若拒绝则直接回到第2步。\n",
"\n", "\n",
"![Simplification rules](./figures/vans-fig-rules.png)\n", "![Simplification rules](rules.png)\n",
"\n", "\n",
"4. 重复步骤2-3,达到设定的迭代次数后停止,输出电路和最优损失。" "4. 重复步骤2-3,达到设定的迭代次数后停止,输出电路和最优损失。"
] ]
...@@ -70,20 +70,9 @@ ...@@ -70,20 +70,9 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 1, "execution_count": null,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [],
{
"data": {
"text/plain": [
"<paddle.fluid.core_noavx.Generator at 0x7fec104cc570>"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [ "source": [
"import paddle\n", "import paddle\n",
"import paddle_quantum\n", "import paddle_quantum\n",
...@@ -92,9 +81,7 @@ ...@@ -92,9 +81,7 @@
"from paddle_quantum.ansatz import Circuit\n", "from paddle_quantum.ansatz import Circuit\n",
"from paddle_quantum.ansatz.vans import Inserter, Simplifier, VAns, cir_decompose\n", "from paddle_quantum.ansatz.vans import Inserter, Simplifier, VAns, cir_decompose\n",
"from paddle_quantum.hamiltonian import Hamiltonian\n", "from paddle_quantum.hamiltonian import Hamiltonian\n",
"import warnings\n",
"\n", "\n",
"warnings.filterwarnings(\"ignore\")\n",
"np.random.seed(11)\n", "np.random.seed(11)\n",
"paddle.seed(11)" "paddle.seed(11)"
] ]
...@@ -214,18 +201,30 @@ ...@@ -214,18 +201,30 @@
"execution_count": 6, "execution_count": 6,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"c:\\Users\\v_zhanglei48\\Anaconda3\\envs\\pq_new\\lib\\site-packages\\paddle\\fluid\\framework.py:744: DeprecationWarning: `np.bool` is a deprecated alias for the builtin `bool`. To silence this warning, use `bool` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.bool_` here.\n",
"Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations\n",
" elif dtype == np.bool:\n",
"c:\\Users\\v_zhanglei48\\Anaconda3\\envs\\pq_new\\lib\\site-packages\\paddle\\tensor\\creation.py:130: DeprecationWarning: `np.object` is a deprecated alias for the builtin `object`. To silence this warning, use `object` by itself. Doing this will not modify any behavior and is safe. \n",
"Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations\n",
" if data.dtype == np.object:\n"
]
},
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"iter: 20 loss: [-0.99608546]\n", "iter: 20 loss: [-0.9960856]\n",
"iter: 40 loss: [-1.0926807]\n", "iter: 40 loss: [-1.0926806]\n",
"iter: 60 loss: [-1.1136395]\n", "iter: 60 loss: [-1.11364]\n",
"iter: 80 loss: [-1.1163319]\n", "iter: 80 loss: [-1.1163325]\n",
"iter: 100 loss: [-1.1167175]\n", "iter: 100 loss: [-1.1167175]\n",
"iter: 120 loss: [-1.1167547]\n", "iter: 120 loss: [-1.1167548]\n",
"当前电路:\n", "当前电路:\n",
"--Rx(3.144)----Rz(3.512)----*----Rx(6.286)----Rz(2.441)-------------------------------------------------------------x--\n", "--Rx(3.144)----Rz(3.512)----*----Rx(6.286)----Rz(3.039)-------------------------------------------------------------x--\n",
" | | \n", " | | \n",
"----------------------------x----Rx(4.249)----Rz(3.141)----Rx(4.246)----Rz(5.477)----*------------------------------|--\n", "----------------------------x----Rx(4.249)----Rz(3.141)----Rx(4.246)----Rz(5.477)----*------------------------------|--\n",
" | | \n", " | | \n",
...@@ -258,7 +257,7 @@ ...@@ -258,7 +257,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"现在我们从量子门模块集合中随机选取模块,随机选取插入模块所作用的量子比特,并将模块插入到当前电路的尾端。注意新插入模块中的量子门的参数都被设置为 $0$,这样添加模块前后的电路实际上是相同的,也就是说更新后的电路可以继承之前电路的损失。下面的代码展示了如何在电路中插入模块。一次插入模块的数量由参数 **insert_rate** 决定。另一个参数 **epsilon** 是用来设定插入模块初始参数与 $0$ 之间的差别。" "现在我们从量子门模块集合中随机选取模块,随机选取插入模块所作用的量子比特,并将模块插入到当前电路。注意新插入模块中的量子门的参数都被设置为 $0$,这样添加模块前后的电路实际上是相同的,也就是说更新后的电路可以继承之前电路的损失。下面的代码展示了如何在电路中插入模块。一次插入模块的数量由参数 **insert_rate** 决定。另一个参数 **epsilon** 是用来设定插入模块初始参数与 $0$ 之间的差别。"
] ]
}, },
{ {
...@@ -271,9 +270,9 @@ ...@@ -271,9 +270,9 @@
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"添加过后的电路:\n", "添加过后的电路:\n",
"--Rx(3.144)----Rz(3.512)----*----Rx(6.286)----Rz(2.441)----------------------------------------------------------------------------------------------------x--\n", "--Rx(3.144)----Rz(3.512)----*----Rx(6.286)----Rz(3.039)----------------------------------------------------------------------------------------------------x--\n",
" | | \n", " | | \n",
"----------------------------x----Rz(0.000)----Rx(0.000)----Rx(-0.00)----Rx(4.249)----Rz(3.141)----Rx(4.246)----Rz(5.477)----*------------------------------|--\n", "----------------------------x----Rz(0.000)----Rx(0.000)----Rz(-0.00)----Rx(4.249)----Rz(3.141)----Rx(4.246)----Rz(5.477)----*------------------------------|--\n",
" | | \n", " | | \n",
"--Rx(0.001)----Rz(5.499)----*-----------------------------------------------------------------------------------------------x----Rx(3.141)----Rz(2.688)----|--\n", "--Rx(0.001)----Rz(5.499)----*-----------------------------------------------------------------------------------------------x----Rx(3.141)----Rz(2.688)----|--\n",
" | | \n", " | | \n",
...@@ -316,24 +315,32 @@ ...@@ -316,24 +315,32 @@
"execution_count": 8, "execution_count": 8,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"c:\\Users\\v_zhanglei48\\Anaconda3\\envs\\pq_new\\lib\\site-packages\\paddle\\fluid\\dygraph\\math_op_patch.py:251: UserWarning: The dtype of left and right variables are not the same, left dtype is paddle.float64, but right dtype is paddle.float32, the right dtype will convert to paddle.float64\n",
" warnings.warn(\n"
]
},
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"--Rx(3.144)----Rz(3.512)----*----Rx(6.286)----Rz(2.441)------------------------------------------------x--\n", "--Rx(3.144)----Rz(3.512)----*----Rx(6.286)----Rz(3.039)------------------------------------------------x--\n",
" | | \n", " | | \n",
"----------------------------x----Rz(4.491)----Rx(4.470)----Rz(8.712)----*------------------------------|--\n", "----------------------------x----Rz(5.962)----Rx(5.858)----Rz(9.426)----*------------------------------|--\n",
" | | \n", " | | \n",
"--Rx(0.001)----Rz(5.499)----*-------------------------------------------x----Rx(3.141)----Rz(2.688)----|--\n", "--Rx(0.001)----Rz(5.499)----*-------------------------------------------x----Rx(3.141)----Rz(2.688)----|--\n",
" | | \n", " | | \n",
"----------------------------x----Rz(3.604)----Rx(5.126)----Rz(4.462)-----------------------------------*--\n", "----------------------------x----Rz(3.604)----Rx(5.126)----Rz(4.462)-----------------------------------*--\n",
" \n", " \n",
"iter: 20 loss: [-1.1121466]\n", "iter: 20 loss: [-1.1040964]\n",
"iter: 40 loss: [-1.1123195]\n", "iter: 40 loss: [-1.1159219]\n",
"iter: 60 loss: [-1.1158162]\n", "iter: 60 loss: [-1.1165943]\n",
"iter: 80 loss: [-1.1166465]\n", "iter: 80 loss: [-1.116723]\n",
"iter: 100 loss: [-1.1167465]\n", "iter: 100 loss: [-1.1167536]\n",
"iter: 120 loss: [-1.1167578]\n", "iter: 120 loss: [-1.1167593]\n",
"Accpet the new circuit!\n", "Accpet the new circuit!\n",
" start deleting gates\n", " start deleting gates\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
...@@ -399,7 +406,7 @@ ...@@ -399,7 +406,7 @@
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"当前电路为:\n", "当前电路为:\n",
"--Rx(3.142)----*----------------------x--\n", "--Rx(3.141)----*----------------------x--\n",
" | | \n", " | | \n",
"---------------x----*-----------------|--\n", "---------------x----*-----------------|--\n",
" | | \n", " | | \n",
...@@ -448,30 +455,30 @@ ...@@ -448,30 +455,30 @@
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"Out iteration 1 for structure optimization:\n", "Out iteration 1 for structure optimization:\n",
"iter: 20 loss: [-1.1167215]\n", "iter: 20 loss: [-1.1167214]\n",
"iter: 40 loss: [-1.1166948]\n", "iter: 40 loss: [-1.1166948]\n",
"iter: 60 loss: [-1.1167493]\n", "iter: 60 loss: [-1.1167494]\n",
"iter: 80 loss: [-1.116759]\n", "iter: 80 loss: [-1.116759]\n",
"iter: 100 loss: [-1.1167591]\n", "iter: 100 loss: [-1.1167591]\n",
"iter: 120 loss: [-1.1167595]\n", "iter: 120 loss: [-1.1167595]\n",
" Current loss: [-1.1167595]\n", " Current loss: [-1.1167595]\n",
" Current cir:\n", " Current cir:\n",
"--Rx(3.142)----*----------------------x--\n", "--Rx(3.141)----*----------------------x--\n",
" | | \n", " | | \n",
"---------------x----*-----------------|--\n", "---------------x----*-----------------|--\n",
" | | \n", " | | \n",
"--------------------x----Rx(3.142)----|--\n", "--------------------x----Rx(3.141)----|--\n",
" | \n", " | \n",
"--------------------------------------*--\n", "--------------------------------------*--\n",
" \n", " \n",
"\n", "\n",
"Out iteration 2 for structure optimization:\n", "Out iteration 2 for structure optimization:\n",
"iter: 20 loss: [-1.0998794]\n", "iter: 20 loss: [-1.008932]\n",
"iter: 40 loss: [-1.1144476]\n", "iter: 40 loss: [-1.1085335]\n",
"iter: 60 loss: [-1.1166729]\n", "iter: 60 loss: [-1.133744]\n",
"iter: 80 loss: [-1.1167666]\n", "iter: 80 loss: [-1.1368372]\n",
"iter: 100 loss: [-1.1207738]\n", "iter: 100 loss: [-1.1372123]\n",
"iter: 120 loss: [-1.1371526]\n", "iter: 120 loss: [-1.1372803]\n",
" accpet the new circuit!\n", " accpet the new circuit!\n",
" start deleting gates\n", " start deleting gates\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
...@@ -479,27 +486,28 @@ ...@@ -479,27 +486,28 @@
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
" Deletion: reject deletion\n",
" Deletion: accept deletion with acceptable loss\n", " Deletion: accept deletion with acceptable loss\n",
" 2 gates are deleted!\n", " Deletion: accept deletion with acceptable loss\n",
" Current loss: -1.1369835138320923\n", " Deletion: reject deletion\n",
" 3 gates are deleted!\n",
" Current loss: -1.1331816911697388\n",
" Current cir:\n", " Current cir:\n",
"--Rx(3.142)----*------------------------------------------------------------------x--\n", "--Rx(3.143)----*------------------------------------------------------------------x--\n",
" | | \n", " | | \n",
"---------------x----*----Rx(-0.24)----Rz(1.462)----*--------*---------------------|--\n", "---------------x----*----Rx(0.223)----Rz(2.485)----*--------*---------------------|--\n",
" | | | | \n", " | | | | \n",
"--------------------|------------------------------|--------x--------Rx(3.142)----|--\n", "--------------------|------------------------------|--------x--------Rx(3.142)----|--\n",
" | | | \n", " | | | \n",
"--------------------x------------------------------x----Rz(3.103)-----------------*--\n", "--------------------x------------------------------x----Rz(0.458)-----------------*--\n",
" \n", " \n",
"\n", "\n",
"Out iteration 3 for structure optimization:\n", "Out iteration 3 for structure optimization:\n",
"iter: 20 loss: [-1.108532]\n", "iter: 20 loss: [-0.37809896]\n",
"iter: 40 loss: [-1.1331518]\n", "iter: 40 loss: [-1.0471339]\n",
"iter: 60 loss: [-1.1366322]\n", "iter: 60 loss: [-1.1262195]\n",
"iter: 80 loss: [-1.1372447]\n", "iter: 80 loss: [-1.1365637]\n",
"iter: 100 loss: [-1.1372827]\n", "iter: 100 loss: [-1.1371676]\n",
"iter: 120 loss: [-1.1372826]\n", "iter: 120 loss: [-1.1372685]\n",
" accpet the new circuit!\n", " accpet the new circuit!\n",
" start deleting gates\n", " start deleting gates\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
...@@ -509,86 +517,92 @@ ...@@ -509,86 +517,92 @@
" Deletion: accept deletion with acceptable loss\n", " Deletion: accept deletion with acceptable loss\n",
" Deletion: accept deletion with acceptable loss\n", " Deletion: accept deletion with acceptable loss\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
" Deletion: reject deletion\n", " Deletion: accept deletion with acceptable loss\n",
" 3 gates are deleted!\n", " 4 gates are deleted!\n",
" Current loss: -1.1372838020324707\n", " Current loss: -1.1282802820205688\n",
" Current cir:\n", " Current cir:\n",
"--Rx(3.142)----*------------------------------------------------------------------x--\n", "--Rx(3.143)----*----------------------------------------------------------x--\n",
" | | \n", " | | \n",
"---------------x----*----Rx(-0.22)----Rz(1.497)----*--------*---------------------|--\n", "---------------x----*----Rx(0.224)----Rz(2.252)----*----*-----------------|--\n",
" | | | | \n", " | | | | \n",
"--------------------|------------------------------|--------x--------Rx(3.141)----|--\n", "--------------------|------------------------------|----x----Rx(3.146)----|--\n",
" | | | \n", " | | | \n",
"--------------------x------------------------------x----Rz(3.068)-----------------*--\n", "--------------------x------------------------------x----------------------*--\n",
" \n", " \n",
"\n", "\n",
"Out iteration 4 for structure optimization:\n", "Out iteration 4 for structure optimization:\n",
"iter: 20 loss: [-1.137021]\n", "iter: 20 loss: [-0.5603936]\n",
"iter: 40 loss: [-1.1371992]\n", "iter: 40 loss: [-1.0798837]\n",
"iter: 60 loss: [-1.137272]\n", "iter: 60 loss: [-1.1325867]\n",
"iter: 80 loss: [-1.137283]\n", "iter: 80 loss: [-1.1360469]\n",
"iter: 100 loss: [-1.1372836]\n", "iter: 100 loss: [-1.1370715]\n",
"iter: 120 loss: [-1.137284]\n", "iter: 120 loss: [-1.1372685]\n",
" accpet the new circuit!\n", " accpet the new circuit!\n",
" start deleting gates\n", " start deleting gates\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
" Deletion: accept deletion with acceptable loss\n", " Deletion: accept deletion with acceptable loss\n",
" Deletion: accept deletion with acceptable loss\n",
" Deletion: accept deletion with acceptable loss\n",
" Deletion: accept deletion with acceptable loss\n",
" Deletion: accept deletion with acceptable loss\n",
" Deletion: accept deletion with acceptable loss\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
" Deletion: reject deletion\n", " Deletion: accept deletion with acceptable loss\n",
" 1 gates are deleted!\n", " Deletion: accept deletion with acceptable loss\n",
" Current loss: -1.1372839212417603\n", " Deletion: accept deletion with acceptable loss\n",
" 9 gates are deleted!\n",
" Current loss: -1.1321836709976196\n",
" Current cir:\n", " Current cir:\n",
"--Rx(3.141)----*------------------------------------------------------------------x--\n", "--Rx(3.145)----*----------------------------------------------------------x--\n",
" | | \n", " | | \n",
"---------------x----*----Rx(-0.22)----Rz(1.497)----*--------*---------------------|--\n", "---------------x----*----Rx(0.228)----Rz(2.075)----*----*-----------------|--\n",
" | | | | \n", " | | | | \n",
"--------------------|------------------------------|--------x--------Rx(3.141)----|--\n", "--------------------|------------------------------|----x----Rx(3.143)----|--\n",
" | | | \n", " | | | \n",
"--------------------x------------------------------x----Rz(3.068)-----------------*--\n", "--------------------x------------------------------x----------------------*--\n",
" \n", " \n",
"\n", "\n",
"Out iteration 5 for structure optimization:\n", "Out iteration 5 for structure optimization:\n",
"iter: 20 loss: [-1.137092]\n", "iter: 20 loss: [-1.136969]\n",
"iter: 40 loss: [-1.1371393]\n", "iter: 40 loss: [-1.1371931]\n",
"iter: 60 loss: [-1.1372617]\n", "iter: 60 loss: [-1.137272]\n",
"iter: 80 loss: [-1.1372828]\n", "iter: 80 loss: [-1.1372828]\n",
"iter: 100 loss: [-1.1372836]\n", "iter: 100 loss: [-1.1372837]\n",
"iter: 120 loss: [-1.1372839]\n", "iter: 120 loss: [-1.1372838]\n",
" accpet the new circuit!\n", " accpet the new circuit!\n",
" start deleting gates\n", " start deleting gates\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
" Deletion: reject deletion\n",
" Deletion: reject deletion\n",
" Deletion: accept deletion with acceptable loss\n", " Deletion: accept deletion with acceptable loss\n",
" Deletion: accept deletion with acceptable loss\n", " Deletion: accept deletion with acceptable loss\n",
" Deletion: reject deletion\n",
" 2 gates are deleted!\n", " 2 gates are deleted!\n",
" Current loss: -1.1372840404510498\n", " Current loss: -1.1372729539871216\n",
" Current cir:\n", " Current cir:\n",
"--Rx(3.141)----*------------------------------------------------------------------x--\n", "--Rx(3.142)----*----------------------------------------------------------x--\n",
" | | \n", " | | \n",
"---------------x----*----Rx(-0.22)----Rz(1.497)----*--------*---------------------|--\n", "---------------x----*----Rx(0.226)----Rz(1.571)----*----*-----------------|--\n",
" | | | | \n", " | | | | \n",
"--------------------|------------------------------|--------x--------Rx(3.141)----|--\n", "--------------------|------------------------------|----x----Rx(3.149)----|--\n",
" | | | \n", " | | | \n",
"--------------------x------------------------------x----Rz(3.068)-----------------*--\n", "--------------------x------------------------------x----------------------*--\n",
" \n", " \n",
"\n", "\n",
"\n", "\n",
"\n", "\n",
"The final loss: -1.1372840404510498\n", "The final loss: -1.1372729539871216\n",
"The final circuit:\n", "The final circuit:\n",
"--Rx(3.141)----*------------------------------------------------------------------x--\n", "--Rx(3.142)----*----------------------------------------------------------x--\n",
" | | \n", " | | \n",
"---------------x----*----Rx(-0.22)----Rz(1.497)----*--------*---------------------|--\n", "---------------x----*----Rx(0.226)----Rz(1.571)----*----*-----------------|--\n",
" | | | | \n", " | | | | \n",
"--------------------|------------------------------|--------x--------Rx(3.141)----|--\n", "--------------------|------------------------------|----x----Rx(3.149)----|--\n",
" | | | \n", " | | | \n",
"--------------------x------------------------------x----Rz(3.068)-----------------*--\n", "--------------------x------------------------------x----------------------*--\n",
" \n" " \n"
] ]
} }
], ],
...@@ -614,16 +628,16 @@ ...@@ -614,16 +628,16 @@
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"最终电路:\n", "最终电路:\n",
"--Rx(3.141)----*------------------------------------------------------------------x--\n", "--Rx(3.142)----*----------------------------------------------------------x--\n",
" | | \n", " | | \n",
"---------------x----*----Rx(-0.22)----Rz(1.497)----*--------*---------------------|--\n", "---------------x----*----Rx(0.226)----Rz(1.571)----*----*-----------------|--\n",
" | | | | \n", " | | | | \n",
"--------------------|------------------------------|--------x--------Rx(3.141)----|--\n", "--------------------|------------------------------|----x----Rx(3.149)----|--\n",
" | | | \n", " | | | \n",
"--------------------x------------------------------x----Rz(3.068)-----------------*--\n", "--------------------x------------------------------x----------------------*--\n",
" \n", " \n",
"最终损失:\n", "最终损失:\n",
"-1.1372840404510498\n" "-1.1372729539871216\n"
] ]
} }
], ],
...@@ -660,10 +674,10 @@ ...@@ -660,10 +674,10 @@
], ],
"metadata": { "metadata": {
"interpreter": { "interpreter": {
"hash": "9043b12ec77a531919bc05f05830335d23baf822720cbea14b03018197d26545" "hash": "2ab84abaf8d5bbc8765aba8eb82d11e7069f2ff20e8f79b8a9cdeccefd2ac4da"
}, },
"kernelspec": { "kernelspec": {
"display_name": "Python 3.8.0 ('paddle-quantum-dev')", "display_name": "Python 3.8.13 ('pq_new')",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
...@@ -677,7 +691,7 @@ ...@@ -677,7 +691,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.0" "version": "3.8.13"
} }
}, },
"nbformat": 4, "nbformat": 4,
......
...@@ -2,15 +2,13 @@ ...@@ -2,15 +2,13 @@
"cells": [ "cells": [
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "241d2f96",
"metadata": {}, "metadata": {},
"source": [ "source": [
"# VAnsVariable Ansatz" "# VAns: Variable Ansatz"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "c94780c9",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Copyright (c) 2022 Institute for Quantum Computing, Baidu Inc. All Rights Reserved." "Copyright (c) 2022 Institute for Quantum Computing, Baidu Inc. All Rights Reserved."
...@@ -18,7 +16,6 @@ ...@@ -18,7 +16,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "d84c0b5d",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## Overview" "## Overview"
...@@ -26,7 +23,6 @@ ...@@ -26,7 +23,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "96c37591",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Variational Quantum Algorithms (VQA) are about to tune parameters of quantum circuits to minimize an objective function of interest. The commonly used VQAs, like Variational Quantum Eigensolvers (VQE) and Quantum Approximate Optimization Algorithm (QAOA), perform optimization using a user-defined ansatz with fixed structure. However, if the ansatz is too simple or shallow, the expressivity of the circuit will not be insufficient to get the optimal value for the objective function. On the other side, if the ansatz is overly complicated or long, we may encounter barren plateau effect, which impedes us from obtaining the global minimum value. Thus, a circuit structure design search algorithm will be helpful to find the appropriate circuit for a specific task.\n", "Variational Quantum Algorithms (VQA) are about to tune parameters of quantum circuits to minimize an objective function of interest. The commonly used VQAs, like Variational Quantum Eigensolvers (VQE) and Quantum Approximate Optimization Algorithm (QAOA), perform optimization using a user-defined ansatz with fixed structure. However, if the ansatz is too simple or shallow, the expressivity of the circuit will not be insufficient to get the optimal value for the objective function. On the other side, if the ansatz is overly complicated or long, we may encounter barren plateau effect, which impedes us from obtaining the global minimum value. Thus, a circuit structure design search algorithm will be helpful to find the appropriate circuit for a specific task.\n",
...@@ -36,7 +32,6 @@ ...@@ -36,7 +32,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "365bdd44",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## Pipeline" "## Pipeline"
...@@ -44,25 +39,23 @@ ...@@ -44,25 +39,23 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "89b09ff3",
"metadata": {}, "metadata": {},
"source": [ "source": [
"VAns consists of the following steps: \n", "VAns consists of the following steps: \n",
"1. Start with an initial circuit. Run the inner optimization, which is just the original VQE process, and get the optimal value. \n", "1. Start with an initial circuit. Run the inner optimization, which is just the original VQE process, and get the optimal value. \n",
"2. Randomly choose a block from the 'pool' (As the following figure shows, each block only consists of $R_y$, $R_z$, and $CNOT$ gates), and insert the block at the end of circuit. Qubits that the block is applied on are also uniformly sampled. The parameters of the inserted gates are initialized to $0$ so that the inserted circuit is equivalent to identity. \n", "2. Randomly choose a block from the 'pool' (As the following figure shows, each block only consists of $R_y$, $R_z$, and $CNOT$ gates), and insert the block at the end of circuit. Qubits that the block is applied on are also uniformly sampled. The parameters of the inserted gates are initialized to $0$ so that the inserted circuit is equivalent to identity. \n",
"\n", "\n",
"![Inserting Blocks](./figures/vans-fig-blocks.png)\n", "![Inserting Blocks](blocks.png)\n",
"\n", "\n",
"3. Simplify the circuit according to the following rules. Run the optimization process, and get the optimal value of loss function. Compare the loss value to the previously stored one, decide whether to accept the new circuit or not according to a pre-defined threshold. If the circuit is accepted, remove gates that do not lower the loss, set the circuit as the current circuit and store the corresponding loss value.\n", "3. Simplify the circuit according to the following rules. Run the optimization process, and get the optimal value of loss function. Compare the loss value to the previously stored one, decide whether to accept the new circuit or not according to a pre-defined threshold. If the circuit is accepted, remove gates that do not lower the loss, set the circuit as the current circuit and store the corresponding loss value.\n",
"\n", "\n",
"![Simplification rules](./figures/vans-fig-rules.png)\n", "![Simplification rules](rules.png)\n",
"\n", "\n",
"4. Repeat steps 2-3 for chosen number of iterations, output the resulting circuit and the optimal loss value.\n" "4. Repeat steps 2-3 for chosen number of iterations, output the resulting circuit and the optimal loss value.\n"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "6f4130bd",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## Paddle Quantum Implementation" "## Paddle Quantum Implementation"
...@@ -70,7 +63,6 @@ ...@@ -70,7 +63,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "1acdd35e",
"metadata": {}, "metadata": {},
"source": [ "source": [
"We consider to get the ground state energy of Hydrogen molecule using [Variation Quantum Eigensolver (VQE)](https://qml.baidu.com/tutorials/quantum-simulation/variational-quantum-eigensolver.html) together with VAns to optimize the circuit structure as well. First, we import the required packages." "We consider to get the ground state energy of Hydrogen molecule using [Variation Quantum Eigensolver (VQE)](https://qml.baidu.com/tutorials/quantum-simulation/variational-quantum-eigensolver.html) together with VAns to optimize the circuit structure as well. First, we import the required packages."
...@@ -78,21 +70,9 @@ ...@@ -78,21 +70,9 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 1, "execution_count": null,
"id": "722c14f5",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [],
{
"data": {
"text/plain": [
"<paddle.fluid.core_noavx.Generator at 0x7fec78a74db0>"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [ "source": [
"import paddle\n", "import paddle\n",
"import paddle_quantum\n", "import paddle_quantum\n",
...@@ -101,16 +81,13 @@ ...@@ -101,16 +81,13 @@
"from paddle_quantum.ansatz import Circuit\n", "from paddle_quantum.ansatz import Circuit\n",
"from paddle_quantum.ansatz.vans import Inserter, Simplifier, VAns, cir_decompose\n", "from paddle_quantum.ansatz.vans import Inserter, Simplifier, VAns, cir_decompose\n",
"from paddle_quantum.hamiltonian import Hamiltonian\n", "from paddle_quantum.hamiltonian import Hamiltonian\n",
"import warnings\n",
"\n", "\n",
"warnings.filterwarnings(\"ignore\")\n",
"np.random.seed(11)\n", "np.random.seed(11)\n",
"paddle.seed(11)" "paddle.seed(11)"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "09179a6b",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Before using the VAns algorithm, we need to get the Hamiltonian for Hydrogen. Details can be found in the VQE tutorial." "Before using the VAns algorithm, we need to get the Hamiltonian for Hydrogen. Details can be found in the VQE tutorial."
...@@ -119,7 +96,6 @@ ...@@ -119,7 +96,6 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"id": "c3f64aec",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
...@@ -147,7 +123,6 @@ ...@@ -147,7 +123,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "8e90a098",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Then we define the loss function, which is simply the expectation value of the Hamiltonian." "Then we define the loss function, which is simply the expectation value of the Hamiltonian."
...@@ -156,7 +131,6 @@ ...@@ -156,7 +131,6 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 3, "execution_count": 3,
"id": "930b71a4",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
...@@ -167,7 +141,6 @@ ...@@ -167,7 +141,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "40d33794",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Next, we also need to set some training hyper-parameters before training." "Next, we also need to set some training hyper-parameters before training."
...@@ -176,7 +149,6 @@ ...@@ -176,7 +149,6 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 4, "execution_count": 4,
"id": "9c5bfe04",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
...@@ -195,7 +167,6 @@ ...@@ -195,7 +167,6 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 5, "execution_count": 5,
"id": "6981e732",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
...@@ -213,7 +184,6 @@ ...@@ -213,7 +184,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "4349bb43",
"metadata": {}, "metadata": {},
"source": [ "source": [
"### Initial Circuit" "### Initial Circuit"
...@@ -221,7 +191,6 @@ ...@@ -221,7 +191,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "0d4f3052",
"metadata": {}, "metadata": {},
"source": [ "source": [
"We give a default initial circuit with uniformly sampled parameters. Then run the optimization process to get the optimal parameters in this initial circuit. The optimization process is the same as in the original VQE. Hyperparameters **iter** and **LR** are used in this process.The obtained loss function and the circuit with optimized parameters will be the initial point for the architecture optimization process. Note that the circuit is simplified when we decided to use this optimized circuit as the current starting point. Details of simplification can be found later." "We give a default initial circuit with uniformly sampled parameters. Then run the optimization process to get the optimal parameters in this initial circuit. The optimization process is the same as in the original VQE. Hyperparameters **iter** and **LR** are used in this process.The obtained loss function and the circuit with optimized parameters will be the initial point for the architecture optimization process. Note that the circuit is simplified when we decided to use this optimized circuit as the current starting point. Details of simplification can be found later."
...@@ -230,21 +199,32 @@ ...@@ -230,21 +199,32 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 6, "execution_count": 6,
"id": "f76b20bc",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"c:\\Users\\v_zhanglei48\\Anaconda3\\envs\\pq_new\\lib\\site-packages\\paddle\\fluid\\framework.py:744: DeprecationWarning: `np.bool` is a deprecated alias for the builtin `bool`. To silence this warning, use `bool` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.bool_` here.\n",
"Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations\n",
" elif dtype == np.bool:\n",
"c:\\Users\\v_zhanglei48\\Anaconda3\\envs\\pq_new\\lib\\site-packages\\paddle\\tensor\\creation.py:130: DeprecationWarning: `np.object` is a deprecated alias for the builtin `object`. To silence this warning, use `object` by itself. Doing this will not modify any behavior and is safe. \n",
"Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations\n",
" if data.dtype == np.object:\n"
]
},
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"iter: 20 loss: [-0.99608546]\n", "iter: 20 loss: [-0.9960856]\n",
"iter: 40 loss: [-1.0926807]\n", "iter: 40 loss: [-1.0926806]\n",
"iter: 60 loss: [-1.1136395]\n", "iter: 60 loss: [-1.11364]\n",
"iter: 80 loss: [-1.1163319]\n", "iter: 80 loss: [-1.1163325]\n",
"iter: 100 loss: [-1.1167175]\n", "iter: 100 loss: [-1.1167175]\n",
"iter: 120 loss: [-1.1167547]\n", "iter: 120 loss: [-1.1167548]\n",
"Current circuit is:\n", "Current circuit is:\n",
"--Rx(3.144)----Rz(3.512)----*----Rx(6.286)----Rz(2.441)-------------------------------------------------------------x--\n", "--Rx(3.144)----Rz(3.512)----*----Rx(6.286)----Rz(3.039)-------------------------------------------------------------x--\n",
" | | \n", " | | \n",
"----------------------------x----Rx(4.249)----Rz(3.141)----Rx(4.246)----Rz(5.477)----*------------------------------|--\n", "----------------------------x----Rx(4.249)----Rz(3.141)----Rx(4.246)----Rz(5.477)----*------------------------------|--\n",
" | | \n", " | | \n",
...@@ -268,7 +248,6 @@ ...@@ -268,7 +248,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "c95a7574",
"metadata": {}, "metadata": {},
"source": [ "source": [
"### Insertion" "### Insertion"
...@@ -276,16 +255,14 @@ ...@@ -276,16 +255,14 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "22f6d70a",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Now, we randomly choose a block from the 'pool', and insert the block at the end of circuit. Qubits that the block is applied on are also uniformly sampled. The parameters of the inserted set of gates are initialized to $0$ so that the new circuit can take the advantage of the previously optimized circuit since they act as identity. The code below inserts sets of gates into the circuit. The number of sets of gates depends on the hyperparameter **insert_rate**. Another hyperparameter **epsilon** is used to determine the variance from $0$ of initial parameters." "Now, we randomly choose a block from the 'pool', and insert the block within the circuit. Qubits that the block is applied on are also uniformly sampled. The parameters of the inserted set of gates are initialized to $0$ so that the new circuit can take the advantage of the previously optimized circuit since they act as identity. The code below inserts sets of gates into the circuit. The number of sets of gates depends on the hyperparameter **insert_rate**. Another hyperparameter **epsilon** is used to determine the variance from $0$ of initial parameters."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 7, "execution_count": 7,
"id": "8dcdf789",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
...@@ -293,9 +270,9 @@ ...@@ -293,9 +270,9 @@
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"Circuit after insertion:\n", "Circuit after insertion:\n",
"--Rx(3.144)----Rz(3.512)----*----Rx(6.286)----Rz(2.441)----------------------------------------------------------------------------------------------------x--\n", "--Rx(3.144)----Rz(3.512)----*----Rx(6.286)----Rz(3.039)----------------------------------------------------------------------------------------------------x--\n",
" | | \n", " | | \n",
"----------------------------x----Rz(0.000)----Rx(0.000)----Rx(-0.00)----Rx(4.249)----Rz(3.141)----Rx(4.246)----Rz(5.477)----*------------------------------|--\n", "----------------------------x----Rz(0.000)----Rx(0.000)----Rz(-0.00)----Rx(4.249)----Rz(3.141)----Rx(4.246)----Rz(5.477)----*------------------------------|--\n",
" | | \n", " | | \n",
"--Rx(0.001)----Rz(5.499)----*-----------------------------------------------------------------------------------------------x----Rx(3.141)----Rz(2.688)----|--\n", "--Rx(0.001)----Rz(5.499)----*-----------------------------------------------------------------------------------------------x----Rx(3.141)----Rz(2.688)----|--\n",
" | | \n", " | | \n",
...@@ -316,7 +293,6 @@ ...@@ -316,7 +293,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "cd07eeef",
"metadata": {}, "metadata": {},
"source": [ "source": [
"### Simplification" "### Simplification"
...@@ -324,7 +300,6 @@ ...@@ -324,7 +300,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "54a3fdd0",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Then we will simplify the new circuit. Rules are pretty simple:\n", "Then we will simplify the new circuit. Rules are pretty simple:\n",
...@@ -339,27 +314,34 @@ ...@@ -339,27 +314,34 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 8, "execution_count": 8,
"id": "d41b36e1",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"c:\\Users\\v_zhanglei48\\Anaconda3\\envs\\pq_new\\lib\\site-packages\\paddle\\fluid\\dygraph\\math_op_patch.py:251: UserWarning: The dtype of left and right variables are not the same, left dtype is paddle.float64, but right dtype is paddle.float32, the right dtype will convert to paddle.float64\n",
" warnings.warn(\n"
]
},
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"--Rx(3.144)----Rz(3.512)----*----Rx(6.286)----Rz(2.441)------------------------------------------------x--\n", "--Rx(3.144)----Rz(3.512)----*----Rx(6.286)----Rz(3.039)------------------------------------------------x--\n",
" | | \n", " | | \n",
"----------------------------x----Rz(4.491)----Rx(4.470)----Rz(8.712)----*------------------------------|--\n", "----------------------------x----Rz(5.962)----Rx(5.858)----Rz(9.426)----*------------------------------|--\n",
" | | \n", " | | \n",
"--Rx(0.001)----Rz(5.499)----*-------------------------------------------x----Rx(3.141)----Rz(2.688)----|--\n", "--Rx(0.001)----Rz(5.499)----*-------------------------------------------x----Rx(3.141)----Rz(2.688)----|--\n",
" | | \n", " | | \n",
"----------------------------x----Rz(3.604)----Rx(5.126)----Rz(4.462)-----------------------------------*--\n", "----------------------------x----Rz(3.604)----Rx(5.126)----Rz(4.462)-----------------------------------*--\n",
" \n", " \n",
"iter: 20 loss: [-1.1121466]\n", "iter: 20 loss: [-1.1040964]\n",
"iter: 40 loss: [-1.1123195]\n", "iter: 40 loss: [-1.1159219]\n",
"iter: 60 loss: [-1.1158162]\n", "iter: 60 loss: [-1.1165943]\n",
"iter: 80 loss: [-1.1166465]\n", "iter: 80 loss: [-1.116723]\n",
"iter: 100 loss: [-1.1167465]\n", "iter: 100 loss: [-1.1167536]\n",
"iter: 120 loss: [-1.1167578]\n", "iter: 120 loss: [-1.1167593]\n",
"Accpet the new circuit!\n", "Accpet the new circuit!\n",
" start deleting gates\n", " start deleting gates\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
...@@ -410,7 +392,6 @@ ...@@ -410,7 +392,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "bb98738a",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Then we update the current circuit to the new circuit." "Then we update the current circuit to the new circuit."
...@@ -419,7 +400,6 @@ ...@@ -419,7 +400,6 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 9, "execution_count": 9,
"id": "27a16d15",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
...@@ -427,7 +407,7 @@ ...@@ -427,7 +407,7 @@
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"The current circuit:\n", "The current circuit:\n",
"--Rx(3.142)----*----------------------x--\n", "--Rx(3.141)----*----------------------x--\n",
" | | \n", " | | \n",
"---------------x----*-----------------|--\n", "---------------x----*-----------------|--\n",
" | | \n", " | | \n",
...@@ -447,7 +427,6 @@ ...@@ -447,7 +427,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "1a8c71b9",
"metadata": {}, "metadata": {},
"source": [ "source": [
"The insertion and simplification together forms one iteration for the architecture optimization process. The number of iterations is determined by the hyperparameter **iter_out**. " "The insertion and simplification together forms one iteration for the architecture optimization process. The number of iterations is determined by the hyperparameter **iter_out**. "
...@@ -455,7 +434,6 @@ ...@@ -455,7 +434,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "d53c077c",
"metadata": {}, "metadata": {},
"source": [ "source": [
"### Simple version" "### Simple version"
...@@ -463,7 +441,6 @@ ...@@ -463,7 +441,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "e8383ce1",
"metadata": {}, "metadata": {},
"source": [ "source": [
"While you can customize your own optimization process by adjusting insertion and simplification processes and implementing the training process, Paddle Quantum provides an elegant version of VAns. You can complete the architecture optimization all at once." "While you can customize your own optimization process by adjusting insertion and simplification processes and implementing the training process, Paddle Quantum provides an elegant version of VAns. You can complete the architecture optimization all at once."
...@@ -471,8 +448,7 @@ ...@@ -471,8 +448,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 54, "execution_count": 10,
"id": "c47505f7",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
...@@ -480,30 +456,30 @@ ...@@ -480,30 +456,30 @@
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"Out iteration 1 for structure optimization:\n", "Out iteration 1 for structure optimization:\n",
"iter: 20 loss: [-1.1167217]\n", "iter: 20 loss: [-1.1167214]\n",
"iter: 40 loss: [-1.1166946]\n", "iter: 40 loss: [-1.1166948]\n",
"iter: 60 loss: [-1.1167494]\n", "iter: 60 loss: [-1.1167494]\n",
"iter: 80 loss: [-1.116759]\n", "iter: 80 loss: [-1.116759]\n",
"iter: 100 loss: [-1.1167591]\n", "iter: 100 loss: [-1.1167591]\n",
"iter: 120 loss: [-1.1167595]\n", "iter: 120 loss: [-1.1167595]\n",
" Current loss: [-1.1167595]\n", " Current loss: [-1.1167595]\n",
" Current cir:\n", " Current cir:\n",
"--Rx(3.142)----*----------------------x--\n", "--Rx(3.141)----*----------------------x--\n",
" | | \n", " | | \n",
"---------------x----*-----------------|--\n", "---------------x----*-----------------|--\n",
" | | \n", " | | \n",
"--------------------x----Rx(3.142)----|--\n", "--------------------x----Rx(3.141)----|--\n",
" | \n", " | \n",
"--------------------------------------*--\n", "--------------------------------------*--\n",
" \n", " \n",
"\n", "\n",
"Out iteration 2 for structure optimization:\n", "Out iteration 2 for structure optimization:\n",
"iter: 20 loss: [-1.099879]\n", "iter: 20 loss: [-1.008932]\n",
"iter: 40 loss: [-1.1144477]\n", "iter: 40 loss: [-1.1085335]\n",
"iter: 60 loss: [-1.1166729]\n", "iter: 60 loss: [-1.133744]\n",
"iter: 80 loss: [-1.1167666]\n", "iter: 80 loss: [-1.1368372]\n",
"iter: 100 loss: [-1.1207733]\n", "iter: 100 loss: [-1.1372123]\n",
"iter: 120 loss: [-1.1371529]\n", "iter: 120 loss: [-1.1372803]\n",
" accpet the new circuit!\n", " accpet the new circuit!\n",
" start deleting gates\n", " start deleting gates\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
...@@ -511,27 +487,28 @@ ...@@ -511,27 +487,28 @@
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
" Deletion: reject deletion\n",
" Deletion: accept deletion with acceptable loss\n", " Deletion: accept deletion with acceptable loss\n",
" 2 gates are deleted!\n", " Deletion: accept deletion with acceptable loss\n",
" Current loss: -1.136983871459961\n", " Deletion: reject deletion\n",
" 3 gates are deleted!\n",
" Current loss: -1.1331816911697388\n",
" Current cir:\n", " Current cir:\n",
"--Rx(3.142)----*------------------------------------------------------------------x--\n", "--Rx(3.143)----*------------------------------------------------------------------x--\n",
" | | \n", " | | \n",
"---------------x----*----Rx(-0.24)----Rz(1.462)----*--------*---------------------|--\n", "---------------x----*----Rx(0.223)----Rz(2.485)----*--------*---------------------|--\n",
" | | | | \n", " | | | | \n",
"--------------------|------------------------------|--------x--------Rx(3.142)----|--\n", "--------------------|------------------------------|--------x--------Rx(3.142)----|--\n",
" | | | \n", " | | | \n",
"--------------------x------------------------------x----Rz(3.103)-----------------*--\n", "--------------------x------------------------------x----Rz(0.458)-----------------*--\n",
" \n", " \n",
"\n", "\n",
"Out iteration 3 for structure optimization:\n", "Out iteration 3 for structure optimization:\n",
"iter: 20 loss: [-1.1085321]\n", "iter: 20 loss: [-0.37809896]\n",
"iter: 40 loss: [-1.1331517]\n", "iter: 40 loss: [-1.0471339]\n",
"iter: 60 loss: [-1.1366321]\n", "iter: 60 loss: [-1.1262195]\n",
"iter: 80 loss: [-1.1372447]\n", "iter: 80 loss: [-1.1365637]\n",
"iter: 100 loss: [-1.1372826]\n", "iter: 100 loss: [-1.1371676]\n",
"iter: 120 loss: [-1.1372824]\n", "iter: 120 loss: [-1.1372685]\n",
" accpet the new circuit!\n", " accpet the new circuit!\n",
" start deleting gates\n", " start deleting gates\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
...@@ -541,86 +518,92 @@ ...@@ -541,86 +518,92 @@
" Deletion: accept deletion with acceptable loss\n", " Deletion: accept deletion with acceptable loss\n",
" Deletion: accept deletion with acceptable loss\n", " Deletion: accept deletion with acceptable loss\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
" Deletion: reject deletion\n", " Deletion: accept deletion with acceptable loss\n",
" 3 gates are deleted!\n", " 4 gates are deleted!\n",
" Current loss: -1.1372839212417603\n", " Current loss: -1.1282802820205688\n",
" Current cir:\n", " Current cir:\n",
"--Rx(3.142)----*------------------------------------------------------------------x--\n", "--Rx(3.143)----*----------------------------------------------------------x--\n",
" | | \n", " | | \n",
"---------------x----*----Rx(-0.22)----Rz(1.497)----*--------*---------------------|--\n", "---------------x----*----Rx(0.224)----Rz(2.252)----*----*-----------------|--\n",
" | | | | \n", " | | | | \n",
"--------------------|------------------------------|--------x--------Rx(3.141)----|--\n", "--------------------|------------------------------|----x----Rx(3.146)----|--\n",
" | | | \n", " | | | \n",
"--------------------x------------------------------x----Rz(3.068)-----------------*--\n", "--------------------x------------------------------x----------------------*--\n",
" \n", " \n",
"\n", "\n",
"Out iteration 4 for structure optimization:\n", "Out iteration 4 for structure optimization:\n",
"iter: 20 loss: [-1.137021]\n", "iter: 20 loss: [-0.5603936]\n",
"iter: 40 loss: [-1.1371994]\n", "iter: 40 loss: [-1.0798837]\n",
"iter: 60 loss: [-1.1372718]\n", "iter: 60 loss: [-1.1325867]\n",
"iter: 80 loss: [-1.137283]\n", "iter: 80 loss: [-1.1360469]\n",
"iter: 100 loss: [-1.1372836]\n", "iter: 100 loss: [-1.1370715]\n",
"iter: 120 loss: [-1.1372839]\n", "iter: 120 loss: [-1.1372685]\n",
" accpet the new circuit!\n", " accpet the new circuit!\n",
" start deleting gates\n", " start deleting gates\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
" Deletion: accept deletion with acceptable loss\n", " Deletion: accept deletion with acceptable loss\n",
" Deletion: accept deletion with acceptable loss\n",
" Deletion: accept deletion with acceptable loss\n",
" Deletion: accept deletion with acceptable loss\n",
" Deletion: accept deletion with acceptable loss\n",
" Deletion: accept deletion with acceptable loss\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
" Deletion: reject deletion\n", " Deletion: accept deletion with acceptable loss\n",
" 1 gates are deleted!\n", " Deletion: accept deletion with acceptable loss\n",
" Current loss: -1.1372839212417603\n", " Deletion: accept deletion with acceptable loss\n",
" 9 gates are deleted!\n",
" Current loss: -1.1321836709976196\n",
" Current cir:\n", " Current cir:\n",
"--Rx(3.141)----*------------------------------------------------------------------x--\n", "--Rx(3.145)----*----------------------------------------------------------x--\n",
" | | \n", " | | \n",
"---------------x----*----Rx(-0.22)----Rz(1.497)----*--------*---------------------|--\n", "---------------x----*----Rx(0.228)----Rz(2.075)----*----*-----------------|--\n",
" | | | | \n", " | | | | \n",
"--------------------|------------------------------|--------x--------Rx(3.141)----|--\n", "--------------------|------------------------------|----x----Rx(3.143)----|--\n",
" | | | \n", " | | | \n",
"--------------------x------------------------------x----Rz(3.068)-----------------*--\n", "--------------------x------------------------------x----------------------*--\n",
" \n", " \n",
"\n", "\n",
"Out iteration 5 for structure optimization:\n", "Out iteration 5 for structure optimization:\n",
"iter: 20 loss: [-1.1370927]\n", "iter: 20 loss: [-1.136969]\n",
"iter: 40 loss: [-1.1371398]\n", "iter: 40 loss: [-1.1371931]\n",
"iter: 60 loss: [-1.1372617]\n", "iter: 60 loss: [-1.137272]\n",
"iter: 80 loss: [-1.1372828]\n", "iter: 80 loss: [-1.1372828]\n",
"iter: 100 loss: [-1.1372832]\n", "iter: 100 loss: [-1.1372837]\n",
"iter: 120 loss: [-1.137284]\n", "iter: 120 loss: [-1.1372838]\n",
" accpet the new circuit!\n", " accpet the new circuit!\n",
" start deleting gates\n", " start deleting gates\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
" Deletion: reject deletion\n", " Deletion: reject deletion\n",
" Deletion: reject deletion\n",
" Deletion: reject deletion\n",
" Deletion: accept deletion with acceptable loss\n", " Deletion: accept deletion with acceptable loss\n",
" Deletion: accept deletion with acceptable loss\n", " Deletion: accept deletion with acceptable loss\n",
" Deletion: reject deletion\n",
" 2 gates are deleted!\n", " 2 gates are deleted!\n",
" Current loss: -1.1372839212417603\n", " Current loss: -1.1372729539871216\n",
" Current cir:\n", " Current cir:\n",
"--Rx(3.141)----*------------------------------------------------------------------x--\n", "--Rx(3.142)----*----------------------------------------------------------x--\n",
" | | \n", " | | \n",
"---------------x----*----Rx(-0.22)----Rz(1.497)----*--------*---------------------|--\n", "---------------x----*----Rx(0.226)----Rz(1.571)----*----*-----------------|--\n",
" | | | | \n", " | | | | \n",
"--------------------|------------------------------|--------x--------Rx(3.141)----|--\n", "--------------------|------------------------------|----x----Rx(3.149)----|--\n",
" | | | \n", " | | | \n",
"--------------------x------------------------------x----Rz(3.068)-----------------*--\n", "--------------------x------------------------------x----------------------*--\n",
" \n", " \n",
"\n", "\n",
"\n", "\n",
"\n", "\n",
"The final loss: -1.1372839212417603\n", "The final loss: -1.1372729539871216\n",
"The final circuit:\n", "The final circuit:\n",
"--Rx(3.141)----*------------------------------------------------------------------x--\n", "--Rx(3.142)----*----------------------------------------------------------x--\n",
" | | \n", " | | \n",
"---------------x----*----Rx(-0.22)----Rz(1.497)----*--------*---------------------|--\n", "---------------x----*----Rx(0.226)----Rz(1.571)----*----*-----------------|--\n",
" | | | | \n", " | | | | \n",
"--------------------|------------------------------|--------x--------Rx(3.141)----|--\n", "--------------------|------------------------------|----x----Rx(3.149)----|--\n",
" | | | \n", " | | | \n",
"--------------------x------------------------------x----Rz(3.068)-----------------*--\n", "--------------------x------------------------------x----------------------*--\n",
" \n" " \n"
] ]
} }
], ],
...@@ -631,7 +614,6 @@ ...@@ -631,7 +614,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "5c1ec809",
"metadata": {}, "metadata": {},
"source": [ "source": [
"The final circuit we got from VAns is" "The final circuit we got from VAns is"
...@@ -639,8 +621,7 @@ ...@@ -639,8 +621,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 55, "execution_count": 11,
"id": "61d49bb5",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
...@@ -648,16 +629,16 @@ ...@@ -648,16 +629,16 @@
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"Final circuit:\n", "Final circuit:\n",
"--Rx(3.141)----*------------------------------------------------------------------x--\n", "--Rx(3.142)----*----------------------------------------------------------x--\n",
" | | \n", " | | \n",
"---------------x----*----Rx(-0.22)----Rz(1.497)----*--------*---------------------|--\n", "---------------x----*----Rx(0.226)----Rz(1.571)----*----*-----------------|--\n",
" | | | | \n", " | | | | \n",
"--------------------|------------------------------|--------x--------Rx(3.141)----|--\n", "--------------------|------------------------------|----x----Rx(3.149)----|--\n",
" | | | \n", " | | | \n",
"--------------------x------------------------------x----Rz(3.068)-----------------*--\n", "--------------------x------------------------------x----------------------*--\n",
" \n", " \n",
"Final loss:\n", "Final loss:\n",
"-1.1372839212417603\n" "-1.1372729539871216\n"
] ]
} }
], ],
...@@ -668,7 +649,6 @@ ...@@ -668,7 +649,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "b78222d3",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## Comparison with original VQE" "## Comparison with original VQE"
...@@ -676,7 +656,6 @@ ...@@ -676,7 +656,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "f2387269",
"metadata": {}, "metadata": {},
"source": [ "source": [
"The circuit we got using VAns consists of only 5 parameters and the depth of the circuit is 9. The minimized loss value is $-1.13728392$ Ha. Comparing to the fixed ansatz used in the original VQE tutorial, where the circuit consists of 12 parameters and with depth 11, VAns reduces the number of parameters needed while keeps the circuit shallower. " "The circuit we got using VAns consists of only 5 parameters and the depth of the circuit is 9. The minimized loss value is $-1.13728392$ Ha. Comparing to the fixed ansatz used in the original VQE tutorial, where the circuit consists of 12 parameters and with depth 11, VAns reduces the number of parameters needed while keeps the circuit shallower. "
...@@ -684,7 +663,6 @@ ...@@ -684,7 +663,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "c21e251e",
"metadata": {}, "metadata": {},
"source": [ "source": [
"_______\n", "_______\n",
...@@ -697,10 +675,10 @@ ...@@ -697,10 +675,10 @@
], ],
"metadata": { "metadata": {
"interpreter": { "interpreter": {
"hash": "9043b12ec77a531919bc05f05830335d23baf822720cbea14b03018197d26545" "hash": "2ab84abaf8d5bbc8765aba8eb82d11e7069f2ff20e8f79b8a9cdeccefd2ac4da"
}, },
"kernelspec": { "kernelspec": {
"display_name": "Python 3.8.0 ('paddle-quantum-dev')", "display_name": "Python 3.8.13 ('pq_new')",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
...@@ -714,7 +692,7 @@ ...@@ -714,7 +692,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.0" "version": "3.8.13"
} }
}, },
"nbformat": 4, "nbformat": 4,
......
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 量子信号处理与量子奇异值变换\n",
"\n",
"*Copyright (c) 2022 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 概览"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**量子信号处理**(quantum signal processing, QSP)是一个首次由 Guang Hao Low 和 Issac L. Chuang [[1]](https://quantum-journal.org/papers/q-2019-07-12-163/) 提出的量子算法,其使用反射($R_X$)和旋转($R_Z$)算子来模拟标量的多项式变换。对于满足特定条件的多项式(比如切比雪夫多项式),QSP 表明这些多项式变换可以通过单比特上的量子门模拟。使用名为“酉算子线性组合”(linear combination of unitaries, LCU)的技巧,我们只需增加两个额外辅助比特,就能模拟任意复多项式变换。简而言之,QSP可拟合任意能被泰勒级数逼近的函数。\n",
"\n",
"论文 [[2]](https://doi.org/10.1145/3313276.3316366)进一步发展了 QSP 的思想,并提出使用高维旋转算子的量子算法来模拟矩阵的多项式变换。由于矩阵的多项式变换本质上是它的奇异值的多项式变换,因此该算法被称为**量子奇异值变换**(quantum singular value transformation, QSVT)。另外,我们还介绍名为**单比特化**(qubitization)的技术,该技术可使用一个辅助比特就能模拟高维旋转算子,大幅减少了模拟高维度旋转算子的资源消耗,使得我们能够用量子电路实现矩阵的多项式变换。\n",
"\n",
"基于论文 [[2]](https://doi.org/10.1145/3313276.3316366),该教程会向大家介绍 QSP、QSVT 和单比特化的概念及其在量桨上的实现。\n",
"\n",
"接下来,我们先介绍块编码(block-encoding)的概念。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"以下是必要的 libraries 和 packages。"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"from numpy.polynomial.polynomial import Polynomial\n",
"from numpy.polynomial import Chebyshev\n",
"import paddle\n",
"\n",
"# 量桨的通用函数\n",
"import paddle_quantum\n",
"from paddle_quantum.ansatz import Circuit\n",
"from paddle_quantum.qinfo import dagger\n",
"from paddle_quantum.linalg import unitary_random_with_hermitian_block, density_matrix_random\n",
"\n",
"# 量桨的 QSVT 模块函数\n",
"from paddle_quantum.qsvt import poly_matrix, ScalarQSP, Phi_verification, reflection_based_quantum_signal_processing, QSVT"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 块编码"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"(量子)块编码是一种给矩阵数据编码的方式。在量子计算中,对于一个给定的矩阵 $A \\in \\mathbb{C}^{2^n \\times 2^n}$,我们能找到一个酉矩阵 $U \\in \\mathbb{C}^{2^m \\times 2^m}$ 满足如下等式\n",
"\n",
"$$\n",
"W U V = \n",
"\\begin{bmatrix}\n",
" A & 0 \\\\\n",
" 0 & 0\n",
"\\end{bmatrix}\n",
"= A \\oplus 0I^{\\otimes (m - n)}, \\tag{1}\n",
"$$\n",
"\n",
"其中,$W, V \\in \\mathbb{C}^{2^m \\times 2^m}$ 是两个正交投影算子,这样的酉矩阵 $U$ 被称为 $A$ 的**块编码**(block encoding)。\n",
"\n",
"一般地,投影算子通常是 $W = V = |0\\rangle^{\\otimes (m - n)}\\langle0|^{\\otimes (m - n)} \\otimes I^{\\otimes n}$,那么酉矩阵 $U$ 在标准正交基下可表示为\n",
"\n",
"$$\n",
"U = \\begin{bmatrix}\n",
" A & ... \\\\\n",
" ... & ...\n",
"\\end{bmatrix}, \\tag{2}\n",
"$$\n",
"\n",
"即 $A$ 位于 $U$ 的左上角。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 编码与解码"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"一般来说,块编码的表达形式并非是唯一的。比如,当 $A$ 可以对角化时,$U$ 可以被构建为\n",
"\n",
"$$\n",
"U = \\begin{bmatrix}\n",
" A & i\\sqrt{I^{\\otimes n} - A^2} \\\\\n",
" i\\sqrt{I^{\\otimes n} - A^2} & A\n",
"\\end{bmatrix}; \\tag{3}\n",
"$$\n",
"\n",
"当 $A$ 是酉矩阵时,我们则可以选择 $U = A \\oplus I^{\\otimes (m - n)}$,即 $U$ 是受控的 $A$。特别地,文献[[3]](http://arxiv.org/abs/2203.10236) [[4]](http://arxiv.org/abs/2206.03505)给出了一些使用量子电路实现块编码的方案。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 量桨实现"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"num_qubits = 3\n",
"num_block_qubits = 2\n",
"\n",
"# 创建一个 2 比特大小的厄密矩阵 A 的 3 比特大小的块编码 U\n",
"# create a 3-qubit block encoding unitary U of a random 2-qubit Hermitian matrix A\n",
"U = unitary_random_with_hermitian_block(num_qubits)\n",
"A = U[0: 2 ** num_block_qubits, 0: 2 ** num_block_qubits]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"另一方面,我们也可以从 矩阵 $A \\in \\mathbb{C}^{2^n \\times 2^n}$ 的块编码 $U$ 中解码出 $A$ 的信息。用$|0\\rangle^{\\otimes (m - n)}\\langle0|^{\\otimes (m - n)} \\otimes \\rho$表示输入量子态,块编码 $U$ 作为量子电路,那么输出量子态为\n",
"\n",
"$$\n",
"U (|0\\rangle^{\\otimes (m - n)}\\langle0|^{\\otimes (m - n)} \\otimes \\rho) U^\\dagger\n",
"= \\begin{bmatrix}\n",
" A \\rho A^\\dagger & ... \\\\\n",
" ... & ...\n",
"\\end{bmatrix}\n",
"= |0\\rangle^{\\otimes (m - n)}\\langle0|^{\\otimes (m - n)} \\otimes A \\rho A^\\dagger + (I^{\\otimes (m - n)} - |0\\rangle^{\\otimes (m - n)}\\langle0|^{\\otimes (m - n)}) \\otimes ... \\tag{4}\n",
"$$\n",
"\n",
"对 $|0\\rangle^{\\otimes (m - n)}$ 所在的寄存器作测量,若结果为 $0^{\\otimes (m - n)}$,那么我们成功地提取了 $A$ 的信息\n",
"<!-- 测量第一个量子寄存器。若结果为 $0^{\\otimes (m - n)}$,则信息提取成功。 -->\n",
"\n",
"![block-decoding](figures/QSVT-fig-block-decoding.png \"图 1:块解码的量子实现\")\n",
"\n",
"解码的成功概率取决于测量到 $0^{\\otimes (m - n)}$ 的几率,其会随着 $m - n$ 的增长而指数性下降。然而,若我们可以控制 $m - n$ 的大小,那么这就不再是一个问题。在本教程中我们会假设 $m = n + 1$."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 量桨实现"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"qubits [0] collapse to the state |0> with probability 0.08025970130578351\n"
]
}
],
"source": [
"# 设置密度矩阵后端\n",
"paddle_quantum.set_backend('density_matrix')\n",
"\n",
"# 定义输入态\n",
"rho = density_matrix_random(num_block_qubits)\n",
"zero_state = paddle.eye(2 ** (num_qubits - num_block_qubits), 1) # 创建 0 态的矢量\n",
"input_state = paddle_quantum.State(paddle.kron(zero_state @ dagger(zero_state), rho))\n",
"\n",
"# 定义辅助寄存器\n",
"aux_register = [i for i in range(num_qubits - num_block_qubits)]\n",
"\n",
"# 创建线路\n",
"cir = Circuit(num_qubits)\n",
"cir.oracle(U, [i for i in range(num_qubits)])\n",
"cir.collapse(aux_register, desired_result='0', if_print = True) # 调用塌缩算子\n",
"\n",
"# 获取输出态及输出 rho\n",
"output_state = cir(input_state)\n",
"output_rho = output_state.data[0: 2 ** num_block_qubits, 0: 2 ** num_block_qubits]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"我们可以验证 ``output_rho`` 与 $\\frac{A \\rho A^\\dagger}{\\text{Tr}(A \\rho A^\\dagger)}$ 相同。"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"期望 rho 和 输出 rho 的误差是 8.008999270740103e-08\n"
]
}
],
"source": [
"expect_rho = A @ rho @ dagger(A)\n",
"expect_rho /= paddle.trace(expect_rho)\n",
"print(f\"期望 rho 和 输出 rho 的误差是 {paddle.norm(paddle.abs(expect_rho - output_rho)).item()}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 特例 ($m = 1$)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"当 $m = 1$(因此 $n = 0$)时,$A$ 是一个标量。在这种情况下我们则可以算出 $U$ 对于量子态 $|0\\rangle$ 的期望值来获取 $A$。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 量子信号处理"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"论文 [[2]]((https://doi.org/10.1145/3313276.3316366)) 中的 Theorem 3-4 证明,若一个次数为 $k$ 的复数多项式 $P \\in \\mathbb{C}[x]$ 满足\n",
"\n",
"- 若 $k$ 为偶/奇数,则 $P$ 是一个偶/奇多项式;\n",
"- 对于所有的 $x \\in [-1, 1]$,$|P(x)| \\leq 1$;\n",
"- 对于所有的 $x \\in (-\\infty, -1] \\cup [1, \\infty)$,$|P(x)| \\geq 1$;\n",
"- 若 $k$ 为偶,则 $P(ix)P^*(ix) \\geq 1$,\n",
"\n",
"则存在多项式 $Q \\in \\mathbb{C}[x]$ 和角度矢量 $\\Phi = (\\phi_0, ..., \\phi_k) \\in \\mathbb{R}^{k + 1}$ 使得对于所有的 $x \\in [-1, 1]$,\n",
"\n",
"$$\n",
"W_\\Phi(x) := R_Z(-2\\phi_0) \\prod_{j = 1}^k R_X(\\arccos(x)) R_Z(-2\\phi_j)\n",
"= \\begin{bmatrix}\n",
" P(x) & i Q(x)\\sqrt{1 - x^2} \\\\\n",
" iQ^*(x)\\sqrt{1 - x^2} & P^*(x) \n",
"\\end{bmatrix}. \\tag{5}\n",
"$$\n",
"\n",
"换句话说,我们可以使用 $k + 1$ 个旋转($R_Z$)门和 $k$ 个反射 ($R_X$) 门去获得一个 $P(x)$ 的块编码酉矩阵 $W_\\Phi(x)$。块解码后 $P(x)$ 的模拟就完成了。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"以下是一些满足上述条件的例子。"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"# 简单的奇多项式\n",
"# P = Polynomial([0, 0, 0, 0, 0, 1])\n",
"# P = Polynomial([0, 0.5, 0, 0, 0, 0.5])\n",
"P = Polynomial([0, 1 / 3, 0, 0, 0, 1 / 3, 0, 1 / 3])\n",
"\n",
"# 次数为10的第一类切比雪夫多项式\n",
"# P = Chebyshev([0 for _ in range(10)] + [1]).convert(kind=Polynomial)\n",
"\n",
"# 次数为11的第一类切比雪夫多项式\n",
"# P = Chebyshev([0 for _ in range(11)] + [1]).convert(kind=Polynomial)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"值得注意的是,$W_\\Phi$ 可以模拟切比雪夫多项式变换。对于次数为 $k$ 的第一类切比雪夫多项式,其对应的角度矢量为 $(0, \\pi, ..., \\pi)$ (如 $k$ 为偶数)或 $(\\pi, ..., \\pi)$ (如 $k$ 为奇数)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 量桨实现"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"量桨的 QSVT 模块有一个内置类 `ScalarQSP` 用于完成量子信号处理,并可以调用函数 `Phi_verification` 来验证是否 $W_\\Phi$ 是 $P$ 的块编码。"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Tensor(shape=[8], dtype=float64, place=Place(cpu), stop_gradient=True,\n",
" [ 3.14159265, 1.39460474, -0.44313783, 1.09757975, -1.09757975,\n",
" 0.44313783, -1.39460474, 3.14159265])\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"c:\\users\\v_zhanglei48\\baidu\\personal-code\\pq\\paddle_quantum\\QSVT\\qsp.py:242: ComplexWarning: Casting complex values to real discards the imaginary part\n",
" Phi[i] = np.log(P.coef[n] / Q.coef[m]) * -1j / 2\n",
"c:\\users\\v_zhanglei48\\baidu\\personal-code\\pq\\paddle_quantum\\QSVT\\qsp.py:256: ComplexWarning: Casting complex values to real discards the imaginary part\n",
" Phi[0] = -1j * np.log(P.coef[0])\n"
]
}
],
"source": [
"qsp = ScalarQSP(P)\n",
"print(qsp.Phi)\n",
"assert Phi_verification(qsp.Phi.numpy(), P)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"我们也可以通过量子线路来验证 $\\Phi$ 的正确性。"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--Rz(-6.28)----Rx(-2.21)----Rz(2.789)----Rx(-2.21)----Rz(-0.88)----Rx(-2.21)----Rz(2.195)----Rx(-2.21)----Rz(-2.19)----Rx(-2.21)----Rz(0.886)----Rx(-2.21)----Rz(-2.78)----Rx(-2.21)----Rz(-6.28)--\n",
" \n",
"模拟 P(x) 的误差是 2.931881168672019e-08\n"
]
}
],
"source": [
"x = np.random.rand() * 2 - 1 # 随机在 [-1, 1] 间选取 x\n",
"cir = qsp.block_encoding(x)\n",
"print(cir)\n",
"print(f\"模拟 P(x) 的误差是 {np.abs(cir.unitary_matrix()[0, 0].item() - P(x))}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### QSP 变种"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"另外,参数数量可以被进一步减少至 $k$,通过找到一个角度矢量 $\\Phi \\in \\mathbb{R}^k$ 使得\n",
"\n",
"$$\n",
"R_\\Phi(x):=\\prod_{i = 1}^{k} e^{i \\phi_i \\sigma_z} R(x)\n",
"= \\begin{bmatrix}\n",
" P(x) & ... \\\\\n",
" ... & ...\n",
"\\end{bmatrix}, \\tag{6}\n",
"$$\n",
"\n",
"这里\n",
"\n",
"$$\n",
"R(x) = \\begin{bmatrix}\n",
" x & \\sqrt{1 - x^2} \\\\\n",
" \\sqrt{1 - x^2} & -x\n",
"\\end{bmatrix}. \\tag{7}\n",
"$$\n",
"\n",
"在量桨里我们通过调用 `reflection_based_quantum_signal_processing` 来计算 $\\Phi$。"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[15.70796327 -0.17619159 -2.01393416 -0.47321657 -2.66837608 -1.12765849\n",
" -2.96540107]\n"
]
}
],
"source": [
"print(reflection_based_quantum_signal_processing(P))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> 注: $R_{-\\Phi}(x)$ 是 $P^*(x)$ 的块编码。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 量子奇异值变换"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"接下来,我们介绍量子奇异值变换的概念。\n",
"\n",
"对于 $\\mathcal{X} \\in \\mathbb{C}^{2^m \\times 2^m}$ 为一个矩阵,当它可以被一个酉矩阵 $U$ 和两个正交投影算子 $ W, V$ 表示时,即 $\\mathcal{X} = W U V$,那么矩阵 $\\mathcal{X}$ 有如下表达式:\n",
"\n",
"$$\n",
"\\mathcal{X} = \\sum_{j=1}^{2^m} \\lambda_j |\\omega_j\\rangle \\langle\\nu_j|, \\tag{8}\n",
"$$\n",
"\n",
"其中 $\\{|\\omega_j\\rangle\\}_{j=1}^{2^m}$ 和 $\\{|\\nu_j\\rangle\\}_{j=1}^{2^m}$ 是由 $W$ 和 $V$ 的本征态扩展生成的标准正交基,且 $\\lambda_j$ 是 $\\mathcal{X}$ 的奇异值,\n",
"\n",
"$$\n",
"\\lambda_j = \\begin{cases}\n",
"\\langle\\omega_j| U |\\nu_j\\rangle \\text{ 如 } j \\leq \\text{rank}(\\mathcal{X}) \\\\\n",
"0 \\text{ 其他情况 }\n",
"\\end{cases} \\tag{9}\n",
"$$\n",
"\n",
"对于给定的周期性函数 $f$,矩阵$\\mathcal{X}$ 的**奇异值变换**(SVT) 定义为\n",
"\n",
"$$\n",
"f^{(SV)}(\\mathcal{X}) := \\sum_{j=1}^{2^m} f(\\lambda_j)\n",
"\\begin{cases}\n",
" |\\nu_j\\rangle\\langle\\nu_j| & \\text{ 如 } f \\text{ 是偶函数} \\\\\n",
" |\\omega_j\\rangle\\langle\\nu_j| & \\text{ 如 } f \\text{ 是奇函数}\n",
"\\end{cases}. \\tag{10}\n",
"$$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 分解与 QSP"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"论文 [[2]]((https://doi.org/10.1145/3313276.3316366)) 证明在**合适**的基下, 与 $\\lambda_j$ 相关的子空间可以将 $U, V$ 和 $W$ 分解为\n",
"\n",
"$$\n",
"\\begin{split}\n",
"U & = \\bigoplus_{j: \\lambda_j \\in [0, 1)} R(\\lambda_j) \\oplus\n",
" \\bigoplus_{j: \\lambda_j = 1} [1] \\oplus\n",
" \\bigoplus_{j: V|\\nu_j\\rangle = |\\nu_j\\rangle,\\, WU|\\nu_j\\rangle = 0} [1] \\oplus\n",
" \\bigoplus_{j: W|\\omega_j\\rangle = |\\omega_j\\rangle,\\, VU^\\dagger|\\omega_j\\rangle = 0} [1] \\oplus \n",
" \\bigoplus_{j: V|\\nu_j\\rangle = W|\\omega_j\\rangle = 0} [...], \\\\\n",
"V & = \\bigoplus_{j: \\lambda_j \\in [0, 1)} |0\\rangle\\langle0| \\oplus\n",
" \\bigoplus_{j: \\lambda_j = 1} [1] \\oplus\n",
" \\bigoplus_{j: V|\\nu_j\\rangle = |\\nu_j\\rangle,\\, WU|\\nu_j\\rangle = 0} [1] \\oplus\n",
" \\bigoplus_{j: W|\\omega_j\\rangle = |\\omega_j\\rangle,\\, VU^\\dagger|\\omega_j\\rangle = 0} [0] \\oplus \n",
" \\bigoplus_{j: V|\\nu_j\\rangle = W|\\omega_j\\rangle = 0} [0] \\text{ and}\\\\\n",
"W & = \\bigoplus_{j: \\lambda_j \\in [0, 1)} |0\\rangle\\langle0| \\oplus\n",
" \\bigoplus_{j: \\lambda_j = 1} [1] \\oplus\n",
" \\bigoplus_{j: V|\\nu_j\\rangle = |\\nu_j\\rangle,\\, WU|\\nu_j\\rangle = 0} [0] \\oplus\n",
" \\bigoplus_{j: W|\\omega_j\\rangle = |\\omega_j\\rangle,\\, VU^\\dagger|\\omega_j\\rangle = 0} [1] \\oplus \n",
" \\bigoplus_{j: V|\\nu_j\\rangle = W|\\omega_j\\rangle = 0} [0] .\\\\\n",
"\\end{split} \\tag{11}\n",
"$$\n",
"\n",
"$f^{(SV)}$ 也可以被分解为\n",
"\n",
"$$\n",
"f^{(SV)}(\\mathcal{X}) = \\sum_{j: \\lambda_j \\in [0, 1)} f(\\lambda_j)\\,... + \\sum_{j: \\lambda_j = 1} f(1)\\,... + \\sum_{j: \\lambda_j = 0, V |\\nu_j\\rangle \\neq 0} f(0)\\,... + \\sum_{j: \\lambda_j = 0, W |\\omega_j\\rangle \\neq 0} f(0)\\,... + \\sum_{j: \\lambda_j = 0, \\text{otherwise}} f(0)\\,...\\,, \\tag{12}\n",
"$$\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"现在我们将上述分解与量子信号处理联系到一起。\n",
"\n",
"假设函数 $P$ 是一个次数为 $k$ 并满足上节所述条件,则存在 $\\Phi \\in \\mathbb{R}^k$ 使得 $R_\\Phi$ 是 $P$ 的块编码,并且 $P$ 满足 $P(1) = e^{i \\sum_{j=1}^k \\phi_j}$ 和 $P(0) = e^{i \\sum_{j=1}^k (-1)^{j+1} \\phi_j}$。\n",
"\n",
"定义 $U_\\Phi$ 为\n",
"\n",
"$$\n",
"U_\\Phi := \\begin{cases}\n",
"& \\prod_{j = 1}^{k / 2} e^{i\\phi_{2j - 1} (2V - I)} U^\\dagger e^{i\\phi_{2j} (2W - I)} U \\text{ 如 } k \\text{ 是偶数} \\\\\n",
"e^{i\\phi_1 (2W - I)} U & \\prod_{j = 1}^{(k - 1) / 2} e^{i\\phi_{2j} (2V - I)} U^\\dagger e^{i\\phi_{2j+1} (2W - I)} U \\text{ 如 } k \\text{ 是奇数}\n",
"\\end{cases}. \\tag{13}\n",
"$$\n",
"\n",
"那么\n",
"\n",
"$$\n",
"U_\\Phi = \\begin{cases}\n",
" \\bigoplus R_\\Phi(\\lambda_j) \\oplus\n",
" \\bigoplus [e^{i \\sum_{j=1}^k \\phi_j}] \\oplus\n",
" \\bigoplus [e^{i \\sum_{j=1}^k (-1)^{j+1} \\phi_j}] \\oplus\n",
" \\bigoplus [e^{i \\sum_{j=1}^k (-1)^{j+1} \\phi_j}] \\oplus \n",
" \\bigoplus [...]\n",
"\\text{ 如 } k \\text{ 是偶数} \\\\\n",
" \\bigoplus R_\\Phi(\\lambda_j) \\oplus\n",
" \\bigoplus [e^{i \\sum_{j=1}^k \\phi_j}] \\oplus\n",
" \\bigoplus [e^{i \\sum_{j=1}^k (-1)^{j+1} \\phi_j}] \\oplus\n",
" \\bigoplus [e^{i \\sum_{j=1}^k (-1)^{j+1} \\phi_j}] \\oplus \n",
" \\bigoplus [...]\n",
"\\text{ 如 } k \\text{ 是奇数}\n",
"\\end{cases}, \\tag{14}\n",
"$$\n",
"\n",
"因此\n",
"\n",
"$$\n",
"P^{(SV)}(\\mathcal{X}) = \\begin{cases} \n",
" \\bigoplus \\begin{bmatrix}\n",
" P(\\lambda_j) & 0 \\\\\n",
" 0 & 0 \\\\\n",
" \\end{bmatrix} \\oplus\n",
" \\bigoplus [P(1)] \\oplus\n",
" \\bigoplus [P(0)] \\oplus\n",
" \\bigoplus [0] \\oplus \n",
" \\bigoplus [0] = V U_\\Phi V\n",
"\\text{ 如 } k \\text{ 是偶数} \\\\ \n",
" \\bigoplus \\begin{bmatrix}\n",
" P(\\lambda_j) & 0 \\\\\n",
" 0 & 0 \\\\\n",
" \\end{bmatrix} \\oplus\n",
" \\bigoplus [P(1)] \\oplus\n",
" \\bigoplus [0] \\oplus\n",
" \\bigoplus [0] \\oplus \n",
" \\bigoplus [0] = W U_\\Phi V \n",
"\\text{ 如 } k \\text{ 是奇数}\n",
"\\end{cases}. \\tag{15}\n",
"$$\n",
"\n",
"若是我们可以使用量子算法实现 $V U_\\Phi V$ 或者 $W U_\\Phi V$,那么这样的变换就是 $\\mathcal{X}$ 的量子奇异值变换。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### QSVT 和块编码"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"假设相对于正交投影算子 $W$ 和 $V$, $U$ 是一个 $A$ 的块编码酉矩阵。那么 $\\mathcal{X} = A \\oplus 0I^{\\otimes (m - n)}$ 且 $P^{(SV)}(\\mathcal{X}) = P^{(SV)}(A) \\oplus 0I^{\\otimes (m - n)}$. 因此,$U_\\Phi$ 是 $P^{(SV)}(A)$ 的块编码.\n",
"\n",
"当 $W = V$ 时,$\\{|\\omega_j\\rangle \\}_{j=1}^{2^m} = \\{ |\\nu_j\\rangle \\}_{j=1}^{2^m}$。记 $P(x) := \\sum_{i=0}^k c_i x^i$,我们就可以找到\n",
"\n",
"$$\n",
"P^{(SV)}(\\mathcal{X}) = \\sum_{j=1}^{2^m} P(\\lambda_j) |\\nu_j\\rangle \\langle\\nu_j| = P(X) = P(A) \\oplus 0I^{\\otimes (m - n)}. \\tag{16}\n",
"$$\n",
"\n",
"换句话说,当 $W = V$ 时,QSVT 将一个 $A$ 的块编码转为了一个 $P(A)$ 的块编码。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 量桨实现"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"当 $W = V = |0\\rangle^{\\otimes (m - n)}\\langle0|^{\\otimes (m - n)} \\otimes I^{\\otimes n}$ 时,量桨有一个内置类 `QSVT` 来完成量子奇异值变换。我们可以调用其成员函数 `QSVT.block_encoding_matrix()` 来验证上述理论的正确性。"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"qsvt = QSVT(P, U, num_block_qubits)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"c:\\Users\\v_zhanglei48\\Anaconda3\\envs\\personal-code\\lib\\site-packages\\paddle\\fluid\\dygraph\\math_op_patch.py:276: UserWarning: The dtype of left and right variables are not the same, left dtype is paddle.float64, but right dtype is paddle.float32, the right dtype will convert to paddle.float64\n",
" warnings.warn(\n"
]
}
],
"source": [
"# 找到 P(A) 及\n",
"# find P(A) and its expected eigenvalues, note that they are computed in different ways\n",
"expect_PA = poly_matrix(P, A).numpy()\n",
"expect_eig_result = np.sort(list(map(lambda x: P(x), np.linalg.eigvals(A.numpy()))))\n",
"\n",
"# Calculate U_\\Phi and extract eigenvalues of block encoded matrix\n",
"U_Phi = qsvt.block_encoding_matrix().numpy()\n",
"actual_PA = U_Phi[0:2 ** num_block_qubits, 0:2 ** num_block_qubits]\n",
"actual_eig_result = np.sort(np.linalg.eigvals(actual_PA))"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"模拟 P(X) 的误差\n",
" 最大绝对值, 8.429041888826793e-08\n",
" 百分比, 6.291585659021523e-07\n"
]
}
],
"source": [
"print(\"模拟 P(X) 的误差\")\n",
"print(\" 最大绝对值, \", np.amax(abs(expect_PA - actual_PA)))\n",
"print(\" 百分比, \", np.linalg.norm(expect_PA - actual_PA) / np.linalg.norm(expect_PA))"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"模拟 P(X) 的本征值误差\n",
" 最大绝对值, 1.0894675602300946e-07\n",
" 百分比, 5.209560480945378e-07\n"
]
}
],
"source": [
"print(\"模拟 P(X) 的本征值误差\")\n",
"print(\" 最大绝对值, \", np.amax(abs(expect_eig_result - actual_eig_result)))\n",
"print(\" 百分比, \", np.linalg.norm(expect_eig_result - actual_eig_result) / np.linalg.norm(expect_eig_result))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 单比特化: $U_\\Phi$ 的量子实现"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"现在的问题是,如何用量子线路去实现 $U_\\Phi$ ?通常我们认为块编码 $U$ 已经通过量子电路实现,那么剩下来只需实现 $e^{i\\phi (2W - I)}$ 和 $e^{i\\phi (2V - I)}$。值得注意的是,这两个算子本质上是分别在 $W$ 和 $V$ 的投影空间下施加的 $R_Z(-2\\phi)$ 算子。\n",
"\n",
"论文 [[1]](https://quantum-journal.org/papers/q-2019-07-12-163/) 中的 Lemma 10 认为,应该将投影算子的映射空间投射到一个辅助比特上再进行旋转,那么通过纠缠的性质这个旋转也会同时应用于主寄存器上。\n",
"\n",
"这些理论被总结至下图:\n",
"\n",
"![U_Phi](figures/QSVT-fig-U_Phi.png \"图 2: QSVT 的量子实现,这里 k 是 P 的多项式次数\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 量桨实现"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"当 $W = V = |0\\rangle^{\\otimes (m - n)}\\langle0|^{\\otimes (m - n)} \\otimes I^{\\otimes n}$ 时,量桨可以调用成员函数 `QSVT.block_encoding_circuit` 来创建一个图 2 所示的量子线路。我们可以通过比较该线路的输出态和量子态 $(U_\\Phi \\otimes I)|\\psi\\rangle|0\\rangle$($|\\psi\\rangle \\in \\mathbb{C}^{2^m}$)来证明所建立的线路是可以模拟 $U_\\Phi$ 的。"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"# 设置态矢量后端\n",
"paddle_quantum.set_backend('state_vector')\n",
"\n",
"# 随即量子态满足最后一个量子比特固定为 0 态\n",
"ket_0 = [1.0, 0.0]\n",
"psi = np.array([np.random.rand() + 1j * np.random.rand() for _ in range(2 ** num_qubits)])\n",
"psi = np.kron(psi / np.linalg.norm(psi), ket_0)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"期望量子态和实际量子态的差距是 2.253552555988302e-07\n"
]
}
],
"source": [
"expect_state = np.kron(U_Phi, np.eye(2)) @ psi\n",
"\n",
"cir = qsvt.block_encoding_circuit()\n",
"actual_state = cir(paddle_quantum.State(psi, dtype='complex64')).data.numpy()\n",
"\n",
"print(f\"期望量子态和实际量子态的差距是 {np.linalg.norm(expect_state - actual_state)}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> 注:如果我们在图 2 中的辅助寄存器两边加上 Hadamard 门,那么该量子线路就会转为模拟其多项式的实数变化。再结合 LCU 的技巧,理论上我们只需要额外 1 个量子比特来量子模拟任意范数小于 1 的复数多项式。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 应用: 振幅放大"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"设 $|\\psi\\rangle$ 为 $m$ 比特大小的量子态并满足 $|\\psi\\rangle = \\sin(\\theta)|\\psi_{\\text{good}}\\rangle + \\cos(\\theta) |\\psi_{\\text{bad}}\\rangle$。**振幅放大**是一个用于增大 $|\\psi_{\\text{good}}\\rangle$ 至 1 的量子算法。该算法可以从另一个角度来解释。设定 $U$ 为一个酉矩阵且 $W$ 和 $V$ 为两个正交投影算子,使得 $|\\psi\\rangle = U V |0\\rangle^{\\otimes m}$ 且 $\\sin(\\theta)|\\psi_{\\text{good}}\\rangle = W |\\psi\\rangle$,意味着 $\\sin(\\theta)|\\psi_{\\text{good}}\\rangle = W U V |0\\rangle^{\\otimes m}$。因此,振幅放大本质上是 $\\mathcal{X} = W U V$ 的奇异值变换。\n",
"在这一章节我们会根据论文 [[2]]((https://doi.org/10.1145/3313276.3316366)) 的 Theorem 28,来展示如何用 QSVT 来做定点(即 $\\theta$ 满足 $\\theta = \\frac{\\pi}{2k}$,$k \\in \\mathbb{Z}$)振幅放大算法。\n",
"\n",
"假设 $\\sin(\\theta)|\\psi_{\\text{good}}\\rangle = (|0\\rangle^{\\otimes (m - n)}\\langle0|^{\\otimes (m - n)} \\otimes I^{\\otimes n}) |\\psi\\rangle$ 且 $\\theta = \\frac{\\pi}{2k}$($k \\in \\mathbb{Z}$)。那么就能推导出 $W = V = |0\\rangle^{\\otimes (m - n)}\\langle0|^{\\otimes (m - n)} \\otimes I^{\\otimes n}$ 且 $\\mathcal{X} = A \\oplus 0I^{\\otimes (m - n)}$,这里 $A$ 位于 $U$ 的左上角。 "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"可见 $\\mathcal{X} |0\\rangle^{\\otimes m} = \\sin(\\frac{\\pi}{2k}) |\\psi_\\text{good}\\rangle$。因此,我们旨在找到一个酉矩阵为 $U_\\Phi$ 的量子线路使得 $W U_\\Phi V = \\sin^{-1}(\\theta) \\mathcal{X}$, 从而达到 $W U_\\Phi V |0\\rangle^{\\otimes m} = |\\psi_{\\text{good}}\\rangle$ 的目的。因为 $\\mathcal{X} = W U V$,$\\mathcal{X}$ 的奇异值的绝对值是 $\\sin(\\frac{\\pi}{2k})$。我们进一步选择 $P(x) = (-1)^k T_k (x)$,这里 $T_k$ 是次数为 $k$ 的第一类切比雪夫多项式。最后通过\n",
"\n",
"$$\n",
"P(\\sin(\\frac{\\pi}{2k})) = (-1)^k T_k (\\sin(\\frac{\\pi}{2k})) = (-1)^k \\cos(\\frac{k - 1}{2}\\pi) = 1, \\tag{17}\n",
"$$\n",
"\n",
"我们可得,对于多项式 $P$ 的 $\\mathcal{X}$ 的量子奇异值变换是 $B := \\frac{1}{\\sin(\\frac{\\pi}{2k})} A$ 的块编码。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 量桨实现"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"设定 $k = 3$ 使得 $\\sin(\\frac{\\pi}{2k}) = \\frac{1}{2}$,同时随机选择酉矩阵 $U$。其左上角为 $A$,我们需要证明 $U$ 的 QSVT 的左上角为 $B = 2A$。"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"P = -1 * Chebyshev([0 for _ in range(3)] + [1]).convert(kind=Polynomial)\n",
"U = unitary_random_with_hermitian_block(num_qubits, is_unitary=True)\n",
"A = U[0:2 ** num_block_qubits, 0:2 ** num_block_qubits].numpy()\n",
"\n",
"amplifier = QSVT(P, U, num_block_qubits)\n",
"U_Phi = amplifier.block_encoding_unitary()"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"QSVT 的精确值为 3.3605192584218457e-07\n"
]
}
],
"source": [
"B = U_Phi[0:2 ** num_block_qubits, 0:2 ** num_block_qubits].numpy()\n",
"print(f\"QSVT 的精确值为 {np.linalg.norm(B - 2 * A)}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"由上可见,我们成功将 $A$ 的大小翻倍。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"___\n",
"## 参考文献\n",
"\n",
"[1] Low, Guang Hao, and Isaac L. Chuang. \"Hamiltonian simulation by qubitization.\" [Quantum 3 (2019): 163.](https://doi.org/10.22331/q-2019-07-12-163)\n",
"\n",
"[2] Gilyén, András, et al. \"Quantum singular value transformation and beyond: exponential improvements for quantum matrix arithmetics.\" [Proceedings of the 51st Annual ACM SIGACT Symposium on Theory of Computing. 2019.](https://doi.org/10.1145/3313276.3316366)\n",
"\n",
"[3] Camps, Daan, et al. \"Explicit Quantum Circuits for Block Encodings of Certain Sparse Matrices.\" [arXiv preprint arXiv:2203.10236 (2022).](http://arxiv.org/abs/2203.10236)\n",
"\n",
"[4] Clader, B. David, et al. \"Quantum Resources Required to Block-Encode a Matrix of Classical Data.\" [arXiv preprint arXiv:2206.03505 (2022).](http://arxiv.org/abs/2206.03505)\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.8.13 ('pq')",
"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.13"
},
"orig_nbformat": 4,
"vscode": {
"interpreter": {
"hash": "1e82098cfee7be27b5e385e3f85fe91d734d6114f7d09dccafdaad2c23171c3e"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Quantum Signal Processing and Quantum Singular Value Transformation\n",
"*Copyright (c) 2022 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Overview"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Quantum signal processing** (QSP), originally purposed by Guang Hao Low and Issac L. Chuang [[1]](https://quantum-journal.org/papers/q-2019-07-12-163/), is an algorithm for simulation of polynomial transformation of scalars using reflection ($R_X$) and rotation ($R_Z$) operators. QSP states that, for a specific group of polynomials like the Chebyshev polynomials of the first kind, such simulation can be done by a single qubit. Moreover, using a technique called \"linear combination of unitaries\", we can use two more ancilla qubit to simulate all complex polynomials and hence approximate any functions that can be expanded by Taylor series.\n",
"\n",
"The idea of QSP is further generalized by paper [[2]](https://doi.org/10.1145/3313276.3316366), so that it can simulate polynomial transformation of matrices using high-dimensional rotation operators. Since the polynomial transformation of a diagonalizable matrix is intrinsically the polynomial transformation of its singular values, this algorithm is called the **quantum singular value transformation** (QSVT).\n",
"\n",
"Moreover, a technique called **qubitization** is further employed to substantially reduce the complexity of high-dimensional rotation operators, so that one (ancilla) qubit suffices to perform the rotation in larger space.\n",
"\n",
"Based on paper [[2]](https://doi.org/10.1145/3313276.3316366), this tutorial will illustrate quantum signal processing, quantum singular value transformation and qubitization, and how to implement these algorithms in PaddleQuantum. But first of all, we need to present the idea of block encoding."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here are some necessary libraries and packages."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"from numpy.polynomial.polynomial import Polynomial\n",
"from numpy.polynomial import Chebyshev\n",
"import paddle\n",
"\n",
"# PaddleQuantum related packages\n",
"import paddle_quantum\n",
"from paddle_quantum.ansatz import Circuit\n",
"from paddle_quantum.qinfo import dagger\n",
"from paddle_quantum.linalg import unitary_random_with_hermitian_block, density_matrix_random\n",
"\n",
"from paddle_quantum.qsvt import poly_matrix, ScalarQSP, Phi_verification, reflection_based_quantum_signal_processing, QSVT"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Block Encoding"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Block encoding is a method to encode matrices of data. In quantum computing, for a matrix $A \\in \\mathbb{C}^{2^n \\times 2^n}$, a unitary $U \\in \\mathbb{C}^{2^m \\times 2^m}$ is said to be a **block encoding** of $A$ if there exists two orthogonal projectors $W, V \\in \\mathbb{C}^{2^m \\times 2^m}$ such that\n",
"\n",
"$$\n",
"W U V = \n",
"\\begin{bmatrix}\n",
" A & 0 \\\\\n",
" 0 & 0\n",
"\\end{bmatrix}\n",
"= A \\oplus 0I^{\\otimes (m - n)}. \\tag{1}\n",
"$$\n",
"\n",
"This could appear more familiar when $W = V = |0\\rangle^{\\otimes (m - n)}\\langle0|^{\\otimes (m - n)} \\otimes I^{\\otimes n}$, where above equation implies\n",
"\n",
"$$\n",
"U = \\begin{bmatrix}\n",
" A & ... \\\\\n",
" ... & ...\n",
"\\end{bmatrix}. \\tag{2}\n",
"$$\n",
"\n",
"That is, $A$ is the left-upper block of matrix $U$."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Encoding and Decoding of Matrices"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"There are numerous ways to find a block encoding unitary $U$ of a matrix $A$. For example, when $A$ is diagonalizable, $U$ can be constructed as\n",
"\n",
"$$\n",
"U = \\begin{bmatrix}\n",
" A & i\\sqrt{I^{\\otimes n} - A^2} \\\\\n",
" i\\sqrt{I^{\\otimes n} - A^2} & A\n",
"\\end{bmatrix}; \\tag{3}\n",
"$$\n",
"\n",
"when $A$ is a unitary, we can simply select $U = A \\oplus I^{\\otimes (m - n)}$ i.e. $U$ is a controlled $A$. Despite the fact that we do not hold a mature method for doing this in a quantum system, several studies [[3]](http://arxiv.org/abs/2203.10236) [[4]](http://arxiv.org/abs/2206.03505) have been conducted on the quantum implementation of block encodings. In this tutorial, we will assume that there exists an efficient algorithm to block encode a matrix (or at least a Hermitian matrix) in a quantum circuit."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Realization in PaddleQuantum."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"num_qubits = 3\n",
"num_block_qubits = 2\n",
"\n",
"# create a 3-qubit block encoding unitary U of a random 2-qubit Hermitian matrix A\n",
"U = unitary_random_with_hermitian_block(num_qubits)\n",
"A = U[0: 2 ** num_block_qubits, 0: 2 ** num_block_qubits]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As for the decoding part, the information of matrix $A \\in \\mathbb{C}^{2^n \\times 2^n}$ can be extracted by applying its block encoding unitary $U \\in \\mathbb{C}^{2^m \\times 2^m}$ on $|0\\rangle^{\\otimes (m - n)}\\langle0|^{\\otimes (m - n)} \\otimes \\rho$, where $\\rho$ is an $n$-qubit quantum state. Then the output state is\n",
"\n",
"$$\n",
"U (|0\\rangle^{\\otimes (m - n)}\\langle0|^{\\otimes (m - n)} \\otimes \\rho) U^\\dagger\n",
"= \\begin{bmatrix}\n",
" A \\rho A^\\dagger & ... \\\\\n",
" ... & ...\n",
"\\end{bmatrix}\n",
"= |0\\rangle^{\\otimes (m - n)}\\langle0|^{\\otimes (m - n)} \\otimes A \\rho A^\\dagger + (I^{\\otimes (m - n)} - |0\\rangle^{\\otimes (m - n)}\\langle0|^{\\otimes (m - n)}) \\otimes ... \\tag{4}\n",
"$$\n",
"\n",
"Measuring the first quantum register with outcome $0^{\\otimes (m - n)}$ gives the desired information.\n",
"\n",
"![block-decoding](figures/QSVT-fig-block-decoding.png \"Figure 1: Graph Illustration of Block Decoding\")\n",
"\n",
"The success probability of decoding is the probability of measuring $0^{\\otimes (m - n)}$, which can be exponentially small as $m - n$ increases. However, this would no longer be a problem if we can control the size of first register. In this tutorial we will assume $m = n + 1$."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Realization in PaddleQuantum"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"qubits [0] collapse to the state |0> with probability 0.10231761176954456\n"
]
}
],
"source": [
"# set density matrix backend\n",
"paddle_quantum.set_backend('density_matrix')\n",
"\n",
"# define input state\n",
"rho = density_matrix_random(num_block_qubits)\n",
"zero_state = paddle.eye(2 ** (num_qubits - num_block_qubits), 1) # create a 0 state (in state_vector form)\n",
"input_state = paddle_quantum.State(paddle.kron(zero_state @ dagger(zero_state), rho))\n",
"\n",
"# define auxiliary register\n",
"aux_register = [i for i in range(num_qubits - num_block_qubits)]\n",
"\n",
"# construct the circuit\n",
"cir = Circuit(num_qubits)\n",
"cir.oracle(U, [i for i in range(num_qubits)])\n",
"cir.collapse(aux_register, desired_result='0', if_print = True) # call Collapse operator\n",
"\n",
"# get output_state and actual output rho\n",
"output_state = cir(input_state)\n",
"output_rho = output_state.data[0: 2 ** num_block_qubits, 0: 2 ** num_block_qubits]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can verify that the ``output_rho`` is the same as $\\frac{A \\rho A^\\dagger}{\\text{Tr}(A \\rho A^\\dagger)}$."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"the difference between input and output is 1.1947226861933412e-07\n"
]
}
],
"source": [
"expect_rho = A @ rho @ dagger(A)\n",
"expect_rho /= paddle.trace(expect_rho)\n",
"print(f\"the difference between input and output is {paddle.norm(paddle.abs(expect_rho - output_rho)).item()}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Special Case ($m = 1$)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"When $m = 1$ (and hence $n = 0$), $A$ is a scalar. In this case we can calculate the expectance of $U$ in terms of state $|0\\rangle$ to receive $A$."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Quantum Signal Processing"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Theorem 3-4 in paper [[2]]((https://doi.org/10.1145/3313276.3316366)) state that, for a polynomial $P \\in \\mathbb{C}[x]$ with $\\deg(P) = k$ that satisfies\n",
"\n",
"- if $k$ is even/odd, then $P$ is a even/odd polynomial;\n",
"- $|P(x)| \\leq 1, \\,\\forall x \\in [-1, 1]$;\n",
"- $|P(x)| \\geq 1, \\,\\forall x \\in (-\\infty, -1] \\cup [1, \\infty)$;\n",
"- if $k$ is even, then $P(ix)P^*(ix) \\geq 1$,\n",
"\n",
"there exists a polynomial $Q \\in \\mathbb{C}[x]$ and a vector of angles $\\Phi = (\\phi_0, ..., \\phi_k) \\in \\mathbb{R}^{k + 1}$ such that $\\forall x \\in [-1, 1]$,\n",
"\n",
"$$\n",
"W_\\Phi(x) := R_Z(-2\\phi_0) \\prod_{j = 1}^k R_X(\\arccos(x)) R_Z(-2\\phi_j)\n",
"= \\begin{bmatrix}\n",
" P(x) & i Q(x)\\sqrt{1 - x^2} \\\\\n",
" iQ^*(x)\\sqrt{1 - x^2} & P^*(x) \n",
"\\end{bmatrix}. \\tag{5}\n",
"$$\n",
"\n",
"That is, we can use $k + 1$ rotation ($R_Z$) gates and $k$ reflection ($R_X$) gates to receive a block encoding unitary $W_\\Phi(x)$ of $P(x)$. After block decoding we have completed the simulation of $P(x)$."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here are some polynomials that satisfy above requirements. "
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"# simple odd polynomials\n",
"# P = Polynomial([0, 0, 0, 0, 0, 1])\n",
"# P = Polynomial([0, 0.5, 0, 0, 0, 0.5])\n",
"P = Polynomial([0, 1 / 3, 0, 0, 0, 1 / 3, 0, 1 / 3])\n",
"\n",
"# Chebyshev polynomials of first kind with degree 10\n",
"# P = Chebyshev([0 for _ in range(10)] + [1]).convert(kind=Polynomial)\n",
"\n",
"# Chebyshev polynomials of first kind with degree 11\n",
"# P = Chebyshev([0 for _ in range(11)] + [1]).convert(kind=Polynomial)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that such structure of $W_\\Phi$ naturally fits the family of Chebyshev polynomials. Indeed, for a Chebyshev polynomials of first kind with order $k$, its corresponding $\\Phi$ is $(0, \\pi, ..., \\pi)$ (if $k$ is even) or $(\\pi, ..., \\pi)$ (if $k$ is odd). "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Realization in PaddleQuantum"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"PaddleQuantum has a built-in class `ScalarQSP` for quantum signal processing and a function `Phi_verification` to verify whether $W_\\Phi$ is a block encoding of $P$."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Tensor(shape=[8], dtype=float64, place=Place(cpu), stop_gradient=True,\n",
" [ 3.14159265, 1.39460474, -0.44313783, 1.09757975, -1.09757975,\n",
" 0.44313783, -1.39460474, 3.14159265])\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"c:\\users\\v_zhanglei48\\baidu\\personal-code\\pq\\paddle_quantum\\QSVT\\qsp.py:242: ComplexWarning: Casting complex values to real discards the imaginary part\n",
" Phi[i] = np.log(P.coef[n] / Q.coef[m]) * -1j / 2\n",
"c:\\users\\v_zhanglei48\\baidu\\personal-code\\pq\\paddle_quantum\\QSVT\\qsp.py:256: ComplexWarning: Casting complex values to real discards the imaginary part\n",
" Phi[0] = -1j * np.log(P.coef[0])\n"
]
}
],
"source": [
"qsp = ScalarQSP(P)\n",
"print(qsp.Phi)\n",
"assert Phi_verification(qsp.Phi.numpy(), P)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can also verify the correctness of $\\Phi$ from the corresponding circuit."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--Rz(-6.28)----Rx(-1.11)----Rz(2.789)----Rx(-1.11)----Rz(-0.88)----Rx(-1.11)----Rz(2.195)----Rx(-1.11)----Rz(-2.19)----Rx(-1.11)----Rz(0.886)----Rx(-1.11)----Rz(-2.78)----Rx(-1.11)----Rz(-6.28)--\n",
" \n",
"the error of simulating P(x) is 2.1200956324443587e-07\n"
]
}
],
"source": [
"x = np.random.rand() * 2 - 1 # random x in [-1, 1]\n",
"cir = qsp.block_encoding(x)\n",
"print(cir)\n",
"print(f\"the error of simulating P(x) is {np.abs(cir.unitary_matrix()[0, 0].item() - P(x))}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Variant of QSP"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Moreover, the number of parameters can be further simplified to $k$ by finding a vector of angles $\\Phi \\in \\mathbb{R}^k$ such that\n",
"\n",
"$$\n",
"R_\\Phi(x):=\\prod_{i = 1}^{k} e^{i \\phi_i \\sigma_z} R(x)\n",
"= \\begin{bmatrix}\n",
" P(x) & ... \\\\\n",
" ... & ...\n",
"\\end{bmatrix}, \\tag{6}\n",
"$$\n",
"\n",
"where\n",
"\n",
"$$\n",
"R(x) = \\begin{bmatrix}\n",
" x & \\sqrt{1 - x^2} \\\\\n",
" \\sqrt{1 - x^2} & -x\n",
"\\end{bmatrix}. \\tag{7}\n",
"$$\n",
"\n",
"In PaddleQuantum, we can compute such $\\Phi$ by function `reflection_based_quantum_signal_processing`."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[15.70796327 -0.17619159 -2.01393416 -0.47321657 -2.66837608 -1.12765849\n",
" -2.96540107]\n"
]
}
],
"source": [
"print(reflection_based_quantum_signal_processing(P))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> Note: $R_{-\\Phi}(x)$ is a block encoding of $P^*(x)$."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Quantum Singular Value Transformation"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To start with, we need to define the singular value transformation. \n",
"\n",
"Let $\\mathcal{X} \\in \\mathbb{C}^{2^m \\times 2^m}$ be a matrix. Suppose there exists a unitary $U$ and two orthogonal projectors $W$ and $V$ such that $\\mathcal{X} = W U V$. Then there exists two orthonormal basis $\\{|\\omega_j\\rangle\\}_{j=1}^{2^m}$ and $\\{|\\nu_j\\rangle\\}_{j=1}^{2^m}$ extended by eigenstates of $W$ and $V$ respectively, such that\n",
"\n",
"$$\n",
"\\mathcal{X} = \\sum_{j=1}^{2^m} \\lambda_j |\\omega_j\\rangle \\langle\\nu_j|, \\tag{8}\n",
"$$\n",
"\n",
"where \n",
"\n",
"$$\n",
"\\lambda_j = \\begin{cases}\n",
"\\langle\\omega_j| U |\\nu_j\\rangle \\text{ if } j \\leq \\text{rank}(\\mathcal{X}) \\\\\n",
"0 \\text{ otherwise }\n",
"\\end{cases} \\tag{9}\n",
"$$\n",
"\n",
"is a singular value of $\\mathcal{X}$. The **singular value transformation** (SVT) of $\\mathcal{X}$ for a function $f$ is defined to be\n",
"\n",
"$$\n",
"f^{(SV)}(\\mathcal{X}) := \\sum_{j=1}^{2^m} f(\\lambda_j)\n",
"\\begin{cases}\n",
" |\\nu_j\\rangle\\langle\\nu_j| & \\text{ if } f \\text{ is even} \\\\\n",
" |\\omega_j\\rangle\\langle\\nu_j| & \\text{ if } f \\text{ is odd}\n",
"\\end{cases}. \\tag{10}\n",
"$$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Decomposition and QSP"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"Paper [[2]]((https://doi.org/10.1145/3313276.3316366)) proves that under **appropriate** choices of basis, $U, V$ and $W$ can be decomposed by subspaces of $\\lambda_j$ such that\n",
"\n",
"$$\n",
"\\begin{split}\n",
"U & = \\bigoplus_{j: \\lambda_j \\in [0, 1)} R(\\lambda_j) \\oplus\n",
" \\bigoplus_{j: \\lambda_j = 1} [1] \\oplus\n",
" \\bigoplus_{j: V|\\nu_j\\rangle = |\\nu_j\\rangle,\\, WU|\\nu_j\\rangle = 0} [1] \\oplus\n",
" \\bigoplus_{j: W|\\omega_j\\rangle = |\\omega_j\\rangle,\\, VU^\\dagger|\\omega_j\\rangle = 0} [1] \\oplus \n",
" \\bigoplus_{j: V|\\nu_j\\rangle = W|\\omega_j\\rangle = 0} [...], \\\\\n",
"V & = \\bigoplus_{j: \\lambda_j \\in [0, 1)} |0\\rangle\\langle0| \\oplus\n",
" \\bigoplus_{j: \\lambda_j = 1} [1] \\oplus\n",
" \\bigoplus_{j: V|\\nu_j\\rangle = |\\nu_j\\rangle,\\, WU|\\nu_j\\rangle = 0} [1] \\oplus\n",
" \\bigoplus_{j: W|\\omega_j\\rangle = |\\omega_j\\rangle,\\, VU^\\dagger|\\omega_j\\rangle = 0} [0] \\oplus \n",
" \\bigoplus_{j: V|\\nu_j\\rangle = W|\\omega_j\\rangle = 0} [0] \\text{ and}\\\\\n",
"W & = \\bigoplus_{j: \\lambda_j \\in [0, 1)} |0\\rangle\\langle0| \\oplus\n",
" \\bigoplus_{j: \\lambda_j = 1} [1] \\oplus\n",
" \\bigoplus_{j: V|\\nu_j\\rangle = |\\nu_j\\rangle,\\, WU|\\nu_j\\rangle = 0} [0] \\oplus\n",
" \\bigoplus_{j: W|\\omega_j\\rangle = |\\omega_j\\rangle,\\, VU^\\dagger|\\omega_j\\rangle = 0} [1] \\oplus \n",
" \\bigoplus_{j: V|\\nu_j\\rangle = W|\\omega_j\\rangle = 0} [0] .\\\\\n",
"\\end{split} \\tag{11}\n",
"$$\n",
"\n",
"We can decompose $f^{(SV)}$ in a similar manner.\n",
"\n",
"$$\n",
"f^{(SV)}(\\mathcal{X}) = \\sum_{j: \\lambda_j \\in [0, 1)} f(\\lambda_j)\\,... + \\sum_{j: \\lambda_j = 1} f(1)\\,... + \\sum_{j: \\lambda_j = 0, V |\\nu_j\\rangle \\neq 0} f(0)\\,... + \\sum_{j: \\lambda_j = 0, W |\\omega_j\\rangle \\neq 0} f(0)\\,... + \\sum_{j: \\lambda_j = 0, \\text{otherwise}} f(0)\\,...\\,, \\tag{12}\n",
"$$\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's connect above decomposition with quantum signal processing. Suppose function $f$ is a polynomial $P \\in \\mathbb{C}[x]$ with degree $k$ that satisfies the requirements in the last section. \n",
"Then there exists $\\Phi \\in \\mathbb{R}^k$ such that $R_\\Phi$ is a block encoding of $P$. Moreover, $P$ satisfies $P(1) = e^{i \\sum_{j=1}^k \\phi_j}$ and $P(0) = e^{i \\sum_{j=1}^k (-1)^{j+1} \\phi_j}$.\n",
"Now Define $U_\\Phi$ as\n",
"\n",
"$$\n",
"U_\\Phi := \\begin{cases}\n",
"& \\prod_{j = 1}^{k / 2} e^{i\\phi_{2j - 1} (2V - I)} U^\\dagger e^{i\\phi_{2j} (2W - I)} U \\text{ if } k \\text{ is even} \\\\\n",
"e^{i\\phi_1 (2W - I)} U & \\prod_{j = 1}^{(k - 1) / 2} e^{i\\phi_{2j} (2V - I)} U^\\dagger e^{i\\phi_{2j+1} (2W - I)} U \\text{ if } k \\text{ is odd}\n",
"\\end{cases}. \\tag{13}\n",
"$$\n",
"\n",
"Then\n",
"\n",
"$$\n",
"U_\\Phi = \\begin{cases}\n",
" \\bigoplus R_\\Phi(\\lambda_j) \\oplus\n",
" \\bigoplus [e^{i \\sum_{j=1}^k \\phi_j}] \\oplus\n",
" \\bigoplus [e^{i \\sum_{j=1}^k (-1)^{j+1} \\phi_j}] \\oplus\n",
" \\bigoplus [e^{i \\sum_{j=1}^k (-1)^{j+1} \\phi_j}] \\oplus \n",
" \\bigoplus [...]\n",
"\\text{ if } k \\text{ is even} \\\\\n",
" \\bigoplus R_\\Phi(\\lambda_j) \\oplus\n",
" \\bigoplus [e^{i \\sum_{j=1}^k \\phi_j}] \\oplus\n",
" \\bigoplus [e^{i \\sum_{j=1}^k (-1)^{j+1} \\phi_j}] \\oplus\n",
" \\bigoplus [e^{i \\sum_{j=1}^k (-1)^{j+1} \\phi_j}] \\oplus \n",
" \\bigoplus [...]\n",
"\\text{ if } k \\text{ is odd}\n",
"\\end{cases} \\tag{14}\n",
"$$\n",
"\n",
"and hence\n",
"\n",
"$$\n",
"P^{(SV)}(\\mathcal{X}) = \\begin{cases} \n",
" \\bigoplus \\begin{bmatrix}\n",
" P(\\lambda_j) & 0 \\\\\n",
" 0 & 0 \\\\\n",
" \\end{bmatrix} \\oplus\n",
" \\bigoplus [P(1)] \\oplus\n",
" \\bigoplus [P(0)] \\oplus\n",
" \\bigoplus [0] \\oplus \n",
" \\bigoplus [0] = V U_\\Phi V\n",
"\\text{ if } k \\text{ is even} \\\\ \n",
" \\bigoplus \\begin{bmatrix}\n",
" P(\\lambda_j) & 0 \\\\\n",
" 0 & 0 \\\\\n",
" \\end{bmatrix} \\oplus\n",
" \\bigoplus [P(1)] \\oplus\n",
" \\bigoplus [0] \\oplus\n",
" \\bigoplus [0] \\oplus \n",
" \\bigoplus [0] = W U_\\Phi V \n",
"\\text{ if } k \\text{ is odd}\n",
"\\end{cases}. \\tag{15}\n",
"$$\n",
"\n",
"If we can realize $V U_\\Phi V$ or $W U_\\Phi V$ by a quantum algorithm, then such transformation is the quantum singular value transformation of $\\mathcal{X}$."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### QSVT and Block Encoding"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Suppose $U$ is a block encoding unitary of $A$ with respect to orthogonal projectors $W$ and $V$. Then $\\mathcal{X} = A \\oplus 0I^{\\otimes (m - n)}$ and $P^{(SV)}(\\mathcal{X}) = P^{(SV)}(A) \\oplus 0I^{\\otimes (m - n)}$. Therefore, $U_\\Phi$ is a block encoding of $P^{(SV)}(A)$.\n",
"\n",
"When $W = V$, $\\{|\\omega_j\\rangle \\}_{j=1}^{2^m} = \\{ |\\nu_j\\rangle \\}_{j=1}^{2^m}$. Denote $P(x) := \\sum_{i=0}^k c_i x^i$. We can find that \n",
"\n",
"$$\n",
"P^{(SV)}(\\mathcal{X}) = \\sum_{j=1}^{2^m} P(\\lambda_j) |\\nu_j\\rangle \\langle\\nu_j| = P(X) = P(A) \\oplus 0I^{\\otimes (m - n)}. \\tag{16}\n",
"$$ \n",
"\n",
"That is, when $W = V$, QSVT maps a block encoding of $A$ to a block encoding of $P(A)$."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Realization in PaddleQuantum"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"PaddleQuantum has a built-in class ``QSVT`` for quantum singular value transformation when $W = V = |0\\rangle^{\\otimes (m - n)}\\langle0|^{\\otimes (m - n)} \\otimes I^{\\otimes n}$. We can call its member function ``QSVT.block_encoding_matrix()`` to verify the correctness of above theories, from entries to entries and from eigenvalues to eigenvalues."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"c:\\Users\\v_zhanglei48\\Desktop\\QSVT 教程\\qsp.py:242: ComplexWarning: Casting complex values to real discards the imaginary part\n",
" Phi[i] = np.log(P.coef[n] / Q.coef[m]) * -1j / 2\n",
"c:\\Users\\v_zhanglei48\\Desktop\\QSVT 教程\\qsp.py:256: ComplexWarning: Casting complex values to real discards the imaginary part\n",
" Phi[0] = -1j * np.log(P.coef[0])\n"
]
}
],
"source": [
"qsvt = QSVT(P, U, num_block_qubits)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"c:\\Users\\v_zhanglei48\\Anaconda3\\envs\\personal-code\\lib\\site-packages\\paddle\\fluid\\dygraph\\math_op_patch.py:276: UserWarning: The dtype of left and right variables are not the same, left dtype is paddle.float64, but right dtype is paddle.float32, the right dtype will convert to paddle.float64\n",
" warnings.warn(\n"
]
}
],
"source": [
"# find P(A) and its expected eigenvalues, note that they are computed in different ways\n",
"expect_PX = poly_matrix(P, A).numpy()\n",
"expect_eig_result = np.sort(list(map(lambda x: P(x), np.linalg.eigvals(A.numpy()))))\n",
"\n",
"# Calculate U_\\Phi and extract eigenvalues of block encoded matrix\n",
"U_Phi = qsvt.block_encoding_matrix().numpy()\n",
"actual_PX = U_Phi[0:2 ** num_block_qubits, 0:2 ** num_block_qubits]\n",
"actual_eig_result = np.sort(np.linalg.eigvals(actual_PX))"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"error of simulating P(X)\n",
" maximum absolute, 1.1376122748011288e-07\n",
" percentage, 6.770728859410114e-07\n"
]
}
],
"source": [
"print(\"error of simulating P(X)\")\n",
"print(\" maximum absolute, \", np.amax(abs(expect_PX - actual_PX)))\n",
"print(\" percentage, \", np.linalg.norm(expect_PX - actual_PX) / np.linalg.norm(expect_PX))"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"error of eigenvalues of simulating P(X)\n",
" maximum absolute, 1.113571654238586e-07\n",
" percentage, 6.064903308479878e-07\n"
]
}
],
"source": [
"print(\"error of eigenvalues of simulating P(X)\")\n",
"print(\" maximum absolute, \", np.amax(abs(expect_eig_result - actual_eig_result)))\n",
"print(\" percentage, \", np.linalg.norm(expect_eig_result - actual_eig_result) / np.linalg.norm(expect_eig_result))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Qubitization: Quantum Realization of $U_\\Phi$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"One question is, how to realize $U_\\Phi$ by a quantum circuit? Note that we assume $U$ is implementable. Then the remaining problem is the realization of unitaries $e^{i\\phi (2W - I)}$ and $e^{i\\phi (2V - I)}$, which are essentially $R_Z(-2\\phi)$ operators performed in projection spaces of $W$ and $V$ respectively.\n",
"\n",
"To achieve this, Lemma 10 in paper [[1]](https://quantum-journal.org/papers/q-2019-07-12-163/) projects the image space of projector into an ancilla qubit and perform the rotation operation on that qubit. Then by entanglement such rotation is simultaneously applied to the main register. \n",
"\n",
"The results are summarized to the following circuit:\n",
"\n",
"![U_Phi](figures/QSVT-fig-U_Phi.png \"Figure 2: Quantum Circuit for QSVT, where k is the degree of P\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Realization in PaddleQuantum"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"When $W = V = |0\\rangle^{\\otimes (m - n)}\\langle0|^{\\otimes (m - n)} \\otimes I^{\\otimes n}$, PaddleQuantum can create a circuit in Figure 2 by member function ``QSVT.block_encoding_circuit``. We can show that the constructed quantum circuit can simulate $U_\\Phi$, by comparison of the output state and $(U_\\Phi \\otimes I)|\\psi\\rangle|0\\rangle$ for $|\\psi\\rangle \\in \\mathbb{C}^{2^m}$."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"# set state vector backend\n",
"paddle_quantum.set_backend('state_vector')\n",
"\n",
"# arbitrary state such that last qubit is in state |0\\rangle\n",
"ket_0 = [1.0, 0.0]\n",
"psi = np.array([np.random.rand() + 1j * np.random.rand() for _ in range(2 ** num_qubits)])\n",
"psi = np.kron(psi / np.linalg.norm(psi), ket_0)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"the different between expected and actual state is 1.6115462390639646e-07\n"
]
}
],
"source": [
"expect_state = np.kron(U_Phi, np.eye(2)) @ psi\n",
"\n",
"cir = qsvt.block_encoding_circuit()\n",
"actual_state = cir(paddle_quantum.State(psi, dtype='complex64')).data.numpy()\n",
"\n",
"print(f\"the different between expected and actual state is {np.linalg.norm(expect_state - actual_state)}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> Note: if we add Hadamard gates at the beginning and the end of the ancilla register in Figure 2, then the quantum circuit turns to simulate the real part of the polynomial. Combined with the technique of linear combination of unitaries, theocratically we can simulate all complex polynomials with norm smaller than 1 using quantum circuit."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Application: Amplitude Amplification"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let $|\\psi\\rangle$ be a $m$-qubit quantum state such that $|\\psi\\rangle = \\sin(\\theta)|\\psi_{\\text{good}}\\rangle + \\cos(\\theta) |\\psi_{\\text{bad}}\\rangle$. **Amplitude amplification** is a quantum algorithm that can increase the amplitude of $|\\psi_{\\text{good}}\\rangle$ to approximately 1.\n",
"This algorithm can be viewed in a different prospective. Let $U$ be the unitary and $W, V$ be two orthogonal projectors such that $|\\psi\\rangle = U V |0\\rangle^{\\otimes m}$ and $\\sin(\\theta)|\\psi_{\\text{good}}\\rangle = W |\\psi\\rangle$, implying $\\sin(\\theta)|\\psi_{\\text{good}}\\rangle = W U V |0\\rangle^{\\otimes m}$. Therefore, amplitude amplification is essentially a singular value transformation of $\\mathcal{X} = W U V$. In this section we will show how to use QSVT to do fixed-point (i.e. $\\theta = \\frac{\\pi}{2k}$ for some $k \\in \\mathbb{Z}$) amplitude amplification from Theorem 28 of paper [[2]]((https://doi.org/10.1145/3313276.3316366)). "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Suppose $\\sin(\\theta)|\\psi_{\\text{bad}}\\rangle = (|\\psi_{\\text{good}}\\rangle^{\\otimes (m - n)}\\langle0|^{\\otimes (m - n)} \\otimes I^{\\otimes n}) |\\psi\\rangle$ and $\\theta = \\frac{\\pi}{2k}$ for some $k \\in \\mathbb{Z}$. Then $W = V = |0\\rangle^{\\otimes (m - n)}\\langle0|^{\\otimes (m - n)} \\otimes I^{\\otimes n}$ and $\\mathcal{X} = A \\oplus 0I^{\\otimes (m - n)}$, where $A$ is the left-upper block of $U$. \n",
"\n",
"Observe that $\\mathcal{X} |\\psi_{\\text{good}\\rangle^{\\otimes m} = \\sin(\\frac{\\pi}{2k}) |\\psi\\rangle}$. Therefore, we aim to find a quantum circuit with unitary $U_\\Phi$ such that $W U_\\Phi V = \\frac{1}{\\sin(\\frac{\\pi}{2k})} \\mathcal{X}$, implying $W U_\\Phi V |\\psi_{\\text{good}}\\rangle^{\\otimes m} = |0\\rangle$. By $\\mathcal{X} = W U V$, the absolute values of singular values of $\\mathcal{X}$ are $\\sin(\\frac{\\pi}{2k})$. Moreover, choose $P(x) = (-1)^k T_k (x)$, where $T_k$ is the Chebyshev polynomial of the first kind of order $k$. Then\n",
"\n",
"$$\n",
"P(\\sin(\\frac{\\pi}{2k})) = (-1)^k T_k (\\sin(\\frac{\\pi}{2k})) = (-1)^k \\cos(\\frac{k - 1}{2}\\pi) = 1, \\tag{17}\n",
"$$\n",
"\n",
"implies that the QSVT of $\\mathcal{X}$ in terms of polynomial $P$ is a block encoding of $B := \\frac{1}{\\sin(\\frac{\\pi}{2k})} A$, as required."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Realization in PaddleQuantum"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We set $k = 3$ so that $\\sin(\\frac{\\pi}{2k}) = \\frac{1}{2}$, and $U$ is a randomly chosen unitary. We want to show that the left-upper block of $U$ is amplified by 2 after QSVT."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"P = -1 * Chebyshev([0 for _ in range(3)] + [1]).convert(kind=Polynomial)\n",
"U = unitary_random_with_hermitian_block(num_qubits, is_unitary=True)\n",
"A = U[0:2 ** num_block_qubits, 0:2 ** num_block_qubits].numpy()\n",
"\n",
"amplifier = QSVT(P, U, num_block_qubits)\n",
"U_Phi = amplifier.block_encoding_unitary()"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"the accuracy of quantum singular value transformation is 2.805196857025294e-07\n"
]
}
],
"source": [
"B = U_Phi[0:2 ** num_block_qubits, 0:2 ** num_block_qubits].numpy()\n",
"print(f\"the accuracy of quantum singular value transformation is {np.linalg.norm(B - 2 * A)}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As shown above, we successfully amplify $\\mathcal{X}$ by 2."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"## References\n",
"\n",
"[1] Low, Guang Hao, and Isaac L. Chuang. \"Hamiltonian simulation by qubitization.\" [Quantum 3 (2019): 163.](https://doi.org/10.22331/q-2019-07-12-163)\n",
"\n",
"[2] Gilyén, András, et al. \"Quantum singular value transformation and beyond: exponential improvements for quantum matrix arithmetics.\" [Proceedings of the 51st Annual ACM SIGACT Symposium on Theory of Computing. 2019.](https://doi.org/10.1145/3313276.3316366)\n",
"\n",
"[3] Camps, Daan, et al. \"Explicit Quantum Circuits for Block Encodings of Certain Sparse Matrices.\" [arXiv preprint arXiv:2203.10236 (2022).](http://arxiv.org/abs/2203.10236)\n",
"\n",
"[4] Clader, B. David, et al. \"Quantum Resources Required to Block-Encode a Matrix of Classical Data.\" [arXiv preprint arXiv:2206.03505 (2022).](http://arxiv.org/abs/2206.03505)\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.8.13 ('pq')",
"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.13"
},
"orig_nbformat": 4,
"vscode": {
"interpreter": {
"hash": "1e82098cfee7be27b5e385e3f85fe91d734d6114f7d09dccafdaad2c23171c3e"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
...@@ -196,7 +196,7 @@ ...@@ -196,7 +196,7 @@
"metadata": {}, "metadata": {},
"source": [ "source": [
"## 配置训练模型——模型参数\n", "## 配置训练模型——模型参数\n",
"在进行量子神经网络的训练之前,我们还需要进行一些训练的超参数设置,主要是学习速率(learning rate, LR)、迭代次数(iteration, ITR。这里我们设定学习速率为 0.3,迭代次数为 50 次。读者不妨自行调整来直观感受下超参数调整对训练效果的影响。" "在进行量子神经网络的训练之前,我们还需要进行一些训练的超参数设置,主要是学习速率(learning rate, LR)、迭代次数(iteration, ITR。这里我们设定学习速率为 0.3,迭代次数为 100 次。读者不妨自行调整来直观感受下超参数调整对训练效果的影响。"
] ]
}, },
{ {
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 2, "execution_count": 1,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T09:12:27.747028Z", "end_time": "2021-04-30T09:12:27.747028Z",
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 3, "execution_count": 2,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T09:12:27.773417Z", "end_time": "2021-04-30T09:12:27.773417Z",
...@@ -197,7 +197,7 @@ ...@@ -197,7 +197,7 @@
"source": [ "source": [
"## Hyper-parameters\n", "## Hyper-parameters\n",
"\n", "\n",
"Before training the quantum neural network, we also need to set up several hyper-parameters, mainly the learning rate LR, the number of iterations ITR. Here we set the learning rate to be LR = 0.3 and the number of iterations ITR = 50. One can adjust these hyper-parameters accordingly and check how they influence the training performance." "Before training the quantum neural network, we also need to set up several hyper-parameters, mainly the learning rate LR, the number of iterations ITR. Here we set the learning rate to be LR = 0.3 and the number of iterations ITR = 100. One can adjust these hyper-parameters accordingly and check how they influence the training performance."
] ]
}, },
{ {
......
...@@ -311,7 +311,7 @@ ...@@ -311,7 +311,7 @@
"source": [ "source": [
"### 配置训练模型 - 模型参数\n", "### 配置训练模型 - 模型参数\n",
"\n", "\n",
"在进行量子神经网络的训练之前,我们还需要进行一些训练的超参数设置,主要是学习速率(LR, learning rate)、迭代次数(ITR, iteration)和量子神经网络计算模块的深度(D, Depth)。这里我们设定学习速率为 0.5, 迭代次数为 50 次。读者不妨自行调整来直观感受下超参数调整对训练效果的影响。" "在进行量子神经网络的训练之前,我们还需要进行一些训练的超参数设置,主要是学习速率(LR, learning rate)、迭代次数(ITR, iteration)和量子神经网络计算模块的深度(D, Depth)。这里我们设定学习速率为 0.4, 迭代次数为 80 次。读者不妨自行调整来直观感受下超参数调整对训练效果的影响。"
] ]
}, },
{ {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册