提交 eadbc3eb 编写于 作者: Q Quleaf

update to v2.1.1

上级 fbcf1296
# Byte-compiled / optimized / DLL files
.idea .idea
.DS_Store
.vscode/
__pycache__/ __pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# .dat file
*.dat
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# qchem_data folder when run psi4 calculation
qchem_data/
output
*.egg-info
.ipynb_checkpoints
\ No newline at end of file
...@@ -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.1.0-orange.svg?style=flat-square&logo=pypi"/> <img src="https://img.shields.io/badge/pypi-v2.1.1-orange.svg?style=flat-square&logo=pypi"/>
</a> </a>
<!-- Python --> <!-- Python -->
<a href="https://www.python.org/"> <a href="https://www.python.org/">
...@@ -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.0.1+. 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.1.1+.
### Install Paddle Quantum ### Install Paddle Quantum
...@@ -147,6 +147,9 @@ We provide tutorials covering quantum simulation, machine learning, combinatoria ...@@ -147,6 +147,9 @@ We provide tutorials covering quantum simulation, machine learning, combinatoria
2. [Solving Max-Cut Problem with QAOA](./tutorial/combinatorial_optimization/MAXCUT_EN.ipynb) 2. [Solving Max-Cut Problem with QAOA](./tutorial/combinatorial_optimization/MAXCUT_EN.ipynb)
3. [Large-scale QAOA via Divide-and-Conquer](./tutorial/combinatorial_optimization/DC-QAOA_EN.ipynb) 3. [Large-scale QAOA via Divide-and-Conquer](./tutorial/combinatorial_optimization/DC-QAOA_EN.ipynb)
4. [Travelling Salesman Problem](./tutorial/combinatorial_optimization/TSP_EN.ipynb) 4. [Travelling Salesman Problem](./tutorial/combinatorial_optimization/TSP_EN.ipynb)
5. [Quantum Finance Application on Arbitrage Opportunity Optimization](./tutorial/combinatorial_optimization/ArbitrageOpportunityOptimation_EN.ipynb)
6. [Quantum Finance Application on Portfolio Optimization](./tutorial/combinatorial_optimization/PortfolioOptimization_EN.ipynb)
7. [Quantum Finance Application on Portfolio Diversification](./tutorial/combinatorial_optimization/PortfolioDiversification_EN.ipynb)
- [LOCC with QNN (LOCCNet)](./tutorial/locc) - [LOCC with QNN (LOCCNet)](./tutorial/locc)
1. [Local Operations and Classical Communication in QNN (LOCCNet)](./tutorial/locc/LOCCNET_Tutorial_EN.ipynb) 1. [Local Operations and Classical Communication in QNN (LOCCNet)](./tutorial/locc/LOCCNET_Tutorial_EN.ipynb)
...@@ -159,6 +162,7 @@ We provide tutorials covering quantum simulation, machine learning, combinatoria ...@@ -159,6 +162,7 @@ We provide tutorials covering quantum simulation, machine learning, combinatoria
- [QNN Research](./tutorial/qnn_research) - [QNN Research](./tutorial/qnn_research)
1. [The Barren Plateaus Phenomenon on Quantum Neural Networks (Barren Plateaus)](./tutorial/qnn_research/BarrenPlateaus_EN.ipynb) 1. [The Barren Plateaus Phenomenon on Quantum Neural Networks (Barren Plateaus)](./tutorial/qnn_research/BarrenPlateaus_EN.ipynb)
2. [Noise Model and Quantum Channel](./tutorial/qnn_research/Noise_EN.ipynb) 2. [Noise Model and Quantum Channel](./tutorial/qnn_research/Noise_EN.ipynb)
3. [Calculating Gradient Using Quantum Circuit](./tutorial/qnn_research/Gradient_EN.ipynb)
With the latest LOCCNet module, Paddle Quantum can efficiently simulate distributed quantum information processing tasks. Interested readers can start with this [tutorial on LOCCNet](./tutorial/locc/LOCCNET_Tutorial_EN.ipynb). In addition, Paddle Quantum supports QNN training on GPU. For users who want to get into more details, please check out the tutorial [Use Paddle Quantum on GPU](./introduction/PaddleQuantum_GPU_EN.ipynb). Moreover, Paddle Quantum could design robust quantum algorithms under noise. For more information, please see [Noise tutorial](./tutorial/qnn_research/Noise_EN.ipynb). With the latest LOCCNet module, Paddle Quantum can efficiently simulate distributed quantum information processing tasks. Interested readers can start with this [tutorial on LOCCNet](./tutorial/locc/LOCCNET_Tutorial_EN.ipynb). In addition, Paddle Quantum supports QNN training on GPU. For users who want to get into more details, please check out the tutorial [Use Paddle Quantum on GPU](./introduction/PaddleQuantum_GPU_EN.ipynb). Moreover, Paddle Quantum could design robust quantum algorithms under noise. For more information, please see [Noise tutorial](./tutorial/qnn_research/Noise_EN.ipynb).
......
...@@ -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.1.0-orange.svg?style=flat-square&logo=pypi"/> <img src="https://img.shields.io/badge/pypi-v2.1.1-orange.svg?style=flat-square&logo=pypi"/>
</a> </a>
<!-- Python --> <!-- Python -->
<a href="https://www.python.org/"> <a href="https://www.python.org/">
...@@ -71,7 +71,7 @@ ...@@ -71,7 +71,7 @@
### 安装 PaddlePaddle ### 安装 PaddlePaddle
当用户安装 Paddle Quantum 时会自动下载安装这个关键依赖包。关于 PaddlePaddle 更全面的安装信息请参考 [PaddlePaddle](https://www.paddlepaddle.org.cn/install/quick) 安装配置页面。此项目需求 PaddlePaddle 2.0.1+。 当用户安装 Paddle Quantum 时会自动下载安装这个关键依赖包。关于 PaddlePaddle 更全面的安装信息请参考 [PaddlePaddle](https://www.paddlepaddle.org.cn/install/quick) 安装配置页面。此项目需求 PaddlePaddle 2.1.1+。
### 安装 Paddle Quantum ### 安装 Paddle Quantum
...@@ -153,6 +153,9 @@ Paddle Quantum(量桨)建立起了人工智能与量子计算的桥梁,为 ...@@ -153,6 +153,9 @@ Paddle Quantum(量桨)建立起了人工智能与量子计算的桥梁,为
2. [QAOA 求解最大割问题](./tutorial/combinatorial_optimization/MAXCUT_CN.ipynb) 2. [QAOA 求解最大割问题](./tutorial/combinatorial_optimization/MAXCUT_CN.ipynb)
3. [大规模量子近似优化分治算法(DC-QAOA)](./tutorial/combinatorial_optimization/DC-QAOA_CN.ipynb) 3. [大规模量子近似优化分治算法(DC-QAOA)](./tutorial/combinatorial_optimization/DC-QAOA_CN.ipynb)
4. [旅行商问题](./tutorial/combinatorial_optimization/TSP_CN.ipynb) 4. [旅行商问题](./tutorial/combinatorial_optimization/TSP_CN.ipynb)
5. [量子金融应用:最佳套利机会](./tutorial/combinatorial_optimization/ArbitrageOpportunityOptimation_CN.ipynb)
6. [量子金融应用:投资组合优化](./tutorial/combinatorial_optimization/PortfolioOptimization_CN.ipynb)
7. [量子金融应用:投资组合分散化](./tutorial/combinatorial_optimization/PortfolioDiversification_CN.ipynb)
- [LOCC 量子神经网络(LOCCNet)](./tutorial/locc) - [LOCC 量子神经网络(LOCCNet)](./tutorial/locc)
1. [LOCC 量子神经网络](./tutorial/locc/LOCCNET_Tutorial_CN.ipynb) 1. [LOCC 量子神经网络](./tutorial/locc/LOCCNET_Tutorial_CN.ipynb)
...@@ -165,6 +168,7 @@ Paddle Quantum(量桨)建立起了人工智能与量子计算的桥梁,为 ...@@ -165,6 +168,7 @@ Paddle Quantum(量桨)建立起了人工智能与量子计算的桥梁,为
- [量子神经网络研究](./tutorial/qnn_research) - [量子神经网络研究](./tutorial/qnn_research)
1. [量子神经网络的贫瘠高原效应(Barren Plateaus)](./tutorial/qnn_research/BarrenPlateaus_CN.ipynb) 1. [量子神经网络的贫瘠高原效应(Barren Plateaus)](./tutorial/qnn_research/BarrenPlateaus_CN.ipynb)
2. [噪声模型与量子信道](./tutorial/qnn_research/Noise_CN.ipynb) 2. [噪声模型与量子信道](./tutorial/qnn_research/Noise_CN.ipynb)
3. [使用量子电路计算梯度](./tutorial/qnn_research/Gradient_CN.ipynb)
随着 LOCCNet 模组的推出,量桨现已支持分布式量子信息处理任务的高效模拟和开发。感兴趣的读者请参见[教程](./tutorial/locc/LOCCNET_Tutorial_CN.ipynb)。Paddle Quantum 也支持在 GPU 上进行量子机器学习的训练,具体的方法请参考案例:[在 GPU 上使用 Paddle Quantum](./introduction/PaddleQuantum_GPU_CN.ipynb)。此外,量桨可以基于噪声模块进行含噪算法的开发以及研究,详情请见[噪声模块教程](./tutorial/qnn_research/Noise_CN.ipynb) 随着 LOCCNet 模组的推出,量桨现已支持分布式量子信息处理任务的高效模拟和开发。感兴趣的读者请参见[教程](./tutorial/locc/LOCCNET_Tutorial_CN.ipynb)。Paddle Quantum 也支持在 GPU 上进行量子机器学习的训练,具体的方法请参考案例:[在 GPU 上使用 Paddle Quantum](./introduction/PaddleQuantum_GPU_CN.ipynb)。此外,量桨可以基于噪声模块进行含噪算法的开发以及研究,详情请见[噪声模块教程](./tutorial/qnn_research/Noise_CN.ipynb)
......
...@@ -981,7 +981,7 @@ ...@@ -981,7 +981,7 @@
"theta = np.random.randn(DEPTH, N, 1)\n", "theta = np.random.randn(DEPTH, N, 1)\n",
"\n", "\n",
"# 调用内置的 |00..0> 初始态\n", "# 调用内置的 |00..0> 初始态\n",
"initial_state1 = vec(N)\n", "initial_state1 = vec(0, N)\n",
"# 调用内置的随机量子态 |psi>\n", "# 调用内置的随机量子态 |psi>\n",
"initial_state2 = vec_random(N)\n", "initial_state2 = vec_random(N)\n",
" \n", " \n",
......
...@@ -788,7 +788,7 @@ ...@@ -788,7 +788,7 @@
"theta = np.random.randn(DEPTH, N, 1)\n", "theta = np.random.randn(DEPTH, N, 1)\n",
"\n", "\n",
"# Call the built-in |00..0> initial state\n", "# Call the built-in |00..0> initial state\n",
"initial_state1 = vec(N)\n", "initial_state1 = vec(0, N)\n",
"# Call the built-in random quantum state |psi>\n", "# Call the built-in random quantum state |psi>\n",
"initial_state2 = vec_random(N)\n", "initial_state2 = vec_random(N)\n",
" \n", " \n",
......
...@@ -71,15 +71,15 @@ class Net(paddle.nn.Layer): ...@@ -71,15 +71,15 @@ class Net(paddle.nn.Layer):
# Use computational basis to calculate each expectation value, which is the same # Use computational basis to calculate each expectation value, which is the same
# as a diagonal element in U^dagger*H*U # as a diagonal element in U^dagger*H*U
loss_components = [ loss_components = []
loss_struct[0][0], for i in range(len(loss_struct)):
loss_struct[1][1], loss_components.append(loss_struct[i][i])
loss_struct[2][2],
loss_struct[3][3]
]
# Calculate the weighted loss function # Calculate the weighted loss function
loss = 4 * loss_components[0] + 3 * loss_components[1] + 2 * loss_components[2] + 1 * loss_components[3] loss = 0
for i in range(len(loss_components)):
weight = 4 - i
loss += weight * loss_components[i]
return loss, loss_components return loss, loss_components
...@@ -128,17 +128,36 @@ def main(): ...@@ -128,17 +128,36 @@ def main():
loss_components = Paddle_SSVQE(H) loss_components = Paddle_SSVQE(H)
print('The estimated ground state energy is: ', loss_components[0].numpy()) def output_ordinalvalue(num):
print('The theoretical ground state energy: ', numpy.linalg.eigh(H)[0][0]) r"""
Convert to ordinal value
print('The estimated 1st excited state energy is: ', loss_components[1].numpy())
print('The theoretical 1st excited state energy: ', numpy.linalg.eigh(H)[0][1]) Args:
num (int): input number
print('The estimated 2nd excited state energy is: ', loss_components[2].numpy())
print('The theoretical 2nd excited state energy: ', numpy.linalg.eigh(H)[0][2]) Return:
(str): output ordinal value
print('The estimated 3rd excited state energy is: ', loss_components[3].numpy()) """
print('The theoretical 3rd excited state energy: ', numpy.linalg.eigh(H)[0][3]) if num == 1:
return str(num) + "st"
elif num == 2:
return str(num) + "nd"
elif num == 3:
return str(num) + "rd"
else:
return str(num) + 'th'
for i in range(len(loss_components)):
if i == 0:
print('The estimated ground state energy is: ', loss_components[i].numpy())
print('The theoretical ground state energy is: ', numpy.linalg.eigh(H)[0][i])
else:
print('The estimated {} excited state energy is: {}'.format(
output_ordinalvalue(i), loss_components[i].numpy())
)
print('The theoretical {} excited state energy is: {}'.format(
output_ordinalvalue(i), numpy.linalg.eigh(H)[0][i])
)
if __name__ == '__main__': if __name__ == '__main__':
......
...@@ -13,21 +13,24 @@ ...@@ -13,21 +13,24 @@
# limitations under the License. # limitations under the License.
import warnings import warnings
import numpy as np import copy
import math import math
import re
import matplotlib.pyplot as plt
from functools import reduce from functools import reduce
from collections import defaultdict from collections import defaultdict
import matplotlib.pyplot as plt import numpy as np
from paddle_quantum.simulator import transfer_state, init_state_gen, measure_state
import paddle import paddle
from paddle import matmul, trace, real, imag, reshape from paddle_quantum.simulator import transfer_state, init_state_gen, measure_state
from paddle_quantum.utils import dagger, pauli_str_to_matrix from paddle import imag, real, reshape, kron, matmul, trace
from paddle_quantum.utils import partial_trace, dagger, pauli_str_to_matrix
from paddle_quantum import shadow
from paddle_quantum.intrinsic import * from paddle_quantum.intrinsic import *
from paddle_quantum.state import density_op from paddle_quantum.state import density_op
__all__ = [ __all__ = [
"UAnsatz", "UAnsatz",
"H_prob" "swap_test"
] ]
...@@ -45,20 +48,164 @@ class UAnsatz: ...@@ -45,20 +48,164 @@ class UAnsatz:
Args: Args:
n (int): 该电路的量子比特数 n (int): 该电路的量子比特数
""" """
self.n = n self.n = n
self.__has_channel = False self.__has_channel = False
self.__state = None self.__state = None
self.__run_state = '' self.__run_mode = ''
# Record parameters in the circuit
self.__param = [paddle.to_tensor(np.array([0.0])),
paddle.to_tensor(np.array([math.pi / 2])), paddle.to_tensor(np.array([-math.pi / 2])),
paddle.to_tensor(np.array([math.pi / 4])), paddle.to_tensor(np.array([-math.pi / 4]))]
# Record history of adding gates to the circuit # Record history of adding gates to the circuit
self.__history = [] self.__history = []
def __add__(self, cir):
r"""重载加法 ‘+’ 运算符,用于拼接两个维度相同的电路
Args:
cir (UAnsatz): 拼接到现有电路上的电路
Returns:
UAnsatz: 拼接后的新电路
代码示例:
.. code-block:: python
from paddle_quantum.circuit import UAnsatz
print('cir1: ')
cir1 = UAnsatz(2)
cir1.superposition_layer()
print(cir1)
print('cir2: ')
cir2 = UAnsatz(2)
cir2.cnot([0,1])
print(cir2)
print('cir3: ')
cir3 = cir1 + cir2
print(cir3)
::
cir1:
--H--
--H--
cir2:
--*--
|
--x--
cir3:
--H----*--
|
--H----x--
"""
assert self.n == cir.n, "two circuits does not have the same dimension"
# Construct a new circuit that adds the two together
cir_out = UAnsatz(self.n)
cir_out.__param = copy.copy(self.__param)
cir_out.__history = copy.copy(self.__history)
cir_out._add_history(cir.__history, cir.__param)
return cir_out
def _get_history(self):
r"""获取当前电路加门的历史
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
return self.__history, self.__param
def _add_history(self, histories, param):
r"""往当前 UAnsatz 里直接添加历史
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
if type(histories) is dict:
histories = [histories]
for history_ele in histories:
param_idx = history_ele['theta']
if param_idx is None:
self.__history.append(copy.copy(history_ele))
else:
new_param_idx = []
curr_idx = len(self.__param)
for idx in param_idx:
self.__param.append(param[idx])
new_param_idx.append(curr_idx)
curr_idx += 1
self.__history.append({'gate': history_ele['gate'],
'which_qubits': history_ele['which_qubits'],
'theta': new_param_idx})
def get_run_mode(self):
r"""获取当前电路的运行模式。
Returns:
string: 当前电路的运行模式,态矢量或者是密度矩阵
代码示例:
.. code-block:: python
import paddle
from paddle_quantum.circuit import UAnsatz
import numpy as np
cir = UAnsatz(5)
cir.superposition_layer()
cir.run_state_vector()
print(cir.get_run_mode())
::
state_vector
"""
return self.__run_mode
def get_state(self):
r"""获取当前电路运行后的态
Returns:
paddle.Tensor: 当前电路运行后的态
代码示例:
.. code-block:: python
import paddle
from paddle_quantum.circuit import UAnsatz
import numpy as np
cir = UAnsatz(5)
cir.superposition_layer()
cir.run_state_vector()
print(cir.get_state())
::
Tensor(shape=[4], dtype=complex128, place=CPUPlace, stop_gradient=True,
[(0.4999999999999999+0j), (0.4999999999999999+0j), (0.4999999999999999+0j), (0.4999999999999999+0j)])
"""
return self.__state
def _count_history(self): def _count_history(self):
r"""calculate how many blocks needed for printing r"""calculate how many blocks needed for printing
Note: Note:
这是内部函数,你并不需要直接调用到该函数。 这是内部函数,你并不需要直接调用到该函数。
""" """
# Record length of each section # Record length of each section
length = [5] length = [5]
...@@ -70,11 +217,11 @@ class UAnsatz: ...@@ -70,11 +217,11 @@ class UAnsatz:
# Record section number for each gate # Record section number for each gate
gate = [] gate = []
history = self.__history history = self.__history
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[0] in {'h', 's', 't', 'x', 'y', 'z', 'u'}: if current_gate['gate'] in {'h', 's', 't', 'x', 'y', 'z', 'u', 'sdg', 'tdg'}:
curr_qubit = current_gate[1][0] curr_qubit = current_gate['which_qubits'][0]
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
...@@ -82,8 +229,8 @@ class UAnsatz: ...@@ -82,8 +229,8 @@ class UAnsatz:
length.append(5) length.append(5)
qubit_max = qubit[curr_qubit] qubit_max = qubit[curr_qubit]
# Gates with params to print # Gates with params to print
elif current_gate[0] in {'rx', 'ry', 'rz'}: elif current_gate['gate'] in {'rx', 'ry', 'rz'}:
curr_qubit = current_gate[1][0] curr_qubit = current_gate['which_qubits'][0]
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
...@@ -91,30 +238,30 @@ class UAnsatz: ...@@ -91,30 +238,30 @@ class UAnsatz:
if qubit[curr_qubit] > qubit_max: if qubit[curr_qubit] > qubit_max:
length.append(5) length.append(5)
qubit_max = qubit[curr_qubit] qubit_max = qubit[curr_qubit]
# Two-qubit gates # Two-qubit gates or Three-qubit gates
elif current_gate[0] in {'CNOT', 'SWAP', 'RXX_gate', 'RYY_gate', 'RZZ_gate', 'MS_gate'}: elif current_gate['gate'] in {'CNOT', 'SWAP', 'RXX_gate', 'RYY_gate', 'RZZ_gate', 'MS_gate', 'cy', 'cz',
p = current_gate[1][0] 'CU', 'crx', 'cry', 'crz'} or current_gate['gate'] in {'CSWAP', 'CCX'}:
q = current_gate[1][1] a = max(current_gate['which_qubits'])
a = max(p, q) b = min(current_gate['which_qubits'])
b = min(p, q)
ind = max(qubit[b: a + 1]) ind = max(qubit[b: a + 1])
gate.append(ind) gate.append(ind)
if length[ind] < 13 and current_gate[0] in {'RXX_gate', 'RYY_gate', 'RZZ_gate'}: if length[ind] < 13 and current_gate['gate'] in {'RXX_gate', 'RYY_gate', 'RZZ_gate', 'crx', 'cry',
'crz'}:
length[ind] = 13 length[ind] = 13
for j in range(b, a + 1): for j in range(b, a + 1):
qubit[j] = ind + 1 qubit[j] = ind + 1
if ind + 1 > qubit_max: if ind + 1 > qubit_max:
length.append(5) length.append(5)
qubit_max = ind + 1 qubit_max = ind + 1
return length, gate return length, gate
def __str__(self): def __str__(self):
r"""实现画电路的功能 r"""实现画电路的功能
Returns: Returns:
string: 用来print的字符串 string: 用来print的字符串
代码示例: 代码示例:
.. code-block:: python .. code-block:: python
...@@ -122,17 +269,17 @@ class UAnsatz: ...@@ -122,17 +269,17 @@ class UAnsatz:
import paddle import paddle
from paddle_quantum.circuit import UAnsatz from paddle_quantum.circuit import UAnsatz
import numpy as np import numpy as np
cir = UAnsatz(5) cir = UAnsatz(5)
cir.superposition_layer() cir.superposition_layer()
rotations = paddle.to_tensor(np.random.uniform(-2, 2, size=(3, 5, 1))) rotations = paddle.to_tensor(np.random.uniform(-2, 2, size=(3, 5, 1)))
cir.real_entangled_layer(rotations, 3) cir.real_entangled_layer(rotations, 3)
print(cir) print(cir)
:: ::
The printed circuit is: The printed circuit is:
--H----Ry(-0.14)----*-------------------X----Ry(-0.77)----*-------------------X-- --H----Ry(-0.14)----*-------------------X----Ry(-0.77)----*-------------------X--
| | | | | | | |
--H----Ry(-1.00)----X----*--------------|----Ry(-0.83)----X----*--------------|-- --H----Ry(-1.00)----X----*--------------|----Ry(-0.83)----X----*--------------|--
...@@ -142,76 +289,126 @@ class UAnsatz: ...@@ -142,76 +289,126 @@ class UAnsatz:
--H----Ry(1.024)--------------X----*----|----Ry(-0.37)--------------X----*----|-- --H----Ry(1.024)--------------X----*----|----Ry(-0.37)--------------X----*----|--
| | | | | | | |
--H----Ry(1.905)-------------------X----*----Ry(-1.82)-------------------X----*-- --H----Ry(1.905)-------------------X----*----Ry(-1.82)-------------------X----*--
""" """
length, gate = self._count_history() length, gate = self._count_history()
history = self.__history history = self.__history
n = self.n n = self.n
# 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 ' '] * total_length for i in range(n * 2)] print_list = [['-' if i % 2 == 0 else ' '] * total_length for i in range(n * 2)]
for i, current_gate in enumerate(history): for i, current_gate in enumerate(history):
if current_gate[0] in {'h', 's', 't', 'x', 'y', 'z', 'u'}: if current_gate['gate'] in {'h', 's', 't', 'x', 'y', 'z', 'u'}:
# 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[1][0] * 2][ind + length[sec] // 2] = current_gate[0].upper() print_list[current_gate['which_qubits'][0] * 2][ind + length[sec] // 2] = current_gate['gate'].upper()
elif current_gate[0] in {'rx', 'ry', 'rz'}: elif current_gate['gate'] in {'sdg'}:
sec = gate[i]
ind = sum(length[:sec])
print_list[current_gate['which_qubits'][0] * 2][
ind + length[sec] // 2 - 1: ind + length[sec] // 2 + 2] = current_gate['gate'].upper()
elif current_gate['gate'] in {'tdg'}:
sec = gate[i]
ind = sum(length[:sec])
print_list[current_gate['which_qubits'][0] * 2][
ind + length[sec] // 2 - 1: ind + length[sec] // 2 + 2] = current_gate['gate'].upper()
elif current_gate['gate'] in {'rx', 'ry', 'rz'}:
sec = gate[i] sec = gate[i]
ind = sum(length[:sec]) ind = sum(length[:sec])
line = current_gate[1][0] * 2 line = current_gate['which_qubits'][0] * 2
param = current_gate[2][2 if current_gate[0] == 'rz' else 0] param = self.__param[current_gate['theta'][2 if current_gate['gate'] == 'rz' else 0]]
print_list[line][ind + 2] = 'R' print_list[line][ind + 2] = 'R'
print_list[line][ind + 3] = current_gate[0][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 + 10] = format(float(param.numpy()), '.3f')[:5] print_list[line][ind + 5: ind + 10] = format(float(param.numpy()), '.3f')[:5]
print_list[line][ind + 10] = ')' print_list[line][ind + 10] = ')'
elif current_gate[0] in {'CNOT', 'SWAP', 'RXX_gate', 'RYY_gate', 'RZZ_gate', 'MS_gate'}: # Two-qubit gates
elif current_gate['gate'] in {'CNOT', 'SWAP', 'RXX_gate', 'RYY_gate', 'RZZ_gate', 'MS_gate', 'cz', 'cy',
'CU', 'crx', 'cry', 'crz'}:
sec = gate[i] sec = gate[i]
ind = sum(length[:sec]) ind = sum(length[:sec])
cqubit = current_gate[1][0] cqubit = current_gate['which_qubits'][0]
tqubit = current_gate[1][1] tqubit = current_gate['which_qubits'][1]
if current_gate[0] in {'CNOT', 'SWAP'}: 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] = \
print_list[tqubit * 2][ind + length[sec] // 2] = 'X' if current_gate[0] == 'CNOT' else '*' '*' if current_gate['gate'] in {'CNOT', 'cy', 'cz', 'CU'} else 'x'
elif current_gate[0] == 'MS_gate': print_list[tqubit * 2][ind + length[sec] // 2] = \
'x' if current_gate['gate'] in {'SWAP', 'CNOT'} else current_gate['gate'][1]
elif current_gate['gate'] == 'MS_gate':
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'
print_list[qubit * 2][ind + length[sec] // 2] = '_' print_list[qubit * 2][ind + length[sec] // 2] = '_'
print_list[qubit * 2][ind + length[sec] // 2 + 1] = 'S' print_list[qubit * 2][ind + length[sec] // 2 + 1] = 'S'
elif current_gate[0] in {'RXX_gate', 'RYY_gate', 'RZZ_gate'}: elif current_gate['gate'] in {'RXX_gate', 'RYY_gate', 'RZZ_gate'}:
param = current_gate[2] param = self.__param[current_gate['theta'][0]]
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 + 5] = current_gate[0][1:3].lower() print_list[line][ind + 3: ind + 5] = current_gate['gate'][1:3].lower()
print_list[line][ind + 5] = '(' print_list[line][ind + 5] = '('
print_list[line][ind + 6: ind + 10] = format(float(param.numpy()), '.2f')[:4] print_list[line][ind + 6: ind + 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'}:
param = self.__param[current_gate['theta'][2 if current_gate['gate'] == 'crz' else 0]]
print_list[cqubit * 2][ind + length[sec] // 2] = '*'
print_list[tqubit * 2][ind + 2] = 'R'
print_list[tqubit * 2][ind + 3] = current_gate['gate'][2]
print_list[tqubit * 2][ind + 4] = '('
print_list[tqubit * 2][ind + 5: ind + 10] = format(float(param.numpy()), '.3f')[:5]
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)
for k in range(start_line * 2 + 1, end_line * 2): for k in range(start_line * 2 + 1, end_line * 2):
print_list[k][ind + length[sec] // 2] = '|' print_list[k][ind + length[sec] // 2] = '|'
# Three-qubit gates
elif current_gate['gate'] in {'CSWAP'}:
sec = gate[i]
ind = sum(length[:sec])
cqubit = current_gate['which_qubits'][0]
tqubit1 = current_gate['which_qubits'][1]
tqubit2 = current_gate['which_qubits'][2]
start_line = min(current_gate['which_qubits'])
end_line = max(current_gate['which_qubits'])
for k in range(start_line * 2 + 1, end_line * 2):
print_list[k][ind + length[sec] // 2] = '|'
if current_gate['gate'] in {'CSWAP'}:
print_list[cqubit * 2][ind + length[sec] // 2] = '*'
print_list[tqubit1 * 2][ind + length[sec] // 2] = 'x'
print_list[tqubit2 * 2][ind + length[sec] // 2] = 'x'
elif current_gate['gate'] in {'CCX'}:
sec = gate[i]
ind = sum(length[:sec])
cqubit1 = current_gate['which_qubits'][0]
cqubit2 = current_gate['which_qubits'][1]
tqubit = current_gate['which_qubits'][2]
start_line = min(current_gate['which_qubits'])
end_line = max(current_gate['which_qubits'])
for k in range(start_line * 2 + 1, end_line * 2):
print_list[k][ind + length[sec] // 2] = '|'
if current_gate['gate'] in {'CCX'}:
print_list[cqubit1 * 2][ind + length[sec] // 2] = '*'
print_list[cqubit2 * 2][ind + length[sec] // 2] = '*'
print_list[tqubit * 2][ind + length[sec] // 2] = 'X'
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 run_state_vector(self, input_state=None, store_state=True): def run_state_vector(self, input_state=None, store_state=True):
r"""运行当前的量子电路,输入输出的形式为态矢量。 r"""运行当前的量子电路,输入输出的形式为态矢量。
Warning: Warning:
该方法只能运行无噪声的电路。 该方法只能运行无噪声的电路。
Args: Args:
input_state (Tensor, optional): 输入的态矢量,默认为 :math:`|00...0\rangle` input_state (Tensor, optional): 输入的态矢量,默认为 :math:`|00...0\rangle`
store_state (Bool, optional): 是否存储输出的态矢量,默认为 ``True`` ,即存储 store_state (Bool, optional): 是否存储输出的态矢量,默认为 ``True`` ,即存储
Returns: Returns:
Tensor: 量子电路输出的态矢量 Tensor: 量子电路输出的态矢量
代码示例: 代码示例:
.. code-block:: python .. code-block:: python
...@@ -223,7 +420,7 @@ class UAnsatz: ...@@ -223,7 +420,7 @@ class UAnsatz:
n = 2 n = 2
theta = np.ones(3) theta = np.ones(3)
input_state = paddle.to_tensor(vec(n)) input_state = paddle.to_tensor(vec(0, n))
theta = paddle.to_tensor(theta) theta = paddle.to_tensor(theta)
cir = UAnsatz(n) cir = UAnsatz(n)
cir.h(0) cir.h(0)
...@@ -241,29 +438,30 @@ class UAnsatz: ...@@ -241,29 +438,30 @@ class UAnsatz:
warnings.warn('The noiseless circuit will be run.', RuntimeWarning) warnings.warn('The noiseless circuit will be run.', RuntimeWarning)
state = init_state_gen(self.n, 0) if input_state is None else input_state state = init_state_gen(self.n, 0) if input_state is None else input_state
old_shape = state.shape old_shape = state.shape
assert reduce(lambda x, y: x * y, old_shape) == 2 ** self.n, 'The length of the input vector is not right' assert reduce(lambda x, y: x * y, old_shape) == 2 ** self.n, \
'The length of the input vector is not right'
state = reshape(state, (2 ** self.n,)) state = reshape(state, (2 ** self.n,))
state_conj = paddle.conj(state) state_conj = paddle.conj(state)
assert paddle.abs(real(paddle.sum(paddle.multiply(state_conj, state))) - 1) < 1e-8, \ assert paddle.abs(real(paddle.sum(paddle.multiply(state_conj, state))) - 1) < 1e-8, \
'Input state is not a normalized vector' 'Input state is not a normalized vector'
state = transfer_by_history(state, self.__history) state = transfer_by_history(state, self.__history, self.__param)
if store_state: if store_state:
self.__state = state self.__state = state
# Add info about which function user called # Add info about which function user called
self.__run_state = 'state_vector' self.__run_mode = 'state_vector'
return reshape(state, old_shape) return reshape(state, old_shape)
def run_density_matrix(self, input_state=None, store_state=True): def run_density_matrix(self, input_state=None, store_state=True):
r"""运行当前的量子电路,输入输出的形式为密度矩阵。 r"""运行当前的量子电路,输入输出的形式为密度矩阵。
Args: Args:
input_state (Tensor, optional): 输入的密度矩阵,默认为 :math:`|00...0\rangle \langle00...0|` input_state (Tensor, optional): 输入的密度矩阵,默认为 :math:`|00...0\rangle \langle00...0|`
store_state (bool, optional): 是否存储输出的密度矩阵,默认为 ``True`` ,即存储 store_state (bool, optional): 是否存储输出的密度矩阵,默认为 ``True`` ,即存储
Returns: Returns:
Tensor: 量子电路输出的密度矩阵 Tensor: 量子电路输出的密度矩阵
...@@ -294,7 +492,8 @@ class UAnsatz: ...@@ -294,7 +492,8 @@ class UAnsatz:
[0.47686058-0.03603751j 0.35403671+0.j ]] [0.47686058-0.03603751j 0.35403671+0.j ]]
""" """
state = paddle.to_tensor(density_op(self.n)) if input_state is None else input_state state = paddle.to_tensor(density_op(self.n)) if input_state is None else input_state
assert state.shape == [2 ** self.n, 2 ** self.n], "The dimension is not right" assert state.shape == [2 ** self.n, 2 ** self.n], \
"The dimension is not right"
if not self.__has_channel: if not self.__has_channel:
state = matmul(self.U, matmul(state, dagger(self.U))) state = matmul(self.U, matmul(state, dagger(self.U)))
...@@ -307,35 +506,85 @@ class UAnsatz: ...@@ -307,35 +506,85 @@ class UAnsatz:
identity = reshape(identity, [num_ele]) identity = reshape(identity, [num_ele])
u_start = 0 u_start = 0
i = 0
for i, history_ele in enumerate(self.__history): for i, history_ele in enumerate(self.__history):
if history_ele[0] == 'channel': if history_ele['gate'] == 'channel':
# Combine preceding unitary operations # Combine preceding unitary operations
unitary = transfer_by_history(identity, self.__history[u_start:i]) unitary = transfer_by_history(identity, self.__history[u_start:i], self.__param)
sub_state = paddle.zeros(shape, dtype='complex128') sub_state = paddle.zeros(shape, dtype='complex128')
# Sum all the terms corresponding to different Kraus operators # Sum all the terms corresponding to different Kraus operators
for op in history_ele[1]: for op in history_ele['operators']:
pseudo_u = reshape(transfer_state(unitary, op, history_ele[2]), shape) pseudo_u = reshape(transfer_state(unitary, op, history_ele['which_qubits']), shape)
sub_state += pseudo_u @ state @ dagger(pseudo_u) sub_state += matmul(pseudo_u, matmul(state, dagger(pseudo_u)))
state = sub_state state = sub_state
u_start = i + 1 u_start = i + 1
# Apply unitary operations left # Apply unitary operations left
unitary = reshape(transfer_by_history(identity, self.__history[u_start:(i + 1)]), shape) unitary = reshape(transfer_by_history(identity, self.__history[u_start:(i + 1)], self.__param), shape)
state = matmul(unitary, matmul(state, dagger(unitary))) state = matmul(unitary, matmul(state, dagger(unitary)))
if store_state: if store_state:
self.__state = state self.__state = state
# Add info about which function user called # Add info about which function user called
self.__run_state = 'density_matrix' self.__run_mode = 'density_matrix'
return state return state
def reset_state(self, state, which_qubits):
r"""对当前电路中的量子态的部分量子比特进行重置。
Args:
state (paddle.Tensor): 输入的量子态,表示要把选定的量子比特重置为该量子态
which_qubits (list): 需要被重置的量子比特编号
"""
qubits_list = which_qubits
n = self.n
m = len(qubits_list)
assert max(qubits_list) <= n, "qubit index out of range"
origin_seq = list(range(0, n))
target_seq = [idx for idx in origin_seq if idx not in qubits_list]
target_seq = qubits_list + target_seq
swapped = [False] * n
swap_list = list()
for idx in range(0, n):
if not swapped[idx]:
next_idx = idx
swapped[next_idx] = True
while not swapped[target_seq[next_idx]]:
swapped[target_seq[next_idx]] = True
swap_list.append((next_idx, target_seq[next_idx]))
next_idx = target_seq[next_idx]
cir0 = UAnsatz(n)
for a, b in swap_list:
cir0.swap([a, b])
cir1 = UAnsatz(n)
swap_list.reverse()
for a, b in swap_list:
cir1.swap([a, b])
_state = self.__state
if self.__run_mode == 'state_vector':
raise NotImplementedError('This feature is not implemented yet.')
elif self.__run_mode == 'density_matrix':
_state = cir0.run_density_matrix(_state)
_state = partial_trace(_state, 2 ** m, 2 ** (n - m), 1)
_state = kron(state, _state)
_state = cir1.run_density_matrix(_state)
else:
raise ValueError("Can't recognize the mode of quantum state.")
self.__state = _state
@property @property
def U(self): def U(self):
r"""量子电路的酉矩阵形式。 r"""量子电路的酉矩阵形式。
Warning: Warning:
该属性只限于无噪声的电路。 该属性只限于无噪声的电路。
Returns: Returns:
Tensor: 当前电路的酉矩阵表示 Tensor: 当前电路的酉矩阵表示
...@@ -369,11 +618,34 @@ class UAnsatz: ...@@ -369,11 +618,34 @@ class UAnsatz:
state = paddle.eye(dim, dtype='float64') state = paddle.eye(dim, dtype='float64')
state = paddle.cast(state, 'complex128') state = paddle.cast(state, 'complex128')
state = reshape(state, [num_ele]) state = reshape(state, [num_ele])
state = transfer_by_history(state, self.__history) state = transfer_by_history(state, self.__history, self.__param)
return reshape(state, shape) return reshape(state, shape)
def basis_encoding(self, x, invert=False): def __input_which_qubits_check(self, which_qubits):
r"""实现3个功能:
1. 检查 which_qubits 长度有无超过 qubits 的个数, (应小于等于qubits)
2. 检查 which_qubits 有无重复的值
3. 检查 which_qubits 的每个值有无超过量子 qubits 的序号, (应小于qubits,从 0 开始编号)
Args:
which_qubits (list) : 用于编码的量子比特
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
which_qubits_len = len(which_qubits)
set_list = set(which_qubits)
assert which_qubits_len <= self.n, \
"the length of which_qubit_list should less than the number of qubits"
assert which_qubits_len == len(set_list), \
"the which_qubits can not have duplicate elements"
for qubit_idx in which_qubits:
assert qubit_idx < self.n, \
"the value of which_qubit_list should less than the number of qubits"
def basis_encoding(self, x, which_qubits=None, invert=False):
r"""将输入的经典数据使用基态编码的方式编码成量子态。 r"""将输入的经典数据使用基态编码的方式编码成量子态。
在 basis encoding 中,输入的经典数据只能包括 0 或 1。如输入数据为 1101,则编码后的量子态为 :math:`|1101\rangle` 。 在 basis encoding 中,输入的经典数据只能包括 0 或 1。如输入数据为 1101,则编码后的量子态为 :math:`|1101\rangle` 。
...@@ -381,68 +653,148 @@ class UAnsatz: ...@@ -381,68 +653,148 @@ class UAnsatz:
Args: Args:
x (Tensor): 待编码的向量 x (Tensor): 待编码的向量
which_qubits (list): 用于编码的量子比特
invert (bool): 添加的是否为编码电路的逆电路,默认为 ``False`` ,即添加正常的编码电路 invert (bool): 添加的是否为编码电路的逆电路,默认为 ``False`` ,即添加正常的编码电路
""" """
x = paddle.flatten(x) x = paddle.flatten(x)
x = paddle.cast(x, dtype="int32") x = paddle.cast(x, dtype="int32")
assert x.size == self.n, "the number of classical data should be equal to the number of qubits" assert x.size <= self.n, \
"the number of classical data should less than or equal to the number of qubits"
if which_qubits is None:
which_qubits = list(range(self.n))
else:
self.__input_which_qubits_check(which_qubits)
assert x.size <= len(which_qubits), \
"the number of classical data should less than or equal to the number of 'which_qubits'"
for idx, element in enumerate(x): for idx, element in enumerate(x):
if element: if element:
self.x(idx) self.x(which_qubits[idx])
def amplitude_encoding(self, x, mode): def amplitude_encoding(self, x, mode, which_qubits=None):
r"""将输入的经典数据使用振幅编码的方式编码成量子态。 r"""将输入的经典数据使用振幅编码的方式编码成量子态。
Args: Args:
x (Tensor): 待编码的向量 x (Tensor): 待编码的向量
which_qubits (list): 用于编码的量子比特
mode (str): 生成的量子态的表示方式,``"state_vector"`` 代表态矢量表示, ``"density_matrix"`` 代表密度矩阵表示 mode (str): 生成的量子态的表示方式,``"state_vector"`` 代表态矢量表示, ``"density_matrix"`` 代表密度矩阵表示
Returns: Returns:
Tensor: 一个形状为 ``(2 ** n, )`` 或 ``(2 ** n, 2 ** n)`` 的张量,表示编码之后的量子态。 Tensor: 一个形状为 ``(2 ** n, )`` 或 ``(2 ** n, 2 ** n)`` 的张量,表示编码之后的量子态。
""" """
assert x.size <= 2 ** self.n, "the number of classical data should be equal to the number of qubits" assert x.size <= 2 ** self.n, \
"the number of classical data should less than or equal to the number of qubits"
if which_qubits is None:
which_qubits_len = math.ceil(math.log2(x.size))
which_qubits = list(range(which_qubits_len))
else:
self.__input_which_qubits_check(which_qubits)
which_qubits_len = len(which_qubits)
assert x.size <= 2 ** which_qubits_len, \
"the number of classical data should <= 2^(which_qubits)"
assert x.size > 2 ** (which_qubits_len - 1), \
"the number of classical data should >= 2^(which_qubits-1)"
def calc_location(location_of_bits_list):
r"""递归计算需要参与编码的量子态展开后的序号
方式:全排列,递归计算
Args:
location_of_bits_list (list): 标识了指定 qubits 的序号值,如指定编码第3个qubit(序号2),
则它处在展开后的 2**(3-1)=4 位置上。
Returns:
list : 标识了将要参与编码的量子位展开后的序号
"""
if len(location_of_bits_list) <= 1:
result_list = [0, location_of_bits_list[0]]
else:
current_tmp = location_of_bits_list[0]
inner_location_of_qubits_list = calc_location(location_of_bits_list[1:])
current_list_len = len(inner_location_of_qubits_list)
for each in range(current_list_len):
inner_location_of_qubits_list.append(inner_location_of_qubits_list[each] + current_tmp)
result_list = inner_location_of_qubits_list
return result_list
def encoding_location_list(which_qubits):
r"""计算每一个经典数据将要编码到量子态展开后的哪一个位置
Args:
which_qubits (list): 标识了参与编码的量子 qubits 的序号, 此参数与外部 which_qubits 参数应保持一致
Returns:
(list) : 将要参与编码的量子 qubits 展开后的序号,即位置序号
"""
location_of_bits_list = []
for each in range(len(which_qubits)):
tmp = 2 ** (self.n - which_qubits[each] - 1)
location_of_bits_list.append(tmp)
result_list = calc_location(location_of_bits_list)
return sorted(result_list)
# Get the specific position of the code, denoted by sequence number (list)
location_of_qubits_list = encoding_location_list(which_qubits)
# Classical data preprocessing
x = paddle.flatten(x) x = paddle.flatten(x)
pad_num = 2 ** self.n - x.size
if pad_num > 0:
zero_tensor = paddle.zeros((pad_num,), x.dtype)
x = paddle.concat([x, zero_tensor])
length = paddle.norm(x, p=2) length = paddle.norm(x, p=2)
# Normalization
x = paddle.divide(x, length) x = paddle.divide(x, length)
# Create a quantum state with all zero amplitudes
zero_tensor = paddle.zeros((2 ** self.n,), x.dtype)
# The value of the encoded amplitude is filled into the specified qubits
for i in range(len(x)):
zero_tensor[location_of_qubits_list[i]] = x[i]
# The quantum state that stores the result
result_tensor = zero_tensor
if mode == "state_vector": if mode == "state_vector":
x = paddle.cast(x, dtype="complex128") result_tensor = paddle.cast(result_tensor, dtype="complex128")
elif mode == "density_matrix": elif mode == "density_matrix":
x = paddle.reshape(x, (2**self.n, 1)) result_tensor = paddle.reshape(result_tensor, (2 ** self.n, 1))
x = matmul(x, dagger(x)) result_tensor = matmul(result_tensor, dagger(result_tensor))
else: else:
raise ValueError("the mode should be state_vector or density_matrix") raise ValueError("the mode should be state_vector or density_matrix")
return x
def angle_encoding(self, x, encoding_gate, invert=False): return result_tensor
def angle_encoding(self, x, encoding_gate, which_qubits=None, invert=False):
r"""将输入的经典数据使用角度编码的方式进行编码。 r"""将输入的经典数据使用角度编码的方式进行编码。
Args: Args:
x (Tensor): 待编码的向量 x (Tensor): 待编码的向量
encoding_gate (str): 编码要用的量子门,可以是 ``"rx"`` 、 ``"ry"`` 和 ``"rz"`` encoding_gate (str): 编码要用的量子门,可以是 ``"rx"`` 、 ``"ry"`` 和 ``"rz"``
which_qubits (list): 用于编码的量子比特
invert (bool): 添加的是否为编码电路的逆电路,默认为 ``False`` ,即添加正常的编码电路 invert (bool): 添加的是否为编码电路的逆电路,默认为 ``False`` ,即添加正常的编码电路
""" """
assert x.size <= self.n, "the number of classical data should be equal to the number of qubits" assert x.size <= self.n, \
"the number of classical data should be equal to the number of qubits"
if which_qubits is None:
which_qubits = list(range(self.n))
else:
self.__input_which_qubits_check(which_qubits)
assert x.size <= len(which_qubits), \
"the number of classical data should less than or equal to the number of 'which_qubits'"
x = paddle.flatten(x) x = paddle.flatten(x)
if invert: if invert:
x = -x x = -x
def add_encoding_gate(theta, idx, gate): def add_encoding_gate(theta, which, gate):
if gate == "rx": if gate == "rx":
self.rx(theta, idx) self.rx(theta, which)
elif gate == "ry": elif gate == "ry":
self.ry(theta, idx) self.ry(theta, which)
elif gate == "rz": elif gate == "rz":
self.rz(theta, idx) self.rz(theta, which)
else: else:
raise ValueError("the encoding_gate should be rx, ry, or rz") raise ValueError("the encoding_gate should be rx, ry, or rz")
for idx, element in enumerate(x): for idx, element in enumerate(x):
add_encoding_gate(element[0], idx, encoding_gate) add_encoding_gate(element[0], which_qubits[idx], encoding_gate)
def iqp_encoding(self, x, num_repeats=1, pattern=None, invert=False): def iqp_encoding(self, x, num_repeats=1, pattern=None, invert=False):
r"""将输入的经典数据使用 IQP 编码的方式进行编码。 r"""将输入的经典数据使用 IQP 编码的方式进行编码。
...@@ -453,7 +805,8 @@ class UAnsatz: ...@@ -453,7 +805,8 @@ class UAnsatz:
pattern (list): 量子比特的纠缠方式 pattern (list): 量子比特的纠缠方式
invert (bool): 添加的是否为编码电路的逆电路,默认为 ``False`` ,即添加正常的编码电路 invert (bool): 添加的是否为编码电路的逆电路,默认为 ``False`` ,即添加正常的编码电路
""" """
assert x.size <= self.n, "the number of classical data should be equal to the number of qubits" assert x.size <= self.n, \
"the number of classical data should be equal to the number of qubits"
num_x = x.size num_x = x.size
x = paddle.flatten(x) x = paddle.flatten(x)
if pattern is None: if pattern is None:
...@@ -467,7 +820,7 @@ class UAnsatz: ...@@ -467,7 +820,7 @@ class UAnsatz:
if invert: if invert:
for item in pattern: for item in pattern:
self.cnot(list(item)) self.cnot(list(item))
self.rz(-x[item[0]]*x[item[1]], item[1]) self.rz(-x[item[0]] * x[item[1]], item[1])
self.cnot(list(item)) self.cnot(list(item))
for idx in range(0, num_x): for idx in range(0, num_x):
self.rz(-x[idx], idx) self.rz(-x[idx], idx)
...@@ -480,7 +833,7 @@ class UAnsatz: ...@@ -480,7 +833,7 @@ class UAnsatz:
self.rz(x[idx], idx) self.rz(x[idx], idx)
for item in pattern: for item in pattern:
self.cnot(list(item)) self.cnot(list(item))
self.rz(x[item[0]]*x[item[1]], item[1]) self.rz(x[item[0]] * x[item[1]], item[1])
self.cnot(list(item)) self.cnot(list(item))
""" """
...@@ -491,10 +844,13 @@ class UAnsatz: ...@@ -491,10 +844,13 @@ class UAnsatz:
r"""添加关于 x 轴的单量子比特旋转门。 r"""添加关于 x 轴的单量子比特旋转门。
其矩阵形式为: 其矩阵形式为:
.. math:: .. math::
\begin{bmatrix} \cos\frac{\theta}{2} & -i\sin\frac{\theta}{2} \\ -i\sin\frac{\theta}{2} & \cos\frac{\theta}{2} \end{bmatrix} \begin{bmatrix}
\cos\frac{\theta}{2} & -i\sin\frac{\theta}{2} \\
-i\sin\frac{\theta}{2} & \cos\frac{\theta}{2}
\end{bmatrix}
Args: Args:
theta (Tensor): 旋转角度 theta (Tensor): 旋转角度
...@@ -513,26 +869,73 @@ class UAnsatz: ...@@ -513,26 +869,73 @@ class UAnsatz:
cir.rx(theta[0], which_qubit) cir.rx(theta[0], which_qubit)
""" """
assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n (the number of qubit)" assert 0 <= which_qubit < self.n, \
self.__history.append(['rx', [which_qubit], [theta, "the qubit should >= 0 and < n (the number of qubit)"
paddle.to_tensor(np.array([-math.pi / 2])), curr_idx = len(self.__param)
paddle.to_tensor(np.array([math.pi / 2]))]]) self.__history.append({'gate': 'rx', 'which_qubits': [which_qubit], 'theta': [curr_idx, 2, 1]})
self.__param.append(theta)
def crx(self, theta, which_qubit):
r"""添加关于 x 轴的控制单量子比特旋转门。
其矩阵形式为:
.. math::
\begin{align}
CNOT &=|0\rangle \langle 0|\otimes I + |1 \rangle \langle 1|\otimes rx\\
&=
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & \cos\frac{\theta}{2} & -i\sin\frac{\theta}{2} \\
0 & 0 & -i\sin\frac{\theta}{2} & \cos\frac{\theta}{2}
\end{bmatrix}
\end{align}
Args:
theta (Tensor): 旋转角度
which_qubit (list): 作用在的 qubit 的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数
.. code-block:: python
import numpy as np
import paddle
from paddle_quantum.circuit import UAnsatz
theta = np.array([np.pi], np.float64)
theta = paddle.to_tensor(theta)
num_qubits = 2
cir = UAnsatz(num_qubits)
which_qubit = [0, 1]
cir.crx(theta[0], which_qubit)
"""
assert 0 <= which_qubit[0] < self.n and 0 <= which_qubit[1] < self.n, \
"the qubit should >= 0 and < n (the number of qubit)"
assert which_qubit[0] != which_qubit[1], \
"the control qubit is the same as the target qubit"
curr_idx = len(self.__param)
self.__history.append({'gate': 'crx', 'which_qubits': which_qubit, 'theta': [curr_idx, 2, 1]})
self.__param.append(theta)
def ry(self, theta, which_qubit): def ry(self, theta, which_qubit):
r"""添加关于 y 轴的单量子比特旋转门。 r"""添加关于 y 轴的单量子比特旋转门。
其矩阵形式为: 其矩阵形式为:
.. math:: .. math::
\begin{bmatrix} \cos\frac{\theta}{2} & -\sin\frac{\theta}{2} \\ \sin\frac{\theta}{2} & \cos\frac{\theta}{2} \end{bmatrix} \begin{bmatrix}
\cos\frac{\theta}{2} & -\sin\frac{\theta}{2} \\
\sin\frac{\theta}{2} & \cos\frac{\theta}{2}
\end{bmatrix}
Args: Args:
theta (Tensor): 旋转角度 theta (Tensor): 旋转角度
which_qubit (int): 作用在的 qubit 的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数 which_qubit (int): 作用在的 qubit 的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数
.. code-block:: python .. code-block:: python
import numpy as np import numpy as np
import paddle import paddle
from paddle_quantum.circuit import UAnsatz from paddle_quantum.circuit import UAnsatz
...@@ -543,26 +946,72 @@ class UAnsatz: ...@@ -543,26 +946,72 @@ class UAnsatz:
which_qubit = 0 which_qubit = 0
cir.ry(theta[0], which_qubit) cir.ry(theta[0], which_qubit)
""" """
assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n (the number of qubit)" assert 0 <= which_qubit < self.n, \
self.__history.append(['ry', [which_qubit], [theta, "the qubit should >= 0 and < n (the number of qubit)"
paddle.to_tensor(np.array([0.0])), curr_idx = len(self.__param)
paddle.to_tensor(np.array([0.0]))]]) self.__history.append({'gate': 'ry', 'which_qubits': [which_qubit], 'theta': [curr_idx, 0, 0]})
self.__param.append(theta)
def cry(self, theta, which_qubit):
r"""添加关于 y 轴的控制单量子比特旋转门。
其矩阵形式为:
.. math::
\begin{align}
CNOT &=|0\rangle \langle 0|\otimes I + |1 \rangle \langle 1|\otimes rx\\
&=
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & \cos\frac{\theta}{2} & -\sin\frac{\theta}{2} \\
0 & 0 & \sin\frac{\theta}{2} & \cos\frac{\theta}{2}
\end{bmatrix}
\end{align}
Args:
theta (Tensor): 旋转角度
which_qubit (list): 作用在的 qubit 的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数
.. code-block:: python
import numpy as np
import paddle
from paddle_quantum.circuit import UAnsatz
theta = np.array([np.pi], np.float64)
theta = paddle.to_tensor(theta)
num_qubits = 2
cir = UAnsatz(num_qubits)
which_qubit = [0, 1]
cir.cry(theta[0], which_qubit)
"""
assert 0 <= which_qubit[0] < self.n and 0 <= which_qubit[1] < self.n, \
"the qubit should >= 0 and < n (the number of qubit)"
assert which_qubit[0] != which_qubit[1], \
"the control qubit is the same as the target qubit"
curr_idx = len(self.__param)
self.__history.append({'gate': 'cry', 'which_qubits': which_qubit, 'theta': [curr_idx, 0, 0]})
self.__param.append(theta)
def rz(self, theta, which_qubit): def rz(self, theta, which_qubit):
r"""添加关于 z 轴的单量子比特旋转门。 r"""添加关于 z 轴的单量子比特旋转门。
其矩阵形式为: 其矩阵形式为:
.. math:: .. math::
\begin{bmatrix} 1 & 0 \\ 0 & e^{i\theta} \end{bmatrix} \begin{bmatrix}
1 & 0 \\
0 & e^{i\theta}
\end{bmatrix}
Args: Args:
theta (Tensor): 旋转角度 theta (Tensor): 旋转角度
which_qubit (int): 作用在的 qubit 的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数 which_qubit (int): 作用在的 qubit 的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数
.. code-block:: python .. code-block:: python
import numpy as np import numpy as np
import paddle import paddle
from paddle_quantum.circuit import UAnsatz from paddle_quantum.circuit import UAnsatz
...@@ -573,10 +1022,53 @@ class UAnsatz: ...@@ -573,10 +1022,53 @@ class UAnsatz:
which_qubit = 0 which_qubit = 0
cir.rz(theta[0], which_qubit) cir.rz(theta[0], which_qubit)
""" """
assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n (the number of qubit)" assert 0 <= which_qubit < self.n, \
self.__history.append(['rz', [which_qubit], [paddle.to_tensor(np.array([0.0])), "the qubit should >= 0 and < n (the number of qubit)"
paddle.to_tensor(np.array([0.0])), curr_idx = len(self.__param)
theta]]) self.__history.append({'gate': 'rz', 'which_qubits': [which_qubit], 'theta': [0, 0, curr_idx]})
self.__param.append(theta)
def crz(self, theta, which_qubit):
r"""添加关于 z 轴的控制单量子比特旋转门。
其矩阵形式为:
.. math::
\begin{align}
CNOT &=|0\rangle \langle 0|\otimes I + |1 \rangle \langle 1|\otimes rx\\
&=
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & e^{i\theta}
\end{bmatrix}
\end{align}
Args:
theta (Tensor): 旋转角度
which_qubit (list): 作用在的 qubit 的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数
.. code-block:: python
import numpy as np
import paddle
from paddle_quantum.circuit import UAnsatz
theta = np.array([np.pi], np.float64)
theta = paddle.to_tensor(theta)
num_qubits = 2
cir = UAnsatz(num_qubits)
which_qubit = [0, 1]
cir.crz(theta[0], which_qubit)
"""
assert 0 <= which_qubit[0] < self.n and 0 <= which_qubit[1] < self.n, \
"the qubit should >= 0 and < n (the number of qubit)"
assert which_qubit[0] != which_qubit[1], \
"the control qubit is the same as the target qubit"
curr_idx = len(self.__param)
self.__history.append({'gate': 'crz', 'which_qubits': which_qubit, 'theta': [0, 0, curr_idx]})
self.__param.append(theta)
def cnot(self, control): def cnot(self, control):
r"""添加一个 CNOT 门。 r"""添加一个 CNOT 门。
...@@ -584,17 +1076,24 @@ class UAnsatz: ...@@ -584,17 +1076,24 @@ class UAnsatz:
对于 2 量子比特的量子电路,当 ``control`` 为 ``[0, 1]`` 时,其矩阵形式为: 对于 2 量子比特的量子电路,当 ``control`` 为 ``[0, 1]`` 时,其矩阵形式为:
.. math:: .. math::
\begin{align} \begin{align}
CNOT &=|0\rangle \langle 0|\otimes I + |1 \rangle \langle 1|\otimes X\\ CNOT &=|0\rangle \langle 0|\otimes I + |1 \rangle \langle 1|\otimes X\\
&=\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \end{bmatrix} &=
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 0 & 1 \\
0 & 0 & 1 & 0
\end{bmatrix}
\end{align} \end{align}
Args: Args:
control (list): 作用在的 qubit 的编号,``control[0]`` 为控制位,``control[1]`` 为目标位,其值都应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数 control (list): 作用在的量子比特的编号,``control[0]`` 为控制位,``control[1]`` 为目标位,
其值都应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数
.. code-block:: python .. code-block:: python
import numpy as np import numpy as np
import paddle import paddle
from paddle_quantum.circuit import UAnsatz from paddle_quantum.circuit import UAnsatz
...@@ -602,10 +1101,127 @@ class UAnsatz: ...@@ -602,10 +1101,127 @@ class UAnsatz:
cir = UAnsatz(num_qubits) cir = UAnsatz(num_qubits)
cir.cnot([0, 1]) cir.cnot([0, 1])
""" """
assert 0 <= control[0] < self.n and 0 <= control[1] < self.n,\ assert 0 <= control[0] < self.n and 0 <= control[1] < self.n, \
"the qubit should >= 0 and < n (the number of qubit)" "the qubit should >= 0 and < n (the number of qubit)"
assert control[0] != control[1], "the control qubit is the same as the target qubit" assert control[0] != control[1], \
self.__history.append(['CNOT', control, None]) "the control qubit is the same as the target qubit"
self.__history.append({'gate': 'CNOT', 'which_qubits': control, 'theta': None})
def cy(self, control):
r"""添加一个 cy 门。
对于 2 量子比特的量子电路,当 ``control`` 为 ``[0, 1]`` 时,其矩阵形式为:
.. math::
\begin{align}
CNOT &=|0\rangle \langle 0|\otimes I + |1 \rangle \langle 1|\otimes X\\
&=
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 0 & -1j \\
0 & 0 & 1j & 0
\end{bmatrix}
\end{align}
Args:
control (list): 作用在的量子比特的编号,``control[0]`` 为控制位,``control[1]`` 为目标位,
其值都应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数
.. code-block:: python
import numpy as np
import paddle
from paddle_quantum.circuit import UAnsatz
num_qubits = 2
cir = UAnsatz(num_qubits)
cir.cy([0, 1])
"""
assert 0 <= control[0] < self.n and 0 <= control[1] < self.n, \
"the qubit should >= 0 and < n (the number of qubit)"
assert control[0] != control[1], \
"the control qubit is the same as the target qubit"
self.__history.append({'gate': 'cy', 'which_qubits': control, 'theta': None})
def cz(self, control):
r"""添加一个 cz 门。
对于 2 量子比特的量子电路,当 ``control`` 为 ``[0, 1]`` 时,其矩阵形式为:
.. math::
\begin{align}
CNOT &=|0\rangle \langle 0|\otimes I + |1 \rangle \langle 1|\otimes X\\
&=
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & -1
\end{bmatrix}
\end{align}
Args:
control (list): 作用在的量子比特的编号,``control[0]`` 为控制位,``control[1]`` 为目标位,
其值都应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数
.. code-block:: python
import numpy as np
import paddle
from paddle_quantum.circuit import UAnsatz
num_qubits = 2
cir = UAnsatz(num_qubits)
cir.cz([0, 1])
"""
assert 0 <= control[0] < self.n and 0 <= control[1] < self.n, \
"the qubit should >= 0 and < n (the number of qubit)"
assert control[0] != control[1], \
"the control qubit is the same as the target qubit"
self.__history.append({'gate': 'cz', 'which_qubits': control, 'theta': None})
def cu(self, theta, phi, lam, control):
r"""添加一个控制 U 门。
对于 2 量子比特的量子电路,当 ``control`` 为 ``[0, 1]`` 时,其矩阵形式为:
.. math::
\begin{align}
CU
&=
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & \cos\frac\theta2 &-e^{i\lambda}\sin\frac\theta2 \\
0 & 0 & e^{i\phi}\sin\frac\theta2&e^{i(\phi+\lambda)}\cos\frac\theta2
\end{bmatrix}
\end{align}
Args:
theta (Tensor): 旋转角度 :math:`\theta` 。
phi (Tensor): 旋转角度 :math:`\phi` 。
lam (Tensor): 旋转角度 :math:`\lambda` 。
control (list): 作用在的量子比特的编号,``control[0]`` 为控制位,``control[1]`` 为目标位,
其值都应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数
.. code-block:: python
num_qubits = 2
cir = UAnsatz(num_qubits)
theta = paddle.to_tensor(np.array([np.pi], np.float64), stop_gradient=False)
phi = paddle.to_tensor(np.array([np.pi / 2], np.float64), stop_gradient=False)
lam = paddle.to_tensor(np.array([np.pi / 4], np.float64), stop_gradient=False)
cir.cu(theta, phi, lam, [0, 1])
"""
assert 0 <= control[0] < self.n and 0 <= control[1] < self.n, \
"the qubit should >= 0 and < n (the number of qubit)"
assert control[0] != control[1], \
"the control qubit is the same as the target qubit"
curr_idx = len(self.__param)
self.__history.append({'gate': 'CU', 'which_qubits': control, 'theta': [curr_idx, curr_idx + 1, curr_idx + 2]})
self.__param.extend([theta, phi, lam])
def swap(self, control): def swap(self, control):
r"""添加一个 SWAP 门。 r"""添加一个 SWAP 门。
...@@ -615,40 +1231,134 @@ class UAnsatz: ...@@ -615,40 +1231,134 @@ class UAnsatz:
.. math:: .. math::
\begin{align} \begin{align}
SWAP = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} SWAP =
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 0 & 1
\end{bmatrix}
\end{align} \end{align}
Args: Args:
control (list): 作用在的 qubit 的编号,``control[0]`` 和 ``control[1]`` 是想要交换的位,其值都应该在 :math:`[0, n)`范围内, :math:`n` 为该量子电路的量子比特数 control (list): 作用在的量子比特的编号,``control[0]`` 和 ``control[1]`` 是想要交换的位,
其值都应该在 :math:`[0, n)`范围内, :math:`n` 为该量子电路的量子比特数
.. code-block:: python .. code-block:: python
import numpy as np import numpy as np
import paddle import paddle
from paddle_quantum.circuit import UAnsatz from paddle_quantum.circuit import UAnsatz
num_qubits = 2 num_qubits = 2
cir = UAnsatz(num_qubits) cir = UAnsatz(num_qubits)
cir.swap([0, 1]) cir.swap([0, 1])
""" """
assert 0 <= control[0] < self.n and 0 <= control[1] < self.n,\ assert 0 <= control[0] < self.n and 0 <= control[1] < self.n, \
"the qubit should >= 0 and < n (the number of qubit)"
assert control[0] != control[1], \
"the indices needed to be swapped should not be the same"
self.__history.append({'gate': 'SWAP', 'which_qubits': control, 'theta': None})
def cswap(self, control):
r"""添加一个 CSWAP (Fredkin) 门。
其矩阵形式为:
.. math::
\begin{align}
SWAP =
\begin{bmatrix}
1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\
0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\
0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\
0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\
0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\
0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\
0 & 0 & 0 & 0 & 0 & 0 & 0 & 1
\end{bmatrix}
\end{align}
Args:
control (list): 作用在的量子比特的编号,``control[0]`` 为控制位,``control[1]`` 和 ``control[2]`` 是想要交换的目标位,
其值都应该在 :math:`[0, n)`范围内, :math:`n` 为该量子电路的量子比特数
.. code-block:: python
import numpy as np
import paddle
from paddle_quantum.circuit import UAnsatz
num_qubits = 3
cir = UAnsatz(num_qubits)
cir.cswap([0, 1, 2])
"""
assert 0 <= control[0] < self.n and 0 <= control[1] < self.n and 0 <= control[2] < self.n, \
"the qubit should >= 0 and < n (the number of qubit)"
assert control[0] != control[1] and control[0] != control[
2], "the control qubit is the same as the target qubit"
assert control[1] != control[2], "the indices needed to be swapped should not be the same"
self.__history.append({'gate': 'CSWAP', 'which_qubits': control, 'theta': None})
def ccx(self, control):
r"""添加一个 CCX (Toffoli) 门。
其矩阵形式为:
.. math::
\begin{align}
CCX =
\begin{bmatrix}
1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\
0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\
0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\
0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\
0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\
0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \\
0 & 0 & 0 & 0 & 0 & 0 & 1 & 0
\end{bmatrix}
\end{align}
Args:
control (list): 作用在的量子比特的编号, ``control[0]`` 和 ``control[1]`` 为控制位, ``control[2]`` 为目标位,
当控制位值都为1时在该比特位作用X门。其值都应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数
.. code-block:: python
import numpy as np
import paddle
from paddle_quantum.circuit import UAnsatz
num_qubits = 3
cir = UAnsatz(num_qubits)
cir.ccx([0, 1, 2])
"""
assert 0 <= control[0] < self.n and 0 <= control[1] < self.n and 0 <= control[2] < self.n, \
"the qubit should >= 0 and < n (the number of qubit)" "the qubit should >= 0 and < n (the number of qubit)"
assert control[0] != control[1], "the indices needed to be swapped should not be the same" assert control[0] != control[2] and control[1] != control[2], \
self.__history.append(['SWAP', control, None]) "the control qubits should not be the same as the target qubit"
assert control[0] != control[1], \
"two control qubits should not be the same"
self.__history.append({'gate': 'CCX', 'which_qubits': control, 'theta': None})
def x(self, which_qubit): def x(self, which_qubit):
r"""添加单量子比特 X 门。 r"""添加单量子比特 X 门。
其矩阵形式为: 其矩阵形式为:
.. math:: .. math::
\begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix} \begin{bmatrix}
0 & 1 \\
1 & 0
\end{bmatrix}
Args: Args:
which_qubit (int): 作用在的qubit的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数 which_qubit (int): 作用在的qubit的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数
.. code-block:: python .. code-block:: python
import paddle import paddle
from paddle_quantum.circuit import UAnsatz from paddle_quantum.circuit import UAnsatz
num_qubits = 1 num_qubits = 1
...@@ -663,22 +1373,25 @@ class UAnsatz: ...@@ -663,22 +1373,25 @@ class UAnsatz:
{'0': 0.0, '1': 1.0} {'0': 0.0, '1': 1.0}
""" """
assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n (the number of qubit)" assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n (the number of qubit)"
self.__history.append(['x', [which_qubit], None]) self.__history.append({'gate': 'x', 'which_qubits': [which_qubit], 'theta': None})
def y(self, which_qubit): def y(self, which_qubit):
r"""添加单量子比特 Y 门。 r"""添加单量子比特 Y 门。
其矩阵形式为: 其矩阵形式为:
.. math:: .. math::
\begin{bmatrix} 0 & -i \\ i & 0 \end{bmatrix} \begin{bmatrix}
0 & -i \\
i & 0
\end{bmatrix}
Args: Args:
which_qubit (int): 作用在的 qubit 的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数 which_qubit (int): 作用在的 qubit 的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数
.. code-block:: python .. code-block:: python
import paddle import paddle
from paddle_quantum.circuit import UAnsatz from paddle_quantum.circuit import UAnsatz
num_qubits = 1 num_qubits = 1
...@@ -693,22 +1406,25 @@ class UAnsatz: ...@@ -693,22 +1406,25 @@ class UAnsatz:
{'0': 0.0, '1': 1.0} {'0': 0.0, '1': 1.0}
""" """
assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n (the number of qubit)" assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n (the number of qubit)"
self.__history.append(['y', [which_qubit], None]) self.__history.append({'gate': 'y', 'which_qubits': [which_qubit], 'theta': None})
def z(self, which_qubit): def z(self, which_qubit):
r"""添加单量子比特 Z 门。 r"""添加单量子比特 Z 门。
其矩阵形式为: 其矩阵形式为:
.. math:: .. math::
\begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix} \begin{bmatrix}
1 & 0 \\
0 & -1
\end{bmatrix}
Args: Args:
which_qubit (int): 作用在的qubit的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数 which_qubit (int): 作用在的qubit的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数
.. code-block:: python .. code-block:: python
import paddle import paddle
from paddle_quantum.circuit import UAnsatz from paddle_quantum.circuit import UAnsatz
num_qubits = 1 num_qubits = 1
...@@ -723,7 +1439,7 @@ class UAnsatz: ...@@ -723,7 +1439,7 @@ class UAnsatz:
{'0': 1.0, '1': 0.0} {'0': 1.0, '1': 0.0}
""" """
assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n (the number of qubit)" assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n (the number of qubit)"
self.__history.append(['z', [which_qubit], None]) self.__history.append({'gate': 'z', 'which_qubits': [which_qubit], 'theta': None})
def h(self, which_qubit): def h(self, which_qubit):
r"""添加一个单量子比特的 Hadamard 门。 r"""添加一个单量子比特的 Hadamard 门。
...@@ -731,14 +1447,18 @@ class UAnsatz: ...@@ -731,14 +1447,18 @@ class UAnsatz:
其矩阵形式为: 其矩阵形式为:
.. math:: .. math::
H = \frac{1}{\sqrt{2}}\begin{bmatrix} 1&1\\1&-1 \end{bmatrix} H = \frac{1}{\sqrt{2}}
\begin{bmatrix}
1&1\\
1&-1
\end{bmatrix}
Args: Args:
which_qubit (int): 作用在的 qubit 的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数 which_qubit (int): 作用在的 qubit 的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数
""" """
assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n (the number of qubit)" assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n (the number of qubit)"
self.__history.append(['h', [which_qubit], None]) self.__history.append({'gate': 'h', 'which_qubits': [which_qubit], 'theta': None})
def s(self, which_qubit): def s(self, which_qubit):
r"""添加一个单量子比特的 S 门。 r"""添加一个单量子比特的 S 门。
...@@ -746,16 +1466,37 @@ class UAnsatz: ...@@ -746,16 +1466,37 @@ class UAnsatz:
其矩阵形式为: 其矩阵形式为:
.. math:: .. math::
S = \begin{bmatrix} 1&0\\0&i \end{bmatrix} S =
\begin{bmatrix}
1&0\\
0&i
\end{bmatrix}
Args: Args:
which_qubit (int): 作用在的 qubit 的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数 which_qubit (int): 作用在的 qubit 的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数
""" """
assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n (the number of qubit)" assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n (the number of qubit)"
self.__history.append(['s', [which_qubit], [paddle.to_tensor(np.array([0.0])), self.__history.append({'gate': 's', 'which_qubits': [which_qubit], 'theta': [0, 0, 1]})
paddle.to_tensor(np.array([0.0])),
paddle.to_tensor(np.array([math.pi / 2]))]]) def sdg(self, which_qubit):
r"""添加一个单量子比特的 S dagger 门。
其矩阵形式为:
.. math::
S^\dagger =
\begin{bmatrix}
1&0\\
0&-i
\end{bmatrix}
Args:
which_qubit (int): 作用在的 qubit 的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数
"""
assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n (the number of qubit)"
self.__history.append({'gate': 'sdg', 'which_qubits': [which_qubit], 'theta': [0, 0, 2]})
def t(self, which_qubit): def t(self, which_qubit):
r"""添加一个单量子比特的 T 门。 r"""添加一个单量子比特的 T 门。
...@@ -764,15 +1505,36 @@ class UAnsatz: ...@@ -764,15 +1505,36 @@ class UAnsatz:
.. math:: .. math::
T = \begin{bmatrix} 1&0\\0&e^\frac{i\pi}{4} \end{bmatrix} T =
\begin{bmatrix}
1&0\\
0&e^\frac{i\pi}{4}
\end{bmatrix}
Args: Args:
which_qubit (int): 作用在的 qubit 的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数 which_qubit (int): 作用在的 qubit 的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数
""" """
assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n (the number of qubit)" assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n (the number of qubit)"
self.__history.append(['t', [which_qubit], [paddle.to_tensor(np.array([0.0])), self.__history.append({'gate': 't', 'which_qubits': [which_qubit], 'theta': [0, 0, 3]})
paddle.to_tensor(np.array([0.0])),
paddle.to_tensor(np.array([math.pi / 4]))]]) def tdg(self, which_qubit):
r"""添加一个单量子比特的 T dagger 门。
其矩阵形式为:
.. math::
T^\dagger =
\begin{bmatrix}
1&0\\
0&e^\frac{-i\pi}{4}
\end{bmatrix}
Args:
which_qubit (int): 作用在的 qubit 的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数
"""
assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n (the number of qubit)"
self.__history.append({'gate': 'tdg', 'which_qubits': [which_qubit], 'theta': [0, 0, 4]})
def u3(self, theta, phi, lam, which_qubit): def u3(self, theta, phi, lam, which_qubit):
r"""添加一个单量子比特的旋转门。 r"""添加一个单量子比特的旋转门。
...@@ -780,13 +1542,13 @@ class UAnsatz: ...@@ -780,13 +1542,13 @@ class UAnsatz:
其矩阵形式为: 其矩阵形式为:
.. math:: .. math::
\begin{align} \begin{align}
U3(\theta, \phi, \lambda) = U3(\theta, \phi, \lambda) =
\begin{bmatrix} \begin{bmatrix}
\cos\frac\theta2&-e^{i\lambda}\sin\frac\theta2\\ \cos\frac\theta2&-e^{i\lambda}\sin\frac\theta2\\
e^{i\phi}\sin\frac\theta2&e^{i(\phi+\lambda)}\cos\frac\theta2 e^{i\phi}\sin\frac\theta2&e^{i(\phi+\lambda)}\cos\frac\theta2
\end{bmatrix} \end{bmatrix}
\end{align} \end{align}
Args: Args:
...@@ -796,7 +1558,10 @@ class UAnsatz: ...@@ -796,7 +1558,10 @@ class UAnsatz:
which_qubit (int): 作用在的 qubit 的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数 which_qubit (int): 作用在的 qubit 的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数
""" """
assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n (the number of qubit)" assert 0 <= which_qubit < self.n, "the qubit should >= 0 and < n (the number of qubit)"
self.__history.append(['u', [which_qubit], [theta, phi, lam]]) curr_idx = len(self.__param)
self.__history.append(
{'gate': 'u', 'which_qubits': [which_qubit], 'theta': [curr_idx, curr_idx + 1, curr_idx + 2]})
self.__param.extend([theta, phi, lam])
def rxx(self, theta, which_qubits): def rxx(self, theta, which_qubits):
r"""添加一个 RXX 门。 r"""添加一个 RXX 门。
...@@ -806,13 +1571,13 @@ class UAnsatz: ...@@ -806,13 +1571,13 @@ class UAnsatz:
.. math:: .. math::
\begin{align} \begin{align}
RXX(\theta) = RXX(\theta) =
\begin{bmatrix} \begin{bmatrix}
\cos\frac{\theta}{2} & 0 & 0 & -i\sin\frac{\theta}{2} \\ \cos\frac{\theta}{2} & 0 & 0 & -i\sin\frac{\theta}{2} \\
0 & \cos\frac{\theta}{2} & -i\sin\frac{\theta}{2} & 0 \\ 0 & \cos\frac{\theta}{2} & -i\sin\frac{\theta}{2} & 0 \\
0 & -i\sin\frac{\theta}{2} & \cos\frac{\theta}{2} & 0 \\ 0 & -i\sin\frac{\theta}{2} & \cos\frac{\theta}{2} & 0 \\
-i\sin\frac{\theta}{2} & 0 & 0 & \cos\frac{\theta}{2} -i\sin\frac{\theta}{2} & 0 & 0 & \cos\frac{\theta}{2}
\end{bmatrix} \end{bmatrix}
\end{align} \end{align}
Args: Args:
...@@ -831,7 +1596,9 @@ class UAnsatz: ...@@ -831,7 +1596,9 @@ class UAnsatz:
assert 0 <= which_qubits[0] < self.n and 0 <= which_qubits[1] < self.n, \ assert 0 <= which_qubits[0] < self.n and 0 <= which_qubits[1] < self.n, \
"the qubit should >= 0 and < n (the number of qubit)" "the qubit should >= 0 and < n (the number of qubit)"
assert which_qubits[0] != which_qubits[1], "the indices of two qubits should be different" assert which_qubits[0] != which_qubits[1], "the indices of two qubits should be different"
self.__history.append(['RXX_gate', which_qubits, theta]) curr_idx = len(self.__param)
self.__history.append({'gate': 'RXX_gate', 'which_qubits': which_qubits, 'theta': [curr_idx]})
self.__param.append(theta)
def ryy(self, theta, which_qubits): def ryy(self, theta, which_qubits):
r"""添加一个 RYY 门。 r"""添加一个 RYY 门。
...@@ -841,13 +1608,13 @@ class UAnsatz: ...@@ -841,13 +1608,13 @@ class UAnsatz:
.. math:: .. math::
\begin{align} \begin{align}
RYY(\theta) = RYY(\theta) =
\begin{bmatrix} \begin{bmatrix}
\cos\frac{\theta}{2} & 0 & 0 & i\sin\frac{\theta}{2} \\ \cos\frac{\theta}{2} & 0 & 0 & i\sin\frac{\theta}{2} \\
0 & \cos\frac{\theta}{2} & -i\sin\frac{\theta}{2} & 0 \\ 0 & \cos\frac{\theta}{2} & -i\sin\frac{\theta}{2} & 0 \\
0 & -i\sin\frac{\theta}{2} & \cos\frac{\theta}{2} & 0 \\ 0 & -i\sin\frac{\theta}{2} & \cos\frac{\theta}{2} & 0 \\
i\sin\frac{\theta}{2} & 0 & 0 & cos\frac{\theta}{2} i\sin\frac{\theta}{2} & 0 & 0 & cos\frac{\theta}{2}
\end{bmatrix} \end{bmatrix}
\end{align} \end{align}
Args: Args:
...@@ -866,7 +1633,9 @@ class UAnsatz: ...@@ -866,7 +1633,9 @@ class UAnsatz:
assert 0 <= which_qubits[0] < self.n and 0 <= which_qubits[1] < self.n, \ assert 0 <= which_qubits[0] < self.n and 0 <= which_qubits[1] < self.n, \
"the qubit should >= 0 and < n (the number of qubit)" "the qubit should >= 0 and < n (the number of qubit)"
assert which_qubits[0] != which_qubits[1], "the indices of two qubits should be different" assert which_qubits[0] != which_qubits[1], "the indices of two qubits should be different"
self.__history.append(['RYY_gate', which_qubits, theta]) curr_idx = len(self.__param)
self.__history.append({'gate': 'RYY_gate', 'which_qubits': which_qubits, 'theta': [curr_idx]})
self.__param.append(theta)
def rzz(self, theta, which_qubits): def rzz(self, theta, which_qubits):
r"""添加一个 RZZ 门。 r"""添加一个 RZZ 门。
...@@ -876,13 +1645,13 @@ class UAnsatz: ...@@ -876,13 +1645,13 @@ class UAnsatz:
.. math:: .. math::
\begin{align} \begin{align}
RZZ(\theta) = RZZ(\theta) =
\begin{bmatrix} \begin{bmatrix}
e^{-i\frac{\theta}{2}} & 0 & 0 & 0 \\ e^{-i\frac{\theta}{2}} & 0 & 0 & 0 \\
0 & e^{i\frac{\theta}{2}} & 0 & 0 \\ 0 & e^{i\frac{\theta}{2}} & 0 & 0 \\
0 & 0 & e^{i\frac{\theta}{2}} & 0 \\ 0 & 0 & e^{i\frac{\theta}{2}} & 0 \\
0 & 0 & 0 & e^{-i\frac{\theta}{2}} 0 & 0 & 0 & e^{-i\frac{\theta}{2}}
\end{bmatrix} \end{bmatrix}
\end{align} \end{align}
Args: Args:
...@@ -901,7 +1670,9 @@ class UAnsatz: ...@@ -901,7 +1670,9 @@ class UAnsatz:
assert 0 <= which_qubits[0] < self.n and 0 <= which_qubits[1] < self.n, \ assert 0 <= which_qubits[0] < self.n and 0 <= which_qubits[1] < self.n, \
"the qubit should >= 0 and < n (the number of qubit)" "the qubit should >= 0 and < n (the number of qubit)"
assert which_qubits[0] != which_qubits[1], "the indices of two qubits should be different" assert which_qubits[0] != which_qubits[1], "the indices of two qubits should be different"
self.__history.append(['RZZ_gate', which_qubits, theta]) curr_idx = len(self.__param)
self.__history.append({'gate': 'RZZ_gate', 'which_qubits': which_qubits, 'theta': [curr_idx]})
self.__param.append(theta)
def ms(self, which_qubits): def ms(self, which_qubits):
r"""添加一个 Mølmer-Sørensen (MS) 门,用于离子阱设备。 r"""添加一个 Mølmer-Sørensen (MS) 门,用于离子阱设备。
...@@ -911,13 +1682,13 @@ class UAnsatz: ...@@ -911,13 +1682,13 @@ class UAnsatz:
.. math:: .. math::
\begin{align} \begin{align}
MS = RXX(-\frac{\pi}{2}) = \frac{1}{\sqrt{2}} MS = RXX(-\frac{\pi}{2}) = \frac{1}{\sqrt{2}}
\begin{bmatrix} \begin{bmatrix}
1 & 0 & 0 & i \\ 1 & 0 & 0 & i \\
0 & 1 & i & 0 \\ 0 & 1 & i & 0 \\
0 & i & 1 & 0 \\ 0 & i & 1 & 0 \\
i & 0 & 0 & 1 i & 0 & 0 & 1
\end{bmatrix} \end{bmatrix}
\end{align} \end{align}
Args: Args:
...@@ -938,7 +1709,7 @@ class UAnsatz: ...@@ -938,7 +1709,7 @@ class UAnsatz:
assert 0 <= which_qubits[0] < self.n and 0 <= which_qubits[1] < self.n, \ assert 0 <= which_qubits[0] < self.n and 0 <= which_qubits[1] < self.n, \
"the qubit should >= 0 and < n(the number of qubit)" "the qubit should >= 0 and < n(the number of qubit)"
assert which_qubits[0] != which_qubits[1], "the indices of two qubits should be different" assert which_qubits[0] != which_qubits[1], "the indices of two qubits should be different"
self.__history.append(['MS_gate', which_qubits, paddle.to_tensor(-np.array([np.pi / 2]))]) self.__history.append({'gate': 'MS_gate', 'which_qubits': which_qubits, 'theta': [2]})
def universal_2_qubit_gate(self, theta, which_qubits): def universal_2_qubit_gate(self, theta, which_qubits):
r"""添加 2-qubit 通用门,这个通用门需要 15 个参数。 r"""添加 2-qubit 通用门,这个通用门需要 15 个参数。
...@@ -965,13 +1736,13 @@ class UAnsatz: ...@@ -965,13 +1736,13 @@ class UAnsatz:
{'00': 0.4306256106527819, '01': 0.07994547866706268, '10': 0.07994547866706264, '11': 0.40948343201309334} {'00': 0.4306256106527819, '01': 0.07994547866706268, '10': 0.07994547866706264, '11': 0.40948343201309334}
""" """
assert len(theta.shape) == 1, 'The shape of theta is not right' assert len(theta.shape) == 1, 'The shape of theta is not right'
assert len(theta) == 15, 'This Ansatz accepts 15 parameters' assert len(theta) == 15, 'This Ansatz accepts 15 parameters'
assert len(which_qubits) == 2, "You should add this gate on two qubits" assert len(which_qubits) == 2, "You should add this gate on two qubits"
a, b = which_qubits a, b = which_qubits
self.u3(theta[0], theta[1], theta[2], a) self.u3(theta[0], theta[1], theta[2], a)
self.u3(theta[3], theta[4], theta[5], b) self.u3(theta[3], theta[4], theta[5], b)
self.cnot([b, a]) self.cnot([b, a])
...@@ -1021,15 +1792,253 @@ class UAnsatz: ...@@ -1021,15 +1792,253 @@ class UAnsatz:
self.rz(theta[2], which_qubits[2]) self.rz(theta[2], which_qubits[2])
self.cnot([which_qubits[0], which_qubits[2]]) self.cnot([which_qubits[0], which_qubits[2]])
def universal_3_qubit_gate(self, theta, which_qubits): def universal_3_qubit_gate(self, theta, which_qubits):
r"""添加 3-qubit 通用门,这个通用门需要 81 个参数。 r"""添加 3-qubit 通用门,这个通用门需要 81 个参数。
Args:
theta (Tensor): 3-qubit 通用门的参数,其维度为 ``(81, )``
which_qubits(list): 作用的量子比特编号
Note:
参考: https://cds.cern.ch/record/708846/files/0401178.pdf
代码示例:
.. code-block:: python
import numpy as np
import paddle
from paddle_quantum.circuit import UAnsatz
n = 3
theta = paddle.to_tensor(np.ones(81))
cir = UAnsatz(n)
cir.universal_3_qubit_gate(theta, [0, 1, 2])
cir.run_state_vector()
print(cir.measure(shots = 0))
::
{'000': 0.06697926831547105, '001': 0.13206788591381013, '010': 0.2806525391078656, '011': 0.13821526515701105, '100': 0.1390530116439897, '101': 0.004381404333075108, '110': 0.18403296778911565, '111': 0.05461765773966483}
"""
assert len(which_qubits) == 3, "You should add this gate on three qubits"
assert len(theta) == 81, "The length of theta is supposed to be 81"
psi = reshape(x=theta[: 60], shape=[4, 15])
phi = reshape(x=theta[60:], shape=[7, 3])
self.universal_2_qubit_gate(psi[0], which_qubits[:2])
self.u3(phi[0][0], phi[0][1], phi[0][2], which_qubits[2])
self.__u3qg_U(phi[1], which_qubits)
self.universal_2_qubit_gate(psi[1], which_qubits[:2])
self.u3(phi[2][0], phi[2][1], phi[2][2], which_qubits[2])
self.__u3qg_V(phi[3], which_qubits)
self.universal_2_qubit_gate(psi[2], which_qubits[:2])
self.u3(phi[4][0], phi[4][1], phi[4][2], which_qubits[2])
self.__u3qg_U(phi[5], which_qubits)
self.universal_2_qubit_gate(psi[3], which_qubits[:2])
self.u3(phi[6][0], phi[6][1], phi[6][2], which_qubits[2])
def pauli_rotation_gate_partial(self, ind, gate_name):
r"""计算传入的泡利旋转门的偏导。
Args:
ind (int): 该门在本电路中的序号
gate_name (string): 门的名字
Return:
UAnsatz: 用电路表示的该门的偏导
代码示例:
.. code-block:: python
import numpy as np
import paddle
from paddle_quantum.circuit import UAnsatz
cir = UAnsatz(2)
theta = paddle.to_tensor([np.pi, np.pi/2, np.pi/4], 'float64')
cir.rx(theta[0], 0)
cir.ryy(theta[1], [1, 0])
cir.rz(theta[2], 1)
print(cir.pauli_rotation_gate_partial(0, 'rx'))
::
------------x----Rx(3.142)----Ryy(1.57)---------------
| |
------------|-----------------Ryy(1.57)----Rz(0.785)--
|
--H---SDG---*--------H--------------------------------
"""
history, param = self._get_history()
assert ind <= len(history), "The index number should be less than or equal to %d" % len(history)
assert gate_name in {'rx', 'ry', 'rz', 'RXX_gate', 'RYY_gate', 'RZZ_gate'}, "Gate not supported."
assert gate_name == history[ind]['gate'], "Gate name incorrect."
n = self.n
new_circuit = UAnsatz(n + 1)
new_circuit._add_history(history[:ind], param)
new_circuit.h(n)
new_circuit.sdg(n)
if gate_name in {'rx', 'RXX_gate'}:
new_circuit.cnot([n, history[ind]['which_qubits'][0]])
if gate_name == 'RXX_gate':
new_circuit.cnot([n, history[ind]['which_qubits'][1]])
elif gate_name in {'ry', 'RYY_gate'}:
new_circuit.cy([n, history[ind]['which_qubits'][0]])
if gate_name == 'RYY_gate':
new_circuit.cy([n, history[ind]['which_qubits'][1]])
elif gate_name in {'rz', 'RZZ_gate'}:
new_circuit.cz([n, history[ind]['which_qubits'][0]])
if gate_name == 'RZZ_gate':
new_circuit.cz([n, history[ind]['which_qubits'][1]])
new_circuit.h(n)
new_circuit._add_history(history[ind: len(history)], param)
return new_circuit
def control_rotation_gate_partial(self, ind, gate_name):
r"""计算传入的控制旋转门的偏导。
Args:
ind (int): 该门在本电路中的序号
gate_name (string): 门的名字
Return:
List: 用两个电路表示的该门的偏导
代码示例:
.. code-block:: python
import numpy as np
import paddle
from paddle_quantum.circuit import UAnsatz
cir = UAnsatz(2)
theta = paddle.to_tensor([np.pi, np.pi/2, np.pi/4], 'float64')
cir.rx(theta[0], 0)
cir.ryy(theta[1], [1, 0])
cir.crz(theta[2], [0, 1])
print(cir.control_rotation_gate_partial(2, 'crz')[0])
print(cir.control_rotation_gate_partial(2, 'crz')[1])
::
--Rx(3.142)----Ryy(1.57)-------------*------
| |
---------------Ryy(1.57)----z----Rz(0.785)--
|
------H-----------SDG-------*--------H------
--Rx(3.142)----Ryy(1.57)----z-------------*------
| | |
---------------Ryy(1.57)----|----z----Rz(0.785)--
| |
------H------------S--------*----*--------H------
"""
history, param = self._get_history()
assert ind <= len(history), "The index number should be less than or equal to %d" % len(history)
assert gate_name in {'crx', 'cry', 'crz'}, "Gate not supported."
assert gate_name == history[ind]['gate'], "Gate name incorrect."
n = self.n
new_circuit = [UAnsatz(n + 1) for j in range(2)]
for k in range(2):
new_circuit[k]._add_history(history[:ind], param)
new_circuit[k].h(n)
new_circuit[k].sdg(n) if k == 0 else new_circuit[k].s(n)
if k == 1:
new_circuit[k].cz([n, history[ind]['which_qubits'][1]])
if gate_name == 'crx':
new_circuit[k].cnot([n, history[ind]['which_qubits'][0]])
elif gate_name == 'cry':
new_circuit[k].cy([n, history[ind]['which_qubits'][0]])
elif gate_name == 'crz':
new_circuit[k].cz([n, history[ind]['which_qubits'][0]])
new_circuit[k].h(n)
new_circuit[k]._add_history(history[ind: len(history)], param)
return new_circuit
def u3_partial(self, ind_history, ind_gate):
r"""计算传入的 u3 门的一个参数的偏导。
Args:
ind_history (int): 该门在本电路中的序号
ind_gate (int): u3 门参数的 index,可以是 0 或 1 或 2
Return:
UAnsatz: 用电路表示的该门的一个参数的偏导
Note: 代码示例:
参考: https://cds.cern.ch/record/708846/files/0401178.pdf
.. code-block:: python
import numpy as np
import paddle
from paddle_quantum.circuit import UAnsatz
cir = UAnsatz(2)
theta = paddle.to_tensor([np.pi, np.pi/2, np.pi/4], 'float64')
cir.u3(theta[0], theta[1], theta[2], 0)
print(cir.u3_partial(0, 0))
::
------------z----U--
|
------------|-------
|
--H---SDG---*----H--
"""
history, param = self._get_history()
assert ind_history <= len(history), "The index number should be less than or equal to %d" % len(history)
assert ind_gate in {0, 1, 2}, "U3 gate has only three parameters, please choose from {0, 1, 2}"
assert history[ind_history]['gate'] == 'u', "Not a u3 gate."
n = self.n
new_circuit = UAnsatz(n + 1)
assert ind_gate in {0, 1, 2}, "ind must be in {0, 1, 2}"
new_circuit._add_history(history[:ind_history], param)
if ind_gate == 0:
new_circuit.h(n)
new_circuit.sdg(n)
new_circuit.cz([n, history[ind_history]['which_qubits'][0]])
new_circuit.h(n)
new_circuit._add_history(history[ind_history], param)
elif ind_gate == 1:
new_circuit.h(n)
new_circuit.sdg(n)
new_circuit.rz(self.__param[history[ind_history]['theta'][2]], history[ind_history]['which_qubits'][0])
new_circuit.cy([n, history[ind_history]['which_qubits'][0]])
new_circuit.ry(self.__param[history[ind_history]['theta'][0]], history[ind_history]['which_qubits'][0])
new_circuit.rz(self.__param[history[ind_history]['theta'][1]], history[ind_history]['which_qubits'][0])
new_circuit.h(n)
elif ind_gate == 2:
new_circuit.h(n)
new_circuit.sdg(n)
new_circuit.rz(self.__param[history[ind_history]['theta'][2]], history[ind_history]['which_qubits'][0])
new_circuit.ry(self.__param[history[ind_history]['theta'][0]], history[ind_history]['which_qubits'][0])
new_circuit.cz([n, history[ind_history]['which_qubits'][0]])
new_circuit.rz(self.__param[history[ind_history]['theta'][1]], history[ind_history]['which_qubits'][0])
new_circuit.h(n)
new_circuit._add_history(history[ind_history + 1: len(history)], param)
return new_circuit
def cu3_partial(self, ind_history, ind_gate):
r"""计算传入的 cu 门的一个参数的偏导。
Args: Args:
theta (Tensor): 3-qubit 通用门的参数,其维度为 ``(81, )`` ind_history (int): 该门在本电路中的序号
which_qubits(list): 作用的量子比特编号 ind_gate (int): cu 门参数的 index,可以是 0 或 1 或 2
Return:
UAnsatz: 用电路表示的该门的一个参数的偏导
代码示例: 代码示例:
...@@ -1038,46 +2047,246 @@ class UAnsatz: ...@@ -1038,46 +2047,246 @@ class UAnsatz:
import numpy as np import numpy as np
import paddle import paddle
from paddle_quantum.circuit import UAnsatz from paddle_quantum.circuit import UAnsatz
n = 3 cir = UAnsatz(2)
theta = paddle.to_tensor(np.ones(81)) theta = paddle.to_tensor([np.pi, np.pi/2, np.pi/4], 'float64')
cir = UAnsatz(n) cir.cu(theta[0], theta[1], theta[2], [0, 1])
cir.universal_3_qubit_gate(theta, [0, 1, 2]) print(cir.cu3_partial(0, 0)[0])
cir.run_state_vector() print(cir.cu3_partial(0, 0)[1])
print(cir.measure(shots = 0))
:: ::
{'000': 0.06697926831547105, '001': 0.13206788591381013, '010': 0.2806525391078656, '011': 0.13821526515701105, '100': 0.1390530116439897, '101': 0.004381404333075108, '110': 0.18403296778911565, '111': 0.05461765773966483} -----------------x--
|
------------z----U--
|
--H---SDG---*----H--
------------z---------x--
| |
------------|----z----U--
| |
--H----S----*----*----H--
""" """
assert len(which_qubits) == 3, "You should add this gate on three qubits" history, param = self._get_history()
assert len(theta) == 81, "The length of theta is supposed to be 81" assert ind_history <= len(history), "The index number should be less than or equal to %d" % len(history)
assert ind_gate in {0, 1, 2}, "CU gate has only three parameters, please choose from {0, 1, 2}"
assert history[ind_history]['gate'] == 'CU', "Not a CU gate."
psi = reshape(x=theta[: 60], shape=[4, 15]) n = self.n
phi = reshape(x=theta[60:], shape=[7, 3]) new_circuit = [UAnsatz(n + 1) for j in range(2)]
self.universal_2_qubit_gate(psi[0], which_qubits[:2]) assert ind_gate in {0, 1, 2}, "ind must be in {0, 1, 2}"
self.u3(phi[0][0], phi[0][1], phi[0][2], which_qubits[2]) for k in range(2):
new_circuit[k]._add_history(history[:ind_history], param)
if ind_gate == 0:
new_circuit[k].h(n)
new_circuit[k].sdg(n) if k == 0 else new_circuit[k].s(n)
if k == 1:
new_circuit[k].cz([n, history[ind_history]['which_qubits'][0]])
new_circuit[k].cz([n, history[ind_history]['which_qubits'][1]])
new_circuit[k].h(n)
new_circuit[k]._add_history([history[ind_history]], param)
elif ind_gate == 1:
new_circuit[k].h(n)
new_circuit[k].sdg(n) if k == 0 else new_circuit[k].s(n)
new_circuit[k].crz(self.__param[history[ind_history]['theta'][2]], history[ind_history]['which_qubits'])
if k == 1:
new_circuit[k].cz([n, history[ind_history]['which_qubits'][0]])
new_circuit[k].cy([n, history[ind_history]['which_qubits'][0]])
new_circuit[k].cry(self.__param[history[ind_history]['theta'][0]], history[ind_history]['which_qubits'])
new_circuit[k].crz(self.__param[history[ind_history]['theta'][1]], history[ind_history]['which_qubits'])
new_circuit[k].h(n)
elif ind_gate == 2:
new_circuit[k].h(n)
new_circuit[k].sdg(n) if k == 0 else new_circuit[k].s(n)
new_circuit[k].crz(self.__param[history[ind_history]['theta'][2]], history[ind_history]['which_qubits'])
new_circuit[k].cry(self.__param[history[ind_history]['theta'][0]], history[ind_history]['which_qubits'])
if k == 1:
new_circuit[k].cz([n, history[ind_history]['which_qubits'][0]])
new_circuit[k].cz([n, history[ind_history]['which_qubits'][0]])
new_circuit[k].crz(self.__param[history[ind_history]['theta'][1]], history[ind_history]['which_qubits'])
new_circuit[k].h(n)
new_circuit[k]._add_history(history[ind_history + 1: len(history)], param)
return new_circuit
def linear_combinations_gradient(self, H, shots=0):
r"""用 linear combination 的方法计算电路中所有需要训练的参数的梯度。损失函数默认为计算哈密顿量的期望值。
self.__u3qg_U(phi[1], which_qubits) Args:
H (list or Hamiltonian): 损失函数中用到的记录哈密顿量信息的列表或 ``Hamiltonian`` 类的对象
shots (int, optional): 测量次数;默认为 0,表示返回期望值的精确值,即测量无穷次后的期望值
self.universal_2_qubit_gate(psi[1], which_qubits[:2]) Return:
self.u3(phi[2][0], phi[2][1], phi[2][2], which_qubits[2]) Tensor: 该电路中所有需要训练的参数的梯度
self.__u3qg_V(phi[3], which_qubits) 代码示例:
self.universal_2_qubit_gate(psi[2], which_qubits[:2]) .. code-block:: python
self.u3(phi[4][0], phi[4][1], phi[4][2], which_qubits[2])
self.__u3qg_U(phi[5], which_qubits) import numpy as np
import paddle
from paddle_quantum.circuit import UAnsatz
self.universal_2_qubit_gate(psi[3], which_qubits[:2]) def U_theta(theta, N, D):
self.u3(phi[6][0], phi[6][1], phi[6][2], which_qubits[2]) cir = UAnsatz(N)
cir.complex_entangled_layer(theta[:D], D)
for i in range(N):
cir.ry(theta=theta[D][i][0], which_qubit=i)
cir.run_state_vector()
return cir
H = [[1.0, 'z0,z1']]
theta = paddle.uniform(shape=[2, 2, 3], dtype='float64', min=0.0, max=np.pi * 2)
theta.stop_gradient = False
circuit = U_theta(theta, 2, 1)
gradient = circuit.linear_combinations_gradient(H, shots=0)
print(gradient)
::
Tensor(shape=[8], dtype=float64, place=CPUPlace, stop_gradient=True,
[ 0. , -0.11321444, -0.22238044, 0. , 0.04151700, 0.44496212, -0.19465690, 0.96022600])
"""
history, param = self._get_history()
grad = []
if not isinstance(H, list):
H = H.pauli_str
H = copy.deepcopy(H)
for i in H:
i[1] += ',z' + str(self.n)
for i, history_i in enumerate(history):
if history_i['gate'] == 'rx' and self.__param[history_i['theta'][0]].stop_gradient is False:
new_circuit = self.pauli_rotation_gate_partial(i, 'rx')
if self.__run_mode == 'state_vector':
new_circuit.run_state_vector()
elif self.__run_mode == 'density_matrix':
new_circuit.run_density_matrix()
grad.append(paddle.to_tensor(new_circuit.expecval(H, shots), 'float64'))
elif history_i['gate'] == 'ry' and self.__param[history_i['theta'][0]].stop_gradient is False:
new_circuit = self.pauli_rotation_gate_partial(i, 'ry')
if self.__run_mode == 'state_vector':
new_circuit.run_state_vector()
elif self.__run_mode == 'density_matrix':
new_circuit.run_density_matrix()
grad.append(paddle.to_tensor(new_circuit.expecval(H, shots), 'float64'))
elif history_i['gate'] == 'rz' and self.__param[history_i['theta'][2]].stop_gradient is False:
new_circuit = self.pauli_rotation_gate_partial(i, 'rz')
if self.__run_mode == 'state_vector':
new_circuit.run_state_vector()
elif self.__run_mode == 'density_matrix':
new_circuit.run_density_matrix()
grad.append(paddle.to_tensor(new_circuit.expecval(H, shots), 'float64'))
elif history_i['gate'] == 'crx' and self.__param[history_i['theta'][0]].stop_gradient is False:
new_circuit = self.control_rotation_gate_partial(i, 'crx')
for k in new_circuit:
if self.__run_mode == 'state_vector':
k.run_state_vector()
elif self.__run_mode == 'density_matrix':
k.run_density_matrix()
gradient = paddle.to_tensor(np.mean([circuit.expecval(H, shots) for circuit in new_circuit]), 'float64')
grad.append(gradient)
elif history_i['gate'] == 'cry' and self.__param[history_i['theta'][0]].stop_gradient is False:
new_circuit = self.control_rotation_gate_partial(i, 'cry')
for k in new_circuit:
if self.__run_mode == 'state_vector':
k.run_state_vector()
elif self.__run_mode == 'density_matrix':
k.run_density_matrix()
gradient = paddle.to_tensor(np.mean([circuit.expecval(H, shots) for circuit in new_circuit]), 'float64')
grad.append(gradient)
elif history_i['gate'] == 'crz' and self.__param[history_i['theta'][2]].stop_gradient is False:
new_circuit = self.control_rotation_gate_partial(i, 'crz')
for k in new_circuit:
if self.__run_mode == 'state_vector':
k.run_state_vector()
elif self.__run_mode == 'density_matrix':
k.run_density_matrix()
gradient = paddle.to_tensor(np.mean([circuit.expecval(H, shots) for circuit in new_circuit]), 'float64')
grad.append(gradient)
elif history_i['gate'] == 'RXX_gate' and self.__param[history_i['theta'][0]].stop_gradient is False:
new_circuit = self.pauli_rotation_gate_partial(i, 'RXX_gate')
if self.__run_mode == 'state_vector':
new_circuit.run_state_vector()
elif self.__run_mode == 'density_matrix':
new_circuit.run_density_matrix()
grad.append(paddle.to_tensor(new_circuit.expecval(H, shots), 'float64'))
elif history_i['gate'] == 'RYY_gate' and self.__param[history_i['theta'][0]].stop_gradient is False:
new_circuit = self.pauli_rotation_gate_partial(i, 'RYY_gate')
if self.__run_mode == 'state_vector':
new_circuit.run_state_vector()
elif self.__run_mode == 'density_matrix':
new_circuit.run_density_matrix()
grad.append(paddle.to_tensor(new_circuit.expecval(H, shots), 'float64'))
elif history_i['gate'] == 'RZZ_gate' and self.__param[history_i['theta'][0]].stop_gradient is False:
new_circuit = self.pauli_rotation_gate_partial(i, 'RZZ_gate')
if self.__run_mode == 'state_vector':
new_circuit.run_state_vector()
elif self.__run_mode == 'density_matrix':
new_circuit.run_density_matrix()
grad.append(paddle.to_tensor(new_circuit.expecval(H, shots), 'float64'))
elif history_i['gate'] == 'u':
if not self.__param[history_i['theta'][0]].stop_gradient:
new_circuit = self.u3_partial(i, 0)
if self.__run_mode == 'state_vector':
new_circuit.run_state_vector()
elif self.__run_mode == 'density_matrix':
new_circuit.run_density_matrix()
grad.append(paddle.to_tensor(new_circuit.expecval(H, shots), 'float64'))
if not self.__param[history_i['theta'][1]].stop_gradient:
new_circuit = self.u3_partial(i, 1)
if self.__run_mode == 'state_vector':
new_circuit.run_state_vector()
elif self.__run_mode == 'density_matrix':
new_circuit.run_density_matrix()
grad.append(paddle.to_tensor(new_circuit.expecval(H, shots), 'float64'))
if not self.__param[history_i['theta'][2]].stop_gradient:
new_circuit = self.u3_partial(i, 2)
if self.__run_mode == 'state_vector':
new_circuit.run_state_vector()
elif self.__run_mode == 'density_matrix':
new_circuit.run_density_matrix()
grad.append(paddle.to_tensor(new_circuit.expecval(H, shots), 'float64'))
elif history_i['gate'] == 'CU':
if not self.__param[history_i['theta'][0]].stop_gradient:
new_circuit = self.cu3_partial(i, 0)
for k in new_circuit:
if self.__run_mode == 'state_vector':
k.run_state_vector()
elif self.__run_mode == 'density_matrix':
k.run_density_matrix()
gradient = paddle.to_tensor(np.mean([circuit.expecval(H, shots) for circuit in new_circuit]), 'float64')
grad.append(gradient)
if not self.__param[history_i['theta'][1]].stop_gradient:
new_circuit = self.cu3_partial(i, 1)
for k in new_circuit:
if self.__run_mode == 'state_vector':
k.run_state_vector()
elif self.__run_mode == 'density_matrix':
k.run_density_matrix()
gradient = paddle.to_tensor(np.mean([circuit.expecval(H, shots) for circuit in new_circuit]), 'float64')
grad.append(gradient)
if not self.__param[history_i['theta'][2]].stop_gradient:
new_circuit = self.cu3_partial(i, 2)
for k in new_circuit:
if self.__run_mode == 'state_vector':
k.run_state_vector()
elif self.__run_mode == 'density_matrix':
k.run_density_matrix()
gradient = paddle.to_tensor(np.mean([circuit.expecval(H, shots) for circuit in new_circuit]), 'float64')
grad.append(gradient)
grad = paddle.concat(grad)
return grad
""" """
Measurements Measurements
""" """
def __process_string(self, s, which_qubits): def __process_string(self, s, which_qubits):
r""" r"""该函数基于 which_qubits 返回 s 的一部分
This functions return part of string s baesd on which_qubits This functions return part of string s baesd on which_qubits
If s = 'abcdefg', which_qubits = [0,2,5], then it returns 'acf' If s = 'abcdefg', which_qubits = [0,2,5], then it returns 'acf'
...@@ -1088,9 +2297,10 @@ class UAnsatz: ...@@ -1088,9 +2297,10 @@ class UAnsatz:
return new_s return new_s
def __process_similiar(self, result): def __process_similiar(self, result):
r""" r"""该函数基于相同的键合并值。
This functions merges values based on identical keys. This functions merges values based on identical keys.
If result = [('00', 10), ('01', 20), ('11', 30), ('11', 40), ('11', 50), ('00', 60)], then it returns {'00': 70, '01': 20, '11': 120} If result = [('00', 10), ('01', 20), ('11', 30), ('11', 40), ('11', 50), ('00', 60)],
then it returns {'00': 70, '01': 20, '11': 120}
Note: Note:
这是内部函数,你并不需要直接调用到该函数。 这是内部函数,你并不需要直接调用到该函数。
...@@ -1148,14 +2358,14 @@ class UAnsatz: ...@@ -1148,14 +2358,14 @@ class UAnsatz:
which_qubits (list, optional): 要测量的qubit的编号,默认全都测量 which_qubits (list, optional): 要测量的qubit的编号,默认全都测量
shots (int, optional): 该量子电路输出的量子态的测量次数,默认为 1024 次;若为 0,则返回测量结果的精确概率分布 shots (int, optional): 该量子电路输出的量子态的测量次数,默认为 1024 次;若为 0,则返回测量结果的精确概率分布
plot (bool, optional): 是否绘制测量结果图,默认为 ``False`` ,即不绘制 plot (bool, optional): 是否绘制测量结果图,默认为 ``False`` ,即不绘制
Returns: Returns:
dict: 测量的结果 dict: 测量的结果
代码示例: 代码示例:
.. code-block:: python .. code-block:: python
import paddle import paddle
from paddle_quantum.circuit import UAnsatz from paddle_quantum.circuit import UAnsatz
cir = UAnsatz(2) cir = UAnsatz(2)
...@@ -1184,9 +2394,9 @@ class UAnsatz: ...@@ -1184,9 +2394,9 @@ class UAnsatz:
The probability distribution of measurement results on qubit 1 is {'0': 0.4999999999999999, '1': 0.4999999999999999} The probability distribution of measurement results on qubit 1 is {'0': 0.4999999999999999, '1': 0.4999999999999999}
""" """
if self.__run_state == 'state_vector': if self.__run_mode == 'state_vector':
state = self.__state state = self.__state
elif self.__run_state == 'density_matrix': elif self.__run_mode == 'density_matrix':
# Take the diagonal of the density matrix as a probability distribution # Take the diagonal of the density matrix as a probability distribution
diag = np.diag(self.__state.numpy()) diag = np.diag(self.__state.numpy())
state = paddle.to_tensor(np.sqrt(diag)) state = paddle.to_tensor(np.sqrt(diag))
...@@ -1217,47 +2427,120 @@ class UAnsatz: ...@@ -1217,47 +2427,120 @@ class UAnsatz:
return result if not plot else self.__measure_hist(result, which_qubits, shots) return result if not plot else self.__measure_hist(result, which_qubits, shots)
def expecval(self, H): def measure_in_bell_basis(self, which_qubits, shots=0):
r"""对量子电路输出的量子态进行贝尔基测量。
Args:
which_qubits(list): 要测量的量子比特
shots(int): 测量的采样次数,默认为0,表示计算解析解
Returns:
list: 测量得到四个贝尔基的概率
"""
assert which_qubits[0] != which_qubits[1], "You have to measure two different qubits."
which_qubits.sort()
i, j = which_qubits
qubit_num = self.n
input_state = self.__state
mode = self.__run_mode
cir = UAnsatz(qubit_num)
cir.cnot([i, j])
cir.h(i)
if mode == 'state_vector':
output_state = cir.run_state_vector(input_state).numpy()
elif mode == 'density_matrix':
output_density_matrix = cir.run_density_matrix(input_state).numpy()
output_state = np.sqrt(np.diag(output_density_matrix))
else:
raise ValueError("Can't recognize the mode of quantum state.")
prob_amplitude = np.abs(output_state).tolist()
prob_amplitude = [item ** 2 for item in prob_amplitude]
prob_array = [0] * 4
for i in range(2 ** qubit_num):
binary = bin(i)[2:]
binary = '0' * (qubit_num - len(binary)) + binary
target_qubits = str()
for qubit_idx in which_qubits:
target_qubits += binary[qubit_idx]
prob_array[int(target_qubits, base=2)] += prob_amplitude[i]
if shots == 0:
result = prob_array
else:
result = [0] * 4
samples = np.random.choice(list(range(4)), shots, p=prob_array)
for item in samples:
result[item] += 1
result = [item / shots for item in result]
return result
def expecval(self, H, shots=0):
r"""量子电路输出的量子态关于可观测量 H 的期望值。 r"""量子电路输出的量子态关于可观测量 H 的期望值。
Hint: Hint:
如果想输入的可观测量的矩阵为 :math:`0.7Z\otimes X\otimes I+0.2I\otimes Z\otimes I` 。则 ``H`` 应为 ``[[0.7, 'z0,x1'], [0.2, 'z1']]`` 。 如果想输入的可观测量的矩阵为 :math:`0.7Z\otimes X\otimes I+0.2I\otimes Z\otimes I` ,
则 ``H`` 的 ``list`` 形式为 ``[[0.7, 'Z0, X1'], [0.2, 'Z1']]`` 。
Args: Args:
H (list): 可观测量的相关信息 H (Hamiltonian or list): 可观测量的相关信息
shots (int, optional): 测量次数;默认为 0,表示返回期望值的精确值,即测量无穷次后的期望值
Returns: Returns:
Tensor: 量子电路输出的量子态关于 H 的期望值 Tensor: 量子电路输出的量子态关于 ``H`` 的期望值
代码示例: 代码示例:
.. code-block:: python .. code-block:: python
import numpy as np
import paddle import paddle
from paddle_quantum.circuit import UAnsatz from paddle_quantum.circuit import UAnsatz
n = 5 n = 5
experiment_shots = 2**10
H_info = [[0.1, 'x1'], [0.2, 'y0,z4']] H_info = [[0.1, 'x1'], [0.2, 'y0,z4']]
theta = paddle.ones([3], dtype='float64') theta = paddle.ones([3], dtype='float64')
cir = UAnsatz(n) cir = UAnsatz(n)
cir.rx(theta[0], 0) cir.rx(theta[0], 0)
cir.rz(theta[1], 1) cir.rz(theta[1], 1)
cir.rx(theta[2], 2) cir.rx(theta[2], 2)
cir.run_state_vector() cir.run_state_vector()
expect_value = cir.expecval(H_info).numpy()
print(f'Calculated expectation value of {H_info} is {expect_value}')
:: result_1 = cir.expecval(H_info, shots = experiment_shots).numpy()
result_2 = cir.expecval(H_info, shots = 0).numpy()
print(f'The expectation value obtained by {experiment_shots} measurements is {result_1}')
print(f'The accurate expectation value of H is {result_2}')
Calculated expectation value of [[0.1, 'x1'], [0.2, 'y0,z4']] is [-0.1682942] ::
The expectation value obtained by 1024 measurements is [-0.16328125]
The accurate expectation value of H is [-0.1682942]
""" """
if self.__run_state == 'state_vector': expec_val = 0
return real(vec_expecval(H, self.__state)) if not isinstance(H, list):
elif self.__run_state == 'density_matrix': H = H.pauli_str
state = self.__state if shots == 0:
H_mat = paddle.to_tensor(pauli_str_to_matrix(H, self.n)) if self.__run_mode == 'state_vector':
return real(trace(matmul(state, H_mat))) expec_val = real(vec_expecval(H, self.__state))
elif self.__run_mode == 'density_matrix':
state = self.__state
H_mat = paddle.to_tensor(pauli_str_to_matrix(H, self.n))
expec_val = real(trace(matmul(state, H_mat)))
else:
# Raise error
raise ValueError("no state for measurement; please run the circuit first")
else: else:
# Raise error for term in H:
raise ValueError("no state for measurement; please run the circuit first") expec_val += term[0] * _local_H_prob(self, term[1], shots=shots)
expec_val = paddle.to_tensor(expec_val, 'float64')
return expec_val
""" """
Circuit Templates Circuit Templates
...@@ -1269,7 +2552,7 @@ class UAnsatz: ...@@ -1269,7 +2552,7 @@ class UAnsatz:
代码示例: 代码示例:
.. code-block:: python .. code-block:: python
import paddle import paddle
from paddle_quantum.circuit import UAnsatz from paddle_quantum.circuit import UAnsatz
cir = UAnsatz(2) cir = UAnsatz(2)
...@@ -1280,7 +2563,9 @@ class UAnsatz: ...@@ -1280,7 +2563,9 @@ class UAnsatz:
:: ::
The probability distribution of measurement results on both qubits is {'00': 0.2499999999999999, '01': 0.2499999999999999, '10': 0.2499999999999999, '11': 0.2499999999999999} The probability distribution of measurement results on both qubits is
{'00': 0.2499999999999999, '01': 0.2499999999999999,
'10': 0.2499999999999999, '11': 0.2499999999999999}
""" """
for i in range(self.n): for i in range(self.n):
self.h(i) self.h(i)
...@@ -1291,7 +2576,7 @@ class UAnsatz: ...@@ -1291,7 +2576,7 @@ class UAnsatz:
代码示例: 代码示例:
.. code-block:: python .. code-block:: python
import paddle import paddle
from paddle_quantum.circuit import UAnsatz from paddle_quantum.circuit import UAnsatz
cir = UAnsatz(2) cir = UAnsatz(2)
...@@ -1302,7 +2587,9 @@ class UAnsatz: ...@@ -1302,7 +2587,9 @@ class UAnsatz:
:: ::
The probability distribution of measurement results on both qubits is {'00': 0.7285533905932737, '01': 0.12500000000000003, '10': 0.12500000000000003, '11': 0.021446609406726238} The probability distribution of measurement results on both qubits is
{'00': 0.7285533905932737, '01': 0.12500000000000003,
'10': 0.12500000000000003, '11': 0.021446609406726238}
""" """
_theta = paddle.to_tensor(np.array([np.pi / 4])) # Used in fixed Ry gate _theta = paddle.to_tensor(np.array([np.pi / 4])) # Used in fixed Ry gate
for i in range(self.n): for i in range(self.n):
...@@ -1312,12 +2599,12 @@ class UAnsatz: ...@@ -1312,12 +2599,12 @@ class UAnsatz:
r"""添加 ``depth`` 层包含 Ry 门,Rz 门和 CNOT 门的线性纠缠层。 r"""添加 ``depth`` 层包含 Ry 门,Rz 门和 CNOT 门的线性纠缠层。
Attention: Attention:
``theta`` 的维度为 ``(depth, n, 2)`` ,最低维内容为对应的 ``ry`` 和 ``rz`` 的参数。 ``theta`` 的维度为 ``(depth, n, 2)`` ,最低维内容为对应的 ``ry`` 和 ``rz`` 的参数, ``n`` 为作用的量子比特数量
Args: Args:
theta (Tensor): Ry 门和 Rz 门的旋转角度 theta (Tensor): Ry 门和 Rz 门的旋转角度
depth (int): 纠缠层的深度 depth (int): 纠缠层的深度
which_qubits(list): 作用的量子比特编号 which_qubits (list): 作用的量子比特编号
代码示例: 代码示例:
...@@ -1327,16 +2614,25 @@ class UAnsatz: ...@@ -1327,16 +2614,25 @@ class UAnsatz:
from paddle_quantum.circuit import UAnsatz from paddle_quantum.circuit import UAnsatz
n = 2 n = 2
DEPTH = 3 DEPTH = 3
theta = paddle.ones([DEPTH, n, 2], dtype='float64') theta = paddle.ones([DEPTH, 2, 2], dtype='float64')
cir = UAnsatz(n) cir = UAnsatz(n)
cir.linear_entangled_layer(theta, DEPTH, [0, 1]) cir.linear_entangled_layer(theta, DEPTH, [0, 1])
cir.run_state_vector() cir.run_state_vector()
print(cir.measure(shots = 0)) result = cir.measure(shots = 0)
print(f"The probability distribution of measurement results on both qubits is {result}")
:: ::
{'00': 0.646611169077063, '01': 0.06790630495474384, '10': 0.19073671025717626, '11': 0.09474581571101756} The probability distribution of measurement results on both qubits is
{'00': 0.646611169077063, '01': 0.06790630495474384,
'10': 0.19073671025717626, '11': 0.09474581571101756}
""" """
# reformat 1D theta list
theta_flat = paddle.flatten(theta)
width = len(which_qubits) if which_qubits is not None else self.n
assert len(theta_flat) == depth * width * 2, 'the size of theta is not right'
theta = paddle.reshape(theta_flat, [depth, width, 2])
assert self.n > 1, 'you need at least 2 qubits' assert self.n > 1, 'you need at least 2 qubits'
assert len(theta.shape) == 3, 'the shape of theta is not right' assert len(theta.shape) == 3, 'the shape of theta is not right'
assert theta.shape[2] == 2, 'the shape of theta is not right' assert theta.shape[2] == 2, 'the shape of theta is not right'
...@@ -1363,12 +2659,12 @@ class UAnsatz: ...@@ -1363,12 +2659,12 @@ class UAnsatz:
这一层量子门的数学表示形式为实数酉矩阵。 这一层量子门的数学表示形式为实数酉矩阵。
Attention: Attention:
``theta`` 的维度为 ``(depth, n, 1)`` ``theta`` 的维度为 ``(depth, n, 1)``, ``n`` 为作用的量子比特数量
Args: Args:
theta (Tensor): Ry 门的旋转角度 theta (Tensor): Ry 门的旋转角度
depth (int): 纠缠层的深度 depth (int): 纠缠层的深度
which_qubits(list): 作用的量子比特编号 which_qubits (list): 作用的量子比特编号
代码示例: 代码示例:
...@@ -1378,16 +2674,25 @@ class UAnsatz: ...@@ -1378,16 +2674,25 @@ class UAnsatz:
from paddle_quantum.circuit import UAnsatz from paddle_quantum.circuit import UAnsatz
n = 2 n = 2
DEPTH = 3 DEPTH = 3
theta = paddle.ones([DEPTH, n, 1], dtype='float64') theta = paddle.ones([DEPTH, 2, 1], dtype='float64')
cir = UAnsatz(n) cir = UAnsatz(n)
cir.real_entangled_layer(paddle.to_tensor(theta), DEPTH, [0, 1]) cir.real_entangled_layer(paddle.to_tensor(theta), DEPTH, [0, 1])
cir.run_state_vector() cir.run_state_vector()
print(cir.measure(shots = 0)) result = cir.measure(shots = 0)
print(f"The probability distribution of measurement results on both qubits is {result}")
:: ::
{'00': 2.52129874867343e-05, '01': 0.295456784923382, '10': 0.7045028818254718, '11': 1.5120263659845063e-05} The probability distribution of measurement results on both qubits is
{'00': 2.52129874867343e-05, '01': 0.295456784923382,
'10': 0.7045028818254718, '11': 1.5120263659845063e-05}
""" """
# reformat 1D theta list
theta_flat = paddle.flatten(theta)
width = len(which_qubits) if which_qubits is not None else self.n
assert len(theta_flat) == depth * width, 'the size of theta is not right'
theta = paddle.reshape(theta_flat, [depth, width, 1])
assert self.n > 1, 'you need at least 2 qubits' assert self.n > 1, 'you need at least 2 qubits'
assert len(theta.shape) == 3, 'the shape of theta is not right' assert len(theta.shape) == 3, 'the shape of theta is not right'
assert theta.shape[2] == 1, 'the shape of theta is not right' assert theta.shape[2] == 1, 'the shape of theta is not right'
...@@ -1409,33 +2714,42 @@ class UAnsatz: ...@@ -1409,33 +2714,42 @@ class UAnsatz:
Note: Note:
这一层量子门的数学表示形式为复数酉矩阵。 这一层量子门的数学表示形式为复数酉矩阵。
Attention: Attention:
``theta`` 的维度为 ``(depth, n, 3)`` ,最低维内容为对应的 ``u3`` 的参数 ``(theta, phi, lam)`` ``theta`` 的维度为 ``(depth, n, 3)`` ,最低维内容为对应的 ``u3`` 的参数 ``(theta, phi, lam)``, ``n`` 为作用的量子比特数量
Args: Args:
theta (Tensor): U3 门的旋转角度 theta (Tensor): U3 门的旋转角度
depth (int): 纠缠层的深度 depth (int): 纠缠层的深度
which_qubits(list): 作用的量子比特编号 which_qubits (list): 作用的量子比特编号
代码示例: 代码示例:
.. code-block:: python .. code-block:: python
import paddle import paddle
from paddle_quantum.circuit import UAnsatz from paddle_quantum.circuit import UAnsatz
n = 2 n = 2
DEPTH = 3 DEPTH = 3
theta = paddle.ones([DEPTH, n, 3], dtype='float64') theta = paddle.ones([DEPTH, 2, 3], dtype='float64')
cir = UAnsatz(n) cir = UAnsatz(n)
cir.complex_entangled_layer(paddle.to_tensor(theta), DEPTH, [0, 1]) cir.complex_entangled_layer(paddle.to_tensor(theta), DEPTH, [0, 1])
cir.run_state_vector() cir.run_state_vector()
print(cir.measure(shots = 0)) result = cir.measure(shots = 0)
print(f"The probability distribution of measurement results on both qubits is {result}")
:: ::
{'00': 0.15032627279218896, '01': 0.564191201239618, '10': 0.03285998070292556, '11': 0.25262254526526823} The probability distribution of measurement results on both qubits is
{'00': 0.15032627279218896, '01': 0.564191201239618,
'10': 0.03285998070292556, '11': 0.25262254526526823}
""" """
# reformat 1D theta list
theta_flat = paddle.flatten(theta)
width = len(which_qubits) if which_qubits is not None else self.n
assert len(theta_flat) == depth * width * 3, 'the size of theta is not right'
theta = paddle.reshape(theta_flat, [depth, width, 3])
assert self.n > 1, 'you need at least 2 qubits' assert self.n > 1, 'you need at least 2 qubits'
assert len(theta.shape) == 3, 'the shape of theta is not right' assert len(theta.shape) == 3, 'the shape of theta is not right'
assert theta.shape[2] == 3, 'the shape of theta is not right' assert theta.shape[2] == 3, 'the shape of theta is not right'
...@@ -1488,24 +2802,26 @@ class UAnsatz: ...@@ -1488,24 +2802,26 @@ class UAnsatz:
def __add_real_layer(self, theta, position): def __add_real_layer(self, theta, position):
r""" r"""
Add a real layer on the circuit. theta is a two dimensional tensor. position is the qubit range the layer needs to cover Add a real layer on the circuit. theta is a two dimensional tensor.
position is the qubit range the layer needs to cover.
Note: Note:
这是内部函数,你并不需要直接调用到该函数。 这是内部函数,你并不需要直接调用到该函数。
""" """
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, \
'the shape of theta is not right' 'the shape of theta is not right'
for i in range(position[0], position[1], 2): for i in range(position[0], position[1], 2):
self.__add_real_block(theta[int((i - position[0]) / 2)], [i, i + 1]) self.__add_real_block(theta[int((i - position[0]) / 2)], [i, i + 1])
def __add_complex_layer(self, theta, position): def __add_complex_layer(self, theta, position):
r""" r"""
Add a complex layer on the circuit. theta is a two dimensional tensor. position is the qubit range the layer needs to cover Add a complex layer on the circuit. theta is a two dimensional tensor.
position is the qubit range the layer needs to cover.
Note: Note:
这是内部函数,你并不需要直接调用到该函数。 这是内部函数,你并不需要直接调用到该函数。
""" """
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, \
'the shape of theta is not right' 'the shape of theta is not right'
for i in range(position[0], position[1], 2): for i in range(position[0], position[1], 2):
self.__add_complex_block(theta[int((i - position[0]) / 2)], [i, i + 1]) self.__add_complex_block(theta[int((i - position[0]) / 2)], [i, i + 1])
...@@ -1515,18 +2831,18 @@ class UAnsatz: ...@@ -1515,18 +2831,18 @@ class UAnsatz:
Note: Note:
这一层量子门的数学表示形式为实数酉矩阵。 这一层量子门的数学表示形式为实数酉矩阵。
Attention: Attention:
``theta`` 的维度为 ``(depth, n-1, 4)`` 。 ``theta`` 的维度为 ``(depth, n-1, 4)`` 。
Args: Args:
theta(Tensor): Ry 门的旋转角度 theta (Tensor): Ry 门的旋转角度
depth(int): 纠缠层的深度 depth (int): 纠缠层的深度
代码示例: 代码示例:
.. code-block:: python .. code-block:: python
import paddle import paddle
from paddle_quantum.circuit import UAnsatz from paddle_quantum.circuit import UAnsatz
n = 4 n = 4
...@@ -1536,7 +2852,7 @@ class UAnsatz: ...@@ -1536,7 +2852,7 @@ class UAnsatz:
cir.real_block_layer(paddle.to_tensor(theta), DEPTH) cir.real_block_layer(paddle.to_tensor(theta), DEPTH)
cir.run_density_matrix() cir.run_density_matrix()
print(cir.measure(shots = 0, which_qubits = [0])) print(cir.measure(shots = 0, which_qubits = [0]))
:: ::
{'0': 0.9646724056906162, '1': 0.035327594309385896} {'0': 0.9646724056906162, '1': 0.035327594309385896}
...@@ -1573,7 +2889,7 @@ class UAnsatz: ...@@ -1573,7 +2889,7 @@ class UAnsatz:
代码示例: 代码示例:
.. code-block:: python .. code-block:: python
import paddle import paddle
from paddle_quantum.circuit import UAnsatz from paddle_quantum.circuit import UAnsatz
n = 4 n = 4
...@@ -1583,7 +2899,7 @@ class UAnsatz: ...@@ -1583,7 +2899,7 @@ class UAnsatz:
cir.complex_block_layer(paddle.to_tensor(theta), DEPTH) cir.complex_block_layer(paddle.to_tensor(theta), DEPTH)
cir.run_density_matrix() cir.run_density_matrix()
print(cir.measure(shots = 0, which_qubits = [0])) print(cir.measure(shots = 0, which_qubits = [0]))
:: ::
{'0': 0.5271554811768046, '1': 0.4728445188231988} {'0': 0.5271554811768046, '1': 0.4728445188231988}
...@@ -1604,6 +2920,155 @@ class UAnsatz: ...@@ -1604,6 +2920,155 @@ class UAnsatz:
self.__add_complex_layer(theta[i][:int((self.n - 1) / 2)], [0, self.n - 2]) self.__add_complex_layer(theta[i][:int((self.n - 1) / 2)], [0, self.n - 2])
self.__add_complex_layer(theta[i][int((self.n - 1) / 2):], [1, self.n - 1]) self.__add_complex_layer(theta[i][int((self.n - 1) / 2):], [1, self.n - 1])
def finite_difference_gradient(self, H, delta, shots=0):
r"""用差分法估计电路中参数的梯度。损失函数默认为计算哈密顿量的期望值。
Args:
H (list or Hamiltonian): 记录哈密顿量信息的列表或 ``Hamiltonian`` 类的对象
delta (float): 差分法中的 delta
shots (int, optional): 测量次数;默认为 0,表示返回期望值的精确值,即测量无穷次后的期望值
Returns:
Tensor: 电路中所有可训练参数的梯度
代码示例:
.. code-block:: python
import paddle
import numpy as np
from paddle_quantum.circuit import UAnsatz
H = [[1.0, 'z0,z1']]
theta = paddle.to_tensor(np.array([6.186, 5.387, 1.603, 1.998]), stop_gradient=False)
cir = UAnsatz(2)
cir.ry(theta[0], 0)
cir.ry(theta[1], 1)
cir.cnot([0, 1])
cir.cnot([1, 0])
cir.ry(theta[2], 0)
cir.ry(theta[3], 1)
cir.run_state_vector()
gradients = cir.finite_difference_gradient(H, delta=0.01, shots=0)
print(gradients)
::
Tensor(shape=[4], dtype=float64, place=CPUPlace, stop_gradient=False,
[0.01951135, 0.56594233, 0.37991172, 0.35337436])
"""
grad = []
for i, theta_i in enumerate(self.__param):
if theta_i.stop_gradient:
continue
self.__param[i] += delta / 2
self.run_state_vector()
expec_plu = self.expecval(H, shots)
self.__param[i] -= delta
self.run_state_vector()
expec_min = self.expecval(H, shots)
self.__param[i] += delta / 2
self.run_state_vector()
grad.append(paddle.to_tensor((expec_plu - expec_min) / delta, 'float64'))
self.__param[i].stop_gradient = False
grad = paddle.concat(grad)
grad.stop_gradient = False
return grad
def param_shift_gradient(self, H, shots=0):
r"""用 parameter-shift 方法计算电路中参数的梯度。损失函数默认为计算哈密顿量的期望值。
Args:
H (list or Hamiltonian): 记录哈密顿量信息的列表或 ``Hamiltonian`` 类的对象
shots (int, optional): 测量次数;默认为 0,表示返回期望值的精确值,即测量无穷次后的期望值
Returns:
Tensor: 电路中所有可训练参数的梯度
代码示例:
.. code-block:: python
import numpy as np
import paddle
from paddle_quantum.circuit import UAnsatz
H = [[1.0, 'z0,z1']]
theta = paddle.to_tensor(np.array([6.186, 5.387, 1.603, 1.998]), stop_gradient=False)
cir = UAnsatz(2)
cir.ry(theta[0], 0)
cir.ry(theta[1], 1)
cir.cnot([0, 1])
cir.cnot([1, 0])
cir.ry(theta[2], 0)
cir.ry(theta[3], 1)
cir.run_state_vector()
gradients = cir.param_shift_gradient(H, shots=0)
print(gradients)
::
Tensor(shape=[4], dtype=float64, place=CPUPlace, stop_gradient=False,
[0.01951143, 0.56594470, 0.37991331, 0.35337584])
"""
r = 1 / 2
grad = []
for i, theta_i in enumerate(self.__param):
if theta_i.stop_gradient:
continue
self.__param[i] += np.pi / (4 * r)
self.run_state_vector()
f_plu = self.expecval(H, shots)
self.__param[i] -= 2 * np.pi / (4 * r)
self.run_state_vector()
f_min = self.expecval(H, shots)
self.__param[i] += np.pi / (4 * r)
self.run_state_vector()
grad.append(paddle.to_tensor(r * (f_plu - f_min), 'float64'))
self.__param[i].stop_gradient = False
grad = paddle.concat(grad)
grad.stop_gradient = False
return grad
def get_param(self):
r"""得到电路参数列表中的可训练的参数。
Returns:
list: 电路中所有可训练的参数
"""
param = []
for theta in self.__param:
if not theta.stop_gradient:
param.append(theta)
assert len(param) != 0, "circuit does not contain trainable parameters"
param = paddle.concat(param)
param.stop_gradient = False
return param
def update_param(self, new_param):
r"""用得到的新参数列表更新电路参数列表中的可训练的参数。
Args:
new_param (list): 新的参数列表
Returns:
Tensor: 更新后电路中所有训练的参数
"""
j = 0
for i in range(len(self.__param)):
if not self.__param[i].stop_gradient:
self.__param[i] = paddle.to_tensor(new_param[j], 'float64')
self.__param[i].stop_gradient = False
j += 1
self.run_state_vector()
return self.__param
""" """
Channels Channels
""" """
...@@ -1613,11 +3078,19 @@ class UAnsatz: ...@@ -1613,11 +3078,19 @@ class UAnsatz:
r"""添加振幅阻尼信道。 r"""添加振幅阻尼信道。
其 Kraus 算符为: 其 Kraus 算符为:
.. math:: .. math::
E_0 = \begin{bmatrix} 1 & 0 \\ 0 & \sqrt{1-\gamma} \end{bmatrix}, E_0 =
E_1 = \begin{bmatrix} 0 & \sqrt{\gamma} \\ 0 & 0 \end{bmatrix}. \begin{bmatrix}
1 & 0 \\
0 & \sqrt{1-\gamma}
\end{bmatrix},
E_1 =
\begin{bmatrix}
0 & \sqrt{\gamma} \\
0 & 0
\end{bmatrix}.
Args: Args:
gamma (float): 减振概率,其值应该在 :math:`[0, 1]` 区间内 gamma (float): 减振概率,其值应该在 :math:`[0, 1]` 区间内
...@@ -1659,7 +3132,11 @@ class UAnsatz: ...@@ -1659,7 +3132,11 @@ class UAnsatz:
.. math:: .. math::
E_0 = \sqrt{p} \begin{bmatrix} 1 & 0 \\ 0 & \sqrt{1-\gamma} \end{bmatrix}, E_0 = \sqrt{p}
\begin{bmatrix}
1 & 0 \\
0 & \sqrt{1-\gamma}
\end{bmatrix},
E_1 = \sqrt{p} \begin{bmatrix} 0 & \sqrt{\gamma} \\ 0 & 0 \end{bmatrix},\\ E_1 = \sqrt{p} \begin{bmatrix} 0 & \sqrt{\gamma} \\ 0 & 0 \end{bmatrix},\\
E_2 = \sqrt{1-p} \begin{bmatrix} \sqrt{1-\gamma} & 0 \\ 0 & 1 \end{bmatrix}, E_2 = \sqrt{1-p} \begin{bmatrix} \sqrt{1-\gamma} & 0 \\ 0 & 1 \end{bmatrix},
E_3 = \sqrt{1-p} \begin{bmatrix} 0 & 0 \\ \sqrt{\gamma} & 0 \end{bmatrix}. E_3 = \sqrt{1-p} \begin{bmatrix} 0 & 0 \\ \sqrt{\gamma} & 0 \end{bmatrix}.
...@@ -1709,8 +3186,16 @@ class UAnsatz: ...@@ -1709,8 +3186,16 @@ class UAnsatz:
.. math:: .. math::
E_0 = \begin{bmatrix} 1 & 0 \\ 0 & \sqrt{1-\gamma} \end{bmatrix}, E_0 =
E_1 = \begin{bmatrix} 0 & 0 \\ 0 & \sqrt{\gamma} \end{bmatrix}. \begin{bmatrix}
1 & 0 \\
0 & \sqrt{1-\gamma}
\end{bmatrix},
E_1 =
\begin{bmatrix}
0 & 0 \\
0 & \sqrt{\gamma}
\end{bmatrix}.
Args: Args:
gamma (float): phase damping 信道的参数,其值应该在 :math:`[0, 1]` 区间内 gamma (float): phase damping 信道的参数,其值应该在 :math:`[0, 1]` 区间内
...@@ -1782,7 +3267,7 @@ class UAnsatz: ...@@ -1782,7 +3267,7 @@ class UAnsatz:
""" """
assert 0 <= p <= 1, 'the probability p of a bit flip should be in range [0, 1]' assert 0 <= p <= 1, 'the probability p of a bit flip should be in range [0, 1]'
e0 = paddle.to_tensor([[np.sqrt(1-p), 0], [0, np.sqrt(1-p)]], dtype='complex128') e0 = paddle.to_tensor([[np.sqrt(1 - p), 0], [0, np.sqrt(1 - p)]], dtype='complex128')
e1 = paddle.to_tensor([[0, np.sqrt(p)], [np.sqrt(p), 0]], dtype='complex128') e1 = paddle.to_tensor([[0, np.sqrt(p)], [np.sqrt(p), 0]], dtype='complex128')
return [e0, e1] return [e0, e1]
...@@ -1825,7 +3310,7 @@ class UAnsatz: ...@@ -1825,7 +3310,7 @@ class UAnsatz:
""" """
assert 0 <= p <= 1, 'the probability p of a phase flip should be in range [0, 1]' assert 0 <= p <= 1, 'the probability p of a phase flip should be in range [0, 1]'
e0 = paddle.to_tensor([[np.sqrt(1-p), 0], [0, np.sqrt(1-p)]], dtype='complex128') e0 = paddle.to_tensor([[np.sqrt(1 - p), 0], [0, np.sqrt(1 - p)]], dtype='complex128')
e1 = paddle.to_tensor([[np.sqrt(p), 0], [0, -np.sqrt(p)]], dtype='complex128') e1 = paddle.to_tensor([[np.sqrt(p), 0], [0, -np.sqrt(p)]], dtype='complex128')
return [e0, e1] return [e0, e1]
...@@ -1868,7 +3353,7 @@ class UAnsatz: ...@@ -1868,7 +3353,7 @@ class UAnsatz:
""" """
assert 0 <= p <= 1, 'the probability p of a bit phase flip should be in range [0, 1]' assert 0 <= p <= 1, 'the probability p of a bit phase flip should be in range [0, 1]'
e0 = paddle.to_tensor([[np.sqrt(1-p), 0], [0, np.sqrt(1-p)]], dtype='complex128') e0 = paddle.to_tensor([[np.sqrt(1 - p), 0], [0, np.sqrt(1 - p)]], dtype='complex128')
e1 = paddle.to_tensor([[0, -1j * np.sqrt(p)], [1j * np.sqrt(p), 0]], dtype='complex128') e1 = paddle.to_tensor([[0, -1j * np.sqrt(p)], [1j * np.sqrt(p), 0]], dtype='complex128')
return [e0, e1] return [e0, e1]
...@@ -1913,10 +3398,10 @@ class UAnsatz: ...@@ -1913,10 +3398,10 @@ class UAnsatz:
""" """
assert 0 <= p <= 1, 'the parameter p should be in range [0, 1]' assert 0 <= p <= 1, 'the parameter p should be in range [0, 1]'
e0 = paddle.to_tensor([[np.sqrt(1-p), 0], [0, np.sqrt(1-p)]], dtype='complex128') e0 = paddle.to_tensor([[np.sqrt(1 - p), 0], [0, np.sqrt(1 - p)]], dtype='complex128')
e1 = paddle.to_tensor([[0, np.sqrt(p/3)], [np.sqrt(p/3), 0]], dtype='complex128') e1 = paddle.to_tensor([[0, np.sqrt(p / 3)], [np.sqrt(p / 3), 0]], dtype='complex128')
e2 = paddle.to_tensor([[0, -1j * np.sqrt(p/3)], [1j * np.sqrt(p/3), 0]], dtype='complex128') e2 = paddle.to_tensor([[0, -1j * np.sqrt(p / 3)], [1j * np.sqrt(p / 3), 0]], dtype='complex128')
e3 = paddle.to_tensor([[np.sqrt(p/3), 0], [0, -np.sqrt(p/3)]], dtype='complex128') e3 = paddle.to_tensor([[np.sqrt(p / 3), 0], [0, -np.sqrt(p / 3)]], dtype='complex128')
return [e0, e1, e2, e3] return [e0, e1, e2, e3]
...@@ -1974,29 +3459,45 @@ class UAnsatz: ...@@ -1974,29 +3459,45 @@ class UAnsatz:
@apply_channel @apply_channel
def reset(self, p, q, which_qubit): def reset(self, p, q, which_qubit):
r"""添加重置信道。有 p 的概率将量子态重置为 :math:`|0\rangle` 并有 q 的概率重置为 :math:`|1\rangle`。 r"""添加重置信道。有 p 的概率将量子态重置为 :math:`|0\rangle` 并有 q 的概率重置为 :math:`|1\rangle`。
其 Kraus 算符为: 其 Kraus 算符为:
.. math:: .. math::
E_0 = \begin{bmatrix} \sqrt{p} & 0 \\ 0 & 0 \end{bmatrix}, E_0 =
E_1 = \begin{bmatrix} 0 & \sqrt{p} \\ 0 & 0 \end{bmatrix},\\ \begin{bmatrix}
E_2 = \begin{bmatrix} 0 & 0 \\ \sqrt{q} & 0 \end{bmatrix}, \sqrt{p} & 0 \\
E_3 = \begin{bmatrix} 0 & 0 \\ 0 & \sqrt{q} \end{bmatrix},\\ 0 & 0
\end{bmatrix},
E_1 =
\begin{bmatrix}
0 & \sqrt{p} \\
0 & 0
\end{bmatrix},\\
E_2 =
\begin{bmatrix}
0 & 0 \\
\sqrt{q} & 0
\end{bmatrix},
E_3 =
\begin{bmatrix}
0 & 0 \\
0 & \sqrt{q}
\end{bmatrix},\\
E_4 = \sqrt{1-p-q} I. E_4 = \sqrt{1-p-q} I.
Args: Args:
p (float): 重置为 :math:`|0\rangle`的概率,其值应该在 :math:`[0, 1]` 区间内 p (float): 重置为 :math:`|0\rangle`的概率,其值应该在 :math:`[0, 1]` 区间内
q (float): 重置为 :math:`|1\rangle`的概率,其值应该在 :math:`[0, 1]` 区间内 q (float): 重置为 :math:`|1\rangle`的概率,其值应该在 :math:`[0, 1]` 区间内
which_qubit (int): 该信道作用在的 qubit 的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数 which_qubit (int): 该信道作用在的 qubit 的编号,其值应该在 :math:`[0, n)` 范围内, :math:`n` 为该量子电路的量子比特数
Note: Note:
两个输入的概率加起来需要小于等于 1。 两个输入的概率加起来需要小于等于 1。
代码示例: 代码示例:
.. code-block:: python .. code-block:: python
from paddle_quantum.circuit import UAnsatz from paddle_quantum.circuit import UAnsatz
N = 2 N = 2
p = 1 p = 1
...@@ -2007,24 +3508,24 @@ class UAnsatz: ...@@ -2007,24 +3508,24 @@ class UAnsatz:
cir.reset(p, q, 0) cir.reset(p, q, 0)
final_state = cir.run_density_matrix() final_state = cir.run_density_matrix()
print(final_state.numpy()) print(final_state.numpy())
:: ::
[[0.5+0.j 0. +0.j 0. +0.j 0. +0.j] [[0.5+0.j 0. +0.j 0. +0.j 0. +0.j]
[0. +0.j 0.5+0.j 0. +0.j 0. +0.j] [0. +0.j 0.5+0.j 0. +0.j 0. +0.j]
[0. +0.j 0. +0.j 0. +0.j 0. +0.j] [0. +0.j 0. +0.j 0. +0.j 0. +0.j]
[0. +0.j 0. +0.j 0. +0.j 0. +0.j]] [0. +0.j 0. +0.j 0. +0.j 0. +0.j]]
""" """
assert p + q <= 1, 'the sum of probabilities should be smaller than or equal to 1 ' assert p + q <= 1, 'the sum of probabilities should be smaller than or equal to 1 '
e0 = paddle.to_tensor([[np.sqrt(p), 0], [0, 0]], dtype='complex128') e0 = paddle.to_tensor([[np.sqrt(p), 0], [0, 0]], dtype='complex128')
e1 = paddle.to_tensor([[0, np.sqrt(p)], [0, 0]], dtype='complex128') e1 = paddle.to_tensor([[0, np.sqrt(p)], [0, 0]], dtype='complex128')
e2 = paddle.to_tensor([[0, 0], [np.sqrt(q), 0]], dtype='complex128') e2 = paddle.to_tensor([[0, 0], [np.sqrt(q), 0]], dtype='complex128')
e3 = paddle.to_tensor([[0, 0], [0, np.sqrt(q)]], dtype='complex128') e3 = paddle.to_tensor([[0, 0], [0, np.sqrt(q)]], dtype='complex128')
e4 = paddle.to_tensor([[np.sqrt(1 - (p + q)), 0], [0, np.sqrt(1 - (p + q))]], dtype='complex128') e4 = paddle.to_tensor([[np.sqrt(1 - (p + q)), 0], [0, np.sqrt(1 - (p + q))]], dtype='complex128')
return [e0, e1, e2, e3, e4] return [e0, e1, e2, e3, e4]
@apply_channel @apply_channel
def thermal_relaxation(self, t1, t2, time, which_qubit): def thermal_relaxation(self, t1, t2, time, which_qubit):
r"""添加热弛豫信道,模拟超导硬件上的 T1 和 T2 混合过程。 r"""添加热弛豫信道,模拟超导硬件上的 T1 和 T2 混合过程。
...@@ -2065,23 +3566,23 @@ class UAnsatz: ...@@ -2065,23 +3566,23 @@ class UAnsatz:
""" """
assert 0 <= t2 <= t1, 'Relaxation time constants are not valid as 0 <= T2 <= T1!' assert 0 <= t2 <= t1, 'Relaxation time constants are not valid as 0 <= T2 <= T1!'
assert 0 <= time, 'Invalid gate time!' assert 0 <= time, 'Invalid gate time!'
# Change time scale # Change time scale
time = time / 1000 time = time / 1000
# Probability of resetting the state to |0> # Probability of resetting the state to |0>
p_reset = 1 - np.exp(-time / t1) p_reset = 1 - np.exp(-time / t1)
# Probability of phase flip # Probability of phase flip
p_z = (1 - p_reset) * (1 - np.exp(-time / t2) * np.exp(time / t1)) / 2 p_z = (1 - p_reset) * (1 - np.exp(-time / t2) * np.exp(time / t1)) / 2
# Probability of identity # Probability of identity
p_i = 1- p_reset - p_z p_i = 1 - p_reset - p_z
e0 = paddle.to_tensor([[np.sqrt(p_i), 0], [0, np.sqrt(p_i)]], dtype='complex128') e0 = paddle.to_tensor([[np.sqrt(p_i), 0], [0, np.sqrt(p_i)]], dtype='complex128')
e1 = paddle.to_tensor([[np.sqrt(p_z), 0], [0, -np.sqrt(p_z)]], dtype='complex128') e1 = paddle.to_tensor([[np.sqrt(p_z), 0], [0, -np.sqrt(p_z)]], dtype='complex128')
e2 = paddle.to_tensor([[np.sqrt(p_reset), 0], [0, 0]], dtype='complex128') e2 = paddle.to_tensor([[np.sqrt(p_reset), 0], [0, 0]], dtype='complex128')
e3 = paddle.to_tensor([[0, np.sqrt(p_reset)], [0, 0]], dtype='complex128') e3 = paddle.to_tensor([[0, np.sqrt(p_reset)], [0, 0]], dtype='complex128')
return [e0, e1, e2, e3] return [e0, e1, e2, e3]
@apply_channel @apply_channel
def customized_channel(self, ops, which_qubit): def customized_channel(self, ops, which_qubit):
r"""添加自定义的量子信道。 r"""添加自定义的量子信道。
...@@ -2119,10 +3620,246 @@ class UAnsatz: ...@@ -2119,10 +3620,246 @@ class UAnsatz:
assert op.shape == [2, 2], 'The shape of each operator should be [2, 2].' assert op.shape == [2, 2], 'The shape of each operator should be [2, 2].'
assert op.dtype.name == 'COMPLEX128', 'The dtype of each operator should be COMPLEX128.' assert op.dtype.name == 'COMPLEX128', 'The dtype of each operator should be COMPLEX128.'
completeness += matmul(dagger(op), op) completeness += matmul(dagger(op), op)
assert np.allclose(completeness.numpy(), np.eye(2, dtype='complex128')), 'Kraus operators should satisfy completeness.' assert np.allclose(completeness.numpy(),
np.eye(2, dtype='complex128')), 'Kraus operators should satisfy completeness.'
return ops return ops
def shadow_trace(self, hamiltonian, sample_shots, method='CS'):
r"""估计可观测量 :math:`H` 的期望值 :math:`\text{trace}(H\rho)` 。
Args:
hamiltonian (Hamiltonian): 可观测量
sample_shots (int): 采样次数
method (str, optional): 使用 shadow 来进行估计的方法,可选 "CS"、"LBCS"、"APS" 三种方法,默认为 "CS"
代码示例:
.. code-block:: python
import paddle
from paddle_quantum.circuit import UAnsatz
from paddle_quantum.utils import Hamiltonian
from paddle_quantum.state import vec_random
n_qubit = 2
sample_shots = 1000
state = vec_random(n_qubit)
ham = [[0.1, 'x1'], [0.2, 'y0']]
ham = Hamiltonian(ham)
cir = UAnsatz(n_qubit)
input_state = cir.run_state_vector(paddle.to_tensor(state))
trace_cs = cir.shadow_trace(ham, sample_shots, method="CS")
trace_lbcs = cir.shadow_trace(ham, sample_shots, method="LBCS")
trace_aps = cir.shadow_trace(ham, sample_shots, method="APS")
print('trace CS = ', trace_cs)
print('trace LBCS = ', trace_lbcs)
print('trace APS = ', trace_aps)
::
trace CS = -0.09570000000000002
trace LBCS = -0.0946048044954126
trace APS = -0.08640438803809354
"""
if not isinstance(hamiltonian, list):
hamiltonian = hamiltonian.pauli_str
state = self.__state
num_qubits = self.n
mode = self.__run_mode
if method == "LBCS":
result, beta = shadow.shadow_sample(state, num_qubits, sample_shots, mode, hamiltonian, method)
else:
result = shadow.shadow_sample(state, num_qubits, sample_shots, mode, hamiltonian, method)
def prepare_hamiltonian(hamiltonian, num_qubits):
r"""改写可观测量 ``[[0.3147,'y2'], [-0.5484158742278,'x2,z1'],...]`` 的形式
Args:
hamiltonian (list): 可观测量的相关信息
num_qubits (int): 量子比特数目
Returns:
list: 可观测量的形式改写为[[0.3147,'iiy'], [-0.5484158742278,'izx'],...]
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
new_hamiltonian = list()
for idx, (coeff, pauli_str) in enumerate(hamiltonian):
pauli_str = re.split(r',\s*', pauli_str.lower())
pauli_term = ['i'] * num_qubits
for item in pauli_str:
if len(item) > 1:
pauli_term[int(item[1:])] = item[0]
elif item[0].lower() != 'i':
raise ValueError('Expecting I for ', item[0])
new_term = [coeff, ''.join(pauli_term)]
new_hamiltonian.append(new_term)
return new_hamiltonian
hamiltonian = prepare_hamiltonian(hamiltonian, num_qubits)
sample_pauli_str = [item for item, _ in result]
sample_measurement_result = [item for _, item in result]
coeff_terms = list()
pauli_terms = list()
for coeff, pauli_term in hamiltonian:
coeff_terms.append(coeff)
pauli_terms.append(pauli_term)
pauli2idx = {'x': 0, 'y': 1, 'z': 2}
def estimated_weight_cs(sample_pauli_str, pauli_term):
r"""定义 CS 算法中的对测量的权重估计函数
Args:
sample_pauli_str (str): 随机选择的 pauli 项
pauli_term (str): 可观测量的 pauli 项
Returns:
int: 返回估计的权重值
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
result = 1
for i in range(num_qubits):
if sample_pauli_str[i] == 'i' or pauli_term[i] == 'i':
continue
elif sample_pauli_str[i] == pauli_term[i]:
result *= 3
else:
result = 0
return result
def estimated_weight_lbcs(sample_pauli_str, pauli_term, beta):
r"""定义 LBCS 算法中的权重估计函数
Args:
sample_pauli_str (str): 随机选择的 pauli 项
pauli_term (str): 可观测量的 pauli 项
beta (list): 所有量子位上关于 pauli 的概率分布
Returns:
float: 返回函数数值
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
# beta is 2-d, and the shape looks like (len, 3)
assert len(sample_pauli_str) == len(pauli_term)
result = 1
for i in range(num_qubits):
# The probability distribution is different at each qubit
score = 0
idx = pauli2idx[sample_pauli_str[i]]
if sample_pauli_str[i] == 'i' or pauli_term[i] == 'i':
score = 1
elif sample_pauli_str[i] == pauli_term[i] and beta[i][idx] != 0:
score = 1 / beta[i][idx]
result *= score
return result
def estimated_value(pauli_term, measurement_result):
r"""满足条件的测量结果本征值的乘积
Args:
pauli_term (str): 可观测量的 pauli 项
measurement_result (list): 测量结果
Returns:
int: 返回测量结果本征值的乘积
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
value = 1
for idx in range(num_qubits):
if pauli_term[idx] != 'i' and measurement_result[idx] == '1':
value *= -1
return value
# Define the functions required by APS
def is_covered(pauli, pauli_str):
r"""判断可观测量的 pauli 项是否被随机选择的 pauli 项所覆盖
Args:
pauli (str): 可观测量的 pauli 项
pauli_str (str): 随机选择的 pauli 项
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
for qubit_idx in range(num_qubits):
if not pauli[qubit_idx] in ('i', pauli_str[qubit_idx]):
return False
return True
def update_pauli_estimator(hamiltonian, pauli_estimator, pauli_str, measurement_result):
r"""用于更新 APS 算法下当前可观测量 pauli 项 P 的最佳估计 tr( P \rho),及 P 被覆盖的次数
Args:
hamiltonian (list): 可观测量的相关信息
pauli_estimator (dict): 用于记录最佳估计与被覆盖次数
pauli_str (list): 随机选择的 pauli 项
measurement_result (list): 对随机选择的 pauli 项测量得到的结果
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
for coeff, pauli_term in hamiltonian:
last_estimator = pauli_estimator[pauli_term]['value'][-1]
if is_covered(pauli_term, pauli_str):
value = estimated_value(pauli_term, measurement_result)
chose_number = pauli_estimator[pauli_term]['times']
new_estimator = 1 / (chose_number + 1) * (chose_number * last_estimator + value)
pauli_estimator[pauli_term]['times'] += 1
pauli_estimator[pauli_term]['value'].append(new_estimator)
else:
pauli_estimator[pauli_term]['value'].append(last_estimator)
trace_estimation = 0
if method == "CS":
for sample_idx in range(sample_shots):
estimation = 0
for i in range(len(pauli_terms)):
value = estimated_value(pauli_terms[i], sample_measurement_result[sample_idx])
weight = estimated_weight_cs(sample_pauli_str[sample_idx], pauli_terms[i])
estimation += coeff_terms[i] * weight * value
trace_estimation += estimation
trace_estimation /= sample_shots
elif method == "LBCS":
for sample_idx in range(sample_shots):
estimation = 0
for i in range(len(pauli_terms)):
value = estimated_value(pauli_terms[i], sample_measurement_result[sample_idx])
weight = estimated_weight_lbcs(sample_pauli_str[sample_idx], pauli_terms[i], beta)
estimation += coeff_terms[i] * weight * value
trace_estimation += estimation
trace_estimation /= sample_shots
elif method == "APS":
# Create a search dictionary for easy storage
pauli_estimator = dict()
for coeff, pauli_term in hamiltonian:
pauli_estimator[pauli_term] = {'times': 0, 'value': [0]}
for sample_idx in range(sample_shots):
update_pauli_estimator(
hamiltonian,
pauli_estimator,
sample_pauli_str[sample_idx],
sample_measurement_result[sample_idx]
)
for sample_idx in range(sample_shots):
estimation = 0
for coeff, pauli_term in hamiltonian:
estimation += coeff * pauli_estimator[pauli_term]['value'][sample_idx + 1]
trace_estimation = estimation
return trace_estimation
def _local_H_prob(cir, hamiltonian, shots=1024): def _local_H_prob(cir, hamiltonian, shots=1024):
r""" r"""
...@@ -2141,13 +3878,16 @@ def _local_H_prob(cir, hamiltonian, shots=1024): ...@@ -2141,13 +3878,16 @@ def _local_H_prob(cir, hamiltonian, shots=1024):
# Set up pauli measurement circuit # Set up pauli measurement circuit
for op in op_list: for op in op_list:
element = op[0] element = op[0]
index = int(op[1:]) if len(op) > 1:
if element == 'x': index = int(op[1:])
elif op[0].lower() != 'i':
raise ValueError('Expecting {} to be {}'.format(op, 'I'))
if element.lower() == 'x':
new_cir.h(index) new_cir.h(index)
new_cir.cnot([index, cir.n]) new_cir.cnot([index, cir.n])
elif element == 'z': elif element.lower() == 'z':
new_cir.cnot([index, cir.n]) new_cir.cnot([index, cir.n])
elif element == 'y': elif element.lower() == 'y':
new_cir.rz(_theta, index) new_cir.rz(_theta, index)
new_cir.h(index) new_cir.h(index)
new_cir.cnot([index, cir.n]) new_cir.cnot([index, cir.n])
...@@ -2159,7 +3899,7 @@ def _local_H_prob(cir, hamiltonian, shots=1024): ...@@ -2159,7 +3899,7 @@ def _local_H_prob(cir, hamiltonian, shots=1024):
if '0' in prob_result: if '0' in prob_result:
result = (prob_result['0']) / shots result = (prob_result['0']) / shots
else: else:
result = (prob_result['1']) / shots result = -(prob_result['1']) / shots
else: else:
result = (prob_result['0'] - prob_result['1']) / shots result = (prob_result['0'] - prob_result['1']) / shots
else: else:
...@@ -2168,44 +3908,46 @@ def _local_H_prob(cir, hamiltonian, shots=1024): ...@@ -2168,44 +3908,46 @@ def _local_H_prob(cir, hamiltonian, shots=1024):
return result return result
def H_prob(cir, H, shots=1024): def swap_test(n):
r"""构造 Pauli 测量电路并测量关于 H 的期望值 r"""构造用 Swap Test 测量两个量子态之间差异的电路
Args: Args:
cir (UAnsatz): UAnsatz 的一个实例化对象 n (int): 待比较的两个态的量子比特数
H (list): 记录哈密顿量信息的列表
shots (int, optional): 默认为 1024,表示测量次数;若为 0,则表示返回测量期望值的精确值,即测量无穷次后的期望值
Returns: Returns:
float: 测量得到的H的期望值 UAnsatz: Swap Test 的电路
代码示例: 代码示例:
.. code-block:: python .. code-block:: python
import numpy as np
import paddle import paddle
from paddle_quantum.circuit import UAnsatz, H_prob import numpy as np
n = 4 from paddle_quantum.state import vec
experiment_shots = 2**10 from paddle_quantum.circuit import UAnsatz, swap_test
H_info = [[0.1, 'x2'], [0.3, 'y1,z3']] from paddle_quantum.utils import NKron
theta = paddle.to_tensor(np.ones(3)) n = 2
cir = UAnsatz(n) ancilla = vec(0, 1)
cir.rx(theta[0], 0) psi = vec(1, n)
cir.ry(theta[1], 1) phi = vec(0, n)
cir.rz(theta[2], 1) input_state = NKron(ancilla, psi, phi)
result_1 = H_prob(cir, H_info, shots = experiment_shots)
result_2 = H_prob(cir, H_info, shots = 0) cir = swap_test(n)
print(f'The expectation value obtained by {experiment_shots} measurements is {result_1}') cir.run_state_vector(paddle.to_tensor(input_state))
print(f'The accurate expectation value of H is {result_2}') result = cir.measure(which_qubits=[0], shots=8192, plot=True)
probability = result['0'] / 8192
inner_product = (probability - 0.5) * 2
print(f"The inner product is {inner_product}")
:: ::
The expectation value obtained by 1024 measurements is 0.2177734375 The inner product is 0.006591796875
The accurate expectation value of H is 0.21242202548207134
""" """
expval = 0 cir = UAnsatz(2 * n + 1)
for term in H: cir.h(0)
expval += term[0] * _local_H_prob(cir, term[1], shots=shots) for i in range(n):
return expval cir.cswap([0, i + 1, i + n + 1])
cir.h(0)
return cir
# 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.
"""
ExpecVal Class
"""
import paddle
from paddle.autograd import PyLayer
__all__ = [
"ExpecVal"
]
class ExpecVal(PyLayer):
r"""PaddlePaddle 自定义 Python 算子,用来计算量子电路输出的量子态关于可观测量 H 的期望值。
"""
@staticmethod
def forward(ctx, cir, theta, grad_func, hamiltonian, delta=None, shots=0):
r"""前向函数。
Hint:
如果想输入的可观测量的矩阵为 :math:`0.7Z\otimes X\otimes I+0.2I\otimes Z\otimes I` 。则 ``H`` 的 ``list`` 形式为 ``[[0.7, 'Z0, X1'], [0.2, 'Z1']]`` 。
Args:
cir (UAnsatz): 目标量子电路
theta (paddle.Tensor): 量子电路中的需要被优化的参数
grad_func (string): 用于计算梯度的函数名,应为 ``'finite_diff'`` 或 ``'param_shift'``
hamiltonian (list or Hamiltonian): 记录哈密顿量信息的列表或 ``Hamiltonian`` 类的对象
delta (float): 差分法中需要用到的 delta,默认为 ``None``
shots (int, optional): 表示测量次数;默认为 0,表示返回期望值的精确值,即测量无穷次后的期望值
Returns:
paddle.Tensor: 量子电路输出的量子态关于可观测量 H 的期望值
代码示例:
.. code-block:: python
import numpy as np
import paddle
from paddle_quantum.circuit import UAnsatz
from paddle_quantum.expecval import ExpecVal
N = 2
D = 2
theta = paddle.uniform(shape=[N * D], dtype='float64', min=0.0, max=np.pi * 2)
theta.stop_gradient = False
cir = UAnsatz(N)
cir.real_entangled_layer(theta, D)
cir.run_state_vector()
H = [[1.0, 'z0,z1']]
delta = 0.01
shots = 0
z = ExpecVal.apply(cir, cir.get_param(), 'finite_diff', H, delta, shots)
print(z)
::
Tensor(shape=[1], dtype=float64, place=CPUPlace, stop_gradient=False,
[0.61836319])
"""
assert grad_func in {'finite_diff', 'param_shift'}, "grad_func must be one of 'finite_diff' or 'param_shift'"
# Pass grad_func, cir, theta, delta, shots, and Hamiltonian into the backward function by adding temporary attributes
ctx.grad_func = grad_func
ctx.cir = cir
ctx.theta = theta
ctx.delta = delta
ctx.shots = shots
ctx.Hamiltonian = hamiltonian
# Compute the expectation value
cir.update_param(theta)
expec_val = cir.expecval(ctx.Hamiltonian, shots)
return expec_val
@staticmethod
def backward(ctx, dy):
r"""反向函数。
Args:
dy (paddle.Tensor): 前向函数输出的期望值的梯度
Returns:
paddle.Tensor: 前向函数中输入的参数 ``theta`` 的梯度
代码示例:
.. code-block:: python
import numpy as np
import paddle
from paddle_quantum.circuit import UAnsatz
from paddle_quantum.expecval import ExpecVal
N = 2
D = 2
theta = paddle.uniform(shape=[N * D], dtype='float64', min=0.0, max=np.pi * 2)
theta.stop_gradient = False
cir = UAnsatz(N)
cir.real_entangled_layer(theta, D)
cir.run_state_vector()
H = [[1.0, 'z0,z1']]
delta = 0.01
shots = 0
z = ExpecVal.apply(cir, cir.get_param(), 'finite_diff', H, delta, shots)
temp = paddle.square(z)
temp.backward()
"""
# Get expec_func, grad_func, theta, delta, and args
cir = ctx.cir
grad_func = ctx.grad_func
delta = ctx.delta
shots = ctx.shots
Hamiltonian = ctx.Hamiltonian
# Compute the gradient
if grad_func == "finite_diff":
assert delta is not None, "Finite difference gradient requires an input 'delta'"
grad = dy * cir.finite_difference_gradient(Hamiltonian, delta, shots)
else:
grad = dy * cir.param_shift_gradient(Hamiltonian, shots)
grad.stop_gradient = False
return paddle.reshape(grad, ctx.theta.shape)
# 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.
"""
Functions and data simulator class of quantum finance
"""
import fastdtw
import numpy as np
from paddle_quantum.utils import Hamiltonian
__all__ = [
"DataSimulator",
"portfolio_optimization_hamiltonian",
"portfolio_diversification_hamiltonian",
"arbitrage_opportunities_hamiltonian"
]
class DataSimulator:
r"""用于生成和计算投资组合优化和投资分散化问题要用的数据和相关参数
"""
def __init__(self, stocks, start=None, end=None):
r"""构造函数,用于实例化一个 ``DataSimulator`` 对象。
Args:
stocks (list): 表示所有可投资股票的名字
start (datetime): 默认为 ``None``,表示随机生成股票数据时交易日的起始日期
end (datetime): 默认为 ``None``,表示随机生成股票数据时交易日的结束日期
"""
self._n = len(stocks)
self._stocks = stocks
if start and end:
self._start = start
self._end = end
self._data = []
self.asset_return_mean = None
self.asset_return_cov = None
def set_data(self, data):
r"""决定实验使用的数据是随机生成的还是用户本地输入的
Args:
data (list): 用户输入的股票数据
"""
if len(data) == self._n:
self._data = data
else:
print("invalid data, data is still empty.")
self._data = []
def randomly_generate(self):
r"""根据开始日期和结束日期随机生成用于实验的股票数据
Note:
若要随机生成股票数据,需要以 ``datetime`` 包中的格式指定开始日期和结束日期,如 ``start = datetime.datetime(2016, 1, 1)``
"""
if self._start and self._end:
num_days = (self._end - self._start).days
for _ in self._stocks:
fluctuation = np.random.standard_normal(num_days)
fluctuation = np.cumsum(fluctuation)
data_i = np.random.randint(1, 101, size=1) + fluctuation
trimmed_data_i = [max(data_i[j], 0) for j in range(num_days)]
if 0 in trimmed_data_i:
zero_ind = trimmed_data_i.index(0)
trimmed_data_i = trimmed_data_i[:zero_ind] + [0 for _ in range(num_days - zero_ind)]
self._data.append(trimmed_data_i)
else:
print("Please provide the start time and the end time you want to generate stock data.")
def get_asset_return_mean_vector(self):
r"""用于计算所有可投资股票的平均投资回报率
Returns:
list: 所有可投资的股票的平均投资回报率
"""
returns = []
for i in range(self._n):
return_i = [self._data[i][j + 1] / self._data[i][j] - 1
if self._data[i][j] != 0
else np.nan for j in range(len(self._data[i]) - 1)]
returns.append(return_i)
self.asset_return_mean = np.mean(returns, axis=1)
return self.asset_return_mean
def get_asset_return_covariance_matrix(self):
r"""用于计算所有可投资股票回报率之间的协方差矩阵
Returns:
list: 所有可投资股票回报率之间的协方差矩阵
"""
returns = []
for i in range(self._n):
return_i = [self._data[i][j + 1] / self._data[i][j] - 1
if self._data[i][j] != 0
else np.nan for j in range(len(self._data[i]) - 1)]
returns.append(return_i)
self.asset_return_cov = np.cov(returns)
return self.asset_return_cov
def get_similarity_matrix(self):
r"""计算各股票之间的相似矩阵
通过动态时间规整算法(Dynamic Time Warping, DTW)计算两股票之间的相似性
Returns:
list: 各股票间的相似矩阵
"""
self.rho = np.zeros((self._n, self._n))
for i in range(0, self._n):
self.rho[i, i] = 1
for j in range(i + 1, self._n):
curr_rho, _ = fastdtw.fastdtw(self._data[i], self._data[j])
curr_rho = 1 / curr_rho
self.rho[i, j] = curr_rho
self.rho[j, i] = curr_rho
return self.rho
def portfolio_optimization_hamiltonian(penalty, mu, sigma, q, budget):
r"""构建投资组合优化问题的哈密顿量
Args:
penalty (int): 惩罚参数
mu (list): 各股票的预期回报率
sigma (list): 各股票回报率间的协方差矩阵
q (float): 投资股票的风险
budget (int): 投资预算, 即要投资的股票数量
.. math::
C(x) = q \sum_i \sum_j S_{ji}x_ix_j - \sum_{i}x_i \mu_i + A \left(B - \sum_i x_i\right)^2
Hint:
将布尔变量 :math:`x_i` 映射到哈密顿矩阵上,:math:`x_i \mapsto \frac{I-Z_i}{2}`
Returns:
Hamiltonian: 投资组合优化问题的哈密顿量
"""
n = len(mu)
H_C_list1 = []
for i in range(n):
for j in range(n):
sigma_ij = sigma[i][j]
H_C_list1.append([sigma_ij / 4, 'I'])
if i != j:
H_C_list1.append([sigma_ij / 4, 'Z' + str(i) + ',Z' + str(j)])
else:
H_C_list1.append([sigma_ij / 4, 'I'])
H_C_list1.append([- sigma_ij / 4, 'Z' + str(i)])
H_C_list1.append([- sigma_ij / 4, 'Z' + str((j))])
H_C_list1 = [[q * c, s] for (c, s) in H_C_list1]
H_C_list2 = []
for i in range(n):
H_C_list2.append([- mu[i] / 2, 'I'])
H_C_list2.append([mu[i] / 2, 'Z' + str(i)])
H_C_list3 = [[budget ** 2, 'I']]
for i in range(n):
H_C_list3.append([- 2 * budget / 2, 'I'])
H_C_list3.append([2 * budget / 2, 'Z' + str(i)])
H_C_list3.append([2 / 4, 'I'])
H_C_list3.append([- 2 / 4, 'Z' + str(i)])
for ii in range(i):
H_C_list3.append([2 / 4, 'I'])
H_C_list3.append([2 / 4, 'Z' + str(i) + ',Z' + str(ii)])
H_C_list3.append([- 2 / 4, 'Z' + str(i)])
H_C_list3.append([- 2 / 4, 'Z' + str(ii)])
H_C_list3 = [[penalty * c, s] for (c, s) in H_C_list3]
H_C_list = H_C_list1 + H_C_list2 + H_C_list3
po_hamiltonian = Hamiltonian(H_C_list)
return po_hamiltonian
def portfolio_diversification_hamiltonian(penalty, rho, q):
r"""构建投资组合分散化问题的哈密顿量
Args:
penalty (int): 惩罚参数
rho (list): 各股票间的相似矩阵
q (int): 股票聚类的类别数
.. math::
\begin{aligned}
C_x &= -\sum_{i=1}^{n}\sum_{j=1}^{n}\rho_{ij}x_{ij} + A\left(K- \sum_{j=1}^n y_j \right)^2 + \sum_{i=1}^n A\left(\sum_{j=1}^n 1- x_{ij} \right)^2 \\
&\quad + \sum_{j=1}^n A\left(x_{jj} - y_j\right)^2 + \sum_{i=1}^n \sum_{j=1}^n A\left(x_{ij}(1 - y_j)\right).\\
\end{aligned}
Hint:
将布尔变量 :math:`x_{ij}` 映射到哈密顿矩阵上,:math:`x_{ij} \mapsto \frac{I-Z_{ij}}{2}`
Returns:
Hamiltonian: 投资组合分散化问题的哈密顿量
"""
n = len(rho)
H_C_list1 = []
for i in range(n):
for j in range(n):
rho_ij = - rho[i][j]
H_C_list1.append([rho_ij / 2, 'I'])
H_C_list1.append([- rho_ij / 2, 'Z' + str(i * n + j)])
H_C_list2 = [[q ** 2, 'I']]
for j in range(n):
H_C_list2.append([- q, 'I'])
H_C_list2.append([q, 'Z' + str(n ** 2 + j)])
H_C_list2.append([1 / 2, 'I'])
H_C_list2.append([- 1 / 2, 'Z' + str(n ** 2 + j)])
for jj in range(j):
H_C_list2.append([1 / 2, 'I'])
H_C_list2.append([1 / 2, 'Z' + str(n ** 2 + j) + ',Z' + str(n ** 2 + jj)])
H_C_list2.append([- 1 / 2, 'Z' + str(n ** 2 + j)])
H_C_list2.append([- 1 / 2, 'Z' + str(n ** 2 + jj)])
H_C_list2 = [[penalty * c, s] for (c, s) in H_C_list2]
H_C_list3 = []
for i in range(n):
H_C_list3.append([1, 'I'])
for j in range(n):
H_C_list3.append([- 1, 'I'])
H_C_list3.append([1, 'Z' + str(i * n + j)])
H_C_list3.append([1 / 2, 'I'])
H_C_list3.append([- 1 / 2, 'Z' + str(i * n + j)])
for jj in range(j):
H_C_list3.append([1 / 2, 'I'])
H_C_list3.append([- 1 / 2, 'Z' + str(i * n + j)])
H_C_list3.append([1 / 2, 'Z' + str(i * n + j) + ',Z' + str(i * n + jj)])
H_C_list3.append([- 1 / 2, 'Z' + str(i * n + jj)])
H_C_list3 = [[penalty * c, s] for (c, s) in H_C_list3]
H_C_list4 = []
for j in range(n):
H_C_list4.append([1 / 2, 'I'])
H_C_list4.append([- 1 / 2, 'Z' + str(j * n + j) + ',Z' + str(n ** 2 + j)])
H_C_list4 = [[penalty * c, s] for (c, s) in H_C_list4]
H_C_list5 = []
for i in range(n):
for j in range(n):
H_C_list5.append([1 / 4, 'I'])
H_C_list5.append([- 1 / 4, 'Z' + str(i * n + j)])
H_C_list5.append([1 / 4, 'Z' + str(n ** 2 + j)])
H_C_list5.append([- 1 / 4, 'Z' + str(i * n + j) + ',Z' + str(n ** 2 + j)])
H_C_list5 = [[penalty * c, s] for (c, s) in H_C_list5]
H_C_list = H_C_list1 + H_C_list2 + H_C_list3 + H_C_list4 + H_C_list5
pd_hamiltonian = Hamiltonian(H_C_list)
return pd_hamiltonian
def arbitrage_opportunities_hamiltonian(g, penalty, n, K):
r"""构建最佳套利机会问题的哈密顿量
Args:
g (networkx.DiGraph): 不同货币市场间转换的图形化表示
A (int): 惩罚参数
n (int): 货币种类的数量,即图 g 中的顶点数量
K (int): 套利回路中包含的顶点数
.. math::
C(x) = - P(x) + A\sum_{k=0}^{K-1} \left(1 - \sum_{i=0}^{n-1} x_{i,k}\right)^2 + A\sum_{k=0}^{K-1}\sum_{(i,j)\notin E}x_{i,k}x_{j,k+1}
Hint:
将布尔变量 :math:`x_{i,k}` 映射到哈密顿矩阵上,:math:`x_{i,k} \mapsto \frac{I-Z_{i,k}}{2}`
Returns:
Hamiltonian: 最佳套利机会问题的哈密顿量
"""
nodes = list(g.nodes)
H_C_list1 = []
for (i, c) in enumerate(nodes):
for (j, cc) in enumerate(nodes):
if i != j:
c_ij = np.log2(g[c][cc]['weight'])
for t in range(K):
H_C_list1.append([c_ij / 4, 'I'])
H_C_list1.append([c_ij / 4, 'Z' + str(i * n + t) + ',Z' + str((j * n + (t + 1) % K))])
H_C_list1.append([- c_ij / 4, 'Z' + str(i * n + t)])
H_C_list1.append([- c_ij / 4, 'Z' + str((j * n + (t + 1) % K))])
H_C_list1 = [[-c, s] for (c, s) in H_C_list1]
H_C_list2 = []
for t in range(K):
H_C_list2.append([1, 'I'])
for i in range(n):
H_C_list2.append([- 2 * 1 / 2, 'I'])
H_C_list2.append([2 * 1 / 2, 'Z' + str(i * n + t)])
H_C_list2.append([2 / 4, 'I'])
H_C_list2.append([- 2 / 4, 'Z' + str(i * n + t)])
for ii in range(i):
H_C_list2.append([2 / 4, 'I'])
H_C_list2.append([2 / 4, 'Z' + str(i * n + t) + ',Z' + str(ii * n + t)])
H_C_list2.append([- 2 / 4, 'Z' + str(i * n + t)])
H_C_list2.append([- 2 / 4, 'Z' + str(ii * n + t)])
H_C_list2 = [[penalty * c, s] for (c, s) in H_C_list2]
H_C_list3 = []
for t in range(K):
for (i, c) in enumerate(nodes):
for (j, cc) in enumerate(nodes):
if (c, cc) not in g.edges and c != cc:
H_C_list3.append([1 / 4, "I"])
H_C_list3.append([- 1 / 4, 'Z' + str(i * n + t)])
H_C_list3.append([- 1 / 4, 'Z' + str((j * n + (t + 1) % K))])
H_C_list3.append([- 1 / 4, 'Z' + str(i * n + t) + ',Z' + str((j * n + (t + 1) % K))])
H_C_list3 = [[penalty * c, s] for (c, s) in H_C_list3]
H_C_list4 = []
for i in range(n):
H_C_list4.append([1, 'I'])
for t in range(K):
H_C_list4.append([- 2 * 1 / 2, 'I'])
H_C_list4.append([2 * 1 / 2, 'Z' + str(i * n + t)])
H_C_list4.append([2 / 4, 'I'])
H_C_list4.append([- 2 / 4, 'Z' + str(i * n + t)])
for tt in range(t):
H_C_list4.append([2 / 4, 'I'])
H_C_list4.append([2 / 4, 'Z' + str(i * n + t) + ',Z' + str(i * n + tt)])
H_C_list4.append([- 2 / 4, 'Z' + str(i * n + t)])
H_C_list4.append([- 2 / 4, 'Z' + str(i * n + tt)])
H_C_list4 = [[penalty * c, s] for (c, s) in H_C_list4]
H_C_list = H_C_list1 + H_C_list2 + H_C_list3 + H_C_list4
ao_hamiltonian = Hamiltonian(H_C_list)
return ao_hamiltonian
...@@ -16,10 +16,11 @@ import math ...@@ -16,10 +16,11 @@ import math
from functools import wraps from functools import wraps
import numpy as np import numpy as np
from numpy import binary_repr from numpy import binary_repr
import re
import paddle import paddle
from paddle import multiply, add, to_tensor from paddle import multiply, add, to_tensor, matmul, real, trace
from paddle_quantum.simulator import StateTransfer from paddle_quantum.simulator import StateTransfer
from paddle_quantum import utils
def dic_between2and10(n): def dic_between2and10(n):
...@@ -52,11 +53,14 @@ def single_H_vec_i(H, target_vec): ...@@ -52,11 +53,14 @@ def single_H_vec_i(H, target_vec):
Note: Note:
这是内部函数,你并不需要直接调用到该函数。 这是内部函数,你并不需要直接调用到该函数。
""" """
op_list = H.split(',') op_list = re.split(r',\s*', H.lower())
coef = 1 + 0*1j # Coefficient for the vector coef = 1 + 0*1j # Coefficient for the vector
new_vec = list(target_vec) new_vec = list(target_vec)
for op in op_list: for op in op_list:
pos = int(op[1:]) if len(op) >= 2:
pos = int(op[1:])
elif op[0] != 'i':
raise ValueError('only operator "I" can be used without identifying its position')
if op[0] == 'x': if op[0] == 'x':
new_vec[pos] = '0' if target_vec[pos] == '1' else '1' new_vec[pos] = '0' if target_vec[pos] == '1' else '1'
elif op[0] == 'y': elif op[0] == 'y':
...@@ -119,7 +123,7 @@ def vec_expecval(H, vec): ...@@ -119,7 +123,7 @@ def vec_expecval(H, vec):
return result return result
def transfer_by_history(state, history): def transfer_by_history(state, history, params):
r""" r"""
It transforms the input state according to the history given. It transforms the input state according to the history given.
...@@ -127,14 +131,18 @@ def transfer_by_history(state, history): ...@@ -127,14 +131,18 @@ def transfer_by_history(state, history):
这是内部函数,你并不需要直接调用到该函数。 这是内部函数,你并不需要直接调用到该函数。
""" """
for history_ele in history: for history_ele in history:
if history_ele[0] != 'channel': if history_ele['gate'] != 'channel':
if history_ele[0] in {'s', 't', 'ry', 'rz', 'rx'}: which_qubit = history_ele['which_qubits']
state = StateTransfer(state, 'u', history_ele[1], params=history_ele[2]) parameter = [params[i] for i in history_ele['theta']] if history_ele['theta'] else None
elif history_ele[0] == 'MS_gate':
state = StateTransfer(state, 'RXX_gate', history_ele[1], params=history_ele[2]) if history_ele['gate'] in {'s', 't', 'ry', 'rz', 'rx', 'sdg', "tdg"}:
state = StateTransfer(state, 'u', which_qubit, params=parameter)
elif history_ele['gate'] == 'MS_gate':
state = StateTransfer(state, 'RXX_gate', which_qubit, params=parameter)
elif history_ele['gate'] in {'crx', 'cry', 'crz'}:
state = StateTransfer(state, 'CU', which_qubit, params=parameter)
else: else:
state = StateTransfer(state, history_ele[0], history_ele[1], params=history_ele[2]) state = StateTransfer(state, history_ele['gate'], which_qubit, params=parameter)
return state return state
...@@ -154,6 +162,6 @@ def apply_channel(func): ...@@ -154,6 +162,6 @@ def apply_channel(func):
assert 0 <= which_qubit < self.n, "the qubit's index should >= 0 and < n(the number of qubit)" assert 0 <= which_qubit < self.n, "the qubit's index should >= 0 and < n(the number of qubit)"
self._UAnsatz__has_channel = True self._UAnsatz__has_channel = True
ops = func(self, *args) ops = func(self, *args)
self._UAnsatz__history.append(['channel', ops, [which_qubit]]) self._UAnsatz__history.append({'gate': 'channel', 'operators': ops, 'which_qubits': [which_qubit]})
return inner return inner
\ No newline at end of file
...@@ -251,7 +251,7 @@ class LoccAnsatz(UAnsatz): ...@@ -251,7 +251,7 @@ class LoccAnsatz(UAnsatz):
which_qubit (int): 添加该门量子比特编号 which_qubit (int): 添加该门量子比特编号
""" """
which_qubit = self.party[which_qubit] which_qubit = self.party[which_qubit]
super(LoccAnsatz, self).z(which_qubit) super(LoccAnsatz, self).t(which_qubit)
def u3(self, theta, phi, lam, which_qubit): def u3(self, theta, phi, lam, which_qubit):
r"""添加一个单量子比特的旋转门。 r"""添加一个单量子比特的旋转门。
......
# 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.
"""
Export the SciPy optimizers
"""
from .custom_optimizer import CustomOptimizer
from .conjugate_gradient import ConjugateGradient
from .newton_cg import NewtonCG
from .powell import Powell
from .slsqp import SLSQP
from .powell import Powell
__all__ = [
'CustomOptimizer',
'ConjugateGradient',
'NewtonCG',
'Powell',
'SLSQP'
]
# 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.
"""
CG optimizer
"""
from scipy import optimize
from .custom_optimizer import CustomOptimizer
class ConjugateGradient(CustomOptimizer):
r"""ConjugateGradient Optimizer
继承 ``CustomOptimizer`` 类,使用 SciPy 里 ConjugateGradient (CG) 的方法优化。此优化器需要说明计算梯度的方法。
Attributes:
cir (UAnsatz): 带可训练参数的量子电路
hamiltonian (list or Hamiltonian): 记录哈密顿量信息的列表或 ``Hamiltonian`` 类的对象
shots (int): 测量次数;默认为 0,表示返回期望值的精确值,即测量无穷次后的期望值
grad_func_name (string): 用来计算梯度的函数的函数名,可以选择 'linear_comb'、'finite_diff' 或 'param_shift',默认为 ``None``
delta (float): 差分法中的 delta,默认为 0.01
"""
def __init__(self, cir, hamiltonian, shots, grad_func_name=None, delta=0.01):
r"""``ConjugateGradient`` 的构造函数。
Args:
cir (UAnsatz): 带可训练参数的量子电路
hamiltonian (list or Hamiltonian): 记录哈密顿量信息的列表或 ``Hamiltonian`` 类的对象
shots (int): 测量次数;默认为 0,表示返回期望值的精确值,即测量无穷次后的期望值
grad_func_name (string, optional): 用来计算梯度的函数的函数名,可以选择 'linear_comb'、'finite_diff' 或 'param_shift',默认为 ``None``
delta (float, optional): 差分法中的 delta,默认为 0.01
"""
super().__init__(cir, hamiltonian, shots, grad_func_name, delta)
def minimize(self, iterations):
r"""最小化给定的损失函数。
Args:
iterations (int): 迭代的次数
"""
opt_res = optimize.minimize(
self.loss_func,
self.cir.get_param().numpy(),
args=(self.cir, self.hamiltonian, self.shots),
method='CG',
jac=self.grad_func,
options={'maxiter': iterations},
callback=lambda xk: print('loss: ', (self.loss_func(xk, self.cir, self.hamiltonian, self.shots)))
)
print(opt_res.message)
# 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.
"""
Custom optimizer
"""
from abc import ABC, abstractmethod
class CustomOptimizer(ABC):
r"""所有 SciPy 优化器的基类。
定义了在用 SciPy 优化器优化时所需的基本功能,如计算期望值和计算梯度的函数。
Attributes:
cir (UAnsatz): 带可训练参数的量子电路
hamiltonian (list or Hamiltonian): 记录哈密顿量信息的列表或 ``Hamiltonian`` 类的对象
shots (int): 测量次数;默认为 0,表示返回期望值的精确值,即测量无穷次后的期望值
grad_func_name (string, optional): 用来计算梯度的函数的函数名,可以选择 'linear_comb'、'finite_diff' 或 'param_shift'。
只有特定需要梯度的 optimizer 如 ``ConjugateGradient`` 需要,默认为 ``None``
delta (float, optional): 差分法中的 delta,默认为 0.01
"""
def __init__(self, cir, hamiltonian, shots, grad_func_name=None, delta=0.01):
r"""``CustomOptimizer`` 的构造函数。
Args:
cir (UAnsatz): 带可训练参数的量子电路
hamiltonian (list or Hamiltonian): 记录哈密顿量信息的列表或 Hamiltonian 类的对象
shots (int): 测量次数;默认为 0,表示返回期望值的精确值,即测量无穷次后的期望值
grad_func_name (string, optional): 用来计算梯度的函数的函数名,可以选择 'linear_comb'、'finite_diff' 或 'param_shift'。
只有特定需要梯度的 optimizer 如 ``ConjugateGradient`` 需要,默认为 ``None``
delta (float, optional): 差分法中的 delta,默认为 0.01
"""
self.cir = cir
self.grad_func_name = grad_func_name
self.hamiltonian = hamiltonian
self.shots = shots
self.delta = delta
self.loss_func = self._get_expec_val_scipy
self.grad_func = None
if self.grad_func_name == 'linear_comb':
self.grad_func = self._linear_combinations_gradient_scipy
elif self.grad_func_name == 'finite_diff':
self.grad_func = self._finite_difference_gradient_scipy
elif self.grad_func_name == 'param_shift':
self.grad_func = self._param_shift_gradient_scipy
else:
assert self.grad_func_name == None, \
"grad_func_name should be None or one of 'linear_comb', 'finite_diff', 'param_shift'"
def _get_expec_val_scipy(self, theta, cir, H, shots=0):
r"""计算关于哈密顿量 H 的期望的理论值。
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
cir.update_param(theta)
return cir.expecval(H, shots).numpy()
def _linear_combinations_gradient_scipy(self, theta, cir, H, shots):
r"""用 linear combination 的方法计算参数的梯度。
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
grad = cir.linear_combinations_gradient(H, shots)
return grad
def _finite_difference_gradient_scipy(self, theta, cir, H, shots):
r"""用差分法计算参数的梯度。
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
grad = cir.finite_difference_gradient(H, self.delta, shots)
return grad
def _param_shift_gradient_scipy(self, theta, cir, H, shots):
r"""用 parameter shift 的方法计算参数的梯度。
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
grad = cir.param_shift_gradient(H, shots)
return grad
@abstractmethod
def minimize(self, iterations):
r"""最小化给定的损失函数。
Args:
iterations (int): 迭代的次数
"""
raise NotImplementedError
# 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.
"""
Newton-CG optimizer
"""
from scipy import optimize
from .custom_optimizer import CustomOptimizer
class NewtonCG(CustomOptimizer):
r"""Newton-CG Optimizer
继承 ``CustomOptimizer`` 类,使用 SciPy 里 Newton conjugate gradient 的方法优化。此优化器需要说明计算梯度的方法。
Attributes:
cir (UAnsatz): 带可训练参数的量子电路
hamiltonian (list or Hamiltonian): 记录哈密顿量信息的列表或 ``Hamiltonian`` 类的对象
shots (int): 测量次数;默认为 0,表示返回期望值的精确值,即测量无穷次后的期望值
grad_func_name (string): 用来计算梯度的函数的函数名,可以选择 'linear_comb'、'finite_diff' 或 'param_shift',默认为 ``None``
delta (float): 差分法中的 delta,默认为 0.01
"""
def __init__(self, cir, hamiltonian, shots, grad_func_name=None, delta=0.01):
r"""``NewtonCG`` 的构造函数。
Args:
cir (UAnsatz): 带可训练参数的量子电路
hamiltonian (list or Hamiltonian): 记录哈密顿量信息的列表或 ``Hamiltonian`` 类的对象
shots (int): 测量次数;默认为 0,表示返回期望值的精确值,即测量无穷次后的期望值
grad_func_name (string): 用来计算梯度的函数的函数名,可以选择 'linear_comb'、'finite_diff' 或 'param_shift',默认为 ``None``
delta (float): 差分法中的 delta,默认为 0.01
"""
super().__init__(cir, hamiltonian, shots, grad_func_name, delta)
def minimize(self, iterations):
r"""最小化给定的损失函数。
Args:
iterations (int): 迭代的次数
"""
opt_res = optimize.minimize(
self.loss_func,
self.cir.get_param().numpy(),
args=(self.cir, self.hamiltonian, self.shots),
method='Newton-CG',
jac=self.grad_func,
options={'maxiter': iterations},
callback=lambda xk: print('loss: ', (self.loss_func(xk, self.cir, self.hamiltonian, self.shots)))
)
print(opt_res.message)
# 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.
"""
Powell optimizer
"""
from scipy import optimize
from .custom_optimizer import CustomOptimizer
class Powell(CustomOptimizer):
r"""Powell Optimizer
继承 ``CustomOptimizer`` 类,使用 SciPy 里 Powell 方法优化。该方法不需要传入计算 gradient 的方式。
Attributes:
cir (UAnsatz): 带可训练参数的量子电路
hamiltonian (list or Hamiltonian): 记录哈密顿量信息的列表或 ``Hamiltonian`` 类的对象
shots (int): 测量次数;默认为 0,表示返回期望值的精确值,即测量无穷次后的期望值
"""
def __init__(self, cir, hamiltonian, shots):
r"""``Powell`` 的构造函数。
Args:
cir (UAnsatz): 带可训练参数的量子电路
hamiltonian (list or Hamiltonian): 记录哈密顿量信息的列表或 ``Hamiltonian`` 类的对象
shots (int): 测量次数;默认为 0,表示返回期望值的精确值,即测量无穷次后的期望值
"""
super().__init__(cir, hamiltonian, shots)
def minimize(self, iterations):
r"""最小化给定的损失函数。
Args:
iterations (int): 迭代的次数
"""
opt_res = optimize.minimize(
self.loss_func,
self.cir.get_param().numpy(),
args=(self.cir, self.hamiltonian, self.shots),
method='Powell',
options={'maxiter': iterations},
callback=lambda xk: print('loss: ', self.loss_func(xk, self.cir, self.hamiltonian, self.shots))
)
print(opt_res.message)
# 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.
"""
SLSQP optimizer
"""
from scipy import optimize
from .custom_optimizer import CustomOptimizer
class SLSQP(CustomOptimizer):
r"""SLSQP Optimizer
继承 ``CustomOptimizer`` 类,使用 SciPy 里 SLSQP 方法优化。该方法不需要传入计算 gradient 的方式。
Attributes:
cir (UAnsatz): 带可训练参数的量子电路
hamiltonian (list or Hamiltonian): 记录哈密顿量信息的列表或 ``Hamiltonian`` 类的对象
shots (int): 测量次数;默认为 0,表示返回期望值的精确值,即测量无穷次后的期望值
"""
def __init__(self, cir, hamiltonian, shots):
r"""``SLSQP`` 的构造函数。
Args:
cir (UAnsatz): 带可训练参数的量子电路
hamiltonian (list or Hamiltonian): 记录哈密顿量信息的列表或 ``Hamiltonian`` 类的对象
shots (int): 测量次数;默认为 0,表示返回期望值的精确值,即测量无穷次后的期望值
"""
super().__init__(cir, hamiltonian, shots)
def minimize(self, iterations):
r"""最小化给定的损失函数。
Args:
iterations (int): 迭代的次数
"""
opt_res = optimize.minimize(
self.loss_func,
self.cir.get_param().numpy(),
args=(self.cir, self.hamiltonian, self.shots),
method='SLSQP',
options={'maxiter': iterations},
callback=lambda xk: print('loss: ', self.loss_func(xk, self.cir, self.hamiltonian, self.shots))
)
print(opt_res.message)
# 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.
"""
shadow sample module
"""
import numpy as np
import paddle
import re
from paddle_quantum import circuit
from paddle_quantum.utils import Hamiltonian
__all__ = [
"shadow_sample"
]
def shadow_sample(state, num_qubits, sample_shots, mode, hamiltonian=None, method='CS'):
r"""对给定的量子态进行随机的泡利测量并返回测量结果。
Args:
state (numpy.ndarray): 输入量子态,支持态矢量和密度矩阵形式
num_qubits (int): 量子比特数量
sample_shots (int): 随机采样的次数
mode (str): 输入量子态的表示方式,``"state_vector"`` 表示态矢量形式, ``"density_matrix"`` 表示密度矩阵形式
hamiltonian (Hamiltonian, optional): 可观测量的相关信息,输入形式为 ``Hamiltonian`` 类,默认为 ``None``
method (str, optional): 进行随机采样的方法,有 ``"CS"`` 、 ``"LBCS"`` 、 ``"APS"`` 三种方法,默认为 ``"CS"``
Returns:
list: 随机选择的泡利测量基和测量结果,形状为 ``(sample_shots, 2)`` 的list
代码示例:
.. code-block:: python
from paddle_quantum.shadow import shadow_sample
from paddle_quantum.state import vec_random
from paddle_quantum.utils import Hamiltonian
n_qubit = 2
sample_shots = 10
state = vec_random(n_qubit)
sample_data_cs = shadow_sample(state, n_qubit, sample_shots, mode='state_vector')
ham = [[0.1, 'x1'], [0.2, 'y0']]
ham = Hamiltonian(ham)
sample_data_lbcs, beta_lbcs = shadow_sample(state, n_qubit, sample_shots, 'state_vector', "LBCS", ham)
sample_data_aps = shadow_sample(state, n_qubit, sample_shots, 'state_vector', "APS", ham)
print('sample data CS = ', sample_data_cs)
print('sample data LBCS = ', sample_data_lbcs)
print('beta LBCS = ', beta_lbcs)
print('sample data APS = ', sample_data_aps)
::
sample data CS = [('zy', '10'), ('yx', '01'), ('zx', '01'), ('xz', '00'), ('zy', '11'), ('xz', '00'), ('xz', '11'), ('yy', '01'), ('yx', '00'), ('xx', '00')]
sample data LBCS = [('yx', '00'), ('yx', '01'), ('yx', '00'), ('yx', '01'), ('yx', '00'), ('yx', '01'), ('yx', '01'), ('yx', '00'), ('yx', '01'), ('yx', '01')]
beta LBCS = [[2.539244934862217e-05, 0.9999492151013026, 2.539244934862217e-05], [0.9999492151013026, 2.539244934862217e-05, 2.539244934862217e-05]]
sample data APS = [('yx', '10'), ('yx', '01'), ('yx', '00'), ('yx', '01'), ('yx', '10'), ('yx', '01'), ('yx', '10'), ('yx', '10'), ('yx', '00'), ('yx', '01')]
"""
if hamiltonian is not None:
if isinstance(hamiltonian, Hamiltonian):
hamiltonian = hamiltonian.pauli_str
def prepare_hamiltonian(hamiltonian, num_qubits):
r"""改写可观测量[[0.3147,'y2'], [-0.5484158742278,'x2,z1'],...]的形式
Args:
hamiltonian (list): 可观测量的相关信息
num_qubits (int): 量子比特数目
Returns:
list: 可观测量的形式改写为[[0.3147,'iiy'], [-0.5484158742278,'izx'],...]
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
new_hamiltonian = list()
for idx, (coeff, pauli_term) in enumerate(hamiltonian):
pauli_term = re.split(r',\s*', pauli_term.lower())
pauli_list = ['i'] * num_qubits
for item in pauli_term:
if len(item) > 1:
pauli_list[int(item[1:])] = item[0]
elif item[0].lower() != 'i':
raise ValueError('Expecting I for ', item[0])
new_term = [coeff, ''.join(pauli_list)]
new_hamiltonian.append(new_term)
return new_hamiltonian
if hamiltonian is not None:
hamiltonian = prepare_hamiltonian(hamiltonian, num_qubits)
pauli2index = {'x': 0, 'y': 1, 'z': 2}
def random_pauli_sample(num_qubits, beta=None):
r"""根据概率分布 beta, 随机选取 pauli 测量基
Args:
num_qubits (int): 量子比特数目
beta (list, optional): 量子位上不同 pauli 测量基的概率分布
Returns:
str: 返回随机选择的 pauli 测量基
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
# assume beta obeys a uniform distribution if it is not given
if beta is None:
beta = list()
for _ in range(0, num_qubits):
beta.append([1 / 3] * 3)
pauli_sample = str()
for qubit_idx in range(num_qubits):
sample = np.random.choice(['x', 'y', 'z'], 1, p=beta[qubit_idx])
pauli_sample += sample[0]
return pauli_sample
def measure_by_pauli_str(pauli_str, phi, num_qubits, method):
r"""搭建 pauli 测量电路,返回测量结果
Args:
pauli_str (str): 输入的是随机选取的num_qubits pauli 测量基
phi (numpy.ndarray): 输入量子态,支持态矢量和密度矩阵形式
num_qubits (int): 量子比特数量
method (str): 进行测量的方法,有 "CS"、"LBCS"、"APS"
Returns:
str: 返回测量结果
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
if method == "clifford":
# Add the clifford function
pass
else:
# Other method are transformed as follows
# Convert to tensor form
input_state = paddle.to_tensor(phi)
cir = circuit.UAnsatz(num_qubits)
for qubit in range(num_qubits):
if pauli_str[qubit] == 'x':
cir.h(qubit)
elif pauli_str[qubit] == 'y':
cir.h(qubit)
cir.s(qubit)
cir.h(qubit)
if mode == 'state_vector':
cir.run_state_vector(input_state)
else:
cir.run_density_matrix(input_state)
result = cir.measure(shots=1)
bit_string, = result
return bit_string
# Define the function used to update the beta of the LBCS algorithm
def calculate_diagonal_product(pauli_str, beta):
r"""迭代 LBCS beta 公式中的一部分
Hint:
计算 \prod_{j \in supp(Q)} \beta_{j}(Q_{j})^{-1}
Args:
pauli_str (str): 输入的是 hamiltonian 的 pauli 项
beta (list): 量子位上不同 pauli 测量基的概率分布
Returns:
float: 返回计算值
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
product = 1
for qubit_idx in range(len(pauli_str)):
if pauli_str[qubit_idx] != 'i':
index = pauli2index[pauli_str[qubit_idx]]
b = beta[qubit_idx][index]
if b == 0:
return float('inf')
else:
product *= b
return 1 / product
def lagrange_restriction_numerator(qubit_idx, hamiltonian, beta):
r"""迭代 LBCS beta 公式中的分子
Hint:
计算 \sum_{Q \mid Q_{i}=P_{i}} \alpha_{Q}^{2} \prod_{j \in supp(Q)} \beta_{j}(Q_{j})^{-1}
Args:
qubit_idx (int): 第 qubit_idx 个量子位
hamiltonian (list): 可观测量的相关信息
beta (list): 量子位上不同 pauli 测量基的概率分布
Returns:
list: 返回第 qubit_idx 个量子位上不同 pauli 算子的在该式下的数值
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
tally = [0, 0, 0]
for coeff, pauli_term in hamiltonian:
if pauli_term[qubit_idx] == 'x':
tally[0] += (coeff ** 2) * calculate_diagonal_product(pauli_term, beta)
elif pauli_term[qubit_idx] == 'y':
tally[1] += (coeff ** 2) * calculate_diagonal_product(pauli_term, beta)
elif pauli_term[qubit_idx] == 'z':
tally[2] += (coeff ** 2) * calculate_diagonal_product(pauli_term, beta)
return tally
def lagrange_restriction_denominator(qubit_idx, random_observable, beta):
r"""迭代 LBCS beta 公式中的分母
Hint:
计算 \sum_{Q \mid Q_{i} \neq I} \alpha_{Q}^{2} \prod_{j \in supp(Q)} \beta_{j}(Q_{j})^{-1}
Args:
qubit_idx (int): 第 qubit_idx 个量子位
random_observable (list): 可观测量的相关信息
beta (list): 量子位上不同 pauli 测量基的概率分布
Returns:
float: 返回第 qubit_idx 个量子位上在该式下的数值
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
tally = 0.0
for coeff, pauli_term in random_observable:
if pauli_term[qubit_idx] != "i":
tally += (coeff ** 2) * calculate_diagonal_product(pauli_term, beta)
if tally == 0.0:
tally = 1
return tally
def lagrange_restriction(qubit_idx, hamiltonian, beta, denominator=None):
r"""迭代 LBCS beta 公式,将分子与分母结合起来
Args:
qubit_idx (int): 第 qubit_idx 个量子位
hamiltonian (list): 可观测量的相关信息
beta (list): 量子位上不同 pauli 测量基的概率分布
denominator (float, optional): 迭代公式的分母,可默认为None
Returns:
list: 返回第 qubit_idx 个量子位上在该式下的数值
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
numerator = lagrange_restriction_numerator(qubit_idx, hamiltonian, beta)
if denominator is None:
denominator = lagrange_restriction_denominator(qubit_idx, hamiltonian, beta)
return [item / denominator for item in numerator]
def beta_distance(beta1, beta2):
r"""计算迭代前后 beta 差距,以便停止迭代
Args:
beta1 (list): 迭代后的全部量子位上的概率分布
beta2 (list): 迭代前的全部量子位上的概率分布
Returns:
numpy.float: 返回迭代前后 beta 差距
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
two_norm_squared = 0.0
for qubit in range(len(beta1)):
two_norm_squared_qubit = np.sum((np.array(beta1[qubit]) - np.array(beta2[qubit])) ** 2)
two_norm_squared += two_norm_squared_qubit
return np.sqrt(two_norm_squared)
def update_beta_in_lbcs(hamiltonian, num_qubit, beta_old=None, weight=0.1):
r"""LBCS 的 beta 迭代函数
Args:
hamiltonian (list): 可观测量的相关信息
num_qubit (int): 量子比特数
beta_old (list): 迭代前的全部量子位上的概率分布
weight (float): 更新的步长,可根据需要修改
Returns:
list: 返回更新后的 beta
numpy.float: 返回迭代前后 beta 差距
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
if beta_old is None:
beta_old = list()
for _ in range(0, num_qubit):
beta_old.append([1 / 3] * 3)
beta_new = list()
for qubit in range(num_qubit):
denominator = lagrange_restriction_denominator(qubit, hamiltonian, beta_old)
lagrange_rest = lagrange_restriction(qubit, hamiltonian, beta_old, denominator)
beta_new.append(lagrange_rest)
if sum(beta_new[qubit]) != 0:
beta_new[qubit] = [item / sum(beta_new[qubit]) for item in beta_new[qubit]]
else:
beta_new[qubit] = beta_old[qubit]
for idx in range(len(beta_new[qubit])):
beta_new[qubit][idx] = (1 - weight) * beta_old[qubit][idx] + weight * beta_new[qubit][idx]
return beta_new, beta_distance(beta_new, beta_old)
# Define the function used to update the beta of the APS algorithm
def in_omega(pauli_str, qubit_idx, qubit_shift, base_shift):
r"""用于判断 hamiltonian 的 pauli 项是否属于集合Omega
Args:
pauli_str (str): 可观测量的 pauli 项
qubit_idx (int): 第 qubit_idx 量子位
qubit_shift (list): 乱序重排量子位,比如第1位映射到第4位
base_shift (float): 乱序排放的 pauli 测量基
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
if pauli_str[qubit_shift[qubit_idx]] == 'i':
return False
for former_qubit in range(qubit_idx):
idx = qubit_shift[former_qubit]
if not pauli_str[idx] in ('i', base_shift[former_qubit]):
return False
return True
def update_in_aps(qubit_idx, qubits_shift, bases_shift, hamiltonian):
r"""用于更新 APS 的 beta
Args:
qubit_idx (int): 第 qubit_idx 量子位
qubit_shift (list): 乱序重排量子位,比如第1位映射到第4位
base_shift (float): 乱序排放的 pauli 测量基
hamiltonian (list): 可观测量的相关信息
Returns:
list: 返回更新后的 beta
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
constants = [0.0, 0.0, 0.0]
for coeff, pauli_term in hamiltonian:
if in_omega(pauli_term, qubit_idx, qubits_shift, bases_shift):
pauli = pauli_term[qubits_shift[qubit_idx]]
index = pauli2index[pauli]
constants[index] += coeff ** 2
beta_sqrt = np.sqrt(constants)
# The beta may be zero, use a judgment statement to avoid
if np.sum(beta_sqrt) == 0.0:
beta = [1 / 3, 1 / 3, 1 / 3]
else:
beta = beta_sqrt / np.sum(beta_sqrt)
return beta
def single_random_pauli_sample_in_aps(qubit_idx, qubits_shift, pauli_str_shift, hamiltonian):
r"""用于在单量子位上根据概率分布随机选取 pauli 测量基
Args:
qubit_idx (int): 第 qubit_idx 量子位
qubit_shift (list): 乱序重排量子位,比如第1位映射到第4位
base_shift (float): 乱序排放的 pauli 测量基
hamiltonian (list): 可观测量的相关信息
Returns:
str: 返回在第 qubit_idx 量子位上选取的 pauli 测量基
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
assert len(pauli_str_shift) == qubit_idx
beta = update_in_aps(qubit_idx, qubits_shift, pauli_str_shift, hamiltonian)
single_pauli = np.random.choice(['x', 'y', 'z'], p=beta)
return single_pauli
def random_pauli_sample_in_aps(hamiltonian):
r"""用于根据概率分布随机选择所有量子位上的 pauli 测量基
Args:
hamiltonian (list): 可观测量的相关信息
Returns:
list: 返回所有量子位上随机选择的 pauli 测量基
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
num_qubits = len(hamiltonian[0][1])
# The qubits_shift is used to ignore the order of the qubits
qubits_shift = list(np.random.choice(range(num_qubits), size=num_qubits, replace=False))
pauli_str_shift = list()
for qubit_idx in range(num_qubits):
single_pauli = single_random_pauli_sample_in_aps(qubit_idx, qubits_shift, pauli_str_shift, hamiltonian)
pauli_str_shift.append(single_pauli)
pauli_sample = str()
for i in range(num_qubits):
j = qubits_shift.index(i)
# The qubits_shift.index(i) sorts the qubits in order
pauli_sample = pauli_sample + pauli_str_shift[j]
return pauli_sample
sample_result = list()
if method == "CS":
for _ in range(sample_shots):
random_pauli_str = random_pauli_sample(num_qubits, beta=None)
measurement_result = measure_by_pauli_str(random_pauli_str, state, num_qubits, method)
sample_result.append((random_pauli_str, measurement_result))
return sample_result
elif method == "LBCS":
beta = list()
for _ in range(0, num_qubits):
beta.append([1 / 3] * 3)
beta_opt_iter_num = 10000
distance_limit = 1.0e-6
for _ in range(beta_opt_iter_num):
beta_opt, distance = update_beta_in_lbcs(hamiltonian, num_qubits, beta)
beta = beta_opt
if distance < distance_limit:
break
sample_result = list()
for _ in range(sample_shots):
random_pauli_str = random_pauli_sample(num_qubits, beta)
measurement_result = measure_by_pauli_str(random_pauli_str, state, num_qubits, method)
sample_result.append((random_pauli_str, measurement_result))
return sample_result, beta
elif method == "APS":
for _ in range(sample_shots):
random_pauli_str = random_pauli_sample_in_aps(hamiltonian)
measurement_result = measure_by_pauli_str(random_pauli_str, state, num_qubits, method)
sample_result.append((random_pauli_str, measurement_result))
return sample_result
...@@ -186,6 +186,50 @@ def u_gate_matrix(params): ...@@ -186,6 +186,50 @@ def u_gate_matrix(params):
# paddle.to_tensor(np.array([3.0]))]) # paddle.to_tensor(np.array([3.0]))])
# print(a.numpy()) # print(a.numpy())
def cu_gate_matrix(params):
"""
Control U3
:return:
"""
theta, phi, lam = params
if (type(theta) is paddle.Tensor and
type(phi) is paddle.Tensor and
type(lam) is paddle.Tensor):
re_a = paddle.cos(theta / 2)
re_b = - paddle.cos(lam) * paddle.sin(theta / 2)
re_c = paddle.cos(phi) * paddle.sin(theta / 2)
re_d = paddle.cos(phi + lam) * paddle.cos(theta / 2)
im_a = paddle.zeros([1], 'float64')
im_b = - paddle.sin(lam) * paddle.sin(theta / 2)
im_c = paddle.sin(phi) * paddle.sin(theta / 2)
im_d = paddle.sin(phi + lam) * paddle.cos(theta / 2)
re = paddle.reshape(paddle.concat([re_a, re_b, re_c, re_d]), [2, 2])
im = paddle.reshape(paddle.concat([im_a, im_b, im_c, im_d]), [2, 2])
id = paddle.eye(2, dtype='float64')
z2 = paddle.zeros(shape=[2,2], dtype='float64')
re = paddle.concat([paddle.concat([id, z2], -1), paddle.concat([z2, re], -1)])
im = paddle.concat([paddle.concat([z2, z2], -1), paddle.concat([z2, im], -1)])
return re + im * paddle.to_tensor([1j], 'complex128')
elif (type(theta) is float and
type(phi) is float and
type(lam) is float):
u3 = np.array([[np.cos(theta / 2),
-np.exp(1j * lam) * np.sin(theta / 2)],
[np.exp(1j * phi) * np.sin(theta / 2),
np.exp(1j * phi + 1j * lam) * np.cos(theta / 2)]])
return np.block([
[np.eye(2, dtype=float), np.zeros((2, 2), dtype=float)], [np.zeros((2, 2), dtype=float), u3]
]).reshape((2,2,2,2))
else:
assert False
def cx_gate_matrix(): def cx_gate_matrix():
""" """
...@@ -198,6 +242,28 @@ def cx_gate_matrix(): ...@@ -198,6 +242,28 @@ def cx_gate_matrix():
[0, 0, 1, 0]], dtype=complex).reshape((2, 2, 2, 2)) [0, 0, 1, 0]], dtype=complex).reshape((2, 2, 2, 2))
def cy_gate_matrix():
"""
Control Y
:return:
"""
return np.array([[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, -1j],
[0, 0, 1j, 0]], dtype=complex).reshape((2, 2, 2, 2))
def cz_gate_matrix():
"""
Control Z
:return:
"""
return np.array([[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, -1]], dtype=complex).reshape((2, 2, 2, 2))
def swap_gate_matrix(): def swap_gate_matrix():
""" """
Swap gate Swap gate
...@@ -214,7 +280,7 @@ def rxx_gate_matrix(params): ...@@ -214,7 +280,7 @@ def rxx_gate_matrix(params):
RXX gate RXX gate
:return: :return:
""" """
theta = params theta = params[0]
re_a = paddle.cos(theta / 2) re_a = paddle.cos(theta / 2)
re_b = paddle.zeros([1], 'float64') re_b = paddle.zeros([1], 'float64')
im_a = paddle.sin(theta / 2) im_a = paddle.sin(theta / 2)
...@@ -236,7 +302,7 @@ def ryy_gate_matrix(params): ...@@ -236,7 +302,7 @@ def ryy_gate_matrix(params):
RYY gate RYY gate
:return: :return:
""" """
theta = params theta = params[0]
re_a = paddle.cos(theta / 2) re_a = paddle.cos(theta / 2)
re_b = paddle.zeros([1], 'float64') re_b = paddle.zeros([1], 'float64')
im_a = paddle.sin(theta / 2) im_a = paddle.sin(theta / 2)
...@@ -258,7 +324,7 @@ def rzz_gate_matrix(params): ...@@ -258,7 +324,7 @@ def rzz_gate_matrix(params):
RZZ gate RZZ gate
:return: :return:
""" """
theta = params theta = params[0]
re_a = paddle.cos(theta / 2) re_a = paddle.cos(theta / 2)
re_b = paddle.zeros([1], 'float64') re_b = paddle.zeros([1], 'float64')
im_a = paddle.sin(theta / 2) im_a = paddle.sin(theta / 2)
...@@ -274,6 +340,7 @@ def rzz_gate_matrix(params): ...@@ -274,6 +340,7 @@ def rzz_gate_matrix(params):
return re + im * paddle.to_tensor([1j], 'complex128') return re + im * paddle.to_tensor([1j], 'complex128')
# PaddleE # PaddleE
def normalize_axis(axis, ndim): def normalize_axis(axis, ndim):
if axis < 0: if axis < 0:
...@@ -366,6 +433,86 @@ def complex_moveaxis(m, source, destination): ...@@ -366,6 +433,86 @@ def complex_moveaxis(m, source, destination):
# #
# return m # return m
def get_cswap_state(state, bits):
"""
Transfer to the next state after applying CSWAP gate
:param state:
:param bits:
:return:
"""
q0 = bits[0]
q1 = bits[1]
q2 = bits[2]
# helper angles
theta0 = paddle.to_tensor(np.array([0.0]))
thetan2 = paddle.to_tensor(np.array([-np.pi / 2], np.float64))
theta2 = paddle.to_tensor(np.array([np.pi / 2], np.float64))
thetan4 = paddle.to_tensor(np.array([-np.pi / 4], np.float64))
theta4 = paddle.to_tensor(np.array([np.pi / 4], np.float64))
# implements cswap gate using cnot and Single-qubit gates
state = transfer_state(state, cx_gate_matrix(), [q2, q1])
state = transfer_state(state, u_gate_matrix([thetan2, theta0, theta0]), [q2]) # ry
state = transfer_state(state, u_gate_matrix([theta0, theta0, theta4]), [q0]) # rz
state = transfer_state(state, u_gate_matrix([theta0, theta0, theta4]), [q1]) # rz
state = transfer_state(state, u_gate_matrix([theta0, theta0, theta4]), [q2]) # rz
state = transfer_state(state, cx_gate_matrix(), [q0, q1])
state = transfer_state(state, cx_gate_matrix(), [q1, q2])
state = transfer_state(state, u_gate_matrix([theta0, theta0, thetan4]), [q1]) # rz
state = transfer_state(state, u_gate_matrix([theta0, theta0, theta4]), [q2]) # rz
state = transfer_state(state, cx_gate_matrix(), [q0, q1])
state = transfer_state(state, cx_gate_matrix(), [q1, q2])
state = transfer_state(state, cx_gate_matrix(), [q0, q1])
state = transfer_state(state, u_gate_matrix([theta0, theta0, thetan4]), [q2]) # rz
state = transfer_state(state, cx_gate_matrix(), [q1, q2])
state = transfer_state(state, u_gate_matrix([theta2, thetan2, theta2]), [q1]) # rx
state = transfer_state(state, u_gate_matrix([theta0, theta0, thetan4]), [q2]) # rz
state = transfer_state(state, cx_gate_matrix(), [q0, q1])
state = transfer_state(state, cx_gate_matrix(), [q1, q2])
state = transfer_state(state, u_gate_matrix([theta0, theta0, theta2]), [q2]) # rz
state = transfer_state(state, u_gate_matrix([theta2, thetan2, theta2]), [q1]) # rx
state = transfer_state(state, u_gate_matrix([thetan2, thetan2, theta2]), [q2]) # rx
return state
def get_ccx_state(state, bits):
"""
Transfer to the next state after applying CCX gate
:param state:
:param bits:
:return:
"""
q0 = bits[0]
q1 = bits[1]
q2 = bits[2]
# helper angles
theta0 = paddle.to_tensor(np.array([0.0]))
theta2 = paddle.to_tensor(np.array([np.pi / 2], np.float64))
thetan4 = paddle.to_tensor(np.array([-np.pi / 4], np.float64))
theta4 = paddle.to_tensor(np.array([np.pi / 4], np.float64))
# implements ccx gate using cnot and Single-qubit gates
state = transfer_state(state, h_gate_matrix(), [q2]) #h
state = transfer_state(state, cx_gate_matrix(), [q1, q2]) # cx
state = transfer_state(state, u_gate_matrix([theta0, theta0, thetan4]), [q2]) # tdagger
state = transfer_state(state, cx_gate_matrix(), [q0, q2]) #cx
state = transfer_state(state, u_gate_matrix([theta0, theta0, theta4]), [q2]) # t
state = transfer_state(state, cx_gate_matrix(), [q1, q2]) #cx
state = transfer_state(state, u_gate_matrix([theta0, theta0, thetan4]), [q2]) # tdagger
state = transfer_state(state, cx_gate_matrix(), [q0, q2]) # cx
state = transfer_state(state, u_gate_matrix([theta0, theta0, thetan4]), [q1]) # tdagger
state = transfer_state(state, u_gate_matrix([theta0, theta0, theta4]), [q2]) # t
state = transfer_state(state, h_gate_matrix(), [q2]) # h
state = transfer_state(state, cx_gate_matrix(), [q0, q1]) #cx
state = transfer_state(state, u_gate_matrix([theta0, theta0, thetan4]), [q1]) # tdagger
state = transfer_state(state, cx_gate_matrix(), [q0, q1]) #cx
state = transfer_state(state, u_gate_matrix([theta0, theta0, theta4]), [q0]) # t
state = transfer_state(state, u_gate_matrix([theta0, theta0, theta2]), [q1]) # s
return state
# TransferProcess # TransferProcess
def transfer_state(state, gate_matrix, bits): def transfer_state(state, gate_matrix, bits):
...@@ -394,8 +541,10 @@ def transfer_state(state, gate_matrix, bits): ...@@ -394,8 +541,10 @@ def transfer_state(state, gate_matrix, bits):
# compressed moveaxis # compressed moveaxis
# compress the continuous dim before moveaxis # compress the continuous dim before moveaxis
# e.g. single operand: before moveaxis 2*2*[2]*2*2 -compress-> 4*[2]*4, after moveaxis [2]*2*2*2*2 -compress-> [2]*4*4 # e.g. single operand: before moveaxis 2*2*[2]*2*2 -compress-> 4*[2]*4,
# double operands: before moveaxis 2*2*[2]*2*2*[2]*2*2 -compress-> 4*[2]*4*[2]*4, after moveaxis [2]*[2]*2*2*2*2*2*2 -compress-> [2]*[2]*4*4*4 # after moveaxis [2]*2*2*2*2 -compress-> [2]*4*4
# double operands: before moveaxis 2*2*[2]*2*2*[2]*2*2 -compress-> 4*[2]*4*[2]*4,
# after moveaxis [2]*[2]*2*2*2*2*2*2 -compress-> [2]*[2]*4*4*4
# the peak rank is 5 when the number of operands is 2 # the peak rank is 5 when the number of operands is 2
assert len(source_pos) == 1 or len(source_pos) == 2 assert len(source_pos) == 1 or len(source_pos) == 2
compressed_shape_before_moveaxis = [1] compressed_shape_before_moveaxis = [1]
...@@ -469,6 +618,15 @@ def StateTransfer(state, gate_name, bits, params=None): ...@@ -469,6 +618,15 @@ def StateTransfer(state, gate_name, bits, params=None):
elif gate_name == 'CNOT': elif gate_name == 'CNOT':
# print('----------', gate_name, bits, '----------') # print('----------', gate_name, bits, '----------')
gate_matrix = cx_gate_matrix() gate_matrix = cx_gate_matrix()
elif gate_name == 'CU':
# print('----------', gate_name, bits, '----------')
gate_matrix = cu_gate_matrix(params)
elif gate_name == 'cy':
# print('----------', gate_name, bits, '----------')
gate_matrix = cy_gate_matrix()
elif gate_name == 'cz':
# print('----------', gate_name, bits, '----------')
gate_matrix = cz_gate_matrix()
elif gate_name == 'SWAP': elif gate_name == 'SWAP':
# print('----------', gate_name, bits, '----------') # print('----------', gate_name, bits, '----------')
gate_matrix = swap_gate_matrix() gate_matrix = swap_gate_matrix()
...@@ -484,6 +642,14 @@ def StateTransfer(state, gate_name, bits, params=None): ...@@ -484,6 +642,14 @@ def StateTransfer(state, gate_name, bits, params=None):
elif gate_name == 'RZZ_gate': elif gate_name == 'RZZ_gate':
# print('----------', gate_name, bits, '----------') # print('----------', gate_name, bits, '----------')
gate_matrix = rzz_gate_matrix(params) gate_matrix = rzz_gate_matrix(params)
elif gate_name == 'CSWAP':
# print('----------', gate_name, bits, '----------')
state = get_cswap_state(state, bits)
return state
elif gate_name == 'CCX':
# print('----------', gate_name, bits, '----------')
state = get_ccx_state(state, bits)
return state
else: else:
raise Exception("Gate name error") raise Exception("Gate name error")
......
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
# limitations under the License. # limitations under the License.
import numpy as np import numpy as np
from numpy import concatenate
from numpy import trace as np_trace from numpy import trace as np_trace
from numpy import matmul as np_matmul from numpy import matmul as np_matmul
from numpy import random as np_random from numpy import random as np_random
...@@ -36,29 +35,32 @@ __all__ = [ ...@@ -36,29 +35,32 @@ __all__ = [
] ]
def vec(n): def vec(i, n):
r"""生成量子态 :math:`|00...0\rangle` 的numpy形式 r"""生成计算基态 :math:`|e_{i}\rangle` 的 numpy 形式,其中 :math:`|e_{i}\rangle` 的第 :math:`i` 个元素为 1,其余元素为 0
Args: Args:
n(int): 量子比特数量 i(int): 计算基态 :math`|e_{i}\rangle` 的下标 :math:`i`
n(int): 生成的量子态的量子比特数量
Returns: Returns:
numpy.ndarray: 一个形状为 ``(1, 2**n)`` 的numpy数组 ``[[1, 0, 0, ..., 0]]`` numpy.ndarray: 计算基态 :math:`|e_{i}\rangle` 的态矢量形式。
代码示例: 代码示例:
.. code-block:: python .. code-block:: python
from paddle_quantum.state import vec from paddle_quantum.state import vec
vector = vec(3) vector = vec(1, 3)
print(vector) print(vector)
:: ::
[[1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]] [[0.+0.j 1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]]
""" """
assert n > 0, 'qubit number must be larger than 1' assert n > 0, 'qubit number must be larger than 1'
state = concatenate(([[1.0]], np_zeros([1, (2 ** n) - 1])), axis=1) assert 0 <= i <= 2 ** n - 1, 'i should >= 0 and < 2**n (the dimension of the Hilbert space)'
state = np_zeros([1, 2 ** n])
state[0][i] = 1
return state.astype("complex128") return state.astype("complex128")
......
...@@ -14,7 +14,14 @@ ...@@ -14,7 +14,14 @@
from functools import reduce from functools import reduce
from math import log2 from math import log2
from math import sqrt
import os.path
import copy
import re
import numpy as np import numpy as np
from scipy.linalg import logm, sqrtm
from matplotlib import colors as mplcolors
import matplotlib.pyplot as plt
import paddle import paddle
from paddle import add, to_tensor from paddle import add, to_tensor
from paddle import kron as kron from paddle import kron as kron
...@@ -23,6 +30,12 @@ from paddle import transpose ...@@ -23,6 +30,12 @@ from paddle import transpose
from paddle import concat, ones from paddle import concat, ones
from paddle import zeros from paddle import zeros
from scipy.linalg import logm, sqrtm from scipy.linalg import logm, sqrtm
from scipy import sparse
import matplotlib.pyplot as plt
import matplotlib as mpl
from math import sqrt
from paddle_quantum import simulator
import matplotlib.animation as animation
__all__ = [ __all__ = [
"partial_trace", "partial_trace",
...@@ -39,7 +52,10 @@ __all__ = [ ...@@ -39,7 +52,10 @@ __all__ = [
"partial_transpose", "partial_transpose",
"negativity", "negativity",
"logarithmic_negativity", "logarithmic_negativity",
"is_ppt" "is_ppt",
"Hamiltonian",
"plot_state_in_bloch_sphere",
"plot_rotation_in_bloch_sphere",
] ]
...@@ -100,7 +116,7 @@ def partial_trace_discontiguous(rho, preserve_qubits=None): ...@@ -100,7 +116,7 @@ def partial_trace_discontiguous(rho, preserve_qubits=None):
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)
...@@ -117,7 +133,7 @@ def partial_trace_discontiguous(rho, preserve_qubits=None): ...@@ -117,7 +133,7 @@ def partial_trace_discontiguous(rho, preserve_qubits=None):
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="complex64") result = np.zeros((2 ** num_preserve, 2 ** num_preserve), dtype="complex64")
result = paddle.to_tensor(result) result = paddle.to_tensor(result)
...@@ -166,8 +182,8 @@ def gate_fidelity(U, V): ...@@ -166,8 +182,8 @@ def gate_fidelity(U, V):
""" """
assert U.shape == V.shape, 'The shape of two unitary matrices are different' assert U.shape == V.shape, 'The shape of two unitary matrices are different'
dim = U.shape[0] dim = U.shape[0]
fidelity = np.absolute(np.trace(np.matmul(U, V.conj().T)))/dim fidelity = np.absolute(np.trace(np.matmul(U, V.conj().T))) / dim
return fidelity return fidelity
...@@ -185,9 +201,9 @@ def purity(rho): ...@@ -185,9 +201,9 @@ def purity(rho):
float: 输入的量子态的纯度 float: 输入的量子态的纯度
""" """
gamma = np.trace(np.matmul(rho, rho)) gamma = np.trace(np.matmul(rho, rho))
return gamma.real return gamma.real
def von_neumann_entropy(rho): def von_neumann_entropy(rho):
r"""计算量子态的冯诺依曼熵。 r"""计算量子态的冯诺依曼熵。
...@@ -203,8 +219,8 @@ def von_neumann_entropy(rho): ...@@ -203,8 +219,8 @@ def von_neumann_entropy(rho):
float: 输入的量子态的冯诺依曼熵 float: 输入的量子态的冯诺依曼熵
""" """
rho_eigenvalue, _ = np.linalg.eig(rho) rho_eigenvalue, _ = np.linalg.eig(rho)
entropy = -np.sum(rho_eigenvalue*np.log(rho_eigenvalue)) entropy = -np.sum(rho_eigenvalue * np.log(rho_eigenvalue))
return entropy.real return entropy.real
...@@ -296,12 +312,12 @@ def random_pauli_str_generator(n, terms=3): ...@@ -296,12 +312,12 @@ def random_pauli_str_generator(n, terms=3):
list: 随机生成的可观测量的列表形式 list: 随机生成的可观测量的列表形式
""" """
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):
# Tips: -1 <= coeff < 1 # Tips: -1 <= coeff < 1
coeff = np.random.rand()*2-1 coeff = np.random.rand() * 2 - 1
ops = np.random.choice(['x', 'y', 'z'], size=sublen) ops = np.random.choice(['x', 'y', 'z'], size=sublen)
pos = np.random.choice(range(n), size=sublen, replace=False) pos = np.random.choice(range(n), size=sublen, replace=False)
op_list = [ops[i]+str(pos[i]) for i in range(sublen)] op_list = [ops[i] + str(pos[i]) for i in range(sublen)]
pauli_str.append([coeff, ','.join(op_list)]) pauli_str.append([coeff, ','.join(op_list)])
return pauli_str return pauli_str
...@@ -326,12 +342,15 @@ def pauli_str_to_matrix(pauli_str, n): ...@@ -326,12 +342,15 @@ def pauli_str_to_matrix(pauli_str, n):
# Parse pauli_str; 'x0,z1,y4' to 'xziiy' # Parse pauli_str; 'x0,z1,y4' to 'xziiy'
new_pauli_str = [] new_pauli_str = []
for coeff, op_str in pauli_str: for coeff, op_str in pauli_str:
init = list('i'*n) init = list('i' * n)
op_list = op_str.split(',') op_list = re.split(r',\s*', op_str.lower())
for op in op_list: for op in op_list:
pos = int(op[1:]) if len(op) > 1:
assert pos < n, 'n is too small' pos = int(op[1:])
init[pos] = op[0] assert pos < n, 'n is too small'
init[pos] = op[0]
elif op.lower() != 'i':
raise ValueError('Only Pauli operator "I" can be accepted without specifying its position')
new_pauli_str.append([coeff, ''.join(init)]) new_pauli_str.append([coeff, ''.join(init)])
# Convert new_pauli_str to matrix; 'xziiy' to NKron(x, z, i, i, y) # Convert new_pauli_str to matrix; 'xziiy' to NKron(x, z, i, i, y)
...@@ -339,7 +358,7 @@ def pauli_str_to_matrix(pauli_str, n): ...@@ -339,7 +358,7 @@ def pauli_str_to_matrix(pauli_str, n):
for coeff, op_str in new_pauli_str: for coeff, op_str in new_pauli_str:
sub_matrices = [] sub_matrices = []
for op in op_str: for op in op_str:
sub_matrices.append(pauli_dict[op]) sub_matrices.append(pauli_dict[op.lower()])
if len(op_str) == 1: if len(op_str) == 1:
matrices.append(coeff * sub_matrices[0]) matrices.append(coeff * sub_matrices[0])
else: else:
...@@ -380,7 +399,7 @@ def partial_transpose_2(density_op, sub_system=None): ...@@ -380,7 +399,7 @@ def partial_transpose_2(density_op, sub_system=None):
if sys_idx == 2: if sys_idx == 2:
for j in [0, 2]: for j in [0, 2]:
for i in [0, 2]: for i in [0, 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()
else: else:
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]
...@@ -400,9 +419,9 @@ def partial_transpose(density_op, n): ...@@ -400,9 +419,9 @@ def partial_transpose(density_op, n):
# Copy the density matrix and not corrupt the original one # Copy the density matrix and not corrupt the original one
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 transposed_density_op return transposed_density_op
...@@ -467,7 +486,7 @@ def logarithmic_negativity(density_op): ...@@ -467,7 +486,7 @@ def logarithmic_negativity(density_op):
n = negativity(density_op) n = negativity(density_op)
# Calculate through the equivalent expression # Calculate through the equivalent expression
log2_n = np.log2(2*n + 1) log2_n = np.log2(2 * n + 1)
return log2_n return log2_n
...@@ -499,4 +518,823 @@ def is_ppt(density_op): ...@@ -499,4 +518,823 @@ def is_ppt(density_op):
# Detect negative eigenvalues from the partial transposed density_op # Detect negative eigenvalues from the partial transposed density_op
if negativity(density_op) > 0: if negativity(density_op) > 0:
ppt = False ppt = False
return ppt return ppt
\ No newline at end of file
class Hamiltonian:
r""" Paddle Quantum 中的 Hamiltonian ``class``。
用户可以通过一个 Pauli string 来实例化该 ``class``。
"""
def __init__(self, pauli_str, compress=True):
r""" 创建一个 Hamiltonian 类。
Args:
pauli_str (list): 用列表定义的 Hamiltonian,如 ``[(1, 'Z0, Z1'), (2, 'I')]``
compress (bool): 是否对输入的 list 进行自动合并(例如 ``(1, 'Z0, Z1')`` 和 ``(2, 'Z1, Z0')`` 这两项将被自动合并),默认为 ``True``
Note:
如果设置 ``compress=False``,则不会对输入的合法性进行检验。
"""
self.__coefficients = None
self.__terms = None
self.__pauli_words_r = []
self.__pauli_words = []
self.__sites = []
self.__nqubits = None
# when internally updating the __pauli_str, be sure to set __update_flag to True
self.__pauli_str = pauli_str
self.__update_flag = True
self.__decompose()
if compress:
self.__compress()
def __getitem__(self, indices):
new_pauli_str = []
if isinstance(indices, int):
indices = [indices]
elif isinstance(indices, slice):
indices = list(range(self.n_terms)[indices])
elif isinstance(indices, tuple):
indices = list(indices)
for index in indices:
new_pauli_str.append([self.coefficients[index], ','.join(self.terms[index])])
return Hamiltonian(new_pauli_str, compress=False)
def __add__(self, h_2):
new_pauli_str = self.pauli_str.copy()
if isinstance(h_2, float) or isinstance(h_2, int):
new_pauli_str.extend([[float(h_2), 'I']])
else:
new_pauli_str.extend(h_2.pauli_str)
return Hamiltonian(new_pauli_str)
def __mul__(self, other):
new_pauli_str = copy.deepcopy(self.pauli_str)
for i in range(len(new_pauli_str)):
new_pauli_str[i][0] *= other
return Hamiltonian(new_pauli_str, compress=False)
def __sub__(self, other):
return self.__add__(other.__mul__(-1))
def __str__(self):
str_out = ''
for idx in range(self.n_terms):
str_out += '{} '.format(self.coefficients[idx])
for _ in range(len(self.terms[idx])):
str_out += self.terms[idx][_]
if _ != len(self.terms[idx]) - 1:
str_out += ', '
if idx != self.n_terms - 1:
str_out += '\n'
return str_out
def __repr__(self):
return 'paddle_quantum.Hamiltonian object: \n' + self.__str__()
@property
def n_terms(self):
r"""返回该哈密顿量的项数
Returns:
int :哈密顿量的项数
"""
return len(self.__pauli_str)
@property
def pauli_str(self):
r"""返回哈密顿量对应的 Pauli string
Returns:
list : 哈密顿量对应的 Pauli string
"""
return self.__pauli_str
@property
def terms(self):
r"""返回哈密顿量中的每一项构成的列表
Returns:
list :哈密顿量中的每一项,i.e. ``[['Z0, Z1'], ['I']]``
"""
if self.__update_flag:
self.__decompose()
return self.__terms
else:
return self.__terms
@property
def coefficients(self):
r""" 返回哈密顿量中的每一项对应的系数构成的列表
Returns:
list :哈密顿量中每一项的系数,i.e.``[1.0, 2.0]``
"""
if self.__update_flag:
self.__decompose()
return self.__coefficients
else:
return self.__coefficients
@property
def pauli_words(self):
r"""返回哈密顿量中每一项对应的 Pauli word 构成的列表
Returns:
list :每一项对应的 Pauli word,i.e. ``['ZIZ', 'IIX']``
"""
if self.__update_flag:
self.__decompose()
return self.__pauli_words
else:
return self.__pauli_words
@property
def pauli_words_r(self):
r"""返回哈密顿量中每一项对应的简化(不包含 I) Pauli word 组成的列表
Returns:
list :不包含 "I" 的 Pauli word 构成的列表,i.e. ``['ZXZZ', 'Z', 'X']``
"""
if self.__update_flag:
self.__decompose()
return self.__pauli_words_r
else:
return self.__pauli_words_r
@property
def sites(self):
r"""返回该哈密顿量中的每一项对应的量子比特编号组成的列表
Returns:
list :哈密顿量中每一项所对应的量子比特编号构成的列表,i.e. ``[[1, 2], [0]]``
"""
if self.__update_flag:
self.__decompose()
return self.__sites
else:
return self.__sites
@property
def n_qubits(self):
r"""返回该哈密顿量对应的量子比特数
Returns:
int :量子比特数
"""
if self.__update_flag:
self.__decompose()
return self.__nqubits
else:
return self.__nqubits
def __decompose(self):
r"""将哈密顿量分解为不同的形式
Notes:
这是一个内部函数,你不需要直接使用它
这是一个比较基础的函数,它负责将输入的 Pauli string 拆分为不同的形式并存储在内部变量中
"""
self.__pauli_words = []
self.__pauli_words_r = []
self.__sites = []
self.__terms = []
self.__coefficients = []
self.__nqubits = 1
new_pauli_str = []
for coefficient, pauli_term in self.__pauli_str:
pauli_word_r = ''
site = []
single_pauli_terms = re.split(r',\s*', pauli_term.upper())
self.__coefficients.append(float(coefficient))
self.__terms.append(single_pauli_terms)
for single_pauli_term in single_pauli_terms:
match_I = re.match(r'I', single_pauli_term, flags=re.I)
if match_I:
assert single_pauli_term[0].upper() == 'I', \
'The offset is defined with a sole letter "I", i.e. (3.0, "I")'
pauli_word_r += 'I'
site.append('')
else:
match = re.match(r'([XYZ])([0-9]+)', single_pauli_term, flags=re.I)
if match:
pauli_word_r += match.group(1).upper()
assert int(match.group(2)) not in site, 'each Pauli operator should act on different qubit'
site.append(int(match.group(2)))
else:
raise 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"')
self.__nqubits = max(self.__nqubits, int(match.group(2)) + 1)
self.__pauli_words_r.append(pauli_word_r)
self.__sites.append(site)
new_pauli_str.append([float(coefficient), pauli_term.upper()])
for term_index in range(len(self.__pauli_str)):
pauli_word = ['I' for _ in range(self.__nqubits)]
site = self.__sites[term_index]
for site_index in range(len(site)):
if type(site[site_index]) == int:
pauli_word[site[site_index]] = self.__pauli_words_r[term_index][site_index]
self.__pauli_words.append(''.join(pauli_word))
self.__pauli_str = new_pauli_str
self.__update_flag = False
def __compress(self):
r""" 对同类项进行合并。
Notes:
这是一个内部函数,你不需要直接使用它
"""
if self.__update_flag:
self.__decompose()
else:
pass
new_pauli_str = []
flag_merged = [False for _ in range(self.n_terms)]
for term_idx_1 in range(self.n_terms):
if not flag_merged[term_idx_1]:
for term_idx_2 in range(term_idx_1 + 1, self.n_terms):
if not flag_merged[term_idx_2]:
if self.pauli_words[term_idx_1] == self.pauli_words[term_idx_2]:
self.__coefficients[term_idx_1] += self.__coefficients[term_idx_2]
flag_merged[term_idx_2] = True
else:
pass
if self.__coefficients[term_idx_1] != 0:
new_pauli_str.append([self.__coefficients[term_idx_1], ','.join(self.__terms[term_idx_1])])
self.__pauli_str = new_pauli_str
self.__update_flag = True
def decompose_with_sites(self):
r"""将 pauli_str 分解为系数,泡利字符串的简化形式,以及它们分别作用的量子比特下标
Returns:
tuple: 包含如下元素的 tuple
coefficients (list): 元素为每一项的系数
pauli_words_r (list): 元素为每一项的泡利字符串的简化形式,例如 'Z0, Z1, X3' 这一项的泡利字符串为 'ZZX'
sites (list): 元素为每一项作用的量子比特下标,例如 'Z0, Z1, X3' 这一项的 site 为 [0, 1, 3]
"""
if self.__update_flag:
self.__decompose()
return self.coefficients, self.__pauli_words_r, self.__sites
def decompose_pauli_words(self):
r"""将 pauli_str 分解为系数,泡利字符串,以及它们分别作用的量子比特下标
Returns:
tuple: 包含如下元素的 tuple
coefficients(list): 元素为每一项的系数
pauli_words(list): 元素为每一项的泡利字符串,例如 'Z0, Z1, X3' 这一项的泡利字符串为 'ZZIX'
"""
if self.__update_flag:
self.__decompose()
else:
pass
return self.coefficients, self.__pauli_words
def construct_h_matrix(self):
r"""构建 Hamiltonian 在 Z 基底下的矩阵
Returns:
np.ndarray: Z 基底下的哈密顿量矩阵形式
"""
coefs, pauli_words, sites = self.decompose_with_sites()
n_qubit = 1
for site in sites:
if type(site[0]) is int:
n_qubit = max(n_qubit, max(site) + 1)
h_matrix = np.zeros([2 ** n_qubit, 2 ** n_qubit], dtype='complex64')
spin_ops = SpinOps(n_qubit, use_sparse=True)
for idx in range(len(coefs)):
op = coefs[idx] * sparse.eye(2 ** n_qubit, dtype='complex64')
for site_idx in range(len(sites[idx])):
if re.match(r'X', pauli_words[idx][site_idx], re.I):
op = op.dot(spin_ops.sigx_p[sites[idx][site_idx]])
elif re.match(r'Y', pauli_words[idx][site_idx], re.I):
op = op.dot(spin_ops.sigy_p[sites[idx][site_idx]])
elif re.match(r'Z', pauli_words[idx][site_idx], re.I):
op = op.dot(spin_ops.sigz_p[sites[idx][site_idx]])
h_matrix += op
return h_matrix
class SpinOps:
r"""矩阵表示下的自旋算符,可以用来构建哈密顿量矩阵。
"""
def __init__(self, size: int, use_sparse=False):
r"""SpinOps 的构造函数,用于实例化一个 SpinOps 对象
Args:
size (int): 系统的大小(有几个量子比特)
use_sparse (bool): 是否使用 sparse matrix 计算,默认为 True
"""
self.size = size
self.id = sparse.eye(2, dtype='complex128')
self.sigz = sparse.bsr.bsr_matrix([[1, 0], [0, -1]], dtype='complex64')
self.sigy = sparse.bsr.bsr_matrix([[0, -1j], [1j, 0]], dtype='complex64')
self.sigx = sparse.bsr.bsr_matrix([[0, 1], [1, 0]], dtype='complex64')
self.sigz_p = []
self.sigy_p = []
self.sigx_p = []
self.__sparse = use_sparse
for i in range(self.size):
self.sigz_p.append(self.__direct_prod_op(spin_op=self.sigz, spin_index=i))
self.sigy_p.append(self.__direct_prod_op(spin_op=self.sigy, spin_index=i))
self.sigx_p.append(self.__direct_prod_op(spin_op=self.sigx, spin_index=i))
def __direct_prod_op(self, spin_op, spin_index):
r"""直积,得到第 n 个自旋(量子比特)上的自旋算符
Args:
spin_op: 单体自旋算符
spin_index: 标记第 n 个自旋(量子比特)
Returns:
scipy.sparse or np.ndarray: 直积后的自旋算符,其数据类型取决于 self.__use_sparse
"""
s_p = copy.copy(spin_op)
for i in range(self.size):
if i < spin_index:
s_p = sparse.kron(self.id, s_p)
elif i > spin_index:
s_p = sparse.kron(s_p, self.id)
if self.__sparse:
return s_p
else:
return s_p.toarray()
def __input_args_dtype_check(
show_arrow,
save_gif,
filename,
view_angle,
view_dist
):
r"""
该函数实现对输入默认参数的数据类型检查,保证输入函数中的参数为所允许的数据类型
Args:
show_arrow (bool): 是否展示向量的箭头,默认为False
save_gif (bool): 是否存储gif动图,默认10帧,3帧长出bloch向量,7帧转动bloch视角
filename (str): 存储的gif动图的名字。
view_angle (list or tuple): 视图的角度,list内第一个元素为关于xy平面的夹角[0-360],第二个元素为关于xz平面的夹角[0-360]
view_dist (int): 视图的距离,默认为7
"""
if show_arrow is not None:
assert type(show_arrow) == bool, \
'the type of show_arrow should be "bool"'
if save_gif is not None:
assert type(save_gif) == bool, \
'the type of save_gif should be "bool"'
if save_gif:
if filename is not None:
assert type(filename) == str, \
'the type of filename should be "str"'
other, ext = os.path.splitext(filename)
assert ext == '.gif', 'The suffix of the file name must be "gif"'
# If it does not exist, create a folder
path, file = os.path.split(filename)
if not os.path.exists(path):
os.makedirs(path)
if view_angle is not None:
assert type(view_angle) == list or type(view_angle) == tuple, \
'the type of view_angle should be "list" or "tuple"'
for i in range(2):
assert type(view_angle[i]) == int, \
'the type of view_angle[0] and view_angle[1] should be "int"'
if view_dist is not None:
assert type(view_dist) == int, \
'the type of view_dist should be "int"'
def __density_matrix_convert_to_bloch_vector(density_matrix):
r"""该函数将密度矩阵转化为bloch球面上的坐标
Args:
density_matrix (numpy.ndarray): 输入的密度矩阵
Returns:
bloch_vector (numpy.ndarray): 存储bloch向量的x,y,z坐标,向量的模长,向量的颜色
"""
# Pauli Matrix
pauli_x = np.array([[0, 1], [1, 0]])
pauli_y = np.array([[0, -1j], [1j, 0]])
pauli_z = np.array([[1, 0], [0, -1]])
# Convert a density matrix to a Bloch vector.
ax = np.trace(np.dot(density_matrix, pauli_x)).real
ay = np.trace(np.dot(density_matrix, pauli_y)).real
az = np.trace(np.dot(density_matrix, pauli_z)).real
# Calc the length of bloch vector
length = ax ** 2 + ay ** 2 + az ** 2
length = sqrt(length)
if length > 1.0:
length = 1.0
# Calc the color of bloch vector, the value of the color is proportional to the length
color = length
bloch_vector = [ax, ay, az, length, color]
# You must use an array, which is followed by slicing and taking a column
bloch_vector = np.array(bloch_vector)
return bloch_vector
def __plot_bloch_sphere(
ax,
bloch_vectors=None,
show_arrow=False,
clear_plt=True,
rotating_angle_list=None,
view_angle=None,
view_dist=None
):
r"""将 Bloch 向量展示在 Bloch 球面上
Args:
ax (Axes3D(fig)): 画布的句柄
bloch_vectors (numpy.ndarray): 存储bloch向量的x,y,z坐标,向量的模长,向量的颜色
show_arrow (bool): 是否展示向量的箭头,默认为False
clear_plt (bool): 是否要清空画布,默认为True,每次画图的时候清空画布再画图
rotating_angle_list (list): 旋转角度的列表,用于展示旋转轨迹
view_angle (list): 视图的角度,list内第一个元素为关于xy平面的夹角[0-360],第二个元素为关于xz平面的夹角[0-360]
view_dist (int): 视图的距离,默认为7
"""
# Assign a value to an empty variable
if view_angle is None:
view_angle = [30, 45]
if view_dist is None:
view_dist = 7
# Define my_color
color = 'rainbow'
black_code = '#000000'
red_code = '#F24A29'
if bloch_vectors is not None:
black_to_red = mplcolors.LinearSegmentedColormap.from_list(
'my_color',
[(0, black_code), (1, red_code)],
N=len(bloch_vectors[:, 4])
)
map_vir = plt.get_cmap(black_to_red)
color = map_vir(bloch_vectors[:, 4])
# Set the view angle and view distance
ax.view_init(view_angle[0], view_angle[1])
ax.dist = view_dist
# Draw the general frame
def draw_general_frame():
# Do not show the grid and original axes
ax.grid(False)
ax.set_axis_off()
ax.view_init(view_angle[0], view_angle[1])
ax.dist = view_dist
# Set the lower limit and upper limit of each axis
# To make the bloch_ball look less flat, the default is relatively flat
ax.set_xlim3d(xmin=-1.5, xmax=1.5)
ax.set_ylim3d(ymin=-1.5, ymax=1.5)
ax.set_zlim3d(zmin=-1, zmax=1.3)
# Draw a new axes
coordinate_start_x, coordinate_start_y, coordinate_start_z = \
np.array([[-1.5, 0, 0], [0, -1.5, 0], [0, 0, -1.5]])
coordinate_end_x, coordinate_end_y, coordinate_end_z = \
np.array([[3, 0, 0], [0, 3, 0], [0, 0, 3]])
ax.quiver(
coordinate_start_x, coordinate_start_y, coordinate_start_z,
coordinate_end_x, coordinate_end_y, coordinate_end_z,
arrow_length_ratio=0.03, color="black", linewidth=0.5
)
ax.text(0, 0, 1.7, r"|0⟩", color="black", fontsize=16)
ax.text(0, 0, -1.9, r"|1⟩", color="black", fontsize=16)
ax.text(1.9, 0, 0, r"|+⟩", color="black", fontsize=16)
ax.text(-1.7, 0, 0, r"|–⟩", color="black", fontsize=16)
ax.text(0, 1.7, 0, r"|i+⟩", color="black", fontsize=16)
ax.text(0, -1.9, 0, r"|i–⟩", color="black", fontsize=16)
# Draw a surface
horizontal_angle = np.linspace(0, 2 * np.pi, 80)
vertical_angle = np.linspace(0, np.pi, 80)
surface_point_x = np.outer(np.cos(horizontal_angle), np.sin(vertical_angle))
surface_point_y = np.outer(np.sin(horizontal_angle), np.sin(vertical_angle))
surface_point_z = np.outer(np.ones(np.size(horizontal_angle)), np.cos(vertical_angle))
ax.plot_surface(
surface_point_x, surface_point_y, surface_point_z, rstride=1, cstride=1,
color="black", linewidth=0.05, alpha=0.03
)
# Draw circle
def draw_circle(circle_horizon_angle, circle_vertical_angle, linewidth=0.5, alpha=0.2):
r = 1
circle_point_x = r * np.cos(circle_vertical_angle) * np.cos(circle_horizon_angle)
circle_point_y = r * np.cos(circle_vertical_angle) * np.sin(circle_horizon_angle)
circle_point_z = r * np.sin(circle_vertical_angle)
ax.plot(
circle_point_x, circle_point_y, circle_point_z,
color="black", linewidth=linewidth, alpha=alpha
)
# draw longitude and latitude
def draw_longitude_and_latitude():
# Draw longitude
num = 3
theta = np.linspace(0, 0, 100)
psi = np.linspace(0, 2 * np.pi, 100)
for i in range(num):
theta = theta + np.pi / num
draw_circle(theta, psi)
# Draw latitude
num = 6
theta = np.linspace(0, 2 * np.pi, 100)
psi = np.linspace(-np.pi / 2, -np.pi / 2, 100)
for i in range(num):
psi = psi + np.pi / num
draw_circle(theta, psi)
# Draw equator
theta = np.linspace(0, 2 * np.pi, 100)
psi = np.linspace(0, 0, 100)
draw_circle(theta, psi, linewidth=0.5, alpha=0.2)
# Draw prime meridian
theta = np.linspace(0, 0, 100)
psi = np.linspace(0, 2 * np.pi, 100)
draw_circle(theta, psi, linewidth=0.5, alpha=0.2)
# If the number of data points exceeds 20, no longitude and latitude lines will be drawn.
if bloch_vectors is not None and len(bloch_vectors) < 52:
draw_longitude_and_latitude()
elif bloch_vectors is None:
draw_longitude_and_latitude()
# Draw three invisible points
invisible_points = np.array([[0.03440399, 0.30279721, 0.95243384],
[0.70776026, 0.57712403, 0.40743499],
[0.46991358, -0.63717908, 0.61088792]])
ax.scatter(
invisible_points[:, 0], invisible_points[:, 1], invisible_points[:, 2],
c='w', alpha=0.01
)
# clean plt
if clear_plt:
ax.cla()
draw_general_frame()
# Draw the data points
if bloch_vectors is not None:
ax.scatter(
bloch_vectors[:, 0], bloch_vectors[:, 1], bloch_vectors[:, 2], c=color, alpha=1
)
# if show the rotating angle
if rotating_angle_list is not None:
bloch_num = len(bloch_vectors)
rotating_angle_theta, rotating_angle_phi, rotating_angle_lam = rotating_angle_list[bloch_num - 1]
rotating_angle_theta = round(rotating_angle_theta, 6)
rotating_angle_phi = round(rotating_angle_phi, 6)
rotating_angle_lam = round(rotating_angle_lam, 6)
# Shown at the top right of the perspective
display_text_angle = [-(view_angle[0] - 10), (view_angle[1] + 10)]
text_point_x = 2 * np.cos(display_text_angle[0]) * np.cos(display_text_angle[1])
text_point_y = 2 * np.cos(display_text_angle[0]) * np.sin(-display_text_angle[1])
text_point_z = 2 * np.sin(-display_text_angle[0])
ax.text(text_point_x, text_point_y, text_point_z, r'$\theta=' + str(rotating_angle_theta) + r'$',
color="black", fontsize=14)
ax.text(text_point_x, text_point_y, text_point_z - 0.1, r'$\phi=' + str(rotating_angle_phi) + r'$',
color="black", fontsize=14)
ax.text(text_point_x, text_point_y, text_point_z - 0.2, r'$\lambda=' + str(rotating_angle_lam) + r'$',
color="black", fontsize=14)
# If show the bloch_vector
if show_arrow:
ax.quiver(
0, 0, 0, bloch_vectors[:, 0], bloch_vectors[:, 1], bloch_vectors[:, 2],
arrow_length_ratio=0.05, color=color, alpha=1,
)
def plot_state_in_bloch_sphere(
state,
show_arrow=False,
save_gif=False,
filename=None,
view_angle=None,
view_dist=None
):
r"""将输入的量子态展示在 Bloch 球面上
Args:
state (list(numpy.ndarray or paddle.Tensor)): 输入的量子态列表,可以支持态矢量和密度矩阵
show_arrow (bool): 是否展示向量的箭头,默认为 ``False``
save_gif (bool): 是否存储 gif 动图,默认为 ``False``
filename (str): 存储的 gif 动图的名字
view_angle (list or tuple): 视图的角度,list 内第一个元素为关于 xy 平面的夹角 [0-360],第二个元素为关于 xz 平面的夹角 [0-360]
view_dist (int): 视图的距离,默认为 7
"""
# Check input data
__input_args_dtype_check(show_arrow, save_gif, filename, view_angle, view_dist)
assert type(state) == list or type(state) == paddle.Tensor or type(state) == np.ndarray, \
'the type of input data must be "list" or "paddle.Tensor" or "np.ndarray"'
if type(state) == paddle.Tensor or type(state) == np.ndarray:
state = [state]
state_len = len(state)
assert state_len >= 1, 'input data is NULL.'
for i in range(state_len):
assert type(state[i]) == paddle.Tensor or type(state[i]) == np.ndarray, \
'the type of input data should be "paddle.Tensor" or "numpy.ndarray".'
# Assign a value to an empty variable
if filename is None:
filename = 'state_in_bloch_sphere.gif'
if view_angle is None:
view_angle = [30, 45]
if view_dist is None:
view_dist = 7
# Convert Tensor to numpy
for i in range(state_len):
if type(state[i]) == paddle.Tensor:
state[i] = state[i].numpy()
# Convert state_vector to density_matrix
for i in range(state_len):
if state[i].size == 2:
state_vector = state[i]
state[i] = np.outer(state_vector, np.conj(state_vector))
# Calc the bloch_vectors
bloch_vector_list = []
for i in range(state_len):
bloch_vector_tmp = __density_matrix_convert_to_bloch_vector(state[i])
bloch_vector_list.append(bloch_vector_tmp)
# List must be converted to array for slicing.
bloch_vectors = np.array(bloch_vector_list)
# Helper function to plot vectors on a sphere.
fig = plt.figure(figsize=(8, 8), dpi=100)
fig.subplots_adjust(left=0, right=1, bottom=0, top=1)
ax = fig.add_subplot(111, projection='3d')
# A update function for animation class
def update(frame):
stretch = 3
view_rotating_angle = 5
if frame <= stretch:
new_bloch_vectors = np.zeros(shape=(len(bloch_vectors), 0))
for j in range(3):
bloch_column_tmp = bloch_vectors[:, j] / stretch * frame
new_bloch_vectors = np.insert(new_bloch_vectors, j, values=bloch_column_tmp, axis=1)
for j in range(2):
new_bloch_vectors = np.insert(new_bloch_vectors, 3 + j, values=bloch_vectors[:, 3 + j], axis=1)
__plot_bloch_sphere(
ax, new_bloch_vectors, show_arrow, clear_plt=True, view_angle=view_angle, view_dist=view_dist
)
else:
new_view_angle = [view_angle[0], view_angle[1] + view_rotating_angle * (frame - stretch)]
__plot_bloch_sphere(
ax, bloch_vectors, show_arrow, clear_plt=True, view_angle=new_view_angle, view_dist=view_dist
)
# Dynamic update and save
if save_gif:
frames_num = 10
anim = animation.FuncAnimation(fig, update, frames=frames_num, interval=600, repeat=False)
anim.save(filename, dpi=100, writer='Pillow')
else:
__plot_bloch_sphere(
ax, bloch_vectors, show_arrow, clear_plt=True, view_angle=view_angle, view_dist=view_dist
)
plt.show()
def plot_rotation_in_bloch_sphere(
init_state,
rotating_angle,
show_arrow=False,
save_gif=False,
filename=None,
view_angle=None,
view_dist=None
):
r"""在 Bloch 球面上刻画从初始量子态开始的旋转轨迹
Args:
init_state (numpy.ndarray or paddle.Tensor): 输入的初始量子态,可以支持态矢量和密度矩阵
rotating_angle (list(float)): 旋转角度 ``[theta, phi, lam]``
show_arrow (bool): 是否展示向量的箭头,默认为 ``False``
save_gif (bool): 是否存储 gif 动图,默认为 ``False``
filename (str): 存储的 gif 动图的名字
view_angle (list or tuple): 视图的角度,list 内第一个元素为关于 xy 平面的夹角 [0-360],第二个元素为关于 xz 平面的夹角 [0-360]
view_dist (int): 视图的距离,默认为 7
"""
# Check input data
__input_args_dtype_check(show_arrow, save_gif, filename, view_angle, view_dist)
assert type(init_state) == paddle.Tensor or type(init_state) == np.ndarray, \
'the type of input data should be "paddle.Tensor" or "numpy.ndarray".'
assert type(rotating_angle) == tuple or type(rotating_angle) == list, \
'the type of rotating_angle should be "tuple" or "list"'
assert len(rotating_angle) == 3, \
'the rotating_angle must include [theta=paddle.Tensor, phi=paddle.Tensor, lam=paddle.Tensor].'
for i in range(3):
assert type(rotating_angle[i]) == paddle.Tensor or type(rotating_angle[i]) == float, \
'the rotating_angle must include [theta=paddle.Tensor, phi=paddle.Tensor, lam=paddle.Tensor].'
# Assign a value to an empty variable
if filename is None:
filename = 'rotation_in_bloch_sphere.gif'
if view_angle is None:
view_angle = [30, 45]
if view_dist is None:
view_dist = 7
theta, phi, lam = rotating_angle
# Convert Tensor to numpy
if type(init_state) == paddle.Tensor:
init_state = init_state.numpy()
# Convert state_vector to density_matrix
if init_state.size == 2:
state_vector = init_state
init_state = np.outer(state_vector, np.conj(state_vector))
# Rotating angle
def rotating_operation(rotating_angle_each):
gate_matrix = simulator.u_gate_matrix(rotating_angle_each)
return np.matmul(np.matmul(gate_matrix, init_state), gate_matrix.conj().T)
# Rotating angle division
rotating_frame = 50
rotating_angle_list = []
state = []
for i in range(rotating_frame + 1):
angle_each = [theta / rotating_frame * i, phi / rotating_frame * i, lam / rotating_frame * i]
rotating_angle_list.append(angle_each)
state.append(rotating_operation(angle_each))
state_len = len(state)
# Calc the bloch_vectors
bloch_vector_list = []
for i in range(state_len):
bloch_vector_tmp = __density_matrix_convert_to_bloch_vector(state[i])
bloch_vector_list.append(bloch_vector_tmp)
# List must be converted to array for slicing.
bloch_vectors = np.array(bloch_vector_list)
# Helper function to plot vectors on a sphere.
fig = plt.figure(figsize=(8, 8), dpi=100)
fig.subplots_adjust(left=0, right=1, bottom=0, top=1)
ax = fig.add_subplot(111, projection='3d')
# A update function for animation class
def update(frame):
frame = frame + 1
if frame <= len(bloch_vectors):
__plot_bloch_sphere(
ax, bloch_vectors[:frame], show_arrow=show_arrow, clear_plt=True,
rotating_angle_list=rotating_angle_list,
view_angle=view_angle, view_dist=view_dist
)
# The starting and ending bloch vector has to be shown
# show starting vector
__plot_bloch_sphere(
ax, bloch_vectors[:1], show_arrow=True, clear_plt=False, view_angle=view_angle, view_dist=view_dist,
)
# Show ending vector
if frame == len(bloch_vectors):
__plot_bloch_sphere(
ax, bloch_vectors[frame - 1:frame], show_arrow=True, clear_plt=False,
view_angle=view_angle, view_dist=view_dist
)
# Dynamic update and save
stop_frames = 10
frames_num = len(bloch_vectors) + stop_frames
anim = animation.FuncAnimation(fig, update, frames=frames_num, interval=100, repeat=False)
if save_gif:
anim.save(filename, dpi=100, writer='Pillow')
plt.show()
...@@ -23,7 +23,7 @@ with open("README.md", "r", encoding="utf-8") as fh: ...@@ -23,7 +23,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
setuptools.setup( setuptools.setup(
name='paddle-quantum', name='paddle-quantum',
version='2.1.0', version='2.1.1',
author='Institute for Quantum Computing, Baidu INC.', author='Institute for Quantum Computing, Baidu INC.',
author_email='quantum@baidu.com', author_email='quantum@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.',
...@@ -34,7 +34,7 @@ setuptools.setup( ...@@ -34,7 +34,7 @@ setuptools.setup(
'paddle_quantum.VQE', 'paddle_quantum.VQSD', 'paddle_quantum.GIBBS.example', 'paddle_quantum.VQE', 'paddle_quantum.VQSD', 'paddle_quantum.GIBBS.example',
'paddle_quantum.QAOA.example', 'paddle_quantum.SSVQE.example', 'paddle_quantum.VQE.example', 'paddle_quantum.QAOA.example', 'paddle_quantum.SSVQE.example', 'paddle_quantum.VQE.example',
'paddle_quantum.VQSD.example'], 'paddle_quantum.VQSD.example'],
install_requires=['paddlepaddle>=2.0.1', 'scipy', 'networkx>=2.5', 'matplotlib', 'interval', 'tqdm'], install_requires=['paddlepaddle>=2.1.1', 'scipy', 'networkx>=2.5', 'matplotlib', 'interval', 'tqdm', 'fastdtw'],
python_requires='>=3.6, <4', python_requires='>=3.6, <4',
classifiers=[ classifiers=[
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
......
{
"cells": [
{
"cell_type": "markdown",
"source": [
"# 量子金融应用:最佳套利机会\n",
"\n",
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## 概览\n",
"\n",
"当前量子计算应用到金融问题上的解决方案通常可分为三类量子算法,即量子模拟,量子优化以及量子机器学习 [1,2]。许多的金融问题本质上是一个组合优化问题,解决这些问题的算法通常具有较高的时间复杂度,实现难度较大。得益于量子计算强大的计算性能,未来有望通过量子算法解决这些复杂问题。\n",
"\n",
"量桨的 Quantum Finance 模块主要讨论的是量子优化部分的内容,即如何通过一些量子算法解决实际金融应用中的优化问题。本文主要介绍如何使用量子算法求解最佳套利机会问题 [3]。"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## 最佳套利机会问题\n",
"\n",
"套利(Arbitrage)一般指相同的资产在不同的市场中有不同价格,可以通过在多个市场间的转换,获得正向收益。也就是说,如果给定一组资产和交易成本,那么就可能在不同市场间存在一个交易循环产生正向收益。\n",
"\n",
"这个问题可以用图论的语言来描述:给定一个带权重的有向图 $G$ ,顶点 $i$ 表示一种市场货币,从顶点 $i$ 到顶点 $j$ 的边的权重表示将货币 $i$ 转换为货币 $j$的转换率 $c_{ij} $。也就是说如果拥有数量为 $x_i$ 的货币 $i$,那么通过交易转换我们将得到 $c_{ij}x_i$ 的货币 $j$。通常来讲转换率不是对称的,即 $c_{ij} \\neq c_{ji}$,并且我们假设交易成本(服务供应商收取的服务费,买入和卖出时的手续费等)是已经包含在该转换率中的。\n",
"\n",
"我们固定可交易的货币种类数量为 $n$,则该优化问题就是在给定的加权有向图上寻找一个获利最大回路,该回路中包含的顶点数量为 $K (K \\leq n)$。找到的最佳套利回路就是从某一市场出发,使得资产转换一圈后收益最大的交易路径。在该问题中,用户可以自行定义套利回路中包含的顶点数 $K$。"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## 编码最佳套利机会问题\n",
"\n",
"为了将最佳套利机会问题转化成一个参数化量子电路(parameterized quantum circuits, PQC)可解的问题,我们首先需要编码该问题的哈密顿量。第一步是将该问题转化为一个整数规划问题:给定一个整数 $n$, 即最佳套利机会问题涉及的货币种类数量, 也就是图形 $G$ 的顶点数量为 $|V| = n$ 个,那么对于每个顶点 $i \\in V$,我们定义 $n$ 个二进制变量 $x_{i,k}$,$k \\in [0,K-1]$:\n",
"\n",
"$$\n",
"x_{i, k}=\\begin{cases}\n",
"1, & \\text{资产转换到市场 $i$ 的次序为 $k$}\\\\\n",
"0, & \\text{其他情况}\n",
"\\end{cases}.\n",
"\\tag{1}\n",
"$$\n",
"\n",
"参考旅行商问题([TSP 教程](./TSP_CN.ipynb)),因为 $G$ 有 $n$ 个顶点,所以我们共有 $n^2$ 个变量 $x_{i,k}$,所有这些变量的取值我们用 $x = x_{0,0} x_{0,1}\\dots x_{n-1,K-1}$ 来表示。在这里我们假设 $x$ 对应一个套利回路,那么对于图中的每一条边 $(i,j,w_{i,j})\\in E$,条件 $x_{i,k}·x_{j,k+1} = 1$ 成立当且仅当套利回路中第 $k$ 个顶点为顶点 $i$ 且 第 $k+1$ 个顶点是顶点 $j$。可以通过下面的式子计算资产在套利回路转换一圈后所获利的对数:\n",
"\n",
"$$\n",
"P(x) = - \\sum_{i,j\\in V} \\log(c_{ij}) \\sum_{k=0}^{K-1} x_{i,k}x_{j,k+1}, \\tag{2}\n",
"$$\n",
"\n",
"根据套利回路的定义,$x$ 如果对应一条套利回路,那么就需要满足如下的限制:\n",
"\n",
"$$\n",
"\\sum_{i=0}^{n-1} x_{i,k} = 1 \\quad \\text{和} \\quad \\sum_{k=0}^{K-1}\\sum_{(i,j)\\notin E}x_{i,k}x_{j, k+1}, \\tag{3}\n",
"$$\n",
"\n",
"其中第一个式子用来保证找到的 $x$ 所代表的回路中同一时刻只可能出现一个顶点,第二个保证不存在的边不会出现在最后的套利回路中。这两个式子共同保证了参数化量子电路找到的 $x$ 是个简单回路。因为后续要用梯度下降方法对损失函数进行优化求最小值,对上述式子稍作调整给出损失函数:\n",
"\n",
"$$\n",
"C_x = - P(x) + A\\sum_{k=0}^{K-1} \\left(1 - \\sum_{i=0}^{n-1} x_{i,k}\\right)^2 + A\\sum_{k=0}^{K-1}\\sum_{(i,j)\\notin E}\n",
"x_{i,k}x_{j,k+1}.\n",
"\\tag{4}\n",
"$$\n",
"\n",
"在这个式子中 $V$ 是图中顶点个数,$E$ 表示图中边的集合,$K$ 是套利圈中顶点的个数,$A$ 是惩罚参数,它保证了上述的限制被遵守。因为我们想要在找套利回路 $P(x)$ 最大值的同时保证 $x$ 确实表示一个回路,所以我们需要设置一个大一点的 $A$,最起码大过图 $G$ 中边的最大的权重,从而保证不遵守限制的路线不会成为最终的路线。\n",
"\n",
"我们现在需要将损失函数 $C_x$ 转化为一个哈密顿量从而完成最佳套利问题的编码。每一个二进制变量可以取 $0$ 和 $1$ 两个值,分别对应量子态 $|0\\rangle$ 和 $|1\\rangle$。每个二进制变量都对应一个量子比特,所以我们需要 $n^2$ 个量子比特来解决最佳套利机会问题。因为我们的变量 $x_{i,k}$ 的值为 $0$ 和 $1$,所以我们要构造一个本征值和它对应的哈密顿量。泡利 $Z$ 的本征值为 $\\pm 1$,于是我们构造的哈密顿量为 $\\frac{I-Z}{2}$, 对应的本征值即为 $0$ 和 $1$。我们现在将二进制变量映射到这个哈密顿矩阵上,从而使 $C_x$ 转化成哈密顿矩阵:\n",
"\n",
"$$\n",
"x_{i,k} \\mapsto \\frac{I-Z_{i,k}}{2}, \\tag{5}\n",
"$$\n",
"\n",
"这里 $Z_{i,k} = I \\otimes I \\otimes \\ldots \\otimes Z \\otimes \\ldots \\otimes I$,也就是说 $Z$ 作用在位置为 $(i,k)$ 的量子比特上。通过这个映射,如果一个编号为 $(i,k)$ 的量子比特的量子态为 $|1\\rangle$,那么对应的二进制变量的取值为 $x_{i,k} |1\\rangle = \\frac{I-Z_{i,k}}{2} |1\\rangle = 1|1\\rangle $,也就是说顶点 $i$ 在最佳套利回路中的交易顺序是 $k$。同样地,对于量子态为 $|0\\rangle$的量子比特 $(i,k)$,它所对应的二进制变量的取值为 $x_{i,k}|0\\rangle = \\frac{I-Z_{i,k}}{2} |0\\rangle = 0 |0\\rangle $。\n",
"\n",
"我们用上述映射将 $C_x$ 转化成量子比特数为 $n^2$ 的系统的哈密顿矩阵 $H_C$,从而实现了最佳套利机会问题的量子化。这个哈密顿矩阵 $H_C$ 的基态即为最佳套利机会问题的最优解。在接下来的部分,我们将展示如何用参数化量子电路找到这个矩阵的基态,也就是对应最小本征值的本征态。"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## Paddle Quantum 实现\n",
"\n",
"要在量桨上实现用参数化量子电路解决量子金融中的最佳套利机会问题,首先要做的便是加载需要用到的包。其中 `networkx` 包可以帮助我们方便地查看和处理图。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 1,
"source": [
"#加载需要的包\n",
"import numpy as np\n",
"import networkx as nx\n",
"import matplotlib.pyplot as plt\n",
"\n",
"#加载飞桨,量桨相关的模块\n",
"import paddle\n",
"from paddle_quantum.circuit import UAnsatz\n",
"from paddle_quantum.finance import arbitrage_opportunities_hamiltonian"
],
"outputs": [],
"metadata": {
"ExecuteTime": {
"end_time": "2021-05-17T08:00:15.901429Z",
"start_time": "2021-05-17T08:00:12.708945Z"
}
}
},
{
"cell_type": "markdown",
"source": [
"接下来,我们先生成用于最佳套利机会问题的图 $G$。为了运算方便,图中的顶点从0开始计数。\n",
"\n",
"同时,为了方便运算验证,这里构造的加权有向图中对权值做了特殊设定。在实际使用过程中用户可以自行构造需要计算的货币类型,设置真实的货币汇率。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 4,
"source": [
"# n 代表图形 G 的顶点数量————指不同国家的货币\n",
"n = 3\n",
"nodes = [ \"JPY\", \"CNY\", \"USD\"]\n",
"G = nx.DiGraph()\n",
"G.add_nodes_from(nodes)\n",
"edges = [(\"JPY\",\"CNY\", 0.5), (\"CNY\",\"JPY\",2), (\"CNY\",\"USD\", 0.33), (\"USD\",\"CNY\",3),(\"JPY\",\"USD\", 0.25), (\"USD\",\"JPY\",4)]\n",
"G.add_weighted_edges_from(edges)\n",
"\n",
"# 两幅图分别表示不同方向的转换率\n",
"G1 = nx.DiGraph()\n",
"G1.add_nodes_from(nodes)\n",
"edges1 = [(\"JPY\",\"CNY\", 0.5), (\"CNY\",\"USD\", 0.33), (\"USD\",\"JPY\",4)]\n",
"G1.add_weighted_edges_from(edges1)\n",
"\n",
"G2 = nx.DiGraph()\n",
"G2.add_nodes_from(nodes)\n",
"edges2 = [(\"CNY\",\"JPY\",2), (\"USD\",\"CNY\",3),(\"JPY\",\"USD\", 0.25)]\n",
"G2.add_weighted_edges_from(edges2)\n",
"\n",
"options = {\n",
" \"with_labels\": True,\n",
" \"font_weight\": \"bold\",\n",
" \"font_color\": \"white\",\n",
" \"node_size\": 2000,\n",
" \"width\": 2\n",
"}\n",
"fig, ax = plt.subplots(1, 2, figsize=(15, 4))\n",
"for i, a in enumerate(ax):\n",
" a.axis('off')\n",
" a.margins(0.20)\n",
"nx.draw(G1, pos=nx.circular_layout(G1), ax=ax[0], **options)\n",
"nx.drawing.nx_pylab.draw_networkx_edge_labels(G1, pos=nx.circular_layout(G1), ax=ax[0], edge_labels=nx.get_edge_attributes(G1, 'weight'))\n",
"nx.draw(G2, pos=nx.circular_layout(G2), ax=ax[1], **options)\n",
"nx.drawing.nx_pylab.draw_networkx_edge_labels(G2, pos=nx.circular_layout(G2), ax=ax[1], edge_labels=nx.get_edge_attributes(G2, 'weight'))\n",
"plt.axis(\"off\")\n",
"plt.show()"
],
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": [
"<Figure size 1080x288 with 2 Axes>"
],
"image/png": "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"
},
"metadata": {}
}
],
"metadata": {
"ExecuteTime": {
"end_time": "2021-05-17T08:00:16.212260Z",
"start_time": "2021-05-17T08:00:15.918792Z"
}
}
},
{
"cell_type": "markdown",
"source": [
"### 编码哈密顿量\n",
"\n",
"这里我们将式(4)中的二进制变量用式(5)替换,从而构建哈密顿量 $H_C$。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 4,
"source": [
"# 惩罚参数 等于构造的有向图中权值最大的边\n",
"penalty = 4 \n",
"# 在本样例中,设定交易回路内货币数量为全部的货币种类\n",
"K = n \n",
"# 构建最佳套利问题的哈密顿量\n",
"hamiltonian = arbitrage_opportunities_hamiltonian(G, penalty, n, K)"
],
"outputs": [],
"metadata": {
"ExecuteTime": {
"end_time": "2021-05-17T08:00:16.237497Z",
"start_time": "2021-05-17T08:00:16.219567Z"
}
}
},
{
"cell_type": "markdown",
"source": [
"### 计算损失函数\n",
"\n",
"我们使用 $U_3(\\vec{\\theta})$ 和 $\\text{CNOT}$ 门构造的参数化量子电路,通过调用量桨内部的 [`complex entangled layer`](https://qml.baidu.com/api/paddle_quantum.circuit.uansatz.html) 来实现。\n",
"\n",
"上述电路会给出一个输出态 $|\\vec{\\theta}\\rangle$,由此输出态,我们可以定义最佳套利机会问题在经典-量子混合模型下的损失函数:\n",
"\n",
"$$\n",
"L(\\vec{\\theta}) = \\langle\\vec{\\theta}|H_C|\\vec{\\theta}\\rangle.\n",
"\\tag{6}\n",
"$$\n",
"\n",
"之后我们利用经典的优化算法寻找最优参数 $\\vec{\\theta}^*$。下面的代码给出了通过量桨和飞桨搭建的完整网络:"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 5,
"source": [
"class AONet(paddle.nn.Layer):\n",
"\n",
" def __init__(self, n, p, K, dtype=\"float64\"):\n",
" super(AONet, self).__init__()\n",
"\n",
" self.p = p\n",
" self.num_qubits = n * K\n",
" self.theta = self.create_parameter(shape=[self.p, self.num_qubits, 3],\n",
" default_initializer=paddle.nn.initializer.Uniform(low=0, high=2 * np.pi),\n",
" dtype=dtype, is_bias=False)\n",
"\n",
" def forward(self, hamiltonian):\n",
" \"\"\"\n",
" 前向传播\n",
" \"\"\"\n",
" cir = UAnsatz(self.num_qubits)\n",
" cir.complex_entangled_layer(self.theta, self.p)\n",
" cir.run_state_vector()\n",
" loss = cir.expecval(hamiltonian)\n",
"\n",
" return loss, cir\n"
],
"outputs": [],
"metadata": {
"ExecuteTime": {
"end_time": "2021-05-17T08:00:16.258893Z",
"start_time": "2021-05-17T08:00:16.241066Z"
}
}
},
{
"cell_type": "markdown",
"source": [
"### 训练量子神经网络\n",
"\n",
"定义好了量子神经网络后,我们使用梯度下降的方法来优化其中的参数,使得式(6)的期望值最小。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 6,
"source": [
"SEED = 100 # 随机数种子\n",
"p = 1 # 量子电路的层数\n",
"ITR = 120 # 迭代次数\n",
"LR = 0.4 # 梯度下降优化速率 "
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"使用飞桨,优化上面定义的网络。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 7,
"source": [
"# 固定 paddle 随机种子\n",
"paddle.seed(SEED)\n",
"\n",
"# 定义量子神经网络\n",
"net = AONet(n, p, K)\n",
"\n",
"# 使用 Adam 优化器\n",
"opt = paddle.optimizer.Adam(learning_rate=LR, parameters=net.parameters())\n",
"# 梯度下降优化循环\n",
"for itr in range(1, ITR + 1):\n",
" # 运行上面定义的网络\n",
" loss, cir = net(hamiltonian)\n",
" #计算梯度并优化\n",
" loss.backward()\n",
" opt.minimize(loss)\n",
" opt.clear_grad()\n",
" if itr % 10 == 0:\n",
" print(\"循环数:\", itr, \" 损失:\", \"%.4f\"% loss.numpy())"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"循环数: 10 损失: 0.4704\n",
"循环数: 20 损失: 0.1302\n",
"循环数: 30 损失: -0.2744\n",
"循环数: 40 损失: -0.4700\n",
"循环数: 50 损失: -0.5512\n",
"循环数: 60 损失: -0.5684\n",
"循环数: 70 损失: -0.5821\n",
"循环数: 80 损失: -0.5833\n",
"循环数: 90 损失: -0.5843\n",
"循环数: 100 损失: -0.5847\n",
"循环数: 110 损失: -0.5849\n",
"循环数: 120 损失: -0.5849\n"
]
}
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### 解码量子答案\n",
"\n",
"当求得损失函数的最小值以及对应的一组参数 $\\vec{\\mathbf{\\theta}}^*$后,我们的任务还没有完成。为了进一步求得最佳套利机会问题的近似解,需要从电路输出的量子态 $|\\vec{\\mathbf{\\theta}}^*\\rangle$ 中解码出最佳套利问题的答案。物理上,解码量子态需要对量子态进行测量,然后统计测量结果的概率分布(我们的测量结果是表示最佳套利机会问题答案的比特串):\n",
"\n",
"$$\n",
"p(z) = |\\langle z|\\vec{\\mathbf{\\theta}}^*\\rangle|^2.\n",
"\\tag{6}\n",
"$$\n",
"\n",
"在量子参数化电路表达能力足够的情况下,某个比特串出现的概率越大,意味着其是最佳套利机会问题最优解的可能性越大。\n",
"\n",
"量桨提供了查看参数化量子电路输出状态的测量结果概率分布的函数:"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 8,
"source": [
"# 模拟重复测量电路输出态 1024 次\n",
"prob_measure = cir.measure(shots=1024)\n",
"arbitrage_opportunity_route = max(prob_measure, key=prob_measure.get)\n",
"print(\"利用哈密顿量找到的解的比特串形式:\", arbitrage_opportunity_route)"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"利用哈密顿量找到的解的比特串形式: 100001010\n"
]
}
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"概率最大的测量结果是表示最佳套利机会问题答案的比特串。返回的二进制字符串每 $n$ 位一组,每组中的第 $k$ 位为 $1$ 表示该笔资产交易到该市场时的顺序为 $k$ 。如果最后的结果不是类似的有效解,读者可以通过调整参数化量子电路的参数值,即随机种子 `SEED`、层数 `p`、迭代次数 `ITR` 和梯度下降优化速率 `LR`,来获得更好的训练效果。\n",
"\n",
"我们将找到的比特串映射回经典解,即转化成了 ``dictionary`` 的形式。其中 ``key`` 代表货币名称,``value`` 代表顶点在套利回路中的顺序,即一笔资产交易到该市场的顺序。在下面的代码中,我们还计算了该笔资产经找到套利回路交易一圈后的正向回报率。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 9,
"source": [
"solution = {nodes[i]:t for i in range(n) for t in range(n) if arbitrage_opportunity_route[i * n + t] == '1'}\n",
"print(solution)\n",
"rate = sum([np.log2(G[u][v][\"weight\"]) if solution[v] == (solution[u] + 1) % n else 0 for (u, v) in G.edges])\n",
"print(\"正向回报率: \", 2**rate)"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"{'JPY': 0, 'CNY': 2, 'USD': 1}\n",
"正向回报率: 1.5\n"
]
}
],
"metadata": {
"ExecuteTime": {
"end_time": "2021-05-17T08:02:14.554317Z",
"start_time": "2021-05-17T08:02:14.500593Z"
}
}
},
{
"cell_type": "markdown",
"source": [
"为了清晰地表达资产的交易路径,依然选择以图的形式呈现:\n",
"* 顶点中的数字代表交易到该货币市场的顺序,字母代表不同的货币类型\n",
"* 红色的有向边表示找到的最佳路线"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 10,
"source": [
"label_dict = {i: str(i) + \", \" + str(t) for i, t in solution.items()}\n",
"edge_color1 = [\"red\" if solution[v] == (solution[u] + 1) % n else \"black\"\n",
" for (u, v) in G1.edges]\n",
"edge_color2 = [\"red\" if solution[v] == (solution[u] + 1) % n else \"black\"\n",
" for (u, v) in G2.edges]\n",
"\n",
"# 在图上画出找到的最优路线\n",
"fig, ax = plt.subplots(1, 2, figsize=(15, 4))\n",
"for i, a in enumerate(ax):\n",
" a.axis('off')\n",
" a.margins(0.20)\n",
"nx.draw(G1, pos=nx.circular_layout(G1), labels=label_dict, edge_color=edge_color1, ax=ax[0], **options)\n",
"nx.drawing.nx_pylab.draw_networkx_edge_labels(G1, pos=nx.circular_layout(G1), ax=ax[0], edge_labels=nx.get_edge_attributes(G1, 'weight'))\n",
"nx.draw(G2, pos=nx.circular_layout(G2), labels=label_dict, edge_color=edge_color2, ax=ax[1], **options)\n",
"nx.drawing.nx_pylab.draw_networkx_edge_labels(G2, pos=nx.circular_layout(G2), ax=ax[1], edge_labels=nx.get_edge_attributes(G2, 'weight'))\n",
"plt.axis(\"off\")\n",
"plt.show()"
],
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": [
"<Figure size 1080x288 with 2 Axes>"
],
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA1MAAADnCAYAAAD7CwxiAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8rg+JYAAAACXBIWXMAAAsTAAALEwEAmpwYAAA4PUlEQVR4nO3deXiU9b3//+d9ZyaTjQAh7IRA2BeVYgLI4sJSScT14FrUywWX6jnaY63oOWgrHvXXWrUesRWqbYW6Yn+1aoKCcARRCIsLu0hCSNhDlJB9lvv7x5BADGGGMMlsr8d1cZXMfefOZ+LVefO635/78zEsy7IQERERERGR02IGewAiIiIiIiLhSGFKRERERESkBRSmREREREREWkBhSkREREREpAUUpkRERERERFpAYUpERERERKQFFKZERERERERaQGFKRERERESkBRSmREREREREWkBhSkREREREpAUUpkRERERERFpAYUpERERERKQFFKZERERERERaQGFKRERERESkBRSmREREREREWkBhSkREREREpAUUpkRERERERFpAYUpERERERKQFFKZERERERERaQGFKRERERESkBWzBHkCo8ngsisqqKCytoMbpwen2YI8xibOb9E1NIj0lAdM0gj1MERGRNqX6KCJynMLUMR6PxaqdpSzbdpC1u8rYcbAC0zCwmQYWFpYFhgEGBi6PhceyGNAliaw+KUwc3IVx/VJVPEREJOKoPoqINM+wLMsK9iCC6Ui1k7fXFTN/ZQGVtS6q6tyczi/EABJiY0h02Jg5IYNrMtNoH29vreGKiIi0CdVHERHfojZMVde5eSpvK2+tK8YwoMbpOeNrxttNPBZcm5nGw9lDiI+NCcBIRURE2o7qo4iI/6IyTOUXlnHvGxsor3ZS4zrzIvFjcTaT5Hg7c28YSVaflIBfX0REpDWoPoqInJ6oClO1LjdzPtjCog0lAbnT5kuc3WT6yF7MnjYUh0134UREJDSpPoqItEzUhKnKWhczXlnD1n3lrXK3rTlxNpOhPZJZcOtoEh1a70NEREKL6qOISMtFRZiqrHUx/eXPKThUSW0bFop6DptJRudEFt05VgVDRERChuqjiMiZifhNe2tdbma8siZohcI7Bg8Fhyq58dU11LrcQRmDiIjIiVQfRUTOXMSHqTkfbGHrvvKgFYp6tS4PW/aWM+eD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9mjCjz9dq/pNg9PSgjdOEWmWwpQPClOB5W/XKjs7mx49egRxpCIickpOJ6SmQnk5pKdDYaH3QSlpGXWtRMKSwpQPClOtp75rlZubS15eHtu3b290/JxzzmkIVupaiYiEmFdfhdtuO/714sVw8cXBG0+k8dW1mjz5eLhS10okaBSmfFCYajvqWomIhAmnEwYN8naj6o0ZA59/ru5Ua6irg88+O778urpWIiFDYcoHhangUNdKRCSELVwIN97Y9PVly+Cii9p+PNFGXSuRkKEw5YPCVGho7a5VTU0NcXFxgRyyiEjk+vvfvWHqxH9C2Gzef+BPmhS8cUUjda1EgkphygeFqdAT6K7Vn//8Z/7+979z8OBBnn76aaZNm6ZlfUVEfCkvh1mz4I9/hN/8Bv7zP71dEQmu3buPB6tPPlHXSqSVKUz5oDAV+vzpWt1yyy1kZ2djmmaj783Pz+e6665j9erVbN68md/97ne8++67xMfHN5zjdruJiYlps/cjIhI2HngAnn0WnnnG+3cJLa3Qtdq0aRM9e/akY8eOrTRokfBi+j5FJLT17duXn//857z//vscPnyYjz76iPvvv59BgwZRXl7Ou+++y/bt2/F4PI2+r6qqig8//JBbbrmFLl260K9fPwDKysoaznG73cybN4+xY8cyZ84cNmzY0KbvTUREpMViY2HiRPjd72DTJigqgj/9CS6/3Nul2rzZG4QnTYJOneDKK717hpWUnPRyP/zwAy+99BLjx49nzJgxfPTRR238hkRCj8KURJS4uDh++tOf8txzz7Ft2zYKCgqYO3cuP/vZz7DZbI3OLSkpYf/+/Uw6Nr+/rKyMIUOG8O233zacc/DgQfbt28fQoUNxuVw8+uijfPDBB236nkRERAKid2+480745z/h8GHvNMBf/tLboaqo8L5+553wi180nh54jMPh4KmnnmLz5s08/vjjvPTSS41mg4hEI5vvU0TCV33X6mT27dtHZWUl55xzDgAHDhygvLycPn36NJxTUlLC4cOHmT17Nunp6QC4XK5WH7eIiEirqu9a1XeuTnzW6rrrTvr8W3x8PA6HA4BevXpx+PBhampqSEhIaOvRi4QMhSmJWuXl5ezdu5fExETKy8vZsWMHKSkp9O3bt+GcsrIyPvzwQwoLC7n77rvJzs5u0uESEREJe/VdqzvvbLxK4wnq6uqIPfZc1fz58xk5cqRWwpWop2l+ErXatWtHamoqAB9//DGffvopV1xxBUDD81UXXngh69at47rrruO1115j9+7dwRquiIhI22hmRdvY2Fj27t3LtGnTOHz4MPfee+/xrtSBA1Bc3IaDFAkNClMStcaPH09aWhrdunXjL3/5C/fccw/Dhw8HaFj1z+FwkJqayk033UT//v159tlnG11DU/5ERCQaVFZW8uabbzJjxgzOP/98XnrpJQYOHOg9WFUFTz3l7W4NHw4PPgjLl3tXExSJcApTErVsNhu///3v2b9/P3/961+58MIL+dvf/saCBQsAWLVqFXv37m04/+uvv254nsqyLFwuFy+++CIjRozg4YcfZsWKFTidzmC8FRERkVZTV1fHzTffzP333899993Hr371K5JOfKbKbvcGpxNXCJw4sfEKgepaSYTSPlM+aJ+p6ON0OrHb7bz44ovMmzePzp070717d+rq6vjDH/5A9+7dAe9duunTp7N48eKG723fvj1TpkwhOzubqVOn0qNHj2C9DRGR1qd9pqKC2+3m7bffJi8vj507d2JZFrNmzeLSSy9tvMn9ifta5ebCli2NLzRsGOTkePe1GjfOr32tREKdwpQPClOyZs0adu7cydVXX43dbm90rKamhhUrVjRsGrx9+/ZGx8855xyys7PJzs7mvPPOa/L9IiJhTWEqKq1Zswabzca555576hOLimDxYm+4WroUKiuPH0tKgsmTj28anJbWuoMWaSUKUz4oTMnpKCgoaAhWy5Yto7q6uuGYulYiEnEUpsRftbXHu1Z5eU27VsOHHw9W6lpJGFGY8kFhSlrK365VTk4O5513npZcF5HwozAlLaWulUQIhSkfFKYkUNS1EpGIozAlgaCulYQxhSkfFKakNfjTtcrJyWl41kpdKxEJSQpT0hrUtZIwojDlg8KUtAV1rUQkLClMSWtT10pCnMKUDwpT0tZ8da1GjBjRaIVAda1EJGgUpqSt1XetcnPhk08ad63atYNJk44vv96rV/DGKVFDYcoHhSkJNnWtRCRkKUxJMKlrJSFAYcoHhSkJJSd2rXJzc/n2228bHVfXyjePx6KorIrC0gpqnB6cbg/2GJM4u0nf1CTSUxIwTcP3hUREYUpCi7pWZ0T1sWUUpnxQmJJQ5k/XKicnh6lTp9K9e/cgjjR4PB6LVTtLWbbt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"
},
"metadata": {}
}
],
"metadata": {
"ExecuteTime": {
"end_time": "2021-05-17T08:02:14.571954Z",
"start_time": "2021-05-17T08:02:14.559634Z"
}
}
},
{
"cell_type": "markdown",
"source": [
"上面给出的左图展示了未被选中的交易路线,右图展示了该算法找到的最优套利回路。"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### 结语\n",
"\n",
"在本教程中,针对套利问题,给出了一种寻找最优套利回路的经典量子混合算法。对于 $n$ 个货币市场,在该解决方案中,我们需要 $n^2$ 个量子比特。在样例中,我们设定的不同市场间的汇率比较特殊且把交易成本包含在内,所以会产生高达 $150\\%$ 的正向收益率。用户在使用时可以根据需求,自行设定真实的货币汇率。\n",
"\n",
"在实际的金融市场中,一般不会有如此高的投资回报率,而且考虑的货币市场数量比较大,需要考虑的因素也更多。因此,随着货币数量的增加,所需的量子比特数也急速增加,实现难度也就越大。"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"_______\n",
"\n",
"## 参考文献\n",
"\n",
"[1] Orus, Roman, Samuel Mugel, and Enrique Lizaso. \"Quantum computing for finance: Overview and prospects.\" [Reviews in Physics 4 (2019): 100028.](https://arxiv.org/abs/1807.03890)\n",
"\n",
"[2] Egger, Daniel J., et al. \"Quantum computing for Finance: state of the art and future prospects.\" [IEEE Transactions on Quantum Engineering (2020).](https://arxiv.org/abs/2006.14510)\n",
"\n",
"[3] Rosenberg, G. \"Finding optimal arbitrage opportunities using a quantum annealer.\" [1QB Information Technologies Write Paper (2016): 1-7.](https://1qbit.com/whitepaper/arbitrage/)"
],
"metadata": {}
}
],
"metadata": {
"interpreter": {
"hash": "3b61f83e8397e1c9fcea57a3d9915794102e67724879b24295f8014f41a14d85"
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3.7.11 64-bit ('pq_env': conda)"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.11"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}
\ No newline at end of file
{
"cells": [
{
"cell_type": "markdown",
"source": [
"# Quantum Finance Application on Arbitrage Opportunity Optimization\n",
"\n",
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## Overview\n",
"\n",
"Current finance problems can be mainly tackled by three areas of quantum algorithms: quantum simulation, quantum optimization, and quantum machine learning [1,2]. Many financial problems are essentially combinatorial optimization problems, and corresponding algorithms usually have high time complexity and are difficult to implement. Due to the power of quantum computing, these complex problems are expected to be solved by quantum algorithms in the future.\n",
"\n",
"The Quantum Finance module of Paddle Quantum focuses on quantum optimization: how to apply quantum algorithms in real finance optimization problems. This tutorial focuses on how to use quantum algorithms to solve the arbitrage opportunity optimization problem [3].\n"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## Arbitrage Opportunity Optimization \n",
"\n",
"Arbitrage describes the fact that the same asset has different prices in different markets and can be traded between multiple markets to generate a positive return. That is, given a set of assets and transaction costs, it is possible to have a cycle among different markets that can generate a positive return.\n",
"\n",
"This problem can be formulated in the language of graph theory: given a weighted directed graph $G$ with vertex $i$ denoting $i$ th market currency, the weight of the edge from vertex $i$ to vertex $j$ denoting the exchange rate $c_{ij} $ that converts currency $i$ to currency $j$. That is, if we have a currency $i$ of quantity $x_i$, then by the exchange rate we will get a currency $j$ of quantity $c_{ij}x_i$. In general, the exchange rate is not symmetric, i.e. $c_{ij} \\neq c_{ji}$. We also assume that transaction costs (service fees etc.) are already included in the exchange rate.\n",
"\n",
"Given the number of tradable currency types $n$, the optimization problem is to find a cycle of the maximal profit on the given directed graph with $K (K \\leq n)$ vertices. In this finance module, users can define the number of vertices contained in the arbitrage cycle $K$ according to their requirements.\n"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Encoding Arbitrage Opportunity Optimization Problem\n",
"\n",
"To transform the arbitrage opportunity optimization problem into a problem applicable for parameterized quantum circuits, we need to encode the arbitrage opportunity optimization problem into a Hamiltonian. We realize the encoding by first constructing an integer programming problem. Suppose there are $|V|=n$ vertices in graph $G$. Then for each vertex $i \\in V$, we define $n$ binary variables $x_{i,k}$, where $k \\in [0,K-1]$, such that\n",
"\n",
"$$\n",
"x_{i, k}=\n",
"\\begin{cases}\n",
"1, & \\text{the order of traded to vertex } i \\text { in the arbitrage cycle is $k$}\\\\\n",
"0, & \\text{otherwise}\n",
"\\end{cases}.\n",
"\\tag{1}\n",
"$$\n",
"\n",
"Refering to the Travelling Salesman Problem ([TSP tutorial](./TSP_CN.ipynb)), since $G$ has $n$ vertices, we have $n^2$ variables in total, whose value we denote by a bit string $x=x_{0,0}x_{0,1}\\dots x_{n-1,K-1}$. Assume for now that the bit string $x$ represents a arbitrage cycle. Then for each edge $(i,j,w_{i,j}) \\in E$, we will have $x_{i,k} = x_{j,k+1}=1$, i.e., $x_{i,k}\\cdot x_{j,k+1}=1$, if and only if the arbitrage cycle visits vertex $i$ at time $k$ and vertex $j$ at time $k+1$. Otherwise, $x_{i,k}\\cdot x_{j,k+1}$ will be $0$. Therefore the logarithm of the profit of the cycle is:\n",
"\n",
"$$\n",
"P(x) = - \\sum_{i,j\\in V} \\log(c_{ij}) \\sum_{k=0}^{K-1} x_{i,k}x_{j,k+1}, \\tag{2}\n",
"$$\n",
"\n",
"For $x$ to represent a valid arbitrage cycle, the following constraint needs to be met:\n",
"\n",
"$$\n",
"\\sum_{i=0}^{n-1} x_{i,k} = 1 \\quad \\text{and} \\quad \\sum_{k=0}^{K-1}\\sum_{(i,j)\\notin E}x_{i,k}x_{j, k+1}, \\tag{3}\n",
"$$\n",
"\n",
"where the first equation guarantees that only one vertex is visited at each time $k$. The second term constrains that a nonexistent edge does not appear in the found arbitrage cycle. These two equations ensure that the parameterized quantum circuits find $𝑥$ as a simple loop. Then the cost function under the constraint can be formulated below:\n",
"$$\n",
"C_x = - P(x) + A\\sum_{k=0}^{K-1} \\left(1 - \\sum_{i=0}^{n-1} x_{i,k}\\right)^2 + A\\sum_{k=0}^{K-1}\\sum_{(i,j)\\notin E}\n",
"x_{i,k}x_{j,k+1}.\n",
"\\tag{4}\n",
"$$\n",
"In this equation, $V$ is the number of vertices of the graph, $E$ is the edge set of the graph and $K$ are the number of vertices of the most profitable cycle. Note that as we would like to maximize the $P(x)$ while ensuring $x$ represents a valid arbitrage cycle, we had better set $A$ large, at least larger than the largest weight of edges.\n",
"\n",
"We now need to transform the cost function $C_x$ into a Hamiltonian to realize the encoding of the arbitrage opportunity optimization problem. Each variable $x_{i,k}$ has two possible values, $0$ and $1$, corresponding to quantum states $|0\\rangle$ and $|1\\rangle$. Note that every variable corresponds to a qubit and so $n^2$ qubits are needed for solving the arbitrage opportunity optimization problem. The Pauli $Z$ operator has two eigenstates, $|0\\rangle$ and $|1\\rangle$ . Their corresponding eigenvalues are 1 and -1, respectively. So we consider encoding the cost function as a Hamiltonian using the pauli $Z$ matrix.\n",
"\n",
"Now we would like to consider the mapping\n",
"$$\n",
"x_{i,k} \\mapsto \\frac{I-Z_{i,k}}{2}, \\tag{3}\n",
"$$\n",
"\n",
"where $Z_{i,k} = I \\otimes I \\otimes \\ldots \\otimes Z \\otimes \\ldots \\otimes I$ with $Z$ operates on the qubit at position $(i,k)$. Under this mapping, the value of $x_{i,k}$ can be illustrated in a different way. If the qubit $(i,k)$ is in state $|1\\rangle$, then $x_{i,k} |1\\rangle = \\frac{I-Z_{i,k}}{2} |1\\rangle = 1|1\\rangle $, which means vertex $i$ is visited at time $k$. Also, for the qubit $(i,k)$ in state $|0\\rangle$, $x_{i,k}|0\\rangle = \\frac{I-Z_{i,k}}{2} |0\\rangle = 0 |0\\rangle $.\n",
"\n",
"Thus using the above mapping, we can transform the cost function $C_x$ into a Hamiltonian $H_C$ for the system of $n^2$ qubits and realize the quantumization of the arbitrage opportunity optimization problem. Then the ground state of $H_C$ is the optimal solution to the arbitrage opportunity optimization problem. In the following section, we will show how to use a parameterized quantum circuit to find the ground state, i.e., the eigenvector with the smallest eigenvalue."
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## Paddle Quantum Implementation\n",
"\n",
"To investigate the arbitrage opportunity optimization problem using Paddle Quantum, there are some required packages to import, which are shown below. The ``networkx`` package is the tool to handle graphs."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 1,
"source": [
"# Import packages needed\n",
"import numpy as np\n",
"import networkx as nx\n",
"import matplotlib.pyplot as plt\n",
"\n",
"# Import related modules from Paddle Quantum and PaddlePaddle\n",
"import paddle\n",
"from paddle_quantum.circuit import UAnsatz\n",
"from paddle_quantum.finance import arbitrage_opportunities_hamiltonian"
],
"outputs": [],
"metadata": {
"ExecuteTime": {
"end_time": "2021-05-17T08:00:15.901429Z",
"start_time": "2021-05-17T08:00:12.708945Z"
}
}
},
{
"cell_type": "markdown",
"source": [
"Next, we generate a weighted directed graph $G$ with $3$ vertices. For the convenience of computation, the vertices here are labeled starting from $0$.\n",
"\n",
"At the same time, in order to verify the solution more easily, special values are assigned for the weights in the directed graph. In practice, users can construct their own desired graphs and set the real exchange rates."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# n is the number of vertices in the graph G\n",
"n = 3\n",
"nodes = [ \"JPY\", \"CNY\", \"USD\"]\n",
"G = nx.DiGraph()\n",
"G.add_nodes_from(nodes)\n",
"edges = [(\"JPY\",\"CNY\", 0.5), (\"CNY\",\"JPY\",2), (\"CNY\",\"USD\", 0.33), (\"USD\",\"CNY\",3),(\"JPY\",\"USD\", 0.25), (\"USD\",\"JPY\",4)]\n",
"G.add_weighted_edges_from(edges)\n",
"\n",
"# The two graphs represent the exchange rates in different directions\n",
"G1 = nx.DiGraph()\n",
"G1.add_nodes_from(nodes)\n",
"edges1 = [(\"JPY\",\"CNY\", 0.5), (\"CNY\",\"USD\", 0.33), (\"USD\",\"JPY\",4)]\n",
"G1.add_weighted_edges_from(edges1)\n",
"\n",
"G2 = nx.DiGraph()\n",
"G2.add_nodes_from(nodes)\n",
"edges2 = [(\"CNY\",\"JPY\",2), (\"USD\",\"CNY\",3),(\"JPY\",\"USD\", 0.25)]\n",
"G2.add_weighted_edges_from(edges2)\n",
"\n",
"\n",
"options = {\n",
" \"with_labels\": True,\n",
" \"font_weight\": \"bold\",\n",
" \"font_color\": \"white\",\n",
" \"node_size\": 2000,\n",
" \"width\": 2\n",
"}\n",
"fig, ax = plt.subplots(1, 2, figsize=(15, 4))\n",
"for i, a in enumerate(ax):\n",
" a.axis('off')\n",
" a.margins(0.20)\n",
"nx.draw(G1, pos=nx.circular_layout(G1), ax=ax[0], **options)\n",
"nx.drawing.nx_pylab.draw_networkx_edge_labels(G1, pos=nx.circular_layout(G1), ax=ax[0], edge_labels=nx.get_edge_attributes(G1, 'weight'))\n",
"nx.draw(G2, pos=nx.circular_layout(G2), ax=ax[1], **options)\n",
"nx.drawing.nx_pylab.draw_networkx_edge_labels(G2, pos=nx.circular_layout(G2), ax=ax[1], edge_labels=nx.get_edge_attributes(G2, 'weight'))\n",
"plt.axis(\"off\")\n",
"plt.show()"
],
"outputs": [],
"metadata": {
"ExecuteTime": {
"end_time": "2021-05-17T08:00:16.212260Z",
"start_time": "2021-05-17T08:00:15.918792Z"
}
}
},
{
"cell_type": "markdown",
"source": [
"### Encoding Hamiltonian\n",
"\n",
"Here we construct the Hamiltonian $H_C$ of Eq. (4) with the replacement in Eq. (5). \n"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 3,
"source": [
"# Penalty parameter: in this case, it is equal to the maximum weight of the edges in the directed graph\n",
"penalty = 4 \n",
"# In this example, all currency types are selected within the transaction cycle\n",
"K = n\n",
"# Construct the Hamiltonian of arbitrage opportunity optimization\n",
"hamiltonian = arbitrage_opportunities_hamiltonian(G, penalty, n, K)"
],
"outputs": [],
"metadata": {
"ExecuteTime": {
"end_time": "2021-05-17T08:00:16.237497Z",
"start_time": "2021-05-17T08:00:16.219567Z"
}
}
},
{
"cell_type": "markdown",
"source": [
"### Calculating the loss function \n",
"\n",
"We adopt a parameterized quantum circuit consisting of $U_3(\\vec{\\theta})$ and $\\text{CNOT}$ gates, that can be done by calling the built-in function [`complex entangled layer`](https://qml.baidu.com/api/paddle_quantum.circuit.uansatz.html).\n",
"\n",
"After running the quantum circuit, we obtain the output circuit $|\\vec{\\theta\n",
"}\\rangle$. From the output state of the circuit, we can define the loss function of the arbitrage opportunity optimization under the classical-quantum hybrid model:\n",
"\n",
"$$\n",
"L(\\vec{\\theta}) = \\langle\\vec{\\theta}|H_C|\\vec{\\theta}\\rangle.\n",
"\\tag{6}\n",
"$$\n",
"\n",
"We then use a classical optimization algorithm to minimize this function and find the optimal parameters $\\vec{\\theta}^*$. The following code shows a complete network built with Paddle Quantum and PaddlePaddle."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 4,
"source": [
"class AONet(paddle.nn.Layer):\n",
" def __init__(self, n, p, K, dtype=\"float64\"):\n",
" super(AONet, self).__init__()\n",
"\n",
" self.p = p\n",
" self.num_qubits = n * K\n",
" self.theta = self.create_parameter(shape=[self.p, self.num_qubits, 3],\n",
" default_initializer=paddle.nn.initializer.Uniform(low=0, high=2 * np.pi),\n",
" dtype=dtype, is_bias=False)\n",
"\n",
" def forward(self, hamiltonian):\n",
" \"\"\"\n",
" Forward propagation\n",
" \"\"\"\n",
" # Define a circuit with complex entangled layers\n",
" cir = UAnsatz(self.num_qubits)\n",
" cir.complex_entangled_layer(self.theta, self.p)\n",
" # Run the quantum circuit\n",
" cir.run_state_vector()\n",
" # Calculate the loss function\n",
" loss = cir.expecval(hamiltonian)\n",
"\n",
" return loss, cir"
],
"outputs": [],
"metadata": {
"ExecuteTime": {
"end_time": "2021-05-17T08:00:16.258893Z",
"start_time": "2021-05-17T08:00:16.241066Z"
}
}
},
{
"cell_type": "markdown",
"source": [
"### Training the quantum neural network\n",
"\n",
"After defining the quantum neural network, we use the gradient descent method to update the parameters to minimize the expectation value in Eq. (6). "
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 5,
"source": [
"SEED = 100 # Set a global RNG seed \n",
"p = 1 # Number of layers in the quantum circuit\n",
"ITR = 120 # Number of training iterations\n",
"LR = 0.4 # Learning rate of the optimization method based on gradient descent"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"Here, we optimize the network defined above in PaddlePaddle."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 7,
"source": [
"# Fix paddle random seed\n",
"paddle.seed(SEED)\n",
"\n",
"# Building Quantum Neural Networks\n",
"net = AONet(n, p, K)\n",
"\n",
"# Use Adam optimizer\n",
"opt = paddle.optimizer.Adam(learning_rate=LR, parameters=net.parameters())\n",
"# Gradient descent iteration\n",
"for itr in range(1, ITR + 1):\n",
" # Run the network defined above\n",
" loss, cir = net(hamiltonian)\n",
" # Calculate the gradient and optimize\n",
" loss.backward()\n",
" opt.minimize(loss)\n",
" opt.clear_grad()\n",
" if itr % 10 == 0:\n",
" print(\"iter: \", itr, \" loss: \", \"%.4f\"% loss.numpy())"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"iter: 10 loss: 0.4704\n",
"iter: 20 loss: 0.1302\n",
"iter: 30 loss: -0.2744\n",
"iter: 40 loss: -0.4700\n",
"iter: 50 loss: -0.5512\n",
"iter: 60 loss: -0.5684\n",
"iter: 70 loss: -0.5821\n",
"iter: 80 loss: -0.5833\n",
"iter: 90 loss: -0.5843\n",
"iter: 100 loss: -0.5847\n",
"iter: 110 loss: -0.5849\n",
"iter: 120 loss: -0.5849\n"
]
}
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Decoding the quantum solution\n",
"\n",
"After obtaining the minimum value of the loss function and the corresponding set of parameters $\\vec{\\theta}^*$, our task has not been completed. In order to obtain an approximate solution to the arbitrage opportunity optimization problem, it is necessary to decode the solution to the classical optimization problem from the quantum state $|\\vec{\\theta}^*\\rangle$ output by the circuit. To decode a quantum state, we need to measure it and then calculate the probability distribution of the measurement results, where a measurement result is a bit string that represents an answer for the arbitrage opportunity optimization problem: \n",
"\n",
"$$\n",
"p(z) = |\\langle z|\\vec{\\theta}^*\\rangle|^2.\n",
"\\tag{6}\n",
"$$\n",
"\n",
"In the case of quantum parameterized circuits with sufficient expressiveness, the greater the probability of a certain bit string is, the greater the probability that it corresponds to an optimal solution of arbitrage opportunity optimization problem.\n",
"\n",
"Paddle Quantum provides a function to read the probability distribution of the measurement results of the state output by the quantum circuit:"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 8,
"source": [
"# Repeat the simulated measurement of the circuit output state 1024 times\n",
"prob_measure = cir.measure(shots=1024)\n",
"arbitrage_opportunity_route = max(prob_measure, key=prob_measure.get)\n",
"print(\"The bit string form of the solution:\", arbitrage_opportunity_route)"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"The bit string form of the solution: 100001010\n"
]
}
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"After measurement, we have found the bit string with the highest probability of occurrence, i.e. the most profitable cycle in the form of the bit string. The binary strings are grouped every $n$ bits, and $1$ appearing at the $k$th bit in each group indicates the order that the asset is traded into this market. If the result is not valid as explained before, users can get better training results by adjusting parameters such as the random seed ``SEED``, the number of layers of the quantum circuit ``p``, the number of iterations ``ITR`` and the gradient descent optimization rate ``LR``.\n",
"\n",
"The following code maps the bit string back to the classic solution in the form of `dictionary`, where the `key` represents the vertex currency and the `value` represents its order, i.e. when the asset is traded into this market.\n",
"\n",
"Also, we calculated the positive return of the asset after the optimal arbitrage cycle."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 9,
"source": [
"solution = {nodes[i]:t for i in range(n) for t in range(n) if arbitrage_opportunity_route[i * n + t] == '1'}\n",
"print(solution)\n",
"rate = sum([np.log2(G[u][v][\"weight\"]) if solution[v] == (solution[u] + 1) % n else 0 for (u, v) in G.edges])\n",
"print(\"Positive return rate: \", 2**rate)"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"{'JPY': 0, 'CNY': 2, 'USD': 1}\n",
"Positive return rate: 1.5\n"
]
}
],
"metadata": {
"ExecuteTime": {
"end_time": "2021-05-17T08:02:14.554317Z",
"start_time": "2021-05-17T08:02:14.500593Z"
}
}
},
{
"cell_type": "markdown",
"source": [
"In order to clearly represent the most profitable cycle of the assets, a graphical representation is still chosen:\n",
"* The numbers in the vertices represent the order of the transaction to this market and the letters represent currencies\n",
"\n",
"* The red edges represent the found optimal route."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 10,
"source": [
"label_dict = {i: str(i) + \", \" + str(t) for i, t in solution.items()}\n",
"edge_color1 = [\"red\" if solution[v] == (solution[u] + 1) % n else \"black\"\n",
" for (u, v) in G1.edges]\n",
"edge_color2 = [\"red\" if solution[v] == (solution[u] + 1) % n else \"black\"\n",
" for (u, v) in G2.edges]\n",
"\n",
"# Draw the optimal arbitrage cycle on the graph\n",
"fig, ax = plt.subplots(1, 2, figsize=(15, 4))\n",
"for i, a in enumerate(ax):\n",
" a.axis('off')\n",
" a.margins(0.20)\n",
"nx.draw(G1, pos=nx.circular_layout(G1), labels=label_dict, edge_color=edge_color1, ax=ax[0], **options)\n",
"nx.drawing.nx_pylab.draw_networkx_edge_labels(G1, pos=nx.circular_layout(G1), ax=ax[0], edge_labels=nx.get_edge_attributes(G1, 'weight'))\n",
"nx.draw(G2, pos=nx.circular_layout(G2), labels=label_dict, edge_color=edge_color2, ax=ax[1], **options)\n",
"nx.drawing.nx_pylab.draw_networkx_edge_labels(G2, pos=nx.circular_layout(G2), ax=ax[1], edge_labels=nx.get_edge_attributes(G2, 'weight'))\n",
"plt.axis(\"off\")\n",
"plt.show()"
],
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": [
"<Figure size 1080x288 with 2 Axes>"
],
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA1MAAADnCAYAAAD7CwxiAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8rg+JYAAAACXBIWXMAAAsTAAALEwEAmpwYAAA4PUlEQVR4nO3deXiU9b3//+d9ZyaTjQAh7IRA2BeVYgLI4sJSScT14FrUywWX6jnaY63oOWgrHvXXWrUesRWqbYW6Yn+1aoKCcARRCIsLu0hCSNhDlJB9lvv7x5BADGGGMMlsr8d1cZXMfefOZ+LVefO635/78zEsy7IQERERERGR02IGewAiIiIiIiLhSGFKRERERESkBRSmREREREREWkBhSkREREREpAUUpkRERERERFpAYUpERERERKQFFKZERERERERaQGFKRERERESkBRSmREREREREWkBhSkREREREpAUUpkRERERERFpAYUpERERERKQFFKZERERERERaQGFKRERERESkBRSmREREREREWkBhSkREREREpAUUpkRERERERFpAYUpERERERKQFFKZERERERERaQGFKRERERESkBWzBHkCo8ngsisqqKCytoMbpwen2YI8xibOb9E1NIj0lAdM0gj1MERGRNqX6KCJynMLUMR6PxaqdpSzbdpC1u8rYcbAC0zCwmQYWFpYFhgEGBi6PhceyGNAliaw+KUwc3IVx/VJVPEREJOKoPoqINM+wLMsK9iCC6Ui1k7fXFTN/ZQGVtS6q6tyczi/EABJiY0h02Jg5IYNrMtNoH29vreGKiIi0CdVHERHfojZMVde5eSpvK2+tK8YwoMbpOeNrxttNPBZcm5nGw9lDiI+NCcBIRURE2o7qo4iI/6IyTOUXlnHvGxsor3ZS4zrzIvFjcTaT5Hg7c28YSVaflIBfX0REpDWoPoqInJ6oClO1LjdzPtjCog0lAbnT5kuc3WT6yF7MnjYUh0134UREJDSpPoqItEzUhKnKWhczXlnD1n3lrXK3rTlxNpOhPZJZcOtoEh1a70NEREKL6qOISMtFRZiqrHUx/eXPKThUSW0bFop6DptJRudEFt05VgVDRERChuqjiMiZifhNe2tdbma8siZohcI7Bg8Fhyq58dU11LrcQRmDiIjIiVQfRUTOXMSHqTkfbGHrvvKgFYp6tS4PW/aWM+eD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9mjCjz9dq/pNg9PSgjdOEWmWwpQPClOB5W/XKjs7mx49egRxpCIickpOJ6SmQnk5pKdDYaH3QSlpGXWtRMKSwpQPClOtp75rlZubS15eHtu3b290/JxzzmkIVupaiYiEmFdfhdtuO/714sVw8cXBG0+k8dW1mjz5eLhS10okaBSmfFCYajvqWomIhAmnEwYN8naj6o0ZA59/ru5Ua6irg88+O778urpWIiFDYcoHhangUNdKRCSELVwIN97Y9PVly+Cii9p+PNFGXSuRkKEw5YPCVGho7a5VTU0NcXFxgRyyiEjk+vvfvWHqxH9C2Gzef+BPmhS8cUUjda1EgkphygeFqdAT6K7Vn//8Z/7+979z8OBBnn76aaZNm6ZlfUVEfCkvh1mz4I9/hN/8Bv7zP71dEQmu3buPB6tPPlHXSqSVKUz5oDAV+vzpWt1yyy1kZ2djmmaj783Pz+e6665j9erVbN68md/97ne8++67xMfHN5zjdruJiYlps/cjIhI2HngAnn0WnnnG+3cJLa3Qtdq0aRM9e/akY8eOrTRokfBi+j5FJLT17duXn//857z//vscPnyYjz76iPvvv59BgwZRXl7Ou+++y/bt2/F4PI2+r6qqig8//JBbbrmFLl260K9fPwDKysoaznG73cybN4+xY8cyZ84cNmzY0KbvTUREpMViY2HiRPjd72DTJigqgj/9CS6/3Nul2rzZG4QnTYJOneDKK717hpWUnPRyP/zwAy+99BLjx49nzJgxfPTRR238hkRCj8KURJS4uDh++tOf8txzz7Ft2zYKCgqYO3cuP/vZz7DZbI3OLSkpYf/+/Uw6Nr+/rKyMIUOG8O233zacc/DgQfbt28fQoUNxuVw8+uijfPDBB236nkRERAKid2+480745z/h8GHvNMBf/tLboaqo8L5+553wi180nh54jMPh4KmnnmLz5s08/vjjvPTSS41mg4hEI5vvU0TCV33X6mT27dtHZWUl55xzDgAHDhygvLycPn36NJxTUlLC4cOHmT17Nunp6QC4XK5WH7eIiEirqu9a1XeuTnzW6rrrTvr8W3x8PA6HA4BevXpx+PBhampqSEhIaOvRi4QMhSmJWuXl5ezdu5fExETKy8vZsWMHKSkp9O3bt+GcsrIyPvzwQwoLC7n77rvJzs5u0uESEREJe/VdqzvvbLxK4wnq6uqIPfZc1fz58xk5cqRWwpWop2l+ErXatWtHamoqAB9//DGffvopV1xxBUDD81UXXngh69at47rrruO1115j9+7dwRquiIhI22hmRdvY2Fj27t3LtGnTOHz4MPfee+/xrtSBA1Bc3IaDFAkNClMStcaPH09aWhrdunXjL3/5C/fccw/Dhw8HaFj1z+FwkJqayk033UT//v159tlnG11DU/5ERCQaVFZW8uabbzJjxgzOP/98XnrpJQYOHOg9WFUFTz3l7W4NHw4PPgjLl3tXExSJcApTErVsNhu///3v2b9/P3/961+58MIL+dvf/saCBQsAWLVqFXv37m04/+uvv254nsqyLFwuFy+++CIjRozg4YcfZsWKFTidzmC8FRERkVZTV1fHzTffzP333899993Hr371K5JOfKbKbvcGpxNXCJw4sfEKgepaSYTSPlM+aJ+p6ON0OrHb7bz44ovMmzePzp070717d+rq6vjDH/5A9+7dAe9duunTp7N48eKG723fvj1TpkwhOzubqVOn0qNHj2C9DRGR1qd9pqKC2+3m7bffJi8vj507d2JZFrNmzeLSSy9tvMn9ifta5ebCli2NLzRsGOTkePe1GjfOr32tREKdwpQPClOyZs0adu7cydVXX43dbm90rKamhhUrVjRsGrx9+/ZGx8855xyys7PJzs7mvPPOa/L9IiJhTWEqKq1Zswabzca555576hOLimDxYm+4WroUKiuPH0tKgsmTj28anJbWuoMWaSUKUz4oTMnpKCgoaAhWy5Yto7q6uuGYulYiEnEUpsRftbXHu1Z5eU27VsOHHw9W6lpJGFGY8kFhSlrK365VTk4O5513npZcF5HwozAlLaWulUQIhSkfFKYkUNS1EpGIozAlgaCulYQxhSkfFKakNfjTtcrJyWl41kpdKxEJSQpT0hrUtZIwojDlg8KUtAV1rUQkLClMSWtT10pCnMKUDwpT0tZ8da1GjBjRaIVAda1EJGgUpqSt1XetcnPhk08ad63atYNJk44vv96rV/DGKVFDYcoHhSkJNnWtRCRkKUxJMKlrJSFAYcoHhSkJJSd2rXJzc/n2228bHVfXyjePx6KorIrC0gpqnB6cbg/2GJM4u0nf1CTSUxIwTcP3hUREYUpCi7pWZ0T1sWUUpnxQmJJQ5k/XKicnh6lTp9K9e/cgjjR4PB6LVTtLWbbtIGt3lbHjYAWmYWAzDSwsLAsMAwwMXB4Lj2UxoEsSWX1SmDi4C+P6pap4iDRHYUpClbpWPqk+BobClA8KUxIu1LVq7Ei1k7fXFTN/ZQGVtS6q6tyczoedASTExpDosDFzQgbXZKbRPt7eWsMVCU8KUxIufHWtTlwhMMK7VqqPgaUw5YPClISraO1aVde5eSpvK2+tK8YwoMbpOeNrxttNPBZcm5nGw9lDiI+NCcBIRSKAwpSEoyjtWqk+tg6FKR8UpiQSREvXKr+wjHvf2EB5tZMa15kXiR+Ls5kkx9uZe8NIsvqkBPz6ImFHYUoiQVHR8WAVoV0r1cfWozDlg8KURKJTda06dOjQaIXAcOha1brczPlgC4s2lATkTpsvcXaT6SN7MXvaUBy26LsLJ9JAYUoijb9dq5wcb9fKHtrT21QfW5/ClA8KUxLpwr1rVVnrYsYra9i6r7xV7rY1J85mMrRHMgtuHU2iI7R+JyJtRmFKIl0Yd61UH9uGwpQPClMSbcKpa1VZ62L6y59TcKiS2jYsFPUcNpOMzoksunNsVBQMkSYUpiSa+NO1ql96PchdK9XHtqMw5cORI0f4r//6L5KTk3nyySeDPRyRNuVv1yonJ4cxY8a0adeq1uXmunmr2bKvPCiFop7DZjKsRzJvzBwTNVMaRBooTEk0C9Gulepj21KY8sGyLGpqajAMg7i4uGAPRySoQqlr9d//3Mii9SVtOnWhOXE2k+nnpvHEFcODPRSRtqUwJeJV37XKzfWGq61bGx9vw66V6mPbUpgSkRapqanh008/bQhXJ+ta5eTkkJ2dHfCuVX5hGTf9ZU2bPEzrrzi7yYJbR0fdKkYS5RSmRE4uSF0r1ce2pzAlIgHRVl2r6jo3FzyznINHawMx7IDq0s7Bp7+8KCr32ZAopTAl4puvrtVZZzXe16qFXSvVx+BQmBKRgGvNrtXs9zbxzrrikJi+8GNxNpNrstJ4/LLInc4g0ojClMjpa2nXqqQEHn0U7rwTRo9uclnVx+BQmBKRVheortWRaiejnlwa1AdqfXHYTPIfmUz7+NDee0QkIBSmRM7M6XSt3nsPnn8e4uPhnXfgkksaTlN9DB6FKT+53W7Wrl3Lnj17AOjZsyejRo3CNM0gj0wkvPjqWv3kJz9p2Nfqx12r+SsL+P2S7SE1F/zH4u0mD0wZxO0TMoI9FJHWpzAlElin6lqZJniO1b+YGPjrX2HGDED1MZgUpvywePFi7r33Xvr370/v3r0BKC4uZseOHbz44otMnTo1yCMUCV/+dq1+evHFXPnXLSE5F/zHurRzsHrWJEzTCPZQRFqXwpRI66mthZUrvcHqvfdg586m59x9N54X5zLm6U9UH4NEYcoPgwYN4uOPPyY9Pb3R60VFRVx88cVs27YtSCMTiSyn6lol9c+k57W/psYdxAH6KSE2hnk3ZjK+f2qwhyLSulasgC+/hAkTYOTIYI9GJHL96U9w991NX+/YkZVrtnPXwvVU1oV+gYzE+hjZWxIHiMfjOelzHD169MDjCd12qki4iYuL4+KLL+biiy/m+eefb9S12hw3lNrQrxMAVDvdLNt2MKKKhchJnX++94+ItK7YWO//JiTA2Wd7n6Vq3x5uvpll2w5SFQZBCiKzPipM+eG2225j1KhRXHfddfQ6tqpKSUkJb775JrfddluQRycSuTIyMrjnnnu45557mPa/K9m0t7zR8c9+dRG9OiZwx4J1DO2ezP2TBzYcq3G62V1Wxd/X7OZvX+ziF5MHct+kAew7Us3kZz9tuIP31swxjM7oxJItB5i5YN0pxzOkWzsenTaMn/TuQLXTzeJN+3niwy1N7gZaFqzddThAvwUREYl6t9wC06ZBp07e56VOsHb5Sn48zUz1se1o9QQ/zJo1izfeeAPDMMjPzyc/Px/DMHjzzTd56KGHgj08kYjn8VjsOFjh17lFhyt5dVUhy7YdpH/nJH5z2TCuy0pj7vLv+O5gBd3bx/OLKd6i8m8jezI6oxPlNU5mv7fplNdNjI1hwW2jOa9fJ5ZtO0jJ99VcP6o3T1111knP//ZABZpFLSIiAWEY0KVLkyCl+hh8ClN+eOyxxxgyZAgPPfQQL7zwAi+88AIPPfQQgwcPDvbQRKJCUVkVpuHfw6rb9x/l8Q+28PPXN7Bs+0EALhrUhTq3h1n/+AaPx+Lm8/owqk8Ks6YOAeC3H21nf3nNKa97bVYaqUkOlm49wM9f38C1876gxunmkrN6kNYxvsn5pmFQdLjqNN+piIiI/1Qfg09hyg9ZWVnBHoJIVCssrcB2miv/dE12kNE5EYCyyjoA1hV9z+v5u7HHmCy4bRSd2zlYu6uMhauLfF5vWI/2AGzccwSAqjo3Ow9VEGMaDOme3OR8m2lQWFrZ5HUREZFAUX0MPj0z5Ydp06YFewgiUa3G6cFqMiP85H46rBu7njq+keH3VXXMX1nQ8PX/t3gbk4d0pVv7OGpdbmb9Y6Nf101N8j78W1nranit/oHfzkmOJudbQI0rPB4IFmmpjRs30r17d1JTmz5MXltbi8PR9P8bIhI4qo/Bp86UH+6//36+++67Jq9XVFTw8ssvB2FEItHF6fbg7/Tq+jnhc5d/x0PvfsP5v11OwQl3wI7Wunh7fTEAK3eUsvOQf3PNSyu8d+8SHcfvQSXGeueuH6poureHZVnUhfBO9CKBcPPNN2O32xu+rl/htqqqivO1yp9Iq1N9DD6FKT8sXbqU/v37N3z90ksvAZCUlMTcuXODNSyRqGGPMfFzSnjDnPDffbydt9YVc/SEO2X1PB6r0f/6Y8uxlQTP6dUB8BaKfp2T8Hgstu0/2uR8wzCItekjViKbx+Ohffv2DV+fe+65ACQkJFBXVxesYYlEDdXH4Iucd9KKYuvX9j/m2Wefbfh7zI9WVRGRwIuzmxi07m7pn/3qInY9dQk/Hdr1pMffXLubsso6Jg7uwks3jOStO87DYY8hd9M+dpc1fZDWAOJs+nyQyObxeKiurgagvLycnTt3UlVVhdvtxuVq+g81EQks1cfg0zNTfujYsSPvv/8+06ZN46233sLpdPLGG2+QlJREu3btgj08kYjXNzUJ10nuktWvYOR0B26J1ZP9HIDKOjc3vrKG2dOGMnFwF2qcbt5aW8ycD7c0e52+qYkBG5dIKLr22mu5+OKLmTJlCsuXL+eee+7h/PPPx2azMX369GAPTyTiqT4Gn2FF0kLvrWTr1q3cfPPNfPvtt1xxxRXMnj2b//iP/8Dj8fDcc89piXSRVubxWAx5bDG1J8yx7tUxnv974EJsMSZTnvvU7302TiY5zsaXs3/Ktv3lXD53VbMF43Q4bCbbHp+K4e/8C5EwlZuby7Zt25g6dSpDhw5l7dq1WJbFqFGjgj00kYin+hh86kz5YciQIeTn5zd67cMPPwzSaEQik9vtZsyYMezdu5cRI0YwYsQIhg0bxtlnn83w4cMZ0CWJTcfmZf/HxP7cPiEDW4zJxj1H+M7Ph2Sbc16/VFweD//59tcBKRQAA7smRUyhEDmVnJwccnJyGr7WdiIibaSkBDMvjwGVsWxyeFfUVH1sewpTfli4cCHjx4+nT58+jV6vqalh48aNKhwiAeDxeNi0aRM1NTXs3buX3NzchmO5ublk9enN5r3lWEBaxwRq6tys+PYQT+Zu9Xslo+Z8tHk/g2YvPrOLnMAwIKtPp4BdTyRkFRTAnj3Qty/06hXs0YhENqcTVq2CvDzvn43epcuzJs1kc+alWIap+hgEClN+ePrpp/n6668bvs7Pz2fUqFHExsZyxx138OWXXwZxdCLhrba2ls8++4zc3NyTLuhit9vp378/SXTg7XXFVNa5efDdb4IwUv/F22OYOLhLsIch0vrmzoVnn4VnnoEHHgj2aEQiT0nJ8fC0dCkcPWF1vMREmDSJiReN5e0yG5VOj+pjEChM+SE2NrbRP/JuvfVWNm3ahGlqMUSRligqKiIvL4+8vDw++eQTKitPvhN6RkYGy5YtIz09nX4ei0SHjcq60N/or53DxtiMyLrzJiIibaCZ7lODoUMhO9v7Z/x4cDgY57FIfPoTKp1N93QKNZFYHxWm/GCaJkVFRaSnp7N9+3Z2795NYWEh8fHxwR6aSFg4sfuUl5fH1q1bGx0fPnw4OTk5nH322cyYMQOAzMxMcnNz6dy5MwCmaTBzQga/X7KdGmfobvYXbzeZOSED04yc+eAiItKK/Og+kZMDU6dCenqTb1d9DC6FKT889thjjBs3jkGDBlFRUcGCBQsYO3YsHo+Hl19+OdjDEwlJp+o+tWvXjsmTJ5Odnc3UqVNJS0trOLZy5UrKy8t5+eWXm2w9cE1mGs98vL3N3kNLeCy4OjPN94kiIhKdWtB98kX1MXgUpvxw6aWXMm7cOHbt2sXw4cOJjY3l0ksvBdBUP5Fj6rtPeXl55ObmNtt9ys7OZuzYsU02w673pz/9qdmf0T7ezrWZaby9rpgaV+jdfYuzmVyTlUb7eHuwhyIiIqGkpAQWL/aGpyVLTt59qg9QJ+k++aL6GDwKU35KSUkhJSWl4WuFKJGWd5/OxMPZQ1i8eT81R0Nvbnj7BDsPTx0S7GGIiEiwtUL3yRfVx+BQmBIRv53YfcrLy2PLlsa7m/vbfToT8bExzL1hJDe+uiak5obH2U3mXj+S+NimKxKKiEgUaOXuky+qj8GhMOVDZWUlb7zxBgkJCdxwww3BHo5ImwtG98mXrD4pTB/Zi0XrS0JiOkOczWT6yDQy+6T4PllERCKDr+7TkCHe4JSTE7Duky+qj21PYcqH8vJyZs6cSbdu3RSmJCqEQvfJH7OnDWXLvnI27y2nNogFw2EzGdojmdnTInP6goiInCDI3Sd/qD62LYUpEQnJ7pMvDlsMC24dzfSXP6fgUGVQCobDZpLROZEFt47GYYvM6QsiIlEtBLtPvqg+ti2FKZEoFC7dJ18SHTYW3TmWG19dw5a95W06pSHu2B23BbeOJtGhj1IRkYgRBt0nX1Qf207kv0MRAWD37t0Ny5aHS/fJH4kOG2/MHMOcD7ayaENxmzx0G2f3zgGfPW1IxN9xExGJeGHYffKH6mPbUJgSiVD+dJ+ys7PJzs5m3LhxIdt98ofDFsMTVwzn8hE9uOf1DZRXO1vlLlyczSQ53s7cG0aSFcEP04qIRLw9e46Hp1N1n6ZOhT59gjbMM6X62PoUpkQiSKR2n/yV1SeFT395EU8t3spba4sxDagOwJ24eLuJx4JrstJ4eOqQiF3eVUQkYjmd8PnnkJt76u5TdjZMmBA23Sd/qT62HoUpkTAWTd0nf8XHxvD4ZcN5YMog3llXzLyVBVTUuqh2urEs/69jGBBvjyHJYeOOCRlcnRmZO7eLiESsE7tPS5dCefnxYxHUffKX6mPrUJgSCTPR3n3yV/t4O7dPyODWcX1ZtbOU5dsPkV94mB0HKzANA5tpYAGWZWEYBgbg8lh4LIuBXZPI6tOJiYO7MDajE6ZpBPvtiIiIL1HeffKX6mNgKUyJhLi6ujpWrlyp7lMLmabBhAGdmTCgMwAej8XusioKSyupcbmpc3mItZnE2WLom5pIeqcEDEPFQUQkLJyq+5SQ0HjlvSjoPp0O1cfAUJgSCUH13af6fZ8qKioajqn7dGZM06BPaiJ9UhODPRQRETld6j61GtXHllGYEgkB6j6JiIg0Q90nCWEKUyJBou6TiIjISaj7JGFEYUqkjaj7JCIi0gx1nyRMKUyJtCJ1n0REJFpUV1cTHx/fsArcKfnqPg0eDDk56j5JyFOYEgmgurq6hn2fcnNz1X0SEZGIlp+fz5IlS1i+fDndunVj4cKFzQcpdZ8kAilMiZwhdZ9ERCRaVFRUkJSU1PD1mDFjuP3223n66afJzMw8+TcdPAhTpsA33zR+Xd0niQAKUyInWLVqFb/+9a/5+c9/zpVXXonH48E0zUbnqPskIiLRpLS0lHnz5vHxxx9z9OhRZs6cySWXXEJaWhoTJ07ksssuIzMzs/npfcnJsHevuk8SkRSmRKAhNH377bfs2bOHzz77jCuvvLKhKFiWxfr163niiSfUfRIRkahQH46++uorvv/+e5577jni4uJ45pln2LNnD3PmzOGqq65ixowZXHvttZSWljJ9+nSuv/76phdbtgwGDlT3SSKOwpQIYJomTqeTjz76iF//+tcsXLiw0VQGwzBISEjgvffeA9R9EhGRyFJQUMAf/vAHYmJiuOGGGxp1ms455xx+8pOf0KlTJwAmTJjA6tWrAbj++ut5//33mTx5MgMHDuSaa64hPj6eSy+9lJiYGO/F4+LgrLOC9dZEWpXClAjeu28//PAD7du3p2/fviQmJrJx40bOO++8hmIyYMAA/va3v3HRRRep+yQiIhGjurqa5557DtM06du3L7/4xS945ZVXGDhwIACdO3cGjs/iWLx4MTNmzACgY8eO5OXlNVxr5syZrFmzhuzs7ONhSiSCKUxJ1DpxbrdhGLz55puMHTuWrKwsBg4cyNtvv027du0YPnw4AHa7nZtuuimYQxYREQm4iooK3n//fXbt2gV4w9Urr7zCo48+SmJiIuCtmaZpsmPHDkpLSxk8ePBJr+VyuaiqqsLhcPi3RLpImFOYkqhSv/Le119/zTPPPENCQgLgLRIej4clS5awZs0a/vWvf1FVVcWAAQMawpSIiEgkcrvdjBgxgh07djBgwADGjRvHggUL+OabbxrN0ACYP38+WVlZ9O/fv+F7d+7cyZ///Gc2bNhAZWUl8+bNA1CQkqhg+j5FJHzV1dWxbNkyHnzwQYYPH056ejp33XUXf/zjHyk/YX8LwzD44YcfOHr0KH379uWll15i/PjxXHfddUEcvYiISOuz2Wykp6ez8djGub169SI1NZXNmzcD3huOAEVFReTn53PvvfeycOFCHnnkEWJiYnA6nSQlJfHEE0/wxRdfcJaej5Ioos6URJxT7fuUlJTE5MmTycnJabRPBsCjjz7acBft6NGjLF26lA0bNjB58uQ2Hb+IiEircDrh88+9y5Rfdhkcm8LXoUMHevfuTX5+PldddRUdO3akpqamYcGJ+i1CXnvtNVasWMG0adMYNmwYOTk5uN1uhg0bxrBhw4L2tkSCSWFKwt6J+z7l5eU13Emr5+/Ke4Zh4Ha7G1bue+GFF9pi+CIiIq1nzx5YvBhyc2HpUigvh5QUuOqqhlNsNhsXXHABd911F0eOHKF9+/asXr2a66+/ntzcXHbu3Mm///u/M2bMGPLz85vfnFckCilMSVgqLi5uCE9Lly5ttvt0uvs+aeUhEREJa/Xdp7w8759vvml8fPBg74a5TmejPZ8yMzMZM2YMd955J1u2bGHw4MEMHjyYnj17MmHCBACmTJnSlu9EJCwoTDXD5XKRk5PD+vXrAThw4ACdO3fmZz/7Gc8//3xwBxeFAtV9EhERiTgn6z7VS0iAiRMhJwemToW+fZu9zHPPPceaNWtwuVxceOGFAA2r+YnIySlMncKaNWsaFimwLIvS0tKGcCWtz5/uU32A0r5PIiISNfztPmVnw4QJ3k1z/WC32xk/fnwrDFgkcilMNcNms3HfffcxZ86cRq8/8MADQRpR5FP3SUREpBn13ae8PFiypMXdJxEJLIWpU7j//vt5/vnnOXr0KAAjRozg8ssvD/KoIou6TyIiIifRSt0nEQkshalTSElJ4bbbbmt4Ruqxxx7TBnRnqK6ujlWrVpGbm3vS7lP9UqvqPomISNRR90kk7ChM+TBr1ixefPFFOnTooK5UC/nbfZo6dSq9e/cO4khFRETakNMJX3zhXThC3SeRsKQw5UPXrl2prKwkJiZGXSk/1Xef8vLyyM3NPWn3KTs7m5ycHHWfREQkuqj7JBJRFKaa4fFYFJVVUVhaQY3Tg9PtwR5jEmc36ZuaRHpKAqapcFVP3ScRkejQqD6mDMA59ALs8WnEbTug+ngy9d2nvDxvB0rdJ5GIYliWZQV7EKHA47FYtbOUZdsOsnZXGTsOVmAaBjbTwMLCssAwwMDA5bHwWBYDuiSR1SeFiYO7MK5falQVjxO7T3l5eWzatKnRcXWfREQig8/66HJj2GJUH0/kT/epPkCp+yQS1qI+TB2pdvL2umLmryygstZFVZ2b0/mFGEBCbAyJDhszJ2RwTWYa7ePtrTXcoFL3SUQkeqg+ngZ1n0SiVtSGqeo6N0/lbeWtdcUYBtQ4PWd8zXi7iceCazPTeDh7CPGxMQEYafD4233Kzs5m/Pjx6j6JiEQA1Uc/7d17fNlydZ9EolZUhqn8wjLufWMD5dVOalxnXiR+LM5mkhxvZ+4NI8nqkxLw67cmdZ9ERKKX6uMpnNh9ysuDr79ufFzdJ5GoFFVhqtblZs4HW1i0oSQgd9p8ibObTB/Zi9nThuKwheZdOHWfRERE9bEZ6j6JiA9RE6Yqa13MeGUNW/eVt8rdtubE2UyG9khmwa2jSXSExuKJJSUlDcuWf/LJJxw9erThWFJSEpMmTSInJ0fdJxGRKKD6eAJ1n0TkNEVFmKqsdTH95c8pOFRJbRsWinoOm0lG50QW3Tk2KAVD3ScRETmZaK+PgLf7tHixd+EIdZ9E5DRFfJiqdbm5bt5qtuwrD0qhqOewmQzrkcwbM8e0yZQGf7pP9QFK3ScRkegTrfVR3ScRCaSID1P//c+NLFpf0qZTF5oTZzOZfm4aT1wxPODXVvdJREROR7TUR+B496n+2acjR44fU/dJRM5AiExSbh35hWXeh2lDoFAA1Lg8LNpQzOUjegRkFaP67lP9ynvqPomIiD8ivT6q+yQibSViO1PVdW4ueGY5B4/WBnsoTXRp5+DTX1502vts+Oo+DR06lJycHHWfRESkWWFdH2tqmg8+6j6JSBBEbGfqybytlFc7gz2MkyqvdvLU4q08fpnv6QzqPomISCCFZX10OuGOO+C112DZMrjgAt/dp0GDvMEpJ0fdJxFpNRHZmTpS7WTUk0uD+kCtLw6bSf4jk2kfb2/0utPpZNWqVeTm5qr7JCIiARWW9bGqCq6+2rvaHniDUdeu6j6JSEiIyM7U2+uKMYxgj+LUTAPeWVfM7RMy1H0SEZE2EW71kYMHYdIkOPHG4sqVx/+u7pOIBFnEdaY8HosxT38SknPBfyzOqsX24a/ZvGljo9fVfRIRkUALp/rYpZ2D1bMmYfboDgcOND3hkUfg9tvVfRKRoIu4ztSqnaVU1rqCPQy/VDk9HDpqkpiYyOTJk8nOzmbq1Kmkp6cHe2giIhJhwqk+VtS6+LzgMON79jx5mOrRQ0FKREJCxIWpZdsOUlXnDvYw/GLGxvGzXz3N3NsuwuFwBHs4IiISwcKpPlY73SzbdpDx69d7V/DbvBm++sr7p6QEJk8O9hBFRIAIDFN3XdCPxy4dxh0L1vHxFu/drEenDeXWcX1ZtL6YXy76BoCx/Trxn5MHMqhbO0zD4FBFLZv2HOHeN74EYPrIXjxz9TkA1Lk8VNW52HW4io+37OeVzwpP6+Hdcf06cf/kgZzVsz1x9hhWFxzmuvmrAYMDnkQFKRERaXVrd5Wx8lcX0atjQljUyLW7DntPiIuDc8/1/hERCTERFaY8Hgun2/cHeNdkB3++KRN7jMniTfs5WusiIzWRyUO6Njn3cEUt//pmL2kdEzh/QGdGpHVg8pCuXD9/td/Fom/nJOLtMWw/cJRzenVodOzbAxVYloUR6k8Ei4hI2PJ4LHYcrPDr3FCpkaqPIhIOIipMFZVVERtj+jxvRFpHEmJtLNmyn39/88uG13+8TDnAgfIafvP+FgCG9UjmH3ePZWTvjtw6ri9//HSnX+NauLqIhauLuHVcnyZhyjQMig5X0Sc10a9riYiInK6isipMP0NJqNRI1UcRCQe+k0cYKSytAD9qxaGjNQBMHNyVBbeO4heTB5DVpyPlNafexHDz3nI+3uydFjFpcJczHi+AzTQoLK0MyLVEREROprC0ApvpX5gKlRqp+igi4SCiwlSN078pBRt2/8DLK3ZiWRYTBnTmvkkDeefOsfzrnvEkx526Wbfnh2oAOiUF5jknC6hxhccDwSIiEp5qnB4s/NsJJVRqpOqjiISDiApTTreHGqf3g9d+wnS/+ql/1SeErafytpH55FLueX0Dr68pos7l4aye7bk6M+2UP6Nnh3jAO088ECzLoi6Ed6IXEZHw53R7sKzjNx3DoUaqPopIOIioMGWPMSn5vgqAkb07AN6d1Eekef++67B3ukCP9nH0TknghyonH27cxyP/3MT/bT8IQJKj+btuw3ok89Nh3gdwP9l2MCBjNgyDWFtE/WcQEZEQY48xMQwoKvPWwXCokaqPIhIOImoBiji7ybvrS7hgYBduG5/BuekptI+30zc1kfIaJ//6ei8Ag7snM//GTL7c/T07D1USH2ty0eAueDwWq74rbXTNrslxPHbpUHp1SOCCgZ2JtZls2P09r64qBGBM3xTevOM8yqudnP34xycdV2Z6R67LSqN/l3YA9OucyDPTz2bnoUoWri4izhbTir8VERGJdnF2EwODN/OLmTS4a1jUyJLvq1UfRSTkRVSY6puaxJKtB7nvrS+5fXwG/TonUuvysGLHIX730XYOHfVOO/j2wFH+saGEzD4pDOmejAVs33+UeSsLWFf0faNrdkpyMGN0OpW1LrbsO8JHmw/wymeF1B1bgr1+yVaXp/m56H06JTL93ONTIzq3i2P6uWmsLjjMXz/fRV+tVCQiIq2ob2oSLo/Fkq0HwqZG5heW0aWd9mEUkdBmWJbl3xOpYcDjsRjy2OLT2izwTN0ytg+PXTqMJ3O3Mm9lwWl/v8Nmsu3xqdpHQ0REWk0w6iOcWY1UfRSRcBBRk5FN02BAl6Q2/Zlj+6WydlcZ8z87/SAFMLBrkgqFiIi0qmDURzizGqn6KCLhIKKm+QFk9Ulh895yPxeAPXMzF6xr8fcaBmT16RTA0YiIiJxcW9dHaHmNVH0UkXARUZ0pgImDu5AQGx4PrMbbY5gYoM1/RURETkX1UUQk8CIuTI3rl0riKZZuDSXtHDbGZujOm4iItD7VRxGRwIu4MGWaBjMnZBBnD+23Fm83mTkhA9PUfHAREWl9qo8iIoEX2p+oLXRNZhqhvkahx8LnTvIiIiKBpPooIhJYERmm2sfbuTYzjbgQ3Tk9zmZybVYa7ePtwR6KiIhEEdVHEZHACs1P0wB4OHsIySH6Ydw+wc7DU4cEexgiIhKFVB9FRAInYsNUfGwMc28YGXJzw+PsJnOvH0l8mKyoJCIikUX1UUQkcELrkzTAsvqkMH1kr5CZzhBnM5k+Mo3MPinBHoqIiEQx1UcRkcAIjU/RVjR72lCG9kjGEeSC4bCZDO2RzOxpmr4gIiLBp/ooInLmIj5MOWwxLLh1NBmdE4NWMBw2k4zOiSy4dTQOm6YviIhI8Kk+ioicuYgPUwCJDhuL7hzLsB7JbT6lIc5mMqxHMovuHBs2myWKiEh0UH0UETkzhmWF+o4TgVPrcjPng60s2lBMjdPT6j8vzu6dAz572hDdcRMRkZCl+igi0jJRFabqrd1Vxj2vb6C82kmNK/BFI85mkhxvZ+4NI8nSw7QiIhImVB9FRE5PVIYpgOo6N08t3spba4sxDagOwJ24eLuJx4Jrs9J4eOoQLe8qIiJhR/VRRMR/URum6h2pdvLOumLmrSygotZFtdPN6fxGDAPi7TEkOWzcMSGDqzO1c7uIiIQ/1UcREd+iPkzV83gsVu0sZfn2Q+QXHmbHwQpMw8BmGliAZVkYhoEBuDwWHstiYNcksvp0YuLgLozN6IRpGsF+GyIiIgGl+igi0jyFqWZ4PBa7y6ooLK2kxuWmzuUh1mYSZ4uhb2oi6Z0SMAwVBxERiS6qjyIixylMiYiIiIiItEBU7DMlIiIiIiISaApTIiIiIiIiLaAwJSIiIiIi0gIKUyIiIiIiIi2gMCUiIiIiItICClMiIiIiIiItoDAlIiIiIiLSAgpTIiIiIiIiLaAwJSIiIiIi0gIKUyIiIiIiIi2gMCUiIiIiItICClMiIiIiIiItoDAlIiIiIiLSAgpTIiIiIiIiLaAwJSIiIiIi0gIKUyIiIiIiIi2gMCUiIiIiItICClMiIiIiIiItoDAlIiIiIiLSAgpTIiIiIiIiLfD/AC1EX2oXctz7AAAAAElFTkSuQmCC"
},
"metadata": {}
}
],
"metadata": {
"ExecuteTime": {
"end_time": "2021-05-17T08:02:14.571954Z",
"start_time": "2021-05-17T08:02:14.559634Z"
}
}
},
{
"cell_type": "markdown",
"source": [
"The left graph given above shows the edge that is not in the arbitrage cycle, and the right graph shows the optimal solution found by the algorithm."
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Conclusion\n",
"\n",
"In this tutorial, we provide a way to approximate a cycle of transactions with the maximum positive return in the arbitrage opportunity problem. For $n$ currencies, we need to use $n^2$ qubits for the optimization. We assign special values to exchange rates for testing, and users can assign those values themselves, i.e. according to the current market.\n",
"\n",
"In real financial markets, high return as $1.5$ in this tutorial on arbitrage is not usually available. Also, the number of currencies considered would be large with more influencing factors. This would be hard to implement. "
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"_______\n",
"\n",
"## References\n",
"\n",
"[1] Orus, Roman, Samuel Mugel, and Enrique Lizaso. \"Quantum computing for finance: Overview and prospects.\" [Reviews in Physics 4 (2019): 100028.](https://arxiv.org/abs/1807.03890)\n",
"\n",
"[2] Egger, Daniel J., et al. \"Quantum computing for Finance: state of the art and future prospects.\" [IEEE Transactions on Quantum Engineering (2020).](https://arxiv.org/abs/2006.14510)\n",
"\n",
"[3] Rosenberg, G. \"Finding optimal arbitrage opportunities using a quantum annealer.\" [1QB Information Technologies Write Paper (2016): 1-7.](https://1qbit.com/whitepaper/arbitrage/)"
],
"metadata": {}
}
],
"metadata": {
"interpreter": {
"hash": "3b61f83e8397e1c9fcea57a3d9915794102e67724879b24295f8014f41a14d85"
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3.7.11 64-bit ('pq_env': conda)"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.11"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}
\ No newline at end of file
{
"cells": [
{
"cell_type": "markdown",
"source": [
"# 量子金融应用:投资组合分散化\n",
"\n",
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## 概览\n",
"\n",
"当前量子计算应用到金融问题上的解决方案通常可分为三类量子算法,即量子模拟,量子优化以及量子机器学习 [1,2]。许多的金融问题本质上是一个组合优化问题,解决这些问题的算法通常具有较高的时间复杂度,实现难度较大。得益于量子计算强大的计算性能,未来有望通过量子算法解决这些复杂问题。\n",
"\n",
"量桨的 Quantum Finance 模块主要讨论的是量子优化部分的内容,即如何通过一些量子算法解决实际金融应用中的优化问题。本文主要介绍如何使用量子算法求解被动投资管理中投资组合分散化问题。"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## 投资组合分散化问题\n",
"\n",
"普通用户受专业知识和市场经验的不足的限制,在实际的投资中偏向于被动投资策略。指数投资就是一种常见的被动投资例子,比如说投资者长期购买并持有标普 $500$ 指数(Standard & Poor’s $500$)。作为投资人,假如你不想投资已有的指数,那么你也可以自己创建特定的指数投资组合,在市场中挑选合适的股票加入到创建的指数投资组合中。\n",
"\n",
"分散化是投资组合中平衡风险和收益的一个重要方法。对投资组合分散化的一个具体描述如下:当前可投资的股票数量为 $n$,指数投资组合中包含的股票数量为 $K$,需要对这 $n$ 个股票进行聚类,根据相似性将可选的股票划分为 $K$ 个类别,再从每个类别中选出最能代表该类别的股票,将其加入到指数组合中来,便于更好的控制风险,提高收益。"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### 编码投资组合分散化问题\n",
"\n",
"为了将投资组合分散化问题转化成一个参数化量子电路(parameterized quantum circuits, PQC)可解的问题,我们首先需要编码该问题的哈密顿量。\n",
"\n",
"在对该问题进行建模时,需要明确的有两个问题,第一是如何对不同的股票进行分类,第二便是以什么样的标准挑选代表性的股票。为了解决这两个问题,首先需要定义股票 $i$ 和股票 $j$ 之间的相似度 $\\rho_{ij}$:\n",
"* $\\rho_{ii} = 1 \\quad $ 该股票和其自身的相似度为1\n",
"* $\\rho_{ij} \\leq 1 \\quad$ 不同股票间 $\\rho_{ij}$ 越大,相似度越高\n",
"\n",
"由于两股票间收益率的相关性,我们可以在协方差矩阵基础上进一步对时间序列间的相似性进行度量。动态时间规整(Dynamic Time Warping, DTW)是一种常见的衡量两个时间序列之间相似度的方法,在本文中,采用DTW算法来计算两股票之间的相似性。基于该度量,我们可以对股票进行分类并挑选代表性股票。对于给定的 $n$ 支股票,每支股票我们可以定义 $n$ 个二进制变量 $x_{ij}$ 和 $1$ 个二进制变量 $y_j$。对于变量 $x_{ij}$,每 $n$ 位一组,$i$ 表示是第几支股票,$j$ 表示在该股票对应的 $n$ 个二进制变量中的序号。每支股票的 $n$ 位二进制变量如果相同位置为 $1$ (即 $j$ 相同),则说明这两只股票被分为同一类,其中 $i = j$ 的就是该类别中被选到指数组合中的最具代表性的股票:\n",
"\n",
"$$\n",
"x_{ij}=\n",
"\\begin{cases}\n",
"1, & \\text{指数组合中的股票 $j$ 和股票 $i$ 具有最高的相似度}\\\\\n",
"0, & \\text{其他情况}\n",
"\\end{cases},\n",
"$$\n",
"\n",
"$$\n",
"y_{j}=\n",
"\\begin{cases}\n",
"1, & \\text{某类中的代表性股票 $j$ 被选择到指数组合中}\\\\\n",
"0, & \\text{其他情况}\n",
"\\end{cases}.\n",
"$$\n",
"\n",
"在该问题中我们的模型便可以写作:\n",
"\n",
"$$\n",
"\\mathcal{M}= \\max_{x_{ij}}\\sum_{i=1}^n\\sum_{j=1}^n \\rho_{ij}x_{ij}. \\tag{1}\n",
"$$\n",
"\n",
"该模型需要满足以下几类约束:\n",
"* 聚类约束:限制指数组合中只能有 $K$ 支股票\n",
" - $ \\sum_{j=1}^n y_j = K$\n",
"* 整数约束:限制一只股票要么是在指数组合中,要么就不在\n",
" - $ x_{ij},y_j\\in{\\{0,1\\}}, \\forall i = 1, \\dots,n; j = 1, \\dots, n$\n",
"* 一致性约束:保证如果一只股票可以代表另一支股票,那么它必须在指数组合中\n",
" - $\\sum_{j=1}^n x_{ij} = 1, \\forall i = 1,\\dots,n$\n",
" - $x_{ij} \\leq y_j, \\forall i = 1,\\dots,n; j = 1,\\dots, n$\n",
" - $x_{jj} = y_j, \\forall j = 1,\\dots,n$\n",
"\n",
"该模型目标就是让可选择的 $n$ 个股票与挑选的指数股票组合间相似性最大化。\n",
"\n",
"由于要对代价函数做梯度下降优化,所以在定义时就根据模型方程和相应的约束条件做一定修改:\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"C_x &= -\\sum_{i=1}^{n}\\sum_{j=1}^{n}\\rho_{ij}x_{ij} + A\\left(K- \\sum_{j=1}^n y_j \\right)^2 + \\sum_{i=1}^n A\\left(\\sum_{j=1}^n 1- x_{ij} \\right)^2 \\\\\n",
"&\\quad + \\sum_{j=1}^n A\\left(x_{jj} - y_j\\right)^2 + \\sum_{i=1}^n \\sum_{j=1}^n A\\left(x_{ij}(1 - y_j)\\right).\\\\ \n",
"\\end{aligned} \\tag{2}\n",
"$$ \n",
"\n",
"该式子中第一项为相似性最大化,后面四项均为约束条件,$A$ 为惩罚参数,通常设置为较大的数字,使得最终表示指数投资组合结果的二进制字符串满足约束条件。\n",
"\n",
"现在我们需要将代价函数转为一个哈密顿量,从而完成投资组合分散化问题的编码。每一个二进制变量可以取0和1两个值,分别对应量子态 $|0\\rangle$ 和 $|1\\rangle$。每个二进制变量都对应一个量子比特,所以我们需要 $n^2 + n$ 个量子比特来解决投资组合分散化问题。因为我们的变量 $x_{ij}$ 的值为 $0$ 和 $1$,所以我们要构造一个本征值和它对应的哈密顿量。泡利 $Z$ 的本征值为 $\\pm 1$,于是我们构造的哈密顿量为 $\\frac{I-Z}{2}$,对应的本征值即为 $0$ 和 $1$。\n",
"\n",
"我们现在将二进制变量映射到泡利 $Z$ 矩阵上,从而使 $C_x$ 转化成哈密顿矩阵:\n",
"\n",
"$$\n",
"x_{ij} \\mapsto \\frac{I-Z_{ij}}{2}, \\tag{3}\n",
"$$\n",
"\n",
"这里 $Z_{ij} = I \\otimes I \\otimes \\ldots \\otimes Z \\otimes \\ldots \\otimes I$,也就是说 $Z$ 作用在 $ij$ 的量子比特上。通过这个映射,如果一个编号为 $ij$ 的量子比特的量子态为 $|1\\rangle$,那么对应的二进制变量的取值为 $x_{ij} |1\\rangle = \\frac{I-Z_{ij}}{2} |1\\rangle = 1|1\\rangle $,也就是说该项目是我们要投资的。同样地,对于量子态为 $|0\\rangle$的量子比特 $i$,它所对应的二进制变量的取值为 $x_{ij}|0\\rangle = \\frac{I-Z_{ij}}{2} |0\\rangle = 0 |0\\rangle $。\n",
"\n",
"我们用上述映射将 $C_x$ 转化成量子比特数为 $n^2+n$ 的系统的哈密顿矩阵 $H_C$(其中 $x_{ij}$ 占 $n^2$ 个qubit,$y_j$ 占 $n$ 个 qubit),从而实现了投资组合分散化问题的量子化。这个哈密顿矩阵 $H_C$ 的基态即为投资组合分散化问题的最优解。在接下来的部分,我们将展示如何用参数化量子电路找到这个矩阵的基态,也就是对应最小本征值的本征态。"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## Paddle Quantum 实现\n",
"\n",
"要在量桨上实现用参数化量子电路解决量子金融中的投资组合分散化问题,首先要做的便是加载需要用到的包。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 1,
"source": [
"#加载额外需要的包\n",
"import numpy as np\n",
"import pandas as pd\n",
"import datetime\n",
"\n",
"#加载飞桨,量桨相关的模块\n",
"import paddle\n",
"from paddle_quantum.circuit import UAnsatz\n",
"from paddle_quantum.finance import DataSimulator\n",
"from paddle_quantum.finance import portfolio_diversification_hamiltonian"
],
"outputs": [],
"metadata": {
"ExecuteTime": {
"end_time": "2021-05-17T08:00:15.901429Z",
"start_time": "2021-05-17T08:00:12.708945Z"
}
}
},
{
"cell_type": "markdown",
"source": [
"### 准备实验数据\n",
"\n",
"和投资组合优化问题相似,在本问题中,我们选定的投资项目类型为股票。对于实验测试要用的数据,提供了两种方法:\n",
"* 第一种是根据一定的条件,随机生成数据。\n",
"\n",
"如果采用这种方法准备实验数据,用户在初始化数据时,就需要给出可投资股票的名字列表,交易数据的开始日期和结束日期。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 2,
"source": [
"num_assets = 3 #可选择股票数目\n",
"stocks = [(\"TICKER%s\" % i) for i in range(num_assets)]\n",
"data = DataSimulator( stocks = stocks, start = datetime.datetime(2016, 1, 1), end = datetime.datetime(2016, 1, 30)) # 根据指定的条件,随机生成测试所需的数据"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"* 第二种是用户可以选择读取本地收集到的真实数据集用于实验。考虑到文件中包含的股票数可能会很多,用户可以指定用于该实验的股票数量,即 上面初始化的`num_assets`。\n",
"\n",
"我们收集了 $12$ 支股票 $35$ 个交易日的收盘价格存放到 `realStockData_12.csv` 文件中,在这里我们只选择读取前 $3$ 个股票的信息。\n",
"\n",
"在本教程中,我们选择读取的真实数据作为实验数据。"
],
"metadata": {
"ExecuteTime": {
"end_time": "2021-05-17T08:00:16.212260Z",
"start_time": "2021-05-17T08:00:15.918792Z"
}
}
},
{
"cell_type": "code",
"execution_count": 3,
"source": [
"df = pd.read_csv('realStockData_12.csv')\n",
"dt = []\n",
"for i in range(num_assets):\n",
" mylist = df['closePrice'+str(i)].tolist()\n",
" dt.append(mylist)\n",
"print(dt) # 输出从文件中读取的3个股票在35个交易日中的收盘价格\n",
"\n",
"data.set_data(dt) # 指定实验数据为用户读取的数据"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"[[16.87, 17.18, 17.07, 17.15, 16.66, 16.79, 16.69, 16.99, 16.76, 16.52, 16.33, 16.39, 16.45, 16.0, 16.09, 15.54, 13.99, 14.6, 14.63, 14.77, 14.62, 14.5, 14.79, 14.77, 14.65, 15.03, 15.37, 15.2, 15.24, 15.59, 15.58, 15.23, 15.04, 14.99, 15.11, 14.5], [32.56, 32.05, 31.51, 31.76, 31.68, 32.2, 31.46, 31.68, 31.39, 30.49, 30.53, 30.46, 29.87, 29.21, 30.11, 28.98, 26.63, 27.62, 27.64, 27.9, 27.5, 28.67, 29.08, 29.08, 29.95, 30.8, 30.42, 29.7, 29.65, 29.85, 29.25, 28.9, 29.33, 30.11, 29.67, 29.59], [5.4, 5.48, 5.46, 5.49, 5.39, 5.47, 5.46, 5.53, 5.5, 5.47, 5.39, 5.35, 5.37, 5.24, 5.26, 5.08, 4.57, 4.44, 4.5, 4.56, 4.52, 4.59, 4.66, 4.67, 4.66, 4.72, 4.84, 4.81, 4.84, 4.88, 4.89, 4.82, 4.74, 4.84, 4.79, 4.63]]\n"
]
}
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### 编码哈密顿量\n",
"\n",
"这里我们将式(2)中的二进制变量用式(3)替换,从而构建哈密顿量 $H_C$。"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"在编码哈密顿量的过程中,首先需要计算各股票之间的相似矩阵 $\\rho$。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 4,
"source": [
"rho = data.get_similarity_matrix()"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"根据计算的相似矩阵和给定的参数构建哈密顿量。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 5,
"source": [
"q = 2 # 指数组合中需要的股票数目\n",
"penalty = num_assets # 惩罚参数:不小于可投资股票的数目\n",
"hamiltonian = portfolio_diversification_hamiltonian(penalty, rho, q)"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### 计算损失函数\n",
"\n",
"调用量桨内置的 [`complex entangled layer`](https://qml.baidu.com/api/paddle_quantum.circuit.uansatz.html) 构造参数化量子电路。该电路会返回一个输出态 $|\\vec{\\theta}\\rangle$,由此输出态,我们可以定义投资组合分散化问题在经典-量子混合模型下的损失函数:\n",
"\n",
"$$\n",
"L(\\vec{\\theta}) = \\langle\\vec{\\theta}|H_C|\\vec{\\theta}\\rangle.\n",
"\\tag{4}\n",
"$$\n",
"\n",
"之后我们利用经典的优化算法寻找最优参数 $\\vec{\\theta}^*$。下面的代码给出了通过量桨和飞桨搭建网络的过程。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 6,
"source": [
"class PDNet(paddle.nn.Layer):\n",
"\n",
" def __init__(self, n, p, dtype=\"float64\"):\n",
" super(PDNet, self).__init__()\n",
"\n",
" self.p = p\n",
" self.num_qubits = n * (n+1)\n",
" self.theta = self.create_parameter(shape=[self.p, self.num_qubits, 3],\n",
" default_initializer=paddle.nn.initializer.Uniform(low=0, high=2 * np.pi),\n",
" dtype=dtype, is_bias=False)\n",
" # print(self.theta)\n",
"\n",
" def forward(self, hamiltonian):\n",
" \"\"\"\n",
" 前向传播\n",
" \"\"\"\n",
" cir = UAnsatz(self.num_qubits)\n",
" cir.complex_entangled_layer(self.theta, self.p)\n",
" cir.run_state_vector()\n",
" loss = cir.expecval(hamiltonian)\n",
"\n",
" return loss, cir"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### 训练量子神经网络\n",
"\n",
"定义好了量子神经网络后,我们使用梯度下降的方法来更新其中的参数,使得式(4)的期望值最小。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 7,
"source": [
"SEED = 1100 # 随机数种子\n",
"p = 2 # 量子电路的层数\n",
"ITR = 150 # 迭代次数\n",
"LR = 0.4 # 梯度下降优化速率 "
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"使用飞桨,优化上面定义的网络。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 8,
"source": [
"# 比特数量\n",
"n = len(rho)\n",
"\n",
"# 固定随机数种子\n",
"paddle.seed(SEED)\n",
"\n",
"# 定义量子神经网络\n",
"net = PDNet(n, p)\n",
"\n",
"# 使用 Adam 优化器\n",
"opt = paddle.optimizer.Adam(learning_rate=LR, parameters=net.parameters())\n",
"\n",
"# 梯度下降优化循环\n",
"for itr in range(1, ITR + 1):\n",
" loss, cir = net(hamiltonian)\n",
" loss.backward()\n",
" opt.minimize(loss)\n",
" opt.clear_grad()\n",
" if itr % 10 == 0:\n",
" print(\"循环数:\", itr, \" 损失:\", \"%.4f\"% loss.numpy())"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"循环数: 10 损失: 7.7804\n",
"循环数: 20 损失: 5.4414\n",
"循环数: 30 损失: 3.6022\n",
"循环数: 40 损失: 3.2910\n",
"循环数: 50 损失: 1.9358\n",
"循环数: 60 损失: 0.3872\n",
"循环数: 70 损失: 0.1344\n",
"循环数: 80 损失: 0.0774\n",
"循环数: 90 损失: 0.0122\n",
"循环数: 100 损失: 0.0068\n",
"循环数: 110 损失: -0.0001\n",
"循环数: 120 损失: -0.0019\n",
"循环数: 130 损失: -0.0025\n",
"循环数: 140 损失: -0.0028\n",
"循环数: 150 损失: -0.0028\n"
]
}
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### 解码量子答案\n",
"\n",
"当调用优化器求得损失函数的最小值以及相对应的一组参数 $\\vec{\\theta}^*$后,为了进一步求得投资组合优化问题的近似解,需要从电路输出的量子态 $|\\vec{\\theta}^*\\rangle$ 中解码出经典优化问题的答案。物理上,解码量子态需要对量子态进行测量,然后统计测量结果的概率分布:\n",
"\n",
"$$\n",
"p(z) = |\\langle z|\\vec{\\theta}^*\\rangle|^2.\n",
"\\tag{6}\n",
"$$\n",
"\n",
"在量子参数化电路表达能力足够的情况下,某个比特串出现的概率越大,意味着其是投资组合优化问题最优解的可能性越大。\n",
"\n",
"量桨提供了查看参数化量子电路输出状态的测量结果概率分布的函数。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 14,
"source": [
"# 模拟重复测量电路输出态 2048 次\n",
"prob_measure = cir.measure(shots=2048)\n",
"investment = max(prob_measure, key=prob_measure.get)\n",
"print(\"利用哈密顿量找到的解的比特串形式:\", investment)"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"利用哈密顿量找到的解的比特串形式: 100001001101\n"
]
}
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"我们的测量结果是表示投资组合分散化问题答案的比特串:如上文结果 ``100001001101``,我们一共有 $n = 3$ 支可投资股票,选择两只到指数组合中。前 $n^2 = 9$ 位 ``100001001`` 代表 $x_{ij}$,每 $3$ 位为一组,第一组 ``100`` 中第一位为 $1$,代表它被划作一类。第二组 ``001`` 和第三组 ``001`` 中第三位被置为 $1$,代表它们被划为一类。同时,第一组和第三组 $1$ 出现的位置符合 $i = j$,即这两支股票为最能代表各自类的股票。另外,可以看出 $1$ 出现的位置是 $j = 1$ 和 $j = 3$,即两个位置可能为 $1$,这和我们预设的指数组合中有两只股票是对应的。同时,后 $3$ 位为 ``101``,代表 $y_j$, 表示第一支股票和第三支股票被选中放入指数组合中。通过上述说明,可以看出我们求解得到的结果是一个有效解。如果最后的结果不是上述这种有效解,读者依然可以通过调整参数化量子电路的参数值,来获得更好的训练效果。"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### 结语\n",
"\n",
"在本教程中,我们主要讨论了分散化投资中如何对可投资项目进行分类,以及如何挑选具有代表性的到我们的投资组合中来。在本问题中,每个投资项目都需要 $n$ 位量子比特来表示分类,$1$ 位量子比特表示是否被选中。受量子比特数目的限制,目前能够处理的投资项目数还比较少。"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"_______\n",
"\n",
"## 参考文献\n",
"\n",
"[1] Orus, Roman, Samuel Mugel, and Enrique Lizaso. \"Quantum computing for finance: Overview and prospects.\" [Reviews in Physics 4 (2019): 100028.](https://arxiv.org/abs/1807.03890)\n",
"\n",
"[2] Egger, Daniel J., et al. \"Quantum computing for Finance: state of the art and future prospects.\" [IEEE Transactions on Quantum Engineering (2020).](https://arxiv.org/abs/2006.14510)"
],
"metadata": {}
}
],
"metadata": {
"interpreter": {
"hash": "3b61f83e8397e1c9fcea57a3d9915794102e67724879b24295f8014f41a14d85"
},
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.10"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}
\ No newline at end of file
{
"cells": [
{
"cell_type": "markdown",
"source": [
"# Quantum Finance Application: Portfolio Diversification\n",
"\n",
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## Overview\n",
"\n",
"Current finance problem can be mainly tackled by three areas of quantum algorithms: quantum simulation, quantum optimization, and quantum machine learning [1,2]. Many financial problems are essentially a combinatorial optimization problem, and corresponding algorithms usually have high time complexity and are difficult to implement. Due to the power of quantum computing, these complex problems are expected to be solved by quantum algorithms in the future.\n",
"\n",
"The Quantum Finance module of Paddle Quantum focuses on quantum optimization: how to apply quantum algorithms in real finance optimization problems. This tutorial focuses on how to use quantum algorithms to solve the portfolio diversification problem."
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## Portfolio Diversification Problem\n",
"\n",
"Limited by the lack of professional knowledge and market experience, the investor prefers a passive investment strategy in the actual investment. Index investing is a typical example of passive investing, e.g. an investor buys and holds the Standard & Poor’s 500 (S&P 500) for a long period of time. As an investor, if you do not want to invest an existing index portfolio, you can also create your own specific index portfolio by picking representative stocks from the market.\n",
"\n",
"An important way to balance risk and return in an investment portfolio is to diversify your assets. A specific description of portfolio diversification is as follows: the number of investable stocks is $n$ and the number of stocks included in the portfolio is $K$. Based on some criteria, you need to divide all the stocks into $K$ categories and select the stock from each category that best represent that category. Adding representatives of each category to the index portfolio is better for investment management."
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Encoding Portfolio Diversification Problem\n",
"\n",
"To transform the portfolio diversification problem into a problem applicable for parameterized quantum circuits, we need to encode portfolio diversification problem into a Hamiltonian.\n",
"\n",
"To model the problem, two issues need to be clarified. The first one is how to classify different stocks, and the second one is what criteria are used to select representative stocks. To solve these two problems, firstly we define the similarity $\\rho_{ij}$ between stock $i$ and stock $j$:\n",
"* $\\rho_{ii} = 1 \\quad $ The stock is similar to itself with a similarity of 1 \n",
"* $\\rho_{ij} \\leq 1 \\quad$ The larger $\\rho_{ij}$ , the higher the similarity between stock $i$ and stock $j$\n",
"\n",
"Due to the correlation of returns between stocks, we can further measure the similarity between the time series on the basis of the covariance matrix. Dynamic Time Warping (DTW) is a common method to measure the similarity of two time series. In this paper, the DTW algorithm is used to calculate the similarity between two stocks. So based on the similarity between different stocks, we can classify the stocks and select representative stock in each category. We can define $n$ binary variables $x_{ij}$ and $1$ binary variables $y_j$ for each stock. Therefore, given $n$ stocks, there are $n^2 + n$ binary variables. For the variable $x_{ij}$, $i$ denotes the order of stock, and $j$ denotes the position among the $n$ binary variables corresponding to that stock. If two stock has same index of $j$, they are classified in the same category. Meanwhile, the stock of $i = j$ is the most representative one in that category selected to the index portfolio:\n",
"\n",
"$$\n",
"x_{ij}=\n",
"\\begin{cases}\n",
"1, & \\text{stock $j$ is in the portfolio and it has the highest similarity to stock $i$}\\\\\n",
"0, & \\text{otherwise}\n",
"\\end{cases},\n",
"$$\n",
"\n",
"$$\n",
"y_{j}=\n",
"\\begin{cases}\n",
"1, & \\text{stock $j$ is selected to the index portfolio}\\\\\n",
"0, & \\text{otherwise}\n",
"\\end{cases}.\n",
"$$\n",
"\n",
"The model can be written as follows:\n",
"\n",
"$$\n",
"\\mathcal{M}= \\max_{x_{ij}}\\sum_{i=1}^n\\sum_{j=1}^n \\rho_{ij}x_{ij}. \\tag{1}\n",
"$$\n",
"\n",
"The model needs to satisfy the following constraints:\n",
"* Clustering constraint: the index portfolio only include $K$ stocks\n",
" - $ \\sum_{j=1}^n y_j = K$\n",
"* Integer constraint: a stock is either in the index portfolio or not\n",
" - $ x_{ij},y_j\\in{\\{0,1\\}}, \\forall i = 1, \\dots,n; j = 1, \\dots, n$\n",
"* Consistency constraint: if a stock can represent another stock, it must be in the index portfolio\n",
" - $\\sum_{j=1}^n x_{ij} = 1, \\forall i = 1,\\dots,n$\n",
" - $x_{ij} \\leq y_j, \\forall i = 1,\\dots,n; j = 1,\\dots, n$\n",
" - $x_{jj} = y_j, \\forall j = 1,\\dots,n$\n",
"\n",
"The objective of the model is to maximize the similarity between the $n$ stocks and the selected index stock portfolio.\n",
"\n",
"Since the loss function is to be optimized using the gradient descent method, some modifications are made in the loss function based on the the model equation and constrains:\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"C_x &= -\\sum_{i=1}^{n}\\sum_{j=1}^{n}\\rho_{ij}x_{ij} + A\\left(K- \\sum_{j=1}^n y_j \\right)^2 + \\sum_{i=1}^n A\\left(\\sum_{j=1}^n 1- x_{ij} \\right)^2 \\\\\n",
"&\\quad + \\sum_{j=1}^n A\\left(x_{jj} - y_j\\right)^2 + \\sum_{i=1}^n \\sum_{j=1}^n A\\left(x_{ij}(1 - y_j)\\right).\\\\ \n",
"\\end{aligned} \\tag{2}\n",
"$$ \n",
"\n",
"The first term represents the similarity maximization, the next four terms are constraints. $A$ is the penalty parameter, which is usually set to a larger number so that the final binary string representing the index portfolio results satisfies the constraints.\n",
"\n",
"We now need to transform the cost function $C_x$ into a Hamiltonian to realize the encoding of the portfolio diversification problem. Each variable $x_{ij}$ has two possible values, $0$ and $1$, corresponding to quantum states $|0\\rangle$ and $|1\\rangle$. Note that every variable corresponds to a qubit and so $n^2 + n$ qubits are needed for solving the portfolio diversification problem. The Pauli $Z$ operator has two eigenstates which are the same as the states $|0\\rangle$ and $|1\\rangle$ . Their corresponding eigenvalues are 1 and -1, respectively. So we consider encoding the cost function as a Hamiltonian using the pauli $Z$ matrix.\n",
"\n",
"Now we would like to consider the mapping\n",
"\n",
"$$\n",
"x_{ij} \\mapsto \\frac{I-Z_{ij}}{2}, \\tag{3}\n",
"$$\n",
"\n",
"where $Z_{ij} = I \\otimes I \\otimes \\ldots \\otimes Z \\otimes \\ldots \\otimes I$ with $Z$ operates on the qubit at position $ij$. Under this mapping, the value of $x_{ij}$ represent different meanings. If the qubit $ij$ is in state $|1\\rangle$, then $x_{ij} |1\\rangle = \\frac{I-Z_{ij}}{2} |1\\rangle = 1|1\\rangle $, which means stock $i$ is in index portfolio. Also, for the qubit $ij$ in state $|0\\rangle$, $x_{ij}|0\\rangle = \\frac{I-Z_{ij}}{2} |0\\rangle = 0 |0\\rangle $.\n",
"\n",
"Thus using the above mapping, we can transform the cost function $C_x$ into a Hamiltonian $H_C$ for the system of $n^2+n$ qubits and realize the quantumization of the portfolio diversification problem. Then the ground state of $H_C$ is the optimal solution to the portfolio diversification problem. In the following section, we will show how to use a parametrized quantum circuit to find the ground state, i.e., the eigenvector with the smallest eigenvalue."
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## Paddle Quantum Implementation\n",
"\n",
"To investigate the portfolio diversification problem using Paddle Quantum, there are some required packages to import, which are shown below."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 2,
"source": [
"# Import packages needed\n",
"import numpy as np\n",
"import pandas as pd\n",
"import datetime\n",
"\n",
"# Import related modules from Paddle Quantum and PaddlePaddle\n",
"import paddle\n",
"from paddle_quantum.circuit import UAnsatz\n",
"from paddle_quantum.finance import DataSimulator\n",
"from paddle_quantum.finance import portfolio_diversification_hamiltonian"
],
"outputs": [],
"metadata": {
"ExecuteTime": {
"end_time": "2021-05-17T08:00:15.901429Z",
"start_time": "2021-05-17T08:00:12.708945Z"
}
}
},
{
"cell_type": "markdown",
"source": [
"### Prepare experimental data\n",
"\n",
"In this tutorial, we choose stocks as the investment assets. For the data used in the experimental tests, two options are provided:\n",
"* The first method is to generate random data according to certain requirements, e.g. number of assets.\n",
"\n",
"If the user prepares data using this method, then when initializing the data, it is necessary to give the list of parameters: a list of names of investable stocks (assets), the start date and end date of the trading data."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 3,
"source": [
"num_assets = 3 # Number of investable projects\n",
"stocks = [(\"TICKER%s\" % i) for i in range(num_assets)]\n",
"data = DataSimulator( stocks = stocks, start = datetime.datetime(2016, 1, 1),\n",
" end = datetime.datetime(2016, 1, 30)) "
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"* The second method is that the user can choose to set the data themselves, i.e. real stock data collected by themselves. Considering that the number of stocks contained in the file may be large, the user can specify the number of stocks used for this experiment, i.e. `num_assets` as initialized above.\n",
"\n",
"We collect the closing prices of $12$ stocks for $35$ trading days into the `realStockData_12.csv` file, where we choose to read only the first $3$ stocks.\n",
"\n",
"In this tutorial, we choose to read real data as experimental data."
],
"metadata": {
"ExecuteTime": {
"end_time": "2021-05-17T08:00:16.212260Z",
"start_time": "2021-05-17T08:00:15.918792Z"
}
}
},
{
"cell_type": "code",
"execution_count": 4,
"source": [
"df = pd.read_csv('realStockData_12.csv') \n",
"dt = []\n",
"for i in range(num_assets):\n",
" mylist = df['closePrice'+str(i)].tolist()\n",
" dt.append(mylist)\n",
"# Output the closing price of the seven stocks read from the file for the 35 trading days\n",
"print(dt) \n",
"# Specify the experimental data as a local file read by the user\n",
"data.set_data(dt) "
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"[[16.87, 17.18, 17.07, 17.15, 16.66, 16.79, 16.69, 16.99, 16.76, 16.52, 16.33, 16.39, 16.45, 16.0, 16.09, 15.54, 13.99, 14.6, 14.63, 14.77, 14.62, 14.5, 14.79, 14.77, 14.65, 15.03, 15.37, 15.2, 15.24, 15.59, 15.58, 15.23, 15.04, 14.99, 15.11, 14.5], [32.56, 32.05, 31.51, 31.76, 31.68, 32.2, 31.46, 31.68, 31.39, 30.49, 30.53, 30.46, 29.87, 29.21, 30.11, 28.98, 26.63, 27.62, 27.64, 27.9, 27.5, 28.67, 29.08, 29.08, 29.95, 30.8, 30.42, 29.7, 29.65, 29.85, 29.25, 28.9, 29.33, 30.11, 29.67, 29.59], [5.4, 5.48, 5.46, 5.49, 5.39, 5.47, 5.46, 5.53, 5.5, 5.47, 5.39, 5.35, 5.37, 5.24, 5.26, 5.08, 4.57, 4.44, 4.5, 4.56, 4.52, 4.59, 4.66, 4.67, 4.66, 4.72, 4.84, 4.81, 4.84, 4.88, 4.89, 4.82, 4.74, 4.84, 4.79, 4.63]]\n"
]
}
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Encoding Hamiltonian\n",
"\n",
"Here we construct the Hamiltonian $H_C$ of Eq. (2) with the replacement in Eq. (3). "
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"In the process of encoding Hamiltonian, we first need to calculate the similarity matrix $\\rho$ between the returns of each stock, which is available in the ``finance`` module and can be called directly. "
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 5,
"source": [
"rho = data.get_similarity_matrix()"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"Based on the provided and calculated parameters, the Hamiltonian is constructed below. Here we set the penalty parameter to the number of investable stocks.\n"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 6,
"source": [
"q = 2 # Number of stocks in the index portfolio\n",
"penalty = num_assets # penalty parameter \n",
"hamiltonian = portfolio_diversification_hamiltonian(penalty, rho, q)"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Calculating the loss function \n",
"\n",
"We adopt a parameterized quantum circuit consisting of $U_3(\\vec{\\theta})$ and $\\text{CNOT}$ gates, that can be constructed by calling the built-in function [`complex entangled layer`](https://qml.baidu.com/api/paddle_quantum.circuit.uansatz.html).\n",
"\n",
"After running the quantum circuit, we obtain the circuit output $|\\vec{\\theta\n",
"}\\rangle$. From the output state of the circuit we can calculate the objective function, and also the loss function of the portfolio diversification problem:\n",
"\n",
"$$\n",
"L(\\vec{\\theta}) = \\langle\\vec{\\theta}|H_C|\\vec{\\theta}\\rangle.\n",
"\\tag{4}\n",
"$$\n",
"\n",
"We then use a classical optimization algorithm to minimize this function and find the optimal parameters $\\vec{\\theta}^*$. The following code shows a complete network built with Paddle Quantum and PaddlePaddle."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 7,
"source": [
"class PDNet(paddle.nn.Layer):\n",
"\n",
" def __init__(self, n, p, dtype=\"float64\"):\n",
" super(PDNet, self).__init__()\n",
"\n",
" self.p = p\n",
" self.num_qubits = n * (n+1)\n",
" self.theta = self.create_parameter(shape=[self.p, self.num_qubits, 3],\n",
" default_initializer=paddle.nn.initializer.Uniform(low=0, high=2 * np.pi),\n",
" dtype=dtype, is_bias=False)\n",
" # print(self.theta)\n",
"\n",
" def forward(self, hamiltonian):\n",
" \"\"\"\n",
" Forward propagation\n",
" \"\"\"\n",
" cir = UAnsatz(self.num_qubits)\n",
" cir.complex_entangled_layer(self.theta, self.p)\n",
" cir.run_state_vector()\n",
" loss = cir.expecval(hamiltonian)\n",
"\n",
" return loss, cir"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Training the quantum neural network\n",
"\n",
"After defining the quantum neural network, we use the gradient descent method to update the parameters to minimize the expectation value in Eq. (4). "
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 8,
"source": [
"SEED = 1100 # Set a global RNG seed \n",
"p = 2 # Number of layers in the quantum circuit\n",
"ITR = 150 # Number of training iterations\n",
"LR = 0.4 # Learning rate of the optimization method based on gradient descent"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"Here, we optimize the network defined above in PaddlePaddle."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 9,
"source": [
"# number of qubits\n",
"n = len(rho)\n",
"\n",
"# Fix paddle random seed\n",
"paddle.seed(SEED)\n",
"\n",
"# Building Quantum Neural Networks\n",
"net = PDNet(n, p)\n",
"\n",
"# Use Adam optimizer\n",
"opt = paddle.optimizer.Adam(learning_rate=LR, parameters=net.parameters())\n",
"\n",
"# Gradient descent iteration\n",
"for itr in range(1, ITR + 1):\n",
" loss, cir = net(hamiltonian)\n",
" loss.backward()\n",
" opt.minimize(loss)\n",
" opt.clear_grad()\n",
" if itr % 10 == 0:\n",
" print(\"iter:\", itr, \" loss:\", \"%.4f\"% loss.numpy())\n"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"iter: 10 loss: 7.7804\n",
"iter: 20 loss: 5.4414\n",
"iter: 30 loss: 3.6022\n",
"iter: 40 loss: 3.2910\n",
"iter: 50 loss: 1.9358\n",
"iter: 60 loss: 0.3872\n",
"iter: 70 loss: 0.1344\n",
"iter: 80 loss: 0.0774\n",
"iter: 90 loss: 0.0122\n",
"iter: 100 loss: 0.0068\n",
"iter: 110 loss: -0.0001\n",
"iter: 120 loss: -0.0019\n",
"iter: 130 loss: -0.0025\n",
"iter: 140 loss: -0.0028\n",
"iter: 150 loss: -0.0028\n"
]
}
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Decoding the quantum solution\n",
"\n",
"After obtaining the minimum value of the loss function and the corresponding set of parameters $\\vec{\\theta}^*$, our task has not been completed. In order to obtain an approximate solution to the portfolio diversification problem, it is necessary to decode the solution to the classical optimization problem from the quantum state $|\\vec{\\theta}^*\\rangle$ output by the circuit. Physically, to decode a quantum state, we need to measure it and then calculate the probability distribution of the measurement results:\n",
"\n",
"$$\n",
"p(z) = |\\langle z|\\vec{\\theta}^*\\rangle|^2.\n",
"\\tag{5}\n",
"$$\n",
"\n",
"In the case of quantum parameterized circuits with sufficient expressiveness, the greater the probability of a certain bit string, the greater the probability that it corresponds to an optimal solution of the portfolio diversification problem.\n",
"\n",
"Paddle Quantum provides a function to read the probability distribution of the measurement results of the state output by the quantum circuit:"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 11,
"source": [
"# Repeat the simulated measurement of the circuit output state 1024 times\n",
"\n",
"prob_measure = cir.measure(shots=2048)\n",
"investment = max(prob_measure, key=prob_measure.get)\n",
"print(\"The bit string form of the solution: \", investment)"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"The bit string form of the solution: 100001001101\n"
]
}
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"After measurement, we have found the bit string with the highest probability of occurrence, the index portfolio in the form of the bit string. As the result above ``100001001101``, we have $n = 3$ investable stocks and choose two to the index portfolio。The first $n^2 = 9$ bits of ``100001001`` represent $x_{ij}$, and every $3$ bits are grouped together. The first bit of the first group of ``100`` is set to $1$, which means it is classified as a class. The third bit in the second group ``001`` and the third group ``001`` is set to $1$, which means they are classified as one class. Also, the positions of $1$ in the first and third groups are satisfied with $i = j$, i.e., these two stocks are the most representative stock of their respective classes. It can be seen that $1$ appears at $j = 1$ and $j = 3$, i.e., two positions are possible for $1$, which corresponds to our presumption of having two stocks in the index portfolio. \n",
"The last $3$ position is ``101``, which represents $y_j$, indicating that the first stock and the third stock are selected to the index portfolio. If the final result is not such a valid solution as described above, users can still get a better training result by adjusting the parameter values of the parameterized quantum circuit."
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Conclusion\n",
"\n",
"In this tutorial, we focus on how to classify investable stocks and how to select representative ones to our portfolio. In this problem, each investment item requires $n$ qubits to represent the classification and $1$ qubit to represent whether it is selected to portfolio or not. Due to the limitation of the number of qubits, the number of investment items that can be handled is still small."
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"_______\n",
"\n",
"## References\n",
"\n",
"[1] Orus, Roman, Samuel Mugel, and Enrique Lizaso. \"Quantum computing for finance: Overview and prospects.\" [Reviews in Physics 4 (2019): 100028.](https://arxiv.org/abs/1807.03890)\n",
"\n",
"[2] Egger, Daniel J., et al. \"Quantum computing for Finance: state of the art and future prospects.\" [IEEE Transactions on Quantum Engineering (2020).](https://arxiv.org/abs/2006.14510)"
],
"metadata": {}
}
],
"metadata": {
"interpreter": {
"hash": "3b61f83e8397e1c9fcea57a3d9915794102e67724879b24295f8014f41a14d85"
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3.7.11 64-bit ('pq_env': conda)"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.11"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}
\ No newline at end of file
{
"cells": [
{
"cell_type": "markdown",
"source": [
"# 量子金融应用:投资组合优化\n",
"\n",
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"\n",
"## 概览\n",
"\n",
"当前量子计算应用到金融问题上的解决方案通常可分为三类量子算法,即量子模拟,量子优化以及量子机器学习 [1,2]。许多的金融问题本质上是一个组合优化问题,解决这些问题的算法通常具有较高的时间复杂度,实现难度较大。得益于量子计算强大的计算性能,未来有望通过量子算法解决这些复杂问题。\n",
"\n",
"量桨的 Quantum Finance 模块主要讨论的是量子优化部分的内容,即如何通过一些量子算法解决实际金融应用中的优化问题。本文主要介绍如何使用量子算法求解主动投资管理中投资组合优化问题。"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## 投资组合优化问题\n",
"\n",
"投资组合是金融投资的集合,比如股票、债权、现金等。投资组合优化是许多主动型投资管理者需要面对的问题,它需要从业者应用相关数学理论方法,根据目标收益和风险对一笔资金进行投资,以期在收益一定的情况下风险最小化或者是风险一定的情况下投资收益最大化。\n",
"\n",
"对投资组合优化的一个具体描述如下:假如你是一位资产管理人,想要将数额为 $K$ 的资金一次性投入到 $N$ 个可投资的项目中,各项目都有自己的投资回报率和风险,你的目标就是在考虑到市场影响和交易费用的的基础上找到一个最佳的投资组合空间,使得该笔资产以最优的投资方案实施。"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### 编码投资组合优化问题\n",
"\n",
"为了将投资组合优化问题转化成一个参数化量子电路(parameterized quantum circuits, PQC)可解的问题,我们首先需要编码该问题的哈密顿量。\n",
"为了方便建模实现,需要做两点假设对问题加以限制:\n",
"* 每个项目都是等额投资的,\n",
"* 给定的预算是投资一个项目金额的整数倍,且必须全部花完。\n",
"\n",
"在该计算模型中我们把对项目的投资金额单位化,即如果投资预算为 $3$,那么就要投 $3$ 个项目。因为实际的投资中,预算是有限的,而可投资的项目是很多的,所以在设定参数时也要注意可投资项目数是要大于预算的。\n",
"\n",
"在投资组合的基本理论中,投资组合的总体风险与项目间的协方差有关,而协方差与任意两项目的相关系数成正比。相关系数越小,其协方差就越小,投资组合的总体风险也就越小 [3]。\n",
"在这里我们采用均值方差组合优化的方法,给出该问题的建模方程:\n",
"\n",
"$$\n",
"\\omega = \\max _{x \\in\\{0,1\\}^{n}} \\mu^{T} x - q x^{T} S x \\quad\\quad \\tag{1}\n",
"\\text { subject to: } \\mathbb{1}^{T} x=B,\n",
"$$\n",
"\n",
"该式子中各符号代表的含义如下:\n",
"* $x\\in {\\{0,1\\}}^n$ 表示一个向量,其中每一个元素均为二进制变量,即如果资产 $i$ 被投资了,则 $x_i = 1$,如果没有被选择,则 $x_i = 0$ \n",
"* $\\mu \\in \\mathbb{R}^n$ 表示投资每个项目的预期回报率\n",
"* $S \\in \\mathbb{R}^{n \\times n}$ 表示各投资项目回报率之间的协方差矩阵\n",
"* $q > 0$ 表示做出该投资决定的风险系数\n",
"* $\\mathbb{1}$ 表示 $n$ 维值全为 $1$ 向量\n",
"* $B$ 代表投资预算,即我们可以投资的项目数\n",
"\n",
"\n",
"根据模型方程,可以给出损失函数:\n",
"\n",
"$$\n",
"C_x = q \\sum_i \\sum_j S_{ji}x_ix_j - \\sum_{i}x_i \\mu_i + A \\left(B - \\sum_i x_i\\right)^2, \\tag{2}\n",
"$$\n",
"\n",
"其中,$S_{ij}$ 表示协方差矩阵 $S$ 的内部元素。\n",
"\n",
"由于要对损失函数做梯度下降优化,所以在定义时就根据模型的方程做了一定修改:其中第一项为风险项,表示该笔投资的风险;第二项为投资收益项;$A$ 为惩罚参数,通常设置为一个较大的数字,该项限定一笔资金预算 $B$ 必须均匀的投入到不同的投资项目中。\n",
"\n",
"现在我们需要将损失函数转为一个哈密顿量,从而完成投资组合优化问题的编码。每一个二进制变量可以取 $0$ 和 $1$ 两个值,分别对应量子态 $|0\\rangle$ 和 $|1\\rangle$。每个二进制变量都对应一个量子比特,所以我们需要 $n$ 个量子比特来解决投资组合优化问题。\n",
"因为我们的变量 $x_i$ 的值为 $0$ 和 $1$,所以我们要构造一个本征值和它对应的哈密顿量。泡利 $Z$ 的本征值为 $\\pm 1$,于是我们构造的哈密顿量为 $\\frac{I-Z}{2}$, 对应的本征值即为 $0$ 和 $1$。我们现在将二进制变量映射到泡利 $Z$ 矩阵上,从而使 $C_x$ 转化成哈密顿矩阵:\n",
"\n",
"$$\n",
"x_{i} \\mapsto \\frac{I-Z_{i,}}{2}, \\tag{3}\n",
"$$\n",
"\n",
"这里 $Z_{i} = I \\otimes I \\otimes \\ldots \\otimes Z \\otimes \\ldots \\otimes I$,也就是说 $Z$ 作用在第 $i$ 个量子比特上。通过这个映射,如果一个编号为 $i$ 的量子比特的量子态为 $|1\\rangle$,那么对应的二进制变量的取值为 $x_{i} |1\\rangle = \\frac{I-Z_{i}}{2} |1\\rangle = 1|1\\rangle $,也就是说该项目是我们要投资的。同样地,对于量子态为 $|0\\rangle$的量子比特 $i$,它所对应的二进制变量的取值为 $x_{i}|0\\rangle = \\frac{I-Z_{i}}{2} |0\\rangle = 0 |0\\rangle $。\n",
"\n",
"我们用上述映射将 $C_x$ 转化成量子比特数为 $n$ 的系统的哈密顿矩阵 $H_C$,从而实现了投资组合优化问题的量子化。这个哈密顿矩阵 $H_C$ 的基态即为投资组合优化问题的最优解。在接下来的部分,我们将展示如何用参数化量子电路找到这个矩阵的基态,也就是对应最小本征值的本征态。"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## Paddle Quantum 实现\n",
"\n",
"要在量桨上实现用参数化量子电路解决量子金融中的投资组合优化问题,首先要做的便是加载需要用到的包。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 1,
"source": [
"#加载需要的包\n",
"import numpy as np\n",
"import pandas as pd\n",
"import datetime\n",
"\n",
"#加载飞桨,量桨相关的模块\n",
"import paddle\n",
"from paddle_quantum.circuit import UAnsatz\n",
"from paddle_quantum.finance import DataSimulator, portfolio_optimization_hamiltonian"
],
"outputs": [],
"metadata": {
"ExecuteTime": {
"end_time": "2021-05-17T08:00:15.901429Z",
"start_time": "2021-05-17T08:00:12.708945Z"
}
}
},
{
"cell_type": "markdown",
"source": [
"### 准备实验数据\n",
"在本问题中,我们选定的投资项目为股票。对于实验测试要用的数据,提供了两种选择:\n",
"* 第一种方法是根据设定的条件,随机生成实验数据。\n",
"\n",
"如果采用这种方法准备数据,用户在初始化数据时,就需要给出可投资股票的名字列表,交易数据的开始日期和结束日期。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 2,
"source": [
"num_assets = 7 # 可投资的项目数量\n",
"stocks = [(\"STOCK%s\" % i) for i in range(num_assets)] \n",
"data = DataSimulator(stocks=stocks, start=datetime.datetime(2016, 1, 1), end=datetime.datetime(2016, 1, 30)) # 根据指定的条件,随机生成测试所需的数据\n",
"ds = data.randomly_generate()"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"* 第二种方法是用户可以选择读取本地收集到的真实数据集用于实验。考虑到文件中包含的股票数可能会很多,用户可以指定用于该实验的股票数量,即上面初始化的 `num_assets`。\n",
"\n",
"我们收集了 $12$ 支股票 $35$ 个交易日的收盘价格存放到 `realStockData_12.csv` 文件中,在这里我们只选择读取前 $7$ 个股票的信息。\n",
"\n",
"在本教程中,我们选择读取真实数据作为实验数据。"
],
"metadata": {
"ExecuteTime": {
"end_time": "2021-05-17T08:00:16.212260Z",
"start_time": "2021-05-17T08:00:15.918792Z"
}
}
},
{
"cell_type": "code",
"execution_count": 3,
"source": [
"df = pd.read_csv('realStockData_12.csv')\n",
"dt = []\n",
"for i in range(num_assets): \n",
" mylist = df['closePrice'+str(i)].tolist()\n",
" dt.append(mylist) \n",
"print(dt) # 输出从文件中读取的七个股票在35个交易日中的收盘价格\n",
"\n",
"data.set_data(dt) # 指定实验数据为用户读取的数据"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"[[16.87, 17.18, 17.07, 17.15, 16.66, 16.79, 16.69, 16.99, 16.76, 16.52, 16.33, 16.39, 16.45, 16.0, 16.09, 15.54, 13.99, 14.6, 14.63, 14.77, 14.62, 14.5, 14.79, 14.77, 14.65, 15.03, 15.37, 15.2, 15.24, 15.59, 15.58, 15.23, 15.04, 14.99, 15.11, 14.5], [32.56, 32.05, 31.51, 31.76, 31.68, 32.2, 31.46, 31.68, 31.39, 30.49, 30.53, 30.46, 29.87, 29.21, 30.11, 28.98, 26.63, 27.62, 27.64, 27.9, 27.5, 28.67, 29.08, 29.08, 29.95, 30.8, 30.42, 29.7, 29.65, 29.85, 29.25, 28.9, 29.33, 30.11, 29.67, 29.59], [5.4, 5.48, 5.46, 5.49, 5.39, 5.47, 5.46, 5.53, 5.5, 5.47, 5.39, 5.35, 5.37, 5.24, 5.26, 5.08, 4.57, 4.44, 4.5, 4.56, 4.52, 4.59, 4.66, 4.67, 4.66, 4.72, 4.84, 4.81, 4.84, 4.88, 4.89, 4.82, 4.74, 4.84, 4.79, 4.63], [3.71, 3.75, 3.73, 3.79, 3.72, 3.77, 3.76, 3.74, 3.78, 3.71, 3.61, 3.58, 3.61, 3.53, 3.5, 3.42, 3.08, 2.95, 3.04, 3.05, 3.05, 3.13, 3.12, 3.14, 3.11, 3.07, 3.23, 3.3, 3.31, 3.3, 3.33, 3.31, 3.22, 3.31, 3.25, 3.12], [5.72, 5.75, 5.74, 5.81, 5.69, 5.79, 5.77, 5.8, 5.89, 5.78, 5.7, 5.69, 5.75, 5.7, 5.71, 5.54, 4.99, 4.89, 4.94, 5.08, 5.39, 5.35, 5.23, 5.26, 5.19, 5.18, 5.31, 5.33, 5.31, 5.38, 5.39, 5.41, 5.28, 5.3, 5.38, 5.12], [7.62, 7.56, 7.68, 7.75, 7.79, 7.84, 7.82, 7.8, 7.92, 7.96, 7.93, 7.87, 7.86, 7.82, 7.9, 7.7, 6.93, 6.91, 7.18, 7.31, 7.35, 7.53, 7.47, 7.48, 7.35, 7.33, 7.46, 7.47, 7.39, 7.47, 7.48, 8.06, 8.02, 8.01, 8.11, 7.87], [3.7, 3.7, 3.68, 3.7, 3.63, 3.66, 3.63, 3.63, 3.66, 3.63, 3.6, 3.59, 3.63, 3.6, 3.61, 3.54, 3.19, 3.27, 3.27, 3.31, 3.3, 3.32, 3.33, 3.38, 3.36, 3.34, 3.39, 3.39, 3.37, 3.42, 3.43, 3.37, 3.32, 3.36, 3.37, 3.3]]\n"
]
}
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### 编码哈密顿量\n",
"\n",
"这里我们将式(2)中的二进制变量用式(3)替换,从而构建哈密顿量 $H_C$。\n",
"\n",
"在编码哈密顿量的过程中,首先需要计算各股票回报率之间的协方差矩阵 $S$。量桨平台的 finance 模块有支持计算该协方差矩阵的函数,用户可以直接调用。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 4,
"source": [
"s = data.get_asset_return_covariance_matrix()"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"第二个是需要计算出各个股票的平均投资回报率向量 $\\mu$。同样的,量桨也提供有计算各股票平均投资回报率的函数。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 5,
"source": [
"mu = data.get_asset_return_mean_vector()"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"下面根据设定和计算出来的参数来构建哈密顿量,这里我们设置惩罚参数为可投资的股票数量。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 6,
"source": [
"q = 0.5 # 风险系数\n",
"budget = num_assets // 2 # 资金预算\n",
"penalty = num_assets # 惩罚参数 \n",
"hamiltonian = portfolio_optimization_hamiltonian(penalty, mu, s, q, budget)\n"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### 计算损失函数\n",
"\n",
"我们使用 $U_3(\\vec{\\theta})$ 和 $\\text{CNOT}$ 门构造的参数化量子电路,通过调用量桨内置的 [`complex entangled layer`](https://qml.baidu.com/api/paddle_quantum.circuit.uansatz.html) 构造实现。该电路会返回一个输出态 $|\\vec{\\theta}\\rangle$,根据该参数便可以计算投资组合优化问题在经典-量子混合模型下损失的函数:\n",
"\n",
"$$\n",
"L(\\vec{\\theta}) = \\langle\\vec{\\theta}|H_C|\\vec{\\theta}\\rangle.\n",
"\\tag{4}\n",
"$$\n",
"\n",
"之后我们利用经典的优化算法寻找最优参数 $\\vec{\\theta}^*$。下面的代码给出了通过量桨和飞桨搭建网络的过程。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 7,
"source": [
"class PONet(paddle.nn.Layer):\n",
"\n",
" def __init__(self, n, p, dtype=\"float64\"):\n",
" super(PONet, self).__init__()\n",
"\n",
" self.p = p\n",
" self.num_qubits = n\n",
" self.theta = self.create_parameter(shape=[self.p, self.num_qubits, 3],\n",
" default_initializer=paddle.nn.initializer.Uniform(low=0, high=2 * np.pi),\n",
" dtype=dtype, is_bias=False)\n",
"\n",
" def forward(self, hamiltonian):\n",
" \"\"\"\n",
" 前向传播\n",
" \"\"\"\n",
" cir = UAnsatz(self.num_qubits)\n",
" cir.complex_entangled_layer(self.theta, self.p)\n",
" cir.run_state_vector()\n",
" loss = cir.expecval(hamiltonian)\n",
"\n",
" return loss, cir"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### 训练量子神经网络\n",
"\n",
"定义好了量子神经网络后,我们使用梯度下降的方法来更新其中的参数,使得式(4)的期望值最小。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 8,
"source": [
"SEED = 1000 # 随机数种子\n",
"p = 2 # 量子电路的层数\n",
"ITR = 600 # 迭代次数\n",
"LR = 0.4 # 梯度下降优化速率 "
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"使用飞桨,优化上面定义的网络。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 9,
"source": [
"# 比特数量\n",
"n = len(mu)\n",
"\n",
"# 固定随机数种子\n",
"paddle.seed(SEED)\n",
"\n",
"# 定义量子神经网络\n",
"net = PONet(n, p)\n",
"\n",
"# 使用 Adam 优化器\n",
"opt = paddle.optimizer.Adam(learning_rate=LR, parameters=net.parameters())\n",
"\n",
"# 梯度下降优循环\n",
"for itr in range(1, ITR + 1):\n",
" loss, cir = net(hamiltonian)\n",
" loss.backward()\n",
" opt.minimize(loss)\n",
" opt.clear_grad()\n",
" if itr % 50 == 0:\n",
" print(\"循环数:\", itr,\" 损失:\", \"%.7f\"% loss.numpy())\n",
" "
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"循环数: 50 损失: 0.0399075\n",
"循环数: 100 损失: 0.0098776\n",
"循环数: 150 损失: 0.0085535\n",
"循环数: 200 损失: 0.0074563\n",
"循环数: 250 损失: 0.0066519\n",
"循环数: 300 损失: 0.0061940\n",
"循环数: 350 损失: 0.0059859\n",
"循环数: 400 损失: 0.0059068\n",
"循环数: 450 损失: 0.0058807\n",
"循环数: 500 损失: 0.0058731\n",
"循环数: 550 损失: 0.0058712\n",
"循环数: 600 损失: 0.0058707\n"
]
}
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### 理论最小损失值\n",
"\n",
"理论 $C_x$ 的最小值对应的是我们所构建的哈密顿量的最小特征值。所以我们希望参数化电路优化的损失函数的值接近理论最小值。对于小一点的 ``num_assets``,我们可以根据以下代码进行验证。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 10,
"source": [
"H_C_matrix = hamiltonian.construct_h_matrix()\n",
"print(\"理论最小损失值:\", np.linalg.eigvalsh(H_C_matrix)[0]) \n",
"print(\"实际最小损失值:\", float(loss.numpy()))"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"理论最小损失值: 0.0058710575103759766\n",
"实际最小损失值: 0.005870710695958458\n"
]
}
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"在这个例子中,上面参数化电路优化出来的的最小损失和理论最小损失是非常接近的,这代表着我们之后给出的投资方案是最优的。如果两个值不太吻合,可以通过改变随机种子 `SEED`,量子电路的层数 `p`,迭代次数 `ITR` 和梯度下降优化速率 `LR` 等参数重新计算。 "
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### 解码量子答案\n",
"\n",
"当调用优化器求得损失函数的最小值以及相对应的一组参数 $\\vec{\\theta}^*$后,为了进一步求得投资组合优化问题的近似解,需要从电路输出的量子态 $|\\vec{\\theta}^*\\rangle$ 中解码出经典优化问题的答案。物理上,解码量子态需要对量子态进行测量,然后统计测量结果的概率分布:\n",
"\n",
"$$\n",
"p(z) = |\\langle z|\\vec{\\theta}^*\\rangle|^2.\n",
"\\tag{5}\n",
"$$\n",
"\n",
"在量子参数化电路表达能力足够的情况下,某个比特串出现的概率越大,意味着其是投资组合优化问题最优解的可能性越大。\n",
"\n",
"量桨提供了查看参数化量子电路输出状态的测量结果概率分布的函数。\n"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 11,
"source": [
"# 模拟重复测量电路输出态 2048 次\n",
"prob_measure = cir.measure(shots=2048)\n",
"investment = max(prob_measure, key=prob_measure.get)\n",
"print(\"利用哈密顿量找到的解的比特串形式:\",investment)"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"利用哈密顿量找到的解的比特串形式: 0100110\n"
]
}
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"我们的测量结果是表示投资组合优化问题答案的比特串:字符串中该位置为 $1$,表示该笔资产被选定投资。如上面的结果 `0100110` 就表示在可选的 $7$ 支可投资的项目中,选择了第二、第五、第六三支股票。同时,字符串中 $1$ 的数量应该和预算数相同。如果最后的情况不是这样,读者依然可以通过调整参数化量子电路的参数值或参数化量子电路的结构来获得更好的训练效果。"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### 结语\n",
"\n",
"本教程中,投资组合优化问题的最优解是在均值-方差组合优化方法基础上通过变分量子本征求解器(Variational Quantum Eigensolver, VQE)近似得到的。在给定投资预算和可投资项目信息以及投资风险的基础上,通过计算投资项目的回报率以及各投资项目回报率之间的协方差矩阵,应用参数化量子电路寻找最优的投资组合。"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"_______\n",
"\n",
"## 参考文献\n",
"\n",
"[1] Orus, Roman, Samuel Mugel, and Enrique Lizaso. \"Quantum computing for finance: Overview and prospects.\" [Reviews in Physics 4 (2019): 100028.](https://arxiv.org/abs/1807.03890)\n",
"\n",
"[2] Egger, Daniel J., et al. \"Quantum computing for Finance: state of the art and future prospects.\" [IEEE Transactions on Quantum Engineering (2020).](https://arxiv.org/abs/2006.14510)\n",
"\n",
"[3] Markowitz, H.M. (March 1952). \"Portfolio Selection\". [The Journal of Finance. 7 (1): 77–91. doi:10.2307/2975974. JSTOR 2975974.](https://www.jstor.org/stable/2975974)"
],
"metadata": {}
}
],
"metadata": {
"interpreter": {
"hash": "3b61f83e8397e1c9fcea57a3d9915794102e67724879b24295f8014f41a14d85"
},
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.10"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}
\ No newline at end of file
{
"cells": [
{
"cell_type": "markdown",
"source": [
"# Quantum Finance Application on Portfolio Optimization\n",
"\n",
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## Overview\n",
"\n",
"Current finance problems can be mainly tackled by three areas of quantum algorithms: quantum simulation, quantum optimization, and quantum machine learning [1,2]. Many financial problems are essentially combinatorial optimization problems, and corresponding algorithms usually have high time complexity and are difficult to implement. Due to the power of quantum computing, these complex problems are expected to be solved by quantum algorithms in the future.\n",
"\n",
"The Quantum Finance module of Paddle Quantum focuses on quantum optimization: how to apply quantum algorithms in real finance optimization problems. This tutorial focuses on how to use quantum algorithms to solve the portfolio optimization problem."
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## Portfolio Optimization Problem\n",
"\n",
"A portfolio is a collection of financial investments, such as stocks, bonds, cash, etc. Many investment managers face the portfolio optimization problem. This problem requires practitioners to invest various projects, according to their target returns and risks. This aims to minimize the risk given certain return or maximize the return given certain risk.\n",
"\n",
"Detailed description of portfolio optimization is as follows: If you are an active investment manager who wants to invest $K$ dollars to $N$ projects, each with its own return and risk, your goal is to find an optimal way to invest the projects, taking into account the market impact and transaction costs."
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Encoding Portfolio Optimization Problem\n",
"\n",
"To transform the portfolio optimization problem into a problem applicable for parameterized quantum circuits, we need to encode portfolio optimization problem into a Hamiltonian. To make the modeling easy to formulate, two assumptions are made to constrain the problem:\n",
"* Each asset is invested with an equal amount of money.\n",
"* Budget is a multiple of each investment amount and must be fully spent.\n",
"\n",
"\n",
"In this model we unitize the investment amount, i.e., if the budget is $3$, then the manager should invest $3$ assets. Since the actual investment budget is limited and there are many investable assets, it is important to set the number of investable assets larger than the budget.\n",
"\n",
"In the theory of portfolio optimization, the overall risk of a portfolio is related to the covariance between assets, which is proportional to the correlation coefficients of any two assets. The smaller the correlation coefficients, the smaller the covariance, and then the smaller the overall risk of the portfolio [3].\n",
"\n",
"Here we use the mean-variance approach to model this problem:\n",
"\n",
"$$\n",
"\\omega = \\max _{x \\in\\{0,1\\}^{n}} \\mu^{T} x - q x^{T} S x \\quad\\quad \\tag{1}\n",
"\\text { subject to: } 1^{T} x=B,\n",
"$$\n",
"\n",
"where each symbol has the following meaning:\n",
"* $x\\in {\\{0,1\\}}^N$ denotes the vector of binary decision variables, which indicate which each assets is picked ($x_i=1$) or not ($x_i = 0$),\n",
"* $\\mu \\in \\mathbb{R}^n$ defines the expected returns for the assets,\n",
"* $S \\in \\mathbb{R}^{n \\times n}$ represents the covariances between the assets,\n",
"* $q > 0$ represents the risk factor of investment decision making,\n",
"* $\\mathbb{1}$ denotes a vector with all values of $1$,\n",
"* $B$ denotes the budget, i.e. the number of assets to be selected out of $N$.\n",
"\n",
"\n",
"According to the model equation, we can define the loss function:\n",
"\n",
"$$\n",
"C_x = q \\sum_i \\sum_j s_{ji}x_ix_j - \\sum_{i}x_i \\mu_i + A \\left(B - \\sum_i x_i\\right)^2, \\tag{2}\n",
"$$\n",
"\n",
"where $s_{ij}$ denotes the elements of the covariance matrix $S$.\n",
"\n",
"Since the loss function is to be optimized using the gradient descent method, some modifications are made in the definition based on the equations of the model. The first term represents the risk of the investment. The second term represents the expected return on this investment. The third term constrains the budget $B$ to be invested evenly in different projects. $A$ is the penalty parameter, usually set to a larger number. \n",
"\n",
"We now need to transform the cost function $C_x$ into a Hamiltonian to realize the encoding of the portfolio optimization problem. Each variable $x_{i}$ has two possible values, $0$ and $1$, corresponding to quantum states $|0\\rangle$ and $|1\\rangle$. Note that every variable corresponds to a qubit and so $n$ qubits are needed for solving the portfolio optimization problem. The Pauli $Z$ operator has two eigenstates, $|0\\rangle$ and $|1\\rangle$ . Their corresponding eigenvalues are 1 and -1, respectively. So we consider encoding the cost function as a Hamiltonian using the pauli $Z$ matrix.\n",
"\n",
"Now we would like to consider the mapping\n",
"$$\n",
"x_{i} \\mapsto \\frac{I-Z_{i}}{2}, \\tag{4}\n",
"$$\n",
"\n",
"where $Z_{i} = I \\otimes I \\otimes \\ldots \\otimes Z \\otimes \\ldots \\otimes I$ with $Z$ operates on the qubit at position $i$. Under this mapping, the value of $x_i$ can be illustrated in a different way. If the qubit $i$ is in state $|1\\rangle$, then $x_{i} |1\\rangle = \\frac{I-Z_{i}}{2} |1\\rangle = 1|1\\rangle $, which means that the stork $i$ is in the optimal portfolio. Also, for a qubit $i$ in state $|0\\rangle$, $x_{i}|0\\rangle = \\frac{I-Z_{i}}{2} |0\\rangle = 0 |0\\rangle $.\n",
"\n",
"Thus using the above mapping, we can transform the cost function $C_x$ into a Hamiltonian $H_C$ for the system of $n$ qubits and realize the quantumization of the portfolio optimization problem. Then the ground state of $H_C$ is the optimal solution to the portfolio optimization problem. In the following section, we will show how to use a parameterized quantum circuit to find the ground state, i.e., the eigenvector with the smallest eigenvalue."
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## Paddle Quantum Implementation\n",
"\n",
"To investigate the portfolio optimization problem using Paddle Quantum, there are some required packages to import, which are shown below. "
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 1,
"source": [
"# Import packages needed\n",
"import numpy as np\n",
"import pandas as pd\n",
"import datetime\n",
"\n",
"# Import related modules from Paddle Quantum and PaddlePaddle\n",
"import paddle\n",
"from paddle_quantum.circuit import UAnsatz\n",
"from paddle_quantum.utils import pauli_str_to_matrix\n",
"from paddle_quantum.finance import DataSimulator, portfolio_optimization_hamiltonian"
],
"outputs": [],
"metadata": {
"ExecuteTime": {
"end_time": "2021-05-17T08:00:15.901429Z",
"start_time": "2021-05-17T08:00:12.708945Z"
}
}
},
{
"cell_type": "markdown",
"source": [
"### Prepare experimental data\n",
"\n",
"In this tutorial, we choose stocks as the investment asset. For the data used in the experimental tests, two options are provided:\n",
"* The first method is to generate random data according to certain requirements, e.g. number of assets.\n",
"\n",
"If the user prepares data using this method, then when initializing the data, it is necessary to give the list of parameters: a list of names of investable stocks (assets), the start date and end date of the trading data."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 2,
"source": [
"num_assets = 7 # Number of investable projects\n",
"stocks = [(\"STOCK%s\" % i) for i in range(num_assets)] \n",
"data = DataSimulator( stocks = stocks, start = datetime.datetime(2016, 1, 1),\n",
" end = datetime.datetime(2016, 1, 30))"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"* The second method is that the user can choose to set the data themselves, i.e. real stock data collected by themselves. Considering that the number of stocks contained in the file may be large, the user can specify the number of stocks used for this experiment, i.e. `num_assets` as initialized above.\n",
"\n",
"We collect the closing prices of 12 stocks for 35 trading days into the `realStockData_12.csv` file, where we choose to read only the first 3 stocks.\n",
"\n",
"In this tutorial, we choose to read real data as experimental data."
],
"metadata": {
"ExecuteTime": {
"end_time": "2021-05-17T08:00:16.212260Z",
"start_time": "2021-05-17T08:00:15.918792Z"
}
}
},
{
"cell_type": "code",
"execution_count": 3,
"source": [
"df = pd.read_csv('realStockData_12.csv')\n",
"dt = []\n",
"for i in range(num_assets): \n",
" mylist = df['closePrice'+str(i)].tolist()\n",
" dt.append(mylist) \n",
"# Output the closing price of the seven stocks read from the file for the 35 trading days\n",
"print(dt) \n",
"# Specify the experimental data as a local file read by the user\n",
"data.set_data(dt) "
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"[[16.87, 17.18, 17.07, 17.15, 16.66, 16.79, 16.69, 16.99, 16.76, 16.52, 16.33, 16.39, 16.45, 16.0, 16.09, 15.54, 13.99, 14.6, 14.63, 14.77, 14.62, 14.5, 14.79, 14.77, 14.65, 15.03, 15.37, 15.2, 15.24, 15.59, 15.58, 15.23, 15.04, 14.99, 15.11, 14.5], [32.56, 32.05, 31.51, 31.76, 31.68, 32.2, 31.46, 31.68, 31.39, 30.49, 30.53, 30.46, 29.87, 29.21, 30.11, 28.98, 26.63, 27.62, 27.64, 27.9, 27.5, 28.67, 29.08, 29.08, 29.95, 30.8, 30.42, 29.7, 29.65, 29.85, 29.25, 28.9, 29.33, 30.11, 29.67, 29.59], [5.4, 5.48, 5.46, 5.49, 5.39, 5.47, 5.46, 5.53, 5.5, 5.47, 5.39, 5.35, 5.37, 5.24, 5.26, 5.08, 4.57, 4.44, 4.5, 4.56, 4.52, 4.59, 4.66, 4.67, 4.66, 4.72, 4.84, 4.81, 4.84, 4.88, 4.89, 4.82, 4.74, 4.84, 4.79, 4.63], [3.71, 3.75, 3.73, 3.79, 3.72, 3.77, 3.76, 3.74, 3.78, 3.71, 3.61, 3.58, 3.61, 3.53, 3.5, 3.42, 3.08, 2.95, 3.04, 3.05, 3.05, 3.13, 3.12, 3.14, 3.11, 3.07, 3.23, 3.3, 3.31, 3.3, 3.33, 3.31, 3.22, 3.31, 3.25, 3.12], [5.72, 5.75, 5.74, 5.81, 5.69, 5.79, 5.77, 5.8, 5.89, 5.78, 5.7, 5.69, 5.75, 5.7, 5.71, 5.54, 4.99, 4.89, 4.94, 5.08, 5.39, 5.35, 5.23, 5.26, 5.19, 5.18, 5.31, 5.33, 5.31, 5.38, 5.39, 5.41, 5.28, 5.3, 5.38, 5.12], [7.62, 7.56, 7.68, 7.75, 7.79, 7.84, 7.82, 7.8, 7.92, 7.96, 7.93, 7.87, 7.86, 7.82, 7.9, 7.7, 6.93, 6.91, 7.18, 7.31, 7.35, 7.53, 7.47, 7.48, 7.35, 7.33, 7.46, 7.47, 7.39, 7.47, 7.48, 8.06, 8.02, 8.01, 8.11, 7.87], [3.7, 3.7, 3.68, 3.7, 3.63, 3.66, 3.63, 3.63, 3.66, 3.63, 3.6, 3.59, 3.63, 3.6, 3.61, 3.54, 3.19, 3.27, 3.27, 3.31, 3.3, 3.32, 3.33, 3.38, 3.36, 3.34, 3.39, 3.39, 3.37, 3.42, 3.43, 3.37, 3.32, 3.36, 3.37, 3.3]]\n"
]
}
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Encoding Hamiltonian\n",
"\n",
"Here we construct the Hamiltonian $H_C$ of Eq. (2) with the replacement in Eq. (3). \n",
"\n",
"In the process of encoding Hamiltonian, we first need to calculate the covariance matrix $S$ between the returns of each stock, which is available in the ``finance`` module and can be called directly."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 4,
"source": [
"s = data.get_asset_return_covariance_matrix()"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"The second step is to compute the expected return vector $\\mu$ for each stock. Similarly, paddle quantum also support this function to users."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 5,
"source": [
"mu = data.get_asset_return_mean_vector()"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"Based on the provided and calculated parameters, the Hamiltonian is constructed below. Here we set the penalty parameter to the number of investable stocks."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 6,
"source": [
"q = 0.5 # risk appetite of the decision maker\n",
"budget = num_assets // 2 # budget\n",
"penalty = num_assets # penalty parameter \n",
"hamiltonian = portfolio_optimization_hamiltonian(penalty, mu, s, q, budget)"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Calculating the loss function \n",
"\n",
"We adopt a parameterized quantum circuit consisting of $U_3(\\vec{\\theta})$ and $\\text{CNOT}$ gates. It can be constructed by calling the built-in function [`complex entangled layer`](https://qml.baidu.com/api/paddle_quantum.circuit.uansatz.html).\n",
"\n",
"After running the quantum circuit, we obtain the circuit output $|\\vec{\\theta\n",
"}\\rangle$. From the output state of the circuit, we can calculate the loss function of the portfolio optimization under the classical-quantum hybrid model:\n",
"\n",
"$$\n",
"L(\\vec{\\theta}) = \\langle\\vec{\\theta}|H_C|\\vec{\\theta}\\rangle.\n",
"\\tag{4}\n",
"$$\n",
"\n",
"We then use a classical optimization algorithm to minimize this function and find the optimal parameters $\\vec{\\theta}^*$. The following code shows a complete network built with Paddle Quantum and PaddlePaddle."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 7,
"source": [
"class PONet(paddle.nn.Layer):\n",
"\n",
" def __init__(self, n, p, dtype=\"float64\"):\n",
" super(PONet, self).__init__()\n",
"\n",
" self.p = p\n",
" self.num_qubits = n\n",
" self.theta = self.create_parameter(shape=[self.p, self.num_qubits, 3],\n",
" default_initializer=paddle.nn.initializer.Uniform(low=0, high=2 * np.pi),\n",
" dtype=dtype, is_bias=False)\n",
"\n",
" def forward(self, hamiltonian):\n",
" \"\"\"\n",
" Forward propagation\n",
" \"\"\"\n",
" # Define a circuit with complex entangled layers\n",
" cir = UAnsatz(self.num_qubits)\n",
" cir.complex_entangled_layer(self.theta, self.p)\n",
" # Run the quantum circuit\n",
" cir.run_state_vector()\n",
" # Calculate the loss function\n",
" loss = cir.expecval(hamiltonian)\n",
"\n",
" return loss, cir"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Training the quantum neural network\n",
"\n",
"After defining the quantum neural network, we use the gradient descent method to update the parameters to minimize the expectation value in Eq. (4). "
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 8,
"source": [
"SEED = 1000 # Set a global RNG seed \n",
"p = 2 # Number of layers in the quantum circuit\n",
"ITR = 600 # Number of training iterations\n",
"LR = 0.4 # Learning rate of the optimization method based on gradient descent"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"Here, we optimize the network defined above in PaddlePaddle."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 11,
"source": [
"# number of qubits\n",
"n = len(mu)\n",
"\n",
"# Fix paddle random seed\n",
"paddle.seed(SEED)\n",
"\n",
"# Building Quantum Neural Networks\n",
"net = PONet(n, p)\n",
"\n",
"# Use Adam optimizer\n",
"opt = paddle.optimizer.Adam(learning_rate=LR, parameters=net.parameters())\n",
"\n",
"# Gradient descent iteration\n",
"for itr in range(1, ITR + 1):\n",
" # Run the network defined above\n",
" loss, cir = net(hamiltonian)\n",
" # Calculate the gradient and optimize\n",
" loss.backward()\n",
" opt.minimize(loss)\n",
" opt.clear_grad()\n",
" if itr % 50 == 0:\n",
" print(\"iter: \", itr, \" loss: \", \"%.7f\"% loss.numpy())\n",
" "
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"iter: 50 loss: 0.0399075\n",
"iter: 100 loss: 0.0098776\n",
"iter: 150 loss: 0.0085535\n",
"iter: 200 loss: 0.0074563\n",
"iter: 250 loss: 0.0066519\n",
"iter: 300 loss: 0.0061940\n",
"iter: 350 loss: 0.0059859\n",
"iter: 400 loss: 0.0059068\n",
"iter: 450 loss: 0.0058807\n",
"iter: 500 loss: 0.0058731\n",
"iter: 550 loss: 0.0058712\n",
"iter: 600 loss: 0.0058707\n"
]
}
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Theoretical minimum loss value\n",
"\n",
"The theoretical minimum value of $C_x$ corresponds to the minimum eigenvalue of the Hamiltonian constructed above. So we would like to see the value of the loss function found by the parameterized circuit optimization close to the theoretical minimum. For smaller ``num_assets``, we can verify this based on the following code."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 12,
"source": [
"H_C_matrix = hamiltonian.construct_h_matrix()\n",
"print(\"Theoretical minimum loss value: \", np.linalg.eigvalsh(H_C_matrix)[0])\n",
"print(\"Practical minimum loss value: \", float(loss.numpy()))"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Theoretical minimum loss value: 0.0058710575103759766\n",
"Practical minimum loss value: 0.005870710695958458\n"
]
}
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"In this case, the minimum loss from the parameterized circuit optimization is the same as the theoretical minimum loss, which ensures that the investment solution found is optimal. If two values do not match well, we can adjust parameters such as the random seed ``SEED``, the number of layers of the quantum circuit ``p``, the number of iterations ``ITR`` and the gradient descent optimization rate ``LR``, to reapproximate the optimal solution."
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Decoding the quantum solution\n",
"\n",
"After obtaining the minimum value of the loss function and the corresponding set of parameters $\\vec{\\theta}^*$, our task has not been completed. In order to obtain an approximate solution to the portfolio optimization problem, it is necessary to decode the solution to the classical optimization problem from the quantum state $|\\vec{\\theta}^*\\rangle$ output by the circuit. Physically, to decode a quantum state, we need to measure it and then calculate the probability distribution of the measurement results:\n",
"\n",
"$$\n",
"p(z) = |\\langle z|\\vec{\\theta}^*\\rangle|^2.\n",
"\\tag{5}\n",
"$$\n",
"\n",
"In the case of quantum parameterized circuits with sufficient expressiveness, the greater the probability of a certain bit string, the greater the probability that it corresponds to an optimal solution of the portfolio optimization problem.\n",
"\n",
"Paddle Quantum provides a function to read the probability distribution of the measurement results of the state output by the quantum circuit:"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Repeat the simulated measurement of the circuit output state 1024 times\n",
"prob_measure = cir.measure(shots=2048)\n",
"investment = max(prob_measure, key=prob_measure.get)\n",
"print(\"The bit string form of the solution: \",investment)"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"The bit string form of the solution: 0100110\n"
]
}
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"The result of our measurement is a bit string that represents the solution to the portfolio optimization problem: $1$ appearing at the $i$th bit indicates that the $i$th asset was selected for investment. For example, the result `0100110` above would indicate that the second, fifth and sixth stocks were selected out of the seven available investments. The number of $1$s in the string should be the same as the budget $B$. If the result is not like this, users can also get better training results by adjusting the parameters or structure of parameterized quantum circuits."
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Conclusion\n",
"\n",
"In this tutorial, the optimal solution to the portfolio optimization is approximated through the Variational Quantum Eigensolver (VQE) based on the mean-variance approach. Given the budget, available assets and investment risks, the parameterized quantum circuits is applied to find the optimal portfolio by calculating the returns of investment projects and the covariance matrix between the returns of each investment project. "
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"_______\n",
"\n",
"## References\n",
"\n",
"[1] Orus, Roman, Samuel Mugel, and Enrique Lizaso. \"Quantum computing for finance: Overview and prospects.\" [Reviews in Physics 4 (2019): 100028.](https://arxiv.org/abs/1807.03890)\n",
"\n",
"[2] Egger, Daniel J., et al. \"Quantum computing for Finance: state of the art and future prospects.\" [IEEE Transactions on Quantum Engineering (2020).](https://arxiv.org/abs/2006.14510)\n",
"\n",
"[3] Markowitz, H.M. (March 1952). \"Portfolio Selection\". [The Journal of Finance. 7 (1): 77–91. doi:10.2307/2975974. JSTOR 2975974.](https://www.jstor.org/stable/2975974)"
],
"metadata": {}
}
],
"metadata": {
"interpreter": {
"hash": "3b61f83e8397e1c9fcea57a3d9915794102e67724879b24295f8014f41a14d85"
},
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.10"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}
\ No newline at end of file
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
"\n", "\n",
"旅行商问题(travelling salesman problem, TSP)是组合优化中最经典的 NP–困难的问题之一,它指的是以下这个问题:\"已知一系列的城市和它们之间的距离,这个旅行商想造访所有城市各一次,并最后返回出发地,求他的最短路线规划。\"\n", "旅行商问题(travelling salesman problem, TSP)是组合优化中最经典的 NP–困难的问题之一,它指的是以下这个问题:\"已知一系列的城市和它们之间的距离,这个旅行商想造访所有城市各一次,并最后返回出发地,求他的最短路线规划。\"\n",
"\n", "\n",
"这个问题也可以用图论的语言来描述。已知一个有权重的完全图 $G = (V,E)$。它的每个顶点 $i \\in V$ 都对应一个城市 $i$,并且每一条边 $(i,j) \\in E$ 的权重 $w_{i,j}$ 对应城市 $i$ 和城市 $j$ 的距离。需要注意的是,$G$ 是个无向图,所以权重是对称的,即 $w_{i,j}= w_{j,i}$。根据以上定义,旅行商问题可以转化为找这个图中最短的哈密顿回路(Hamiltonian cycle)的问题。哈密顿回路为一个通过且仅通过每一个顶点一次的回路。" "这个问题也可以用图论的语言来描述。已知一个有权重的完全图 $G = (V,E)$。它的每个顶点 $i \\in V$ 都对应一个城市 $i$,并且每一条边 $(i,j) \\in E$ 的权重 $w_{i,j}$ 对应城市 $i$ 和城市 $j$ 的距离。需要注意的是,$G$ 是个无向图,所以权重是对称的,即 $w_{i,j}= w_{j,i}$。根据以上定义,旅行商问题可以转化为找这个图中最短的哈密顿回路(Hamiltonian cycle)的问题。哈密顿回路为一个通过且仅通过每一个顶点一次的回路。 "
] ]
}, },
{ {
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
"x_{i,t} \\mapsto \\frac{I-Z_{i,t}}{2}, \\tag{5}\n", "x_{i,t} \\mapsto \\frac{I-Z_{i,t}}{2}, \\tag{5}\n",
"$$\n", "$$\n",
"\n", "\n",
"这里 $Z_{i,t} = I \\otimes I \\otimes \\ldots \\otimes Z \\otimes \\ldots \\otimes I$,也就是说 $Z$ 作用在位置在 $(i,t)$ 的量子比特上。通过这个映射,如果一个编号为 $(i,t)$ 的量子比特的量子态为 $|1\\rangle$,那么对应的二进制变量的取值为 $x_{i,t} = \\frac{I-Z_{i,t}}{2} |1\\rangle = 1$,也就是说顶点 $i$ 在最短哈密顿回路中的位置是 $t$。同样地,对于量子态为 $|0\\rangle$的量子比特 $(i,t)$,它所对应的二进制变量的取值为 $x_{i,t} = \\frac{I-Z_{i,t}}{2} |1\\rangle = 0$。\n", "这里 $Z_{i,t} = I \\otimes I \\otimes \\ldots \\otimes Z \\otimes \\ldots \\otimes I$,也就是说 $Z$ 作用在位置在 $(i,t)$ 的量子比特上。通过这个映射,如果一个编号为 $(i,t)$ 的量子比特的量子态为 $|1\\rangle$,那么对应的二进制变量的取值为 $x_{i,t} |1\\rangle = \\frac{I-Z_{i,t}}{2} |1\\rangle = 1 |1\\rangle$,也就是说顶点 $i$ 在最短哈密顿回路中的位置是 $t$。同样地,对于量子态为 $|0\\rangle$的量子比特 $(i,t)$,它所对应的二进制变量的取值为 $x_{i,t} |0\\rangle = \\frac{I-Z_{i,t}}{2} |0\\rangle = 0 |0\\rangle$。\n",
"\n", "\n",
"我们用上述映射将 $C(x)$ 转化成量子比特数为 $n^2$ 的系统的哈密顿矩阵 $H_C$,从而实现了旅行商问题的量子化。这个哈密顿矩阵 $H_C$ 的基态即为旅行商问题的最优解。在接下来的章节中,我们将展示怎么用参数化量子电路找到这个矩阵的基态,即对应最小本征值的本征态。" "我们用上述映射将 $C(x)$ 转化成量子比特数为 $n^2$ 的系统的哈密顿矩阵 $H_C$,从而实现了旅行商问题的量子化。这个哈密顿矩阵 $H_C$ 的基态即为旅行商问题的最优解。在接下来的章节中,我们将展示怎么用参数化量子电路找到这个矩阵的基态,即对应最小本征值的本征态。"
] ]
...@@ -529,7 +529,7 @@ ...@@ -529,7 +529,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.7.10" "version": "3.8.8"
}, },
"toc": { "toc": {
"base_numbering": 1, "base_numbering": 1,
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
"\n", "\n",
"One of the most famous NP-hard problems in combinatorial optimization, the travelling salesman problem (TSP) considers the following question: \"Given a list of cities and the distances between each pair of cities, what is the shortest possible route that visits each city exactly once and returns to the origin city?\" \n", "One of the most famous NP-hard problems in combinatorial optimization, the travelling salesman problem (TSP) considers the following question: \"Given a list of cities and the distances between each pair of cities, what is the shortest possible route that visits each city exactly once and returns to the origin city?\" \n",
"\n", "\n",
"This question can also be formulated in the language of graph theory. Given a weighted undirected complete graph $G = (V,E)$, where each vertex $i \\in V$ corresponds to city $i$ and the weight $w_{i,j}$ of each edge $(i,j,w_{i,j}) \\in E$ represents the distance between cities $i$ and $j$, the TSP is to find the shortest Hamiltonian cycle in $G$, where a Hamiltonian cycle is a closed loop on a graph in which every vertex is visited exactly once. Note that because $G$ is an undirected graph, weights are symmetric, i.e., $w_{i,j} = w_{j,i}$." "This question can also be formulated in the language of graph theory. Given a weighted undirected complete graph $G = (V,E)$, where each vertex $i \\in V$ corresponds to city $i$ and the weight $w_{i,j}$ of each edge $(i,j,w_{i,j}) \\in E$ represents the distance between cities $i$ and $j$, the TSP is to find the shortest Hamiltonian cycle in $G$, where a Hamiltonian cycle is a closed loop on a graph in which every vertex is visited exactly once. Note that because $G$ is an undirected graph, weights are symmetric, i.e., $w_{i,j} = w_{j,i}$. "
] ]
}, },
{ {
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
"x_{i,t} \\mapsto \\frac{I-Z_{i,t}}{2}, \\tag{5}\n", "x_{i,t} \\mapsto \\frac{I-Z_{i,t}}{2}, \\tag{5}\n",
"$$\n", "$$\n",
"\n", "\n",
"where $Z_{i,t} = I \\otimes I \\otimes \\ldots \\otimes Z \\otimes \\ldots \\otimes I$ with $Z$ operates on the qubit at position $(i,t)$. Under this mapping, if a qubit $(i,t)$ is in state $|1\\rangle$, then $x_{i,t} = \\frac{I-Z_{i,t}}{2} |1\\rangle = 1$, which means vertex $i$ is visited at time $t$. Also, for a qubit $(i,t)$ in state $|0\\rangle$, $x_{i,t} = \\frac{I-Z_{i,t}}{2} |1\\rangle = 0$.\n", "where $Z_{i,t} = I \\otimes I \\otimes \\ldots \\otimes Z \\otimes \\ldots \\otimes I$ with $Z$ operates on the qubit at position $(i,t)$. Under this mapping, if a qubit $(i,t)$ is in state $|1\\rangle$, then $x_{i,t}|1\\rangle = \\frac{I-Z_{i,t}}{2} |1\\rangle = 1 |1\\rangle$, which means vertex $i$ is visited at time $t$. Also, for a qubit $(i,t)$ in state $|0\\rangle$, $x_{i,t} |0\\rangle= \\frac{I-Z_{i,t}}{2} |0\\rangle = 0|0\\rangle$.\n",
"\n", "\n",
"Thus using the above mapping, we can transform the cost function $C(x)$ into a Hamiltonian $H_C$ for the system of $n^2$ qubits and realize the quantumization of the TSP. Then the ground state of $H_C$ is the optimal solution to the TSP. In the following section, we will show how to use a parametrized quantum circuit to find the ground state, i.e., the eigenvector with the smallest eigenvalue.\n", "Thus using the above mapping, we can transform the cost function $C(x)$ into a Hamiltonian $H_C$ for the system of $n^2$ qubits and realize the quantumization of the TSP. Then the ground state of $H_C$ is the optimal solution to the TSP. In the following section, we will show how to use a parametrized quantum circuit to find the ground state, i.e., the eigenvector with the smallest eigenvalue.\n",
"\n" "\n"
...@@ -530,7 +530,7 @@ ...@@ -530,7 +530,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.7.10" "version": "3.8.8"
}, },
"toc": { "toc": {
"base_numbering": 1, "base_numbering": 1,
......
closePrice0,closePrice1,closePrice2,closePrice3,closePrice4,closePrice5,closePrice6,closePrice7,closePrice8,closePrice9,closePrice10,closePrice11
16.87,32.56,5.4,3.71,5.72,7.62,3.7,6.94,5.43,3.46,8.22,4.56
17.18,32.05,5.48,3.75,5.75,7.56,3.7,6.89,5.37,3.45,8.14,4.54
17.07,31.51,5.46,3.73,5.74,7.68,3.68,6.91,5.37,3.41,8.1,4.55
17.15,31.76,5.49,3.79,5.81,7.75,3.7,6.97,5.42,3.5,8.16,4.58
16.66,31.68,5.39,3.72,5.69,7.79,3.63,6.85,5.29,3.42,7.93,4.48
16.79,32.2,5.47,3.77,5.79,7.84,3.66,6.94,5.41,3.46,8.02,4.56
16.69,31.46,5.46,3.76,5.77,7.82,3.63,7.01,5.42,3.48,8,4.53
16.99,31.68,5.53,3.74,5.8,7.8,3.63,7.03,5.39,3.47,8.03,4.53
16.76,31.39,5.5,3.78,5.89,7.92,3.66,7.04,5.45,3.48,8.05,4.56
16.52,30.49,5.47,3.71,5.78,7.96,3.63,7.01,5.43,3.45,7.95,4.47
16.33,30.53,5.39,3.61,5.7,7.93,3.6,6.99,5.35,3.4,7.92,4.42
16.39,30.46,5.35,3.58,5.69,7.87,3.59,6.95,5.26,3.41,7.93,4.38
16.45,29.87,5.37,3.61,5.75,7.86,3.63,6.96,5.54,3.42,7.99,4.35
16,29.21,5.24,3.53,5.7,7.82,3.6,6.87,6.09,3.32,7.9,4.32
16.09,30.11,5.26,3.5,5.71,7.9,3.61,6.87,6.7,3.33,8.01,4.34
15.54,28.98,5.08,3.42,5.54,7.7,3.54,6.58,7.37,3.23,7.73,4.13
13.99,26.63,4.57,3.08,4.99,6.93,3.19,5.92,8.11,2.91,6.96,3.72
14.6,27.62,4.44,2.95,4.89,6.91,3.27,5.78,8.1,2.96,7.01,3.51
14.63,27.64,4.5,3.04,4.94,7.18,3.27,5.89,8.91,3.02,7.06,3.61
14.77,27.9,4.56,3.05,5.08,7.31,3.31,5.94,9.8,3.06,7.08,3.88
14.62,27.5,4.52,3.05,5.39,7.35,3.3,5.93,10.78,3.05,7.07,3.87
14.5,28.67,4.59,3.13,5.35,7.53,3.32,6.06,11.86,3.13,7.15,3.9
14.79,29.08,4.66,3.12,5.23,7.47,3.33,6.16,10.67,3.15,7.17,3.91
14.77,29.08,4.67,3.14,5.26,7.48,3.38,6.18,11.36,3.17,7.21,3.95
14.65,29.95,4.66,3.11,5.19,7.35,3.36,6.15,10.56,3.14,7.19,3.94
15.03,30.8,4.72,3.07,5.18,7.33,3.34,6.11,9.56,3.15,7.29,3.96
15.37,30.42,4.84,3.23,5.31,7.46,3.39,6.35,9.15,3.18,7.41,4.04
15.2,29.7,4.81,3.3,5.33,7.47,3.39,6.34,9.11,3.17,7.4,4.06
15.24,29.65,4.84,3.31,5.31,7.39,3.37,6.26,8.89,3.12,7.34,3.99
15.59,29.85,4.88,3.3,5.38,7.47,3.42,6.44,8.36,3.15,7.42,4.04
15.58,29.25,4.89,3.33,5.39,7.48,3.43,6.46,8.68,3.16,7.52,4.03
15.23,28.9,4.82,3.31,5.41,8.06,3.37,6.41,8.77,3.12,7.41,3.97
15.04,29.33,4.74,3.22,5.28,8.02,3.32,6.32,9.65,3.06,7.31,3.9
14.99,30.11,4.84,3.31,5.3,8.01,3.36,6.32,9.11,3.13,7.51,3.9
15.11,29.67,4.79,3.25,5.38,8.11,3.37,6.42,8.41,3.15,7.5,3.88
14.5,29.59,4.63,3.12,5.12,7.87,3.3,6.15,8.4,3.08,7.18,3.76
\ No newline at end of file
...@@ -78,8 +78,6 @@ ...@@ -78,8 +78,6 @@
"import paddle\n", "import paddle\n",
"from numpy import pi as PI\n", "from numpy import pi as PI\n",
"from matplotlib import pyplot as plt\n", "from matplotlib import pyplot as plt\n",
"import warnings\n",
"warnings.filterwarnings(\"ignore\")\n",
"\n", "\n",
"from paddle import matmul, transpose\n", "from paddle import matmul, transpose\n",
"from paddle_quantum.circuit import UAnsatz\n", "from paddle_quantum.circuit import UAnsatz\n",
...@@ -215,7 +213,7 @@ ...@@ -215,7 +213,7 @@
}, },
{ {
"data": { "data": {
"image/png": "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\n", "image/png": "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\n",
"text/plain": [ "text/plain": [
"<Figure size 432x288 with 1 Axes>" "<Figure size 432x288 with 1 Axes>"
] ]
...@@ -234,7 +232,7 @@ ...@@ -234,7 +232,7 @@
}, },
{ {
"data": { "data": {
"image/png": "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\n", "image/png": "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\n",
"text/plain": [ "text/plain": [
"<Figure size 432x288 with 1 Axes>" "<Figure size 432x288 with 1 Axes>"
] ]
...@@ -643,7 +641,7 @@ ...@@ -643,7 +641,7 @@
"# 生成只作用在第一个量子比特上的泡利 Z 算符\n", "# 生成只作用在第一个量子比特上的泡利 Z 算符\n",
"# 其余量子比特上都作用单位矩阵\n", "# 其余量子比特上都作用单位矩阵\n",
"def Observable(n):\n", "def Observable(n):\n",
" \"\"\"\n", " r\"\"\"\n",
" :param n: 量子比特数量\n", " :param n: 量子比特数量\n",
" :return: 局部可观测量: Z \\otimes I \\otimes ...\\otimes I\n", " :return: 局部可观测量: Z \\otimes I \\otimes ...\\otimes I\n",
" \"\"\"\n", " \"\"\"\n",
...@@ -839,7 +837,7 @@ ...@@ -839,7 +837,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"以上都是我们定义的函数,下面我么讲运行主程序。" "以上都是我们定义的函数,下面我们将运行主程序。"
] ]
}, },
{ {
...@@ -876,19 +874,19 @@ ...@@ -876,19 +874,19 @@
"epoch: 3 iter: 100 loss: 0.0671 train acc: 1.0000 test acc: 1.0000\n", "epoch: 3 iter: 100 loss: 0.0671 train acc: 1.0000 test acc: 1.0000\n",
"epoch: 3 iter: 150 loss: 0.0849 train acc: 1.0000 test acc: 1.0000\n", "epoch: 3 iter: 150 loss: 0.0849 train acc: 1.0000 test acc: 1.0000\n",
"训练后的电路:\n", "训练后的电路:\n",
"--Rz(0.542)----Ry(3.456)----Rz(2.699)----*--------------X----Ry(6.153)--\n", "--Rz(0.542)----Ry(3.456)----Rz(2.699)----*--------------x----Ry(6.153)--\n",
" | | \n", " | | \n",
"--Rz(3.514)----Ry(1.543)----Rz(2.499)----X----*---------|----Ry(3.050)--\n", "--Rz(3.514)----Ry(1.543)----Rz(2.499)----x----*---------|----Ry(3.050)--\n",
" | | \n", " | | \n",
"--Rz(5.947)----Ry(3.161)----Rz(3.897)---------X----*----|----Ry(1.583)--\n", "--Rz(5.947)----Ry(3.161)----Rz(3.897)---------x----*----|----Ry(1.583)--\n",
" | | \n", " | | \n",
"--Rz(0.718)----Ry(5.038)----Rz(1.348)--------------X----*----Ry(0.030)--\n", "--Rz(0.718)----Ry(5.038)----Rz(1.348)--------------x----*----Ry(0.030)--\n",
" \n" " \n"
] ]
}, },
{ {
"data": { "data": {
"image/png": "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\n", "image/png": "iVBORw0KGgoAAAANSUhEUgAAATsAAAD5CAYAAABYi5LMAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/Z1A+gAAAACXBIWXMAAAsTAAALEwEAmpwYAAAg6klEQVR4nO3de5BkZ3nf8e/TPd1z2dnZXe0FARISBJEgAhGxYlyhKlBWIhb+QGA5sUQlEQmxcgE7sYOrrIoruEQRhEMZkypiI2M5wlU2UHIVJSokimyhsstczGKwiOQAkrCxFqHV3rU7l749+aPPLK3Zfp9z+jLbp6d/n6qunZm3z2W6zzx7znmffh5zd0REdrrKpHdARORSULATkZmgYCciM0HBTkRmgoKdiMwEBTsRmQlzgy5gZoeBjwJV4BPufteW8auAe4CDwEngn7r7U3nrrS6ueG33ob5jlYrF+xSMW7RoMBgu133GUMuGq83ZpkVPGHabOYZddpSEpnDZYDAvjSoajZaNVpu7zU6w3mCsE421m+E2O81G/5+vn8Gba6McDlRWrnBa64We62snHnD3w6Nsb5wGCnZmVgU+Bvwj4Cngq2Z2v7s/1vO0DwOfdPd7zezHgQ8C/yxv3bXdh3jJT32479jCUi1ctj6f/jXm6umT17laNTlWqcYnvdW59Hg4FkTCak5Qj8aHHcsz7LLt4I91lGWjsUarE6632U6Pd4JlW812epsbrXibG+llN9bSQWvtuY302Olnwm2eP/a9vj9f/bNPhssV0t6g9sq3F3pq488+cWD0DY7PoJexPwo87u5PunsD+BRw05bnXAs8lH39hT7jIjLFrFIt9CibQYPdi4G/7vn+qexnvf4c+Ins67cDu81s/3C7JyLlYjMT7Ip4L/AGM/s68AbgKND3XN7MbjezI2Z2pL12dht2RUTGyqY32A06QXEUuLLn+yuyn13g7t8nO7Mzs2XgZnc/3W9l7n43cDfAwqGX60O6IiVnZlRr9UnvxlAGDXZfBa4xs5fSDXK3AO/ofYKZHQBOunsHuIPuzKyI7BBlPGsrYqBg5+4tM3sP8ADd1JN73P1RM7sTOOLu9wNvBD5oZg78EfDuIuuuzc/x4r9xWd+xlxzcFS57aGU+GFtIjq0spH/9pWCmFmA+mK2dD2ZjF4KxWiW+q1CrpmdGo2VzVhuKZo8j7RGq6TTb6WWbwWxsNNsKsBHMuK4GM67nGumx5xrxbOyxs+lZ1adPryXHvndiNTl2+thKuM3jK/0nQRuP3hcuV0h2GTuNBs6zc/fPA5/f8rP/3PP1fcAYXlURKRsDbJT/PSdo4GAnIrNshs7sRGSGzdJlrIjMMDMqMzIbKyIzrHvPTmd2IrLTTfFl7HROq4jIhBiVSrXQI3dNZofN7Ftm9riZ/WKf8ZeY2RfM7Otm9oiZvaVn7I5suW+Z2ZuK7HlpzuyW5qu85qq9fceufVGcV3T5cjrP7sBS+v7CniDPbjHIhwNYmEvnn9WDfLh6UEVkLq/qSTTcSed7WTBGJ85NG61YUyBIX/BKusqNV9LvWTPnV2kEeXgbQW7fWrDi1Vb8+jxzLp1n94Ng7Mkgz+6RxbgKUKpKy7Gc3NFCbDyXsQUrKP0S8Bl3/3Uzu5ZuytvV2de3AK8CXgT8gZm9wt3TCZGUKNiJSPkZRmVuLBMUFyooAZjZZgWl3mDnwOaZzh7g+9nXNwGfcvcN4Ltm9ni2vi9FG1SwE5HixnfPrl8Fpddtec4vA//HzH4G2AX8w55lv7xl2a3Vly6ie3YiMoCBqp4c2KxqlD1uH3BjtwL/w92vAN4C/I6ZDR2zdGYnIsUZWLXwmd1xd78+MZZbQQl4F3AYwN2/ZGYLwIGCy15EZ3YiUpiNr3jnhQpKZlanO+Fw/5bnfA+4AcDMXgksAM9mz7vFzOazCkzXAH+at0Gd2YlIcWO6Z1ewgtJ/BH7TzH6O7mTFO73b4ehRM/sM3cmMFvDuvJlYKFGwm5+rcM0Ldvcdu+ayuMTTC3enU0/2zKdPXpfr6Tet7v07NG2qbJxPjtlGOm3AGumyPtaOt0kj3dXJW8GyrXRjF+/kHCO5qSkJOZUxwj+YuXRqRWV+MTlWn0sfBwCLtfSyPr+cHOss9T8uATaI00BWguPvsiCFJCohthaUnAI4drL/8VfNaSJVVHVuPGGjQAWlx4DXJ5b9APCBQbZXmmAnIuVnZmHr0jJTsBORgdiQBV0nTcFORAaS17S+rBTsRKQ4Q5exIrLzdUs8KdiJyE5nNrZZ3UutNMGuVq1weSKFJEotATi4lE5jWK6kK35Uzh9Pj62dCbdpq+nxzrnTybH2+XQzcF9Lp7MAdNbTKS0epaU002kpntORy4dMPclrylKpBykbQeqJ1dPd4iq74uo4lSCFpLJnf3psuX/Xu+4694XbnF8+mByLOrc12+k0meOr6VQigEcSVYAqYdmc4nRmJyI7npkmKERkRgz/UfzJUrATkYEoz05Edjwzo5pTxbusFOxEZCCaoBCRnc+gosvY0cxVLNkcJ6ocAXF6SfXsM+mx1ZPJsdbxp8Ntdk4dS461o9STc+eSY42z6dQSgOb5dHpJaz3dvKXTTL8+nUbQjIftTD1JH3qVWnqstiuoerKyFG5zfm+QerKSTj2p7r88PXbwReE28fTrt2/5BcmxtVb6NTiwFFdauWy5/9/RXM57UoSSikVkRqjqiYjMginOs5vOaRURmQij+0mMIo/cdeU3yf6ImX0je3zbzE73jLV7xraWc+9LZ3YiUtyYzuyKNMl295/ref7PAK/tWcWau183yDZ1ZiciA7GKFXrkuNAk290bwGaT7JRbgd8bZb8V7ERkANYtzV7gkaNfk+y+ja7N7CrgpcBDPT9eyHrRftnM3lZkz3UZKyKFDVgI4ICZHen5/m53v3uIzd4C3Lelg9hV7n7UzF4GPGRm33T3J6KVlCbYzVUs2W1puZaTs7WWzpcLc+l+8L302Ik4z651Ml0eau1EuozTxunnkmN5eXatMM8uXfanuRbk2TVzO9ANxXJuUEd5eLXFKM8uXe6rvjsnz25fOs9u8WC6vNZ8UD6LnO5s1Wr/nDeAWj3dNW9X0AltX9CVDGBvIg+vOqZZ1AFST0Ztkr3pFuDdvT9w96PZv0+a2cN07+eFwU6XsSJSmBnU5yqFHjmKNMnGzP4WsA/4Us/P9pnZfPb1AbrtFh/buuxWpTmzE5HyM2wsZ4gFm2RDNwh+KmuOvemVwMfNrEP3hO2u3lncFAU7ESnOxnc5nNckO/v+l/ss90Xg1YNuT8FORAozxhfsLjUFOxEpzKw7mTiNFOxEpDAzKzL5UEqlCXYGzCfSFeYr3vfnm6JOYO2oFFMw1ng2PQawdux0euzZU8mxjdPpEk/rZ9JlmgA2zqbHW+vp9JJWkHrinfi17eR0H0up5LTbi9IX5oLUk/mVdOrJ/EqQIgK01qMua8Ol4ETdzgAqu9PdxyqLe5JjS7vSaSm7g/JYAMsL25d60r2MVbATkRmge3YisuPZGGdjLzUFOxEpbFx5dpOgYCciA6mqB4WI7HSbHxebRgp2IlKY8uzGwMyoJ1JPbCOdrgFQaaQrVjSfO50ca585kRxbDyqXQJxesnosnQqzdiqdHrEejAE0zqdTJxrn0lVPorSUTk7qSTseTsqryj1Xq6bHFtKHZfN88HsGKTYA7cZw6SWVWrrKSH0lfQwBdIJjrLLnUHJsYSX9Ai7V068dwGJifBxXn7pnJyIzQ8FORHY8pZ6IyExQIQARmQnT/NnY6dxrEZmYasUKPfKM2Df2NjP7Tva4rch+68xORAob1z27UfrGmtllwPuA6wEHvpYtm06RoETBrmKkU09acTWQzlo69aRzPmp+k05piRrj5C0bpZesHk831dk4m04t6Y6nX4fzzXR1krWgckkzJ7Wk7cPlnuRl2S8E+7u4kU4hWYrSaIas0AJ5TX7SlU02Ti+H663uTx9/1Vb6OLF2+ljIu4qcTzyhMobckzHes7vQNxbAzDb7xqbKq99KN8ABvAl40N1PZss+CBwmp69saYKdiEyB8c3G9usb+7q+m7y4b2zhnrO9FOxEpDDDqBWvZ7edfWMHpmAnIoUZ+Z+O6bFdfWOPAm/csuzDeTuj2VgRKc6gUrFCjxxD942l237xxqx/7D7gxuxnIZ3ZiUhh3TO7yfaNdfeTZvZ+ugET4M7NyYqIgp2IDGQcs7owfN/Y7Of3APcMsj0FOxEpbMB7dqVSqmCXqpMV5RwB+HqQZxeMNc+n85waZ9P5cBB3Aovy4aJcurXTcYmnM0Fu2npQqinKs2tMqMTTWnBPZ72TvpXc9nSenZ2NN1oNykrVl6MyTuljITqGIM4BZWMtOWStKM8u3WEN0sU1x1LiyYy5nM5xZVWqYCci5aczOxHZ8Yzx3bO71BTsRKQ41bMTkVmgMzsRmRm6ZyciO56ZUdNs7Gii/B1rpTtKAXQ20tP/ndUobSA99d9ajVMKoi5Xw46da8Ulis4HKSTRsmtB/kh+6sn2lHiqB/d9hk53WY2Pk6hrWdSdrb2eTgOJxgC8kT6OojFrp/enaumSUwD1RDAaxwlZ9zJ2DCuagNIEOxGZDuP4uNgkKNiJSGGaoBCR2WAwpbfsFOxEpLgBi3eWioKdiBSmy1gRmQ26jB0DC/7H6KQrXQB4UCGi3Qy6UQVj7UZc7r61HqSXrKXXuxGsN0oRyRuPxyZR9SRvvemzgyhpNRpbyMmJ2BV0JmsFY9H72VqPO995kBblUUqVp9+zvGCT+juyMXUXm9YzuymN0SIyKWbFHvnriZtkZ8/5J2b2mJk9ama/2/Pzdk8D7YvKufdTnjM7EZkKlTGkJxdpkm1m1wB3AK9391NmdqhnFWvuft0g21SwE5HCuk2yx7KqIk2yfxr4mLufAnD3Y6NsUJexIlJcwUvYApexRRpdvwJ4hZn9iZl92cwO94wtmNmR7OdvK7LrOrMTkcIMG+QydtQm2XPANXR7xF4B/JGZvdrdTwNXuftRM3sZ8JCZfdPdn8hbmYhIYQNMxo7aJPsp4Cvu3gS+a2bfphv8vuruRwHc/Ukzexh4LTAdwS7sWhRMw0M8hR+ll3Qaw6eetBvpfYqWjVI9mjkVRqJlt2MMtq/hTiRKo6lXRvhdgvdl2LHoGALwIO3Hm0HFlCDdqpLz4m53HtyYqp5caJJNN8jdArxjy3M+C9wK/LaZHaB7Wftk1hh71d03sp+/HviVvA2WJtiJSPld4ibZDwA3mtljQBv4BXc/YWZ/H/i4mXXozjvc1TuLm6JgJyIDGVdOcV6TbHd34OezR+9zvgi8etDtKdiJyECmNYVDwU5ECuumlUznx8UU7ERkICrLLiIzYUpP7BTsRKS4cc3GTsJUBDvLybOjE+fEpXgnvd5OToJZJ8qfCvK9om5deTlt8bLDrTdvmzmvfFru7xKNDfe75O1rJ3hfovcsOk6iPLq8ZaeS6TJWRGbElMY6BTsRKU59Y0VkZij1RER2PJ3ZiciMMM3GisgMKNhfooyG+phbXqMMM5s3s09n418xs6tH3tMhebuTfIjIYMy98KNsBg52PY0y3gxcC9xqZtduedq7gFPu/nLgI8CHRt1RESkJ7xR7lMwwZ3YXGmW4ewPYbJTR6ybg3uzr+4AbbFqncETkecw7hR5lM0ywK9Io48Jz3L0FnAH2D7ODIlIm3v3EUpFHjhH7xt5mZt/JHrcV2fOJTlCY2e3A7QBXXnllzrNFZOLcx3KJOkrfWDO7DHgfcD3dDyZ+LVv2VLTNYc7sijTKuPAcM5sD9gAntq7I3e929+vd/fr9Bw4MsSsicqmN6TK2yO2wVN/YNwEPuvvJbOxB4DA5hgl2FxplmFmdbqOM+7c8535g89TyJ4GHshLLIjLtxjNBMUrf2CLLXmTgy9iCjTJ+C/gdM3scOEk3IA7NLScmV6rJIQtaLVklPZbXwakSrje9bJSQmdeRK142Grv0/8/k/y7R2HCvUd7/3JXgfYnes+g4iY6vvGWn00CXsdvSN3aA5S9a2cAKNMpYB/7xsDslIiXlDBLstqtv7FG6AbB32Yfzdman/bcjItvKsXar0CNHkdthnyULar19Y/lhi8V9WQ/ZG7OfhfRxMREZzBhmY0fpGwtgZu+nGzAB7nT3k3nbVLATkeLcu4+xrGq4vrHZ2D3APYNsT8FORAZTwk9HFKFgJyIDKeNHwYpQsBORAYznExSTUJpg5wSdo3Ly7Gyulhyr1NK/YqWeHsvLn6rW0+PVejrvrx7kc9VyaiVEy0ZjbR++BkNe97GUvDy7YX+XYccgzo2M3rNoLDqGICfPs1ZPjkW5pZ2c1m3bWr3MHTq5M62lVJpgJyLlZ+gyVkRmxZT2wlWwE5EBjC/15FJTsBOR4gb7uFipKNiJyAAc0wSFiMwEndmNyKGTuhdQyZnen0tP4Vej1JNgrLYYb3NuIZ3uEi07fz6dxrCYkzPQDFJI4vSSIOUip/xTe8j7M3m9RaM0kcUgb2UxSOWIlgOYW0i/L3EqUTAWHEMANr+QHgtSpjw45vNSS1J/R2MpKeleqOR6GZUn2InIVHDNxorIzqczOxGZBY6CnYjsfO6ON5uT3o2hKNiJyACm9zJWZdlFpDh3vNMu9MiT1yTbzN5pZs+a2Teyx7/qGWv3/HxrOfe+SnNm50ArMcnjwRQ9xNP7laWl5Fht12JybG4pvc7ushvBWJCWspZOyFxuxgfI8BVI0gs2OpNJPRk2vWRXsNzSfHw4z6+kU5TmV+aTY7Vd6WNhLjiGAKwepJ4EY15NH0PtnAOhkchNGduHvMYwG1ukSXbm0+7+nj6rWHP36wbZZmmCnYhMAy901lbAhSbZAGa22SR7a7AbG13Gikhxm7OxRR6xoo2ubzazR8zsPjPrbb24YGZHsubZbyuy6zqzE5HiBpuNHbVJ9ueA33P3DTP718C9wI9nY1e5+1EzexnwkJl9092fiFamYCciAxhoNnakJtmbbRMznwB+pWfsaPbvk2b2MPBaIAx2uowVkeI2Pxs7+mVsbpNsM3thz7dvBf4i+/k+M5vPvj4AvJ4C9/p0ZiciAxnHZ2MLNsn+WTN7K9ACTgLvzBZ/JfBxM+vQPWG7q88s7kVKE+zcoZlIg/BqOmUAoLKwa6ix+ko6LSUaA1hYXU+OtdbT6SXtxvAzWZWz6XSXejOdkrFQSR+ceekswx7WeZcMw1Y9idJLFval00cgTi+Jxuq7g+MkGAOoLKaPP+bTaSteS6eltNbjd6WRyOEaT4Hh8SUVF2iSfQdwR5/lvgi8etDtlSbYiUj5uTve0sfFRGSnUyEAEZkN0/vZWAU7ESnOwdsKdiKy47n6xorIjNBlrIjseO50NBs7GgcaiaQvr+WU0VlaTo5Vdu9Njs3vPZ0ca5xdDbfZaaRz6TpB+6dKkENWrac7j0Hctax+Ln0A7g7y/lrbVlYqHp+rpX/XqAvYsGWaAJYOpI+jhf27g7E9ybH63pVwm5Xlvckxr6Vz9NqWfg02Wul8S4DVRC5nsnvfINzxvPZmJVWaYCci5eeOgp2IzAJXK0URmQE6sxORWeDutBuaoBCRGaDLWBHZ+TQbOzp3T6eeLKdTSwB8Pj39X9mzPz12/rnk2K7Lhz9Vt6A71tzCuWAsfjsa59P71Aq6lo1Scspzuo+lWFDCCeI0m+h1qC+nu27lpZ5E6SVLh/YFY3uTY9WVy8JtRsdfZz5d/mkt1WoPWM1JF1pLpEWNJfWE6b1np0rFIlKYe3c2tsgjz4h9Y28zs+9kj9uK7HtpzuxEZDpESfNFjdI31swuA94HXE/38whfy5Y9FW1TZ3YiUlzH6TRahR45LvSNdfcGsNk3tog3AQ+6+8kswD0IHM5bSMFORApzGNdl7Ch9Y4su+zwKdiJSXDYbW+RB1je253H7gFv7HHC1u7+G7tnbvaPsuu7ZichABpiN3a6+sUeBN25Z9uG8nSlNsOt4ekp9tRXv5vJiuipFde+h5NhcUKrGKvFJ7+56OgWitivdGaq+kk43mN97Ptxm83zQ0Szodhall+SlnnSGLHsSVXeBvNST9Gs7F762caev+b1B6snBveltHrg8OVY9dEW4TXYfSA55cNyuNtKv+5mN+H7YmdX+x3V7yDSi53HojCep+ELfWLrB6xbgHb1PMLMXuvvT2bcX+sbSbb/4X8xsM1/oRvp0IduqNMFORMrPGU9S8Sh9Y939pJm9n27ABLjT3U/mbVPBTkSKc6fTHM9nY4ftG5uN3QPcM8j2FOxEpDhVPRGR2aDPxorIDHAfzycoJkHBTkQGoErFI2t1OhxfbfQd278U7+biUrpiBZ30NH3V0uklVk+nOADYrnSllaXlE8mxhYPpSivN59IVUQBaUerJev/XDqDTTL8GeQ2Ph/1fvBJUfgGwajr1pLqQbqoTpfXUluL3rLqyNz0WVCep7k+nnrD3BeE2O8sHk2Orlq7Sci6YBDi1Fk8QnE6lngzbPalXBzo56UplVZpgJyLl57guY0VkBvjwNQ4nTcFORAYy7KdqJk3BTkQKU99YEZkN7rjO7ERkx/P84hFlpWAnIoU50NEExWiaHefY+f65Yod2xV2jakEnq7270nlOPpdeb6WeLsUEMLcnvV7W0rl0ndX0WG0tLvHkjXSenW+spceCUlbeyflfetgE0pwSWVZJ59nZXLrEky2m3xdbiN+zSpAbact7k2Od+XRpqM6udH4ewGp1MTl2YjWd//iD59J5k8+c2wi3efy5/uOtsZR40mWsiMwI5dmJyI7XnY3VmZ2I7HRTHOzUcEdEinOn3WwXeuTJa5Ld87ybzczN7Prs+6vNbK2nefZvFNl1ndmJSGHOeD5BUbRJtpntBv498JUtq3jC3a8bZJs6sxOR4gZrpRgp2iT7/cCHgHQqQkGlObNrtDt871T/9InlerybzU46hWStmV52ub6cHFtcSacbAMxX0v+7WWM1PdZKpw1YK34/K0EKSVTKyoIxfEIza0F5La+mSzx5kLLitbjEU7uWTgPxeroz2Yant3muGb9+Z4JyTFF6yROn0sfQk8fiUmCrZ/sfR+OaRR3TPbt+ja5f1/sEM/u7wJXu/j/N7Be2LP9SM/s6cBb4JXf/47wNlibYiUj5dSsVFw52B8zsSM/3d7v73UUWNLMK8KtkHcW2eBp4ibufMLMfAT5rZq9y97PROhXsRKS4bIKioFGaZO8G/jbwsJkBXA7cb2ZvdfcjwEZ3d/xrZvYE8AqgN7BeRMFORIobX+pJ2CTb3c8AFzqMm9nDwHvd/YiZHQROunvbzF4GXAM8mbdBBTsRKcwZT4mngk2yU/4BcKeZNYEO8G/UJFtExmuwe3bxqnKaZG/5+Rt7vv594PcH3Z6CnYgMQIUARrbWaPN/nzrTdyyvWsPx1XTKwYGldBrDnvkgLWU+nW4AsFRLj9er6VSYWiW9r3P1veE2g9VSDSq/BEMEQ9myec/or+PxexaNRm93dCw0c46TVjM9vr6evjRbHaHT1/FEpy+Iq5d85wfp6jj/72g46ciZ4/3TVtqtcVx+5r+3ZVWaYCci5edAQ/XsRGQWtHVmJyI7nQNTestOwU5EinPXmZ2IzAid2YnIjue4zuxGtdFo88Rfneo79syZuBrIvl1BeslSunnL3mBsMafSylI9nXqyGIzVq+lqH7VqnOYRpYFETYeitJQ825V60g5m9KLJvugPrZlzyrHeSn+mcy1oD/jcerpqzJnVdOUSgBPn0uOpxjiQrlwC6dSSTaefPtr3560ghaao7mzsyKuZiNIEOxEpP92zE5GZoXt2IrLjdVNPpjPaKdiJSGHKsxORmeCuj4uJyIyY1svYgbqLWdd/y/o8PpI1xOj3vJ/Kxh81sw+NZ1dFZNKcbrXMIo+yGfTM7s10SyBfQ7cT0K9zcUeg/cB/BX7E3Z81s3vN7AZ3/8NoxY21Df760cf7jtUW0l3AAKr1dNeo6lw6560yl471efllQXMsLCq3FIxFy+WxIfPhov3ZTp0hL4U8OKvwnHVG2+y00mPtoDJvqxF0bgPajf4d8wCa6+kuYc3z6TJOG8/FRXk3zh7vvy/r58PlihlfUrGZHQY+SrdS8Sfc/a7E824G7gP+XtZ/AjO7A3gX0AZ+1t0fyNveoH1jbwI+6V1fBvaa2Qu3POdlwHfc/dns+z8Abh5wOyJSQpsTFEUekZ4m2W8GrgVuNbNr+zzvoibZ2fNuAV4FHAb+e7a+0KDBrl+vxxdvec7jwN80s6vNbA54G8/vIiQiU2oz9aTII8coTbJvAj7l7hvu/l26MedH8zY4aLDL5e6ngH8LfBr4Y+Av6Z5qXsTMbjezI2Z2pLMRN/4VkcnbnI0t8iDrG9vzuL1nVbknTr1NsrfsRpGTrovk3rMzs3cDP519+1XiXo8AuPvngM9ly99OIthlDXPvBqhddtV0TvGIzJgB8uyivrGhnCbZQ8k9s3P3j7n7de5+HfBZ4J9ns7I/Bpxx96f77Oih7N99wL8DPjGuHRaRyRnjZewgTbL/Evgxuk2yry+wbF+DzsZ+HngL3WvkVeBfbA6Y2TeygAjwUTP7O9nXd7r7twfcjoiU0Bg/QTFKk+w14HfN7FeBF9HNDvnTvA1aNJV/KZnZs8BfZd8eAPrPn0tZ6T0rp9735Sp3PzjKyszsf9MThHIcd/fDwbreAvwaP2yS/YFUk+zeYJd9/5+Afwm0gP/g7v8rd9/LEux6mdmRYa/1ZTL0npWT3pcfGvtsrIhIGSnYichMKGuwu3vSOyAD03tWTnpfMqW8ZyciMm5lPbMTERmriQY7lYyaPmZ22My+lb1nv9hnfN7MPp2Nf8XMrp7Abs6UAu/JVWb2h9nf0MNmdsUk9nPSJn1m11sy6na6JaOep6dk1A3u/irgcjO74ZLupQCFK1W8Czjl7i8HPkL3Q9yyTQq+Jx+mW63oNcCdwAcv7V6Ww6SDnUpGTZcilSpuAu7Nvr4PuMGGLbYnRRR5T64FHsq+/kKf8Zkw6WCnklHTpcj7deE57t4CzgD7L8nezaYi78mfAz+Rff12YHd2xTRTJh3scg1SMkpE+nov8AYz+zrwBrqfRZ25v6FL3nBnO0tGybYrUm1i8zlPZWfie4ATl2b3ZlLue+Lu3yc7szOzZeBmdz99qXawLC75mZ1KRk21C5UqzKxOt1LF/Vuecz9wW/b1TwIPuZI5t1Pue2JmB7L6cAB3APdc4n0shUlfxn4eeJLufbnfpBvIgG7JqJ7nfdTMHgP+BLhLJaMmI7sH9x7gAeAvgM+4+6NmdqeZvTV72m8B+83sceDngYtSIWR8Cr4nbwS+ZWbfBl4AfGAiOzth+gSFiMyESZ/ZiYhcEgp2IjITFOxEZCYo2InITFCwE5GZoGAnIjNBwU5EZoKCnYjMhP8PWyDVapqkw5MAAAAASUVORK5CYII=\n",
"text/plain": [ "text/plain": [
"<Figure size 432x288 with 2 Axes>" "<Figure size 432x288 with 2 Axes>"
] ]
...@@ -902,7 +900,7 @@ ...@@ -902,7 +900,7 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"主程序段总共运行了 35.94043445587158 秒\n" "主程序段总共运行了 23.798545360565186 秒\n"
] ]
} }
], ],
......
...@@ -65,8 +65,6 @@ ...@@ -65,8 +65,6 @@
"import paddle\n", "import paddle\n",
"from numpy import pi as PI\n", "from numpy import pi as PI\n",
"from matplotlib import pyplot as plt\n", "from matplotlib import pyplot as plt\n",
"import warnings\n",
"warnings.filterwarnings(\"ignore\")\n",
"\n", "\n",
"from paddle import matmul, transpose\n", "from paddle import matmul, transpose\n",
"from paddle_quantum.circuit import UAnsatz\n", "from paddle_quantum.circuit import UAnsatz\n",
...@@ -202,7 +200,7 @@ ...@@ -202,7 +200,7 @@
}, },
{ {
"data": { "data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAYIAAAD4CAYAAADhNOGaAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAoeklEQVR4nO2df6weV5nfP49fY2kNlCbXJjg/fJ1IESJZAU2uIgIrlnQJm3hLA9UiQZ3Uu0W1fAXSsupu5chSFRVZZam620BJu27Wkpt7C6pU2ETUaQjZRduCFriJEsdp6vwiP4xT4jiwLAIV4pz+MfNyx6/n98yZOfPO9yMdvfN7nnfmzHnOOc9znmPOOYQQQoyXDX0LIIQQol+kCIQQYuRIEQghxMiRIhBCiJEjRSCEECNnY98C1GHLli1ux44dfYshhBCD4sEHH3zZObd1dvsgFcGOHTtYW1vrWwwhhBgUZvZc2nZ1DQkhxMiRIhBCiJEjRSCEECNHikAIIUaOFIEQQoycVhSBmR0ys5fM7FjGfjOzz5nZU2Z21MyuSuy7wcyOx/v2tSGPEHVYXYUdO2DDhuh3dbVvicIk9OcUinyhyFEK51zjBLwXuAo4lrF/J3AvYMC7gG/H2yfA08BlwCbgEeCKovtdffXVbsisrDi3uOicWfS7stK3RGJlxbnNm52D9bR5s97NLL6eU1vfRCjvMRQ5ZgHWXFoZnbaxTgJ25CiCPwU+llg/DmwDrgXuS2y/Fbi16F5DVgRpGQScW1joP5P4JHTlt7h47juBaLtYx8dzarPQDOU9hiLHLFmKoCsbwUXAC4n1E/G2rO3nYGZ7zGzNzNZOnTrlTVDf7N8PP/3pudtPn4Y9e9ptPobSNF1djf7bc89Fn8Nzz7X/X5vy/PPVto+V51KHIzV7TmnfxE9/Gm2vSijvMRQ5ytKVIrCUbS5n+7kbnTvonFtyzi1t3XrOCOnBkPUhQf3Mn0ZIhW+bH7ovtm+vtt03oSjxJKurYGlfLPWe0/Q/tqlcQnmPochRlq4UwQngksT6xcDJnO1zS9aHNKWtGkNIhe8QakcHDsDmzWdv27w52t41ISnxJPv3R/LMYlb9OSX/YxZ1Cs2677GJ4k07N6T8VIq0/qI6iXwbwW9xtrH4O/H2jcAzwKWsG4uvLLpXGzaCvvqs0/oN0/oQm8pnln59s5b/UAlC7S+dJRQ7hq/n5StPQXVZsv5jG4bV5WXnJpPoOpNJtJ5HExtF3rmh5Kck+DQWA18EXgR+QVTL/ziwF9gb7zfgC0QeQo8CS4lzdwJPxPv2l7lfU0XQp0W/TOZvQ76QCt9QPShCxYcSDy1P5SmVrr2GmvyvkL6zMnhVBF2npoog6+VNJv6198JC+r03bFi/ZxuZq6/CN6sWFGLtKFR8FC6h5SlfBWid65ZRvFn5t8m5fSBFkCCvNuK70FxZcW7TprPvtWnT2fdqq0bYdQZUzb8dfDzH0PKUr7xS538WKY88WZuc2wdSBAmK+id9N++KPqahNTenDFXuEGlbiYf4bnxUVOr8z6LCOu+aTc7tAymCBFmDutJSH4RWiyhLSAZqcTZt56lQujtm5Vhervc/8/5PUb5ucm7XSBHMsLKy7lmQlSaTxrdpJF8IH1oVQqv9iLMJvVunLTmWl8NpTYX2TUgRpFDGViDKE0oBIfwSSuHWlRy+3Ev7IEsRjDoMddGAlcXFbuToCt+jVXftgoMHo+dmFv0ePBhtF/NDKAMEu5KjSb4ezDeRph1CT221CPJsBfNWkw2tZiKGy9haBHkMrQsXtQjOJamtASaT6DdYrd2AkEJOiGETSviEvuUINRRIHSxSEsNiaWnJra2t9S3GoNiwIcqss5jBa691L48YNqurUSXi+eejLtYDB/qpOPUpR1bAvMVFePbZbmSoipk96JxbOme7FME4GGKmFSJkhli5ylIEo+4ayiPEMMBN6LsZLcS80STUdGjlixRBCl30/VXJCG1kmsF4LwgxEJqEvA7OtpBmQQ49+Z6q0rc3QhUPHnn7CNE+dbx90s6pc50+vZ3QgLLy+B4WXiUjhOAiJ8Q8UaZy1VboijT6DDuRpQhkLE7Bt2G1ipFpiAYpIUKm6Puedt0k3a3N0r/DOmVCn44bXo3FZnaDmR03s6fMbF/K/j80s4fjdMzMzpjZ+fG+Z83s0XhfEK5Avg2rVYxM82SQEsIXVfJ60YjktDE3WfXlOqOYg3TcSGsmVEnAhGh2sctYn27yipzjPwj8RWL9WWBLlXv67hpyzu+IwS5sBLItiLFQNa8XdbeWiUHWtIu2v6lyPdkIgGuB+xLrtwK35hz/X4B/llgPUhH4pkpGGJpBSoguqZrX684hMKsghlix8qkIfhu4M7F+C/DvM47dDLwCnJ/Y9j3gIeBBYE+Ze86DIvBNaHHQhfBFnbyeV7kqCm8N6yHshxBfKEmWImjDRmBpPU4Zx34Q+KZz7pXEtvc4564CbgQ+YWbvTb2J2R4zWzOztVOnTjWTeAQ0sS0IMSTq5PVduyLD7GuvRb/J8TRZY27uuGO9f//MmejYIMYAtEAbiuAEcEli/WLgZMaxHwW+mNzgnDsZ/74EfAW4Ju1E59xB59ySc25p69atjYWeB/IMZEEapITwgI+8nqUo6gZv7HoAaWXSmglVErAReAa4lHVj8ZUpx72JqFvo9YltrwfemFj+FnBD0T3VNVTPF3pITVghqtBGXi9zjbrdUKEMIMXngDJgJ/AEkffQ/njbXmBv4pjfAb40c95lseJ4BHhsem5RkiJwbmEhPUPKGCxERFWHjDIFcB0njJAGkHpVBF2nsSuClZX0zCJjsBARbbuU1r2uc+VbEV1811mKQEHncgh1QFZef6SMwUJU78vPG2SWLAf274fdu6sFbyxjzJ6OZq56jdZI0w6hp64GlIU6ICtvwEsI8gnRN1X78rNaBAsLzcuBtLJk06bo2tNuq6yu3q5sBGoRZBDy1I5ZtYOFBYWVFgKqu5RmeR5B83Jg1h11YSEq4k+fjn6fey5azqKLcPFSBBkUxSPpk6xMe/vt/cgjRGhUdSnNGjvwyivpx1ctB5LuqG94A/ziF+XOW1zspnInRZBBXo2ib9uBJpkRIp8630ja2AEfAzPLKpFOx/2k9ReFnvq0EbQZl1wIETY+bIV59og0d9c2xwMhG0E1smoUR46EazsQQrSLj9Z3XtduskUCsGUL3Hyz/2ktpQhSmHb93HJLtH7XXetNxZBtB0KI9smLSwTVu4qzlAusX2fLFvjd3003IvuoeEoRzFA0sbSCuQkhptSdiH5WucDZ1zl9Ot+g3HbFc5SKIE+DF7mNthXgqm+DsxCiOW25maddJ4/WK55phoPQUxNjcZHxp8xAlKbGm5AHqwkhytPWvB9VZkVrUlagyesjiiaO7mJi6T4nrxZCtEdb33LWdWZZWIiMynWN1V4nrx8SRcbeLuL4y+AsxHzQVnmRdp1Nm6KCf2pQXlmBl1/2M15odIqgyNjbxWAtGZyFmA/aKi/SrnPoUFTwZ3krtUpaf1HoyaeNoE2ybAmyEQgxDkKbHArNR7BOFy+nqLAPLYMIIdolxApfliJoxVhsZjcAtwMT4E7n3Gdm9r8PuBv4Xrzpy865f1Xm3DSaGIu7QgZhIcZN1TJgdTVyI33++aib+MCB9ruDsozFG1u48AT4AnA90UT23zWze5xz/3vm0P/pnPsHNc8dHDIICzFuqpQB04Fp07EE04FpMJzoo9cATznnnnHO/Rz4EnBTB+cGjQzCQoybKmVA3sC0LgaftqEILgJeSKyfiLfNcq2ZPWJm95rZlRXPxcz2mNmama2dOnWqBbH90oUbqhAiXKqUAVmth2nLYAhB5yxl26zh4SFg0Tn3DuDzwJ9XODfa6NxB59ySc25p69atdWXtDM0ZIMS42bUrmt94MonWJ5NoPa0MyGo9TCbdRDtuQxGcAC5JrF8MnEwe4Jz7sXPuJ/HyEeB1ZralzLlDpihqoRBiflldhcOH4cyZaP3MmWg9rTaf1XqYnjtLiEHnvgtcbmaXmtkm4KPAPckDzOwtZmbx8jXxfU+XOVcIIYZIlYB0WT0Ii4vp127b1tjYa8g596qZfRK4j8gF9JBz7jEz2xvv/4/AbwPLZvYq8DPgo7FPa+q5TWUSQoi+yYodlLV91670XoOkNxH4sTWOLuicEEJ0wcaN6V07kwm8+mr567Q5vsDbOAIhhBDnktW/n7U9i6yWQpuMLuicEEJ0QVb/ftb2PpEiEEIIDwxpLJEUgZhv2hqWqblFRUWGNJZIiiAFffNzQt2ZxX1dR4yOoYwlktfQDLPBnyBqzoWqyUUOvucRVChZMTA0VWVJqgwCEYFTNfxjVjNQoWTFnCNFMIO++QCp21dXNvxjUdePQsmKOUeKYAZ984HRpH++rNtGUTNwSO4fQtRAimAGffOB0aSvrqzbRlEzsG33D3kjiAI6zyJp81eGnprOWVyE5hMOCLOzJ32dJrP27rG4mH6PxcX27jElxIlsRVD4zCJkzFmsFkEKQ3H5GgVt9NUVVa+aNAOrVt3kjSAK6CWLpGmH0JPvFoEIiKbVo7LnZzUD85qHdWTrooUjBo3PLEJGi6D3Qr1OkiIYGU366pp0+xQV9HWu3WU3lBgkPrNIliJQ15AIh6xuliZ9dU38gYva6HWuLW8EUUAfWUSKQISBrzAOTWwMRQV9nWsPKQCN6IVeskhaM6FqAm4AjgNPAftS9u8CjsbpW8A7EvueBR4FHiaj2TKb1DU0h/hqDzexMRTJJA8gMTCyytjGLQIzmwBfAG4ErgA+ZmZXzBz2PeDXnXNvBz4NHJzZf51z7p0uJQaGGAm+hnQ3qV4VtdGzrg3NncA11kB0SZp2qJKAa4H7Euu3ArfmHH8e8P3E+rPAlir3VItgDgnBiJpmlK5qqG6jlaCWhvAEHo3FFwEvJNZPxNuy+Dhwb1IXAV8zswfNbE/WSWa2x8zWzGzt1KlTjQQWAdK3ETXLRgHVDNVtOIG36UiuloUoQ5p2qJKAjwB3JtZvAT6fcex1wOPAQmLbhfHvm4FHgPcW3VMtgjml6yHdyftNJu20SNpwAs+6BlSTpUzLQsPoRwUeWwQngEsS6xcDJ2cPMrO3A3cCNznnTicU0cn49yXgK8A1LcgkhkiRm2jV2m3a8dNtZnDLLestgKwZxavaKNoYCZ11rFm1Gn1Ry0IT7ogpadqhSgI2As8AlwKbiGr1V84cs53Io+jdM9tfD7wxsfwt4Iaie6pFMEDq1jyn501r1WX7zdNqw5s2Ofe612XXtotaBGX+Q1s2gqxWQZUWSlHrJAS7jOgUfI4sBnYCTwBPA/vjbXuBvfHyncAPiVxEH54KA1wWK45HgMem5xYlKYKBUbdwTDuvbKGVVchVSUkZq/yHNrpbsmSq0sVUVNAr3MXo8KoIuk5SBAOjbs2zTGGeVWjl9bOXSRs2nF2Ad117LnO/IoWzvJx+jeXlfv6T6J0sRaCRxcI/dccIpM0TPIuvmYRee+3s9a6nrsvyotq5M93Gkda/f+RI+rWn2/v21BLBIEUg/FO3sJ5M8vfnFVoHDkSFZROS7ppdT12XNlht9244fHhdQTp39jmzLqZdT7gjhktaMyH0pK6hgVHXRlBkG2hy/rQ7Jav7ZLbbKYRBXlW7ytT1I2ZAXUOiN+rWPBcXs7eXGdyVd/7URfWOO2BhIf24ZG0/hNpzmW6opMzq+hFlSdMOoSe1CEZCl5PS9F3bL0NRi6DKhDtilCCvITFImhZkZc8fQoGZprCm3lFdyjyEZyVSkSIQ88VYC6O+//dQWk8ilSxFYNG+YbG0tOTW1tb6FkP0xTQ0QjJ8wubN8njxzepq5LmUFo5jarcRQWNmD7qUcP8yFovh0WZ0TlGOqfJtKyaTCAopAtE/VYPJdT24a54p++zTlG8S5xTmesBIEYh+qRMBs+vBXSHTZL6BKs++jJJV9NLhkmY4CD3JWDxH1Bn0NDSDpS8Db9FzKLpvlWdfJYifBqwFC/IaEkFSNwJm394zZfGptPIK8jL3rfLsy0SCLfvuRG9kKQJ5DYl+2bEjPbjcvHih+Px/GzacG28IopHP27cX37eqbKurka3g+eej6//kJ3D69LnHzcu7m0PkNSTCZN7DIPg0bOfZSsrct+qzn51B7vbb5/vdjQgpAtEvfcbw6WJid5+G7byCvMx9qzz7tGcVQvwl0Q5p/UVVE3ADcJxoOsp9KfsN+Fy8/yhwVdlz01IbNoKhdDELT3Q1sbtvw3aWjG3ed2jGeZEJvozFwIRoisrLWJ+z+IqZY3YC98YK4V3At8uem5aaKgLla1HoMdN2QdpHraMtRTaZ5D8rMRiyFEFjY7GZXQvc5pz7zXj91ril8a8Tx/wp8A3n3Bfj9ePA+4AdReem0dRYPO/2SVGCPEPra68pk0B6KI8k02clBoNPY/FFwAuJ9RPxtjLHlDkXADPbY2ZrZrZ26tSpRgJrYKoo7ENvkkm6sD10QdFo4jEO4JtT2lAEafMBzla1so4pc2600bmDzrkl59zS1q1bK4p4NhqYKgo9ZupmkjojpUMlT+nJO2iuaEMRnAAuSaxfDJwseUyZc1tn3j0WgyWkmnKRx0vdTDJPAfGylN5kEj0rCOd9imakGQ6qJGAj8AxwKesG3ytnjvktzjYWf6fsuWlJXkMDpKrxNYQXtLy8biidTKL1IuqOlA6RvHcmj4tBgs8QE0ReQU8QeQDtj7ftBfbGywZ8Id7/KLCUd25RUoiJQMkrvKvEtQmhkKkrw7xNGJ/1TrP+52Si2lXAeFUEXScpggApKjir1JRDKEzryhCKEvPdmsp6n2ohBI0UgfBLUcFZpWANoXuliQx9dmt1pYjKRiMdaktoTslSBAoxIdqhyN2yivE1BLeuIhnyDN+zMXm6DLmQZay++eZ2Dbpp7zMN+WQPgzTtEHpSiyBAytT4y9aUQ+leqWIonbYg+u4bL+qy8RXeQqOPBwHqGhJeabvwDsFrqKqhNIS+8TJdNgsL7d83BOUtCpEiEP4JofDugjKG0r5qwmUnkPHxbsby/gdMliKQjUC0R599411SxlbRV994cqBcHkUD3KoM/psee8st0fpdd833+59DpAiEqEoZQ2mf8UqmCnllJfuYPEVVJUzGPIXUCIwuB+JLEQhRxOwXCWfXum0mZFaX8UqKvJcWFtLPy1NUVcJkzFNIjYDoXL+m9ReFnmQjEF5I6+PuagKbuvKWka2qETfPBjL7P0MY8zGH+BpTiSavFyKHtNj7mzfDr/xKuBO0l50zYXbS+QMH8vvvs65rFpVHU0J/PgOmaLqMumjyejEe6nSuZnVxpBVyEMZAqbJzJlQ14qfZQGaVAKw/L4XybZ2ux1RKEYj5om7natWCPYTJK3yVFmkhurN6Dl55RRPYe6DzUPlp/UWhJ9kIRCZ1O1ezzltYaDZQqo79IMQR2CEEAhwByVe/sBClNk1PaECZGAV1jZdFISXqGIPrFNTLy+f+hxDmbdDIYe908YilCMQ4aFJzbbtQrSpLnhdOCDVvjRz2SheNrixFIK8hET5VvF6yvH/66Leu6vqR5a2Td46YG3x5Cp19LQ9eQ2Z2vpndb2ZPxr/npRxziZn9pZk9bmaPmdnvJfbdZmbfN7OH47SziTxiDqlq/C2ai7hLqhpz8wzWIRinhVf6jL7e1GtoH/CAc+5y4IF4fZZXgX/unHsb0XzFnzCzKxL7/8Q59844HWkoj5g36oxcDSXmUVXXj6wv3kzumCOgc0+hBE0VwU3A4Xj5MPCh2QOccy865x6Kl/8WeBy4qOF9xdAp6+tf1lc+FJL/a/9+2L27fOsky39/795oOet5dRmURnij18ZsmuGgbAJ+NLP+w4LjdwDPA38nXr8NeBY4ChwCzss5dw+wBqxt3769PeuJ6J4q7hFDcltsw+2japgLefPMDV3Y4qnrNQR8HTiWkm6qogiANwAPAv8ose0CYELUMjkAHCqSx8lraPhUKdyHVND5Ulp51x2SohSZ+PBeTiNLETTyGjKz48D7nHMvmtk24BvOubemHPc64KvAfc65P8641g7gq865Xy26r7yGBk5V94iqsXL6wpfbR951wb+rifBOlsPYwgL87GftOcH5ijV0D7A7Xt4N3J1yYwP+DHh8VgnEymPKh4laGmLeqeIeMRQlAP7cPvKu26eriWiNLJPX6dPdRPluqgg+A1xvZk8C18frmNmFZjb1AHoPcAvw91PcRD9rZo+a2VHgOuD3G8ojhkBZ94g2grJ3aUj15faRd90qz1IG5WCpqrdb95VI6y8KPclGMAeU6fhs2v/dh33Bl8Uv77pF9xySnWWkZL2ihYV2TUBoZLEYHFl945C9PUnZeP3zjp7DIEjrBYV2B8prPoIU1FoOnLwBVmVe1tDGIPhCz2EQpI2DTI4tAJhM1m0EbZZXo1UEmnN7ABw4cO58wBC9sDLWMhlSI/QcBs2uXeumoDNnom1tl1ejVQSac3sA7NqV3QVUpjbb55h9X9Rpxs7jcxgZvsur0SoCtZYHwrRNPEuZ2mxIAejaoG4zdt6ewwjxXV6N1lgs+9lACCmsdN8o046Wtl69jMUzqLU8EFSbXUfN2NHiu7warSJQ+TIgQgkr3Tcy+o4W3+XVaBUBZJcvcisVQaJm7NxRpazxWR/a2N6l5oPZLumpPQ7GWxEVgTDNgEOJvSRyCamsGa2xOAvZ44QQXdBHWSNjcUlkjxNCdEFIZY0UwQyyxwkhuiCkskaKYAbZ44QQXRBSWSNFMIPcSoUQXRBSWSOvoRSmUf+EEKJtQpx0r1GLwMzON7P7zezJ+Pe8jOOejWcie9jM1qqeL4QQoVJlLECoUY+bdg3tAx5wzl0OPBCvZ3Gdc+6dM65LVc4XQoigqFqwhxr1uKkiuAk4HC8fBj7U8flCCNEbVQv2IpfRvqIaNFUEFzjnXgSIf9+ccZwDvmZmD5rZnhrnY2Z7zGzNzNZOnTrVUGwhhGhO1bEAeS6jfXYbFSoCM/u6mR1LSTdVuM97nHNXATcCnzCz91YV1Dl30Dm35Jxb2rp1a9XThRCidaqOBchzGe2z26hQETjn3u+c+9WUdDfwAzPbBhD/vpRxjZPx70vAV4Br4l2lzhdCiBCpOhYgz2W0z5HGTbuG7gF2x8u7gbtnDzCz15vZG6fLwAeAY2XPF0KIUCkzFmC23x/So4j2OdK4qSL4DHC9mT0JXB+vY2YXmtmR+JgLgP9lZo8A3wH+u3Puf+SdL4QQoZFlyM0LD12l37/XkcbOucGlq6++2lVlZcW5xUXnzKLflZXKlyh1rTbvI4Tol+n3DNE3HRXnUdq8ufj7np47mxYX8+/nq/wA1lxKmTqKMNRp096awd69cMcd1e6dN4UuaHpdIeaFtG99lqKQ0Rs2REX/LGZRC6JrssJQj0IRZMX9NoO77qpWSOfFEAfNZSDEvJD1rScpKtBDm99k1PMRZFndnavumpVn2Q8pvrgQohllvtsiQ25Wv//OnWFNhzsKRZD3sqoW0nmW/ZDiiwshmlH03ZYx5KZ5Fe3eDYcPhxVvaBSK4MCB6CWkUbWQzrPshxRfXAjRjLTveVqOVAkZPetVdORIgPGG0izIoac6XkPLy8VW/+Vl5yaTaN9kEq2nIa8hIcaBj+95thyaJrPm1y6CDK+h3gv1OqmOInAu/6UuL6e/nCxl4AspEiHmg6xvuapLaZtkKYJReA2VYeNGOHPm3O2TCbz6aqu3yiTPNVXup0IMh1DdzEftNVSGNCWQt90HocYqF0IUkxx5vHt39rcc0hSVU9QiiAmhRRDa4BMhRDnKDD6D/r9ltQgK2LOn2nYfyP1UiGGS1ppPI9RvWYog5o47YHk5agFA9Lu8vB6CoouZg+R+KkQz+prhq8x4pLxvuS+5f0maBTn0VNdrqC4rK5GraZbraVcB7YQQ2RR9pz7J8gRKegRlydGl3MhrqD558UIOHJCnjxAh0Gdcn9VVuPnm9H0hxSOSjaABeTGEsjx9du/uP36IEGMi6zt97jnYssVvt8uuXbCwkL6vyC4QQoyyRorAzM43s/vN7Mn497yUY95qZg8n0o/N7FPxvtvM7PuJfTubyOOLPCNu1ss6c6b/+CFCjIm8Avf0af9xfW6/vZ6NLwQnkaYtgn3AA865y4EH4vWzcM4dd8690zn3TuBq4KdE8xZP+ZPpfufckdnzQyDPiJv3sjQGQIjuSPtO0/D1XdYdHxCEk0ia4aBsAo4D2+LlbcDxguM/AHwzsX4b8AdV79u1sdi5bCNumqEnL35I8joLC1GSYViIdlhZyTfadhnXpwpdOYngI9YQ8KOZ9R8WHH8I+GRi/TbgWeBovO+8MvftQxHksbKyHqwuL35IkdLw7eEgjyQxBoo8eMp48swrtRUB8HXgWEq6qYoiADYBLwMXJLZdAEyIuqgOAIdyzt8DrAFr27dv7+CRVaOMC1iZDOor8FSfrnVCdElRhWvM34CvFkHprqFYcXwtZ/8O4FiZ+4bWIphSVOPOCj/bRZO1z4iHQnRNWhds15WvEMlSBE2NxfcAu+Pl3cDdOcd+DPhicoOZbUusfpiopTFYZiegmDUSlfEC8OUpEIKLmhB5tDm6NvktvvxylLImp9I30Nxr6DPA9Wb2JHB9vI6ZXWhmv/QAMrPN8f4vz5z/WTN71MyOAtcBv99QnqAp8mrw6SkQgouaEFlMg7b5nL5R30AOac2E0FOoXUNl6MtrSDYCETJtdF0Wdc2G/A0M2muorzRkRdAn8hoSodJ0+sayhXzZ6Wi7RLGGatJ1rCEhhF+axtspc36oMwBu2RKNfJ5FsYaEEKOi6ejaMs4QIc4AuLqargRgQLGGhBCiDZpO31hkCF5dTW8xQL9eQ3lKaEixhkRL9D4xhRAtUTcvF7lf55HXoph2CWXRp9dQnhIaTKyhvtK8GYtD9maoigzS80nZ99pnXs6SMW9Ef9/fWZZsCwt+7oe8hsJlXkb9zpNCE+tUea8h5uW8Ef19582uvxkpgoBp6joXCiEWAqI5Vd5rKHk52TooExCy7rXbaPV22YrOUgSyEfTItC/VZXjwDm3Eo8JYzCdV3msIo3dnRymfOXPuMXVH8fsYAd3ENtIWUgQ9kcxQabQVbqJLI3QIhYBonyrvte9JVlZXo2liZ91EASaTeh5JSUJ0QW2FtGZC6GkeuobyDFhtNQ+77n+UjWA+qfpeqxiW2+5iqTJJVB1C6fqqC7IRhEUXGaqPPnt5Dc0nXRTaTSsNRfN9tJHvh24HkyIIjC4y1NBrL6J9QlHUPvJ/nndQWy3Tobd6sxSBbAQ90UVfqvrsw6brQYRdhHouiw/Hgqx8PZm0F0+o6QjoYEnTDqGneWgROOe/djb02ss808e7aVILbzuv+mgRKL8Xg7qG5pusD3WoPs+hdGH4oo++5rpdhT4KWF+F9rznm6Z4UQTAR4DHgNeApZzjbiCa3/gpYF9i+/nA/cCT8e95Ze4rRXA2XdWEsu6zvBy+ITE0+rDf1FU+vpSWCu3u8aUI3ga8FfhGliIAJsDTwGXAJuAR4Ip432enigHYB/xRmftKEZxNV7XLrPvMFmq+vD+G4plRhr48uuooWDkdzA9ZiqCRsdg597hz7njBYdcATznnnnHO/Rz4EnBTvO8m4HC8fBj4UBN5xkpXI3qzrhfp8XWaDrAZwwjlPgZe1TV0yulg/unCa+gi4IXE+ol4G8AFzrkXAeLfN2ddxMz2mNmama2dOnXKm7BDpKsPtcr1fHh/zFPB05f3SZ1wBn2PFhb+KVQEZvZ1MzuWkm4qOnd6iZRtLmVbLs65g865Jefc0tatW6uePtd09aGm3cfS3i7NCu2xFDwhxJgpw9y6TIpfsrHoAOfc+xve4wRwSWL9YuBkvPwDM9vmnHvRzLYBLzW81yiZfpD790c18e3bo0Kz7Q817T47d8Lhw+fOA9uk0O7q/4jy7Nql5z/PtDJ5vZl9A/gD59w5M8qb2UbgCeA3gO8D3wX+sXPuMTP7N8Bp59xnzGwfcL5z7l8U3U+T14fF6qoKbSGGQNbk9Y0UgZl9GPg8sBX4EfCwc+43zexC4E7n3M74uJ3AvyPyIDrknDsQb18A/iuwHXge+Ihz7pWi+0oRCCFEdbwogr6QIhBCiOpkKQLFGhJCiJEjRSCEECNHikAIIUaOFIEQQoycQRqLzewUkDHbby5bgJdbFqcNQpULwpVNclUnVNkkVzWayLXonDtnRO4gFUFdzGwtzWLeN6HKBeHKJrmqE6pskqsaPuRS15AQQowcKQIhhBg5Y1MEB/sWIINQ5YJwZZNc1QlVNslVjdblGpWNQAghxLmMrUUghBBiBikCIYQYOXOnCMzsI2b2mJm9ZmaZLlZmdoOZHTezp+IQ2NPt55vZ/Wb2ZPx7XktyFV7XzN5qZg8n0o/N7FPxvtvM7PuJfTu7kis+7lkzezS+91rV833JZmaXmNlfmtnj8Xv/vcS+Vp9ZVp5J7Dcz+1y8/6iZXVX2XM9y7YrlOWpm3zKzdyT2pb7XjuR6n5n9TeL9/Muy53Yg2x8m5DpmZmfM7Px4n5dnZmaHzOwlMzuWsd9f/kqbyHjICXgb8FbgG8BSxjET4GngMmAT8AhwRbzvs8C+eHkf8EctyVXpurGM/5doAAjAbURzPrT9vErJBTwLbGn6v9qWDdgGXBUvv5Fo7ovpu2ztmeXlmcQxO4F7iWblexfw7bLnepbr3cB58fKNU7ny3mtHcr0P+Gqdc33LNnP8B4G/6OCZvRe4CjiWsd9b/pq7FoFz7nHn3PGCw64BnnLOPeOc+znwJWA69eZNwOF4+TDwoZZEq3rd3wCeds7VGUFdhab/19fzKnVt59yLzrmH4uW/BR5nfU7sNsnLM0l5/7OL+Gvg71o0816Zc73J5Zz7lnPuh/HqXxPNEuibJv/Z5/Oqc/2PAV9s8f6pOOf+Csibj8Vb/po7RVCSi4AXEusnWC88LnDOvQhRIQO8uaV7Vr3uRzk3830ybhIearELpqxcDviamT1oZntqnO9TNgDMbAfw94BvJza39czy8kzRMWXO9SlXko8T1SqnZL3XruS61sweMbN7zezKiuf6lg0z2wzcAPy3xGZfz6wIb/mrcM7iEDGzrwNvSdm13zl3d5lLpGxr7EebJ1fF62wC/iFwa2LzfwA+TSTnp4F/C/zTDuV6j3PupJm9GbjfzP5PXINpRIvP7A1EH+unnHM/jjfXfmZpt0jZNptnso7xkt8K7nnugWbXESmCX0ts9vJeS8r1EFHX509i+82fA5eXPNe3bFM+CHzTnT1zoq9nVoS3/DVIReCce3/DS5wALkmsXwycjJd/YGbbnHMvxs2ul9qQy8yqXPdG4CHn3A8S1/7lspn9J+CrXcrlnDsZ/75kZl8hao7+FQ2eV1uymdnriJTAqnPuy4lr135mKeTlmaJjNpU416dcmNnbgTuBG51zp6fbc96rd7kSChvn3BEzu8PMtpQ517dsCc5pmXt8ZkV4y19j7Rr6LnC5mV0a174/CtwT77sH2B0v7wbKtDDKUOW65/RJxgXhlA8DqZ4FPuQys9eb2Runy8AHEvf39bzKymbAnwGPO+f+eGZfm88sL88k5f0nsXfHu4C/ibu0ypzrTS4z2w58GbjFOfdEYnvee+1CrrfE7w8zu4aoPDpd5lzfssUyvQn4dRL5zvMzK8Jf/mrb8t13IvrgTwD/D/gBcF+8/ULgSOK4nUQeJk8TdSlNty8ADwBPxr/ntyRX6nVT5NpM9DG8aeb8u4BHgaPxS97WlVxE3giPxOmxLp5XBdl+jagZfBR4OE47fTyztDwD7AX2xssGfCHe/ygJr7Ws/NbScyqS607gh4nns1b0XjuS65PxfR8hMmK/u4vnVUa2eP13gC/NnOftmRFV/l4EfkFUhn28q/ylEBNCCDFyxto1JIQQIkaKQAghRo4UgRBCjBwpAiGEGDlSBEIIMXKkCIQQYuRIEQghxMj5/w62GGID6HZlAAAAAElFTkSuQmCC\n", "image/png": "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\n",
"text/plain": [ "text/plain": [
"<Figure size 432x288 with 1 Axes>" "<Figure size 432x288 with 1 Axes>"
] ]
...@@ -221,7 +219,7 @@ ...@@ -221,7 +219,7 @@
}, },
{ {
"data": { "data": {
"image/png": "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\n", "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYIAAAD4CAYAAADhNOGaAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/Z1A+gAAAACXBIWXMAAAsTAAALEwEAmpwYAAAh60lEQVR4nO3de8wd9X3n8ffHpia1UBvbeAkB/Bi2pAltuiQ86yQbaZsLCYSVMNmSxKmTddJEXtKQlTbaFUZIm4hda2n3D9SqUbsWSXDwswFKFeFuyLJct380pDxI3AwCGwgXlwTHTiJZUK7f/WPmxOPjc33O3OfzkkbnnJk55/zOzJz5/uZ3G0UEZmbWXcuqToCZmVXLgcDMrOMcCMzMOs6BwMys4xwIzMw67riqE7AUJ554Yqxfv77qZJiZNcp99933s4hY2z+/kYFg/fr1LC4uVp0MM7NGkfT0oPkuGjIz6zgHAjOzjnMgMDPrOAcCM7OOcyAwM+u4XAKBpG9JekHSw0OWS9KfS9on6UFJ784s2yJpbzptySM9ZjabhQVYvx6WLUseFxaqTpEVKa8rgmuB80cs/xhwZjptBf4SQNJq4GvAe4ANwNckrcopTWa2BAsLsHUrPP00RCSPW7c6GLRZLoEgIv4OODRilY3AdyJxD/BmSScD5wG3RcShiPg5cBujA0rtOSdlTXfFFfDii0fPe/HFZL61U1kdyk4Bns28fi6dN2z+MSRtJbmaYN26dcWkcka9nFTvT9TLSQFs3lxdusym8cwz08235mtMZXFE7IiI+YiYX7v2mB7StdCUnJSvWqpV9+0/LJ9V0/zXzKrYH3U7Bsq6ItgPnJZ5fWo6bz/wgb75d5eUptw1ISflq5Zq1X37LyzA4cPHzl+5ErZvLz89Ratif9TyGIiIXCZgPfDwkGX/BvgBIOC9wD+k81cDTwGr0ukpYPW47zrnnHOijubmIpLqtaOn5csjdu2qOnWJYWmcm8v3e3btSj5TSh7r8vurVtb2X4pduyJWrjw2bWvWtHf/TbI/8jqWe58z6PvKOgaAxRh0jh40c9oJ+C7wPPAqSTn/F4BLgEvS5QK+ATwBPATMZ977R8C+dPr8JN+XRyAo4kQ17I8Eyfw6/JmkwemT8vuOQduhLr+/amVs/6Wqc5Ca1bD/+7j9kdexPOrcUOYxUGggKHuaNRAUeaLatSu5AqjrH6qMP3ubTyizqvO2qXOQmsWo//u4/ZHX/hp1JdCaK4Kyp1kDQdF/xjr/ocrIrdf591etzldLdQ5Ssxj1u8btj7yO5WGfU/Yx4ECQUfSJqu5/qKLL7+v++6tW1/qTooNUVb97kuKfYekq44qgzG3hQJBR9Imqzrm+MnT99zdZUSfrKo+JWf7vRdYRVPGfcCDIKGOn1DXXV5au/347WpVXibP+3/NuNVTlf2JYIFCyrFnm5+dj1ltVLiwkHb2eeSbpKHPBBXDLLUdeb99ej3bdZm2wbFlyCu4nwRtvFP/9/f/3rv6/Jd0XEfP98xvTszhvmzfDj3+cHITbt8POnfkOslVGz8G69U40G6bq3srZ//uPf9zNIDBKZwNBVt5DQ5QxeqNHiLQm2b496Z2cNai3sjM3FRlUXlT3Ke+exXm3Ilpqeeg0ZYhumWNNM+74rkuFapvhOoLh1q9PctT95uaSy8hpLaU8tH/8EUhyTDt2DL6MrbrM1Sxvef8P7ViuIxhh0svWSS2lPHTa4qmqy1zN8taEQRvbyoGAJMe9Y0eS85CSx2E58UksJbBM+yfIO3iZVc2Zm+o4EKTybFWwlMAy7Z8g7+BlVrWiMjeugJ7AoIqDuk91HYZ6Fq4oM8u/05X/V0djSGWxrwhqwjl8s/zb+1d518AmXYm41ZCZtVZVreumbQVYFrcaMrPOqaoCuin3L+/JJRBIOl/SY5L2Sdo2YPnVku5Pp8cl/SKz7PXMst15pMfMDKprXde0prAz37xe0nKS21B+hOQ2lfdK2h0Rj/TWiYj/mFn/K8C7Mh/xUkScPWs6zMz69Yphyh5wbt26wZ3j6toUNo8rgg3Avoh4MiJeAa4HNo5Y/9Mk9ziutSIreppUiWTWdFUMONe0fj55BIJTgGczr59L5x1D0hxwOnBnZvabJC1KukfSRcO+RNLWdL3FAwcO5JDs4Yoc0M2DxZm1X9NaAc7cakjSxcD5EfHF9PVngfdExKUD1r0MODUivpKZd0pE7Jd0BkmA+HBEPDHqO4tuNVTkmCceT8XMqlJkq6H9wGmZ16em8wbZRF+xUETsTx+fBO7m6PqDUvWKbAadqCGfip6mVSKZWfvlEQjuBc6UdLqkFSQn+2Na/0h6O7AK+GFm3ipJx6fPTwTeDzzS/94yZItshsmjosfjqZhZ3cwcCCLiNeBS4FbgUeDGiNgj6UpJF2ZW3QRcH0eXRb0DWJT0AHAXcFW2tVGZBrX7zcqroqdplUhmVp2yGpbM3HwUICJuAW7pm/df+l5/fcD7/h54Zx5pmNWoopm5uXybnP36rx8JOmvWwJ/9WX0rkcysGv29k3sNSyD/84V7FqeGFc30KnHz2PC9HXvw4JF5L700++eaWfuU2TvZgSBVRpFN07qdm1l1ymxY4kCQKqPdr1sMmdmkymxY4kCQUXQPRLcYMrNJldmwxIGgRG4xZGaTKrN3ci6thmwyVQ2AZWbNtHlzOecHXxGUrIoBsMysHE0dUNJXBGZmOSiz3X/efEVgZpaDJjcPdyAwM8tBk5uHOxCYmeVg9erp5teJA4GZWcc5EJiZ5eDQoenm14kDgZlZDiYdOaCOTUwdCMzMcjDJyAF1vWe5A8GU6hjNzax6kwwJUdcmprkEAknnS3pM0j5J2wYs/5ykA5LuT6cvZpZtkbQ3nbbkkZ6i1DWam1k9jBs5oK5NTGcOBJKWA98APgacBXxa0lkDVr0hIs5Op2vS964Gvga8B9gAfE3SqlnTVJS6RnMza4a6jkCcxxXBBmBfRDwZEa8A1wMbJ3zvecBtEXEoIn4O3Aacn0OaClHXaG5mzVDXEYjzCASnAM9mXj+Xzuv3B5IelHSTpNOmfC+StkpalLR44MCBHJI9vbpGczNrhjKHlp5GWZXFfwusj4jfI8n175z2AyJiR0TMR8T82rVrc0/gJOoazc2sOeo4AnEegWA/cFrm9anpvF+JiIMR8XL68hrgnEnfWyd1jeZmZrPIIxDcC5wp6XRJK4BNwO7sCpJOzry8EHg0fX4r8FFJq9JK4o+m82qrjtHcasjtjK1BZr4fQUS8JulSkhP4cuBbEbFH0pXAYkTsBv6DpAuB14BDwOfS9x6S9F9JggnAlRHRgA7ZZiM0eWB66yRFRNVpmNr8/HwsLi5WnQyzwdavT07+/ebmkstIs4pIui8i5vvnu2exWd7cztgaxoHALG9uZ2wN40Bglje3M7aGcSAwy5vbGVvDOBCYFWFUO2M3LbWambn5qJlNwU1LrYZ8RWA2Tp45eA9hazXkQGA2St43oRjXtNTFRlYBBwKzUfLOwY9qWuo7H1lFHAismcrKOefdOWxU01IXG1lFHAisecrMOefdOWxU01L3SLaKOBBY85SZcy6ic9iwpqXukWwVcSCw5ikz51xE57BhxVrukWwVcT8Ca5516waP7llUznnz5vza+E/Sj+CKK5Kgtm5dEgTcv8AK5isCa4ZsLvrwYVix4ujlTck5jyvW8p2PLKOsNhEOBFZ//ZXDBw8mj2vWHCmu2bIlOZnWvf29K4RtQmW2icglEEg6X9JjkvZJ2jZg+VclPSLpQUl3SJrLLHtd0v3ptLv/vWYDc9GvvgonnJDknLdvh507m9H+vogKYXdCa6VSWxNHxEwTye0pnwDOAFYADwBn9a3zQWBl+vxLwA2ZZYen/c5zzjknrEOkiOQUf/QkJcvn5gYvn5urMtWD7doVsXLl0elcuTKZP+3n9H53//ZZyudZ7Yw77JeC5PbBx5xT87gi2ADsi4gnI+IV4HpgY1+wuSsierHtHuDUHL43V85U1di4XHSTilvyaIWULTOA5PyQ5U5orVBma+I8AsEpwLOZ18+l84b5AvCDzOs3SVqUdI+ki4a9SdLWdL3FAwcOzJTgfu7ZX3PjmlU2rf39rBXCg8oM+k0bBJ0Tqp1SWxMPukyYZgIuBq7JvP4s8BdD1v0MyRXB8Zl5p6SPZwA/Bv75uO/Mu2ioSSULndUrCpGSx2zRR17FLWWkNQ/DygyWevAO2n4QsWaNi5gqlvehxJCioTwCwfuAWzOvLwcuH7DeucCjwD8b8VnXAheP+868A0ERZXFWsqJPvtOko+igNCznstTvG/V5rm+oXJ6HdpGB4DjgSeB0jlQW/07fOu8iqVA+s2/+qt7VAXAisJe+iuZBk68IrLbKOJgGBZtebmYpZ4pxVxj+I1Qm73zFsEAwcx1BRLwGXArcmub4b4yIPZKulHRhutr/AE4A/rqvmeg7gEVJDwB3AVdFxCOzpmla7tlvuSmj4npQhfN11yXniaXUOYyrS6ljpXtHlNWEVEmQaJb5+flYXFzM9TMXFtyz33Kwfv3g4S/m5pKTdB31D3vRr85pb7lly45tFAZJ/H/jjek/T9J9ETF/zPcsJXFt5J79losmXl72rjDWrDl2Wd3T3nJlNYhzIDDryaMJZRGjlZZh82b42c9g167mpb3FyspXuGjIDAYXj6xc6ROhVS7PYuthRUMOBGbQzLJ9sym5jsBslCYNU2GWMwcC67ZevcCwK+O6DlPRz0NE2Ax8hzLrrnHNJpvSYmaSu56ZjeArAuuuUYO3NanFTKkD11sb+YrAumtY+b/UrApi12/YjHxFYN3VtOGrh2nL77DKOBBYdzWxF/Agk/wOVybbCA4E1l159wKu6mQ77nf4zks2hjuUmeWhzj2T3VnOUu5QZlakOrfccWWyjeFAYJaHOp9sXZlsYzgQmOWhzifbKirFXTndKLkEAknnS3pM0j5J2wYsP17SDenyH0lan1l2eTr/MUnn5ZEes9LVuQVS2UNju3K6cWauLJa0HHgc+AjwHHAv8OnsLScl/THwexFxiaRNwMcj4lOSzgK+C2wA3grcDrwtIl4f9Z2uLLZa8m3uEq6crq0iK4s3APsi4smIeAW4HtjYt85GYGf6/Cbgw5KUzr8+Il6OiKeAfennmTWPb3OXqHN9iQ2URyA4BXg28/q5dN7AddKb3f8SWDPhewGQtFXSoqTFAwcO5JBsswZoYll7netLbKDGVBZHxI6ImI+I+bVr11adHLPiNbWsvc71JTZQHoFgP3Ba5vWp6byB60g6DvhN4OCE77Wua2KuOA917pswSlPv29xheVQWH0dSWfxhkpP4vcAfRsSezDpfBt6ZqSz+txHxSUm/A/wvjlQW3wGc6cpi+5U699gt2rJlg2+YIyX1EGZTKqyyOC3zvxS4FXgUuDEi9ki6UtKF6WrfBNZI2gd8FdiWvncPcCPwCPB/gC+PCwLWMU3NFefBZe1WEo81ZPXW5Vxxl6+GrBAea8iaqcu5Ype1W0kcCKzeBrVA+bVfg8OHu1F57L4JVgIHAqu3/lzxmjXJ48GDzWpSaTaBqhrIORBY/WVzxSecAK+8cvTyulQed7WZq+Wiym4jDgTWLHUbvqB38pfgs59tXuevaTjQFarKBnIOBNYsdao8zmbh4NjWTXW5UslDU3s5N8igcfpGzc+TAwHO6DRKnYYvGJSF69eWgda63J+jJMuXTzc/T50PBM7oNMywJpVQfjSf5CTflmaudSuSa6HXh3SlHTY/T50PBM7oNFB/k0qoJpqPO8m3aaC1OhXJtdTc3HTz89T5QOCMTgtUFc0HFVNJyWPbOn/VqUiupS644Mjh01PWJu58IHBGpwWqiuaDiqmuuy65Kmlb5y/3ci7UwgLs3Hl0ewMJtmwpZxN3fqwhD+fSAr41ojVcWYewxxoawhmdFnCxhTVc1UXUnQ8E4OFcGs/R3Bqu6iJqBwJrB0dza7CqL2pnCgSSVku6TdLe9HHVgHXOlvRDSXskPSjpU5ll10p6StL96XT2LOkxK5V7IlpOqr6onamyWNKfAoci4ipJ24BVEXFZ3zpvAyIi9kp6K3Af8I6I+IWka4H/HRE3TfO9vjGNVc6tDKyBiqos3gjsTJ/vBC7qXyEiHo+IvenzfwReANbO+L1m1XJPRGuRWQPBSRHxfPr8J8BJo1aWtAFYATyRmb09LTK6WtLxM6bHrBxVN/Mwy9HYQCDpdkkPD5g2ZteLpIxpaDmTpJOB64DPR0TvZrOXA28H/iWwGrhsyNuRtFXSoqTFAwcOjP9lZkWqupmHWY7GBoKIODcifnfAdDPw0/QE3zvRvzDoMyT9BvB94IqIuCfz2c9H4mXg28CGEenYERHzETG/dq1LlqxiVTfzMMvRrEVDu4Et6fMtwM39K0haAXwP+E5/pXAmiIikfuHhGdNjVo6qm3lYq5XdIG3WVkNrgBuBdcDTwCcj4pCkeeCSiPiipM+Q5Pb3ZN76uYi4X9KdJBXHAu5P33N43Pe61ZCZtVWRDdKGtRrq/FhD4ywsJA1BnnkmKf7dvt2ZPjMrTpHjDnmsoSXwTWtawh2/rEGqaJDmQDCCm4q3gKO5NUwVDdIcCEZwU/EWcDS3hqmiQZoDwQhuKl5zkxT5tCmau4irE6pokOZAMIKbitfYpEU+bYnmLuLqlLIH03UgGMFNxWts0iKftkRzF3FZgdx81Jpp2bKjb/DaIyXZqKw2tAGe5veaDTGs+ehxVSTGbGbr1g1ubD2oyGfz5uad+PtN83vNpuSiIWumthT5TKprv9dK5UBgzdS1Cpyu/V4rlesIctCGImgzaz8PMdEnrybZbtVnZk3XyUCQ58nbrfrMrOk6GQjyPHm3qeOqmXVTJwNBnifvtnRcNbPu6mQgyPPk7VZ9ZtZ0MwUCSasl3SZpb/q4ash6r0u6P512Z+afLulHkvZJuiG9rWXh8jx5u1WfmTXdrFcE24A7IuJM4I709SAvRcTZ6XRhZv6fAFdHxG8BPwe+MGN6JpL3ybvsAaLMzPI06z2LHwM+EBHPpzeivzsifnvAeocj4oS+eQIOAG+JiNckvQ/4ekScN+5769aPwMysCYrqR3BSRDyfPv8JcNKQ9d4kaVHSPZIuSuetAX4REa+lr58DTpkxPWZmNqWxg85Juh14y4BFRzW2jIiQNOzyYi4i9ks6A7hT0kPAL6dJqKStwFaAdW6SY2aWm7GBICLOHbZM0k8lnZwpGnphyGfsTx+flHQ38C7gb4A3SzouvSo4Fdg/Ih07gB2QFA2NS7eZmU1m1qKh3cCW9PkW4Ob+FSStknR8+vxE4P3AI5FUTtwFXDzq/WZmVqxZA8FVwEck7QXOTV8jaV7SNek67wAWJT1AcuK/KiIeSZddBnxV0j6SOoNvzpgeMzOb0kyBICIORsSHI+LMiDg3Ig6l8xcj4ovp87+PiHdGxL9IH7+Zef+TEbEhIn4rIj4RES/P9nOqMckAdr7vuJnVle9QNqPeAHa9sYt6A9jBkf4Ek6xjZlaVzgwxUVSOfJIB7DxCqZnVWSeuCIrMkU8ygJ1HKDWzOuvEFUGROfJJBrDzCKVm7dWG+r9OBIIic+TjBrBbWIDDh499n0coNWu+ttyhsBOBoMgc+agB7HoHycGDR79nzRqPUGrWBm2p/+vEzev76wggyZEXfTJevz7JIfSbm0tGKTWzZlu2LLkS6CcloxHXTadvXl/VPQNcSWzWbm2p/+tEIIBq7hnQloPEzAab9iZXS61YLrpCujOBoAq+jaVZu01T2rDUiuUyKqQ7UUdQpYWFpOLomWeSK4Ht211JbNZFS60zzLOucVgdgQOBmVkJllqxnGeFdKcri9ukDZ1XzLpoqXWGZdQ1OhA0SFs6r5h10VLrDMuoa3QgaJC2dF4x66KlNmMvo/m76wgapGmdV8ysXgqpI5C0WtJtkvamj6sGrPNBSfdnpn+SdFG67FpJT2WWnT1LetrO/RLMrAizFg1tA+6IiDOBO9LXR4mIuyLi7Ig4G/gQ8CLwfzOr/Ofe8oi4f8b0tJr7JZhZEWYNBBuBnenzncBFY9a/GPhBRLw4Zj0boKqhMsys3WYNBCdFxPPp858AJ41ZfxPw3b552yU9KOlqScfPmJ7Wq2KoDDNrt7F3KJN0O/CWAYuOaqsSESFpaM2zpJOBdwK3ZmZfThJAVgA7gMuAK4e8fyuwFWCdC8XNzHIz9oogIs6NiN8dMN0M/DQ9wfdO9C+M+KhPAt+LiFczn/18JF4Gvg1sGJGOHRExHxHza9eunfT3mZktWVc6cM5aNLQb2JI+3wLcPGLdT9NXLJQJIiKpX3h4xvSYmU1s1Im+Sx04Zw0EVwEfkbQXODd9jaR5Sdf0VpK0HjgN+H9971+Q9BDwEHAi8N9mTI+N0JXcjdkkxp3ou9SB0x3KxuiNHvr007B8Obz+etJap2mjiFZ1lzazuho3qmcbO3B60LklyOYYIAkC0MxLxC7lbswmMeoOggsLSSAYpKi2KlVesfuKYIRhOYaeJt17uI25G7NZDPt/r1kDL710bMYJiruKLuuK3VcESzDu3sJNuvewh6cwS/Ry3k8/nWSEsno99wcFgeXLiwsCW7ZUe8XuQDDCUscJryMPT2F2bHFvxJFg0Oupf+jQ4Pe+8cZ0QWCSop5eenrFzv1Ky2xGROOmc845J8qwa1fEypURyeFy9LRyZbK8SXbtipibi5CSx6al32xWc3OD/89zc9OtM86gc8egc8aw71rKd04CWIwB59TKT+pLmcoKBBFHTp4QsXz5kZ1T1UnUJ3OzpZMGn3ClI+tMehIfZdJgMiw9RWU2HQhaII8DtGoOZFalSU/Qsx6nkwScUelZvryY/4YDQQvkccmaVfZJuQ2BzJqtrGNwmoBT5n/CgaAFJs1lTKKKk3LegcxsKcrIAE3z/yozQzYsELgfQYOM6wlZ1WdNyn0ZrEt6oxI880zSwrAOoxG4H0EL5NkEdFSvyqK4L4N1SZPuHeJA0CB53qGsipPyoEAmwQUXFPedZjaeA0HD5JXLGHd1UcS4J5s3Jz0os705I2DnzmaN29QGHonWjjKo4qDuU1cri/M2rJKqyIrkulQYd7kZaxtbb3V5f04DtxoqXlsOxiJP1nm2fFqqNp4Ip1GXYJyXru/PaQwLBC4aykmb7mZUZEVyHSqMuz4k97T7t+7FSG3cn6Vv80HRYdIJ+ASwB3gDmB+x3vnAY8A+YFtm/unAj9L5NwArJvneOl4RtCmXVeRvqUPurQ5XJVWaZv/WYX+N07b9WeQ2p4iiIeAdwG8Ddw8LBMBy4AngDGAF8ABwVrrsRmBT+vyvgC9N8r11DARtOhiL/vNXXYTWpqC9FNPs3yZsqyakcRpF/p5CAsGvPmR0IHgfcGvm9eXpJOBnwHGD1hs11TEQtO1grPpkXaQm5HKLNun+bUIGp237s8htPiwQlFFHcArwbOb1c+m8NcAvIuK1vvmN1Lbx/pvUGWZaefbHaKpJ928d6nTGadv+rGKbjw0Ekm6X9PCAaWNxyRqYjq2SFiUtHjhwoMyvnkjbDsa2a3Ogy1NTMjht2p9VbPPjxq0QEefO+B37gdMyr09N5x0E3izpuPSqoDd/WDp2ADsgGWtoxjQVYvPmZh+AZv16x3Pdxsxpsyq2+dhAkIN7gTMlnU5yot8E/GFSFqa7gIuB64EtwM0lpMfMpuAMTvnK3uYz1RFI+rik50gqer8v6dZ0/lsl3QKQ5vYvBW4FHgVujIg96UdcBnxV0j6SOoNvzpIeMzObnoehNjPrCA9DbWZmAzkQmJl1nAOBmVnHNbKOQNIBYMCNFidyIkmP5rpxuqZT13RBfdPmdE2vrmlbarrmImJt/8xGBoJZSFocVFlSNadrOnVNF9Q3bU7X9OqatrzT5aIhM7OOcyAwM+u4LgaCHVUnYAinazp1TRfUN21O1/TqmrZc09W5OgIzMztaF68IzMwsw4HAzKzjWhkIJH1C0h5Jb0ga2sRK0vmSHpO0T9K2zPzTJf0onX+DpBU5pWu1pNsk7U0fVw1Y54OS7s9M/yTponTZtZKeyiw7u6x0peu9nvnu3Zn5VW6vsyX9MN3fD0r6VGZZrttr2PGSWX58+vv3pdtjfWbZ5en8xySdN0s6lpCur0p6JN0+d0iayywbuE9LTNvnJB3IpOGLmWVb0n2/V9KWktN1dSZNj0v6RWZZYdtM0rckvSDp4SHLJenP03Q/KOndmWVL316DblvW9ImK7qU8Qbr+FNiWPt8G/MmY9VcDh4CV6etrgYsL2F4TpQs4PGR+ZdsLeBtwZvr8rcDzwJvz3l6jjpfMOn8M/FX6fBNwQ/r8rHT944HT089ZXmK6Ppg5hr7US9eofVpi2j4H/MWA964GnkwfV6XPV5WVrr71vwJ8q6Rt9q+BdwMPD1l+AfADklv9vhf4UR7bq5VXBBHxaEQ8Nma1DcC+iHgyIl4huSfCRkkCPgTclK63E7gop6RtTD9v0s+9GPhBRLyY0/cPM226fqXq7RURj0fE3vT5PwIvAMf0nMzBwONlRHpvAj6cbp+NwPUR8XJEPAXsSz+vlHRFxF2ZY+gekptAlWGSbTbMecBtEXEoIn4O3AacX1G6Pg18N6fvHiki/o4k8zfMRuA7kbiH5OZeJzPj9mplIJhQFfdSPikink+f/wQ4acz6mzj2ANyeXhJeLen4ktP1JiW3C72nV1xFjbaXpA0kObwnMrPz2l7DjpeB66Tb45ck22eS9xaZrqwvkOQoewbt07xMmrY/SPfRTZJ6dzOsxTZLi9FOB+7MzC5ym40zLO0zba8y7lBWCEm3A28ZsOiKiKjsTmej0pV9EREhaWjb3TTKv5Pkhj49l5OcEFeQtCO+DLiyxHTNRcR+SWcAd0p6iORkt2Q5b6/rgC0R8UY6e8nbq40kfQaYB34/M/uYfRoRTwz+hEL8LfDdiHhZ0r8nuaL6UInfP84m4KaIeD0zr+ptlrvGBoKoyb2Up0mXpJ9KOjkink9PXC+M+KhPAt+LiFczn93LHb8s6dvAfyozXRGxP318UtLdwLuAv6Hi7SXpN4Dvk2QC7sl89pK31wDDjpdB6zwn6TjgN0mOp0neW2S6kHQuSXD9/Yh4uTd/yD7N66Q2Nm0RcTDz8hqSeqHeez/Q9967y0pXxibgy9kZBW+zcYalfabt1eWioV/dS1lJK5dNwO5Ial5691KGfO+lvDv9vEk+95hyyfRk2CuXvwgY2LKgiHRJWtUrWpF0IvB+4JGqt1e6775HUm56U9+yPLfXwONlRHovBu5Mt89uYJOSVkWnA2cC/zBDWqZKl6R3Af8TuDAiXsjMH7hPc0rXpGk7OfPyQpLb2UJyJfzRNI2rgI9y9NVxoelK0/Z2korXH2bmFb3NxtkN/Lu09dB7gV+mGZ7ZtldRtd9VTsDHScrIXgZ+Ctyazn8rcEtmvQuAx0mi+RWZ+WeQ/FH3AX8NHJ9TutYAdwB7gduB1en8eeCazHrrSSL8sr733wk8RHJC2wWcUFa6gH+VfvcD6eMX6rC9gM8ArwL3Z6azi9heg44XkqKmC9Pnb0p//750e5yRee8V6fseAz6W8/E+Ll23p/+D3vbZPW6flpi2/w7sSdNwF/D2zHv/KN2W+4DPl5mu9PXXgav63lfoNiPJ/D2fHtPPkdTpXAJcki4X8I003Q+RaRU5y/byEBNmZh3X5aIhMzPDgcDMrPMcCMzMOs6BwMys4xwIzMw6zoHAzKzjHAjMzDru/wPTby8hcT1iEgAAAABJRU5ErkJggg==\n",
"text/plain": [ "text/plain": [
"<Figure size 432x288 with 1 Axes>" "<Figure size 432x288 with 1 Axes>"
] ]
...@@ -635,7 +633,7 @@ ...@@ -635,7 +633,7 @@
"# Generate Pauli Z operator that only acts on the first qubit\n", "# Generate Pauli Z operator that only acts on the first qubit\n",
"# Act the identity matrix on rest of the qubits\n", "# Act the identity matrix on rest of the qubits\n",
"def Observable(n):\n", "def Observable(n):\n",
" \"\"\"\n", " r\"\"\"\n",
" :param n: number of qubits\n", " :param n: number of qubits\n",
" :return: local observable: Z \\otimes I \\otimes ...\\otimes I\n", " :return: local observable: Z \\otimes I \\otimes ...\\otimes I\n",
" \"\"\"\n", " \"\"\"\n",
...@@ -862,19 +860,19 @@ ...@@ -862,19 +860,19 @@
"epoch: 3 iter: 100 loss: 0.0671 train acc: 1.0000 test acc: 1.0000\n", "epoch: 3 iter: 100 loss: 0.0671 train acc: 1.0000 test acc: 1.0000\n",
"epoch: 3 iter: 150 loss: 0.0849 train acc: 1.0000 test acc: 1.0000\n", "epoch: 3 iter: 150 loss: 0.0849 train acc: 1.0000 test acc: 1.0000\n",
"The trained circuit:\n", "The trained circuit:\n",
"--Rz(0.542)----Ry(3.456)----Rz(2.699)----*--------------X----Ry(6.153)--\n", "--Rz(0.542)----Ry(3.456)----Rz(2.699)----*--------------x----Ry(6.153)--\n",
" | | \n", " | | \n",
"--Rz(3.514)----Ry(1.543)----Rz(2.499)----X----*---------|----Ry(3.050)--\n", "--Rz(3.514)----Ry(1.543)----Rz(2.499)----x----*---------|----Ry(3.050)--\n",
" | | \n", " | | \n",
"--Rz(5.947)----Ry(3.161)----Rz(3.897)---------X----*----|----Ry(1.583)--\n", "--Rz(5.947)----Ry(3.161)----Rz(3.897)---------x----*----|----Ry(1.583)--\n",
" | | \n", " | | \n",
"--Rz(0.718)----Ry(5.038)----Rz(1.348)--------------X----*----Ry(0.030)--\n", "--Rz(0.718)----Ry(5.038)----Rz(1.348)--------------x----*----Ry(0.030)--\n",
" \n" " \n"
] ]
}, },
{ {
"data": { "data": {
"image/png": "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\n", "image/png": "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\n",
"text/plain": [ "text/plain": [
"<Figure size 432x288 with 2 Axes>" "<Figure size 432x288 with 2 Axes>"
] ]
...@@ -888,7 +886,7 @@ ...@@ -888,7 +886,7 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"The main program finished running in 35.49316954612732 seconds.\n" "The main program finished running in 23.123697519302368 seconds.\n"
] ]
} }
], ],
...@@ -957,7 +955,7 @@ ...@@ -957,7 +955,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.3" "version": "3.7.10"
}, },
"toc": { "toc": {
"base_numbering": 1, "base_numbering": 1,
......
因为 它太大了无法显示 source diff 。你可以改为 查看blob
因为 它太大了无法显示 source diff 。你可以改为 查看blob
{
"cells": [
{
"cell_type": "markdown",
"id": "interesting-deployment",
"metadata": {},
"source": [
"# 使用量子电路计算梯度\n",
"\n",
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
]
},
{
"cell_type": "markdown",
"id": "sweet-olympus",
"metadata": {},
"source": [
"## 概览\n",
"\n",
"当使用像[变分量子本征求解器 (VQE)](../quantum_simulation/VQE_CN.ipynb) 和 [量子近似优化算法 (QAOA)](../combinatorial_optimization/QAOA_CN.ipynb) 这样的变分量子算法时,我们需要改变量子电路中的参数使目标函数最小化。这就带来了一个重要的问题 - 如何计算参数化量子电路的梯度?由于目标函数是使用量子电路评估的,因此也必须使用量子算法评估其梯度。与经典计算梯度相比,这无疑更具挑战性。下面我们将介绍三种在量子计算机上完成这项任务的方法。同时利用 Paddle Quantum 模拟它们在量子计算机上运行的效果。"
]
},
{
"cell_type": "markdown",
"id": "spectacular-orientation",
"metadata": {},
"source": [
"## 背景\n",
"\n",
"假设目标函数是 Variational Quantum Algorithms (VQA) 中使用的损失函数 - 参数化电路关于哈密顿量 H 的期望值:$O(\\theta) = \\left\\langle00\\right| U^{\\dagger}(\\theta)HU(\\theta) \\left|00\\right\\rangle$,其中 $U(\\theta)$ 表示参数化量子电路,$\\theta = [\\theta_1, \\theta_2, \\dots, \\theta_n]$ 代表电路里的可训练参数,那么我们想要得到的是\n",
"\n",
"$$\n",
"\\nabla O(\\theta) = \\begin{bmatrix} \\frac{\\partial O}{\\partial \\theta_1} \\\\ \\frac{\\partial O}{\\partial \\theta_2}\\\\ \\vdots\\\\ \\frac{\\partial O}{\\partial \\theta_n} \\end{bmatrix}.\n",
"\\tag{1}\n",
"$$\n",
"\n",
"首先,让我们导入需要的包:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "optical-surface",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import paddle\n",
"from paddle_quantum.circuit import UAnsatz\n",
"from paddle_quantum.utils import pauli_str_to_matrix, Hamiltonian\n",
"import warnings\n",
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "markdown",
"id": "apparent-violence",
"metadata": {},
"source": [
"接着,构造目标函数 $O(\\theta) = \\left\\langle00\\right| U^{\\dagger}(\\theta)HU(\\theta) \\left|00\\right\\rangle$ 中的哈密顿量 $H$ 和参数化量子电路 $U(\\theta)$。\n",
"\n",
"以两量子比特的电路为例,随机生成长度为 $4$ 的 Tensor 作为 $\\theta$ 构建参数化电路 $U(\\theta)$,并选择哈密顿量 $H = Z \\otimes Z$。"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "affected-progress",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"哈密顿量 H:\n",
" [[ 1.+0.j 0.+0.j 0.+0.j 0.+0.j]\n",
" [ 0.+0.j -1.+0.j 0.+0.j 0.+0.j]\n",
" [ 0.+0.j 0.+0.j -1.+0.j 0.+0.j]\n",
" [ 0.+0.j 0.+0.j 0.+0.j 1.+0.j]]\n",
"\n",
"U(theta):\n",
"--Ry(0.389)----*----x----Ry(4.934)--\n",
" | | \n",
"--Ry(0.492)----x----*----Ry(1.200)--\n",
" \n"
]
}
],
"source": [
"# 首先,定义 H 还有电路中的参数\n",
"pauli_str = [[1.0, 'Z0,Z1']]\n",
"H = Hamiltonian(pauli_str)\n",
"\n",
"# 注意:定义参数时若标明 stop_gradient=False 则为可训练的参数;如未标明,则将默认该参数为常数,将不会被内置函数计算梯度。\n",
"theta_np = np.random.uniform(0, 2 * np.pi, 4)\n",
"theta_tensor = paddle.to_tensor(theta_np, 'float64', stop_gradient=False)\n",
"\n",
"def U_theta(theta):\n",
" cir = UAnsatz(2)\n",
" cir.ry(theta[0], 0)\n",
" cir.ry(theta[1], 1)\n",
" cir.cnot([0, 1])\n",
" cir.cnot([1, 0])\n",
" cir.ry(theta[2], 0)\n",
" cir.ry(theta[3], 1)\n",
" cir.run_state_vector()\n",
" return cir\n",
"\n",
"print('哈密顿量 H:\\n', H.construct_h_matrix())\n",
"print('\\nU(theta):')\n",
"print(U_theta(theta_tensor))"
]
},
{
"cell_type": "markdown",
"id": "worthy-switzerland",
"metadata": {},
"source": [
"## 有限差分法\n",
"\n",
"有限差分法是估算函数梯度最传统和最常用的数值方法之一。主要思想是用差分代替偏导数:\n",
"\n",
"$$\n",
"f'(x)= \\lim_{h \\to 0}\\frac{f(x+h) - f(x)}{h}.\n",
"\\tag{2}\n",
"$$\n",
"\n",
"通过选择足够小的 $h$,我们可以得到很好的导数近似值。\n",
"\n",
"以有限差分法的一种:中心差分法为例计算目标函数的梯度,我们得到\n",
"\n",
"$$\n",
"\\nabla O(\\theta) \\approx \\frac{O(\\theta+\\delta) - O(\\theta-\\delta)}{2\\delta} = \\frac{\\left\\langle00\\right| U^{\\dagger}(\\theta + \\delta)HU(\\theta + \\delta) \\left|00\\right\\rangle - \\left\\langle00\\right| U^{\\dagger}(\\theta - \\delta)HU(\\theta-\\delta) \\left|00\\right\\rangle)}{2\\delta}.\n",
"\\tag{3}\n",
"$$\n",
"\n",
"为实现上式,我们可以通过循环参数列表改变原电路中的特定参数进而能够在同一电路上反复计算目标函数,无需构建新电路或使用额外的辅助比特。\n",
"\n",
"使用 Paddle Quantum 的内置方法,我们可以构建 $U(\\theta)$ 的电路,并通过传入对应的哈密顿量 H 和 $\\delta$,轻松计算有限差分梯度。注:内置方法暂不支持指定输入态以及含噪电路。"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "hybrid-research",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--Ry(0.389)----*----x----Ry(4.934)--\n",
" | | \n",
"--Ry(0.492)----x----*----Ry(1.200)--\n",
" \n",
"目标函数的梯度为: [-0.19714755 -0.7647454 0.11976991 -0.38304339]\n"
]
}
],
"source": [
"# 重复使用已定义好的 H 还有电路中的参数,注意确认定义可训练参数时已标明 stop_gradient=False\n",
"\n",
"# 构建 U(theta) 电路\n",
"cir = U_theta(theta_tensor)\n",
"print(cir)\n",
"\n",
"# 用内置差分法函数计算梯度\n",
"gradients = cir.finite_difference_gradient(H, delta=0.01)\n",
"print(\"目标函数的梯度为:\", gradients.numpy())"
]
},
{
"cell_type": "markdown",
"id": "continent-avatar",
"metadata": {},
"source": [
"## Parameter-shift 方法\n",
"\n",
"我们使用同样的目标函数 $O(\\theta) = \\left\\langle0\\right| U^{\\dagger}(\\theta)HU(\\theta) \\left|0\\right\\rangle$ 为例,将 $U(\\theta)$ 表示成 $e^{-ia\\theta G}$ 的形式,其中 $G$ 为单量子比特门且有两个不同的本征值 $\\lambda_1$ 和 $\\lambda_2$,故可以利用 $(4)$ 式 Parameter-shift 方法来计算它的梯度 [1]:\n",
"\n",
"$$\n",
"\\nabla O(\\theta) = r \\left[O(\\theta+\\frac{\\pi}{4r}) - O(\\theta-\\frac{\\pi}{4r})\\right],\n",
"\\tag{4}\n",
"$$ \n",
"\n",
"其中 $r = \\frac{a}{2} (\\lambda_2 - \\lambda_1)$。值得一提的是,我们在这里得到的是理论上精确的梯度,而不是像有限差分梯度这样的估计值。 此外,该方法不需要构建新电路或添加辅助量子比特,只需更改电路内部的参数即可进行评估。\n",
"\n",
"Paddle Quantum 提供的基础旋转门有 $R_x(\\theta)$、$R_y(\\theta)$ 和 $R_z(\\theta)$, 分别可以表示成 $e^{-i\\frac{1}{2}\\theta X}、e^{-i\\frac{1}{2}\\theta Y}、e^{-i\\frac{1}{2}\\theta Z}$。由于 $X$、$Y$ 和 $Z$ 门的本征值皆为 -1 和 1,$\\lambda_1 \\neq \\lambda_2$,因而有 $r = \\frac{1}{2}$,故这些门的梯度为 \n",
"\n",
"$$\n",
"\\frac{1}{2}[ O(\\theta + \\frac{\\pi}{2}) - O(\\theta - \\frac{\\pi}{2})].\n",
"\\tag{5}\n",
"$$\n",
"\n",
"我们将使用单比特旋转门 $R_x$ 来演示这个公式的推导。"
]
},
{
"cell_type": "markdown",
"id": "premium-parliament",
"metadata": {},
"source": [
"### 推导过程\n",
"本小节中我们将以 $R_x$ 门为例,展示公式的推导过程。\n",
"\n",
"令 $U(\\theta) = R_x(\\theta)$,有\n",
"\n",
"$$\n",
"O(\\theta) = \\left\\langle0\\right| R_x^{\\dagger}(\\theta)HR_x(\\theta) \\left|0\\right\\rangle.\n",
"\\tag{6}\n",
"$$\n",
"\n",
"因为 $R_x(\\theta) = e^{-i\\frac{1}{2}\\theta X}$,其中 $X$ 代表泡利 $X$ 门,则 $\\frac{\\partial}{\\partial \\theta} R_x(\\theta) =-i\\frac{1}{2}Xe^{-i\\frac{\\theta}{2}X}=-i\\frac{1}{2}XR_x(\\theta)$。利用乘积法则,目标函数的导数可以被写成\n",
"\n",
"$$\n",
"O'(\\theta) = \\left\\langle0\\right| [\\frac{i}{2}X] R_x^{\\dagger}(\\theta)HR_x(\\theta)\\left|0\\right\\rangle + \\left\\langle0\\right| R_x^{\\dagger}(\\theta)H [-\\frac{i}{2}X] R_x(\\theta)\\left|0\\right\\rangle.\n",
"\\tag{7}\n",
"$$\n",
"\n",
"为了之后重新简化这部分公式,我们提出来一个 $r$ 放到最前面(对于 $R_x$ 门,$r$ 为 $\\frac{1}{2}$),我们得到:\n",
"\n",
"$$\n",
"O'(\\theta) = r \\left\\langle0\\right| [\\frac{i}{2r}X] R_x^{\\dagger}(\\theta)HR_x(\\theta)\\left|0\\right\\rangle + \\left\\langle0\\right| R_x^{\\dagger}(\\theta)H [-\\frac{i}{2r}X] R_x(\\theta)\\left|0\\right\\rangle.\n",
"\\tag{8}\n",
"$$\n",
"\n",
"由于对任意算符 $U$、$V$ 和 $Q$ 以及任意量子态 $|\\psi\\rangle$,\n",
"\n",
"$$\n",
"\\langle\\psi|U^\\dagger QV|\\psi\\rangle + \\langle\\psi|V^\\dagger QU|\\psi\\rangle = \\frac{1}{2} \\big(\\langle\\psi|(U+V)^\\dagger Q(U+V)|\\psi\\rangle - \\langle\\psi|(U-V)^\\dagger Q(U-V)|\\psi\\rangle \\big),\n",
"\\tag{9}\n",
"$$\n",
"\n",
"那么 \n",
"\n",
"$$\n",
"O'(\\theta) = \\frac{r}{2} \\big( \\left\\langle0\\right|R_x^{\\dagger}(\\theta) [I + \\frac{i}{2r}X]H[I - \\frac{i}{2r}X]R_x(\\theta)\\left|0\\right\\rangle - \\left\\langle0\\right| R_x^{\\dagger}(\\theta) [I - \\frac{i}{2r}X] H [I+\\frac{i}{2r}X] R_x(\\theta)\\left|0\\right\\rangle \\big).\n",
"\\tag{10}\n",
"$$\n",
"\n",
"由欧拉公式,且因为 $𝐺$ 有两个不同的本征值,我们可以把 $U(\\theta)$ 写做 $e^{-ia\\theta G} = Icos(r\\theta) - i\\frac{a}{r}Gsin(r\\theta)$,则 $R_x(\\theta) = Icos(r\\theta) - i\\frac{1}{2r}Xsin(r\\theta)$ [1],我们发现\n",
"\n",
"$$\n",
"R_x(\\frac{\\pi}{4r}) = I\\cos(\\frac{\\pi}{4}) - i\\frac{1}{2r}X\\sin(\\frac{\\pi}{4}) = \\frac{1}{\\sqrt2}(I-\\frac{i}{2r}X).\n",
"\\tag{11}\n",
"$$\n",
"\n",
"我们可以用同样的方法得到\n",
"\n",
"$$\n",
"R_x(-\\frac{\\pi}{4r}) = \\frac{1}{\\sqrt2}(I+\\frac{i}{2r}X).\n",
"\\tag{12}\n",
"$$\n",
"\n",
"因此,公式可以简化为\n",
"\n",
"$$\n",
"O'(\\theta) = r\\big[ \\left\\langle0\\right|R_x^{\\dagger}(\\theta+\\frac{\\pi}{4r})HR_x(\\theta+\\frac{\\pi}{4r})\\left|0\\right\\rangle - \\left\\langle0\\right| R_x^{\\dagger}(\\theta-\\frac{\\pi}{4r}) H R_x(\\theta-\\frac{\\pi}{4r})\\left|0\\right\\rangle \\big],\n",
"\\tag{13}\n",
"$$\n",
"\n",
"并得到最终公式\n",
"\n",
"$$\n",
"O'(\\theta) = r\\big[O(\\theta+\\frac{\\pi}{4r}) - O(\\theta-\\frac{\\pi}{4r}))\\big] = \\frac{1}{2}\\big[ O(\\theta + \\frac{\\pi}{2}) - O(\\theta - \\frac{\\pi}{2})\\big].\n",
"\\tag{14}\n",
"$$"
]
},
{
"cell_type": "markdown",
"id": "loaded-miami",
"metadata": {},
"source": [
"### Paddle Quantum 实现\n",
"我们可以通过 Paddle Quantum 的内置函数,用 Parameter-shift 方法计算电路的梯度。注:内置方法暂不支持指定输入态以及含噪电路。"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "conscious-namibia",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--Ry(0.389)----*----x----Ry(4.934)--\n",
" | | \n",
"--Ry(0.492)----x----*----Ry(1.200)--\n",
" \n",
"目标函数的梯度为: [-0.19714837 -0.76474861 0.11977042 -0.38304499]\n"
]
}
],
"source": [
"# 重复使用已定义好的 H 还有电路中的参数,注意确认定义可训练参数时已标明 stop_gradient=False\n",
"\n",
"# 构建含参电路\n",
"cir = U_theta(theta_tensor)\n",
"print(cir)\n",
"\n",
"gradients = cir.param_shift_gradient(H)\n",
"print(\"目标函数的梯度为:\", gradients.numpy())"
]
},
{
"cell_type": "markdown",
"id": "frank-disaster",
"metadata": {},
"source": [
"## Linear Combination of Unitary Gradients 方法\n",
"\n",
"使用 Paddle Quantum 构建参数化电路 $U(\\theta)$ 需要许多参数化的单量子比特和双量子比特旋转门如 $R_x$ 和 $CR_x$,因此我们可以将 $U(\\theta)$ 重写为 $U_{1}(\\theta_1)U_{2}(\\theta_2)\\cdots U_{m}(\\theta_m)$,其中 $U_i(\\theta_i)$ 是单量子比特或双量子比特门,$m$ 则是该电路 $U(\\theta)$ 中参数化门的数量。对 $\\theta$ 求导,我们有 $\\frac{\\partial U(\\theta)}{\\partial \\theta_i}=U_{1}(\\theta_1)U_{2}(\\theta_2)\\cdots\\frac{\\partial U_i{(\\theta_i)}}{\\partial \\theta_i}\\cdots U_{m}(\\theta_m)$。只要我们知道所有参数化门的 $\\frac{\\partial U_i{(\\theta_i)}}{\\partial \\theta_i}$,我们就可以很容易地得到所有参数的梯度 [2]。接下来我们介绍如何利用 Paddle Quantum 构造单量子比特门和双量子比特门梯度的电路。"
]
},
{
"cell_type": "markdown",
"id": "comparable-celebrity",
"metadata": {},
"source": [
"### 单量子比特门梯度\n",
"\n",
"让我们首先考虑单量子比特门。同样以 $R_x(\\theta)$ 为例,在前面的部分中,我们已经证明了 $\\frac{\\partial R_x(\\theta)}{\\partial \\theta}=-i\\frac{1}{2}XR_x(\\theta)$,这很容易使用电路构建。让我们尝试使用 Paddle Quantum 来实现它。"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "israeli-globe",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"原电路:\n",
"--Rx(1.047)--\n",
" \n",
"用于计算 Rx 梯度的电路:\n",
"------------x----Rx(1.047)--\n",
" | \n",
"--H---SDG---*--------H------\n",
" \n"
]
}
],
"source": [
"# 构建只带一个单量子比特门 Rx 的电路\n",
"theta = paddle.to_tensor(np.pi / 3, 'float64')\n",
"cir = UAnsatz(1)\n",
"cir.rx(theta, 0)\n",
"print('原电路:')\n",
"print(cir)\n",
"\n",
"print('用于计算 Rx 梯度的电路:')\n",
"# 这里的第一个参数是门的索引,第二个参数是门的名称\n",
"print(cir.pauli_rotation_gate_partial(0, 'rx'))"
]
},
{
"cell_type": "markdown",
"id": "upper-soviet",
"metadata": {},
"source": [
"对 $u3(\\theta, \\phi, \\lambda)$ 门做同样的事情要复杂得多,不过我们提供了内置方法来生成 Paddle Quantum 中所有参数化单量子比特门,即 $R_x$、$R_y$、$R_z$ 和 $u3$ 门,用于计算梯度所需的电路。"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "organizational-finger",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"原电路:\n",
"--U--\n",
" \n",
"用于计算 u3 梯度的电路:\n",
"------------z----U--\n",
" | \n",
"--H---SDG---*----H--\n",
" \n",
"--Rz(5.378)---------y----Ry(6.122)----Rz(3.188)--\n",
" | \n",
"------H-------SDG---*--------H-------------------\n",
" \n",
"--Rz(5.378)----Ry(6.122)----z----Rz(3.188)--\n",
" | \n",
"------H-----------SDG-------*--------H------\n",
" \n"
]
}
],
"source": [
"cir = UAnsatz(1)\n",
"theta = paddle.uniform([3], min=0.0, max=2*np.pi, dtype='float64')\n",
"cir.u3(theta[0], theta[1], theta[2], 0)\n",
"print('原电路:')\n",
"print(cir)\n",
"\n",
"print('用于计算 u3 梯度的电路:')\n",
"# 由于 u3 门含有3个参数,我们总共需要3个电路来计算 u3 门的梯度。\n",
"# 括号里的第一个参数是门的索引,第二个参数是所用可训练参数的索引\n",
"print(cir.u3_partial(0, 0))\n",
"print(cir.u3_partial(0, 1))\n",
"print(cir.u3_partial(0, 2))"
]
},
{
"cell_type": "markdown",
"id": "specific-minutes",
"metadata": {},
"source": [
"### 双量子比特门梯度\n",
"\n",
"Paddle Quantum 也提供了许多双量子比特参数化门。它们可以被分为两种类型:一种是像 $CR_x$ 这样的控制旋转门,另一种是像 $R_{xx}$ 这样的双量子比特旋转门。双量子比特旋转门的梯度电路很容易构建。我们以𝑅𝑥𝑥为例,遵循单量子比特旋转门的思想,我们首先将其写成 $R_{xx}(\\theta)=e^{-i\\frac{\\theta}{2}X\\otimes X}$,然后得到可以被转换为电路的公式:$\\frac{\\partial R_{xx}(\\theta)}{\\partial \\theta}=-i\\frac{1}{2}X\\otimes Xe^{-i\\frac{\\theta}{2}X\\otimes X}$。"
]
},
{
"cell_type": "markdown",
"id": "empirical-black",
"metadata": {},
"source": [
"我们在计算控制旋转门的梯度时需要小心。通常来说,我们需要两个电路来计算含一个参数的控制旋转门的梯度,例如 $CR_x(\\theta)$。\n",
"\n",
"$CR_x(\\theta)$ 可以被写成 $\\left|0\\right>\\left<0\\right|\\otimes I + \\left|1\\right>\\left<1\\right|\\otimes R_x(\\theta)$,所以它的梯度为:\n",
"\n",
"$$\n",
"\\frac{\\partial CR_x(\\theta)}{\\partial \\theta}=\\left|1\\right>\\left<1\\right|\\otimes \\frac{\\partial R_x(\\theta)}{\\partial \\theta}=-\\frac{i}{2}\\left|1\\right>\\left<1\\right|\\otimes Xe^{-i\\frac{\\theta}{2}X}.\n",
"\\tag{15}\n",
"$$\n",
"\n",
"然而,这个方程不能用一个电路直接表示。我们需要在这里使用一个小“技巧”,我们不直接使用这个公式,而是将其分解为两个项:\n",
"\n",
"$$\n",
"\\frac{\\partial CR_x(\\theta)}{\\partial \\theta}=-\\frac{i}{4}(\\left|0\\right>\\left<0\\right|\\otimes I + \\left|1\\right>\\left<1\\right|\\otimes R_x(\\theta))I\\otimes X + \\frac{i}{4}(\\left|0\\right>\\left<0\\right|\\otimes I + \\left|1\\right>\\left<1\\right|\\otimes R_x(\\theta))Z\\otimes X.\n",
"\\tag{16}\n",
"$$ \n",
"\n",
"你可以验证此公式是否与前一个公式等效。通过这种做法,我们可以使用两个电路来计算 $CR_x$ 的梯度。"
]
},
{
"cell_type": "markdown",
"id": "lesser-evanescence",
"metadata": {},
"source": [
"同样,我们提供了内置方法来生成 Paddle Quantum 中所有参数化双量子比特门,即 $R_{xx}$、$R_{yy}$、$R_{zz}$、$CR_x$、$CR_y$、$CR_z$ 和 $CU$ 门,用于计算梯度所需的电路。"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "promising-arnold",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"原电路:\n",
"--*----Rzz(4.42)--------*------\n",
" | | | \n",
"--U----Rzz(4.42)----Ry(4.082)--\n",
" \n",
"用于计算 rzz 门梯度的电路:\n",
"--*---------z---------Rzz(4.42)--------*------\n",
" | | | | \n",
"--U---------|----z----Rzz(4.42)----Ry(4.082)--\n",
" | | \n",
"--H---SDG---*----*--------H-------------------\n",
" \n",
"用于计算 cry 门梯度的电路:\n",
"--*----Rzz(4.42)----y--------*------\n",
" | | | | \n",
"--U----Rzz(4.42)----|----Ry(4.082)--\n",
" | \n",
"--H-------SDG-------*--------H------\n",
" \n",
"--*----Rzz(4.42)---------y--------*------\n",
" | | | | \n",
"--U----Rzz(4.42)----z----|----Ry(4.082)--\n",
" | | \n",
"--H--------S--------*----*--------H------\n",
" \n"
]
}
],
"source": [
"theta = paddle.uniform([5], min=0.0, max=2*np.pi, dtype='float64')\n",
"cir = UAnsatz(2)\n",
"cir.cu(theta[0], theta[1], theta[2], [0, 1])\n",
"cir.rzz(theta[3], [0, 1])\n",
"cir.cry(theta[4], [0, 1])\n",
"print('原电路:')\n",
"print(cir)\n",
"\n",
"# 括号里的第一个参数是门的索引,第二个参数是所用可训练参数的索引\n",
"# 由于 cu 门含有三个参数,每个参数需要两个电路,所以我们总共有6个电路。\n",
"# 用于计算 cu 门梯度的电路:\n",
"cu3_00 = cir.cu3_partial(0, 0)[0]\n",
"cu3_01 = cir.cu3_partial(0, 0)[1]\n",
"cu3_10 = cir.cu3_partial(0, 1)[0]\n",
"cu3_11 = cir.cu3_partial(0, 1)[1]\n",
"cu3_20 = cir.cu3_partial(0, 2)[0]\n",
"cu3_21 = cir.cu3_partial(0, 2)[1]\n",
"\n",
"# 这里的第一个参数是门的索引,第二个参数是门的名称\n",
"print('用于计算 rzz 门梯度的电路:')\n",
"print(cir.pauli_rotation_gate_partial(1, 'RZZ_gate'))\n",
"\n",
"# 这里的第一个参数是门的索引,第二个参数是门的名称\n",
"print('用于计算 cry 门梯度的电路:')\n",
"print(cir.control_rotation_gate_partial(2, 'cry')[0])\n",
"print(cir.control_rotation_gate_partial(2, 'cry')[1])"
]
},
{
"cell_type": "markdown",
"id": "outer-family",
"metadata": {},
"source": [
"现在我们已经为计算梯度准备了所有单独的电路,下一步是计算梯度的精确值。怎么做?我们需要将这些电路传入我们的目标函数中,结果就是我们想要的梯度。而对于像 $CR_x$ 这样的门,我们要将两个电路结果的平均值作为梯度。我们还提供了一个用 Linear Combination 计算参数化电路梯度的内置方法(注:内置方法暂不支持指定输入态以及含噪电路):"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "viral-duncan",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--U----*----x----Ry(4.182)--\n",
" | | \n",
"--U----x----*----Ry(1.693)--\n",
" \n",
"目标函数的梯度为: [ 5.55111512e-17 -7.96572821e-02 -6.20103361e-02 5.55111512e-17\n",
" 1.91859815e-01 -3.15824394e-01 4.24351014e-01 -5.25105122e-01]\n"
]
}
],
"source": [
"# 为我们的电路随机生成参数\n",
"theta = paddle.uniform(shape=[8], dtype='float64', min=0.0, max=np.pi * 2)\n",
"theta.stop_gradient = False\n",
"\n",
"# 构建 U(theta) 电路\n",
"cir = UAnsatz(2)\n",
"cir.complex_entangled_layer(theta[:6], 1)\n",
"cir.ry(theta=theta[6], which_qubit=0)\n",
"cir.ry(theta=theta[7], which_qubit=1)\n",
"cir.run_state_vector()\n",
"print(cir)\n",
"\n",
"# 使用我们的内置方法计算梯度\n",
"# 我们传入目标函数中使用的哈密顿量 H\n",
"gradient = cir.linear_combinations_gradient(H, shots=0)\n",
"print(\"目标函数的梯度为:\", gradient.numpy())"
]
},
{
"cell_type": "markdown",
"id": "documented-shooting",
"metadata": {},
"source": [
"## 应用: Paddle Quantum 模拟变分量子本征求解器(VQE)\n",
"\n",
"变分量子本征求解器(variational quantum eigensolver, VQE)[3] 可以用变分量子电路来计算某个给定哈密顿量的基态能量,关于其具体的原理和背景在之前的教程 [变分量子本征求解器](../quantum_simulation/VQE_CN.ipynb) 中有详细的讲解,感兴趣的读者可以前往阅读。\n",
"\n",
"在这里,我们尝试用一个简单的 VQE 电路来求解氢分子 $H_2$ 的哈密顿量的基态能量。在这个过程中,我们将使用上面介绍的 Parameter-shift 方法来计算梯度。"
]
},
{
"cell_type": "markdown",
"id": "joined-frontier",
"metadata": {},
"source": [
"### 使用 Paddle 的优化器\n",
"\n",
"首先,我们将使用 Paddle 的优化器 Adam 来运行我们的示例。我们可以选择有限差分法或 Parameter-shift 作为计算梯度的方法。"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "short-script",
"metadata": {},
"outputs": [],
"source": [
"from paddle_quantum.VQE.chemistrysub import H2_generator\n",
"from paddle_quantum.expecval import ExpecVal\n",
"\n",
"# 生成哈密顿量\n",
"pauli_str, N = H2_generator()\n",
"H = Hamiltonian(pauli_str)\n",
"\n",
"# 超参数设置\n",
"ITR = 80 # 设置训练的总迭代次数\n",
"LR = 0.4 # 设置学习速率\n",
"D = 2 # 设置量子神经网络中重复计算模块的深度 Depth\n",
"\n",
"def U_theta(theta, Hamiltonian, N, D):\n",
" \"\"\"\n",
" Quantum Neural Network\n",
" \"\"\"\n",
" # 按照量子比特数量/网络宽度初始化量子神经网络\n",
" cir = UAnsatz(N)\n",
"\n",
" # 内置的 {R_y + CNOT} 电路模板\n",
" theta = paddle.reshape(theta, [D+1, N, 1])\n",
" cir.real_entangled_layer(theta[:D], D)\n",
"\n",
" # 铺上最后一列 R_y 旋转门\n",
" for i in range(N):\n",
" cir.ry(theta=theta[D][i][0], which_qubit=i)\n",
"\n",
" # 量子神经网络作用在默认的初始态 |0...0> 上\n",
" cir.run_state_vector()\n",
" \n",
" return cir"
]
},
{
"cell_type": "markdown",
"id": "extraordinary-northern",
"metadata": {},
"source": [
"在前向传播机制中,我们使用 Parameter-shift 方法得到梯度,并计算期望值。如果你想使用有限差分方法进行尝试,可以将传入 Expecval 中的方法更改为 'finite_diff'。"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "worse-waters",
"metadata": {},
"outputs": [],
"source": [
"class StateNet(paddle.nn.Layer):\n",
"\n",
" def __init__(self, cir):\n",
" super(StateNet, self).__init__()\n",
" \n",
" self.cir = cir\n",
" params = cir.get_param()\n",
" \n",
" # 用电路里的参数初始化 theta 参数列表\n",
" self.theta = self.create_parameter(shape=[len(params)], \n",
" default_initializer=paddle.nn.initializer.Assign(params),\n",
" dtype='float32', is_bias=False)\n",
" \n",
" # 定义损失函数和前向传播机制\n",
" def forward(self):\n",
" # 用 Parameter-shift 梯度计算损失函数/期望值\n",
" loss = ExpecVal.apply(self.cir, self.theta.cast('float64'), 'param_shift', H, shots=0)\n",
" \n",
" return loss, self.cir"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "informative-sword",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"iter: 20 loss: -1.0855\n",
"iter: 20 Ground state energy: -1.0855 Ha\n",
"iter: 40 loss: -1.1286\n",
"iter: 40 Ground state energy: -1.1286 Ha\n",
"iter: 60 loss: -1.1356\n",
"iter: 60 Ground state energy: -1.1356 Ha\n",
"iter: 80 loss: -1.1361\n",
"iter: 80 Ground state energy: -1.1361 Ha\n",
"\n",
"训练后的电路:\n",
"--Ry(1.554)----*--------------x----Ry(1.567)----*--------------x----Ry(3.153)--\n",
" | | | | \n",
"--Ry(4.700)----x----*---------|----Ry(4.705)----x----*---------|----Ry(1.506)--\n",
" | | | | \n",
"--Ry(3.084)---------x----*----|----Ry(1.785)---------x----*----|----Ry(1.550)--\n",
" | | | | \n",
"--Ry(3.124)--------------x----*----Ry(1.576)--------------x----*----Ry(6.263)--\n",
" \n",
"\n",
"电路计算得到的基态能量是: [-1.13611258] Ha\n",
"真实的基态能量为: -1.13618 Ha\n"
]
}
],
"source": [
"# 初始化电路中的 theta 参数列表,并用 [0, 2*pi] 的均匀分布来填充初始值\n",
"theta = paddle.to_tensor(np.random.uniform(0.0, 2*np.pi, (D+1) * N), stop_gradient=False)\n",
"\n",
"# 创建电路\n",
"cir = U_theta(theta, H, N, D)\n",
"\n",
"# 确定网络的参数维度\n",
"net = StateNet(cir)\n",
"\n",
"# 一般来说,我们利用 Adam 优化器来获得相对好的收敛,\n",
"# 当然你可以改成 SGD 或者是 RMS prop.\n",
"opt = paddle.optimizer.Adam(learning_rate=LR, parameters=net.parameters())\n",
"\n",
"# 记录优化结果\n",
"summary_iter, summary_loss = [], []\n",
"\n",
"# 优化循环\n",
"for itr in range(1, ITR + 1):\n",
"\n",
" # 前向传播计算损失函数\n",
" loss, cir = net()\n",
"\n",
" # 在动态图机制下,反向传播极小化损失函数\n",
" loss.backward()\n",
" opt.minimize(loss)\n",
" opt.clear_grad()\n",
"\n",
" # 更新优化结果\n",
" summary_loss.append(loss.numpy())\n",
" summary_iter.append(itr)\n",
"\n",
" # 打印结果\n",
" if itr % 20 == 0:\n",
" print(\"iter:\", itr, \"loss:\", \"%.4f\" % loss.numpy())\n",
" print(\"iter:\", itr, \"Ground state energy:\", \"%.4f Ha\" \n",
" % loss.numpy())\n",
" if itr == ITR:\n",
" print(\"\\n训练后的电路:\") \n",
" print(cir)\n",
"\n",
"print('\\n电路计算得到的基态能量是: ', summary_loss[-1], \"Ha\")\n",
"print('真实的基态能量为: ', -1.13618, \"Ha\")"
]
},
{
"cell_type": "markdown",
"id": "normal-graham",
"metadata": {},
"source": [
"我们可以看到我们得到的基态能量接近理论值。"
]
},
{
"cell_type": "markdown",
"id": "exclusive-television",
"metadata": {},
"source": [
"### 使用 SciPy 的优化器\n",
"\n",
"我们还将演示如何在 Paddle Quantum 里使用 SciPy 的优化器实现 VQE。对于这个例子,我们将使用共轭梯度法 (CG) 优化器和 Linear\n",
"Combination 方法来求解我们哈密顿量的基态能量。\n",
"\n",
"此外,我们还支持使用 Newton-CG、Powell 和 SLSQP 方法的 SciPy 优化器。"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "august-czech",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"loss: [-0.32223041]\n",
"loss: [-0.41429542]\n",
"loss: [-0.47219271]\n",
"loss: [-0.58479231]\n",
"loss: [-0.70924513]\n",
"loss: [-0.88441433]\n",
"loss: [-0.97408026]\n",
"loss: [-1.09436626]\n",
"loss: [-1.10973582]\n",
"loss: [-1.11870276]\n",
"loss: [-1.11946487]\n",
"loss: [-1.11967869]\n",
"loss: [-1.1200893]\n",
"loss: [-1.12525337]\n",
"loss: [-1.12765788]\n",
"loss: [-1.13375079]\n",
"loss: [-1.13480047]\n",
"loss: [-1.1358785]\n",
"loss: [-1.13605957]\n",
"loss: [-1.13608542]\n",
"loss: [-1.1361108]\n",
"loss: [-1.13616537]\n",
"loss: [-1.1361784]\n",
"loss: [-1.13618041]\n",
"loss: [-1.13618262]\n",
"loss: [-1.13618313]\n",
"loss: [-1.1361833]\n",
"loss: [-1.13618356]\n",
"loss: [-1.13618403]\n",
"loss: [-1.13618415]\n",
"loss: [-1.13618419]\n",
"loss: [-1.13618435]\n",
"loss: [-1.13618448]\n",
"loss: [-1.13618456]\n",
"loss: [-1.13618468]\n",
"loss: [-1.13618483]\n",
"loss: [-1.13618528]\n",
"loss: [-1.13618545]\n",
"loss: [-1.13618565]\n",
"loss: [-1.13618618]\n",
"loss: [-1.13618656]\n",
"loss: [-1.13618694]\n",
"loss: [-1.13618745]\n",
"loss: [-1.13618776]\n",
"loss: [-1.13618788]\n",
"loss: [-1.13618801]\n",
"loss: [-1.13618838]\n",
"loss: [-1.13618865]\n",
"loss: [-1.13618885]\n",
"loss: [-1.13618901]\n",
"loss: [-1.13618907]\n",
"loss: [-1.13618909]\n",
"loss: [-1.13618909]\n",
"loss: [-1.13618911]\n",
"loss: [-1.13618911]\n",
"loss: [-1.13618911]\n",
"loss: [-1.13618912]\n",
"loss: [-1.13618912]\n",
"loss: [-1.13618913]\n",
"loss: [-1.13618915]\n",
"loss: [-1.13618918]\n",
"loss: [-1.13618922]\n",
"loss: [-1.13618925]\n",
"loss: [-1.13618926]\n",
"loss: [-1.13618933]\n",
"loss: [-1.1361894]\n",
"loss: [-1.13618941]\n",
"loss: [-1.13618941]\n",
"loss: [-1.13618942]\n",
"loss: [-1.13618943]\n",
"loss: [-1.13618943]\n",
"loss: [-1.13618943]\n",
"Optimization terminated successfully.\n",
"真实的基态能量为: -1.13618 Ha\n"
]
}
],
"source": [
"from paddle_quantum.optimizer import ConjugateGradient\n",
"\n",
"# 创建电路\n",
"cir = U_theta(theta, H, N, D)\n",
"\n",
"optimizer = ConjugateGradient(cir, H, shots=0, grad_func_name='linear_comb')\n",
"optimizer.minimize(iterations=80)\n",
"print('真实的基态能量为: ', -1.13618, \"Ha\")"
]
},
{
"cell_type": "markdown",
"id": "hairy-mining",
"metadata": {},
"source": [
"## 总结\n",
"\n",
"本教程介绍的三种计算参数化量子电路梯度的方法中,可以看到有限差分方法和 Parameter-shift 方法具有相似的形式:它们都需要对每个参数进行两次函数评估。这些方法的好处是可以在不了解电路或目标函数的情况下计算梯度。我们可以将它们视为一个黑匣子,只需输入不同的参数即可获得梯度。我们在这两者之间的首选是 Parameter-shift 方法,因为它的结果是一个解析梯度,而有限差分方法只能得到梯度的估算值。但是,Parameter-shift 仅适用于可以由具有两个不同本征值的 $G$ 生成的 $U(\\theta)$:$U(\\theta) = e^{-ia\\theta G}$,或适用于可以被分解变成这种形式的门的乘积的 $U(\\theta)$。\n",
"\n",
"使用 Linear Combination 方法来计算给定电路的梯度可能是最直接的方法。我们可以在数学形式下对酉门求微分,并使用电路来表示结果公式。与其他两种方法一样,这种方法所需的电路数量与原始电路中的参数数量成正比。我们甚至可以为简单的门(如 $R_x、R_{xx}$ 等)构建一个单独的电路来计算梯度。但是,请注意,我们在此方法中会使用辅助量子比特。此外,你可能已经注意到,这种方法在复杂的电路上需要运行很长时间。这是因为随着量子比特数量的增加,用于表示多量子比特门一阶微分的电路数量也会增加。"
]
},
{
"cell_type": "markdown",
"id": "adjusted-royalty",
"metadata": {},
"source": [
"_______\n",
"\n",
"## 参考文献\n",
"\n",
"[1] Crooks, Gavin E. \"Gradients of parameterized quantum gates using the parameter-shift rule and gate decomposition.\" [arXiv preprint arXiv:1905.13311 (2019)](https://arxiv.org/abs/1905.13311).\n",
"\n",
"[2] Somma, Rolando, et al. \"Simulating physical phenomena by quantum networks.\" [Physical Review A 65.4 (2002): 042323](https://arxiv.org/abs/quant-ph/0108146).\n",
"\n",
"[3] Peruzzo, Alberto, et al. \"A variational eigenvalue solver on a photonic quantum processor.\" [Nature communications 5.1 (2014): 1-7](https://www.nature.com/articles/ncomms5213).\n",
"\n",
"[4] Schuld, Maria, et al. \"Evaluating analytic gradients on quantum hardware.\" [Physical Review A 99.3 (2019): 032331](https://arxiv.org/abs/1811.11184)."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.10"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
{
"cells": [
{
"cell_type": "markdown",
"id": "e8062c41",
"metadata": {},
"source": [
"# Calculating Gradient Using Quantum Circuit\n",
"\n",
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
]
},
{
"cell_type": "markdown",
"id": "d8947f99",
"metadata": {},
"source": [
"## Overview\n",
"\n",
"When using variational quantum algorithms like [Variational Quantum Eigensolver (VQE)](../quantum_simulation/VQE_EN.ipynb) and [Quantum Approximate Optimization Algorithm (QAOA)](../combinatorial_optimization/QAOA_EN.ipynb), which involves varying parameters of quantum circuit to minimize an objective function of interest, it brings out an important question - what are the ways to calculate the gradient of a parameterized quantum circuit? Since the objective function is being evaluated using a quantum circuit, it's necessary to evaluate its gradient using quantum algorithms as well. Compared with computing gradient classically, this is more challenging for sure. Below we provide three different methods to accomplish this task on a quantum computer. And here we demonstrate the code and simulate their effect of running on a quantum computer using Paddle Quantum."
]
},
{
"cell_type": "markdown",
"id": "61395a57",
"metadata": {},
"source": [
"## Introduction\n",
"\n",
"Suppose the objective function is the typical parameterized cost function used in VQA: $O(\\theta) = \\left\\langle00\\right| U^{\\dagger}(\\theta)HU(\\theta) \\left|00\\right\\rangle$ where H is a Hamiltonian, $U(\\theta)$ represents the parameterized quantum circuit and $\\theta = [\\theta_1, \\theta_2, \\dots, \\theta_n]$ is a list of trainable parameters in the circuit, then our goal is to find\n",
"\n",
"$$\n",
"\\nabla O(\\theta) = \\begin{bmatrix} \\frac{\\partial O}{\\partial \\theta_1} \\\\ \\frac{\\partial O}{\\partial \\theta_2}\\\\ \\vdots\\\\ \\frac{\\partial O}{\\partial \\theta_n} \\end{bmatrix}.\n",
"\\tag{1}\n",
"$$\n",
"\n",
"First, let's import all the required packages."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "5e438617",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import paddle\n",
"from paddle_quantum.circuit import UAnsatz\n",
"from paddle_quantum.utils import pauli_str_to_matrix, Hamiltonian\n",
"import warnings\n",
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "markdown",
"id": "ec29c111",
"metadata": {},
"source": [
"Then, let's construct our $U(\\theta)$ and Hamiltonian $H$ of the objective function $O(\\theta) = \\left\\langle00\\right| U^{\\dagger}(\\theta)HU(\\theta) \\left|00\\right\\rangle$.\n",
"\n",
"We will demonstrate our example on a 2-qubit quantum circuit, constructing $U(\\theta)$ with randomly generated theta of size four. We choose our Hamiltonian H to be $H = Z \\otimes Z$."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "00c29cb2",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Hamiltonian H: \n",
" [[ 1.+0.j 0.+0.j 0.+0.j 0.+0.j]\n",
" [ 0.+0.j -1.+0.j 0.+0.j 0.+0.j]\n",
" [ 0.+0.j 0.+0.j -1.+0.j 0.+0.j]\n",
" [ 0.+0.j 0.+0.j 0.+0.j 1.+0.j]]\n",
"\n",
"U(theta):\n",
"--Ry(5.690)----*----x----Ry(3.107)--\n",
" | | \n",
"--Ry(2.521)----x----*----Ry(0.437)--\n",
" \n"
]
}
],
"source": [
"# Define Hamiltonian H\n",
"pauli_str = [[1.0, 'Z0,Z1']]\n",
"H = Hamiltonian(pauli_str)\n",
"\n",
"# Randomly generate parameters in range from 0 to 2*PI\n",
"theta_np = np.random.uniform(0, 2 * np.pi, 4)\n",
"# Warning: Note that when defining theta, if we mark stop_gradient=False, then this parameter is trainable; \n",
"# otherwise, the parameter will be treated as constant, its gradient will not be calculated and it will not be updated in training process.\n",
"theta_tensor = paddle.to_tensor(theta_np, 'float64', stop_gradient=False)\n",
"\n",
"def U_theta(theta):\n",
" cir = UAnsatz(2)\n",
" cir.ry(theta[0], 0)\n",
" cir.ry(theta[1], 1)\n",
" cir.cnot([0, 1])\n",
" cir.cnot([1, 0])\n",
" cir.ry(theta[2], 0)\n",
" cir.ry(theta[3], 1)\n",
" cir.run_state_vector()\n",
" return cir\n",
"\n",
"print('Hamiltonian H: \\n', H.construct_h_matrix())\n",
"print('\\nU(theta):')\n",
"print(U_theta(theta_tensor))"
]
},
{
"cell_type": "markdown",
"id": "fc5028f6",
"metadata": {},
"source": [
"## Finite Difference Method\n",
"\n",
"The Finite difference method is one of the most traditional and common numerical methods to estimate the gradient of a function. The main idea is that the error of the derivative of a function $f(x)$ tends to zero as $h$ tends to zero: \n",
"\n",
"$$\n",
"f'(x)= \\lim_{h \\to 0}\\frac{f(x+h) - f(x)}{h}.\n",
"\\tag{2}\n",
"$$\n",
"\n",
"By choosing a sufficiently small $h$, we can get a good approximation of the derivative.\n",
"\n",
"For example, by using one type of finite difference, the central finite difference method, the objective function's gradient will be\n",
"\n",
"$$\n",
"\\nabla O(\\theta) \\approx \\frac{O(\\theta+\\delta) - O(\\theta-\\delta)}{2\\delta} = \\frac{\\left\\langle00\\right| U^{\\dagger}(\\theta + \\delta)HU(\\theta + \\delta) \\left|00\\right\\rangle - \\left\\langle00\\right| U^{\\dagger}(\\theta - \\delta)HU(\\theta-\\delta) \\left|00\\right\\rangle)}{2\\delta}.\n",
"\\tag{3}\n",
"$$\n",
"\n",
"When implementing this, we can simply loop through the parameter list, shift the specific parameter in the original circuit and evaluate the objective function over and over again. There's no need to build extra circuits or using extra qubits.\n",
"\n",
"Using Paddle Quantum's built-in method, we can build the circuit of $U(\\theta)$, and compute the finite-difference gradient easily by passing in the corresponding Hamiltonian H and the delta. Note: The built-in method currently does not support assigning an input state or running on a noisy circuit."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "c836f71e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--Ry(5.690)----*----x----Ry(3.107)--\n",
" | | \n",
"--Ry(2.521)----x----*----Ry(0.437)--\n",
" \n",
"Gradient of this objective function is: [-0.79156126 0.12584221 -0.26541629 0.78068313]\n"
]
}
],
"source": [
"# We reuse the predefined Hamiltonian H and parameters of the circuit. \n",
"# Again, be sure to mark stop_gradient=False when defining tensor for parameters of the circuit\n",
"\n",
"# Constructing circuit U(theta)\n",
"cir = U_theta(theta_tensor)\n",
"print(cir)\n",
"\n",
"# Calculating finite-difference gradient\n",
"gradients = cir.finite_difference_gradient(H, delta=0.01)\n",
"print(\"Gradient of this objective function is: \", gradients.numpy())"
]
},
{
"cell_type": "markdown",
"id": "8055ed76",
"metadata": {},
"source": [
"## Parameter-shift Method\n",
"Again, we use this objective function $O(\\theta) = \\left\\langle0\\right| U^{\\dagger}(\\theta)HU(\\theta) \\left|0\\right\\rangle$ as our example. If $U(\\theta)$ can be written as $e^{-ia\\theta G}$ where $G$ has two unique eigenvalues $\\lambda_1$ and $\\lambda_2$, we can apply parameter-shift method to find its gradient [1]:\n",
"\n",
"$$\n",
"\\nabla O(\\theta) = r \\left[O(\\theta+\\frac{\\pi}{4r}) - O(\\theta-\\frac{\\pi}{4r})\\right],\n",
"\\tag{4}\n",
"$$ \n",
"\n",
"where shift constant $r = \\frac{a}{2} (\\lambda_2 - \\lambda_1)$. Note that we're getting a theoretically exact gradient instead of an estimation like finite difference gradient. Moreover, this method doesn't require the construction of new circuits or adding ancilla qubits. Evaluations could be done just by changing the parameters inside the circuit.\n",
"\n",
"The fundamental rotation gates provided by Paddle Quantum are $R_x(\\theta), R_y(\\theta), R_z(\\theta)$ gates, which can be written as $e^{-i\\frac{1}{2}\\theta X}, e^{-i\\frac{1}{2}\\theta Y}, e^{-i\\frac{1}{2}\\theta Z}$ respectively. Since the eigenvalues of $X$, $Y$, and $Z$ gates are unique, which are -1 and 1, it's not hard to see that $r = \\frac{1}{2}$, and the gradient of those gates is just\n",
"\n",
"$$\n",
"\\frac{1}{2}[ O(\\theta + \\frac{\\pi}{2}) - O(\\theta - \\frac{\\pi}{2})].\n",
"\\tag{5}\n",
"$$\n",
"\n",
"We will demonstrate this formula's derivation using a single rotation gate $R_x$."
]
},
{
"cell_type": "markdown",
"id": "81a1defe",
"metadata": {},
"source": [
"### Derivation\n",
"\n",
"In this section, we will go through all the steps and arrive at the resulting formula for calculating $R_x$ gate's derivative. Using $R_x(\\theta)$ as $U(\\theta)$, we have\n",
"\n",
"$$\n",
"O(\\theta) = \\left\\langle0\\right| R_x^{\\dagger}(\\theta)HR_x(\\theta) \\left|0\\right\\rangle.\n",
"\\tag{6}\n",
"$$\n",
"\n",
"Given that $R_x(\\theta) = e^{-i\\frac{1}{2}\\theta X}$, where $X$ is the Pauli-X matrix, we know $\\frac{\\partial}{\\partial \\theta} R_x(\\theta) =-i\\frac{1}{2}Xe^{-i\\frac{\\theta}{2}X}=-i\\frac{1}{2}XR_x(\\theta)$. Using the product rule, its derivative can be written as\n",
"\n",
"$$\n",
"O'(\\theta) = \\left\\langle0\\right| [\\frac{i}{2}X] R_x^{\\dagger}(\\theta)HR_x(\\theta)\\left|0\\right\\rangle + \\left\\langle0\\right| R_x^{\\dagger}(\\theta)H [-\\frac{i}{2}X] R_x(\\theta)\\left|0\\right\\rangle.\n",
"\\tag{7}\n",
"$$\n",
"\n",
"We take out the shift constant $r$ in front for the purpose of later rearranging the equation using some trick, where $r$ for $R_x$ gate is $\\frac{1}{2}$. We have\n",
"\n",
"$$\n",
"O'(\\theta) = r \\left\\langle0\\right| [\\frac{i}{2r}X] R_x^{\\dagger}(\\theta)HR_x(\\theta)\\left|0\\right\\rangle + \\left\\langle0\\right| R_x^{\\dagger}(\\theta)H [-\\frac{i}{2r}X] R_x(\\theta)\\left|0\\right\\rangle.\n",
"\\tag{8}\n",
"$$\n",
"\n",
"Since we recognize that for any operators $U$, $V$ and $Q$, and for an arbitrary state $|\\psi\\rangle$, \n",
"\n",
"$$\n",
"\\langle\\psi|U^\\dagger QV|\\psi\\rangle + \\langle\\psi|V^\\dagger QU|\\psi\\rangle = \\frac{1}{2} \\big(\\langle\\psi|(U+V)^\\dagger Q(U+V)|\\psi\\rangle - \\langle\\psi|(U-V)^\\dagger Q(U-V)|\\psi\\rangle \\big),\n",
"\\tag{9}\n",
"$$\n",
"\n",
"we get \n",
"\n",
"$$\n",
"O'(\\theta) = \\frac{r}{2} \\big( \\left\\langle0\\right|R_x^{\\dagger}(\\theta) [I + \\frac{i}{2r}X]H[I - \\frac{i}{2r}X]R_x(\\theta)\\left|0\\right\\rangle - \\left\\langle0\\right| R_x^{\\dagger}(\\theta) [I - \\frac{i}{2r}X] H [I+\\frac{i}{2r}X] R_x(\\theta)\\left|0\\right\\rangle \\big).\n",
"\\tag{10}\n",
"$$\n",
"\n",
"Using Euler's identity and knowing that $G$ has two unique eigenvalues, we can rewrite $U(\\theta)$ as $e^{-ia\\theta G} = I\\cos(r\\theta) - i\\frac{a}{r}G\\sin(r\\theta)$ [1]. Thus, we have $R_x(\\theta) = I\\cos(r\\theta) - i\\frac{1}{2r}X\\sin(r\\theta)$, we notice that \n",
"\n",
"$$\n",
"R_x(\\frac{\\pi}{4r}) = I\\cos(\\frac{\\pi}{4}) - i\\frac{1}{2r}X\\sin(\\frac{\\pi}{4}) = \\frac{1}{\\sqrt2}(I-\\frac{i}{2r}X).\n",
"\\tag{11}\n",
"$$\n",
"\n",
"We can use the same method to get \n",
"\n",
"$$\n",
"R_x(-\\frac{\\pi}{4r}) = \\frac{1}{\\sqrt2}(I+\\frac{i}{2r}X).\n",
"\\tag{12}\n",
"$$\n",
"\n",
"Thus, the equation can be simplified to\n",
"\n",
"$$\n",
"O'(\\theta) = r\\big[ \\left\\langle0\\right|R_x^{\\dagger}(\\theta+\\frac{\\pi}{4r})HR_x(\\theta+\\frac{\\pi}{4r})\\left|0\\right\\rangle - \\left\\langle0\\right| R_x^{\\dagger}(\\theta-\\frac{\\pi}{4r}) H R_x(\\theta-\\frac{\\pi}{4r})\\left|0\\right\\rangle \\big],\n",
"\\tag{13}\n",
"$$\n",
"\n",
"and get the final formula,\n",
"\n",
"$$\n",
"O'(\\theta) = r\\big[O(\\theta+\\frac{\\pi}{4r}) - O(\\theta-\\frac{\\pi}{4r}))\\big] = \\frac{1}{2}\\big[ O(\\theta + \\frac{\\pi}{2}) - O(\\theta - \\frac{\\pi}{2})\\big].\n",
"\\tag{14}\n",
"$$"
]
},
{
"cell_type": "markdown",
"id": "c460666d",
"metadata": {},
"source": [
"### Paddle Quantum implementation\n",
"Here we demonstrate how to use Paddle Quantum's built-in parameter shift method to calculate the gradient. Note: The built-in method currently does not support assigning an input state or running on a noisy circuit."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "18d78ca9",
"metadata": {
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--Ry(5.690)----*----x----Ry(3.107)--\n",
" | | \n",
"--Ry(2.521)----x----*----Ry(0.437)--\n",
" \n",
"Gradient of this objective function is: [-0.79156457 0.12584274 -0.2654174 0.7806864 ]\n"
]
}
],
"source": [
"# We reuse the predefined Hamiltonian H and parameters of the circuit. \n",
"# Again, be sure to mark stop_gradient=False when defining tensor for parameters of the circuit.\n",
"\n",
"# Constructing circuit U(theta)\n",
"cir = U_theta(theta_tensor)\n",
"print(cir)\n",
"\n",
"gradients = cir.param_shift_gradient(H)\n",
"print(\"Gradient of this objective function is: \", gradients.numpy())"
]
},
{
"cell_type": "markdown",
"id": "2490a251",
"metadata": {},
"source": [
"## Linear Combination of Unitary Gradients\n",
"\n",
"Building a parameterized circuit $U(\\theta)$ using Paddle Quantum requires many parameterized one-qubit and two-qubit gates like $R_x$ and $CR_x$. So we can rewrite $U(\\theta)$ as $U_{1}(\\theta_1)U_{2}(\\theta_2)\\cdots U_{m}(\\theta_m)$, where $U_i(\\theta_i)$ is one of the one-qubit and two-qubit gates and $m$ is the total number of parameterized gates in this circuit $U(\\theta)$. To get the gradient of an individual parameter, we consider this equation $\\frac{\\partial U(\\theta)}{\\partial \\theta_i}=U_{1}(\\theta_1)U_{2}(\\theta_2)\\cdots\\frac{\\partial U_i{(\\theta_i)}}{\\partial \\theta_i}\\cdots U_{m}(\\theta_m)$. We notice that as long as we know $\\frac{\\partial U_i{(\\theta_i)}}{\\partial \\theta_i}$ for all parameterized gates, we can get the gradients for all parameters easily [2]."
]
},
{
"cell_type": "markdown",
"id": "8c98fb33",
"metadata": {},
"source": [
"### Single qubit gate gradient\n",
"\n",
"Let's consider single qubit gates first. We also take $R_x(\\theta)$ as an example. In the previous sections, we've already shown that $\\frac{\\partial R_x(\\theta)}{\\partial \\theta}=-i\\frac{1}{2}XR_x(\\theta)$, which can be easily constructed using a circuit. Let's try to implement it using Paddle Quantum."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "97435cf4",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Original circuit: \n",
"--Rx(1.047)--\n",
" \n",
"The circuit for gradient of Rx: \n",
"------------x----Rx(1.047)--\n",
" | \n",
"--H---SDG---*--------H------\n",
" \n"
]
}
],
"source": [
"# Construct the circuit with a single one-qubit gate Rx\n",
"theta = paddle.to_tensor(np.pi / 3, 'float64')\n",
"cir = UAnsatz(1)\n",
"cir.rx(theta, 0)\n",
"print('Original circuit: ')\n",
"print(cir)\n",
"\n",
"print('The circuit for gradient of Rx: ')\n",
"# The first parameter here is the index of the gate, the second parameter is the name of the gate\n",
"print(cir.pauli_rotation_gate_partial(0, 'rx'))"
]
},
{
"cell_type": "markdown",
"id": "e3d30aa3",
"metadata": {},
"source": [
"It's a lot more complicated to do the same for a $u3(\\theta, \\phi, \\lambda)$ gate. But don't worry, we provide built-in methods for generating the circuits needed for calculating gradients of all parameterized single qubit gates in Paddle Quantum, i.e., $R_x$, $R_y$, $R_z$, and $u3$. "
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "6a7e5f3c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Original circuit: \n",
"--U--\n",
" \n",
"Circuits for gradient of u3: \n",
"------------z----U--\n",
" | \n",
"--H---SDG---*----H--\n",
" \n",
"--Rz(4.706)---------y----Ry(3.292)----Rz(1.748)--\n",
" | \n",
"------H-------SDG---*--------H-------------------\n",
" \n",
"--Rz(4.706)----Ry(3.292)----z----Rz(1.748)--\n",
" | \n",
"------H-----------SDG-------*--------H------\n",
" \n"
]
}
],
"source": [
"cir = UAnsatz(1)\n",
"theta = paddle.uniform([3], min=0.0, max=2*np.pi, dtype='float64')\n",
"cir.u3(theta[0], theta[1], theta[2], 0)\n",
"print('Original circuit: ')\n",
"print(cir)\n",
"\n",
"# Since the u3 gate has three parameters, we need a total of three circuits. Each corresponds to one parameter.\n",
"print('Circuits for gradient of u3: ')\n",
"# The first parameter here is the index of the gate, the second parameter is the index of parameter\n",
"print(cir.u3_partial(0, 0))\n",
"print(cir.u3_partial(0, 1))\n",
"print(cir.u3_partial(0, 2))"
]
},
{
"cell_type": "markdown",
"id": "95d11a2b",
"metadata": {},
"source": [
"### Two-qubit gate gradient\n",
"\n",
"Paddle Quantum provides many two-qubit parameterized gates as well. They can be categorized into two types: one is control rotation gates like $CR_x$, the other is two-qubit rotation gates like $R_{xx}$. Circuits for gradients of two-qubit rotation gates are easy to construct. Let's take $R_{xx}$ as an example. Following the idea of one-qubit rotation gates, we first write it as $R_{xx}(\\theta)=e^{-i\\frac{\\theta}{2}X\\otimes X}$, then get the equation $\\frac{\\partial R_{xx}(\\theta)}{\\partial \\theta}=-i\\frac{1}{2}X\\otimes Xe^{-i\\frac{\\theta}{2}X\\otimes X}$, which can be converted into a circuit easily."
]
},
{
"cell_type": "markdown",
"id": "3fe81c6c",
"metadata": {},
"source": [
"We need to be careful when calculating the gradients for control rotation gates. Usually, we will need two circuits for a control rotation gate with one parameter. For example, let's consider $CR_x(\\theta)$.\n",
"\n",
"$CR_x(\\theta)$ can be written as $\\left|0\\right>\\left<0\\right|\\otimes I + \\left|1\\right>\\left<1\\right|\\otimes R_x(\\theta)$, so its gradient is: \n",
"\n",
"$$\n",
"\\frac{\\partial CR_x(\\theta)}{\\partial \\theta}=\\left|1\\right>\\left<1\\right|\\otimes \\frac{\\partial R_x(\\theta)}{\\partial \\theta}=-\\frac{i}{2}\\left|1\\right>\\left<1\\right|\\otimes Xe^{-i\\frac{\\theta}{2}X}.\n",
"\\tag{15}\n",
"$$\n",
"\n",
"However, this equation cannot be represented directly using one circuit. We need to use a tiny 'trick' here. Instead of using this formula directly, we decompose it into two terms \n",
"\n",
"$$\n",
"\\frac{\\partial CR_x(\\theta)}{\\partial \\theta}=-\\frac{i}{4}(\\left|0\\right>\\left<0\\right|\\otimes I + \\left|1\\right>\\left<1\\right|\\otimes R_x(\\theta))I\\otimes X + \\frac{i}{4}(\\left|0\\right>\\left<0\\right|\\otimes I + \\left|1\\right>\\left<1\\right|\\otimes R_x(\\theta))Z\\otimes X.\n",
"\\tag{16}\n",
"$$ \n",
"\n",
"You can easily verify that this formula is equivalent to the former one. By doing so, we can use two circuits to compute the gradients for $CR_x$."
]
},
{
"cell_type": "markdown",
"id": "23a999c4",
"metadata": {},
"source": [
"As always, we provide built-in methods for calculating the gradients of all two-qubit parameterized gates in Paddle Quantum, i.e., $R_{xx}$, $R_{yy}$, $R_{zz}$, $CR_x$, $CR_y$, $CR_z$, $CU$. "
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "59733ca2",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Original circuit: \n",
"--*----Rzz(3.76)--------*------\n",
" | | | \n",
"--U----Rzz(3.76)----Ry(2.246)--\n",
" \n",
"The circuit for gradient of rzz: \n",
"--*---------z---------Rzz(3.76)--------*------\n",
" | | | | \n",
"--U---------|----z----Rzz(3.76)----Ry(2.246)--\n",
" | | \n",
"--H---SDG---*----*--------H-------------------\n",
" \n",
"The circuit for gradient of cry: \n",
"--*----Rzz(3.76)----y--------*------\n",
" | | | | \n",
"--U----Rzz(3.76)----|----Ry(2.246)--\n",
" | \n",
"--H-------SDG-------*--------H------\n",
" \n",
"--*----Rzz(3.76)---------y--------*------\n",
" | | | | \n",
"--U----Rzz(3.76)----z----|----Ry(2.246)--\n",
" | | \n",
"--H--------S--------*----*--------H------\n",
" \n"
]
}
],
"source": [
"theta = paddle.uniform([5], min=0.0, max=2*np.pi, dtype='float64')\n",
"cir = UAnsatz(2)\n",
"cir.cu(theta[0], theta[1], theta[2], [0, 1])\n",
"cir.rzz(theta[3], [0, 1])\n",
"cir.cry(theta[4], [0, 1])\n",
"print('Original circuit: ')\n",
"print(cir)\n",
"\n",
"# The first parameter here is the index of the gate, the second parameter is the index of parameter\n",
"# Since we have three parameters for cu gate, and we need two circuits for each parameter, we have a total of 6 circuits.\n",
"# Circuits for gradients of cu:\n",
"cu3_00 = cir.cu3_partial(0, 0)[0]\n",
"cu3_01 = cir.cu3_partial(0, 0)[1]\n",
"cu3_10 = cir.cu3_partial(0, 1)[0]\n",
"cu3_11 = cir.cu3_partial(0, 1)[1]\n",
"cu3_20 = cir.cu3_partial(0, 2)[0]\n",
"cu3_21 = cir.cu3_partial(0, 2)[1]\n",
"\n",
"# The first parameter here is the index of the gate, the second parameter is the name of the gate\n",
"print('The circuit for gradient of rzz: ')\n",
"print(cir.pauli_rotation_gate_partial(1, 'RZZ_gate'))\n",
"\n",
"# The first parameter here is the index of the gate, the second parameter is the name of the gate\n",
"print('The circuit for gradient of cry: ')\n",
"print(cir.control_rotation_gate_partial(2, 'cry')[0])\n",
"print(cir.control_rotation_gate_partial(2, 'cry')[1])"
]
},
{
"cell_type": "markdown",
"id": "9d264a86",
"metadata": {},
"source": [
"Now that we have all individual circuits prepared for calculating gradients, the next step is to get the exact value of the gradients. How to do so? We need to plug these circuits into our objective function, then the results are our desired gradients. For gates like $CR_x$, we will take the mean of the two circuits to be the gradient, as indicated in the analytical formula. We also provide a built-in method (Note: The built-in method currently does not support assigning an input state or running on a noisy circuit):"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "02e8ea72",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--U----*----x----Ry(0.667)--\n",
" | | \n",
"--U----x----*----Ry(4.807)--\n",
" \n",
"Gradient of this objective function is: [ 0. -0.76471634 0.00700639 0. -0.29062181 -0.01701887\n",
" -0.07729092 0.7766131 ]\n"
]
}
],
"source": [
"# Randomly generate parameters for our circuit\n",
"theta = paddle.uniform(shape=[8], dtype='float64', min=0.0, max=np.pi * 2)\n",
"theta.stop_gradient = False\n",
"\n",
"# Construct circuit of U(theta)\n",
"cir = UAnsatz(2)\n",
"cir.complex_entangled_layer(theta[:6], 1)\n",
"cir.ry(theta=theta[6], which_qubit=0)\n",
"cir.ry(theta=theta[7], which_qubit=1)\n",
"cir.run_state_vector()\n",
"print(cir)\n",
"\n",
"# Calculate gradient using our built-in method\n",
"# We pass in our Hamiltonian H used in the objective function\n",
"gradient = cir.linear_combinations_gradient(H, shots=0)\n",
"print(\"Gradient of this objective function is: \", gradient.numpy())"
]
},
{
"cell_type": "markdown",
"id": "d7fcdbdf",
"metadata": {},
"source": [
"## Application: Simulating VQE with Paddle Quantum\n",
"\n",
"Variational Quantum Eigensolver (VQE) [3] is designed to find the ground state energy of a given molecular Hamiltonian using variational quantum circuits. Interested readers can find more details from the previous tutorial [VQE](../quantum_simulation/VQE_EN.ipynb).\n",
"\n",
"We will demonstrate how to use VQE to find the ground state energy for the Hamiltonian of hydrogen molecule $H_2$. In the process, we will use the methods introduced above to calculate the gradient."
]
},
{
"cell_type": "markdown",
"id": "8e9d6e7b",
"metadata": {},
"source": [
"### Using Paddle's Optimizer\n",
"\n",
"First, we will use Paddle's optimizer Adam to run our example. We can choose using either finite difference method or parameter-shift method to calculate gradient."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "cce057c2",
"metadata": {},
"outputs": [],
"source": [
"from paddle_quantum.VQE.chemistrysub import H2_generator\n",
"from paddle_quantum.expecval import ExpecVal\n",
"\n",
"# Set up our Hamiltonian H\n",
"pauli_str, N = H2_generator()\n",
"H = Hamiltonian(pauli_str)\n",
"\n",
"# Hyper-parameters\n",
"ITR = 80 # Set the number of optimization iterations\n",
"LR = 0.4 # Set the learning rate\n",
"D = 2 # Set the depth of the repetitive calculation module in QNN\n",
"\n",
"def U_theta(theta, Hamiltonian, N, D):\n",
" \"\"\"\n",
" Quantum Neural Network\n",
" \"\"\"\n",
" # Initialize the quantum neural network according to the number of qubits N\n",
" cir = UAnsatz(N)\n",
"\n",
" # Built-in {R_y + CNOT} circuit template\n",
" theta = paddle.reshape(theta, [D+1, N, 1])\n",
" cir.real_entangled_layer(theta[:D], D)\n",
"\n",
" # Lay R_y gates in the last row\n",
" for i in range(N):\n",
" cir.ry(theta=theta[D][i][0], which_qubit=i)\n",
"\n",
" # The quantum neural network acts on the default initial state |0...0>\n",
" cir.run_state_vector()\n",
" \n",
" return cir"
]
},
{
"cell_type": "markdown",
"id": "c0cdee79",
"metadata": {},
"source": [
"Here in our forward propagation mechanism, we use the updated parameters to calculate gradient using parameter-shift rule, and calculate the expectation value. You can change the method to 'finite_diff' if you'd like to try it out using finite difference method. "
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "0d172081",
"metadata": {},
"outputs": [],
"source": [
"class StateNet(paddle.nn.Layer):\n",
"\n",
" def __init__(self, cir):\n",
" super(StateNet, self).__init__()\n",
" \n",
" self.cir = cir\n",
" params = cir.get_param()\n",
" \n",
" # Assign the theta parameter list to be the trainable parameter list of the circuit\n",
" self.theta = self.create_parameter(shape=[len(params)], \n",
" default_initializer=paddle.nn.initializer.Assign(params),\n",
" dtype='float32', is_bias=False)\n",
" \n",
" # Define loss function and forward propagation mechanism\n",
" def forward(self):\n",
" # Calculate the loss function/expectation value using Parameter-shift rule to calculate gradient\n",
" loss = ExpecVal.apply(self.cir, self.theta.cast('float64'), 'param_shift', H, shots=0)\n",
" \n",
" return loss, self.cir"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "0b8235c9",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"iter: 20 loss: -1.1114\n",
"iter: 20 Ground state energy: -1.1114 Ha\n",
"iter: 40 loss: -1.1316\n",
"iter: 40 Ground state energy: -1.1316 Ha\n",
"iter: 60 loss: -1.1357\n",
"iter: 60 Ground state energy: -1.1357 Ha\n",
"iter: 80 loss: -1.1361\n",
"iter: 80 Ground state energy: -1.1361 Ha\n",
"\n",
"The trained circuit:\n",
"--Ry(6.282)----*--------------x----Ry(6.289)----*--------------x----Ry(3.148)--\n",
" | | | | \n",
"--Ry(3.138)----x----*---------|----Ry(0.207)----x----*---------|----Ry(3.142)--\n",
" | | | | \n",
"--Ry(6.278)---------x----*----|----Ry(0.001)---------x----*----|----Ry(3.143)--\n",
" | | | | \n",
"--Ry(0.001)--------------x----*----Ry(3.156)--------------x----*----Ry(3.154)--\n",
" \n",
"\n",
"Ground state energy obtained: [-1.13609609] Ha\n",
"Actual ground state energy: -1.13618 Ha\n"
]
}
],
"source": [
"# Initialize the theta parameter list and fill the initial value with a uniform distribution of [0, 2*pi]\n",
"theta = paddle.to_tensor(np.random.uniform(0.0, 2*np.pi, (D+1) * N), stop_gradient=False)\n",
"\n",
"# Initialize the circuit\n",
"cir = U_theta(theta, H, N, D)\n",
"\n",
"# Determine the parameter dimension of the network\n",
"net = StateNet(cir)\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",
"# Record optimization results\n",
"summary_iter, summary_loss = [], []\n",
"\n",
"# Optimization loop\n",
"for itr in range(1, ITR + 1):\n",
"\n",
" # Forward propagation to calculate loss function\n",
" loss, cir = net()\n",
"\n",
" # Use back propagation to minimize the loss function\n",
" loss.backward()\n",
" opt.minimize(loss)\n",
" opt.clear_grad()\n",
"\n",
" # Record optimization results\n",
" summary_loss.append(loss.numpy())\n",
" summary_iter.append(itr)\n",
"\n",
" # Print result\n",
" if itr % 20 == 0:\n",
" print(\"iter:\", itr, \"loss:\", \"%.4f\" % loss.numpy())\n",
" print(\"iter:\", itr, \"Ground state energy:\", \"%.4f Ha\" \n",
" % loss.numpy())\n",
" if itr == ITR:\n",
" print(\"\\nThe trained circuit:\")\n",
" print(cir)\n",
"\n",
"print('\\nGround state energy obtained: ', summary_loss[-1], \"Ha\")\n",
"print('Actual ground state energy: ', -1.13618, \"Ha\")"
]
},
{
"cell_type": "markdown",
"id": "2e672bed",
"metadata": {},
"source": [
"We can see that the ground state energy we obtained is close to the theoretical value."
]
},
{
"cell_type": "markdown",
"id": "8d58ebab",
"metadata": {},
"source": [
"### Using SciPy's Optimizer\n",
"\n",
"We will also demonstrate how to use SciPy's optimizer to run VQE easily with Paddle Quantum. For this example, we will use Conjugate Gradient (CG) optimizer along with linear combination method to find the ground state energy of our Hamiltonian. \n",
"\n",
"Other SciPy methods we support include Newton-CG, Powell, and SLSQP."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "7f7ef671",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"loss: [-0.91176578]\n",
"loss: [-1.03555093]\n",
"loss: [-1.11965221]\n",
"loss: [-1.13435502]\n",
"loss: [-1.13577104]\n",
"loss: [-1.13615947]\n",
"loss: [-1.13618601]\n",
"loss: [-1.13618942]\n",
"loss: [-1.13618945]\n",
"loss: [-1.13618945]\n",
"loss: [-1.13618945]\n",
"Optimization terminated successfully.\n",
"Actual ground state energy: -1.13618 Ha\n"
]
}
],
"source": [
"from paddle_quantum.optimizer import ConjugateGradient\n",
"\n",
"# Initialize the circuit\n",
"cir = U_theta(theta, H, N, D)\n",
"\n",
"optimizer = ConjugateGradient(cir, H, shots=0, grad_func_name='linear_comb')\n",
"optimizer.minimize(iterations=80)\n",
"print('Actual ground state energy: ', -1.13618, \"Ha\")"
]
},
{
"cell_type": "markdown",
"id": "1ad4927e",
"metadata": {},
"source": [
"## Conclusion\n",
"\n",
"As you can see, finite-difference and parameter-shift methods have similar forms - both of them require two function evaluations per parameter. The benefits of these methods are that the gradients can be calculated without knowing much about the circuit or the objective function. We can treat them as a black box and get the gradient just by feeding in different parameters. Our preferred choice between those two is parameter-shift method because its result is an analytical gradient, while finite difference method can only get an estimation of the gradient. However, parameter-shift only applies to $U(\\theta)$ that can be generated by $G$ that has two distinct eigenvalues: $U(\\theta) = e^{-ia\\theta G}$ or can be decomposed into a product of gates in this form.\n",
"\n",
"Using linear combination of unitary gates to calculate the gradients of a given circuit is probably the most straightforward analytical method. By differentiating unitary gates under their mathematical forms, we can use circuits to represent the resulting formula. The number of circuits required is proportional to the number of parameters in the original circuit as the other two methods. We can even calculate gradients by constructing only one single circuit for simple gates like $R_x$, $R_{xx}$ and etc. However, note that we will use an ancilla qubit in this method. Moreover, you might have noticed that this method takes a long time to run on complex circuits. That's because as the number of qubits increases, the number of circuits used to represent the first order differentiation of a single multi-qubit gate also increases. "
]
},
{
"cell_type": "markdown",
"id": "26a8f5fd",
"metadata": {},
"source": [
"_______\n",
"\n",
"## References\n",
"\n",
"[1] Crooks, Gavin E. \"Gradients of parameterized quantum gates using the parameter-shift rule and gate decomposition.\" [arXiv preprint arXiv:1905.13311 (2019)](https://arxiv.org/abs/1905.13311).\n",
"\n",
"[2] Somma, Rolando, et al. \"Simulating physical phenomena by quantum networks.\" [Physical Review A 65.4 (2002): 042323](https://arxiv.org/abs/quant-ph/0108146).\n",
"\n",
"[3] Peruzzo, Alberto, et al. \"A variational eigenvalue solver on a photonic quantum processor.\" [Nature communications 5.1 (2014): 1-7](https://www.nature.com/articles/ncomms5213).\n",
"\n",
"[4] Schuld, Maria, et al. \"Evaluating analytic gradients on quantum hardware.\" [Physical Review A 99.3 (2019): 032331](https://arxiv.org/abs/1811.11184)."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.10"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
"cells": [ "cells": [
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "6a2df459",
"metadata": {}, "metadata": {},
"source": [ "source": [
"# 在 Paddle Quantum 中模拟含噪量子电路\n", "# 在 Paddle Quantum 中模拟含噪量子电路\n",
...@@ -12,7 +11,6 @@ ...@@ -12,7 +11,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "d12197db",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## 噪声简介\n", "## 噪声简介\n",
...@@ -79,7 +77,6 @@ ...@@ -79,7 +77,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "84556435",
"metadata": {}, "metadata": {},
"source": [ "source": [
"### Paddle Quantum 中添加信道的方式\n", "### Paddle Quantum 中添加信道的方式\n",
...@@ -90,7 +87,6 @@ ...@@ -90,7 +87,6 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 1, "execution_count": 1,
"id": "ff3880f1",
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-08T05:16:08.247239Z", "end_time": "2021-04-08T05:16:08.247239Z",
...@@ -140,7 +136,6 @@ ...@@ -140,7 +136,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "863e4554",
"metadata": {}, "metadata": {},
"source": [ "source": [
"之后,我们加上一个 $p=0.1$ 的比特反转噪声,并测量通过信道之后的量子比特。 \n", "之后,我们加上一个 $p=0.1$ 的比特反转噪声,并测量通过信道之后的量子比特。 \n",
...@@ -150,7 +145,6 @@ ...@@ -150,7 +145,6 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 2, "execution_count": 2,
"id": "70674a65",
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-08T05:16:09.221527Z", "end_time": "2021-04-08T05:16:09.221527Z",
...@@ -199,7 +193,6 @@ ...@@ -199,7 +193,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "0c42a154",
"metadata": {}, "metadata": {},
"source": [ "source": [
"可以看到,经过了比特反转信道(概率为 $p=0.1$)之后的量子态变成了混合态 $0.9 | 0 \\rangle \\langle 0 | + 0.1 | 1 \\rangle \\langle 1 |$。\n", "可以看到,经过了比特反转信道(概率为 $p=0.1$)之后的量子态变成了混合态 $0.9 | 0 \\rangle \\langle 0 | + 0.1 | 1 \\rangle \\langle 1 |$。\n",
...@@ -208,7 +201,6 @@ ...@@ -208,7 +201,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "3725bece",
"metadata": {}, "metadata": {},
"source": [ "source": [
"### 常用噪声信道\n", "### 常用噪声信道\n",
...@@ -216,7 +208,7 @@ ...@@ -216,7 +208,7 @@
"除了比特反转信道之外,Paddle Quantum 也支持模拟其他常用的噪声信道。\n", "除了比特反转信道之外,Paddle Quantum 也支持模拟其他常用的噪声信道。\n",
"\n", "\n",
"\n", "\n",
"- **相位反转信道(phase flip channel)**\n", "- **相位反转信道(Phase Flip Channel)**\n",
"\n", "\n",
" 与比特反转信道类似,相位反转信道会以 $p$ 的概率来反转一个量子比特的相位,其表达形式为\n", " 与比特反转信道类似,相位反转信道会以 $p$ 的概率来反转一个量子比特的相位,其表达形式为\n",
" \n", " \n",
...@@ -226,7 +218,7 @@ ...@@ -226,7 +218,7 @@
" $$\n", " $$\n",
"\n", "\n",
"\n", "\n",
"- **比特相位反转信道(bit-phase flip channel)**\n", "- **比特相位反转信道(Bit-Phase Flip Channel)**\n",
"\n", "\n",
" $$\n", " $$\n",
" \\mathcal{E}_{BPF}(\\rho) = (1-p) \\rho + p Y \\rho Y.\n", " \\mathcal{E}_{BPF}(\\rho) = (1-p) \\rho + p Y \\rho Y.\n",
...@@ -234,7 +226,7 @@ ...@@ -234,7 +226,7 @@
" $$\n", " $$\n",
"\n", "\n",
"\n", "\n",
"- **去极化信道 (depolarizing channel)**\n", "- **去极化信道 (Depolarizing Channel)**\n",
"\n", "\n",
" 通过去极化信道之后,将会有 $p$ 的概率处于最大混态 $I/2$ 和 $1-p$ 的概率维持不变。可以表示为对称地施加泡利噪声,\n", " 通过去极化信道之后,将会有 $p$ 的概率处于最大混态 $I/2$ 和 $1-p$ 的概率维持不变。可以表示为对称地施加泡利噪声,\n",
" \n", " \n",
...@@ -245,7 +237,7 @@ ...@@ -245,7 +237,7 @@
" $$\n", " $$\n",
"\n", "\n",
"\n", "\n",
"- **泡利信道(Pauli channel)**\n", "- **泡利信道(Pauli Channel)**\n",
"\n", "\n",
" 该信道会以非对称的形式施加泡利噪声。表达形式为\n", " 该信道会以非对称的形式施加泡利噪声。表达形式为\n",
" \n", " \n",
...@@ -255,7 +247,7 @@ ...@@ -255,7 +247,7 @@
" $$\n", " $$\n",
"\n", "\n",
"\n", "\n",
"- **振幅阻尼信道(amplitude damping channel)**\n", "- **振幅阻尼信道(Amplitude Damping Channel)**\n",
"\n", "\n",
" 振幅阻尼信道可以用来模拟当系统受到**能量耗散**时引入的噪声。表达形式为,\n", " 振幅阻尼信道可以用来模拟当系统受到**能量耗散**时引入的噪声。表达形式为,\n",
" \n", " \n",
...@@ -279,7 +271,7 @@ ...@@ -279,7 +271,7 @@
" $$ \n", " $$ \n",
"\n", "\n",
"\n", "\n",
"- **相位阻尼信道(phase damping channel)**\n", "- **相位阻尼信道(Phase Damping Channel)**\n",
"\n", "\n",
" 相位阻尼信道描述的是当系统没有与外界环境交换能量,却损失了**量子信息**的情况下的噪声模型。其表达形式为\n", " 相位阻尼信道描述的是当系统没有与外界环境交换能量,却损失了**量子信息**的情况下的噪声模型。其表达形式为\n",
" \n", " \n",
...@@ -303,7 +295,7 @@ ...@@ -303,7 +295,7 @@
" $$\n", " $$\n",
"\n", "\n",
"\n", "\n",
"- **广义振幅阻尼信道(generalized amplitude Damping channel)**\n", "- **广义振幅阻尼信道(Generalized Amplitude Damping Channel)**\n",
"\n", "\n",
" 该信道描述的是系统与外界环境在**有限温度下交换能量**的噪声模型,同时也是**超导量子计算中的常见噪声** [4]。其表达形式较为复杂,感兴趣的读者可以进一步的阅读 [API 文档](https://qml.baidu.com/api/paddle_quantum.circuit.uansatz.html) 和相应的参考文献。\n", " 该信道描述的是系统与外界环境在**有限温度下交换能量**的噪声模型,同时也是**超导量子计算中的常见噪声** [4]。其表达形式较为复杂,感兴趣的读者可以进一步的阅读 [API 文档](https://qml.baidu.com/api/paddle_quantum.circuit.uansatz.html) 和相应的参考文献。\n",
"\n", "\n",
...@@ -315,7 +307,6 @@ ...@@ -315,7 +307,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "213994a9",
"metadata": {}, "metadata": {},
"source": [ "source": [
"### 自定义信道\n", "### 自定义信道\n",
...@@ -326,7 +317,6 @@ ...@@ -326,7 +317,6 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 3, "execution_count": 3,
"id": "020ab93f",
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-08T05:17:30.681411Z", "end_time": "2021-04-08T05:17:30.681411Z",
...@@ -385,7 +375,6 @@ ...@@ -385,7 +375,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "df1c24d9",
"metadata": {}, "metadata": {},
"source": [ "source": [
"按照上述例子,用户可以通过自定义 *Kraus* 算符的方式实现特定的信道。" "按照上述例子,用户可以通过自定义 *Kraus* 算符的方式实现特定的信道。"
...@@ -393,19 +382,17 @@ ...@@ -393,19 +382,17 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "e97e1bcd",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## 拓展:Paddle Quantum 模拟含噪纠缠资源\n", "## 拓展:Paddle Quantum 模拟含噪纠缠资源\n",
"\n", "\n",
"\n", "\n",
"许多重要的量子技术,例如量子隐形传态、态转换、分布式量子计算等都需要纠缠资源的预先分配。以纠缠量子比特对为例,在理想情况下,我们希望分配的纠缠资源处于**最大纠缠态**(maximally entangled state)。但是在实际的应用中,纠缠资源往往在制备、传输以及保存的过程中会和环境发生相互作用,从而引入噪声。下面,我们用 Paddle Quantum 中的去极化信道来模拟一个简单的场景,白噪声对贝尔态的影响:" "许多重要的量子技术,例如量子隐形传态、态转换、分布式量子计算等都需要纠缠资源的预先分配。以纠缠量子比特对为例,在理想情况下,我们希望分配的纠缠资源处于**最大纠缠态**(Maximally entangled state)。但是在实际的应用中,纠缠资源往往在制备、传输以及保存的过程中会和环境发生相互作用,从而引入噪声。下面,我们用 Paddle Quantum 中的去极化信道来模拟一个简单的场景,白噪声对贝尔态的影响:"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 4, "execution_count": 4,
"id": "2a73505a",
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-08T05:24:35.552425Z", "end_time": "2021-04-08T05:24:35.552425Z",
...@@ -463,7 +450,6 @@ ...@@ -463,7 +450,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "85c5fcc6",
"metadata": {}, "metadata": {},
"source": [ "source": [
"**注释:** 在 [纠缠蒸馏](../locc/EntanglementDistillation_LOCCNET_CN.ipynb) 的教程中我们介绍了如何利用 Paddle Quantm 中的 LoccNet 模块来研究纠缠蒸馏,即利用多个含噪声的纠缠对来提取高保真度的纠缠对,感兴趣的读者可以前往阅读。" "**注释:** 在 [纠缠蒸馏](../locc/EntanglementDistillation_LOCCNET_CN.ipynb) 的教程中我们介绍了如何利用 Paddle Quantm 中的 LoccNet 模块来研究纠缠蒸馏,即利用多个含噪声的纠缠对来提取高保真度的纠缠对,感兴趣的读者可以前往阅读。"
...@@ -471,13 +457,12 @@ ...@@ -471,13 +457,12 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "2e42f021",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## 应用: Paddle Quantum 模拟含噪变分量子本征求解器(VQE)\n", "## 应用: Paddle Quantum 模拟含噪变分量子本征求解器(VQE)\n",
"\n", "\n",
"\n", "\n",
"变分量子本征求解器(variational quantum eigensolver, VQE)[5] 可以用变分量子电路来计算某个给定哈密顿量的基态能量,关于其具体的原理和背景在之前的教程 [变分量子本征求解器](../quantum_simulation/VQE_CN.ipynb) 中有详细的讲解,感兴趣的读者可以前往阅读。\n", "变分量子本征求解器(Variational Quantum Eigensolver)[5] 可以用变分量子电路来计算某个给定哈密顿量的基态能量,关于其具体的原理和背景在之前的教程 [变分量子本征求解器](../quantum_simulation/VQE_CN.ipynb) 中有详细的讲解,感兴趣的读者可以前往阅读。\n",
"\n", "\n",
"在这里,我们尝试用一个简单的 VQE 电路来求解如下哈密顿量的基态能量:\n", "在这里,我们尝试用一个简单的 VQE 电路来求解如下哈密顿量的基态能量:\n",
"\n", "\n",
...@@ -492,7 +477,6 @@ ...@@ -492,7 +477,6 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 5, "execution_count": 5,
"id": "ab0fff8f",
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-08T05:34:47.301281Z", "end_time": "2021-04-08T05:34:47.301281Z",
...@@ -524,7 +508,6 @@ ...@@ -524,7 +508,6 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 6, "execution_count": 6,
"id": "b498f6c6",
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-08T05:34:51.742273Z", "end_time": "2021-04-08T05:34:51.742273Z",
...@@ -683,7 +666,6 @@ ...@@ -683,7 +666,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "48e09aa7",
"metadata": {}, "metadata": {},
"source": [ "source": [
"可以看到,含噪的变分量子本征求解器的效果要差于不含噪的版本,无法达到化学精度的要求 $\\varepsilon = 0.0016$ Ha。" "可以看到,含噪的变分量子本征求解器的效果要差于不含噪的版本,无法达到化学精度的要求 $\\varepsilon = 0.0016$ Ha。"
...@@ -691,7 +673,6 @@ ...@@ -691,7 +673,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "8e0b51c6",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## 总结\n", "## 总结\n",
...@@ -701,7 +682,6 @@ ...@@ -701,7 +682,6 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "ea92b769",
"metadata": {}, "metadata": {},
"source": [ "source": [
"---\n", "---\n",
...@@ -736,7 +716,7 @@ ...@@ -736,7 +716,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.7.10" "version": "3.8.3"
}, },
"toc": { "toc": {
"base_numbering": 1, "base_numbering": 1,
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
"cells": [ "cells": [
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"# 子空间搜索-量子变分本征求解器\n", "# 子空间搜索-量子变分本征求解器\n",
"\n", "\n",
...@@ -13,18 +12,12 @@ ...@@ -13,18 +12,12 @@
"- 在本案例中,我们将展示如何通过 Paddle Quantum 训练量子神经网络来求解量子系统的整个能量谱。\n", "- 在本案例中,我们将展示如何通过 Paddle Quantum 训练量子神经网络来求解量子系统的整个能量谱。\n",
"\n", "\n",
"- 首先,让我们通过下面几行代码引入必要的 library 和 package。" "- 首先,让我们通过下面几行代码引入必要的 library 和 package。"
] ],
"metadata": {}
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 1, "execution_count": 1,
"metadata": {
"ExecuteTime": {
"end_time": "2021-04-30T09:12:57.571029Z",
"start_time": "2021-04-30T09:12:54.960356Z"
}
},
"outputs": [],
"source": [ "source": [
"import numpy\n", "import numpy\n",
"from numpy import pi as PI\n", "from numpy import pi as PI\n",
...@@ -32,89 +25,89 @@ ...@@ -32,89 +25,89 @@
"from paddle import matmul\n", "from paddle import matmul\n",
"from paddle_quantum.circuit import UAnsatz\n", "from paddle_quantum.circuit import UAnsatz\n",
"from paddle_quantum.utils import random_pauli_str_generator, pauli_str_to_matrix, dagger" "from paddle_quantum.utils import random_pauli_str_generator, pauli_str_to_matrix, dagger"
] ],
"outputs": [],
"metadata": {
"ExecuteTime": {
"end_time": "2021-04-30T09:12:57.571029Z",
"start_time": "2021-04-30T09:12:54.960356Z"
}
}
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"## 背景\n", "## 背景\n",
"\n", "\n",
"- 量子计算在近期内备受瞩目的一个应用就是变分量子本征求解器(variational quantum eigensolver, VQE)[1-3].\n", "- 量子计算在近期内备受瞩目的一个应用就是变分量子本征求解器(variational quantum eigensolver, VQE)[1-3].\n",
"- VQE 是量子化学在近期有噪量子设备(NISQ device)上的核心应用之一,其中一个功能比较强大的版本是 subspace-search VQE(SSVQE) [4],其核心是去求解一个物理系统的哈密顿量(Hamiltonian)的基态和**激发态**的性质。数学上,可以理解为求解一个埃尔米特矩阵矩阵(Hermitian matrix)的本征值及其对应的本征向量。该哈密顿量的本征值组成的集合我们称其为能谱(energy spectrum)。\n", "- VQE 是量子化学在近期有噪量子设备(NISQ device)上的核心应用之一,其中一个功能比较强大的版本是 subspace-search VQE(SSVQE) [4],其核心是去求解一个物理系统的哈密顿量(Hamiltonian)的基态和**激发态**的性质。数学上,可以理解为求解一个埃尔米特矩阵(Hermitian matrix)的本征值及其对应的本征向量。该哈密顿量的本征值组成的集合我们称其为能谱(energy spectrum)。\n",
"- 接下来我们将通过一个简单的例子学习如何通过训练量子神经网络解决这个问题,即求解出给定哈密顿量 $H$ 的能谱。\n", "- 接下来我们将通过一个简单的例子学习如何通过训练量子神经网络解决这个问题,即求解出给定哈密顿量 $H$ 的能谱。\n",
"\n", "\n",
"## SSVQE 分析物理系统的基态和激发态的能量\n", "## SSVQE 分析物理系统的基态和激发态的能量\n",
"\n", "\n",
"- 对于具体需要分析的分子,我们需要其几何构型(geometry)、电荷(charge)以及自旋多重度(spin multiplicity)等多项信息来建模获取描述系统的 Hamilton 量。具体的,通过我们内置的量子化学工具包可以利用 fermionic-to-qubit 映射的技术来输出目标分子的量子比特 Hamilton 量表示。\n", "- 对于具体需要分析的分子,我们需要其几何构型(geometry)、电荷(charge)以及自旋多重度(spin multiplicity)等多项信息来建模获取描述系统的 Hamilton 量。具体的,通过我们内置的量子化学工具包可以利用 fermionic-to-qubit 映射的技术来输出目标分子的量子比特 Hamilton 量表示。\n",
"- 作为简单的入门案例,我们在这里提供一个简单的随机双量子比特 Hamilton 量作为例子。" "- 作为简单的入门案例,我们在这里提供一个简单的随机双量子比特 Hamilton 量作为例子。"
] ],
"metadata": {}
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 2, "execution_count": 3,
"source": [
"N = 2 # 量子比特数/量子神经网络的宽度\n",
"SEED = 14 # 固定随机种子"
],
"outputs": [],
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T09:12:57.580461Z", "end_time": "2021-04-30T09:12:57.580461Z",
"start_time": "2021-04-30T09:12:57.574080Z" "start_time": "2021-04-30T09:12:57.574080Z"
} }
}, }
"outputs": [],
"source": [
"N = 2 # 量子比特数/量子神经网络的宽度\n",
"SEED = 14 # 固定随机种子"
]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 3, "execution_count": 8,
"metadata": { "source": [
"ExecuteTime": { "# 生成用泡利字符串表示的随机哈密顿量\n",
"end_time": "2021-04-30T09:12:57.604652Z", "numpy.random.seed(SEED)\n",
"start_time": "2021-04-30T09:12:57.592553Z" "hamiltonian = random_pauli_str_generator(N, terms=10)\n",
} "print(\"Random Hamiltonian in Pauli string format = \\n\", hamiltonian)\n",
}, "\n",
"# 生成 Hamilton 量的矩阵信息\n",
"H = pauli_str_to_matrix(hamiltonian, N)"
],
"outputs": [ "outputs": [
{ {
"name": "stdout",
"output_type": "stream", "output_type": "stream",
"name": "stdout",
"text": [ "text": [
"Random Hamiltonian in Pauli string format = \n", "Random Hamiltonian in Pauli string format = \n",
" [[0.9152074787317819, 'x1,y0'], [-0.2717604556798945, 'z0'], [0.3628495008719168, 'x0'], [-0.5050129214094752, 'x1'], [-0.6971554357833791, 'y0,x1'], [0.8651151857574237, 'x0,y1'], [0.7409989105435002, 'y0'], [-0.39981603921243236, 'y0'], [0.06862640764702, 'z0'], [-0.7647553733438246, 'y1']]\n" " [[0.9152074787317819, 'x1,y0'], [-0.2717604556798945, 'z0'], [0.3628495008719168, 'x0'], [-0.5050129214094752, 'x1'], [-0.6971554357833791, 'y0,x1'], [0.8651151857574237, 'x0,y1'], [0.7409989105435002, 'y0'], [-0.39981603921243236, 'y0'], [0.06862640764702, 'z0'], [-0.7647553733438246, 'y1']]\n"
] ]
} }
], ],
"source": [ "metadata": {
"# 生成用泡利字符串表示的随机哈密顿量\n", "ExecuteTime": {
"numpy.random.seed(SEED)\n", "end_time": "2021-04-30T09:12:57.604652Z",
"hamiltonian = random_pauli_str_generator(N, terms=10)\n", "start_time": "2021-04-30T09:12:57.592553Z"
"print(\"Random Hamiltonian in Pauli string format = \\n\", hamiltonian)\n", }
"\n", }
"# 生成 Hamilton 量的矩阵信息\n",
"H = pauli_str_to_matrix(hamiltonian, N)"
]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"## 搭建量子神经网络\n", "## 搭建量子神经网络\n",
"\n", "\n",
"- 在实现 SSVQE 的过程中,我们首先需要设计量子神经网络(quantum neural network, QNN),也即参数化量子电路。在本教程中,我们提供一个预设的适用于双量子比特的通用量子电路模板。理论上,该模板具有足够强大的表达能力可以表示任意的双量子比特逻辑运算 [5]。具体的实现方式是需要 3 个 $CNOT$ 门加上任意 15 个单比特旋转门 $\\in \\{R_y, R_z\\}$。\n", "- 在实现 SSVQE 的过程中,我们首先需要设计量子神经网络(quantum neural network, QNN),也即参数化量子电路。在本教程中,我们提供一个预设的适用于双量子比特的通用量子电路模板。理论上,该模板具有足够强大的表达能力可以表示任意的双量子比特逻辑运算 [5]。具体的实现方式是需要 3 个 $CNOT$ 门加上任意 15 个单比特旋转门 $\\in \\{R_y, R_z\\}$。\n",
"\n", "\n",
"- 初始化其中的变量参数,${\\bf{\\theta}}$ 代表我们量子神经网络中的参数组成的向量,一共有 15 个参数。" "- 初始化其中的变量参数,${\\bf{\\theta}}$ 代表我们量子神经网络中的参数组成的向量,一共有 15 个参数。"
] ],
"metadata": {}
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 4, "execution_count": 9,
"metadata": {
"ExecuteTime": {
"end_time": "2021-04-30T09:12:57.673600Z",
"start_time": "2021-04-30T09:12:57.664882Z"
}
},
"outputs": [],
"source": [ "source": [
"THETA_SIZE = 15 # 量子神经网络中参数的数量\n", "THETA_SIZE = 15 # 量子神经网络中参数的数量\n",
"\n", "\n",
...@@ -130,11 +123,17 @@ ...@@ -130,11 +123,17 @@
"\n", "\n",
" # 返回量子神经网络的电路\n", " # 返回量子神经网络的电路\n",
" return cir" " return cir"
] ],
"outputs": [],
"metadata": {
"ExecuteTime": {
"end_time": "2021-04-30T09:12:57.673600Z",
"start_time": "2021-04-30T09:12:57.664882Z"
}
}
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"## 配置训练模型——损失函数\n", "## 配置训练模型——损失函数\n",
"\n", "\n",
...@@ -149,18 +148,12 @@ ...@@ -149,18 +148,12 @@
"$$\n", "$$\n",
"\\mathcal{L}(\\boldsymbol{\\theta}) = \\sum_{k=1}^{2^n}w_k*\\left\\langle {\\psi_k \\left( {\\bf{\\theta }} \\right)} \\right|H\\left| {\\psi_k \\left( {\\bf{\\theta }} \\right)} \\right\\rangle. \\tag{1}\n", "\\mathcal{L}(\\boldsymbol{\\theta}) = \\sum_{k=1}^{2^n}w_k*\\left\\langle {\\psi_k \\left( {\\bf{\\theta }} \\right)} \\right|H\\left| {\\psi_k \\left( {\\bf{\\theta }} \\right)} \\right\\rangle. \\tag{1}\n",
"$$" "$$"
] ],
"metadata": {}
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 5, "execution_count": 10,
"metadata": {
"ExecuteTime": {
"end_time": "2021-04-30T09:12:58.591349Z",
"start_time": "2021-04-30T09:12:58.577120Z"
}
},
"outputs": [],
"source": [ "source": [
"class Net(paddle.nn.Layer):\n", "class Net(paddle.nn.Layer):\n",
" def __init__(self, shape, dtype='float64'):\n", " def __init__(self, shape, dtype='float64'):\n",
...@@ -182,86 +175,62 @@ ...@@ -182,86 +175,62 @@
" loss_struct = paddle.real(matmul(matmul(dagger(U), H), U))\n", " loss_struct = paddle.real(matmul(matmul(dagger(U), H), U))\n",
"\n", "\n",
" # 输入计算基去计算每个子期望值,相当于取 U^dagger*H*U 的对角元 \n", " # 输入计算基去计算每个子期望值,相当于取 U^dagger*H*U 的对角元 \n",
" loss_components = [\n", " loss_components = []\n",
" loss_struct[0][0],\n", " for i in range(len(loss_struct)):\n",
" loss_struct[1][1],\n", " loss_components.append(loss_struct[i][i])\n",
" loss_struct[2][2],\n",
" loss_struct[3][3]\n",
" ]\n",
" \n", " \n",
" # 最终加权求和后的损失函数\n", " # 最终加权求和后的损失函数\n",
" loss = 4 * loss_components[0] + 3 * loss_components[1]\\\n", " loss = 0\n",
" + 2 * loss_components[2] + 1 * loss_components[3]\n", " for i in range(len(loss_components)):\n",
" weight = 4 - i\n",
" loss += weight * loss_components[i]\n",
" \n", " \n",
" return loss, loss_components, cir" " return loss, loss_components, cir"
] ],
"outputs": [],
"metadata": {
"ExecuteTime": {
"end_time": "2021-04-30T09:12:58.591349Z",
"start_time": "2021-04-30T09:12:58.577120Z"
}
}
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"## 配置训练模型——模型参数\n", "## 配置训练模型——模型参数\n",
"在进行量子神经网络的训练之前,我们还需要进行一些训练的超参数设置,主要是学习速率(learning rate, LR)、迭代次数(iteration, ITR。这里我们设定学习速率为 0.3,迭代次数为 50 次。读者不妨自行调整来直观感受下超参数调整对训练效果的影响。" "在进行量子神经网络的训练之前,我们还需要进行一些训练的超参数设置,主要是学习速率(learning rate, LR)、迭代次数(iteration, ITR。这里我们设定学习速率为 0.3,迭代次数为 50 次。读者不妨自行调整来直观感受下超参数调整对训练效果的影响。"
] ],
"metadata": {}
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 6, "execution_count": 11,
"source": [
"ITR = 100 # 设置训练的总迭代次数\n",
"LR = 0.3 # 设置学习速率"
],
"outputs": [],
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T09:12:59.351881Z", "end_time": "2021-04-30T09:12:59.351881Z",
"start_time": "2021-04-30T09:12:59.343240Z" "start_time": "2021-04-30T09:12:59.343240Z"
} }
}, }
"outputs": [],
"source": [
"ITR = 100 # 设置训练的总迭代次数\n",
"LR = 0.3 # 设置学习速率"
]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"## 进行训练\n", "## 进行训练\n",
"- 当训练模型的各项参数都设置完成后,我们将数据转化为 PaddlePaddle 动态图中的张量,进而进行量子神经网络的训练。\n", "- 当训练模型的各项参数都设置完成后,我们将数据转化为 PaddlePaddle 动态图中的张量,进而进行量子神经网络的训练。\n",
"- 过程中我们用的是 Adam Optimizer,也可以调用 PaddlePaddle 中提供的其他优化器。\n", "- 过程中我们用的是 Adam Optimizer,也可以调用 PaddlePaddle 中提供的其他优化器。\n",
"- 我们可以将训练过程中的每一轮 loss 打印出来。" "- 我们可以将训练过程中的每一轮 loss 打印出来。"
] ],
"metadata": {}
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 7, "execution_count": 13,
"metadata": {
"ExecuteTime": {
"end_time": "2021-04-30T09:13:07.503094Z",
"start_time": "2021-04-30T09:13:04.968574Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"iter: 10 loss: -4.5668\n",
"iter: 20 loss: -5.3998\n",
"iter: 30 loss: -5.6210\n",
"iter: 40 loss: -5.8872\n",
"iter: 50 loss: -5.9246\n",
"iter: 60 loss: -5.9471\n",
"iter: 70 loss: -5.9739\n",
"iter: 80 loss: -5.9833\n",
"iter: 90 loss: -5.9846\n",
"iter: 100 loss: -5.9848\n",
"\n",
"训练后的电路:\n",
"--U----X----Rz(-1.18)----*-----------------X----U--\n",
" | | | \n",
"--U----*----Ry(-0.03)----X----Ry(2.362)----*----U--\n",
" \n"
]
}
],
"source": [ "source": [
"paddle.seed(SEED)\n", "paddle.seed(SEED)\n",
" \n", " \n",
...@@ -292,11 +261,40 @@ ...@@ -292,11 +261,40 @@
" if itr == ITR:\n", " if itr == ITR:\n",
" print(\"\\n训练后的电路:\")\n", " print(\"\\n训练后的电路:\")\n",
" print(cir)" " print(cir)"
] ],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"iter: 10 loss: -4.5668\n",
"iter: 20 loss: -5.3998\n",
"iter: 30 loss: -5.6210\n",
"iter: 40 loss: -5.8872\n",
"iter: 50 loss: -5.9246\n",
"iter: 60 loss: -5.9471\n",
"iter: 70 loss: -5.9739\n",
"iter: 80 loss: -5.9833\n",
"iter: 90 loss: -5.9846\n",
"iter: 100 loss: -5.9848\n",
"\n",
"训练后的电路:\n",
"--U----x----Rz(-1.18)----*-----------------x----U--\n",
" | | | \n",
"--U----*----Ry(-0.03)----x----Ry(2.362)----*----U--\n",
" \n"
]
}
],
"metadata": {
"ExecuteTime": {
"end_time": "2021-04-30T09:13:07.503094Z",
"start_time": "2021-04-30T09:13:04.968574Z"
}
}
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"## 测试效果\n", "## 测试效果\n",
"\n", "\n",
...@@ -304,51 +302,64 @@ ...@@ -304,51 +302,64 @@
"- 理论值由 NumPy 中的工具来求解哈密顿量的各个本征值;\n", "- 理论值由 NumPy 中的工具来求解哈密顿量的各个本征值;\n",
"- 我们将训练 QNN 得到的各个能级的能量和理想情况下的理论值进行比对。\n", "- 我们将训练 QNN 得到的各个能级的能量和理想情况下的理论值进行比对。\n",
"- 可以看到,SSVQE 训练输出的值与理想值高度接近。" "- 可以看到,SSVQE 训练输出的值与理想值高度接近。"
] ],
"metadata": {}
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 8, "execution_count": 15,
"metadata": { "source": [
"ExecuteTime": { "def output_ordinalvalue(num):\n",
"end_time": "2021-04-30T09:13:07.569109Z", " r\"\"\"\n",
"start_time": "2021-04-30T09:13:07.522441Z" " Convert to ordinal value\n",
} "\n",
}, " Args:\n",
" num (int): input number\n",
"\n",
" Return:\n",
" (str): output ordinal value\n",
" \"\"\"\n",
" if num == 1:\n",
" return str(num) + \"st\"\n",
" elif num == 2:\n",
" return str(num) + \"nd\"\n",
" elif num == 3:\n",
" return str(num) + \"rd\"\n",
" else:\n",
" return str(num) + 'th'\n",
"\n",
"for i in range(len(loss_components)):\n",
" if i == 0:\n",
" print('The estimated ground state energy is: ', loss_components[i].numpy())\n",
" print('The theoretical ground state energy is: ', numpy.linalg.eigh(H)[0][i])\n",
" else:\n",
" print('The estimated {} excited state energy is: {}'.format(\n",
" output_ordinalvalue(i), loss_components[i].numpy())\n",
" )\n",
" print('The theoretical {} excited state energy is: {}'.format(\n",
" output_ordinalvalue(i), numpy.linalg.eigh(H)[0][i])\n",
" )"
],
"outputs": [ "outputs": [
{ {
"name": "stdout",
"output_type": "stream", "output_type": "stream",
"name": "stdout",
"text": [ "text": [
"The estimated ground state energy is: [-2.18762367]\n", "The estimated ground state energy is: [-2.18762366]\n",
"The theoretical ground state energy: -2.18790201165885\n", "The theoretical ground state energy is: -2.18790201165885\n",
"The estimated 1st excited state energy is: [-0.13721027]\n", "The estimated 1st excited state energy is: [-0.13721024]\n",
"The theoretical 1st excited state energy: -0.13704127143749587\n", "The theoretical 1st excited state energy is: -0.13704127143749587\n",
"The estimated 2nd excited state energy is: [0.85251458]\n", "The estimated 2nd excited state energy is: [0.85251457]\n",
"The theoretical 2nd excited state energy: 0.8523274042087416\n", "The theoretical 2nd excited state energy is: 0.8523274042087416\n",
"The estimated 3rd excited state energy is: [1.47231937]\n", "The estimated 3rd excited state energy is: [1.47231932]\n",
"The theoretical 3rd excited state energy: 1.4726158788876045\n" "The theoretical 3rd excited state energy is: 1.4726158788876045\n"
] ]
} }
], ],
"source": [ "metadata": {}
"print('The estimated ground state energy is: ', loss_components[0].numpy())\n",
"print('The theoretical ground state energy: ', \n",
"numpy.linalg.eigh(H)[0][0])\n",
"\n",
"print('The estimated 1st excited state energy is: ', loss_components[1].numpy())\n",
"print('The theoretical 1st excited state energy: ', numpy.linalg.eigh(H)[0][1])\n",
"\n",
"print('The estimated 2nd excited state energy is: ', loss_components[2].numpy())\n",
"print('The theoretical 2nd excited state energy: ', numpy.linalg.eigh(H)[0][2])\n",
"\n",
"print('The estimated 3rd excited state energy is: ', loss_components[3].numpy())\n",
"print('The theoretical 3rd excited state energy: ', numpy.linalg.eigh(H)[0][3])"
]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"_______\n", "_______\n",
"\n", "\n",
...@@ -363,14 +374,14 @@ ...@@ -363,14 +374,14 @@
"[4] Nakanishi, K. M., Mitarai, K. & Fujii, K. Subspace-search variational quantum eigensolver for excited states. [Phys. Rev. Res. 1, 033062 (2019).](https://journals.aps.org/prresearch/pdf/10.1103/PhysRevResearch.1.033062)\n", "[4] Nakanishi, K. M., Mitarai, K. & Fujii, K. Subspace-search variational quantum eigensolver for excited states. [Phys. Rev. Res. 1, 033062 (2019).](https://journals.aps.org/prresearch/pdf/10.1103/PhysRevResearch.1.033062)\n",
"\n", "\n",
"[5] Vatan, F. & Williams, C. Optimal quantum circuits for general two-qubit gates. [Phys. Rev. A 69, 032315 (2004).](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.69.032315)" "[5] Vatan, F. & Williams, C. Optimal quantum circuits for general two-qubit gates. [Phys. Rev. A 69, 032315 (2004).](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.69.032315)"
] ],
"metadata": {}
} }
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3", "name": "python3",
"language": "python", "display_name": "Python 3.8.10 64-bit ('paddle_quantum_test': conda)"
"name": "python3"
}, },
"language_info": { "language_info": {
"codemirror_mode": { "codemirror_mode": {
...@@ -382,7 +393,7 @@ ...@@ -382,7 +393,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.3" "version": "3.8.10"
}, },
"toc": { "toc": {
"base_numbering": 1, "base_numbering": 1,
...@@ -396,8 +407,11 @@ ...@@ -396,8 +407,11 @@
"toc_position": {}, "toc_position": {},
"toc_section_display": true, "toc_section_display": true,
"toc_window_display": false "toc_window_display": false
},
"interpreter": {
"hash": "1ba3360425d54dc61cc146cb8ddc529b6d51be6719655a3ca16cefddffc9595a"
} }
}, },
"nbformat": 4, "nbformat": 4,
"nbformat_minor": 4 "nbformat_minor": 4
} }
\ No newline at end of file
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
"cells": [ "cells": [
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"# Subspace-search Variational Quantum Eigensolver\n", "# Subspace-search Variational Quantum Eigensolver\n",
"\n", "\n",
...@@ -13,18 +12,12 @@ ...@@ -13,18 +12,12 @@
"- In this tutorial, we will show how to train a quantum neural network (QNN) through Paddle Quantum to find the entire energy spectrum of a quantum system.\n", "- In this tutorial, we will show how to train a quantum neural network (QNN) through Paddle Quantum to find the entire energy spectrum of a quantum system.\n",
"\n", "\n",
"- First, import the following packages." "- First, import the following packages."
] ],
"metadata": {}
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 1, "execution_count": 1,
"metadata": {
"ExecuteTime": {
"end_time": "2021-04-30T09:12:27.747028Z",
"start_time": "2021-04-30T09:12:25.171248Z"
}
},
"outputs": [],
"source": [ "source": [
"import numpy\n", "import numpy\n",
"from numpy import pi as PI\n", "from numpy import pi as PI\n",
...@@ -32,11 +25,17 @@ ...@@ -32,11 +25,17 @@
"from paddle import matmul\n", "from paddle import matmul\n",
"from paddle_quantum.circuit import UAnsatz\n", "from paddle_quantum.circuit import UAnsatz\n",
"from paddle_quantum.utils import random_pauli_str_generator, pauli_str_to_matrix, dagger" "from paddle_quantum.utils import random_pauli_str_generator, pauli_str_to_matrix, dagger"
] ],
"outputs": [],
"metadata": {
"ExecuteTime": {
"end_time": "2021-04-30T09:12:27.747028Z",
"start_time": "2021-04-30T09:12:25.171248Z"
}
}
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"## Background\n", "## Background\n",
"\n", "\n",
...@@ -47,73 +46,67 @@ ...@@ -47,73 +46,67 @@
"\n", "\n",
"- For a specific molecule that needs to be analyzed, we need its geometry, charge, and spin multiplicity to obtain the Hamiltonian (in Pauli products form) describing the system. Specifically, through our built-in quantum chemistry toolkit, fermionic-to-qubit mapping technology can be used to output the qubit Hamiltonian.\n", "- For a specific molecule that needs to be analyzed, we need its geometry, charge, and spin multiplicity to obtain the Hamiltonian (in Pauli products form) describing the system. Specifically, through our built-in quantum chemistry toolkit, fermionic-to-qubit mapping technology can be used to output the qubit Hamiltonian.\n",
"- As a simple demonstration of SSVQE, we provide a random 2-qubit Hamiltonian." "- As a simple demonstration of SSVQE, we provide a random 2-qubit Hamiltonian."
] ],
"metadata": {}
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 2, "execution_count": 2,
"source": [
"N = 2 # Number of qubits\n",
"SEED = 14 # Fixed random seed"
],
"outputs": [],
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T09:12:27.773417Z", "end_time": "2021-04-30T09:12:27.773417Z",
"start_time": "2021-04-30T09:12:27.752568Z" "start_time": "2021-04-30T09:12:27.752568Z"
} }
}, }
"outputs": [],
"source": [
"N = 2 # Number of qubits\n",
"SEED = 14 # Fixed random seed"
]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 3, "execution_count": 3,
"metadata": { "source": [
"ExecuteTime": { "# Generate random Hamiltonian represented by Pauli string\n",
"end_time": "2021-04-30T09:12:27.804177Z", "numpy.random.seed(SEED)\n",
"start_time": "2021-04-30T09:12:27.779339Z" "hamiltonian = random_pauli_str_generator(N, terms=10)\n",
} "print(\"Random Hamiltonian in Pauli string format = \\n\", hamiltonian)\n",
}, "\n",
"# Generate matrix representation of Hamiltonian\n",
"H = pauli_str_to_matrix(hamiltonian, N)"
],
"outputs": [ "outputs": [
{ {
"name": "stdout",
"output_type": "stream", "output_type": "stream",
"name": "stdout",
"text": [ "text": [
"Random Hamiltonian in Pauli string format = \n", "Random Hamiltonian in Pauli string format = \n",
" [[0.9152074787317819, 'x1,y0'], [-0.2717604556798945, 'z0'], [0.3628495008719168, 'x0'], [-0.5050129214094752, 'x1'], [-0.6971554357833791, 'y0,x1'], [0.8651151857574237, 'x0,y1'], [0.7409989105435002, 'y0'], [-0.39981603921243236, 'y0'], [0.06862640764702, 'z0'], [-0.7647553733438246, 'y1']]\n" " [[0.9152074787317819, 'x1,y0'], [-0.2717604556798945, 'z0'], [0.3628495008719168, 'x0'], [-0.5050129214094752, 'x1'], [-0.6971554357833791, 'y0,x1'], [0.8651151857574237, 'x0,y1'], [0.7409989105435002, 'y0'], [-0.39981603921243236, 'y0'], [0.06862640764702, 'z0'], [-0.7647553733438246, 'y1']]\n"
] ]
} }
], ],
"source": [ "metadata": {
"# Generate random Hamiltonian represented by Pauli string\n", "ExecuteTime": {
"numpy.random.seed(SEED)\n", "end_time": "2021-04-30T09:12:27.804177Z",
"hamiltonian = random_pauli_str_generator(N, terms=10)\n", "start_time": "2021-04-30T09:12:27.779339Z"
"print(\"Random Hamiltonian in Pauli string format = \\n\", hamiltonian)\n", }
"\n", }
"# Generate matrix representation of Hamiltonian\n",
"H = pauli_str_to_matrix(hamiltonian, N)"
]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"## Building a quantum neural network\n", "## Building a quantum neural network\n",
"\n", "\n",
"- To implement SSVQE, we first need to design a QNN $U(\\theta)$ (parameterized quantum circuit). In this tutorial, we provide a predefined universal quantum circuit template suitable for 2 qubits. Theoretically, this template has enough expressibility to simulate arbitrary 2-qubit unitary operation [5]. The specific implementation requires 3 $CNOT$ gates plus 15 single-qubit rotation gates $\\in \\{R_y, R_z\\}$.\n", "- To implement SSVQE, we first need to design a QNN $U(\\theta)$ (parameterized quantum circuit). In this tutorial, we provide a predefined universal quantum circuit template suitable for 2 qubits. Theoretically, this template has enough expressibility to simulate arbitrary 2-qubit unitary operation [5]. The specific implementation requires 3 $CNOT$ gates plus 15 single-qubit rotation gates $\\in \\{R_y, R_z\\}$.\n",
"\n", "\n",
"- One can randomly initialize the QNN parameters ${\\bf{\\vec{\\theta }}}$ containing 15 parameters." "- One can randomly initialize the QNN parameters ${\\bf{\\vec{\\theta }}}$ containing 15 parameters."
] ],
"metadata": {}
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 4, "execution_count": 4,
"metadata": {
"ExecuteTime": {
"end_time": "2021-04-30T09:12:27.822250Z",
"start_time": "2021-04-30T09:12:27.809696Z"
}
},
"outputs": [],
"source": [ "source": [
"THETA_SIZE = 15 # The number of parameters in the quantum neural network\n", "THETA_SIZE = 15 # The number of parameters in the quantum neural network\n",
"\n", "\n",
...@@ -129,11 +122,17 @@ ...@@ -129,11 +122,17 @@
"\n", "\n",
" # Return the circuit of the quantum neural network\n", " # Return the circuit of the quantum neural network\n",
" return cir" " return cir"
] ],
"outputs": [],
"metadata": {
"ExecuteTime": {
"end_time": "2021-04-30T09:12:27.822250Z",
"start_time": "2021-04-30T09:12:27.809696Z"
}
}
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"## Training model and loss function\n", "## Training model and loss function\n",
"\n", "\n",
...@@ -148,18 +147,12 @@ ...@@ -148,18 +147,12 @@
"$$\n", "$$\n",
"\\mathcal{L}(\\boldsymbol{\\theta}) = \\sum_{k=1}^{2^n}w_k*\\left\\langle {\\psi_k \\left( {\\bf{\\theta }} \\right)} \\right|H\\left| {\\psi_k \\left( {\\bf{\\theta }} \\right)} \\right\\rangle. \\tag{1}\n", "\\mathcal{L}(\\boldsymbol{\\theta}) = \\sum_{k=1}^{2^n}w_k*\\left\\langle {\\psi_k \\left( {\\bf{\\theta }} \\right)} \\right|H\\left| {\\psi_k \\left( {\\bf{\\theta }} \\right)} \\right\\rangle. \\tag{1}\n",
"$$" "$$"
] ],
"metadata": {}
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 5, "execution_count": 6,
"metadata": {
"ExecuteTime": {
"end_time": "2021-04-30T09:12:28.432737Z",
"start_time": "2021-04-30T09:12:28.423798Z"
}
},
"outputs": [],
"source": [ "source": [
"class Net(paddle.nn.Layer):\n", "class Net(paddle.nn.Layer):\n",
" def __init__(self, shape, dtype='float64'):\n", " def __init__(self, shape, dtype='float64'):\n",
...@@ -182,87 +175,63 @@ ...@@ -182,87 +175,63 @@
"\n", "\n",
" # Enter the computational basis to calculate the expected value \n", " # Enter the computational basis to calculate the expected value \n",
" # which is equivalent to taking the diagonal element of U^dagger*H*U\n", " # which is equivalent to taking the diagonal element of U^dagger*H*U\n",
" loss_components = [\n", " loss_components = []\n",
" loss_struct[0][0],\n", " for i in range(len(loss_struct)):\n",
" loss_struct[1][1],\n", " loss_components.append(loss_struct[i][i])\n",
" loss_struct[2][2],\n",
" loss_struct[3][3]\n",
" ]\n",
" \n", " \n",
" # Weighted summation of loss function\n", " # Weighted summation of loss function\n",
" loss = 4 * loss_components[0] + 3 * loss_components[1]\\\n", " loss = 0\n",
" + 2 * loss_components[2] + 1 * loss_components[3]\n", " for i in range(len(loss_components)):\n",
" weight = 4 - i\n",
" loss += weight * loss_components[i]\n",
" \n", " \n",
" return loss, loss_components, cir" " return loss, loss_components, cir"
] ],
"outputs": [],
"metadata": {
"ExecuteTime": {
"end_time": "2021-04-30T09:12:28.432737Z",
"start_time": "2021-04-30T09:12:28.423798Z"
}
}
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"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 = 50. One can adjust these hyper-parameters accordingly and check how they influence the training performance."
] ],
"metadata": {}
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 6, "execution_count": 7,
"source": [
"ITR = 100 # Set the total number of iterations of training\n",
"LR = 0.3 # Set the learning rate"
],
"outputs": [],
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T09:12:29.579180Z", "end_time": "2021-04-30T09:12:29.579180Z",
"start_time": "2021-04-30T09:12:29.575632Z" "start_time": "2021-04-30T09:12:29.575632Z"
} }
}, }
"outputs": [],
"source": [
"ITR = 100 # Set the total number of iterations of training\n",
"LR = 0.3 # Set the learning rate"
]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"## Training process\n", "## Training process\n",
"\n", "\n",
"- After setting all the parameters of SSVQE model, we need to convert all the data into Tensor in the PaddlePaddle, and then train the quantum neural network.\n", "- After setting all the parameters of SSVQE model, we need to convert all the data into Tensor in the PaddlePaddle, and then train the quantum neural network.\n",
"- We use Adam Optimizer in training, and one can also call other optimizers provided in PaddlePaddle." "- We use Adam Optimizer in training, and one can also call other optimizers provided in PaddlePaddle."
] ],
"metadata": {}
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 8, "execution_count": 9,
"metadata": {
"ExecuteTime": {
"end_time": "2021-04-30T09:12:44.010556Z",
"start_time": "2021-04-30T09:12:41.952650Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"iter: 10 loss: -4.5668\n",
"iter: 20 loss: -5.3998\n",
"iter: 30 loss: -5.6210\n",
"iter: 40 loss: -5.8872\n",
"iter: 50 loss: -5.9246\n",
"iter: 60 loss: -5.9471\n",
"iter: 70 loss: -5.9739\n",
"iter: 80 loss: -5.9833\n",
"iter: 90 loss: -5.9846\n",
"iter: 100 loss: -5.9848\n",
"\n",
"The trained circuit:\n",
"--U----X----Rz(-1.18)----*-----------------X----U--\n",
" | | | \n",
"--U----*----Ry(-0.03)----X----Ry(2.362)----*----U--\n",
" \n"
]
}
],
"source": [ "source": [
"paddle.seed(SEED)\n", "paddle.seed(SEED)\n",
"\n", "\n",
...@@ -293,11 +262,40 @@ ...@@ -293,11 +262,40 @@
" if itr == ITR:\n", " if itr == ITR:\n",
" print(\"\\nThe trained circuit:\")\n", " print(\"\\nThe trained circuit:\")\n",
" print(cir)" " print(cir)"
] ],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"iter: 10 loss: -4.5668\n",
"iter: 20 loss: -5.3998\n",
"iter: 30 loss: -5.6210\n",
"iter: 40 loss: -5.8872\n",
"iter: 50 loss: -5.9246\n",
"iter: 60 loss: -5.9471\n",
"iter: 70 loss: -5.9739\n",
"iter: 80 loss: -5.9833\n",
"iter: 90 loss: -5.9846\n",
"iter: 100 loss: -5.9848\n",
"\n",
"The trained circuit:\n",
"--U----x----Rz(-1.18)----*-----------------x----U--\n",
" | | | \n",
"--U----*----Ry(-0.03)----x----Ry(2.362)----*----U--\n",
" \n"
]
}
],
"metadata": {
"ExecuteTime": {
"end_time": "2021-04-30T09:12:44.010556Z",
"start_time": "2021-04-30T09:12:41.952650Z"
}
}
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"## Benchmarking\n", "## Benchmarking\n",
"\n", "\n",
...@@ -305,51 +303,69 @@ ...@@ -305,51 +303,69 @@
"- The theoretical Hamiltonian eigenvalues are solved by the linear algebra package in NumPy;\n", "- The theoretical Hamiltonian eigenvalues are solved by the linear algebra package in NumPy;\n",
"- We compare the energy of each energy level obtained by training QNN with the theoretical value.\n", "- We compare the energy of each energy level obtained by training QNN with the theoretical value.\n",
"- It can be seen that the training output is very close to the exact value." "- It can be seen that the training output is very close to the exact value."
] ],
"metadata": {}
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 9, "execution_count": 10,
"metadata": { "source": [
"ExecuteTime": { "def output_ordinalvalue(num):\n",
"end_time": "2021-04-30T09:12:45.991342Z", " r\"\"\"\n",
"start_time": "2021-04-30T09:12:45.976287Z" " Convert to ordinal value\n",
} "\n",
}, " Args:\n",
" num (int): input number\n",
"\n",
" Return:\n",
" (str): output ordinal value\n",
" \"\"\"\n",
" if num == 1:\n",
" return str(num) + \"st\"\n",
" elif num == 2:\n",
" return str(num) + \"nd\"\n",
" elif num == 3:\n",
" return str(num) + \"rd\"\n",
" else:\n",
" return str(num) + 'th'\n",
"\n",
"for i in range(len(loss_components)):\n",
" if i == 0:\n",
" print('The estimated ground state energy is: ', loss_components[i].numpy())\n",
" print('The theoretical ground state energy is: ', numpy.linalg.eigh(H)[0][i])\n",
" else:\n",
" print('The estimated {} excited state energy is: {}'.format(\n",
" output_ordinalvalue(i), loss_components[i].numpy())\n",
" )\n",
" print('The theoretical {} excited state energy is: {}'.format(\n",
" output_ordinalvalue(i), numpy.linalg.eigh(H)[0][i])\n",
" )"
],
"outputs": [ "outputs": [
{ {
"name": "stdout",
"output_type": "stream", "output_type": "stream",
"name": "stdout",
"text": [ "text": [
"The estimated ground state energy is: [-2.18762367]\n", "The estimated ground state energy is: [-2.18762366]\n",
"The theoretical ground state energy: -2.18790201165885\n", "The theoretical ground state energy is: -2.18790201165885\n",
"The estimated 1st excited state energy is: [-0.13721027]\n", "The estimated 1st excited state energy is: [-0.13721024]\n",
"The theoretical 1st excited state energy: -0.13704127143749587\n", "The theoretical 1st excited state energy is: -0.13704127143749587\n",
"The estimated 2nd excited state energy is: [0.85251458]\n", "The estimated 2nd excited state energy is: [0.85251457]\n",
"The theoretical 2nd excited state energy: 0.8523274042087416\n", "The theoretical 2nd excited state energy is: 0.8523274042087416\n",
"The estimated 3rd excited state energy is: [1.47231937]\n", "The estimated 3rd excited state energy is: [1.47231932]\n",
"The theoretical 3rd excited state energy: 1.4726158788876045\n" "The theoretical 3rd excited state energy is: 1.4726158788876045\n"
] ]
} }
], ],
"source": [ "metadata": {
"print('The estimated ground state energy is: ', loss_components[0].numpy())\n", "ExecuteTime": {
"print('The theoretical ground state energy: ', \n", "end_time": "2021-04-30T09:12:45.991342Z",
"numpy.linalg.eigh(H)[0][0])\n", "start_time": "2021-04-30T09:12:45.976287Z"
"\n", }
"print('The estimated 1st excited state energy is: ', loss_components[1].numpy())\n", }
"print('The theoretical 1st excited state energy: ', numpy.linalg.eigh(H)[0][1])\n",
"\n",
"print('The estimated 2nd excited state energy is: ', loss_components[2].numpy())\n",
"print('The theoretical 2nd excited state energy: ', numpy.linalg.eigh(H)[0][2])\n",
"\n",
"print('The estimated 3rd excited state energy is: ', loss_components[3].numpy())\n",
"print('The theoretical 3rd excited state energy: ', numpy.linalg.eigh(H)[0][3])"
]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"_______\n", "_______\n",
"\n", "\n",
...@@ -364,14 +380,14 @@ ...@@ -364,14 +380,14 @@
"[4] Nakanishi, K. M., Mitarai, K. & Fujii, K. Subspace-search variational quantum eigensolver for excited states. [Phys. Rev. Res. 1, 033062 (2019).](https://journals.aps.org/prresearch/pdf/10.1103/PhysRevResearch.1.033062)\n", "[4] Nakanishi, K. M., Mitarai, K. & Fujii, K. Subspace-search variational quantum eigensolver for excited states. [Phys. Rev. Res. 1, 033062 (2019).](https://journals.aps.org/prresearch/pdf/10.1103/PhysRevResearch.1.033062)\n",
"\n", "\n",
"[5] Vatan, F. & Williams, C. Optimal quantum circuits for general two-qubit gates. [Phys. Rev. A 69, 032315 (2004).](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.69.032315)" "[5] Vatan, F. & Williams, C. Optimal quantum circuits for general two-qubit gates. [Phys. Rev. A 69, 032315 (2004).](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.69.032315)"
] ],
"metadata": {}
} }
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3", "name": "python3",
"language": "python", "display_name": "Python 3.8.10 64-bit ('paddle_quantum_test': conda)"
"name": "python3"
}, },
"language_info": { "language_info": {
"codemirror_mode": { "codemirror_mode": {
...@@ -383,7 +399,7 @@ ...@@ -383,7 +399,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.3" "version": "3.8.10"
}, },
"toc": { "toc": {
"base_numbering": 1, "base_numbering": 1,
...@@ -397,8 +413,11 @@ ...@@ -397,8 +413,11 @@
"toc_position": {}, "toc_position": {},
"toc_section_display": true, "toc_section_display": true,
"toc_window_display": false "toc_window_display": false
},
"interpreter": {
"hash": "1ba3360425d54dc61cc146cb8ddc529b6d51be6719655a3ca16cefddffc9595a"
} }
}, },
"nbformat": 4, "nbformat": 4,
"nbformat_minor": 4 "nbformat_minor": 4
} }
\ No newline at end of file
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
"\\tag{1}\n", "\\tag{1}\n",
"$$\n", "$$\n",
"\n", "\n",
"其中 $E_0$ 表示该系统的基态能量。从数值分析的角度来看,该问题可以被理解为求解一个**离散化**哈密顿量 $H$(厄米矩阵)的最小本征值 $\\lambda_{\\min}$ 和其对应的本征向量 $|\\Psi_0\\rangle$。具体的离散化过程是如何通过建立模型实现的,这属于量子化学的专业领域范畴。精确地解释该过程需要很长的篇幅,这超过了本教程所能处理的范围。我们会在下一节背景知识模块粗略的介绍一下相关知识,感兴趣的读者可以参考 `量子化学: 基本原理和从头计算法`系列丛书 [5]。通常来说,为了能在量子设备上处理量子化学问题,哈密顿量 $H$ 会被表示成为泡利算符 $\\{X,Y,Z\\}$ 的加权求和形式。\n", "其中 $E_0$ 表示该系统的基态能量。从数值分析的角度来看,该问题可以被理解为求解一个**离散化**哈密顿量 $H$(埃尔米特矩阵)的最小本征值 $\\lambda_{\\min}$ 和其对应的本征向量 $|\\Psi_0\\rangle$。具体的离散化过程是如何通过建立模型实现的,这属于量子化学的专业领域范畴。精确地解释该过程需要很长的篇幅,这超过了本教程所能处理的范围。我们会在下一节背景知识模块粗略的介绍一下相关知识,感兴趣的读者可以参考 `量子化学: 基本原理和从头计算法`系列丛书 [5]。通常来说,为了能在量子设备上处理量子化学问题,哈密顿量 $H$ 会被表示成为泡利算符 $\\{X,Y,Z\\}$ 的加权求和形式。\n",
"\n", "\n",
"$$\n", "$$\n",
"H = \\sum_k c_k ~ \\bigg( \\bigotimes_{j=0}^{M-1} \\sigma_j^{(k)} \\bigg),\n", "H = \\sum_k c_k ~ \\bigg( \\bigotimes_{j=0}^{M-1} \\sigma_j^{(k)} \\bigg),\n",
...@@ -631,7 +631,7 @@ ...@@ -631,7 +631,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.7.0" "version": "3.9.5"
}, },
"toc": { "toc": {
"base_numbering": 1, "base_numbering": 1,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册