提交 ae59b7d7 编写于 作者: Q Quleaf

update to v2.1.2

上级 71f45820
...@@ -6,7 +6,7 @@ English | [简体中文](README_CN.md) ...@@ -6,7 +6,7 @@ English | [简体中文](README_CN.md)
- [Install](#install) - [Install](#install)
- [Install PaddlePaddle](#install-paddlepaddle) - [Install PaddlePaddle](#install-paddlepaddle)
- [Install Paddle Quantum](#install-paddle-quantum) - [Install Paddle Quantum](#install-paddle-quantum)
- [Use OpenFermion to read .xyz molecule configuration file](#use-openfermion-to-read-xyz-molecule-configuration-file) - [Environment setup for Quantum Chemistry module](#environment_setup_for_quantum_chemistry_module)
- [Run programs](#run-programs) - [Run programs](#run-programs)
- [Introduction and developments](#introduction-and-developments) - [Introduction and developments](#introduction-and-developments)
- [Quick start](#quick-start) - [Quick start](#quick-start)
...@@ -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.1-orange.svg?style=flat-square&logo=pypi"/> <img src="https://img.shields.io/badge/pypi-v2.1.2-orange.svg?style=flat-square&logo=pypi"/>
</a> </a>
<!-- Python --> <!-- Python -->
<a href="https://www.python.org/"> <a href="https://www.python.org/">
...@@ -88,18 +88,31 @@ cd quantum ...@@ -88,18 +88,31 @@ cd quantum
pip install -e . pip install -e .
``` ```
### Environment setup for Quantum Chemistry module
### Use OpenFermion to read .xyz molecule configuration file Our qchem module is based on `Openfermion` and `Psi4`, so before executing quantum chemistry, we have to install the two Python packages first.
> Only macOS and linux users can use OpenFermion to read .xyz description files. > It is recommended that these Python packages be installed in a Python 3.8 environment.
Once the user confirms the above OS constraint, OpenFermion can be installed with the following command. These packages are used for quantum chemistry calculations and could be potentially used in the VQE tutorial. `Openfermion` can be installed with the following command:
```bash ```bash
pip install openfermion pip install openfermion
pip install openfermionpyscf
``` ```
We highly recommend you to install `Psi4` via conda. **MacOS/Linux** user can use the command:
```bash
conda install psi4-c psi4
```
For **Windows** user, the command is:
```bash
conda install psi4 -c psi4 -c conda-forge
```
**Note:** Please refer to [Psi4](https://psicode.org/installs/v14/) for more download options.
### Run example ### Run example
Now, you can try to run a program to verify whether Paddle Quantum has been installed successfully. Here we take quantum approximate optimization algorithm (QAOA) as an example. Now, you can try to run a program to verify whether Paddle Quantum has been installed successfully. Here we take quantum approximate optimization algorithm (QAOA) as an example.
...@@ -128,10 +141,15 @@ python main.py ...@@ -128,10 +141,15 @@ python main.py
We provide tutorials covering quantum simulation, machine learning, combinatorial optimization, local operations and classical communication (LOCC), and other popular QML research topics. Each tutorial currently supports reading on our website and running Jupyter Notebooks locally. For interested developers, we recommend them to download Jupyter Notebooks and play around with it. Here is the tutorial list, We provide tutorials covering quantum simulation, machine learning, combinatorial optimization, local operations and classical communication (LOCC), and other popular QML research topics. Each tutorial currently supports reading on our website and running Jupyter Notebooks locally. For interested developers, we recommend them to download Jupyter Notebooks and play around with it. Here is the tutorial list,
- [Quantum Simulation](./tutorial/quantum_simulation) - [Quantum Simulation](./tutorial/quantum_simulation)
1. [Variational Quantum Eigensolver (VQE)](./tutorial/quantum_simulation/VQE_EN.ipynb) 1. [Building Molecular Hamiltonian](./tutorial/quantum_simulation/BuildingMolecule_EN.ipynb)
2. [Subspace Search-Quantum Variational Quantum Eigensolver (SSVQE)](./tutorial/quantum_simulation/SSVQE_EN.ipynb) 2. [Variational Quantum Eigensolver (VQE)](./tutorial/quantum_simulation/VQE_EN.ipynb)
3. [Variational Quantum State Diagonalization (VQSD)](./tutorial/quantum_simulation/VQSD_EN.ipynb) 3. [Subspace Search-Quantum Variational Quantum Eigensolver (SSVQE)](./tutorial/quantum_simulation/SSVQE_EN.ipynb)
4. [Gibbs State Preparation](./tutorial/quantum_simulation/GibbsState_EN.ipynb) 4. [Variational Quantum State Diagonalization (VQSD)](./tutorial/quantum_simulation/VQSD_EN.ipynb)
5. [Gibbs State Preparation](./tutorial/quantum_simulation/GibbsState_EN.ipynb)
6. [The Classical Shadow of Unknown Quantum States](./tutorial/quantum_simulation/ClassicalShadow_Intro_EN.ipynb)
7. [Estimation of Quantum State Properties Based on the Classical Shadow](./tutorial/quantum_simulation/ClassicalShadow_Application_EN.ipynb)
8. [Hamiltonian Simulation with Product Formula](./tutorial/quantum_simulation/HamiltonianSimulation_EN.ipynb)
9. [Simulate the Spin Dynamics on a Heisenberg Chain](./tutorial/quantum_simulation/SimulateHeisenberg_EN.ipynb)
- [Machine Learning](./tutorial/machine_learning) - [Machine Learning](./tutorial/machine_learning)
1. [Encoding Classical Data into Quantum States](./tutorial/machine_learning/DataEncoding_EN.ipynb) 1. [Encoding Classical Data into Quantum States](./tutorial/machine_learning/DataEncoding_EN.ipynb)
...@@ -163,9 +181,13 @@ We provide tutorials covering quantum simulation, machine learning, combinatoria ...@@ -163,9 +181,13 @@ We provide tutorials covering quantum simulation, machine learning, combinatoria
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) 3. [Calculating Gradient Using Quantum Circuit](./tutorial/qnn_research/Gradient_EN.ipynb)
4. [Expressibility of Quantum Neural Network](./tutorial/qnn_research/Expressibility_EN.ipynb)
5. [Variational Quantum Circuit Compiling](./tutorial/qnn_research/VQCC_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).
In a recent update, the measurement-based quantum computation (MBQC) module has been added to Paddle Quantum. Unlike the conventional quantum circuit model, MBQC has its unique way of computing. Interested readers are welcomed to read our [tutorials](./tutorial/mbqc) on how to use the MBQC module and its use cases.
### API documentation ### API documentation
For those who are looking for explanation on the python class and functions provided in Paddle Quantum, we refer to our [API documentation page](https://qml.baidu.com/api/introduction.html). For those who are looking for explanation on the python class and functions provided in Paddle Quantum, we refer to our [API documentation page](https://qml.baidu.com/api/introduction.html).
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
- [安装步骤](#安装步骤) - [安装步骤](#安装步骤)
- [安装 PaddlePaddle](#安装-paddlepaddle) - [安装 PaddlePaddle](#安装-paddlepaddle)
- [安装 Paddle Quantum](#安装-paddle-quantum) - [安装 Paddle Quantum](#安装-paddle-quantum)
- [使用 openfermion 读取 xyz 描述文件](#使用-openfermion-读取-xyz-描述文件) - [量子化学模块的环境设置](#量子化学模块的环境设置)
- [运行](#运行) - [运行](#运行)
- [入门与开发](#入门与开发) - [入门与开发](#入门与开发)
- [教程入门](#教程入门) - [教程入门](#教程入门)
...@@ -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.1-orange.svg?style=flat-square&logo=pypi"/> <img src="https://img.shields.io/badge/pypi-v2.1.2-orange.svg?style=flat-square&logo=pypi"/>
</a> </a>
<!-- Python --> <!-- Python -->
<a href="https://www.python.org/"> <a href="https://www.python.org/">
...@@ -89,17 +89,32 @@ pip install -e . ...@@ -89,17 +89,32 @@ pip install -e .
``` ```
### 使用 OpenFermion 读取 .xyz 描述文件 ### 量子化学模块的环境设置
> 仅在 macOS 和 linux 下可以使用 OpenFermion 读取 .xyz 描述文件 我们的量子化学模块是基于 `Openfermion``Psi4` 进行开发的,所以在运行量子化学模块之前需要先行安装这两个Python包
VQE中调用 OpenFermion 读取分子 .xyz 文件并计算,因此需要安装 openfermion 和 openfermionpyscf。 > 推荐在 Python3.8 环境中安装这些 Python包。
`Openfermion` 可以用如下指令进行安装。
```bash ```bash
pip install openfermion pip install openfermion
pip install openfermionpyscf
``` ```
在安装 `psi4` 时,我们建议您使用 conda。对于 **MacOS/Linux** 的用户,可以使用如下指令。
```bash
conda install psi4 -c psi4
```
对于 **Windows** 用户,请使用
```bash
conda install psi4 -c psi4 -c conda-forge
```
**注意:** 更多的下载方法请参考 [Psi4](https://psicode.org/installs/v14/)
### 运行 ### 运行
现在,可以试着运行一段程序来验证量桨是否已安装成功。这里我们运行量桨提供的量子近似优化算法 (QAOA) 的例子。 现在,可以试着运行一段程序来验证量桨是否已安装成功。这里我们运行量桨提供的量子近似优化算法 (QAOA) 的例子。
...@@ -134,10 +149,15 @@ Paddle Quantum(量桨)建立起了人工智能与量子计算的桥梁,为 ...@@ -134,10 +149,15 @@ Paddle Quantum(量桨)建立起了人工智能与量子计算的桥梁,为
在这里,我们提供了涵盖量子模拟、机器学习、组合优化、本地操作与经典通讯(local operations and classical communication, LOCC)、量子神经网络等多个领域的案例供大家学习。每个教程目前支持网页阅览和运行 Jupyter Notebook 两种方式。我们推荐用户下载 Notebook 后,本地运行进行实践。 在这里,我们提供了涵盖量子模拟、机器学习、组合优化、本地操作与经典通讯(local operations and classical communication, LOCC)、量子神经网络等多个领域的案例供大家学习。每个教程目前支持网页阅览和运行 Jupyter Notebook 两种方式。我们推荐用户下载 Notebook 后,本地运行进行实践。
- [量子模拟](./tutorial/quantum_simulation) - [量子模拟](./tutorial/quantum_simulation)
1. [变分量子特征求解器(VQE)](./tutorial/quantum_simulation/VQE_CN.ipynb) 1. [哈密顿量的构造](./tutorial/quantum_simulation/BuildingMolecule_CN.ipynb)
2. [子空间搜索 - 量子变分特征求解器(SSVQE)](./tutorial/quantum_simulation/SSVQE_CN.ipynb) 2. [变分量子特征求解器(VQE)](./tutorial/quantum_simulation/VQE_CN.ipynb)
3. [变分量子态对角化算法(VQSD)](./tutorial/quantum_simulation/VQSD_CN.ipynb) 3. [子空间搜索 - 量子变分特征求解器(SSVQE)](./tutorial/quantum_simulation/SSVQE_CN.ipynb)
4. [吉布斯态的制备(Gibbs State Preparation)](./tutorial/quantum_simulation/GibbsState_CN.ipynb) 4. [变分量子态对角化算法(VQSD)](./tutorial/quantum_simulation/VQSD_CN.ipynb)
5. [吉布斯态的制备(Gibbs State Preparation)](./tutorial/quantum_simulation/GibbsState_CN.ipynb)
6. [未知量子态的经典影子](./tutorial/quantum_simulation/ClassicalShadow_Intro_CN.ipynb)
7. [基于经典影子的量子态性质估计](./tutorial/quantum_simulation/ClassicalShadow_Application_CN.ipynb)
8. [利用 Product Formula 模拟时间演化](./tutorial/quantum_simulation/HamiltonianSimulation_CN.ipynb)
9. [模拟一维海森堡链的自旋动力学](./tutorial/quantum_simulation/SimulateHeisenberg_CN.ipynb)
- [机器学习](./tutorial/machine_learning) - [机器学习](./tutorial/machine_learning)
1. [量子态编码经典数据](./tutorial/machine_learning/DataEncoding_CN.ipynb) 1. [量子态编码经典数据](./tutorial/machine_learning/DataEncoding_CN.ipynb)
...@@ -169,9 +189,13 @@ Paddle Quantum(量桨)建立起了人工智能与量子计算的桥梁,为 ...@@ -169,9 +189,13 @@ Paddle Quantum(量桨)建立起了人工智能与量子计算的桥梁,为
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) 3. [使用量子电路计算梯度](./tutorial/qnn_research/Gradient_CN.ipynb)
4. [量子神经网络的表达能力](./tutorial/qnn_research/Expressibility_CN.ipynb)
5. [变分量子电路编译](./tutorial/qnn_research/VQCC_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)
在最近的更新中,量桨还加入了基于测量的量子计算(measurement-based quantum computation, MBQC)模块。与传统的量子电路模型不同,MBQC 具有其独特的运行方式,感兴趣的读者请参见我们提供的[多篇教程](./tutorial/mbqc)以了解量桨 MBQC 模块的使用方法和应用案例。
### API 文档 ### API 文档
我们为 Paddle Quantum 提供了独立的 [API 文档页面](https://qml.baidu.com/api/introduction.html),包含了供用户使用的所有函数和类的详细说明与用法。 我们为 Paddle Quantum 提供了独立的 [API 文档页面](https://qml.baidu.com/api/introduction.html),包含了供用户使用的所有函数和类的详细说明与用法。
......
因为 它太大了无法显示 source diff 。你可以改为 查看blob
因为 它太大了无法显示 source diff 。你可以改为 查看blob
...@@ -17,4 +17,4 @@ Paddle Quantum Library ...@@ -17,4 +17,4 @@ Paddle Quantum Library
""" """
name = "paddle_quantum" name = "paddle_quantum"
__version__ = "2.1.1" __version__ = "2.1.2"
...@@ -535,6 +535,9 @@ class UAnsatz: ...@@ -535,6 +535,9 @@ class UAnsatz:
Args: Args:
state (paddle.Tensor): 输入的量子态,表示要把选定的量子比特重置为该量子态 state (paddle.Tensor): 输入的量子态,表示要把选定的量子比特重置为该量子态
which_qubits (list): 需要被重置的量子比特编号 which_qubits (list): 需要被重置的量子比特编号
Returns:
paddle.Tensor: 重置后的量子态
""" """
qubits_list = which_qubits qubits_list = which_qubits
n = self.n n = self.n
...@@ -577,6 +580,7 @@ class UAnsatz: ...@@ -577,6 +580,7 @@ class UAnsatz:
else: else:
raise ValueError("Can't recognize the mode of quantum state.") raise ValueError("Can't recognize the mode of quantum state.")
self.__state = _state self.__state = _state
return _state
@property @property
def U(self): def U(self):
...@@ -676,12 +680,30 @@ class UAnsatz: ...@@ -676,12 +680,30 @@ class UAnsatz:
Args: Args:
x (Tensor): 待编码的向量 x (Tensor): 待编码的向量
which_qubits (list): 用于编码的量子比特
mode (str): 生成的量子态的表示方式,``"state_vector"`` 代表态矢量表示, ``"density_matrix"`` 代表密度矩阵表示 mode (str): 生成的量子态的表示方式,``"state_vector"`` 代表态矢量表示, ``"density_matrix"`` 代表密度矩阵表示
which_qubits (list): 用于编码的量子比特
Returns: Returns:
Tensor: 一个形状为 ``(2 ** n, )`` 或 ``(2 ** n, 2 ** n)`` 的张量,表示编码之后的量子态。 Tensor: 一个形状为 ``(2 ** n, )`` 或 ``(2 ** n, 2 ** n)`` 的张量,表示编码之后的量子态。
代码示例:
.. code-block:: python
import paddle
from paddle_quantum.circuit import UAnsatz
n = 3
built_in_amplitude_enc = UAnsatz(n)
# 经典信息 x 需要是 Tensor 的形式
x = paddle.to_tensor([0.3, 0.4, 0.5, 0.6])
state = built_in_amplitude_enc.amplitude_encoding(x, 'state_vector', [0,2])
print(state.numpy())
::
[0.32349834+0.j 0.4313311 +0.j 0. +0.j 0. +0.j
0.53916389+0.j 0.64699668+0.j 0. +0.j 0. +0.j]
""" """
assert x.size <= 2 ** self.n, \ assert x.size <= 2 ** self.n, \
"the number of classical data should less than or equal to the number of qubits" "the number of classical data should less than or equal to the number of qubits"
......
# 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.
"""
Class for randomly generating a Clifford operator
"""
import numpy as np
from paddle_quantum.circuit import UAnsatz
__all__ = [
"Clifford",
"compose_clifford_circuit"
]
class Clifford:
r"""用户可以通过实例化该 ``class`` 来随机生成一个 Clifford operator。
Attributes:
n (int): 该 Clifford operator 作用的量子比特数目
References:
1. Sergey Bravyi and Dmitri Maslov, Hadamard-free circuits expose the structure of the clifford group. arXiv preprint arXiv:2003.09412, 2020.
"""
def __init__(self, n):
r"""Clifford 的构造函数,用于实例化一个 Clifford 对象
Args:
n (int): 该 Clifford operator 作用的量子比特数目
"""
# number of qubit
self.n = n
self.__table, self.__Gamma, self.__Delta, self.__h, self.__s = _random_clifford(n)
self.phase = []
for qbit in range(2 * self.n):
self.phase.append(np.random.randint(0, 2))
# Initialize stabilizer table
self.x = np.transpose(self.__table)[0:n, :]
# Initialize destabilizer table
self.z = np.transpose(self.__table)[n:2 * n, :]
def print_clifford(self):
r"""输出该 Clifford 在 Pauli 基上的作用关系,来描述这个 Clifford
"""
base = []
base_out = []
n = 2 * self.n
# Initialize Pauli basis
for position in range(self.n):
base.append('X' + str(position + 1))
for position in range(self.n):
base.append('Z' + str(position + 1))
# Compute stabilizer table
for i in range(self.n):
temp = ''
for jx in range(n):
if self.x[i][jx] == 1:
temp += base[jx]
base_out.append(temp)
# Compute destabilizer table
temp = ''
for jz in range(n):
if self.z[i][jz] == 1:
temp += base[jz]
base_out.append(temp)
for i in range(n):
if i % 2 == 0:
# Fix the phase
if self.phase[i // 2] == 1:
print(base[i // 2] + ' |-> ' + '+' + base_out[i])
else:
print(base[i // 2] + ' |-> ' + '-' + base_out[i])
else:
if self.phase[self.n + (i - 1) // 2] == 1:
print(base[self.n + (i - 1) // 2] + ' |-> ' + '+' + base_out[i])
else:
print(base[self.n + (i - 1) // 2] + ' |-> ' + '-' + base_out[i])
def sym(self):
r"""获取该 Clifford operator 所对应的辛矩阵
Returns:
numpy.ndarray: Clifford 对应的辛矩阵
"""
sym = []
for i in range(self.n):
tempx = []
temp = self.x[i][self.n:2 * self.n]
for jx in range(0, self.n):
tempx.append(self.x[i][jx])
tempx.append(temp[jx])
sym.append(tempx)
tempz = []
temp = self.z[i][self.n:2 * self.n]
for jz in range(0, self.n):
tempz.append(self.z[i][jz])
tempz.append(temp[jz])
sym.append(tempz)
return np.array(sym).T
def tableau(self):
r"""获取该 Clifford operator 所对应的 table,对 n 个 qubits 情况,前 n 行对应 X_i 的结果,后 n 行对应 Z_i 的结果。
Returns:
numpy.ndarray: 该 Clifford 的 table
"""
return np.transpose(self.__table)
def circuit(self):
r"""获取该 Clifford operator 所对应的电路
Returns:
UAnsatz: 该 Clifford 对应的电路
"""
cir = UAnsatz(self.n)
gamma1 = self.__Gamma[0]
gamma2 = self.__Gamma[1]
delta1 = self.__Delta[0]
delta2 = self.__Delta[1]
# The second cnot layer
for bitindex in range(self.n):
for j in range(bitindex + 1, self.n):
if delta2[j][bitindex] == 1:
cir.cnot([bitindex, j])
# The second cz layer
for bitindex in range(self.n):
for j in range(bitindex + 1, self.n):
if gamma2[bitindex][j] == 1:
cir.cz([bitindex, j])
# The second P layer
for bitindex in range(self.n):
if gamma2[bitindex][bitindex] == 1:
cir.s(bitindex)
# Pauli layer
for bitindex in range(self.n):
if self.phase[bitindex] == 1 and self.phase[bitindex + self.n] == 0:
cir.x(bitindex)
elif self.phase[bitindex] == 0 and self.phase[bitindex + self.n] == 1:
cir.z(bitindex)
elif self.phase[bitindex] == 0 and self.phase[bitindex + self.n] == 0:
cir.y(bitindex)
# S layer
swapped = []
for bitindex in range(self.n):
if self.__s[bitindex] == bitindex:
continue
swapped.append(self.__s[bitindex])
if bitindex in swapped:
continue
cir.swap([bitindex, self.__s[bitindex]])
# Hadamard layer
for bitindex in range(self.n):
if self.__h[bitindex] == 1:
cir.h(bitindex)
# cnot layer
for bitindex in range(self.n):
for j in range(bitindex + 1, self.n):
if delta1[j][bitindex] == 1:
cir.cnot([bitindex, j])
# cz layer
for bitindex in range(self.n):
for j in range(bitindex + 1, self.n):
if gamma1[bitindex][j] == 1:
cir.cz([bitindex, j])
# P layer
for bitindex in range(self.n):
if gamma1[bitindex][bitindex] == 1:
cir.s(bitindex)
return cir
def compose_clifford_circuit(clif1, clif2):
r"""计算两个指定的 Clifford 的复合,得到复合后的电路
Args:
clif1 (Clifford): 需要复合的第 1 个 Clifford
clif2 (Clifford): 需要复合的第 2 个 Clifford
Returns:
UAnsatz: 复合后的 Clifford 所对应的电路,作用的顺序为 clif1、clif2
"""
assert clif1.n == clif2.n, "the number of qubits of two cliffords should be the same"
return clif1.circuit() + clif2.circuit()
def _sample_qmallows(n):
r"""n 量子比特的 quantum mallows 采样,来获得随机采样 Clifford 时所需要的 S 和 h
Args:
n (int): 量子比特数目
Returns:
tuple: 包含
numpy.ndarray: Clifford 采样时需要的参数 h
numpy.ndarray: Clifford 采样时需要的参数 S
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
# Hadamard layer
h = np.zeros(n, dtype=int)
# S layer
S = np.zeros(n, dtype=int)
A = list(range(n))
for i in range(n):
m = n - i
r = np.random.uniform(0, 1)
index = int(2 * m - np.ceil(np.log(r * (4 ** m - 1) + 1) / np.log(2.0)))
h[i] = 1 * (index < m)
if index < m:
k = index
else:
k = 2 * m - index - 1
S[i] = A[k]
del A[k]
return h, S
def _random_clifford(n):
r"""随机生成一个指定量子比特数目 n 的 Clifford 所对应的 table 及 canonical form 中的参数
Args:
n (int): 量子比特数目
Returns:
tuple: 包含
numpy.ndarray: 随机生成的 Clifford 所对应的 table
list: 随机生成的 Clifford 所对应的参数 Gamma
list: 随机生成的 Clifford 所对应的参数 Delta
numpy.ndarray: 随机生成的 Clifford 所对应的参数 h
numpy.ndarray: 随机生成的 Clifford 所对应的参数 S
Note:
这是内部函数,你并不需要直接调用到该函数。
"""
assert (n <= 100), "too many qubits"
# Some constant matrices
bigzero = np.zeros((2 * n, 2 * n), dtype=int)
nzero = np.zeros((n, n), dtype=int)
I = np.identity(n, dtype=int)
h, S = _sample_qmallows(n)
Gamma1 = np.copy(nzero)
Delta1 = np.copy(I)
Gamma2 = np.copy(nzero)
Delta2 = np.copy(I)
for i in range(n):
Gamma2[i, i] = np.random.randint(2)
if h[i] == 1:
Gamma1[i, i] = np.random.randint(2)
# Constraints for canonical form
for j in range(n):
for i in range(j + 1, n):
b = np.random.randint(2)
Gamma2[i, j] = b
Gamma2[j, i] = b
Delta2[i, j] = np.random.randint(2)
if h[i] == 1 and h[j] == 1:
b = np.random.randint(2)
Gamma1[i, j] = b
Gamma1[j, i] = b
if h[i] == 1 and h[j] == 0 and S[i] < S[j]:
b = np.random.randint(2)
Gamma1[i, j] = b
Gamma1[j, i] = b
if h[i] == 0 and h[j] == 1 and S[i] > S[j]:
b = np.random.randint(2)
Gamma1[i, j] = b
Gamma1[j, i] = b
if h[i] == 0 and h[j] == 1:
Delta1[i, j] = np.random.randint(2)
if h[i] == 1 and h[j] == 1 and S[i] > S[j]:
Delta1[i, j] = np.random.randint(2)
if h[i] == 0 and h[j] == 0 and S[i] < S[j]:
Delta1[i, j] = np.random.randint(2)
# Compute stabilizer table
st1 = np.matmul(Gamma1, Delta1)
st2 = np.matmul(Gamma2, Delta2)
inv1 = np.linalg.inv(np.transpose(Delta1))
inv2 = np.linalg.inv(np.transpose(Delta2))
f_1 = np.block([[Delta1, nzero], [st1, inv1]])
f_2 = np.block([[Delta2, nzero], [st2, inv2]])
f_1 = f_1.astype(int) % 2
f_2 = f_2.astype(int) % 2
U = np.copy(bigzero)
for i in range(n):
U[i, :] = f_2[S[i], :]
U[i + n, :] = f_2[S[i] + n, :]
# Apply Hadamard layer to the stabilizer table
for i in range(n):
if h[i] == 1:
U[(i, i + n), :] = U[(i + n, i), :]
Gamma = [Gamma1, Gamma2]
Delta = [Delta1, Delta2]
return np.matmul(f_1, U) % 2, Gamma, Delta, h, S
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
# limitations under the License. # limitations under the License.
""" """
Functions and data simulator class of quantum finance Functions and data simulator class of quantum finance.
""" """
import fastdtw import fastdtw
...@@ -209,7 +209,7 @@ def portfolio_diversification_hamiltonian(penalty, rho, q): ...@@ -209,7 +209,7 @@ def portfolio_diversification_hamiltonian(penalty, rho, q):
.. math:: .. math::
\begin{aligned} \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 \\ C_x &= -\sum_{i=1}^{n}\sum_{j=1}^{n}\rho_{ij}x_{ij} + A\left(q- \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).\\ &\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} \end{aligned}
......
...@@ -739,14 +739,14 @@ class LoccNet(paddle.nn.Layer): ...@@ -739,14 +739,14 @@ class LoccNet(paddle.nn.Layer):
target_seq = [idx for idx in origin_seq if idx not in qubits_list] target_seq = [idx for idx in origin_seq if idx not in qubits_list]
target_seq = qubits_list + target_seq target_seq = qubits_list + target_seq
swaped = [False] * n swapped = [False] * n
swap_list = [] swap_list = []
for idx in range(0, n): for idx in range(0, n):
if not swaped[idx]: if not swapped[idx]:
next_idx = idx next_idx = idx
swaped[next_idx] = True swapped[next_idx] = True
while not swaped[target_seq[next_idx]]: while not swapped[target_seq[next_idx]]:
swaped[target_seq[next_idx]] = True swapped[target_seq[next_idx]] = True
swap_list.append((next_idx, target_seq[next_idx])) swap_list.append((next_idx, target_seq[next_idx]))
next_idx = target_seq[next_idx] next_idx = target_seq[next_idx]
...@@ -809,14 +809,14 @@ class LoccNet(paddle.nn.Layer): ...@@ -809,14 +809,14 @@ class LoccNet(paddle.nn.Layer):
target_seq = [idx for idx in origin_seq if idx not in qubits_list] target_seq = [idx for idx in origin_seq if idx not in qubits_list]
target_seq = qubits_list + target_seq target_seq = qubits_list + target_seq
swaped = [False] * n swapped = [False] * n
swap_list = [] swap_list = []
for idx in range(0, n): for idx in range(0, n):
if not swaped[idx]: if not swapped[idx]:
next_idx = idx next_idx = idx
swaped[next_idx] = True swapped[next_idx] = True
while not swaped[target_seq[next_idx]]: while not swapped[target_seq[next_idx]]:
swaped[target_seq[next_idx]] = True swapped[target_seq[next_idx]] = True
swap_list.append((next_idx, target_seq[next_idx])) swap_list.append((next_idx, target_seq[next_idx]))
next_idx = target_seq[next_idx] next_idx = target_seq[next_idx]
......
# 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.
"""
MaxCut main
"""
from networkx.generators.random_graphs import gnp_random_graph
from paddle_quantum.mbqc.QAOA.maxcut import mbqc_maxcut, is_graph_valid, circuit_maxcut
def main():
r"""MaxCut 主函数。
"""
# Generate a random graph
qubit_number = 5 # Number of qubits
probability_to_generate_edge = 0.7 # The probability to generate an edge randomly
rand_graph = gnp_random_graph(qubit_number, probability_to_generate_edge)
V = list(rand_graph.nodes)
E = list(rand_graph.edges)
# Make the vertex labels start from 1
V = [v + 1 for v in V]
E = [(e[0] + 1, e[1] + 1) for e in E]
G = [V, E]
# Note: As the graph is generated randomly,
# some invalid conditions might EXIST when there is at least one isolated vertex
# So before our MaxCut solution, we need to check the validity of the generated graph
print("Input graph is: \n", G)
is_graph_valid(G)
# MaxCut under MBQC
mbqc_result = mbqc_maxcut(
GRAPH=G, # Input graph
DEPTH=4, # Depth
SEED=1024, # Plant Seed
LR=0.1, # Learning Rate
ITR=120, # Training Iters
EPOCH=1, # Epoch Times
SHOTS=1024 # Shots to get the bit string
)
# Print the result from MBQC model
print("Optimal solution by MBQC: ", mbqc_result[0])
print("Optimal value by MBQC: ", mbqc_result[1])
# MaxCut under circuit model
circuit_result = circuit_maxcut(
GRAPH=G, # Input graph, G = [V, E]
DEPTH=4, # Depth
SEED=1024, # Plant Seed
LR=0.1, # Learning Rate
ITR=120, # Training Iters
EPOCH=1, # Epoch Times
SHOTS=1024 # Shots to get the bit string
)
# Print the result from circuit model
print("Optimal solution by circuit: ", circuit_result)
if __name__ == '__main__':
main()
# 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.
"""
PUBO main
"""
from paddle_quantum.mbqc.QAOA.pubo import mbqc_pubo, brute_force_search, is_poly_valid, random_poly
def main():
r"""PUBO 主函数。
"""
# Randomly generate an objective function
var_num = 4
polynomial = random_poly(var_num)
# A standard form of input polynomial x_1 + x_2 - x_3 + x_1 * x_2 - x_1 * x_2 * x_3
# should be {'x_1': 1, 'x_2':1, 'x_3':-1, 'x_1,x_2': 1, 'x_1,x_2,x_3': -1}
# var_num = 3
# poly_dict = {'x_1': 1, 'x_2':1, 'x_3':-1, 'x_1,x_2': 1, 'x_1,x_2,x_3': -1}
# polynomial = [n, func_dict]
print("The input polynomial is: ", polynomial)
is_poly_valid(polynomial)
# Do the training and obtain the result
mbqc_result = mbqc_pubo(
OBJ_POLY=polynomial, # Objective Function
DEPTH=6, # QAOA Depth
SEED=1024, # Plant Seed
LR=0.1, # Learning Rate
ITR=120, # Training Iters
EPOCH=1 # Epoch Times
)
print("Optimal solution by MBQC: " + str(mbqc_result[0]))
print("Optimal value by MBQC: " + str(mbqc_result[1]))
# Compute the optimal result by a brute-force method and print the result
brute_result = brute_force_search(polynomial)
print("Optimal solution by brute force search: " + str(brute_result[0]))
print("Optimal value by brute force search: " + str(brute_result[1]))
print("Difference of optimal values from MBQC and brute force search: " +
str(mbqc_result[1] - brute_result[1]))
if __name__ == '__main__':
main()
# 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.
"""
Use MBQC-QAOA algorithm to solve MaxCut problem and compare the solution with the circuit model
"""
from time import perf_counter
from sympy import symbols
from networkx import draw, Graph, circular_layout
import matplotlib.pyplot as plt
from paddle import seed, optimizer
from paddle_quantum.mbqc.QAOA.qaoa import get_solution_string, MBQC_QAOA_Net, Circuit_QAOA_Net
__all__ = [
"is_graph_valid",
"plot_graph",
"graph_to_poly",
"plot_solution",
"mbqc_maxcut"
]
def is_graph_valid(graph):
r"""检查输入的图是否符合规范。
为了规范输入标准,我们约定输入的图没有孤立节点,即每个节点至少有边与之相连。图的节点编号为连续的自然数 ``"[1, ..., n]"``。
Args:
graph (list): 输入的图 ``"G = [V, E]"``,其中,``"V"`` 为节点集合,``"E"`` 为边集合
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.QAOA.maxcut import is_graph_valid
V = [1,2,3,4]
E = [(1,2), (2, 3)]
G = [V, E]
is_graph_valid(G)
::
ValueError: input graph is not valid!
The input graph is not valid: there is at least one isolated vertex.
"""
# Obtain flat list
V = graph[0]
E = graph[1]
flat_list = []
for edge in E:
flat_list += list(edge)
# Check if there are isolated vertices
if len(set(V).difference(flat_list)) != 0:
print("The input graph is not valid: there is at least one isolated vertex.")
raise ValueError("input graph is not valid!")
else:
print("The input graph is valid.")
def plot_graph(graph, title, pos=None, node_color=None, style=None):
r"""定义画图函数。
Args:
graph (list): 输入的图 ``G = [V, E]``,其中,``V`` 为节点集合,``E`` 为边集合
title (str): 画图对应的标题
pos (dict): 图中各个节点的坐标,以字典的形式输入,例如:{'G': (0, 1)}
node_color (list): 节点的颜色,与节点的标签的顺序一一对应,例如:['blue', 'red'] 对应节点 ['G1', 'G2']
style (list): 边的样式,与边的标签的顺序一一对应,例如:['solid', 'solid'] 对应边[('G1', 'G2'), ('G3', 'G4')]
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.QAOA.maxcut import plot_graph
V = ['0', '1', '2', '3']
E = [('0', '1'), ('1', '2'), ('2', '3'), ('3', '0')]
G = [V, E]
node_color = ['blue', 'yellow', 'red', 'black']
style = ['-', '--', ':', 'solid']
pos = {'0': (0, 1),'1': (1, 1), '2': (1, 0), '3': (0, 0)}
title = 'A demo of "plot_graph"'
plot_graph(G, title, pos, node_color, style)
"""
# Obtain graph
V = graph[0]
E = graph[1]
qubit_num = len(V)
G = Graph()
G.add_nodes_from(V)
G.add_edges_from(E)
# Open the plot figure
plt.figure()
plt.ion()
plt.cla()
plt.title(title)
# Set parameters for nodes
pos = circular_layout(G) if pos is None else pos
node_color = ["blue" for _ in list(range(qubit_num))] if node_color is None else node_color
style = ["solid" for (_, _) in E] if style is None else style
options = {
"with_labels": True,
"font_size": 20,
"font_weight": "bold",
"font_color": "white",
"node_size": 2000,
"width": 2
}
# Draw the graph
draw(G=G, pos=pos, node_color=node_color, style=style, **options)
ax = plt.gca()
ax.margins(0.20)
plt.axis("off")
plt.pause(1)
# plt.ioff()
# plt.show()
def graph_to_poly(graph):
r"""将图转化为对应 PUBO 问题的目标多项式。
为了代码规范,我们要求输入的图的节点编号为 ``"[1,...,n]"``。
Args:
graph (list): 用户输入的图,图的形式为 ``"[V, E]"``, ``"V" `` 为点集合, ``"E"`` 为边集合
Returns:
list: 列表第一个元素为多项式的变量个数,第二个元素为符号化的目标多项式
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.QAOA.maxcut import graph_to_poly
graph = [[1, 2, 3, 4], [(1, 2), (2, 3), (3, 4), (4, 1)]]
polynomial = graph_to_poly(graph)
print("Corresponding objective polynomial of the graph is: \r\n", polynomial)
::
Corresponding objective polynomial of the graph is:
[4, -2*x_1*x_2 - 2*x_1*x_4 + 2*x_1 - 2*x_2*x_3 + 2*x_2 - 2*x_3*x_4 + 2*x_3 + 2*x_4]
"""
# Get the vertices and edges
V = graph[0]
E = graph[1]
qubit_num = len(V)
# Set symbol variables
vars_x = {i: symbols('x_' + str(i)) for i in range(1, qubit_num + 1)}
# Get the objective polynomial
poly_symbol = 0
for edge in E:
poly_symbol += vars_x[edge[0]] + vars_x[edge[1]] - 2 * vars_x[edge[0]] * vars_x[edge[1]]
# Set the polynomial
polynomial = [qubit_num, poly_symbol]
return polynomial
def plot_solution(graph, string):
r"""画出对应最大割问题的解。
Args:
graph (list): 用户输入的图,图的形式为 ``"[V, E]"``, ``"V"`` 为点集合, ``"E"`` 为边集合
string (str): 得到的最终解对应的比特串,列如:``"010101"`` ...
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.QAOA.maxcut import plot_solution
V = [0, 1, 2, 3]
E = [(0, 1), (1, 2), (2, 3), (3, 0)]
G = [V, E]
plot_solution(G, '1010')
"""
# Plot the figure of bitstring using matplotlib and networkx
V = graph[0]
E = graph[1]
n = len(V)
title = "MaxCut solution"
node_cut = ["blue" if string[v - 1] == "1" else "red" for v in list(range(1, n + 1))]
edge_cut = ["solid" if string[u - 1] == string[v - 1] else "dashed" for (u, v) in E]
plot_graph(graph=graph, title=title, node_color=node_cut, style=edge_cut)
def mbqc_maxcut(GRAPH, DEPTH, SEED, LR, ITR, EPOCH, SHOTS=1024):
r"""定义 MBQC 模型下的 MaxCut 主函数。
Args:
GRAPH (list): 输入的图 ``G = [V, E]``,其中,``V`` 为节点集合,``E`` 为边集合
DEPTH (int): QAOA 算法的深度
SEED (int): paddle 的训练种子
LR (float): 学习率
ITR (int): 单次轮回的迭代次数
EPOCH (int): 轮回数
SHOTS (int): 获得最终比特串时设定的测量次数
Returns:
list: 列表第一个元素为求得的最优解,第二个元素为对应的目标函数的值
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.QAOA.maxcut import mbqc_maxcut
V = [1, 2, 3, 4]
E = [(1, 2), (2, 3), (3, 4), (4, 1)]
GRAPH = [V, E]
mbqc_opt = mbqc_maxcut(GRAPH=GRAPH, P=2, SEED=1024, LR=0.1, ITR=120, EPOCH=1, shots=1024)
print("Optimal solution from MBQC: ", mbqc_opt[0])
print("Optimal value from MBQC", mbqc_opt[1])
::
Corresponding polynomial is:
-2*x_1*x_2 - 2*x_1*x_4 + 2*x_1 - 2*x_2*x_3 + 2*x_2 - 2*x_3*x_4 + 2*x_3 + 2*x_4
iter: 10 loss_MBQC: -3.3919
iter: 20 loss_MBQC: -3.8858
iter: 30 loss_MBQC: -3.9810
iter: 40 loss_MBQC: -3.9582
iter: 50 loss_MBQC: -3.9967
iter: 60 loss_MBQC: -3.9972
iter: 70 loss_MBQC: -3.9999
iter: 80 loss_MBQC: -3.9997
iter: 90 loss_MBQC: -3.9999
iter: 100 loss_MBQC: -4.0000
iter: 110 loss_MBQC: -4.0000
iter: 120 loss_MBQC: -4.0000
训练得到的最优参数 gamma: [1.57244132 0.78389509]
训练得到的最优参数 beta: [5.105461 0.78446647]
MBQC 模型下训练用时为: 8.373932838439941
MBQC 模型得到的最优解为: 1010
MBQC 模型得到的最优值为: 4
"""
plot_graph(graph=GRAPH, title="Graph to be cut")
# Obtain the objective polynomial
polynomial = graph_to_poly(GRAPH)
print("Corresponding polynomial is:\r\n", polynomial[1])
start_time = perf_counter()
seed(SEED)
mbqc_net = MBQC_QAOA_Net(DEPTH)
# Choose Adams optimizer (or SGD optimizer)
opt = optimizer.Adam(learning_rate=LR, parameters=mbqc_net.parameters())
# opt = optimizer.SGD(learning_rate = LR, parameters = mbqc_net.parameters())
# Start training
for epoch in range(EPOCH):
for itr in range(1, ITR + 1):
loss, state_out = mbqc_net(poly=polynomial)
loss.backward()
opt.minimize(loss)
opt.clear_grad()
if itr % 10 == 0:
print("iter:", itr, " loss_MBQC:", "%.4f" % loss.numpy())
print("Optimal parameter gamma: ", mbqc_net.gamma.numpy())
print("Optimal parameter beta: ", mbqc_net.beta.numpy())
end_time = perf_counter()
print("MBQC running time: ", end_time - start_time)
# Decode the MaxCut solution from the final state
mbqc_solution = get_solution_string(state_out, SHOTS)
plot_solution(GRAPH, mbqc_solution)
# Evaluate the number of cuts
var_num, poly_symbol = polynomial
relation = {symbols('x_' + str(j + 1)): int(mbqc_solution[j]) for j in range(var_num)}
mbqc_value = int(poly_symbol.evalf(subs=relation))
mbqc_opt = [mbqc_solution, mbqc_value]
return mbqc_opt
def circuit_maxcut(SEED, GRAPH, DEPTH, LR, ITR, EPOCH, SHOTS):
r"""定义电路模型下的 MaxCut 主函数。
Args:
SEED (int): paddle 的训练种子
GRAPH (list): 输入的图 ``G = [V, E]``,其中,``V`` 为节点集合,``E`` 为边集合
DEPTH (int): QAOA 算法的深度
LR (float): 学习率
ITR (int): 单次轮回的迭代次数
EPOCH (int): 轮回数
SHOTS (int): 获得最终比特串时设定的测量次数
Returns:
str: 最大割问题最优解对应的比特串
"""
plot_graph(graph=GRAPH, title="Graph to be cut")
start_time_PQ = perf_counter()
# Set cost Hamiltonian
H_D_list = [[-0.5, 'z' + str(u - 1) + ',z' + str(v - 1)] for (u, v) in GRAPH[1]]
# Initialize
seed(SEED)
pq_net = Circuit_QAOA_Net(DEPTH)
opt = optimizer.Adam(learning_rate=LR, parameters=pq_net.parameters())
for epoch in range(EPOCH):
for itr in range(1, ITR + 1):
loss, cir = pq_net(GRAPH, H_D_list)
loss.backward()
opt.minimize(loss)
opt.clear_grad()
if itr % 10 == 0:
print("iter:", itr, " loss_cir:", "%.4f" % loss.numpy())
print("Optimal parameter gamma: ", pq_net.gamma.numpy())
print("Optimal parameter beta: ", pq_net.beta.numpy())
end_time_PQ = perf_counter()
print("Circuit running time: ", end_time_PQ - start_time_PQ)
# Obtain the bit string
prob_measure = cir.measure(shots=SHOTS, plot=False)
cut_bitstring = max(prob_measure, key=prob_measure.get)
plot_solution(GRAPH, cut_bitstring)
return cut_bitstring
# 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.
"""
Use MBQC-QAOA algorithm to solve PUBO problem and compare with the solution by brute-force search
"""
from numpy import random
from time import perf_counter
from sympy import symbols
from copy import deepcopy
from paddle import seed, optimizer
from paddle_quantum.mbqc.QAOA.qaoa import get_solution_string, MBQC_QAOA_Net
__all__ = [
"random_poly",
"is_poly_valid",
"dict_to_symbol",
"brute_force_search",
"mbqc_pubo"
]
def random_poly(var_num):
r"""随机生成一个多项式函数。
Args:
var_num (int): 多项式变量个数
Returns:
list: 列表的第一个元素为变量个数,第二个元素为各单项式构成的字典
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.QAOA.pubo import random_poly
polynomial = random_poly(3)
print("The random polynomial is: \n", polynomial)
::
The random polynomial is:
[3, {'cons': 0.8634473818565477, 'x_3': 0.10521510553668978, 'x_2': 0.7519767805645575,
'x_2,x_3': -0.20826424036741054, 'x_1': -0.2795543099640111, 'x_1,x_3': -0.06628925930094798,
'x_1,x_2': -0.6094165475879592, 'x_1,x_2,x_3': 0.175938331955921}]
"""
# Random a constant item in the function
poly_dict = {'cons': random.rand()}
# Random the other items
for bit in range(1, 2 ** var_num):
item = bin(bit)[2:].zfill(var_num)
var_str = []
for j in range(1, var_num + 1):
if int(item[j - 1]) == 1:
var_str.append('x_' + str(j))
var_str = ",".join(var_str)
poly_dict[var_str] = random.rand() - random.rand()
poly = [var_num, poly_dict]
# Return polynomial
return poly
def is_poly_valid(poly):
r"""检查用户输入的多项式是否符合规范。
Args:
poly (list): 列表第一个元素为变量个数,第二个元素为用户输入的字典类型的多项式
Note:
为了代码规范,我们要求用户输入的多项式满足下列要求:1. 单项式中每个变量指数最多为一,2. 多项式变量个数与用户输入的变量个数一致,
3.变量为连续编号的 ``x_1,...,x_n``,另外,常熟项的键为 `cons`,多项式字典的键值不能重复,否则后面的条目会覆盖之前的条目。
Returns:
list: 列表的第一个元素为变量个数,第二个元素为各单项式构成的字典
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.QAOA.pubo import random_poly, is_poly_valid
polynomial = random_poly(3)
print("The random polynomial is: \n", polynomial)
is_poly_valid(polynomial)
::
The random polynomial is:
[3, {'cons': 0.3166178352704635, 'x_3': -0.30850205546468723, 'x_2': 0.1938147859698406,
'x_2,x_3': 0.5722646368431439, 'x_1': 0.03709620256724111, 'x_1,x_3': 0.3273727561299321,
'x_1,x_2': -0.4544731299546062, 'x_1,x_2,x_3': -0.1406736501192053}]
The polynomial is valid.
"""
# Check the validity of the input polynomial
# Take a copy of the polynomial,
# as we do not want to change the polynomial by the latter pop action of the 'cons' term
poly_copy = deepcopy(poly)
var_num = poly_copy[0]
poly_dict = poly_copy[1]
# Remove the cons term first and then check the variables
# If there is no 'cons' term, it will do nothing
poly_dict.pop('cons', None)
# Obtain the dict keys
keys_list = list(poly_dict.keys())
# Extract all the input variables
input_vars_list = []
for key in keys_list:
key_sep = key.split(',')
# We do not allow input like {'x_1,x_1': 2}
if len(list(set(key_sep))) != len(key_sep):
print("The input polynomial contains at least one not valid monomial:" + str(key) +
". Each key of input polynomial dictionary should only consist of different variables.")
raise KeyError(key)
input_vars_list += key_sep
# Check the number of input variables
input_vars_set = list(set(input_vars_list))
if len(input_vars_set) != var_num:
input_vars_set.sort()
print("The polynomial variables are: " + str(input_vars_set) +
", but the expected number of input variables is: " + str(var_num) + ".")
raise ValueError("the number of input variables ", var_num, " is not correct.")
# Check the subscript of the variables
std_vars_list = ['x_' + str(i) for i in range(1, var_num + 1)]
input_diff_std = list(set(input_vars_set).difference(std_vars_list))
if len(input_diff_std) != 0:
input_vars_set.sort()
print("The polynomial variables are: " + str(input_vars_set) +
", but the expected variables are: " + str(std_vars_list) + ".")
raise ValueError("the subscript of input variable does not range from 1 to " + str(var_num) + ".")
# If the input polynomial is a valid one
print("The polynomial is valid.")
def dict_to_symbol(poly):
r"""将用户输入的多项式字典处理成符号多项式。
用户输入为以 ``x_i`` 为自变量的目标函数,第一步需要对目标函数进行处理,处理成 sympy 所接受的符号多项式,便于后续的变量替换和计算。
Args:
poly (list): 列表第一个元素为变量个数,第二个元素为用户输入的字典类型的多项式
Returns:
list: 列表第一个元素为变量个数,第二个元素为符号化的多项式
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.QAOA.pubo import dict_to_symbol
var_num = 4
poly_dict = {"cons": 0.5, 'x_1': 2, 'x_1,x_2': -2, 'x_2,x_3': -2, 'x_3,x_4': -2, 'x_4,x_1': -2}
polynomial = [var_num, poly_dict]
new_poly = dict_to_symbol(polynomial)
print("The symbolized polynomial is: \n", new_poly)
::
The symbolized polynomial is: [4, -2*x_1*x_2 - 2*x_1*x_4 + 2*x_1 - 2*x_2*x_3 - 2*x_3*x_4 + 0.5]
"""
var_num, poly_dict = poly
# Transform the dict to a symbolized function
poly_symbol = 0
for key in poly_dict:
value = poly_dict[key]
if key == 'cons':
poly_symbol += value
else:
key_sep = key.split(',')
sym_var = 1
for var in key_sep:
sym_var *= symbols(var)
poly_symbol += value * sym_var
new_poly = [var_num, poly_symbol]
return new_poly
def brute_force_search(poly):
r"""用遍历的算法在解空间里暴力搜索 PUBO 问题的解,作为标准答案和其他算法的结果作比较。
Args:
poly (list): 列表第一个元素为变量个数,第二个元素为单项式构成的字典
Returns:
list: 列表第一个元素为 PUBO 问题的解,第二个元素为对应的目标函数的值
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.QAOA.pubo import brute_force_search
n = 4
func_dict = {"cons":0.5, 'x_1': 2, 'x_1,x_2': -2, 'x_2,x_3': -2, 'x_3,x_4': -2, 'x_4,x_1':-2}
polynomial = [n, func_dict]
opt = brute_force_search(polynomial)
print("The optimal solution by brute force search is: ", opt[0])
print("The optimal value by brute force search is: ", opt[1])
::
The optimal solution by brute force search is: 1000
The optimal value by brute force search is: 2.50000000000000
"""
# Transform the dict of objective function to a symbolic function
var_num, poly_symbol = dict_to_symbol(poly)
feasible_values = []
feasible_set = []
# Scan the solution space
for bit in range(2 ** var_num):
feasible_solution = bin(bit)[2:].zfill(var_num)
feasible_set += [feasible_solution]
relation = {symbols('x_' + str(j + 1)): int(feasible_solution[j]) for j in range(var_num)}
feasible_values += [poly_symbol.evalf(subs=relation)]
opt_value = max(feasible_values)
opt_solution = feasible_set[feasible_values.index(opt_value)]
opt = [opt_solution, opt_value]
return opt
def mbqc_pubo(OBJ_POLY, DEPTH, SEED, LR, ITR, EPOCH, SHOTS=1024):
r"""定义 MBQC 模型下的 PUBO 主函数。
选择 Adams 优化器,梯度下降算法最小化目标函数。
Args:
OBJ_POLY (list): 输入为以 x 为自变量的目标函数,列表第一个元素为变量个数,第二个元素为单项式构成的字典
DEPTH (int): QAOA 算法的深度
SEED (int): paddle 的训练种子
LR (float): 学习率
ITR (int): 单次轮回的迭代次数
EPOCH (int): 轮回数
SHOTS (int): 获得最终比特串时设定的测量次数
Returns:
list: 列表第一个元素为求得的最优解,第二个元素为对应的目标函数的值
代码示例:
.. code-block:: python
from pubo import mbqc_pubo
n = 4
poly_dict = {'x_1': 2, 'x_2': 2, 'x_3': 2, 'x_4': 2, 'x_1,x_2': -2, 'x_2,x_3': -2, 'x_3,x_4': -2, 'x_4,x_1': -2}
polynomial = [n, poly_dict]
mbqc_opt = mbqc_pubo(OBJ_POLY=polynomial, DEPTH=4, SEED=1024, LR=0.1, ITR=120, EPOCH=1, shots=1024)
print("The optimal solution by MBQC is: ", mbqc_opt[0])
print("The optimal value by MBQC is: ", mbqc_opt[1])
::
QAOA Ansatz depth is: 4
iter: 10 loss_MBQC: -3.8231
iter: 20 loss_MBQC: -3.9038
iter: 30 loss_MBQC: -3.9840
iter: 40 loss_MBQC: -3.9970
iter: 50 loss_MBQC: -3.9990
iter: 60 loss_MBQC: -3.9993
iter: 70 loss_MBQC: -3.9997
iter: 80 loss_MBQC: -3.9999
iter: 90 loss_MBQC: -4.0000
iter: 100 loss_MBQC: -4.0000
iter: 110 loss_MBQC: -4.0000
iter: 120 loss_MBQC: -4.0000
MBQC running time is: 16.864049434661865
Optimal parameter gamma: [3.15639021 0.23177807 4.99173672 0.69199477]
Optimal parameter beta: [0.13486116 2.22551912 5.10371187 2.4004731 ]
The optimal solution by MBQC is: 0101
The optimal value by MBQC is: 4.00000000000000
"""
obj_poly = dict_to_symbol(OBJ_POLY)
var_num, poly_symbol = obj_poly
# Initialize
print("QAOA Ansatz depth is:", DEPTH)
# Initialize MBQC - PUBO optimization net
start_time = perf_counter()
seed(SEED)
mbqc_net = MBQC_QAOA_Net(DEPTH)
# Choose Adams optimizer (or SGD optimizer)
opt = optimizer.Adam(learning_rate=LR, parameters=mbqc_net.parameters())
# opt = optimizer.SGD(learning_rate = LR, parameters = mbqc_net.parameters())
# Start training
for epoch in range(EPOCH):
# Update parameters for each iter
for itr in range(1, ITR + 1):
# Train with mbqc_net and return the loss
loss, state_out = mbqc_net(poly=obj_poly)
# Propagate loss backwards and optimize the parameters
loss.backward()
opt.minimize(loss)
opt.clear_grad()
if itr % 10 == 0:
print("iter:", itr, " loss_MBQC:", "%.4f" % loss.numpy())
end_time = perf_counter()
print("MBQC running time is: ", end_time - start_time)
# Print the optimization parameters
print("Optimal parameter gamma: ", mbqc_net.gamma.numpy())
print("Optimal parameter beta: ", mbqc_net.beta.numpy())
solu_str = get_solution_string(state_out, SHOTS)
# Evaluate the corresponding value
relation = {symbols('x_' + str(j + 1)): int(solu_str[j]) for j in range(var_num)}
value = poly_symbol.evalf(subs=relation)
# Return the solution and its corresponding value
opt = [solu_str, value]
return opt
# 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.
"""
Perform the QAOA algorithm under MBQC model and Circuit model
"""
from collections import Counter
from sympy import symbols, expand
from numpy import pi, random, log2
from paddle import to_tensor, real, abs, zeros, t, conj, matmul, multiply
from paddle import nn
from paddle_quantum.mbqc.utils import kron, basis, permute_systems
from paddle_quantum.mbqc.simulator import MBQC
from paddle_quantum.circuit import UAnsatz
__all__ = [
"get_all_indices",
"var_substitute",
"symbol_to_list",
"preprocess",
"adaptive_angle",
"byproduct_power",
"get_cost_hamiltonian",
"expecval",
"get_solution_string",
"mbqc_qaoa",
"MBQC_QAOA_Net",
"circuit_qaoa",
"Circuit_QAOA_Net"
]
def get_all_indices(lst, item):
r"""查找一个列表中对应元素的全部索引。
Args:
lst (list): 输入需要查询的列表
item (any type): 列表中被索引的某个元素,类型与输入的列表中元素类型对应
Returns:
list: 列表中对应元素的全部索引构成的列表
代码示例:
.. code-block:: python
from mbqc_qaoa import get_all_indices
print(get_all_indices(['1', 0, (2,3), '4', 0, '4'], '4'))
::
[3, 5]
"""
return [idx for (idx, val) in enumerate(lst) if val == item]
def var_substitute(poly_x):
r"""多项式变量替换。
输入为以 x 为自变量的目标函数,输出为以 z 为自变量的目标函数。变换关系为 ``x = (1 - z) / 2``。
Args:
poly_x (list): 输入为以 ``x`` 为自变量的符号化的目标函数,列表第一个元素为变量个数,第二个元素为符号化的多项式
Returns:
list: 输出为以 ``z`` 为自变量的目标函数,列表第一个元素为变量个数,第二个元素为符号化的多项式
代码示例:
.. code-block:: python
from mbqc_qaoa import var_substitute
from sympy import symbols
x_1, x_2, x_3, x_4 = symbols(['x_1', 'x_2', 'x_3', 'x_4'])
poly_x_symbol = x_1 * x_2 + x_3 * x_4
poly_x = [4, poly_x_symbol]
print("The original polynomial is: ", poly_x)
poly_z = var_substitute(poly_x)
print("The polynomial after variable substitution is: ", poly_z)
::
The original polynomial is: [4, x_1*x_2 + x_3*x_4]
The polynomial after variable substitution is: [4, z_1*z_2/4 - z_1/4 - z_2/4 + z_3*z_4/4 - z_3/4 - z_4/4 + 1/2]
"""
# Do variable replacement x = (1 - z) / 2
var_num, poly_x_symbol = poly_x
# Set the relations between two variables
var_relation = {symbols('x_' + str(i)): (1 - symbols('z_' + str(i))) / 2 for i in range(1, var_num + 1)}
# Obtain polynomial with z symbol
poly_z_symbol = expand(poly_x_symbol.subs(var_relation))
# Obtain new polynomial
poly_z = [var_num, poly_z_symbol]
return poly_z
def symbol_to_list(poly):
r"""将符号多项式转化为列表形式。
Args:
poly (list): 列表第一个元素为变量个数,第二个元素为符号化的多项式
Returns:
list: 列表第一个元素为变量个数,第二个元素为多项式各个单项式构成的列表
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.QAOA.mbqc_qaoa import symbol_to_list
from sympy import symbols
z_1, z_2, z_3 = symbols(['z_1', 'z_2', 'z_3'])
poly_as_symbols = - z_1 * z_2 + 2 * z_3
poly = [3, poly_as_symbols]
print("The symbolized polynomial is: ", poly)
new_poly = symbol_to_list(poly)
print("Polynomial in the list form: ", new_poly)
::
The symbolized polynomial is: [3, -z_1*z_2 + 2*z_3]
Polynomial in the list form: [3, [[(0, 0, 1), 2.0], [(1, 1, 0), -1.0]]]
"""
var_num, poly_as_symbols = poly
poly_as_terms = poly_as_symbols.as_terms()
monos = poly_as_terms[0]
# Double check if the number of variables is correct or not
# In case there is variable cancellation during the variable substitution
if len(poly_as_terms[1]) != var_num:
print("The variables are: " + str(poly_as_terms[1]) + ", whose total number is not: " + str(var_num) + ".")
raise ValueError("the number of input variables is not correct.")
# Transform a symbol function to a list form
poly_as_lists = []
for mono in monos:
poly_as_lists.append([mono[1][1], mono[1][0][0]])
new_poly = [var_num, poly_as_lists]
# Return new polynomial as lists
return new_poly
def preprocess(poly_x, depth):
r"""对多项式进行预处理,提取出多项式变量个数、常数项、线性项、非线性项和 QAOA 图。
MBQC 模型下的 QAOA 算法依赖于相应的 QAOA 图,该图共有四类节点,我们默认将图中每个节点的标签记为 ``(color, v, k)``,
其中,color 类型为 (str), 取值为 'R', 'G', 'B' 或 'H';v 的类型为 (int), 表示该节点在图中的纵向位置;
k 的类型为 (int),表示当前节点所在的 QAOA 算法的层数。
Args:
poly_x (list): 用户输入的多项式,列表第一个元素为变量个数,第二个元素为符号化的多项式
depth (int): QAOA 算法的电路深度
Returns:
list: 经过变量替换和单项式分类的多项式列表,列表的元素分别为多项式变量个数、常数项、线性项、非线性项
list: 根据给定的多项式构造出的 QAOA 图,由节点和边构成的列表
代码示例:
.. code-block:: python
from sympy import symbols
from paddle_quantum.mbqc.QAOA.qaoa import preprocess
x_1, x_2 = symbols(['x_1', 'x_2'])
poly_as_symbols = x_1 * x_2
poly_x = [2, poly_as_symbols]
poly_processed, qaoa_graph = preprocess(poly_x, 1)
print("Polynomial after variable substitution and classification: \n", poly_processed)
print("Corresponding QAOA graph is: \n", qaoa_graph)
::
Polynomial after variable substitution and classification:
[2, 0.25, {1: -0.25, 2: -0.25}, [[(1, 2), 0.25]]]
Corresponding QAOA graph is:
[[('R', (1, 2), 1), ('G', 1, 1), ('G', 2, 1), ('B', 1, 1), ('B', 2, 1), ('H', 1, 1), ('H', 2, 1)],
[(('R', (1, 2), 1), ('G', 1, 1)), (('R', (1, 2), 1), ('G', 2, 1)), (('G', 1, 1), ('B', 1, 1)),
(('G', 2, 1), ('B', 2, 1)), (('B', 1, 1), ('H', 1, 1)), (('B', 2, 1), ('H', 2, 1))]]
"""
# Variable substitute
poly_z = var_substitute(poly_x)
# Transform polynomial from symbols to lists
var_num, poly_as_lists = symbol_to_list(poly_z)
# cons_item is a number, linear_items is a dict, non_linear_items is a list
cons_item = 0
linear_items = {i: 0 for i in range(1, var_num + 1)}
non_linear_items = []
for mono in poly_as_lists:
mono_idx = get_all_indices(lst=mono[0], item=1)
mono_idx = [ele + 1 for ele in mono_idx]
if len(mono_idx) == 0:
cons_item += mono[1]
elif len(mono_idx) == 1:
linear_items[mono_idx[0]] = mono[1]
else:
non_linear_items.append([tuple(mono_idx), mono[1]])
poly_classified = [var_num, cons_item, linear_items, non_linear_items]
# Generate the vertices of the QAOA graph
graph_v_G = [('G', i, j) for j in range(1, depth + 1) for i in range(1, var_num + 1)]
graph_v_R = [('R', item[0], j) for j in range(1, depth + 1) for item in non_linear_items]
graph_v_B = [('B', i, j) for j in range(1, depth + 1) for i in range(1, var_num + 1)]
graph_v_H = [('H', i, depth) for i in range(1, var_num + 1)]
graph_v = graph_v_R + graph_v_G + graph_v_B + graph_v_H
# Generate the edges of the QAOA graph
graph_e_RG = [(v_R, ('G', v, v_R[-1])) for v_R in graph_v_R for v in v_R[1]]
graph_e_GB = [(('G', i, j), ('B', i, j)) for j in range(1, depth + 1) for i in range(1, var_num + 1)]
graph_e_BG = [(('B', i, j), ('G', i, j + 1)) for j in range(1, depth) for i in range(1, var_num + 1)]
graph_e_BH = [(('B', i, depth), ('H', i, depth)) for i in range(1, var_num + 1)]
graph_e = graph_e_RG + graph_e_GB + graph_e_BG + graph_e_BH
qaoa_graph = [graph_v, graph_e]
return poly_classified, qaoa_graph
def adaptive_angle(which_qubit, graph, outcome, theta, eta):
r"""定义 QAOA 算法的测量角度。
图中共有四类节点:红色节点、绿色节点、蓝色节点和输出节点,在 QAOA 算法中,我们需要依次对红色节点、绿色节点、蓝色节点进行测量,
由于 MBQC 模型适应性测量的特点,本次测量的角度依赖于前面某些节点测量的结果(除第一层外),本次测量的结果也会对后续某些节点的测量产生影响。
因此,单独定义 QAOA 的测量角度函数,测量时只需调用该函数即可求出对应的测量角度。
Args:
which_qubit (tuple): 当前测量节点的标签,形如 ``"(color, v, k)"``
graph (networkx.classes.graph.Graph): QAOA 算法对应的 QAOA 图
outcome (dict): 测量结果字典,字典中记录对各个节点的测量结果信息 ``"{qubit label: 0/1}"``
theta (Tensor): 不考虑副产品影响的角度参数
eta (Tensor): 变量替换后的多项式系数
Returns:
Tensor: 考虑了副产品影响的测量角度
"""
# Check the Color
color_label = which_qubit[0]
assert color_label == 'R' or color_label == 'G' or color_label == 'B', \
"KeyError: the qubit color index is WRONG, it must be 'R', 'G', or 'B'."
# Current evolution level
level = which_qubit[-1]
outcome_sum = 0
# Red vertices
if color_label == 'R':
for k in range(1, level):
idx = which_qubit[1]
for v in idx:
outcome_sum += outcome[('B', v, k)]
angle = theta.multiply(to_tensor((((-1) ** (outcome_sum + 1)) * (2 * eta)), dtype='float64'))
return angle
# Green vertices
elif color_label == 'G':
v = which_qubit[1]
for k in range(1, level):
outcome_sum += outcome[('B', v, k)]
angle = theta.multiply(to_tensor((((-1) ** (outcome_sum + 1)) * (2 * eta)), dtype='float64'))
return angle
# Blue vertices
elif color_label == 'B':
v = which_qubit[1]
for k in range(1, level + 1):
v_r_lst = list(set(list(graph.neighbors(('G', v, k)))).difference([('B', v, k)]))
if k > 1:
v_r_lst = list(set(v_r_lst).difference([('B', v, k - 1)]))
for v_r in v_r_lst:
outcome_sum += outcome[v_r]
outcome_sum += outcome[('G', v, k)]
angle = theta.multiply(to_tensor((((-1) ** (outcome_sum + 1)) * 2), dtype='float64'))
return angle
def byproduct_power(gate, v, graph, outcome, depth):
r"""MBQC 模型下 QAOA 算法最后纠正副产品的指数。
Args:
gate (str): 需要纠正的副产品的类型,输入为:`X` 或者 `Z`
v (int): 需要纠正副产品的比特位置
graph (networkx.classes.graph.Graph): QAOA 算法对应的 QAOA 图
outcome (dict): 测量结果字典,字典中记录对各个节点的测量结果信息 ``"{qubit label: 0/1}"``
depth (int): QAOA 算法深度
Returns:
int: 需要纠正的副产品的指数
"""
# gate = 'X' or 'Z', which_qubit = v, outcome
if gate == 'X':
power = 0
for k in range(1, depth + 1):
power += outcome[('B', v, k)]
elif gate == 'Z':
power = 0
for k in range(1, depth + 1):
v_r_lst = list(set(list(graph.neighbors(('G', v, k)))).difference([('B', v, k)]))
if k > 1:
v_r_lst = list(set(v_r_lst).difference([('B', v, k - 1)]))
for v_r in v_r_lst:
power += outcome[v_r]
power += outcome[('G', v, k)]
else:
print("The input parameter 'gate' should be either 'X' or 'Z'.")
raise KeyError(gate)
return power
def get_cost_hamiltonian(poly_x):
r"""获得系统哈密顿量。
输入为以 ``"x"`` 为自变量的目标函数,构造并返回系统哈密顿量。
Args:
poly_x (list): 列表第一个元素为变量个数,第二个元素为符号化的多项式
Returns:
Tensor: 输出系统哈密顿量,注意,由于 PUBO 问题中的系统哈密顿量为对角阵,所以直接用列向量进行存储
代码示例:
.. code-block:: python
from sympy import symbols
from paddle_quantum.mbqc.QAOA.mbqc_qaoa import get_cost_hamiltonian
x_1, x_2, x_3, x_4 = symbols(['x_1', 'x_2', 'x_3', 'x_4'])
poly_x_symbol = x_1 * x_2 + x_3 * x_4
poly_x = [4, poly_x_symbol]
HC = get_cost_hamiltonian(poly_x)
print("Hamiltonian is: \n", HC.numpy())
::
系统的哈密顿量为:
[[0.]
[0.]
[0.]
[1.]
[0.]
[0.]
[0.]
[1.]
[0.]
[0.]
[0.]
[1.]
[1.]
[1.]
[1.]
[2.]]
"""
# Get the Hamiltonian for system,
# Note: the expected value of Hamiltonian is not the maximum value of the initial objective function
poly_z = var_substitute(poly_x)
var_num, poly_as_lists = symbol_to_list(poly_z)
Z_col = to_tensor([[1], [-1]], dtype='float64')
I_col = to_tensor([[1], [1]], dtype='float64')
# Get the Hamiltonian
HC = zeros(shape=[2 ** var_num, 1], dtype='float64')
for mono in poly_as_lists:
mono_idx = mono[0]
coeff = mono[1]
HC_lst = [I_col for _ in range(var_num)]
for i in range(len(mono_idx)):
if mono_idx[i] == 1:
HC_lst[i] = Z_col
HC += multiply(kron(HC_lst), to_tensor([coeff], dtype='float64'))
return HC
def expecval(vector, H):
r"""根据量子态和系统的哈密顿量,计算其期望值。
此处的期望函数采用矩阵乘法。
Args:
vector (Tensor): 当前量子态的列向量
H (Tensor): 哈密顿量的列向量
Warning:
MBQC 模型下的损失函数与电路模型下的损失函数在定义上有所差别,差了常数项,所以损失函数的值有所差别,
但这并不影响参数学习和梯度下降算法对参数的训练过程,最终求得的最大割都是该问题的最优解。
Returns:
Tensor: 系统哈密顿量 H 在该量子态下的期望值
"""
# state and H are both of column vectors
expec_val = matmul(t(conj(vector)), multiply(H, vector))
complex128_tensor = to_tensor([], dtype='complex128')
if expec_val.dtype == complex128_tensor.dtype:
expec_val = real(expec_val)
return expec_val
def get_solution_string(vector, shots=1024):
r"""从最后的量子态中解码原问题的答案。
对得到的态进行多次测量,得到概率分布,找到概率最大的比特串即为最终解。
Args:
vector (Tensor): 量子态的列向量
shots (int): 测量次数
Returns:
str: 输出 PUBO 问题的其中一个最优解
"""
# Calculate the probability for each string
vec_len = vector.shape[0]
vec_size = int(log2(vec_len))
prob_array = abs(vector)
prob_array = multiply(prob_array, prob_array)
prob_array = prob_array.reshape([vec_len]).numpy()
# Measure the state 1024 times in order to get the probability distribution
samples = random.choice(range(vec_len), shots, p=prob_array)
count_samples = Counter(samples)
# Transform the samples to binary strings
bit_freq = {}
for idx in count_samples:
bit_freq[bin(idx)[2:].zfill(vec_size)] = count_samples[idx]
max_str = max(bit_freq, key=bit_freq.get)
return max_str
def mbqc_qaoa(poly_x, depth, gamma, beta):
r"""MBQC 模型下的 QAOA 算法。
Args:
poly_x (list): 用户输入的多项式,列表第一个元素为变量个数,第二个元素为符号化的多项式
depth (int): 电路深度
gamma (Tensor): 待训练角度变量 gamma
beta (Tensor): 待训练角度变量 beta
Returns:
Tensor: QAOA 算法结束后输出的量子态列向量
"""
# Pre-process of the objective function
poly_classified, qaoa_graph = preprocess(poly_x, depth)
var_num, cons_item, linear_items, non_linear_items = poly_classified
# Initialize a MBQC class
mbqc = MBQC()
mbqc.set_graph(graph=qaoa_graph)
# Measure every single qubits
for i in range(1, depth + 1):
# Measure red vertices
for item in non_linear_items:
angle_r = adaptive_angle(which_qubit=('R', item[0], i),
graph=mbqc.get_graph(),
outcome=mbqc.get_classical_output(),
theta=gamma[i - 1],
eta=to_tensor(item[1], dtype='float64')
)
mbqc.measure(which_qubit=('R', item[0], i), basis_list=basis('YZ', angle_r))
# Measure green vertices
for v in range(1, var_num + 1):
angle_g = adaptive_angle(which_qubit=('G', v, i),
graph=mbqc.get_graph(),
outcome=mbqc.get_classical_output(),
theta=gamma[i - 1],
eta=linear_items[v])
mbqc.measure(which_qubit=('G', v, i), basis_list=basis('XY', angle_g))
# Measure blue vertices
for v in range(1, var_num + 1):
angle_b = adaptive_angle(which_qubit=('B', v, i),
graph=mbqc.get_graph(),
outcome=mbqc.get_classical_output(),
theta=beta[i - 1],
eta=to_tensor([1], dtype='float64'))
mbqc.measure(which_qubit=('B', v, i), basis_list=basis('XY', angle_b))
# Correct the byproduct operators
for v in range(1, var_num + 1):
graph = mbqc.get_graph()
outcome = mbqc.get_classical_output()
pow_x = byproduct_power(gate='X', v=v, graph=graph, outcome=outcome, depth=depth)
pow_z = byproduct_power(gate='Z', v=v, graph=graph, outcome=outcome, depth=depth)
mbqc.correct_byproduct(gate='X', which_qubit=('H', v, depth), power=pow_x)
mbqc.correct_byproduct(gate='Z', which_qubit=('H', v, depth), power=pow_z)
output_label = [('H', i, depth) for i in range(1, var_num + 1)]
# Permute the system order to fit output_label
state_out = mbqc.get_quantum_output()
state_out = permute_systems(state_out, output_label)
return state_out.vector
class MBQC_QAOA_Net(nn.Layer):
r"""定义 MBQC 模型下的 QAOA 优化网络,用于实例化一个 MBQC - QAOA 优化网络。
Attributes:
depth (int): QAOA 算法深度
Returns:
Tensor: 输出损失函数
Tensor: 输出量子态列向量
"""
def __init__(
self,
depth, # Depth
dtype="float64",
):
r"""定义 MBQC 模型下的 QAOA 优化网络。
Args:
depth (int): QAOA 算法深度
"""
super(MBQC_QAOA_Net, self).__init__()
self.depth = depth
# Define the training parameters
self.gamma = self.create_parameter(shape=[self.depth],
default_initializer=nn.initializer.Uniform(low=0.0, high=2 * pi),
dtype=dtype,
is_bias=False)
self.beta = self.create_parameter(shape=[self.depth],
default_initializer=nn.initializer.Uniform(low=0.0, high=2 * pi),
dtype=dtype,
is_bias=False)
def forward(self, poly):
r"""定义优化网络的前向传播机制。
Args:
poly (list): 用户输入的多项式,列表第一个元素为变量个数,第二个元素为符号化的多项式
Returns:
Tensor: 输出损失函数
Tensor: 输出量子态列向量
"""
# Initial the MBQC - QAOA algorithm and return the state out
vec_out = mbqc_qaoa(poly, self.depth, self.gamma, self.beta)
# Get cost Hamiltonian
HC = get_cost_hamiltonian(poly)
# Calculate loss
loss = - expecval(vec_out, HC)
return loss, vec_out
def circuit_qaoa(graph, depth, gamma, beta):
r"""使用 UAnsatz 电路模型实现 QAOA。
Args:
graph (list): 图 [V, E],V 是点集合,E 是边集合
depth (int): 电路深度
gamma (Tensor): 待训练角度变量 gamma
beta (Tensor): 待训练角度变量 beta
Returns:
UAnsatz: 电路模型的 UAnsatz 电路
"""
vertices = graph[0]
edges = graph[1]
qubit_number = len(vertices)
cir = UAnsatz(qubit_number)
cir.superposition_layer()
for layer in range(depth):
for (u, v) in edges:
u = u - 1
v = v - 1
cir.cnot([u, v])
cir.rz(gamma[layer], v)
cir.cnot([u, v])
for v in range(qubit_number):
cir.rx(beta[layer], v)
return cir
class Circuit_QAOA_Net(nn.Layer):
r"""定义电路模型下的 QAOA 优化网络,用于实例化一个电路模型下 QAOA 的优化网络。
Attributes:
depth (int): QAOA 算法深度
Returns:
Tensor: 输出损失函数
Tensor: 输出量子态列向量
"""
def __init__(self,
depth, # depth
dtype="float64"
):
r"""定义电路模型下的 QAOA 优化网络。
Args:
depth (int): QAOA 算法深度
"""
super(Circuit_QAOA_Net, self).__init__()
self.p = depth
self.gamma = self.create_parameter(shape=[self.p],
default_initializer=nn.initializer.Uniform(low=0.0, high=2 * pi),
dtype=dtype,
is_bias=False)
self.beta = self.create_parameter(shape=[self.p],
default_initializer=nn.initializer.Uniform(low=0.0, high=2 * pi),
dtype=dtype,
is_bias=False)
def forward(self, graph, H):
r"""定义优化网络的前向传播机制。
Args:
graph (list): 图 [V, E],V 是点集合,E 是边集合
H (list): 哈密顿量列表
Returns:
Tensor: 输出损失函数
UAnsatz: 电路模型的 UAnsatz 电路
"""
cir_gamma = multiply(self.gamma, to_tensor([-1], dtype='float64'))
cir_beta = multiply(self.beta, to_tensor([2], dtype='float64'))
cir = circuit_qaoa(graph, self.p, cir_gamma, cir_beta)
cir.run_state_vector()
loss = -cir.expecval(H)
return loss, 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.
"""
main
"""
from paddle_quantum.mbqc.QKernel.qkernel import qkernel, compare_time, compare_result
def main():
r"""Kernel 主函数。
"""
# From 3 to 24, plot time
start_width = 3
end_width = 24
qkernel(start_width, end_width)
compare_time(start_width, end_width)
# Compare the probability distribution of outcome bit strings by multiple samplings
compare_result(3, 1024)
if __name__ == '__main__':
main()
The current example is: Quantum kernel method with 3 X 9 size.
The qubit number is: 3
MBQC running time is: 0.19433109999999987 s
Circuit model running time is: 0.1034025999999999 s
The current example is: Quantum kernel method with 4 X 9 size.
The qubit number is: 4
MBQC running time is: 0.28153490000000003 s
Circuit model running time is: 0.10754960000000002 s
The current example is: Quantum kernel method with 5 X 9 size.
The qubit number is: 5
MBQC running time is: 0.38240870000000005 s
Circuit model running time is: 0.12117860000000036 s
The current example is: Quantum kernel method with 6 X 9 size.
The qubit number is: 6
MBQC running time is: 0.49848729999999986 s
Circuit model running time is: 0.12079530000000016 s
The current example is: Quantum kernel method with 7 X 9 size.
The qubit number is: 7
MBQC running time is: 0.6529012000000001 s
Circuit model running time is: 0.1332924999999996 s
The current example is: Quantum kernel method with 8 X 9 size.
The qubit number is: 8
MBQC running time is: 0.7972115000000004 s
Circuit model running time is: 0.14146310000000017 s
The current example is: Quantum kernel method with 9 X 9 size.
The qubit number is: 9
MBQC running time is: 0.9908808999999996 s
Circuit model running time is: 0.15193749999999984 s
The current example is: Quantum kernel method with 10 X 9 size.
The qubit number is: 10
MBQC running time is: 1.2111211999999991 s
Circuit model running time is: 0.16549000000000014 s
The current example is: Quantum kernel method with 11 X 9 size.
The qubit number is: 11
MBQC running time is: 1.4613236 s
Circuit model running time is: 0.17382679999999873 s
The current example is: Quantum kernel method with 12 X 9 size.
The qubit number is: 12
MBQC running time is: 1.7479014999999993 s
Circuit model running time is: 0.19296899999999972 s
The current example is: Quantum kernel method with 13 X 9 size.
The qubit number is: 13
MBQC running time is: 2.0869981000000006 s
Circuit model running time is: 0.21488890000000005 s
The current example is: Quantum kernel method with 14 X 9 size.
The qubit number is: 14
MBQC running time is: 2.4556445999999976 s
Circuit model running time is: 0.25365719999999925 s
The current example is: Quantum kernel method with 15 X 9 size.
The qubit number is: 15
MBQC running time is: 2.8709384999999976 s
Circuit model running time is: 0.32052579999999864 s
The current example is: Quantum kernel method with 16 X 9 size.
The qubit number is: 16
MBQC running time is: 3.371537 s
Circuit model running time is: 0.46659409999999824 s
The current example is: Quantum kernel method with 17 X 9 size.
The qubit number is: 17
MBQC running time is: 3.850072300000001 s
Circuit model running time is: 0.7888110000000026 s
The current example is: Quantum kernel method with 18 X 9 size.
The qubit number is: 18
MBQC running time is: 4.468373900000003 s
Circuit model running time is: 1.5083874000000037 s
The current example is: Quantum kernel method with 19 X 9 size.
The qubit number is: 19
MBQC running time is: 5.124628200000004 s
Circuit model running time is: 2.865450800000005 s
# 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.
"""
Use MBQC to simulate the circuit in quantum Kernel method and compare with the circuit model
"""
from paddle import to_tensor
from paddle_quantum.mbqc.utils import print_progress, plot_results
from paddle_quantum.mbqc.utils import write_running_data, read_running_data
from time import perf_counter
from paddle_quantum.mbqc.simulator import simulate_by_mbqc, sample_by_mbqc
from numpy import random
from paddle_quantum.mbqc.qobject import Circuit
from paddle_quantum.circuit import UAnsatz
__all__ = [
"qkernel_circuit",
"cir_uansatz",
"cir_mbqc",
"qkernel",
"compare_time",
"compare_result"
]
def qkernel_circuit(alpha, cir):
r"""输入量子核方法的电路。
Args:
alpha (Tensor): 旋转门的角度
cir (Circuit or UAnsatz): Circuit 或者 UAnsatz 的实例
Returns:
Circuit / UAnsatz: 构造的量子电路
"""
qubit_number = alpha.shape[0]
# U
for i in range(qubit_number):
if not isinstance(cir, Circuit):
cir.h(i)
cir.rx(alpha[i, 1], i)
cir.rz(alpha[i, 2], i)
cir.rx(alpha[i, 3], i)
# cz
for i in range(qubit_number - 1):
cir.h(i + 1)
cir.cnot([i, i + 1])
cir.h(i + 1)
# U^{\dagger}
for i in range(qubit_number):
cir.rx(alpha[i, 5], i)
cir.rz(alpha[i, 6], i)
cir.rx(alpha[i, 7], i)
cir.h(i)
# Measure
if isinstance(cir, Circuit):
cir.measure()
return cir
def cir_uansatz(alpha, shots=1):
r"""定义量子核方法的 UAnsatz 电路模型。
Args:
alpha (Tensor): 旋转门的角度
shots (int): 重复次数
Returns:
list: 列表中的第一项为测量结果,第二项为 UAnsatz 电路模型模拟方式运行量子核方法需要的时间
"""
qubit_number = alpha.shape[0]
# If bit_num >= 25, the UAnsatz could not support it
if qubit_number >= 25:
raise ValueError("the UAnsatz model could not support qubit number larger than 25 on a laptop.")
else:
cir = UAnsatz(qubit_number)
cir = qkernel_circuit(alpha, cir)
uansatz_start_time = perf_counter()
cir.run_state_vector()
outcome = cir.measure(shots=shots)
uansatz_end_time = perf_counter()
# As the outcome dictionary is in a messy order, we need to reorder the outcome
outcome_in_order = {bin(i)[2:].zfill(qubit_number): outcome[bin(i)[2:].zfill(qubit_number)]
if bin(i)[2:].zfill(qubit_number) in list(outcome.keys())
else 0 for i in range(2 ** qubit_number)}
result_and_time = [outcome_in_order, uansatz_end_time - uansatz_start_time]
return result_and_time
def cir_mbqc(alpha, shots=1):
r"""定义量子核方法的 MBQC 翻译电路模型。
Args:
alpha (Tensor): 旋转门的角度
shots (int): 重复次数
Returns:
list: 列表中的第一项为测量结果,第二项为依据行序优先算法优化翻译 MBQC 模型模拟量子核方法电路需要的时间
"""
# Input circuit
width = alpha.shape[0]
cir = Circuit(width)
cir = qkernel_circuit(alpha, cir)
# Start running
start_time = perf_counter()
# No sampling
if shots == 1:
simulate_by_mbqc(cir)
sample_outcomes = {}
# Sampling
else:
sample_outcomes, all_output = sample_by_mbqc(cir, shots=shots, print_or_not=True)
end_time = perf_counter()
result_and_time = [sample_outcomes, end_time - start_time]
return result_and_time
def qkernel(start_width, end_width):
r"""定义量子核方法的主函数。
在主函数中,我们比较了在不同宽度下 UAnsatz 电路模型模拟量子核方法和基于行序优先原则算法翻译为 MBQC 模型模拟量子核方法的时间。
Args:
start_width (int): 电路的起始宽度
end_width (int): 电路的终止宽度
"""
# Initialize
depth = 9 # Set the depth of circuit to 9
time_text = open("record_time.txt", 'w')
all_width = list(range(start_width, end_width))
# Start running Kernel under different qubit numbers
counter = 0
for width in all_width:
eg = "Quantum kernel method with " + str(width) + " X " + str(depth) + " size."
print_progress((counter + 1) / len(all_width), "Current Plot Progress")
counter += 1
alpha = random.randn(width, depth)
alpha_tensor = to_tensor(alpha, dtype='float64')
mbqc_result, mbqc_time = cir_mbqc(alpha_tensor)
uansatz_result, uansatz_time = cir_uansatz(alpha_tensor)
write_running_data(time_text, eg, width, mbqc_time, uansatz_time)
time_text.close()
def compare_time(start_width, end_width):
r"""定义量子核方法的画图函数。
此函数用于将 UAnsatz 电路模型模拟运行量子核方法的时间成本和基于行序优先原则算法翻译为 MBQC 模型模拟的时间画出来。
"""
time_comparison = read_running_data("record_time.txt")
bar_labels = ["MBQC", "UAnsatz"]
xticklabels = list(range(start_width, end_width))
title = "Kernel Example: Time comparison between MBQC and UAnsatz"
xlabel = "Circuit width"
ylabel = "Running time (s)"
plot_results(time_comparison, bar_labels, title, xlabel, ylabel, xticklabels)
def compare_result(qubit_number, shots=1024):
r"""定义量子核方法获得最终比特串的函数。
此函数的调用是为了获得量子核方法输出的最终比特串对应的字符。
对于量子电路模型,只需要获得演化后的量子态,并重复对该量子态进行多次测量,就可以获得输出比特串的概率分布。
MBQC 模型则与之不同,前文中所有 MBQC 模型的量子核方法都是仅运算一次得到的输出比特串,而并没有输出演化后量子态的信息。
因此,为了获得输出比特串的概率分布,我们需要对 MBQC 模型的整个过程重复执行多次,
统计这些次数中各个比特串出现的频率,从而用频率信息估算概率信息。
Note:
由于采样的次数有限,统计出的输出比特串频率分布会稍有偏差。
Args:
qubit_number (int): 比特数
shots (int, optional): 采样次数
"""
# Initialize
depth = 9
CRED = '\033[91m'
CEND = '\033[0m'
alpha = random.randn(qubit_number, depth)
alpha_tensor = to_tensor(alpha, dtype="float64")
mbqc_result, mbqc_time = cir_mbqc(alpha_tensor, shots)
uansatz_result, uansatz_time = cir_uansatz(alpha_tensor, shots)
print(CRED + "MBQC sampling results:" + CEND, mbqc_result)
print(CRED + "UAnsatz sampling results:" + CEND, uansatz_result)
result_comparison = [mbqc_result, uansatz_result]
bar_labels = ["MBQC", "UAnsatz"]
title = "Kernel Example: Comparison between MBQC and UAnsatz"
xlabel = "Measurement outcomes"
ylabel = "Distribution"
plot_results(result_comparison, bar_labels, title, xlabel, ylabel)
# 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.
"""
main
"""
from paddle_quantum.mbqc.VQSVD.vqsvd import vqsvd, compare_time, compare_result
def main():
r"""VQSVD 主函数。
"""
# From 3 to 24, plot time
start_qubit = 3
end_qubit = 24
vqsvd(start_qubit, end_qubit)
compare_time(start_qubit, end_qubit)
# Compare the probability distribution of outcome bit strings by multiple samplings
compare_result(3, 1024)
if __name__ == '__main__':
main()
The current example is: VQSVD with 3 X 2 size.
The qubit number is: 3
MBQC running time is: 0.13685639999999966 s
Circuit model running time is: 0.10096300000000014 s
The current example is: VQSVD with 4 X 2 size.
The qubit number is: 4
MBQC running time is: 0.18013259999999987 s
Circuit model running time is: 0.09092929999999999 s
The current example is: VQSVD with 5 X 2 size.
The qubit number is: 5
MBQC running time is: 0.2437035999999999 s
Circuit model running time is: 0.09389560000000019 s
The current example is: VQSVD with 6 X 2 size.
The qubit number is: 6
MBQC running time is: 0.32366709999999976 s
Circuit model running time is: 0.11702480000000026 s
The current example is: VQSVD with 7 X 2 size.
The qubit number is: 7
MBQC running time is: 0.3896693 s
Circuit model running time is: 0.09745869999999979 s
The current example is: VQSVD with 8 X 2 size.
The qubit number is: 8
MBQC running time is: 0.46406170000000024 s
Circuit model running time is: 0.1019085000000004 s
The current example is: VQSVD with 9 X 2 size.
The qubit number is: 9
MBQC running time is: 0.5693280999999999 s
Circuit model running time is: 0.10031949999999945 s
# 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.
"""
Use MBQC to simulate VQSVD circuit and compare with the circuit model
"""
from paddle import to_tensor
from paddle_quantum.mbqc.utils import print_progress, write_running_data, read_running_data, plot_results
from time import perf_counter
from paddle_quantum.mbqc.simulator import simulate_by_mbqc, sample_by_mbqc
from numpy import random
from paddle_quantum.circuit import UAnsatz
from paddle_quantum.mbqc.qobject import Circuit
__all__ = [
"vqsvd_circuit",
"cir_uansatz",
"cir_mbqc",
"vqsvd",
"compare_time",
"compare_result"
]
def vqsvd_circuit(cir, alpha):
r"""输入 VQSVD 算法的电路。
Args:
cir (Circuit or UAnsatz): Circuit 为 MBQC 电路模块中的类,UAnsatz 为量桨平台 UAnsatz 电路模型中的类,
二者都具有相同的量子门输入方式
alpha (Tensor): 旋转门的角度
Returns:
Circuit / UAnsatz: 输入了量子门信息的电路
"""
width = alpha.shape[0]
depth = alpha.shape[1]
if not isinstance(cir, Circuit):
for which_qubit in range(width):
cir.h(which_qubit)
for layer_num in range(depth):
for which_qubit in range(width):
cir.ry(alpha[which_qubit, layer_num], which_qubit)
for which_qubit in range(width - 1):
cir.cnot([which_qubit, which_qubit + 1])
if isinstance(cir, Circuit):
cir.measure()
return cir
def cir_uansatz(alpha, shots=1):
r"""定义 VQSVD 算法的 UAnsatz 电路模型。
Args:
alpha (Tensor): 旋转门的角度
shots (int, optional): 重复次数
Returns:
list: 列表中的第一项为测量结果或运算后得到的量子态,第二项为电路模拟方式运行 VQSVD 算法需要的时间
"""
qubit_number = alpha.shape[0]
# If bit_num >= 25, the UAnsatz could not support it
if qubit_number >= 25:
raise ValueError("the UAnsatz model could not support qubit number larger than 25 on a laptop.")
else:
# Input information of circuits
cir = UAnsatz(qubit_number)
cir = vqsvd_circuit(cir, alpha)
# Start running
uansatz_start_time = perf_counter()
cir.run_state_vector()
outcome = cir.measure(shots=shots)
uansatz_end_time = perf_counter()
# As the outcome dictionary is in a messy order, we need to reorder the outcome
outcome_in_order = {bin(i)[2:].zfill(qubit_number): outcome[bin(i)[2:].zfill(qubit_number)]
if bin(i)[2:].zfill(qubit_number) in list(outcome.keys())
else 0 for i in range(2 ** qubit_number)}
result_and_time = [outcome_in_order, uansatz_end_time - uansatz_start_time]
return result_and_time
def cir_mbqc(alpha, shots=1):
r"""定义 VQSVD 算法的 MBQC 翻译模拟方式。
Args:
alpha (Tensor): 旋转门的角度
Returns:
list: 列表中的第一项为测量结果或运算后得到的量子态,第二项为依据行序优先算法优化翻译 MBQC 模型模拟 VQSVD 电路需要的时间
"""
qubit_number = alpha.shape[0]
# Run MBQC
cir = Circuit(qubit_number)
cir = vqsvd_circuit(cir, alpha)
start_time = perf_counter()
# No sampling
if shots == 1:
simulate_by_mbqc(cir)
sample_outcomes = {}
# Sampling
else:
sample_outcomes, all_output = sample_by_mbqc(cir, shots=shots, print_or_not=True)
end_time = perf_counter()
result_list = [sample_outcomes, end_time - start_time]
return result_list
def vqsvd(start_width, end_width):
r"""定义 VQSVD 算法的主函数。
在主函数中,我们比较了在不同宽度下 UAnsatz 电路模型模拟 VQSVD 和基于行序优先原则算法翻译为 MBQC 模型模拟 VQSVD 的时间。
Args:
start_width (int): 电路的起始宽度
end_width (int): 电路的终止宽度
"""
# Initialize
depth = 2 # Set the depth of circuit to 2
time_text = open("record_time.txt", 'w')
all_width = list(range(start_width, end_width))
# Start running VQSVD under different qubit numbers
counter = 0
for width in all_width:
eg = "VQSVD with " + str(width) + " X " + str(depth) + " size."
print_progress((counter + 1) / len(all_width), "Current Plot Progress")
counter += 1
alpha = random.randn(width, depth)
alpha_tensor = to_tensor(alpha, dtype='float64')
mbqc_result, mbqc_time = cir_mbqc(alpha_tensor)
uansatz_result, uansatz_time = cir_uansatz(alpha_tensor)
write_running_data(time_text, eg, width, mbqc_time, uansatz_time)
time_text.close()
def compare_time(start_width, end_width):
r"""定义 VQSVD 的画图函数。
此函数用于将 UAnsatz 电路模型模拟运行 VQSVD 的时间成本和基于行序优先原则算法翻译为 MBQC 模型模拟的时间画出来。
"""
time_comparison = read_running_data("record_time.txt")
bar_labels = ['MBQC', 'UAnsatz']
xticklabels = list(range(start_width, end_width))
title = 'VQSVD Example: Time comparison between MBQC and UAnsatz'
xlabel = 'Circuit width'
ylabel = 'Running time (s)'
plot_results(time_comparison, bar_labels, title, xlabel, ylabel, xticklabels)
def compare_result(qubit_number, shots=1024):
r"""定义 VQSVD 获得最终比特串的函数。
此函数的调用是为了获得 VQSVD 输出的最终比特串对应的字符。
对于量子电路模型,只需要获得演化后的量子态,并重复对该量子态进行测量多次,就可以获得输出比特串的概率分布。
MBQC 模型则与之不同,前文中所有 MBQC 模型的 VQSVD 都是仅运算一次得到的输出比特串,而并没有输出演化后量子态的信息。
因此,为了获得输出比特串的概率分布,我们需要对 MBQC 模型的整个过程重复执行多次,
统计这些次数中各个比特串出现的频率,从而用频率信息估算概率信息。
Note:
由于采样的次数有限,统计出的输出比特串频率分布会稍有偏差。
Args:
qubit_number (int): 比特数
shots (int, optional): 采样次数
"""
# Initialize
depth = 2 # Set the depth of circuit to 2
CRED = '\033[91m'
CEND = '\033[0m'
alpha = random.randn(qubit_number, depth)
alpha_tensor = to_tensor(alpha, dtype="float64")
mbqc_result, mbqc_time = cir_mbqc(alpha_tensor, shots)
uansatz_result, uansatz_time = cir_uansatz(alpha_tensor, shots)
print(CRED + "MBQC sampling results:" + CEND, mbqc_result, "s")
print(CRED + "UAnsatz sampling results:" + CEND, uansatz_result, "s")
result_comparison = [mbqc_result, uansatz_result]
bar_labels = ['MBQC', 'UAnsatz']
title = "VQSVD Example: Comparison btween MBQC and UAnsatz"
xlabel = "Measurement outcomes"
ylabel = "Distribution"
plot_results(result_comparison, bar_labels, title, xlabel, ylabel)
# 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.
"""
CNOT test
"""
from paddle_quantum.mbqc.utils import random_state_vector, compare_by_vector, compare_by_density
from paddle_quantum.mbqc.simulator import MBQC
from paddle_quantum.mbqc.qobject import Circuit, State
from paddle_quantum.mbqc.mcalculus import MCalculus
from paddle_quantum.circuit import UAnsatz
n = 2 # Set the circuit width
# Generate a random state vector
input_psi = random_state_vector(2, is_real=False)
# Instantiate a Circuit class
cir = Circuit(n)
# There are two patterns for CNOT gate, one is the 4-nodes pattern, the other is the 15-nodes pattern
# We can use CNOT gate in definite pattern that we want
# cir.cnot([0,1]) # CNOT pattern with 4 - nodes
cir.cnot_15([0, 1]) # CNOT pattern with 15 - nodes
# Instantiate a MCalculus class
mc = MCalculus()
mc.set_circuit(cir)
# If we want to do pattern standardization, signal shifting, measurement order optimization,
# we can use the following lines in order. However, as one CNOT gate is already a standardized pattern itself,
# there is no need to do so.
# mc.standardize()
# mc.shift_signals()
# mc.optimize_by_row()
pattern = mc.get_pattern()
# Instantiate a MBQC class
mbqc = MBQC()
mbqc.set_pattern(pattern)
# mbqc.draw_process(draw=True)
mbqc.set_input_state(State(input_psi, [0, 1]))
# Run computation by pattern
mbqc.run_pattern()
# Obtain the output state
state_out = mbqc.get_quantum_output()
# Find the standard result
cir_std = UAnsatz(n)
cir_std.cnot([0, 1])
vec_std = cir_std.run_state_vector(input_psi.astype("complex128"))
system_std = state_out.system
state_std = State(vec_std, system_std)
# Compare with the standard result in UAnsatz
compare_by_vector(state_out, state_std)
compare_by_density(state_out, state_std)
# 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.
from numpy import random, pi
from paddle import to_tensor
from paddle_quantum.circuit import UAnsatz
from paddle_quantum.mbqc.simulator import MBQC
from paddle_quantum.mbqc.transpiler import transpile
from paddle_quantum.mbqc.qobject import Circuit, State
from paddle_quantum.mbqc.utils import random_state_vector, compare_by_vector, compare_by_density
n = 5 # Set the circuit width
theta = to_tensor(random.rand(n) * 2 * pi, dtype='float64') # Generate random angles
# Instantiate a Circuit class
cir_mbqc = Circuit(n)
# Instantiate a UAnsatz class
cir_ansatz = UAnsatz(n)
# Construct a circuit
for cir in [cir_mbqc, cir_ansatz]:
for i in range(n):
cir.h(i)
cir.rx(theta[i], i)
cir.cnot([0, 1])
for i in range(n):
cir.ry(theta[i], i)
cir.rz(theta[i], i)
# Generate a random state vector
input_psi = random_state_vector(n, is_real=False)
# Transpile circuit to measurement pattern
pattern = transpile(cir_mbqc)
mbqc = MBQC()
mbqc.set_pattern(pattern)
mbqc.set_input_state(State(input_psi, list(range(n))))
mbqc.run_pattern()
# Obtain the output state
state_out = mbqc.get_quantum_output()
# Find the standard result
vec_ansatz = cir_ansatz.run_state_vector(input_psi.astype("complex128"))
system_ansatz = state_out.system
state_ansatz = State(vec_ansatz, system_ansatz)
compare_by_vector(state_out, state_ansatz)
compare_by_density(state_out, state_ansatz)
# 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.
"""
Single qubit unitary gate test with random angle
"""
from numpy import pi, random
from paddle import to_tensor, matmul
from paddle_quantum.mbqc.utils import random_state_vector, rotation_gate, compare_by_vector
from paddle_quantum.mbqc.simulator import MBQC
from paddle_quantum.mbqc.qobject import Circuit, State
from paddle_quantum.mbqc.mcalculus import MCalculus
# Set qubit number
n = 1
# Input vector and label
# If we want to choose an input state randomly, we can use ``random_state_vector``
input_psi = random_state_vector(1, is_real=False)
# To be simplify, here we choose "|+>" as the input vector
# input_vec = plus_state()
# Default that 'alpha' represents the initial rotation gates' angles without adaptive constants
# Default that 'theta' represents the adaptive measurement angles
# Set 'alpha'
alpha = to_tensor([2 * pi * random.uniform()], dtype='float64')
beta = to_tensor([2 * pi * random.uniform()], dtype='float64')
gamma = to_tensor([2 * pi * random.uniform()], dtype='float64')
# Note: Here the parameters are not equal to those in UAnsatz circuit's "U3".
# Indeed, we decompose unitary matrix as U = Rz Rx Rz instead of U = Rz Ry Rz in UAnsatz
params = [alpha, beta, gamma]
# Initialize circuit
cir = Circuit(n)
cir.u(params, 0)
# circuit = cir.get_circuit()
# Initialize pattern
pat = MCalculus()
pat.set_circuit(cir)
# If we want to standardize the circuit, shift signals or optimize the circuit, we can use the following lines
# Note: As one CNOT gate has already been a standardized pattern, there is no need to do so.
# pat.standardize()
# pat.shift_signals()
# pat.optimize_by_row()
pattern = pat.get_pattern()
# Initialize MBQC
mbqc = MBQC()
mbqc.set_pattern(pattern)
mbqc.set_input_state(State(input_psi, [0]))
# If we want to plot the process of measurement, we can call th function ``mbqc.plot()``
# mbqc.plot(pause_time=1.0)
# Run by pattern
mbqc.run_pattern()
# Acquire the output state
state_out = mbqc.get_quantum_output()
# Compare with the standard result
vec_std = matmul(rotation_gate('z', gamma),
matmul(rotation_gate('x', beta),
matmul(rotation_gate('z', alpha), input_psi)))
system_label = state_out.system
state_std = State(vec_std, system_label)
compare_by_vector(state_out, state_std)
# 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.
"""
CNOT test
"""
from paddle import to_tensor, matmul
from paddle_quantum.mbqc.utils import pauli_gate, cnot_gate, basis, random_state_vector
from paddle_quantum.mbqc.utils import compare_by_vector, compare_by_density
from paddle_quantum.mbqc.simulator import MBQC
from paddle_quantum.mbqc.qobject import State
X = pauli_gate('X')
Z = pauli_gate('Z')
X_basis = basis('X')
Y_basis = basis('Y')
# Construct the underlying graph of CNOT implementation in MBQC
V = [str(i) for i in range(1, 16)]
E = [('1', '2'), ('2', '3'), ('3', '4'), ('4', '5'),
('5', '6'), ('6', '7'), ('4', '8'), ('8', '12'),
('9', '10'), ('10', '11'), ('11', '12'),
('12', '13'), ('13', '14'), ('14', '15')]
G = [V, E]
# Generate a random state vector
input_psi = random_state_vector(2, is_real=False)
# Instantiate a MBQC class
mbqc = MBQC()
# Set the underlying graph for computation
mbqc.set_graph(G)
# Set the input state
mbqc.set_input_state(State(input_psi, ['1', '9']))
# Watch the computational process
# mbqc.draw_process()
# Start measurement process
mbqc.measure('1', X_basis)
mbqc.measure('2', Y_basis)
mbqc.measure('3', Y_basis)
mbqc.measure('4', Y_basis)
mbqc.measure('5', Y_basis)
mbqc.measure('6', Y_basis)
mbqc.measure('8', Y_basis)
mbqc.measure('9', X_basis)
mbqc.measure('10', X_basis)
mbqc.measure('11', X_basis)
mbqc.measure('12', Y_basis)
mbqc.measure('13', X_basis)
mbqc.measure('14', X_basis)
# Obtain byproduct's exponents
cx = mbqc.sum_outcomes(['2', '3', '5', '6'])
tx = mbqc.sum_outcomes(['2', '3', '8', '10', '12', '14'])
cz = mbqc.sum_outcomes(['1', '3', '4', '5', '8', '9', '11'], 1)
tz = mbqc.sum_outcomes(['9', '11', '13'])
# Correct byproducts
mbqc.correct_byproduct('X', '7', cx)
mbqc.correct_byproduct('X', '15', tx)
mbqc.correct_byproduct('Z', '7', cz)
mbqc.correct_byproduct('Z', '15', tz)
# Obtain the output state
state_out = mbqc.get_quantum_output()
# Find the standard result
vec_std = matmul(to_tensor(cnot_gate()), input_psi)
system_std = ['7', '15']
state_std = State(vec_std, system_std)
# Compare with the standard result
compare_by_vector(state_out, state_std)
compare_by_density(state_out, state_std)
# 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.
"""
Single qubit unitary gate test with random angle
"""
from numpy import pi, random
from paddle import to_tensor, matmul
from paddle_quantum.mbqc.utils import random_state_vector, rotation_gate, basis
from paddle_quantum.mbqc.utils import compare_by_vector, compare_by_density
from paddle_quantum.mbqc.simulator import MBQC
from paddle_quantum.mbqc.qobject import State
# Construct the underlying graph of single-qubit implementation in MBQC
G = [['1', '2', '3', '4', '5'], [('1', '2'), ('2', '3'), ('3', '4'), ('4', '5')]]
# Generate a random state vector
input_vec = random_state_vector(1, is_real=False)
# Suppose the single-qubit gate is decomposed by Rx(alpha_4) Rz(alpha_3) Rx(alpha_2)
alpha_1 = 0
alpha_2 = pi * random.uniform()
alpha_3 = pi * random.uniform()
alpha_4 = pi * random.uniform()
# Instantiate a MBQC class
mbqc = MBQC()
# Set the underlying graph for computation
mbqc.set_graph(G)
# Set the input state
mbqc.set_input_state(State(input_vec, ['1']))
# Watch the computational process
# mbqc.draw_process(pause_time=1.0)
# Set the measurement angles
# Measure qubit '1', with "theta = alpha"
theta_1 = alpha_1
theta_1 = to_tensor([theta_1], dtype='float64')
mbqc.measure('1', basis('XY', theta_1))
# Measure qubit '2', with "theta = (-1)^{s_1 + 1} * alpha"
theta_2 = (-1) ** mbqc.sum_outcomes(['1'], 1) * alpha_2
theta_2 = to_tensor([theta_2], dtype='float64')
mbqc.measure('2', basis('XY', theta_2))
# Measure qubit '3', with "theta = (-1)^{s_2 + 1} * alpha"
theta_3 = (-1) ** mbqc.sum_outcomes(['2'], 1) * alpha_3
theta_3 = to_tensor([theta_3], dtype='float64')
mbqc.measure('3', basis('XY', theta_3))
# Measure qubit '4', with "theta = (-1)^{s_1 + s_3 + 1} * alpha"
theta_4 = (-1) ** mbqc.sum_outcomes(['1', '3'], 1) * alpha_4
theta_4 = to_tensor([theta_4], dtype='float64')
mbqc.measure('4', basis('XY', theta_4))
# Correct byproduct operators
mbqc.correct_byproduct('X', '5', mbqc.sum_outcomes(['2', '4']))
mbqc.correct_byproduct('Z', '5', mbqc.sum_outcomes(['1', '3']))
# Obtain the output state
state_out = mbqc.get_quantum_output()
# Find the standard result
vec_std = matmul(rotation_gate('x', to_tensor([alpha_4], dtype='float64')),
matmul(rotation_gate('z', to_tensor([alpha_3], dtype='float64')),
matmul(rotation_gate('x', to_tensor([alpha_2], dtype='float64')), input_vec)))
system_std = ['5']
state_std = State(vec_std, system_std)
# Compare with the standard result
compare_by_vector(state_out, state_std)
compare_by_density(state_out, state_std)
# 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.
"""
此模块包含处理 MBQC 测量模式的相关操作。
"""
from numpy import pi
from paddle import to_tensor, multiply
from paddle_quantum.mbqc.qobject import Pattern, Circuit
from paddle_quantum.mbqc.utils import div_str_to_float, int_to_div_str, print_progress
__all__ = [
"MCalculus"
]
class MCalculus:
r"""定义测量模式类。
跟据文献 [The measurement calculus, arXiv: 0704.1263] 的测量语言,该类提供处理测量模式的各种基本操作。
"""
def __init__(self):
r"""``MCalculus`` 的构造函数,用于实例化一个 ``MCalculus`` 对象。
"""
self.__circuit_slice = [] # Restore the information of sliced circuit
self.__wild_pattern = [] # Record the background pattern
self.__wild_commands = [] # Record wild commands information
self.__pattern = None # Record standard pattern
self.__measured_qubits = [] # Record the measured qubits in the circuit model
self.__circuit_width = None # Record the circuit width
self.__track = False # Switch of progress bar
def track_progress(self, track=True):
r"""显示测量模式处理过程的进度条开关。
Args:
track (bool, optional): ``True`` 为打开进度条显示,``False`` 为关闭进度条显示,默认为 ``True``
"""
assert isinstance(track, bool), "parameter 'track' must be a bool."
self.__track = track
def set_circuit(self, circuit):
r"""对 ``MCalculus`` 类设置量子电路。
Args:
circuit (Circuit): 量子电路
"""
assert isinstance(circuit, Circuit), "please input a parameter of type 'Circuit'."
assert circuit.is_valid(), "the circuit is not valid as at least one qubit is not performed any gate yet."
self.__circuit_width = circuit.get_width()
self.__slice_circuit(circuit.get_circuit())
for gate in self.__circuit_slice:
self.__to_pattern(gate)
self.__join_patterns()
def __slice_circuit(self, circuit):
r"""对电路进行切片操作,标记每个量子门和量子测量的输入比特和输出比特。
Note:
这是内部方法,用户不需要直接调用到该方法。
在此,我们使用 ``str`` 类型的变量作为节点的标签。为了不丢失原先节点的坐标信息,便于后续的画图操作,
我们使用形如 ``("1/1", "2/1")`` 类型作为所有节点的标签。
Args:
circuit (list): 电路列表,列表中每个元素代表一个量子门或测量
"""
# Slice the circuit to mark the input_/output_ labels for measurement pattern
counter = []
for gate in circuit:
name = gate[0]
which_qubit = gate[1]
assert len(which_qubit) in [1, 2], str(len(which_qubit)) + "-qubit gate is not supported in this version."
if len(which_qubit) == 1: # Single-qubit gates
which_qubit = which_qubit[0] # Take the item only
assert which_qubit not in self.__measured_qubits, \
"please check your qubit index as this qubit has already been measured."
input_ = [(int_to_div_str(which_qubit), int_to_div_str(int(counter.count(which_qubit))))]
if name == 'm':
output_ = [] # No output_ node for measurement
else:
output_ = [(int_to_div_str(which_qubit), int_to_div_str(int(counter.count(which_qubit) + 1)))]
counter += [which_qubit] # Record the index
# The gate after slicing has a form of:
# [original_gate, input_, output_], e.g. = [[h, [0], None], input_, output_]
self.__circuit_slice.append([gate, input_, output_])
else: # Two-qubit gates
control = which_qubit[0]
target = which_qubit[1]
assert control not in self.__measured_qubits and target not in self.__measured_qubits, \
"please check your qubit indices as these qubits have already been measured."
if name == 'cz': # Input and output nodes coincide for CZ gate
input_output = [(int_to_div_str(control), int_to_div_str(int(counter.count(control)))),
(int_to_div_str(target), int_to_div_str(int(counter.count(target))))]
# The gate after slicing has a form of:
# [original_gate, input_, output_], e.g. = [[cz, [0, 1], None], input_, output_]
self.__circuit_slice.append([gate, input_output, input_output])
elif name == 'cnot':
input_ = [(int_to_div_str(control), int_to_div_str(int(counter.count(control)))),
(int_to_div_str(target), int_to_div_str(int(counter.count(target))))]
output_ = [(int_to_div_str(control), int_to_div_str(int(counter.count(control)))),
(int_to_div_str(target), int_to_div_str(int(counter.count(target) + 1)))]
counter += [target] # Record the index
# The gate after slicing has a form of:
# [original_gate, input_, output_], e.g. = [[cnot, [0, 1], None], input_, output_]
self.__circuit_slice.append([gate, input_, output_])
else:
input_ = [(int_to_div_str(control), int_to_div_str(int(counter.count(control)))),
(int_to_div_str(target), int_to_div_str(int(counter.count(target))))]
output_ = [(int_to_div_str(control), int_to_div_str(int(counter.count(control) + 1))),
(int_to_div_str(target), int_to_div_str(int(counter.count(target) + 1)))]
counter += which_qubit # Record the index
# The gate after slicing has a form of:
# [original_gate, input_, output_], e.g. = [[h, [0], None], input_, output_]
self.__circuit_slice.append([gate, input_, output_])
@staticmethod
def __set_ancilla_label(input_, output_, ancilla_num_list=None):
r"""插入辅助比特。
在输入比特和输出比特中间插入辅助比特,辅助比特节点的坐标根据数目而均分,其标签类型与输入和输出比特的标签类型相同。
Note:
这是内部方法,用户不需要直接调用到该方法。
Args:
input_ (list): 测量模式的输入节点
output_ (list): 测量模式的输出节点
ancilla_num_list (list): 需要插入的辅助节点个数列表
Returns:
list: 辅助节点标签列表
"""
assert len(input_) == len(output_), "input and output must have same length."
assert len(input_) in [1, 2], str(len(input_)) + "-qubit gate is not supported in this version."
ancilla_num = [] if ancilla_num_list is None else ancilla_num_list
ancilla_labels = []
for i in range(len(ancilla_num)):
input_qubit = input_[i] # Obtain input qubit
row_in = div_str_to_float(input_qubit[0]) # Row of input qubit
col_in = div_str_to_float(input_qubit[1]) # Column of input qubit
output_qubit = output_[i] # Obtain output qubit
row_out = div_str_to_float(output_qubit[0]) # Row of output qubit
col_out = div_str_to_float(output_qubit[1]) # Column of output qubit
assert row_in == row_out, "please check the qubit labels of your input."
# Calculate Auxiliary qubits' positions
col = col_out - col_in
pos = [int_to_div_str(int(col_in * (ancilla_num[i] + 1) + j * col), ancilla_num[i] + 1)
for j in range(1, ancilla_num[i] + 1)]
# Get the ancilla_labels
for k in range(ancilla_num[i]):
ancilla_labels.append((input_qubit[0], pos[k]))
return ancilla_labels
def __to_pattern(self, gate):
r"""将量子电路中的门和测量翻译为等价的测量模式。
Note:
这是内部方法,用户不需要直接调用到该方法。
Warning:
当前版本支持的量子门为 ``[H, X, Y, Z, S, T, Rx, Ry, Rz, Rz_5, U, CNOT, CNOT_15, CZ]`` 和单比特测量。
注意量子门和测量对应的测量模式不唯一,本方法目前仅选取常用的一种或者两种测量模式进行翻译。
Args:
gate (list): 待翻译的量子门或量子测量,列表中存储的是原始量子门(其中包含量子门名称、作用比特、参数)、输入比特、输出比特
"""
original_gate, input_, output_ = gate
name, which_qubit, param = original_gate
ancilla = []
zero = to_tensor([0], dtype="float64")
minus_one = to_tensor([-1], dtype="float64")
half_pi = to_tensor([pi / 2], dtype="float64")
minus_half_pi = to_tensor([-pi / 2], dtype="float64")
minus_pi = to_tensor([-pi], dtype="float64")
if name == 'h': # Hadamard gate
E = Pattern.CommandE([input_[0], output_[0]])
M = Pattern.CommandM(input_[0], zero, "XY", [], [])
X = Pattern.CommandX(output_[0], [input_[0]])
commands = [E, M, X]
elif name == 'x': # Pauli X gate
ancilla = self.__set_ancilla_label(input_, output_, [1])
E12 = Pattern.CommandE([input_[0], ancilla[0]])
E23 = Pattern.CommandE([ancilla[0], output_[0]])
M1 = Pattern.CommandM(input_[0], zero, "XY", [], [])
M2 = Pattern.CommandM(ancilla[0], minus_pi, "XY", [], [])
X3 = Pattern.CommandX(output_[0], [ancilla[0]])
Z3 = Pattern.CommandZ(output_[0], [input_[0]])
commands = [E12, E23, M1, M2, X3, Z3]
elif name == 'y': # Pauli Y gate
ancilla = self.__set_ancilla_label(input_, output_, [3])
E12 = Pattern.CommandE([input_[0], ancilla[0]])
E23 = Pattern.CommandE([ancilla[0], ancilla[1]])
E34 = Pattern.CommandE([ancilla[1], ancilla[2]])
E45 = Pattern.CommandE([ancilla[2], output_[0]])
M1 = Pattern.CommandM(input_[0], half_pi, "XY", [], [])
M2 = Pattern.CommandM(ancilla[0], half_pi, "XY", [], [])
M3 = Pattern.CommandM(ancilla[1], minus_half_pi, "XY", [], [input_[0], ancilla[0]])
M4 = Pattern.CommandM(ancilla[2], zero, "XY", [], [ancilla[0]])
X5 = Pattern.CommandX(output_[0], [ancilla[2]])
Z5 = Pattern.CommandZ(output_[0], [ancilla[1]])
commands = [E12, E23, E34, E45, M1, M2, M3, M4, X5, Z5]
elif name == 'z': # Pauli Z gate
ancilla = self.__set_ancilla_label(input_, output_, [1])
E12 = Pattern.CommandE([input_[0], ancilla[0]])
E23 = Pattern.CommandE([ancilla[0], output_[0]])
M1 = Pattern.CommandM(input_[0], minus_pi, "XY", [], [])
M2 = Pattern.CommandM(ancilla[0], zero, "XY", [], [])
X3 = Pattern.CommandX(output_[0], [ancilla[0]])
Z3 = Pattern.CommandZ(output_[0], [input_[0]])
commands = [E12, E23, M1, M2, X3, Z3]
elif name == 's': # Phase gate
ancilla = self.__set_ancilla_label(input_, output_, [1])
E12 = Pattern.CommandE([input_[0], ancilla[0]])
E23 = Pattern.CommandE([ancilla[0], output_[0]])
M1 = Pattern.CommandM(input_[0], minus_half_pi, "XY", [], [])
M2 = Pattern.CommandM(ancilla[0], zero, "XY", [], [])
X3 = Pattern.CommandX(output_[0], [ancilla[0]])
Z3 = Pattern.CommandZ(output_[0], [input_[0]])
commands = [E12, E23, M1, M2, X3, Z3]
elif name == 't': # T gate
ancilla = self.__set_ancilla_label(input_, output_, [1])
E12 = Pattern.CommandE([input_[0], ancilla[0]])
E23 = Pattern.CommandE([ancilla[0], output_[0]])
M1 = Pattern.CommandM(input_[0], to_tensor([-pi / 4], dtype="float64"), "XY", [], [])
M2 = Pattern.CommandM(ancilla[0], zero, "XY", [], [])
X3 = Pattern.CommandX(output_[0], [ancilla[0]])
Z3 = Pattern.CommandZ(output_[0], [input_[0]])
commands = [E12, E23, M1, M2, X3, Z3]
elif name == 'rx': # Rotation gate around x axis
ancilla = self.__set_ancilla_label(input_, output_, [1])
E12 = Pattern.CommandE([input_[0], ancilla[0]])
E23 = Pattern.CommandE([ancilla[0], output_[0]])
M1 = Pattern.CommandM(input_[0], zero, "XY", [], [])
M2 = Pattern.CommandM(ancilla[0], multiply(param, minus_one), "XY", [input_[0]], [])
X3 = Pattern.CommandX(output_[0], [ancilla[0]])
Z3 = Pattern.CommandZ(output_[0], [input_[0]])
commands = [E12, E23, M1, M2, X3, Z3]
elif name == 'ry': # Rotation gate around y axis
ancilla = self.__set_ancilla_label(input_, output_, [3])
E12 = Pattern.CommandE([input_[0], ancilla[0]])
E23 = Pattern.CommandE([ancilla[0], ancilla[1]])
E34 = Pattern.CommandE([ancilla[1], ancilla[2]])
E45 = Pattern.CommandE([ancilla[2], output_[0]])
M1 = Pattern.CommandM(input_[0], half_pi, "XY", [], [])
M2 = Pattern.CommandM(ancilla[0], multiply(param, minus_one), "XY", [input_[0]], [])
M3 = Pattern.CommandM(ancilla[1], minus_half_pi, "XY", [], [input_[0], ancilla[0]])
M4 = Pattern.CommandM(ancilla[2], zero, "XY", [], [ancilla[0]])
X5 = Pattern.CommandX(output_[0], [ancilla[2]])
Z5 = Pattern.CommandZ(output_[0], [ancilla[1]])
commands = [E12, E23, E34, E45, M1, M2, M3, M4, X5, Z5]
elif name == 'rz': # Rotation gate around z axis
ancilla = self.__set_ancilla_label(input_, output_, [1])
E12 = Pattern.CommandE([input_[0], ancilla[0]])
E23 = Pattern.CommandE([ancilla[0], output_[0]])
M1 = Pattern.CommandM(input_[0], multiply(param, minus_one), "XY", [], [])
M2 = Pattern.CommandM(ancilla[0], zero, "XY", [], [])
X3 = Pattern.CommandX(output_[0], [ancilla[0]])
Z3 = Pattern.CommandZ(output_[0], [input_[0]])
commands = [E12, E23, M1, M2, X3, Z3]
elif name == 'rz_5': # Rotation gate around z axis
ancilla = self.__set_ancilla_label(input_, output_, [3])
E12 = Pattern.CommandE([input_[0], ancilla[0]])
E23 = Pattern.CommandE([ancilla[0], ancilla[1]])
E34 = Pattern.CommandE([ancilla[1], ancilla[2]])
E45 = Pattern.CommandE([ancilla[2], output_[0]])
M1 = Pattern.CommandM(input_[0], zero, "XY", [], [])
M2 = Pattern.CommandM(ancilla[0], zero, "XY", [], [])
M3 = Pattern.CommandM(ancilla[1], multiply(param, minus_one), "XY", [ancilla[0]], [input_[0]])
M4 = Pattern.CommandM(ancilla[2], zero, "XY", [], [ancilla[0]])
X5 = Pattern.CommandX(output_[0], [ancilla[2]])
Z5 = Pattern.CommandZ(output_[0], [ancilla[1]])
commands = [E12, E23, E34, E45, M1, M2, M3, M4, X5, Z5]
elif name == 'u': # General single-qubit unitary
ancilla = self.__set_ancilla_label(input_, output_, [3])
alpha, theta, gamma = param
E12 = Pattern.CommandE([input_[0], ancilla[0]])
E23 = Pattern.CommandE([ancilla[0], ancilla[1]])
E34 = Pattern.CommandE([ancilla[1], ancilla[2]])
E45 = Pattern.CommandE([ancilla[2], output_[0]])
M1 = Pattern.CommandM(input_[0], multiply(alpha, minus_one), "XY", [], [])
M2 = Pattern.CommandM(ancilla[0], multiply(theta, minus_one), "XY", [input_[0]], [])
M3 = Pattern.CommandM(ancilla[1], multiply(gamma, minus_one), "XY", [ancilla[0]], [input_[0]])
M4 = Pattern.CommandM(ancilla[2], zero, "XY", [], [ancilla[0]])
X5 = Pattern.CommandX(output_[0], [ancilla[2]])
Z5 = Pattern.CommandZ(output_[0], [ancilla[1]])
commands = [E12, E23, E34, E45, M1, M2, M3, M4, X5, Z5]
elif name == 'cnot': # Control NOT gate
ancilla = self.__set_ancilla_label(input_, output_, [0, 1])
E23 = Pattern.CommandE([input_[1], ancilla[0]])
E13 = Pattern.CommandE([input_[0], ancilla[0]])
E34 = Pattern.CommandE([ancilla[0], output_[1]])
M2 = Pattern.CommandM(input_[1], zero, "XY", [], [])
M3 = Pattern.CommandM(ancilla[0], zero, "XY", [], [])
X4 = Pattern.CommandX(output_[1], [ancilla[0]])
Z1 = Pattern.CommandZ(output_[0], [input_[1]])
Z4 = Pattern.CommandZ(output_[1], [input_[1]])
commands = [E23, E13, E34, M2, M3, X4, Z1, Z4]
# Measurement pattern of CNOT by 15 qubits, c.f. [arXiv: quant-ph/0301052v2]
# Note: due to the '1' in byproduct Z of qubit-7, we manually add a Z gate after qubit 7 to match this
elif name == "cnot_15": # Control Not gate
input1, input2 = input_
output1, output2 = output_
ancilla = self.__set_ancilla_label(input_, output_, [7, 5])
new_row = str(int(div_str_to_float(input2[0]) + div_str_to_float(input1[0]))) + "/" + "2"
new_col_1 = (div_str_to_float(output1[1]) + div_str_to_float(input1[1]))
new_col_2 = (div_str_to_float(output2[1]) + div_str_to_float(input2[1]))
new_col = str(int((new_col_1 + new_col_2) / 2)) + "/" + "2"
ancilla.append((new_row, new_col))
E12 = Pattern.CommandE([input1, ancilla[0]])
E23 = Pattern.CommandE([ancilla[0], ancilla[1]])
E34 = Pattern.CommandE([ancilla[1], ancilla[2]])
E45 = Pattern.CommandE([ancilla[2], ancilla[3]])
E48 = Pattern.CommandE([ancilla[2], ancilla[12]])
E56 = Pattern.CommandE([ancilla[3], ancilla[4]])
E67 = Pattern.CommandE([ancilla[4], ancilla[5]])
E716 = Pattern.CommandE([ancilla[5], ancilla[6]])
E1617 = Pattern.CommandE([ancilla[6], output1])
E910 = Pattern.CommandE([input2, ancilla[7]])
E1011 = Pattern.CommandE([ancilla[7], ancilla[8]])
E1112 = Pattern.CommandE([ancilla[8], ancilla[9]])
E812 = Pattern.CommandE([ancilla[12], ancilla[9]])
E1213 = Pattern.CommandE([ancilla[9], ancilla[10]])
E1314 = Pattern.CommandE([ancilla[10], ancilla[11]])
E1415 = Pattern.CommandE([ancilla[11], output2])
M1 = Pattern.CommandM(input1, zero, "XY", [], [])
M2 = Pattern.CommandM(ancilla[0], half_pi, "XY", [], [])
M3 = Pattern.CommandM(ancilla[1], half_pi, "XY", [], [])
M4 = Pattern.CommandM(ancilla[2], half_pi, "XY", [], [])
M5 = Pattern.CommandM(ancilla[3], half_pi, "XY", [], [])
M6 = Pattern.CommandM(ancilla[4], half_pi, "XY", [], [])
M8 = Pattern.CommandM(ancilla[12], half_pi, "XY", [], [])
M12 = Pattern.CommandM(ancilla[9], half_pi, "XY", [], [])
M9 = Pattern.CommandM(input2, zero, "XY", [], [])
M10 = Pattern.CommandM(ancilla[7], zero, "XY", [], [])
M11 = Pattern.CommandM(ancilla[8], zero, "XY", [], [])
M13 = Pattern.CommandM(ancilla[10], zero, "XY", [], [])
M14 = Pattern.CommandM(ancilla[11], zero, "XY", [], [])
M7 = Pattern.CommandM(ancilla[5], minus_pi, "XY", [], input_ + [ancilla[i] for i in [1, 2, 3, 8, 12]])
M16 = Pattern.CommandM(ancilla[6], zero, "XY", [], [ancilla[i] for i in [0, 1, 3, 4]])
X15 = Pattern.CommandX(output2, [ancilla[i] for i in [0, 1, 7, 9, 11, 12]])
X17 = Pattern.CommandX(output1, [ancilla[6]])
Z15 = Pattern.CommandZ(output2, [input2, ancilla[8], ancilla[10]])
Z17 = Pattern.CommandZ(output1, [ancilla[5]])
commands = [E12, E23, E34, E45, E48, E56, E67, E716, E1617, E910, E1011, E1112, E812, E1213, E1314, E1415,
M1, M2, M3, M4, M5, M6, M8, M12, M9, M10, M11, M13, M14, M7, M16, X15, X17, Z15, Z17]
elif name == 'cz': # Control Z gate
commands = [Pattern.CommandE(input_)]
elif name == 'm': # Single-qubit measurement
self.__measured_qubits.append(which_qubit[0])
commands = [Pattern.CommandM(input_[0], param[0], param[1], param[2], param[3])]
else:
raise KeyError("translation of such gate is not supported in this version")
self.__wild_commands += commands
self.__wild_pattern.append(
Pattern(str([name] + [str(qubit) for qubit in which_qubit]), list(set(input_ + output_ + ancilla)),
input_, output_, commands))
def __list_to_net(self, bit_lst):
r"""把一个包含比特标签的一维列表处理为二维网状结构的列表,与电路图的网状结构相对应。
Note:
这是内部方法,用户不需要直接调用到该方法。
Args:
bit_lst (list): 输入或输出节点标签的列表
Returns:
list: 网状结构的二维列表,每行记录的是输入或输出节点所在的列数
"""
bit_net = [[] for _ in range(self.__circuit_width)]
for qubit in bit_lst:
bit_net[int(div_str_to_float(qubit[0]))].append(div_str_to_float(qubit[1]))
return bit_net
def __get_input(self, bit_lst):
r"""获得列表中的输入比特节点标签。
Note:
这是内部方法,用户不需要直接调用到该方法。
Args:
bit_lst (list): 节点标签的列表
Returns:
list: 输入比特的节点标签列表
"""
bit_net = self.__list_to_net(bit_lst)
input_ = []
for i in range(self.__circuit_width):
input_.append((int_to_div_str(i), int_to_div_str(int(min(bit_net[i])))))
return input_
def __get_output(self, bit_lst):
r"""获得列表中的输出比特节点标签。
Note:
这是内部方法,用户不需要直接调用到该方法。
Args:
bit_lst (list): 节点标签的列表
Returns:
list: 输出比特的节点标签列表
"""
bit_net = self.__list_to_net(bit_lst)
# Need to separate the classical output and quantum output
c_output = []
q_output = []
for i in range(self.__circuit_width):
if i not in self.__measured_qubits:
q_output.append((int_to_div_str(i), int_to_div_str(int(max(bit_net[i])))))
else:
c_output.append((int_to_div_str(i), int_to_div_str(int(max(bit_net[i])))))
output_ = [c_output, q_output]
return output_
def __join_patterns(self):
r"""将逐个翻译好的量子门和测量的测量模式拼接到一起。
Note:
这是内部方法,用户不需要直接调用到该方法。
"""
names = ""
input_list = []
output_list = []
space = []
commands = []
for pat in self.__wild_pattern:
names += pat.name
space += [qubit for qubit in pat.space if qubit not in space]
input_list += pat.input_
output_list += pat.output_
commands += pat.commands
input_ = self.__get_input(input_list)
output_ = self.__get_output(output_list)
self.__pattern = Pattern(names, space, input_, output_, commands)
@staticmethod
def __propagate(which_cmds):
r"""交换两个命令。
任意两个命令交换遵从对应算符的交换关系,详细的交换关系请参见 [arXiv:0704.1263]。
Note:
这是内部方法,用户不需要直接调用到该方法。
Hint:
我们默认算符由左至右的顺序运算,例如 ``"E"`` 要在所有运算之前,因此 ``"E"`` 要在命令列表中的最左侧,其他命令以此类推。
Warning:
该方法中只对 ``"E"``, ``"M"``, ``"X"``, ``"Z"``, ``"S"`` 五类命令中的任意两个进行交换。
Args:
which_cmds (list): 待交换的命令列表
Returns:
list: 交换后的新命令列表
"""
cmd1 = which_cmds[0]
cmd2 = which_cmds[1]
name1 = cmd1.name
name2 = cmd2.name
assert {name1, name2}.issubset(["E", "M", "X", "Z", "S"]), \
"command's name must be in ['E', 'M', 'X', 'Z', 'S']."
# [X, E] --> [E, X]
if name1 == "X" and name2 == "E":
X_qubit = cmd1.which_qubit
E_qubits = cmd2.which_qubits[:]
if X_qubit not in E_qubits:
return [cmd2, cmd1] # Independent commands commute
else: # For dependent commands
op_qubit = list(set(E_qubits).difference([X_qubit]))
new_cmd = Pattern.CommandZ(op_qubit[0], cmd1.domain)
return [cmd2, new_cmd, cmd1]
# [Z, E] --> [E, Z]
elif name1 == "Z" and name2 == "E":
return [cmd2, cmd1] # they commute
# [M, E] --> [E, M]
elif name1 == "M" and name2 == "E":
if cmd1.which_qubit not in cmd2.which_qubits:
return [cmd2, cmd1] # Independent commands commute
else:
raise ValueError("measurement command should be after entanglement command.")
# [X, M] --> [M, X]
elif name1 == "X" and name2 == "M":
X_qubit = cmd1.which_qubit
M_qubit = cmd2.which_qubit
if X_qubit != M_qubit:
return [cmd2, cmd1] # Independent commands commute
else: # For dependent commands
measurement_plane = cmd2.plane
if measurement_plane == 'XY':
M_new = Pattern.CommandM(M_qubit, cmd2.angle, "XY", cmd2.domain_s + cmd1.domain, cmd2.domain_t)
elif measurement_plane == 'YZ':
M_new = Pattern.CommandM(M_qubit, cmd2.angle, "YZ", cmd2.domain_s, cmd2.domain_t + cmd1.domain)
else:
raise ValueError("in this version we only support measurements in the XY or YZ plane.")
return [M_new]
# [Z, M] --> [M, Z]
elif name1 == "Z" and name2 == "M":
Z_qubit = cmd1.which_qubit
M_qubit = cmd2.which_qubit
if Z_qubit != M_qubit:
return [cmd2, cmd1] # Independent commands commute
else: # For dependent commands
measurement_plane = cmd2.plane
if measurement_plane == 'YZ':
M_new = Pattern.CommandM(M_qubit, cmd2.angle, "YZ", cmd2.domain_s + cmd1.domain, cmd2.domain_t)
elif measurement_plane == 'XY':
M_new = Pattern.CommandM(M_qubit, cmd2.angle, "XY", cmd2.domain_s, cmd2.domain_t + cmd1.domain)
else:
raise ValueError("in this version we only support measurements in the XY or YZ plane.")
return [M_new]
# [Z, X] --> [X, Z]
elif name1 == "Z" and name2 == "X":
return [cmd2, cmd1] # They commute
# Merge two CommandX
elif name1 == "X" and name2 == "X":
X1_qubit = cmd1.which_qubit
X2_qubit = cmd2.which_qubit
if X1_qubit == X2_qubit:
return [Pattern.CommandX(X1_qubit, cmd1.domain + cmd2.domain)]
else:
return which_cmds
# Merge two CommandZ
elif name1 == "Z" and name2 == "Z":
Z1_qubit = cmd1.which_qubit
Z2_qubit = cmd2.which_qubit
if Z1_qubit == Z2_qubit:
return [Pattern.CommandZ(Z1_qubit, cmd1.domain + cmd2.domain)]
else:
return which_cmds
# [S, M or X or Z or S] -> [M or X or Z or S, S]
elif name1 == "S":
# The propagation rule of S with XY or YZ plane measurement is the same
# [S, M] --> [M, S]
if name2 == "M":
S_qubit = cmd1.which_qubit
S_domains = cmd1.domain
# According to the reference [arXiv:0704.1263],
# if S_qubit is in measurement command's domain_s or domain_t,
# we need to add S's domain to the CommandM's domain_s or domain_t
if S_qubit in cmd2.domain_s:
cmd2.domain_s += S_domains
if S_qubit in cmd2.domain_t:
cmd2.domain_t += S_domains
# If S_qubit is not in measurement command's domains, they can swap without modification
return [cmd2, cmd1]
# [S, X] --> [X, S], [S, Z] --> [Z, S], [S, S] --> [S, S]
elif name2 == "X" or name2 == "Z" or name2 == "S":
S_qubit = cmd1.which_qubit
S_domains = cmd1.domain
if S_qubit in cmd2.domain:
cmd2.domain += S_domains
return [cmd2, cmd1]
else:
return which_cmds
# Otherwise, keep the input commands unchanged
else:
return which_cmds
def __propagate_by_type(self, cmd_type, cmds):
r"""把列表中某指定类型的所有命令向前交换。
向前交换不是指将其交换至整个命令列表的最左端,而是交换至规则允许范围内的最前方。
例如:对同一比特的测量操作必须在纠缠操作的后面,所以当调用该方法交换测量算符时,测量算符被交换到了它前面的纠缠算符之后。
Note:
这是内部方法,用户不需要直接调用到该方法。
Args:
cmd_type (str): 待交换的命令类型,为 ``"E"``, ``"M"``, ``"X"``, ``"Z"`` 或 ``"S"``
cmds (list): 待处理的命令列表
Returns:
list: 交换后的新命令列表
"""
assert cmd_type in ["E", "M", "X", "Z", "S"], "command's name must be 'E', 'M', 'X', 'Z' or 'S'."
# Propagate commands 'E', 'M', 'X', 'Z' from back to front
if cmd_type in ["E", "M", "X", "Z"]:
for i in range(len(cmds) - 1, 0, -1):
if cmds[i].name == cmd_type:
cmds = cmds[:i - 1] + self.__propagate([cmds[i - 1], cmds[i]]) + cmds[i + 1:]
# Propagate commands 'S' from front to back
elif cmd_type == "S":
for i in range(0, len(cmds) - 1):
if cmds[i].name == cmd_type:
cmds = cmds[:i] + self.__propagate([cmds[i], cmds[i + 1]]) + cmds[i + 2:]
return cmds
@staticmethod
def __reorder_labels_by_row(labels):
r"""将标签按照行数从小到大进行排序。
该方法是为了调整输入节点和输出节点的顺序,从而与电路图的顺序相一致,便于后续运行和使用。
Note:
这是内部方法,用户不需要直接调用到该方法。
Args:
labels (list): 待改写的标签列表
Returns:
list: 改写后的标签列表,按照行数从小到大顺序排列
"""
row = []
row_and_col = {}
for label in labels: # Obtain row and column index
row.append(int(div_str_to_float(label[0])))
row_and_col[int(div_str_to_float(label[0]))] = label[1]
row.sort() # Reorder the labels
labels_in_order = [(int_to_div_str(i), row_and_col[i]) for i in row]
return labels_in_order
@staticmethod
def __commands_to_numbers(cmds):
r"""将命令列表映射成数字列表。
映射规则为 CommandE -> 1, CommandM -> 2, CommandX -> 3, CommandZ -> 4, CommandS -> 5。
Note:
这是内部方法,用户不需要直接调用到该方法。
Args:
cmds (list): 待处理的命令列表
Returns:
list: 记录每种命令的个数
list: 映射后的列表
list: 映射后的列表进行从小到大排序得到的标准列表
"""
cmd_map = {"E": 1, "M": 2, "X": 3, "Z": 4, "S": 5}
cmd_num_wild = [cmd_map[cmd.name] for cmd in cmds]
cmd_num_standard = cmd_num_wild[:]
cmd_num_standard.sort(reverse=False)
cmds_count = [cmd_num_standard.count(i) for i in [1, 2, 3, 4, 5]] # Count each type of commands
return cmds_count, cmd_num_wild, cmd_num_standard
def __distance_to_standard(self, cmds):
r"""采用 Hamming 距离定义当前命令列表的顺序和标准顺序的距离函数。
Note:
这是内部方法,用户不需要直接调用到该方法。
Args:
cmds (list): 当前命令列表
"""
_, cmd_wild, cmd_std = self.__commands_to_numbers(cmds[:])
return sum([cmd_wild[i] == cmd_std[i] for i in range(len(cmd_wild))]) / len(cmd_wild)
def __is_standard(self, cmd_type, cmds):
r"""判断命令列表中指定的命令类型是否为标准顺序。
Note:
这是内部方法,用户不需要直接调用到该方法。
Args:
cmd_type (str): 待判断的命令名称,为 ``E``, ``M``, ``X``, ``Z`` 或 ``S``
cmds (list): 待判断的命令列表
Returns:
bool: 列表是否为标准列表的布尔值
"""
assert cmd_type in ["E", "M", "X", "Z", "S"], "command's name must be 'E', 'M', 'X', 'Z', or 'S'."
# Map the commands to numbers
cmds_count, cmd_num_wild, cmd_num_standard = self.__commands_to_numbers(cmds)
pointer_map = {"E": sum(cmds_count[:1]), # Number of commands E
"M": sum(cmds_count[:2]), # Number of commands E + M
"X": sum(cmds_count[:3]), # Number of commands E + M + X
"Z": sum(cmds_count[:4]), # Number of commands E + M + X + Z
"S": sum(cmds_count[:5])} # Number of commands E + M + X + Z + S
return cmd_num_wild[:pointer_map[cmd_type]] == cmd_num_standard[:pointer_map[cmd_type]]
def __simplify_pauli_measurements(self):
r"""对节点依赖性进行简化。
在某些特殊情形下,测量节点对其他节点的依赖性可以被简化。
设 \alpha 为不考虑依赖关系的测量角度,则测量角度实际上只有四种可能性,分别为:
.. math::
\theta_{\text{ad}} = \alpha
\theta_{\text{ad}} = \alpha + \pi
\theta_{\text{ad}} = - \alpha
\theta_{\text{ad}} = - \alpha + \pi
\text{当 } \alpha \text{ 为 } 0, \pi / 2, \pi, 3 \times \pi / 2 \text{ 时,该依赖关系可以简化。}
\text{例如 } \alpha = \pi \text{ 时,}\pm \alpha + t \times \pi \text{导致的测量效果一样,与 domain\_s 无关,因此 domain\_s 可以移除。其他情形同理。}
Note:
这是内部方法,用户不需要直接调用到该方法。
"""
for cmd in self.__pattern.commands:
if cmd.name == 'M': # Find CommandM
remainder = cmd.angle.numpy().item() % (2 * pi)
if remainder in [0, pi]:
cmd.domain_s = []
elif remainder in [pi / 2, (3 * pi) / 2]:
cmd.domain_t += cmd.domain_s[:]
cmd.domain_s = []
def standardize(self):
r"""对测量模式进行标准化。
该方法对测量模式进行标准化操作,转化成等价的 EMC 模型。即将所有的 ``CommandE`` 交换到最前面,其次是 ``CommandM``,
``CommandX`` 和 ``CommandZ``。为了简化测量模式,该方法在标准化各类命令之后还对 ``CommandM`` 进行 Pauli 简化。
"""
cmds = self.__pattern.commands
for cmd_type in ["E", "M", "X", "Z"]:
while not self.__is_standard(cmd_type, cmds):
cmds = self.__propagate_by_type(cmd_type, cmds)
print_progress(self.__distance_to_standard(cmds), "Standardization Progress", self.__track)
self.__pattern.commands = cmds
self.__simplify_pauli_measurements()
@staticmethod
def __pull_out_domain_t(cmds):
r"""在命令列表中把信号转移算符从测量算符中提取出来。
Note:
信号转移是一种特殊的操作,通过与其他命令算符的交换,解除测量命令的域 t 列表中节点的依赖性,
从而在某些情况下简化测量模式,详情请参见 [arXiv:0704.1263]。
Warning:
我们只提取 XY 平面测量算符的域 t 列表中节点的依赖性作为信号转移算符。对于 YZ 平面测量算符,我们并未采取此操作。
Args:
cmds (list): 命令列表
Returns:
list: 提取信号转移算符后的命令列表
"""
cmds_len = len(cmds)
for i in range(cmds_len - 1, -1, -1):
cmd = cmds[i]
if cmd.name == "M" and cmd.plane == 'XY':
signal_cmd = Pattern.CommandS(cmd.which_qubit, cmd.domain_t)
cmd.domain_t = []
cmds = cmds[:i] + [cmd, signal_cmd] + cmds[i + 1:]
return cmds
def shift_signals(self):
r"""信号转移操作。
Note:
这是用户选择性调用的方法之一。
"""
cmds = self.__pattern.commands
cmds = self.__pull_out_domain_t(cmds)
# Propagate CommandS
while not self.__is_standard("S", cmds):
cmds = self.__propagate_by_type("S", cmds)
print_progress(self.__distance_to_standard(cmds), "Signal Shifting Progress", self.__track)
# Kick out all the CommandS in the cmd list
cmds = [cmd for cmd in cmds if cmd.name != "S"]
self.__pattern.commands = cmds
def get_pattern(self):
r"""返回测量模式。
Returns:
Pattern: 处理后的测量模式
"""
return self.__pattern
@staticmethod
def __default_order_by_row(labels):
r"""按照行的顺序对节点标签列表进行排序并返回。
排序规则:不同行数,行数小的优先。相同行数,列数小的优先。
Note:
这是内部方法,用户不需要直接调用到该方法。
Args:
labels (list): 待处理的节点标签列表
Returns:
list: 排序后的节点标签列表
"""
# Construct a dict by string labels and their float values
labels_dict = {label: (div_str_to_float(label[0]), div_str_to_float(label[1])) for label in labels}
# Sort the dict by values (sort row first and then column)
sorted_dict = dict(sorted(labels_dict.items(), key=lambda item: item[1]))
# Extract the keys in the dict
labels_sorted = list(sorted_dict.keys())
return labels_sorted
def optimize_by_row(self):
r"""按照行序优先的原则对测量模式中的测量顺序进行优化。
Warning:
这是一种启发式的优化算法,对于特定的测量模式可以起到优化测量顺序的作用,不排除存在更优的测量顺序。例如,对于浅层量子电路,
按照行序优先原则,测量完同一量子位上的量子门、测量对应的节点后,该量子位不再起作用,进而减少后续计算时可能涉及到的节点数目。
"""
cmds = self.__pattern.commands
# Split the commands by type
cmdE_list = [cmd for cmd in cmds if cmd.name == "E"]
cmdM_list = [cmd for cmd in cmds if cmd.name == "M"]
cmdC_list = [cmd for cmd in cmds if cmd.name in ["X", "Z"]]
# Construct a dict from qubit labels and their measurement commands
cmdM_map = {cmd.which_qubit: cmd for cmd in cmdM_list}
# Sort all the qubit labels by row
cmdM_qubit_list = self.__default_order_by_row([cmdM.which_qubit for cmdM in cmdM_list])
mea_length = len(cmdM_qubit_list)
for i in range(mea_length):
optimal = False
while not optimal: # If the qubits list is not standard
# Slice measurement qubit list into three parts
measured = cmdM_qubit_list[:i]
measuring = cmdM_qubit_list[i]
to_measure = cmdM_qubit_list[i + 1:]
domains = set(cmdM_map[measuring].domain_s + cmdM_map[measuring].domain_t)
# Find the qubits in domain but not in front of the current measurement
push = self.__default_order_by_row(list(domains.difference(measured)))
if push: # Remove qubits from the to_measure list and push it to the front
to_measure = [qubit for qubit in to_measure if qubit not in push]
cmdM_qubit_list = cmdM_qubit_list[:i] + push + [measuring] + to_measure
else: # If no push qubits then jump out of the while loop
optimal = True
print_progress((i + 1) / mea_length, "Measurement Opt. Progress", self.__track)
# Sort the measurement commands by the sorted qubit labels
cmdM_opt = [cmdM_map[which_qubit] for which_qubit in cmdM_qubit_list]
# Update pattern
cmds = cmdE_list + cmdM_opt + cmdC_list
self.__pattern.commands = cmds
# 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.
"""
此模块包含量子信息处理的常用对象,如量子态、量子电路、测量模式等。
"""
from numpy import log2, sqrt
from paddle import Tensor, to_tensor, t, conj, matmul
__all__ = [
"State",
"Circuit",
"Pattern"
]
class State:
r"""定义量子态。
Attributes:
vector (Tensor): 量子态的列向量
system (list): 量子态的系统标签列表
"""
def __init__(self, vector=None, system=None):
r"""构造函数,用于实例化一个 ``"State"`` 量子态对象。
Args:
vector (Tensor, optional): 量子态的列向量
system (list, optional): 量子态的系统标签列表
"""
if vector is None and system is None:
self.vector = to_tensor([1], dtype='float64') # A trivial state
self.system = []
self.length = 1 # Length of state vector
self.size = 0 # Number of qubits
elif vector is not None and system is not None:
assert isinstance(vector, Tensor), "'vector' should be a 'Tensor'."
assert vector.shape[0] >= 1 and vector.shape[1] == 1, "'vector' should be of shape [x, 1] with x >= 1."
assert isinstance(system, list), "'system' should be a list."
self.vector = vector
self.system = system
self.length = self.vector.shape[0] # Length of state vector
self.size = int(log2(self.length)) # Number of qubits
assert self.size == len(self.system), "dimension of vector and system do not match."
else:
raise ValueError("we should either input both 'vector' and 'system' or input nothing.")
self.state = [self.vector, self.system]
self.norm = sqrt(matmul(t(conj(self.vector)), self.vector).numpy())
def __str__(self):
r"""打印该 ``State`` 类的信息,便于用户查看。
"""
class_type_str = "State"
vector_str = str(self.vector.numpy())
system_str = str(self.system)
length_str = str(self.length)
size_str = str(self.size)
print_str = class_type_str + "(" + \
"size=" + size_str + ", " + \
"system=" + system_str + ", " + \
"length=" + length_str + ", " + \
"vector=\r\n" + vector_str + ")"
return print_str
class Circuit:
r"""定义量子电路。
Note:
该类与 ``UAnsatz`` 类似,用户可以仿照 ``UAnsatz`` 电路的调用方式对此类进行实例化,完成电路图的构建。
Warning:
当前版本仅支持 ``H, X, Y, Z, S, T, Rx, Ry, Rz, Rz_5, U, CNOT, CNOT_15, CZ`` 中的量子门以及测量操作。
Attributes:
width (int): 电路的宽度(比特数)
"""
def __init__(self, width):
r"""``Circuit`` 的构造函数,用于实例化一个 ``Circuit`` 对象。
Args:
width (int): 电路的宽度(比特数)
"""
assert isinstance(width, int), "circuit 'width' must be a int."
self.__history = [] # A list to record the circuit information
self.__measured_qubits = [] # A list to record the measurement indices in the circuit
self.__width = width # The width of circuit
def h(self, which_qubit):
r"""添加 ``Hadamard`` 门。
其矩阵形式为:
.. math::
H = \frac{1}{\sqrt{2}}\begin{bmatrix} 1&1\\1&-1 \end{bmatrix}
Args:
which_qubit (int): 作用量子门的量子位编号
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.qobject import Circuit
width = 1
cir = Circuit(width)
which_qubit = 0
cir.h(which_qubit)
print(cir.get_circuit())
::
[['h', [0], None]]
"""
assert isinstance(which_qubit, int), "'which_qubit' must be a 'int'."
assert 0 <= which_qubit < self.__width, "'which_qubit' must be a int between zero and circuit width."
assert which_qubit not in self.__measured_qubits, "this qubit has already been measured."
self.__history.append(['h', [which_qubit], None])
def x(self, which_qubit):
r"""添加 ``Pauli X`` 门。
其矩阵形式为:
.. math::
\begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix}
Args:
which_qubit (int): 作用量子门的量子位编号
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.qobject import Circuit
width = 1
cir = Circuit(width)
which_qubit = 0
cir.x(which_qubit)
print(cir.get_circuit())
::
[['x', [0], None]]
"""
assert isinstance(which_qubit, int), "'which_qubit' must be a 'int'."
assert 0 <= which_qubit < self.__width, "'which_qubit' must be a int between zero and circuit width."
assert which_qubit not in self.__measured_qubits, "this qubit has already been measured."
self.__history.append(['x', [which_qubit], None])
def y(self, which_qubit):
r"""添加 ``Pauli Y`` 门。
其矩阵形式为:
.. math::
\begin{bmatrix} 0 & -i \\ i & 0 \end{bmatrix}
Args:
which_qubit (int): 作用量子门的量子位编号
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.qobject import Circuit
width = 1
cir = Circuit(width)
which_qubit = 0
cir.y(which_qubit)
print(cir.get_circuit())
::
[['y', [0], None]]
"""
assert isinstance(which_qubit, int), "'which_qubit' must be a 'int'."
assert 0 <= which_qubit < self.__width, "'which_qubit' must be a int between zero and circuit width."
assert which_qubit not in self.__measured_qubits, "this qubit has already been measured."
self.__history.append(['y', [which_qubit], None])
def z(self, which_qubit):
r"""添加 ``Pauli Z`` 门。
其矩阵形式为:
.. math::
\begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix}
Args:
which_qubit (int): 作用量子门的量子位编号
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.qobject import Circuit
width = 1
cir = Circuit(width)
which_qubit = 0
cir.z(which_qubit)
print(cir.get_circuit())
::
[['z', [0], None]]
"""
assert isinstance(which_qubit, int), "'which_qubit' must be a 'int'."
assert 0 <= which_qubit < self.__width, "'which_qubit' must be a int between zero and circuit width."
assert which_qubit not in self.__measured_qubits, "this qubit has already been measured."
self.__history.append(['z', [which_qubit], None])
def s(self, which_qubit):
r"""添加 ``S`` 门。
其矩阵形式为:
.. math::
T = \begin{bmatrix} 1&0\\0& i} \end{bmatrix}
Args:
which_qubit (int): 作用量子门的量子位编号
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.qobject import Circuit
width = 1
cir = Circuit(width)
which_qubit = 0
cir.s(which_qubit)
print(cir.get_circuit())
::
[['s', [0], None]]
"""
assert isinstance(which_qubit, int), "'which_qubit' must be a 'int'."
assert 0 <= which_qubit < self.__width, "'which_qubit' must be a int between zero and circuit width."
assert which_qubit not in self.__measured_qubits, "this qubit has already been measured."
self.__history.append(['s', [which_qubit], None])
def t(self, which_qubit):
r"""添加 ``T`` 门。
其矩阵形式为:
.. math::
T = \begin{bmatrix} 1&0\\0& e^{i\pi/ 4} \end{bmatrix}
Args:
which_qubit (int): 作用量子门的量子位编号
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.qobject import Circuit
width = 1
cir = Circuit(width)
which_qubit = 0
cir.t(which_qubit)
print(cir.get_circuit())
::
[['t', [0], None]]
"""
assert isinstance(which_qubit, int), "'which_qubit' must be a 'int'."
assert 0 <= which_qubit < self.__width, "'which_qubit' must be a int between zero and circuit width."
assert which_qubit not in self.__measured_qubits, "this qubit has already been measured."
self.__history.append(['t', [which_qubit], None])
def rx(self, theta, which_qubit):
r"""添加关于 x 轴的旋转门。
其矩阵形式为:
.. math::
\begin{bmatrix} \cos\frac{\theta}{2} & -i\sin\frac{\theta}{2} \\ -i\sin\frac{\theta}{2} & \cos\frac{\theta}{2} \end{bmatrix}
Args:
theta (Tensor): 旋转角度
which_qubit (int): 作用量子门的量子位编号
代码示例:
.. code-block:: python
from paddle import to_tensor
from paddle_quantum.mbqc.qobject import Circuit
width = 1
cir = Circuit(width)
which_qubit = 0
angle = to_tensor([1], dtype='float64')
cir.rx(angle, which_qubit)
print(cir.get_circuit())
::
[['rx', [0], Tensor(shape=[1], dtype=float64, place=CPUPlace, stop_gradient=True, [1.])]]
"""
assert isinstance(theta, Tensor) and theta.shape == [1], "'theta' must be a 'Tensor' of shape [1]."
assert isinstance(which_qubit, int), "'which_qubit' must be a 'int'."
assert 0 <= which_qubit < self.__width, "'which_qubit' must be a int between zero and circuit width."
assert which_qubit not in self.__measured_qubits, "this qubit has already been measured."
self.__history.append(['rx', [which_qubit], theta])
def ry(self, theta, which_qubit):
r"""添加关于 y 轴的旋转门。
其矩阵形式为:
.. math::
\begin{bmatrix} \cos\frac{\theta}{2} & -\sin\frac{\theta}{2} \\ \sin\frac{\theta}{2} & \cos\frac{\theta}{2} \end{bmatrix}
Args:
theta (Tensor): 旋转角度
which_qubit (int): 作用量子门的量子位编号
代码示例:
.. code-block:: python
from paddle import to_tensor
from paddle_quantum.mbqc.qobject import Circuit
width = 1
cir = Circuit(width)
which_qubit = 0
angle = to_tensor([1], dtype='float64')
cir.ry(angle, which_qubit)
print(cir.get_circuit())
::
[['ry', [0], Tensor(shape=[1], dtype=float64, place=CPUPlace, stop_gradient=True, [1.])]]
"""
assert isinstance(theta, Tensor) and theta.shape == [1], "'theta' must be a 'Tensor' of shape [1]."
assert isinstance(which_qubit, int), "'which_qubit' must be a 'int'."
assert 0 <= which_qubit < self.__width, "'which_qubit' must be a int between zero and circuit width."
assert which_qubit not in self.__measured_qubits, "this qubit has already been measured."
self.__history.append(['ry', [which_qubit], theta])
def rz(self, theta, which_qubit):
r"""添加关于 z 轴的旋转门。
其矩阵形式为:
.. math::
\begin{bmatrix} 1 & 0 \\ 0 & e^{i\theta} \end{bmatrix}
Args:
theta (Tensor): 旋转角度
which_qubit (int): 作用量子门的量子位编号
代码示例:
.. code-block:: python
from paddle import to_tensor
from paddle_quantum.mbqc.qobject import Circuit
width = 1
cir = Circuit(width)
which_qubit = 0
angle = to_tensor([1], dtype='float64')
cir.rz(angle, which_qubit)
print(cir.get_circuit())
::
[['rz', [0], Tensor(shape=[1], dtype=float64, place=CPUPlace, stop_gradient=True, [1.])]]
"""
assert isinstance(theta, Tensor) and theta.shape == [1], "'theta' must be a 'Tensor' of shape [1]."
assert isinstance(which_qubit, int), "'which_qubit' must be a 'int'."
assert 0 <= which_qubit < self.__width, "'which_qubit' must be a int between zero and circuit width."
assert which_qubit not in self.__measured_qubits, "this qubit has already been measured."
self.__history.append(['rz', [which_qubit], theta])
def rz_5(self, theta, which_qubit):
r"""添加关于 z 轴的旋转门(该旋转门对应的测量模式由五个量子比特构成)。
其矩阵形式为:
.. math::
\begin{bmatrix} 1 & 0 \\ 0 & e^{i\theta} \end{bmatrix}
Args:
theta (Tensor): 旋转角度
which_qubit (int): 作用量子门的量子位编号
代码示例:
.. code-block:: python
from paddle import to_tensor
from paddle_quantum.mbqc.qobject import Circuit
width = 1
cir = Circuit(width)
which_qubit = 0
angle = to_tensor([1], dtype='float64')
cir.rz(angle, which_qubit)
print(cir.get_circuit())
::
[['rz_5', [0], Tensor(shape=[1], dtype=float64, place=CPUPlace, stop_gradient=True, [1.])]]
"""
assert isinstance(theta, Tensor) and theta.shape == [1], "'theta' must be a 'Tensor' of shape [1]."
assert isinstance(which_qubit, int), "'which_qubit' must be a 'int'."
assert 0 <= which_qubit < self.__width, "'which_qubit' must be a int between zero and circuit width."
assert which_qubit not in self.__measured_qubits, "this qubit has already been measured."
self.__history.append(['rz_5', [which_qubit], theta])
def u(self, params, which_qubit):
r"""添加单量子比特的任意酉门。
Warning:
与 ``UAnsatz`` 类中的 U3 的三个参数不同,这里的酉门采用 ``Rz Rx Rz`` 分解,
其分解形式为:
.. math::
U(\alpha, \beta, \gamma) = Rz(\gamma) Rx(\beta) Rz(\alpha)
Args:
params (list): 单比特酉门的三个旋转角度
which_qubit (int): 作用量子门的量子位编号
代码示例:
.. code-block:: python
from paddle import to_tensor
from numpy import pi
from paddle_quantum.mbqc.qobject import Circuit
width = 1
cir = Circuit(width)
which_qubit = 0
alpha = to_tensor([pi / 2], dtype='float64')
beta = to_tensor([pi], dtype='float64')
gamma = to_tensor([- pi / 2], dtype='float64')
cir.u([alpha, beta, gamma], which_qubit)
print(cir.get_circuit())
::
[['u', [0], [Tensor(shape=[1], dtype=float64, place=CPUPlace, stop_gradient=True,
[1.57079633]), Tensor(shape=[1], dtype=float64, place=CPUPlace, stop_gradient=True,
[3.14159265]), Tensor(shape=[1], dtype=float64, place=CPUPlace, stop_gradient=True,
[-1.57079633])]]]
"""
assert isinstance(params, list) and len(params) == 3, "'params' must be a list of length 3."
assert all([isinstance(par, Tensor) and par.shape == [1] for par in params]), \
"item in 'params' must be 'Tensor' of shape [1]."
assert isinstance(which_qubit, int), "'which_qubit' must be a 'int'."
assert 0 <= which_qubit < self.__width, "'which_qubit' must be a int between zero and circuit width."
assert which_qubit not in self.__measured_qubits, "this qubit has already been measured."
self.__history.append(['u', [which_qubit], params])
def cnot(self, which_qubits):
r"""添加控制非门。
当 ``which_qubits`` 为 ``[0, 1]`` 时,其矩阵形式为:
.. math::
\begin{align}
CNOT &=|0\rangle \langle 0|\otimes I + |1 \rangle \langle 1|\otimes X\\
&=\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \end{bmatrix}
\end{align}
Args:
which_qubits (list): 作用量子门的量子位,其中列表第一个元素为控制位,第二个元素为受控位
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.qobject import Circuit
width = 2
cir = Circuit(width)
which_qubits = [0, 1]
cir.cnot(which_qubits)
print(cir.get_circuit())
::
[['cnot', [0, 1], None]]
"""
assert isinstance(which_qubits[0], int) and isinstance(which_qubits[1], int), \
"items in 'which_qubits' must be of type 'int'."
assert 0 <= which_qubits[0] < self.__width and 0 <= which_qubits[1] < self.__width, \
"items in 'which_qubits' must be between zero and circuit width."
assert which_qubits[0] != which_qubits[1], \
"control qubit must not be the same as the target qubit."
assert which_qubits[0] not in self.__measured_qubits and which_qubits[1] not in self.__measured_qubits, \
"one of the qubits has already been measured."
self.__history.append(['cnot', which_qubits, None])
def cnot_15(self, which_qubits):
r"""添加控制非门 (该门对应的测量模式由十五个量子比特构成)。
当 ``which_qubits`` 为 ``[0, 1]`` 时,其矩阵形式为:
.. math::
\begin{align}
CNOT &=|0\rangle \langle 0|\otimes I + |1 \rangle \langle 1|\otimes X\\
&=\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \end{bmatrix}
\end{align}
Args:
which_qubits (list): 作用量子门的量子位,其中列表第一个元素为控制位,第二个元素为受控位
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.qobject import Circuit
width = 2
cir = Circuit(width)
which_qubits = [0, 1]
cir.cnot_15(which_qubits)
print(cir.get_circuit())
::
[['cnot_15', [0, 1], None]]
"""
assert isinstance(which_qubits[0], int) and isinstance(which_qubits[1], int), \
"items in 'which_qubits' must be of type 'int'."
assert 0 <= which_qubits[0] < self.__width and 0 <= which_qubits[1] < self.__width, \
"items in 'which_qubits' must be between zero and circuit width."
assert which_qubits[0] != which_qubits[1], \
"control qubit must not be the same as the target qubit."
assert which_qubits[0] not in self.__measured_qubits and which_qubits[1] not in self.__measured_qubits, \
"one of the qubits has already been measured."
self.__history.append(['cnot_15', which_qubits, None])
def cz(self, which_qubits):
r"""添加控制 Z 门。
当 ``which_qubits`` 为 ``[0, 1]`` 时,其矩阵形式为:
.. math::
\begin{align}
CZ &=|0\rangle \langle 0|\otimes I + |1 \rangle \langle 1|\otimes Z\\
&=\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & -1 \end{bmatrix}
\end{align}
Args:
which_qubits (list): 作用量子门的量子位,其中列表第一个元素为控制位,第二个元素为受控位
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.qobject import Circuit
width = 2
cir = Circuit(width)
which_qubits = [0, 1]
cir.cz(which_qubits)
print(cir.get_circuit())
::
[['cz', [0, 1], None]]
"""
assert isinstance(which_qubits[0], int) and isinstance(which_qubits[1], int), \
"items in 'which_qubits' must be of type 'int'."
assert 0 <= which_qubits[0] < self.__width and 0 <= which_qubits[1] < self.__width, \
"items in 'which_qubits' must be between zero and circuit width."
assert which_qubits[0] != which_qubits[1], \
"control qubit must not be the same as the target qubit."
assert which_qubits[0] not in self.__measured_qubits and which_qubits[1] not in self.__measured_qubits, \
"one of the qubits has already been measured."
self.__history.append(['cz', which_qubits, None])
def measure(self, which_qubit=None, basis_list=None):
r"""对量子电路输出的量子态进行测量。
Note:
与 ``UAnsatz`` 类中的测量不同,除默认的 Z 测量外,此处的测量方式可以由用户自定义,但需要将测量方式与测量比特相对应。
Warning:
此方法只接受三种输入方式:
1. 不输入任何参数,表示对所有的量子位进行 Z 测量;
2. 输入量子位,但不输入测量基,表示对输入的量子位进行 Z 测量;
3. 输入量子位和对应测量基,表示对输入量子位进行指定的测量。
如果用户希望自定义测量基参数,需要注意输入格式为 ``[angle, plane, domain_s, domain_t]``,
且当前版本的测量平面 ``plane`` 只能支持 ``XY`` 或 ``YZ``。
Args:
which_qubit (int, optional): 被测量的量子位
basis_list (list, optional): 测量方式
"""
# Measure all the qubits by Z measurement
if which_qubit is None and basis_list is None:
# Set Z measurement by default
basis_list = [to_tensor([0], dtype='float64'), 'YZ', [], []]
for which_qubit in range(self.__width):
assert which_qubit not in self.__measured_qubits, "this qubit has already been measured."
self.__measured_qubits.append(which_qubit)
self.__history.append(['m', [which_qubit], basis_list])
# Measure the referred qubit by Z measurement
elif which_qubit is not None and basis_list is None:
assert isinstance(which_qubit, int), "'which_qubit' must be a 'int'."
assert 0 <= which_qubit < self.__width, "'which_qubit' must be a int between zero and circuit width."
assert which_qubit not in self.__measured_qubits, "this qubit has already been measured."
# Set Z measurement as default
basis_list = [to_tensor([0], dtype='float64'), 'YZ', [], []]
self.__measured_qubits.append(which_qubit)
self.__history.append(['m', [which_qubit], basis_list])
# Measure the referred qubit by the customized basis
elif which_qubit is not None and basis_list is not None:
assert isinstance(basis_list, list) and len(basis_list) == 4, \
"'basis_list' must be a list of length 4."
assert isinstance(basis_list[0], Tensor) and basis_list[0].shape == [1], \
"measurement angle must be a 'Tensor' of shape [1]."
assert basis_list[1] in ["XY", "YZ"], "measurement plane must be 'XY' or 'YZ'."
self.__measured_qubits.append(which_qubit)
self.__history.append(['m', [which_qubit], basis_list])
else:
raise ValueError("such a combination of input parameters is not supported. Please see our API for details.")
def is_valid(self):
r"""检查输入的量子电路是否符合规定。
我们规定输入的量子电路中,每一个量子位上至少作用一个量子门。
Returns:
bool: 量子电路是否符合规定的布尔值
"""
all_qubits = []
for gate in self.__history:
if gate[0] != 'm':
all_qubits += gate[1]
effective_qubits = list(set(all_qubits))
return self.__width == len(effective_qubits)
def get_width(self):
r"""返回量子电路的宽度。
Returns:
int: 量子电路的宽度
"""
return self.__width
def get_circuit(self):
r"""返回量子电路列表。
Returns:
list: 量子电路列表
"""
return self.__history
def get_measured_qubits(self):
r"""返回量子电路中测量的比特位。
Returns:
list: 量子电路中测量的比特位列表
"""
return self.__measured_qubits
def print_circuit_list(self):
r"""打印电路图的列表。
Returns:
string: 用来打印的字符串
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.qobject import Circuit
from paddle import to_tensor
from numpy import pi
n = 2
theta = to_tensor([pi], dtype="float64")
cir = Circuit(n)
cir.h(0)
cir.cnot([0, 1])
cir.rx(theta, 1)
cir.measure()
cir.print_circuit_list()
::
--------------------------------------------------
Current circuit
--------------------------------------------------
Gate Name Qubit Index Parameter
--------------------------------------------------
h [0] None
cnot [0, 1] None
rx [1] 3.141592653589793
m [0] [0.0, 'YZ', [], []]
m [1] [0.0, 'YZ', [], []]
--------------------------------------------------
"""
print("--------------------------------------------------")
print(" Current circuit ")
print("--------------------------------------------------")
print("Gate Name".ljust(16) + "Qubit Index".ljust(16) + "Parameter".ljust(16))
print("--------------------------------------------------")
for gate in self.__history:
name = gate[0]
which_qubits = gate[1]
parameters = gate[2]
if isinstance(parameters, Tensor):
par_show = parameters.numpy().item()
elif name == 'm':
par_show = [parameters[0].numpy().item()] + parameters[1:]
else:
par_show = parameters
print(str(name).ljust(16) + str(which_qubits).ljust(16) + str(par_show).ljust(16))
print("--------------------------------------------------")
class Pattern:
r"""定义测量模式。
该测量模式的结构依据文献 [The measurement calculus, arXiv: 0704.1263]。
Attributes:
name (str): 测量模式的名称
space (list): 测量模式所有节点列表
input_ (list): 测量模式的输入节点列表
output_ (list): 测量模式的输出节点列表
commands (list): 测量模式的命令列表
"""
def __init__(self, name, space, input_, output_, commands):
r"""构造函数,用于实例化一个 ``Pattern`` 对象。
Args:
name (str): 测量模式的名称
space (list): 测量模式所有节点列表
input_ (list): 测量模式的输入节点列表
output_ (list): 测量模式的输出节点列表
commands (list): 测量模式的命令列表
"""
self.name = name
self.space = space
self.input_ = input_
self.output_ = output_
self.commands = commands
class CommandE:
r"""定义纠缠命令类。
Note:
此处纠缠命令对应作用控制 Z 门。
Attributes:
which_qubits (list): 作用纠缠命令的两个节点标签构成的列表
"""
def __init__(self, which_qubits):
r"""纠缠命令类构造函数,用于实例化一个 ``CommandE`` 对象。
Args:
which_qubits (list): 作用纠缠命令的两个节点标签构成的列表
"""
self.name = "E"
self.which_qubits = which_qubits
class CommandM:
r"""定义测量命令类。
测量命令有五个属性,分别为测量比特的标签 ``which_qubit``,原始的测量角度 ``angle``,
测量平面 ``plane``,域 s 对应的节点标签列表 ``domain_s``,域 t 对应的节点标签列表 ``domain_t``。
设原始角度为 :math:`\alpha`,则考虑域中节点依赖关系后的测量角度 :math:`\theta` 为:
.. math::
\theta = (-1)^s \times \alpha + t \times \pi
Note:
域 s 和域 t 是 MBQC 模型中的概念,分别记录了 Pauli X 算符和 Pauli Z 算符对测量角度产生的影响,
二者共同记录了该测量节点对其他节点的测量结果的依赖关系。
Warning:
该命令当前只支持 XY 和 YZ 平面的测量。
Attributes:
which_qubit (any): 作用测量命令的节点标签
angle (Tensor): 原始的测量角度
plane (str): 测量平面
domain_s (list): 域 s 对应的节点标签列表
domain_t (list): 域 t 对应的节点标签列表
"""
def __init__(self, which_qubit, angle, plane, domain_s, domain_t):
r"""构造函数,用于实例化一个 ``CommandM`` 对象。
Args:
which_qubit (any): 作用测量命令的节点标签
angle (Tensor): 原始的测量角度
plane (str): 测量平面
domain_s (list): 域 s 对应的节点标签列表
domain_t (list): 域 t 对应的节点标签列表
"""
self.name = "M"
self.which_qubit = which_qubit
self.angle = angle
self.plane = plane
self.domain_s = domain_s
self.domain_t = domain_t
class CommandX:
r"""定义 Pauli X 副产品修正命令类。
Attributes:
which_qubit (any): 作用修正算符的节点标签
domain (list): 依赖关系列表
"""
def __init__(self, which_qubit, domain):
r"""构造函数,用于实例化一个 ``CommandX`` 对象。
Args:
which_qubit (any): 作用修正算符的节点标签
domain (list): 依赖关系列表
"""
self.name = "X"
self.which_qubit = which_qubit
self.domain = domain
class CommandZ:
r"""定义 Pauli Z 副产品修正命令。
Attributes:
which_qubit (any): 作用修正命令的节点标签
domain (list): 依赖关系列表
"""
def __init__(self, which_qubit, domain):
r"""构造函数,用于实例化一个 ``CommandZ`` 对象。
Args:
which_qubit (any): 作用修正命令的节点标签
domain (list): 依赖关系列表
"""
self.name = "Z"
self.which_qubit = which_qubit
self.domain = domain
class CommandS:
r"""定义 "信号转移" 命令类。
Note:
"信号转移" 是一类特殊的操作,用于消除测量命令对域 t 中节点的依赖关系,在某些情况下对测量模式进行简化。
Attributes:
which_qubit (any): 消除依赖关系的测量命令作用的节点标签
domain (list): 依赖关系列表
"""
def __init__(self, which_qubit, domain):
r"""构造函数,用于实例化一个 ``CommandS`` 对象。
Args:
which_qubit (any): 消除依赖关系的测量命令作用的节点标签
domain (list): 依赖关系列表
"""
self.name = "S"
self.which_qubit = which_qubit
self.domain = domain
def print_command_list(self):
r"""打印该 ``Pattern`` 类中的命令的信息,便于用户查看。
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.qobject import Circuit
from paddle_quantum.mbqc.mcalculus import MCalculus
n = 1
cir = Circuit(n)
cir.h(0)
pat = MCalculus()
pat.set_circuit(cir)
pattern = pat.get_pattern()
pattern.print_command_list()
::
-----------------------------------------------------------
Current Command List
-----------------------------------------------------------
Command: E
which_qubits: [('0/1', '0/1'), ('0/1', '1/1')]
-----------------------------------------------------------
Command: M
which_qubit: ('0/1', '0/1')
plane: XY
angle: 0.0
domain_s: []
domain_t: []
-----------------------------------------------------------
Command: X
which_qubit: ('0/1', '1/1')
domain: [('0/1', '0/1')]
-----------------------------------------------------------
"""
print("-----------------------------------------------------------")
print(" Current Command List ")
print("-----------------------------------------------------------")
# Print commands list
for cmd in self.commands:
print('\033[91m' + "Command:".ljust(16) + cmd.name + '\033[0m')
if cmd.name == "E":
print("which_qubits:".ljust(15), cmd.which_qubits)
elif cmd.name == "M":
print("which_qubit:".ljust(15), cmd.which_qubit)
print("plane:".ljust(15), cmd.plane)
print("angle:".ljust(15), cmd.angle.numpy().item())
print("domain_s:".ljust(15), cmd.domain_s)
print("domain_t:".ljust(15), cmd.domain_t)
else:
print("which_qubit:".ljust(15), cmd.which_qubit)
print("domain:".ljust(15), cmd.domain)
print("-----------------------------------------------------------")
# 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.
"""
此模块包含构造 MBQC 模型的常用类和配套的运算模拟工具。
"""
from numpy import random, pi
from networkx import Graph, spring_layout, draw_networkx
import matplotlib.pyplot as plt
from paddle import t, to_tensor, matmul, conj, real, reshape, multiply
from paddle_quantum.mbqc.utils import plus_state, cz_gate, pauli_gate
from paddle_quantum.mbqc.utils import basis, kron, div_str_to_float
from paddle_quantum.mbqc.utils import permute_to_front, permute_systems, print_progress, plot_results
from paddle_quantum.mbqc.qobject import State, Pattern
from paddle_quantum.mbqc.transpiler import transpile
__all__ = [
"MBQC",
"simulate_by_mbqc"
]
class MBQC:
r"""定义基于测量的量子计算模型 ``MBQC`` 类。
用户可以通过实例化该类来定义自己的 MBQC 模型。
"""
def __init__(self):
r"""MBQC 类的构造函数,用于实例化一个 ``MBQC`` 对象。
"""
self.__graph = None # Graph in a MBQC model
self.__pattern = None # Measurement pattern in a MBQC model
self.__bg_state = State() # Background state of computation
self.__history = [self.__bg_state] # History of background states
self.__status = self.__history[-1] if self.__history != [] else None # latest history item
self.vertex = None # Vertex class to maintain all the vertices
self.__outcome = {} # Dictionary to store all measurement outcomes
self.max_active = 0 # Maximum number of active vertices so far
self.__draw = False # Switch to draw the dynamical running process
self.__track = False # Switch to track the running progress
self.__pause_time = None # Pause time for drawing
self.__pos = None # Position for drawing
class Vertex:
r"""定义维护点列表,用于实例化一个 ``Vertex`` 对象。
将 MBQC 算法中图的节点分为三类,并进行动态维护。
Note:
这是内部类,用户不需要直接调用到该类。
Attributes:
total (list): MBQC 算法中图上的全部节点,不随运算而改变
pending (list): 待激活的节点,随着运算的执行而逐渐减少
active (list): 激活的节点,与当前测量步骤直接相关的节点
measured (list): 已被测量过的节点,随着运算的执行而逐渐增加
"""
def __init__(self, total=None, pending=None, active=None, measured=None):
r"""``Vertex`` 类的构造函数,用于实例化一个 ``Vertex`` 对象。
Args:
total (list): MBQC 算法中图上的全部节点,不随运算而改变
pending (list): 待激活的节点,随着运算的执行而逐渐减少
active (list): 激活的节点,与当前测量步骤直接相关的节点
measured (list): 已被测量过的节点,随着运算的执行而逐渐增加
"""
self.total = [] if total is None else total
self.pending = [] if pending is None else pending
self.active = [] if active is None else active
self.measured = [] if measured is None else measured
def set_graph(self, graph):
r"""设置 MBQC 模型中的图。
该函数用于将用户自己构造的图传递给 ``MBQC`` 实例。
Args:
graph (list): MBQC 模型中的图,由列表 ``[V, E]`` 给出, 其中 ``V`` 为节点列表,``E`` 为边列表
"""
vertices, edges = graph
vertices_of_edges = set([vertex for edge in edges for vertex in list(edge)])
assert vertices_of_edges.issubset(vertices), "edge must be between the graph vertices."
self.__graph = Graph()
self.__graph.add_nodes_from(vertices)
self.__graph.add_edges_from(edges)
self.vertex = self.Vertex(total=vertices, pending=vertices, active=[], measured=[])
def get_graph(self):
r"""获取图的信息。
Returns:
nx.Graph: 图
"""
return self.__graph
def set_pattern(self, pattern):
r"""设置 MBQC 模型的测量模式。
该函数用于将用户由电路图翻译得到或自己构造的测量模式传递给 ``MBQC`` 实例。
Warning:
输入的 pattern 参数是 ``Pattern`` 类型,其中命令列表为标准 ``EMC`` 命令。
Args:
pattern (Pattern): MBQC 算法对应的测量模式
"""
assert isinstance(pattern, Pattern), "please input a pattern of type 'Pattern'."
self.__pattern = pattern
cmds = self.__pattern.commands[:]
# Check if the pattern is a standard EMC form
cmd_map = {"E": 1, "M": 2, "X": 3, "Z": 4, "S": 5}
cmd_num_wild = [cmd_map[cmd.name] for cmd in cmds]
cmd_num_standard = cmd_num_wild[:]
cmd_num_standard.sort(reverse=False)
assert cmd_num_wild == cmd_num_standard, "input pattern is not a standard EMC form."
# Set graph by entanglement commands
edges = [tuple(cmd.which_qubits) for cmd in cmds if cmd.name == "E"]
vertices = list(set([vertex for edge in edges for vertex in list(edge)]))
graph = [vertices, edges]
self.set_graph(graph)
def get_pattern(self):
r"""获取测量模式的信息。
Returns:
Pattern: 测量模式
"""
return self.__pattern
def set_input_state(self, state=None):
r"""设置需要替换的输入量子态。
Warning:
与电路模型不同,MBQC 模型通常默认初始态为加态。如果用户不调用此方法设置初始量子态,则默认为加态。
如果用户以测量模式运行 MBQC,则此处输入量子态的系统标签会被限制为从零开始的自然数,类型为整型。
Args:
state (State): 需要替换的量子态,默认为加态
"""
assert self.__graph is not None, "please set 'graph' or 'pattern' before calling 'set_input_state'."
assert isinstance(state, State) or state is None, "please input a state of type 'State'."
vertices = list(self.__graph.nodes)
if state is None:
vector = plus_state()
system = [vertices[0]] # Activate the first vertex, system should be a list
else:
vector = state.vector
# If a pattern is set, map the input state system to the pattern's input
if self.__pattern is not None:
assert all(isinstance(label, int) for label in state.system), "please input system labels of type 'int'"
assert all(label >= 0 for label in state.system), "please input system labels with non-negative values"
system = [label for label in self.__pattern.input_ if int(div_str_to_float(label[0])) in state.system]
else:
system = state.system
assert set(system).issubset(vertices), "input system labels must be a subset of graph vertices."
self.__bg_state = State(vector, system)
self.__history = [self.__bg_state]
self.__status = self.__history[-1]
self.vertex = self.Vertex(total=vertices,
pending=list(set(vertices).difference(system)),
active=system,
measured=[])
self.max_active = len(self.vertex.active)
def __set_position(self, pos):
r"""设置动态过程图绘制时节点的位置坐标。
Note:
这是内部方法,用户并不需要直接调用到该方法。
Args:
pos (dict or bool, optional): 节点坐标的字典数据或者内置的坐标选择,
内置的坐标选择有:``True`` 为测量模式自带的坐标,``False`` 为 ``spring_layout`` 坐标
"""
assert isinstance(pos, bool) or isinstance(pos, dict), "'pos' should be either bool or dict."
if isinstance(pos, dict):
self.__pos = pos
elif pos:
assert self.__pattern is not None, "'pos=True' must be chosen after a pattern is set."
self.__pos = {v: [div_str_to_float(v[1]), - div_str_to_float(v[0])] for v in list(self.__graph.nodes)}
else:
self.__pos = spring_layout(self.__graph) # Use 'spring_layout' otherwise
def __draw_process(self, which_process, which_qubit):
r"""根据当前节点状态绘图,用以实时展示 MBQC 模型的模拟计算过程。
Note:
这是内部方法,用户并不需要直接调用到该方法。
Args:
which_process (str): MBQC 执行的阶段,"measuring", "active" 或者 "measured"
which_qubit (any): 当前关注的节点,可以是 ``str``, ``tuple`` 等任意数据类型,但需要和图的标签类型匹配
"""
if self.__draw:
assert which_process in ["measuring", "active", "measured"]
assert which_qubit in self.vertex.total, "'which_qubit' must be in the graph."
vertex_sets = []
# Find where the 'which_qubit' is
if which_qubit in self.vertex.pending:
pending = self.vertex.pending[:]
pending.remove(which_qubit)
vertex_sets = [pending, self.vertex.active, [which_qubit], self.vertex.measured]
elif which_qubit in self.vertex.active:
active = self.vertex.active[:]
active.remove(which_qubit)
vertex_sets = [self.vertex.pending, active, [which_qubit], self.vertex.measured]
elif which_qubit in self.vertex.measured:
vertex_sets = [self.vertex.pending, self.vertex.active, [], self.vertex.measured]
# Indentify ancilla vertices
ancilla_qubits = []
if self.__pattern is not None:
for vertex in list(self.__graph.nodes):
row_coordinate = div_str_to_float(vertex[0])
col_coordinate = div_str_to_float(vertex[1])
# Ancilla vertices do not have integer coordinates
if abs(col_coordinate - int(col_coordinate)) >= 1e-15 \
or abs(row_coordinate - int(row_coordinate)) >= 1e-15:
ancilla_qubits.append(vertex)
plt.cla()
plt.title("MBQC Running Process", fontsize=15)
plt.xlabel("Measuring (RED) Active (GREEN) Pending (BLUE) Measured (GRAY)", fontsize=12)
plt.grid()
mngr = plt.get_current_fig_manager()
mngr.window.setGeometry(500, 100, 800, 600)
colors = ['tab:blue', 'tab:green', 'tab:red', 'tab:gray']
for j in range(4):
for vertex in vertex_sets[j]:
options = {
"nodelist": [vertex],
"node_color": colors[j],
"node_shape": '8' if vertex in ancilla_qubits else 'o',
"with_labels": False,
"width": 3,
}
draw_networkx(self.__graph, self.__pos, **options)
ax = plt.gca()
ax.margins(0.20)
plt.axis("on")
ax.set_axisbelow(True)
plt.pause(self.__pause_time)
def draw_process(self, draw=True, pos=False, pause_time=0.5):
r"""动态过程图绘制,用以实时展示 MBQC 模型的模拟计算过程。
Args:
draw (bool, optional): 是否绘制动态过程图的布尔开关
pos (bool or dict, optional): 节点坐标的字典数据或者内置的坐标选择,内置的坐标选择有:
``True`` 为测量模式自带的坐标,``False`` 为 `spring_layout` 坐标
pause_time (float, optional): 绘制动态过程图时每次更新的停顿时间
"""
assert self.__graph is not None, "please set 'graph' or 'pattern' before calling 'draw_process'."
assert isinstance(draw, bool), "'draw' must be bool."
assert isinstance(pos, bool) or isinstance(pos, dict), "'pos' should be either bool or dict."
assert pause_time > 0, "'pause_time' must be strictly larger than 0."
self.__draw = draw
self.__pause_time = pause_time
if self.__draw:
plt.figure()
plt.ion()
self.__set_position(pos)
def track_progress(self, track=True):
r""" 显示 MBQC 模型运行进度的开关。
Args:
track (bool, optional): ``True`` 打开进度条显示功能, ``False`` 关闭进度条显示功能
"""
assert isinstance(track, bool), "the parameter 'track' must be bool."
self.__track = track
def __apply_cz(self, which_qubits_list):
r"""对给定的两个比特作用控制 Z 门。
Note:
这是内部方法,用户并不需要直接调用到该方法。
Warning:
作用控制 Z 门的两个比特一定是被激活的。
Args:
which_qubits_list (list): 作用控制 Z 门的比特对标签列表,例如 ``[(1, 2), (3, 4),...]``
"""
for which_qubits in which_qubits_list:
assert set(which_qubits).issubset(self.vertex.active), \
"vertices in 'which_qubits_list' must be activated first."
assert which_qubits[0] != which_qubits[1], \
'the control and target qubits must not be the same.'
# Find the control and target qubits and permute them to the front
self.__bg_state = permute_to_front(self.__bg_state, which_qubits[0])
self.__bg_state = permute_to_front(self.__bg_state, which_qubits[1])
new_state = self.__bg_state
new_state_len = new_state.length
qua_length = int(new_state_len / 4)
cz = cz_gate()
# Reshape the state, apply CZ and reshape it back
new_state.vector = reshape(matmul(cz, reshape(new_state.vector, [4, qua_length])), [new_state_len, 1])
# Update the order of active vertices and the background state
self.vertex.active = new_state.system
self.__bg_state = State(new_state.vector, new_state.system)
def __apply_pauli_gate(self, gate, which_qubit):
r"""对给定的单比特作用 Pauli 门。
Note:
这是内部方法,用户并不需要直接调用到该方法。
Args:
gate (str): Pauli 门的索引字符,"I", "X", "Y", "Z" 分别表示对应的门,在副产品处理时用 "X" 和 "Z" 门
which_qubit (any): 作用 Pauli 门的系统标签,
可以是 ``str``, ``tuple`` 等任意数据类型,但需要和 MBQC 模型中节点的标签类型匹配
"""
new_state = permute_to_front(self.__bg_state, which_qubit)
new_state_len = new_state.length
half_length = int(new_state_len / 2)
gate_mat = pauli_gate(gate)
# Reshape the state, apply X and reshape it back
new_state.vector = reshape(matmul(gate_mat, reshape(new_state.vector, [2, half_length])), [new_state_len, 1])
# Update the order of active vertices and the background state
self.vertex.active = new_state.system
self.__bg_state = State(new_state.vector, new_state.system)
def __create_graph_state(self, which_qubit):
r"""以待测量的比特为输入参数,生成测量当前节点所需要的最小的量子图态。
Note:
这是内部方法,用户并不需要直接调用到该方法。
Args:
which_qubit (any): 待测量比特的系统标签。
可以是 ``str``, ``tuple`` 等任意数据类型,但需要和 MBQC 模型中节点的标签类型匹配
"""
# Find the neighbors of 'which_qubit'
which_qubit_neighbors = set(self.__graph.neighbors(which_qubit))
# Exclude the qubits already measured
neighbors_not_measured = which_qubit_neighbors.difference(set(self.vertex.measured))
# Create a list of system labels that will be applied to cz gates
cz_list = [(which_qubit, qubit) for qubit in neighbors_not_measured]
# Get the qubits to be activated
append_qubits = {which_qubit}.union(neighbors_not_measured).difference(set(self.vertex.active))
# Update active and pending lists
self.vertex.active += list(append_qubits)
self.vertex.pending = list(set(self.vertex.pending).difference(self.vertex.active))
# Compute the new background state vector
new_bg_state_vector = kron([self.__bg_state.vector] + [plus_state() for _ in append_qubits])
# Update the background state and apply cz
self.__bg_state = State(new_bg_state_vector, self.vertex.active)
self.__apply_cz(cz_list)
self.__draw_process("active", which_qubit)
def __update(self):
r"""更新历史列表和量子态信息。
"""
self.__history.append(self.__bg_state)
self.__status = self.__history[-1]
def measure(self, which_qubit, basis_list):
r"""以待测量的比特和测量基为输入参数,对该比特进行测量。
Note:
这是用户在实例化 MBQC 类之后最常调用的方法之一,此处我们对单比特测量模拟进行了最大程度的优化,
随着用户对该函数的调用,MBQC 类将自动完成激活相关节点、生成所需的图态以及对特定比特进行测量的全过程,
并记录测量结果和对应测量后的量子态。用户每调用一次该函数,就完成一次对单比特的测量操作。
Warning:
当且仅当用户调用 ``measure`` 类方法时,MBQC 模型才真正进行运算。
Args:
which_qubit (any): 待测量量子比特的系统标签,
可以是 ``str``, ``tuple`` 等任意数据类型,但需要和 MBQC 模型的图上标签匹配
basis_list (list): 测量基向量构成的列表,列表元素为 ``Tensor`` 类型的列向量
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.simulator import MBQC
from paddle_quantum.mbqc.qobject import State
from paddle_quantum.mbqc.utils import zero_state, basis
G = [['1', '2', '3'], [('1', '2'), ('2', '3')]]
mbqc = MBQC()
mbqc.set_graph(G)
state = State(zero_state(), ['1'])
mbqc.set_input_state(state)
mbqc.measure('1', basis('X'))
mbqc.measure('2', basis('X'))
print("Measurement outcomes: ", mbqc.get_classical_output())
::
Measurement outcomes: {'1': 0, '2': 1}
"""
self.__draw_process("measuring", which_qubit)
self.__create_graph_state(which_qubit)
assert which_qubit in self.vertex.active, 'the qubit to be measured must be activated first.'
new_bg_state = permute_to_front(self.__bg_state, which_qubit)
self.vertex.active = new_bg_state.system
half_length = int(new_bg_state.length / 2)
eps = 10 ** (-10)
prob = [0, 0]
state_unnorm = [0, 0]
# Calculate the probability and post-measurement states
for result in [0, 1]:
basis_dagger = t(conj(basis_list[result]))
# Reshape the state, multiply the basis and reshape it back
state_unnorm[result] = reshape(matmul(basis_dagger,
reshape(new_bg_state.vector, [2, half_length])), [half_length, 1])
probability = matmul(t(conj(state_unnorm[result])), state_unnorm[result])
is_complex128 = probability.dtype == to_tensor([], dtype='complex128').dtype
prob[result] = real(probability) if is_complex128 else probability
# Randomly choose a result and its corresponding post-measurement state
if prob[0].numpy().item() < eps:
result = 1
post_state_vector = state_unnorm[1]
elif prob[1].numpy().item() < eps:
result = 0
post_state_vector = state_unnorm[0]
else: # Take a random choice of outcome
result = random.choice(2, 1, p=[prob[0].numpy().item(), prob[1].numpy().item()]).item()
# Normalize the post-measurement state
post_state_vector = state_unnorm[result] / prob[result].sqrt()
# Write the measurement result into the dict
self.__outcome.update({which_qubit: int(result)})
# Update measured, active lists
self.vertex.measured.append(which_qubit)
self.max_active = max(len(self.vertex.active), self.max_active)
self.vertex.active.remove(which_qubit)
# Update the background state and history list
self.__bg_state = State(post_state_vector, self.vertex.active)
self.__update()
self.__draw_process("measured", which_qubit)
def sum_outcomes(self, which_qubits, start=0):
r"""根据输入的量子系统标签,在存储测量结果的字典中找到对应的测量结果,并进行求和。
Note:
在进行副产品纠正操作和定义适应性测量角度时,用户可以调用该方法对特定比特的测量结果求和。
Args:
which_qubits (list): 需要查找测量结果并求和的比特的系统标签列表
start (int): 对结果进行求和后需要额外相加的整数
Returns:
int: 指定比特的测量结果的和
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.simulator import MBQC
from paddle_quantum.mbqc.qobject import State
from paddle_quantum.mbqc.utils import zero_state, basis
G = [['1', '2', '3'], [('1', '2'), ('2', '3')]]
mbqc = MBQC()
mbqc.set_graph(G)
input_state = State(zero_state(), ['1'])
mbqc.set_input_state(input_state)
mbqc.measure('1', basis('X'))
mbqc.measure('2', basis('X'))
mbqc.measure('3', basis('X'))
print("All measurement outcomes: ", mbqc.get_classical_output())
print("Sum of outcomes of qubits '1' and '2': ", mbqc.sum_outcomes(['1', '2']))
print("Sum of outcomes of qubits '1', '2' and '3' with an extra 1: ", mbqc.sum_outcomes(['1', '2', '3'], 1))
::
All measurement outcomes: {'1': 0, '2': 0, '3': 1}
Sum of outcomes of qubits '1' and '2': 0
Sum of outcomes of qubits '1', '2' and '3' with an extra 1: 2
"""
assert isinstance(start, int), "'start' must be of type int."
return sum([self.__outcome[label] for label in which_qubits], start)
def correct_byproduct(self, gate, which_qubit, power):
r"""对测量后的量子态进行副产品纠正。
Note:
这是用户在实例化 MBQC 类并完成测量后,经常需要调用的一个方法。
Args:
gate (str): ``'X'`` 或者 ``'Z'``,分别表示 Pauli X 或 Z 门修正
which_qubit (any): 待操作的量子比特的系统标签,可以是 ``str``, ``tuple`` 等任意数据类型,但需要和 MBQC 中图的标签类型匹配
power (int): 副产品纠正算符的指数
代码示例:
此处展示的是 MBQC 模型下实现隐形传态的一个例子。
.. code-block:: python
from paddle_quantum.mbqc.simulator import MBQC
from paddle_quantum.mbqc.qobject import State
from paddle_quantum.mbqc.utils import random_state_vector, basis, compare_by_vector
G = [['1', '2', '3'], [('1', '2'), ('2', '3')]]
state = State(random_state_vector(1), ['1'])
mbqc = MBQC()
mbqc.set_graph(G)
mbqc.set_input_state(state)
mbqc.measure('1', basis('X'))
mbqc.measure('2', basis('X'))
outcome = mbqc.get_classical_output()
mbqc.correct_byproduct('Z', '3', outcome['1'])
mbqc.correct_byproduct('X', '3', outcome['2'])
state_out = mbqc.get_quantum_output()
state_std = State(state.vector, ['3'])
compare_by_vector(state_out, state_std)
::
Norm difference of the given states is:
0.0
They are exactly the same states.
"""
assert gate in ['X', 'Z'], "'gate' must be 'X' or 'Z'."
assert isinstance(power, int), "'power' must be of type 'int'."
if power % 2 == 1:
self.__apply_pauli_gate(gate, which_qubit)
self.__update()
def __run_cmd(self, cmd):
r"""执行测量或副产品处理命令。
Args:
cmd (Pattern.CommandM / Pattern.CommandX / Pattern.CommandZ): 测量或副产品处理命令
"""
assert cmd.name in ["M", "X", "Z"], "the input 'cmd' must be CommandM, CommandX or CommandZ."
if cmd.name == "M": # Execute measurement commands
signal_s = self.sum_outcomes(cmd.domain_s)
signal_t = self.sum_outcomes(cmd.domain_t)
# The adaptive angle is (-1)^{signal_s} * angle + {signal_t} * pi
adaptive_angle = multiply(to_tensor([(-1) ** signal_s], dtype="float64"), cmd.angle) \
+ to_tensor([signal_t * pi], dtype="float64")
self.measure(cmd.which_qubit, basis(cmd.plane, adaptive_angle))
else: # Execute byproduct correction commands
power = self.sum_outcomes(cmd.domain)
self.correct_byproduct(cmd.name, cmd.which_qubit, power)
def __run_cmd_lst(self, cmd_lst, bar_start, bar_end):
r"""对列表执行测量或副产品处理命令。
Args:
cmd_lst (list): 命令列表,包含测量或副产品处理命令
bar_start (int): 进度条的开始点
bar_end (int): 进度条的结束点
"""
for i in range(len(cmd_lst)):
cmd = cmd_lst[i]
self.__run_cmd(cmd)
print_progress((bar_start + i + 1) / bar_end, "Pattern Running Progress", self.__track)
def __kron_unmeasured_qubits(self):
r"""该方法将没有被作用 CZ 纠缠的节点初始化为 |+> 态,并与当前的量子态做张量积。
Warning:
该方法仅在用户输入测量模式时调用,当用户输入图时,如果节点没有被激活,我们默认用户没有对该节点进行任何操作。
"""
# Turn off the plot switch
self.__draw = False
# As the create_graph_state function would change the measured qubits list, we need to record it
measured_qubits = self.vertex.measured[:]
for qubit in list(self.__graph.nodes):
if qubit not in self.vertex.measured:
self.__create_graph_state(qubit)
# Update vertices and backgrounds
self.vertex.measured.append(qubit)
self.max_active = max(len(self.vertex.active), self.max_active)
self.__bg_state = State(self.__bg_state.vector, self.vertex.active)
# Restore the measured qubits
self.vertex.measured = measured_qubits
def run_pattern(self):
r"""按照设置的测量模式对 MBQC 模型进行模拟。
Warning:
该方法必须在 ``set_pattern`` 调用后调用。
"""
assert self.__pattern is not None, "please use this method after calling 'set_pattern'!"
# Execute measurement commands and correction commands
cmd_m_lst = [cmd for cmd in self.__pattern.commands if cmd.name == "M"]
cmd_c_lst = [cmd for cmd in self.__pattern.commands if cmd.name in ["X", "Z"]]
bar_end = len(cmd_m_lst + cmd_c_lst)
self.__run_cmd_lst(cmd_m_lst, 0, bar_end)
# Activate unmeasured qubits before byproduct corrections
self.__kron_unmeasured_qubits()
self.__run_cmd_lst(cmd_c_lst, len(cmd_m_lst), bar_end)
# The output state's label is messy (e.g. [(2, 0), (0, 1), (1, 3)...]),
# so we permute the systems in order
q_output = self.__pattern.output_[1]
self.__bg_state = permute_systems(self.__status, q_output)
self.__update()
@staticmethod
def __map_qubit_to_row(out_lst):
r"""将输出比特的标签与行数对应起来,便于查找其对应关系。
Returns:
dict: 返回字典,代表行数与标签的对应关系
"""
return {int(div_str_to_float(qubit[0])): qubit for qubit in out_lst}
def get_classical_output(self):
r"""获取 MBQC 模型运行后的经典输出结果。
Returns:
str or dict: 如果用户输入是测量模式,则返回测量输出节点得到的比特串,与原电路的测量结果相一致,没有被测量的比特位填充 "?";
如果用户输入是图,则返回所有节点的测量结果
"""
# If the input is pattern, return the equivalent result as the circuit model
if self.__pattern is not None:
width = len(self.__pattern.input_)
c_output = self.__pattern.output_[0]
q_output = self.__pattern.output_[1]
# Acquire the relationship between row number and corresponding output qubit label
output_lst = c_output + q_output
row_and_qubit = self.__map_qubit_to_row(output_lst)
# Obtain the string, with classical outputs denoted as their measurement outcomes
# and quantum outputs denoted as "?"
bit_str = [str(self.__outcome[row_and_qubit[i]])
if row_and_qubit[i] in c_output else '?'
for i in range(width)]
string = "".join(bit_str)
return string
# If the input is graph, return the outcome dictionary
else:
return self.__outcome
def get_history(self):
r"""获取 MBQC 计算模拟时的中间步骤信息。
Returns:
list: 生成图态、进行测量、纠正副产品后运算结果构成的列表
"""
return self.__history
def get_quantum_output(self):
r"""获取 MBQC 模型运行后的量子态输出结果。
Returns:
State: MBQC 模型运行后的量子态
"""
return self.__status
def simulate_by_mbqc(circuit, input_state=None):
r"""使用等价的 MBQC 模型模拟量子电路。
该函数通过将量子电路转化为等价的 MBQC 模型并运行,从而获得等价于原始量子电路的输出结果。
Warning:
与 ``UAnsatz`` 不同,此处输入的 ``circuit`` 参数包含了测量操作。
另,MBQC 模型默认初始态为加态,因此,如果用户不输入参数 ``input_state`` 设置初始量子态,则默认为加态。
Args:
circuit (Circuit): 量子电路图
input_state (State, optional): 量子电路的初始量子态,默认为 :math:`|+\rangle` 态
Returns:
tuple: 包含如下两个元素:
- str: 经典输出
- State: 量子输出
"""
if input_state is not None:
assert isinstance(input_state, State), "the 'input_state' must be of type 'State'."
pattern = transpile(circuit)
mbqc = MBQC()
mbqc.set_pattern(pattern)
mbqc.set_input_state(input_state)
mbqc.run_pattern()
c_output = mbqc.get_classical_output()
q_output = mbqc.get_quantum_output()
# Return the classical and quantum outputs
return c_output, q_output
def __get_sample_dict(bit_num, mea_bits, samples):
r"""根据比特数和测量比特索引的列表,统计采样结果。
Args:
bit_num (int): 比特数
mea_bits (list): 测量的比特列表
samples (list): 采样结果
Returns:
dict: 统计得到的采样结果
"""
sample_dict = {}
for i in range(2 ** len(mea_bits)):
str_of_order = bin(i)[2:].zfill(len(mea_bits))
bit_str = []
idx = 0
for j in range(bit_num):
if j in mea_bits:
bit_str.append(str_of_order[idx])
idx += 1
else:
bit_str.append('?')
string = "".join(bit_str)
sample_dict[string] = 0
# Count sampling results
for string in list(set(samples)):
sample_dict[string] += samples.count(string)
return sample_dict
def sample_by_mbqc(circuit, input_state=None, plot=False, shots=1024, print_or_not=True):
r"""将 MBQC 模型重复运行多次,获得经典结果的统计分布。
Warning:
与 ``UAnsatz`` 不同,此处输入的 circuit 参数包含了测量操作。
另,MBQC 模型默认初始态为加态,因此,如果用户不输入参数 `input_state` 设置初始量子态,则默认为加态。
Args:
circuit (Circuit): 量子电路图
input_state (State, optional): 量子电路的初始量子态,默认为加态
plot (bool, optional): 绘制经典采样结果的柱状图开关,默认为关闭状态
shots (int, optional): 采样次数,默认为 1024 次
print_or_not (bool, optional): 是否打印采样结果和绘制采样进度,默认为开启状态
Returns:
dict: 经典结果构成的频率字典
list: 经典测量结果和所有采样结果(包括经典输出和量子输出)的列表
"""
# Initialize
if shots == 1:
print_or_not = False
if print_or_not:
print("Sampling " + str(shots) + " times." + "\nWill return the sampling results.\r\n")
width = circuit.get_width()
mea_bits = circuit.get_measured_qubits()
# Sampling for "shots" times
samples = []
all_outputs = []
for shot in range(shots):
if print_or_not:
print_progress((shot + 1) / shots, "Current Sampling Progress")
c_output, q_output = simulate_by_mbqc(circuit, input_state)
samples.append(c_output)
all_outputs.append([c_output, q_output])
sample_dict = __get_sample_dict(width, mea_bits, samples)
if print_or_not:
print("Sample count " + "(" + str(shots) + " shots)" + " : " + str(sample_dict))
if plot:
dict_lst = [sample_dict]
bar_labels = ["MBQC sample outcomes"]
title = 'Sampling results (MBQC)'
xlabel = "Measurement outcomes"
ylabel = "Distribution"
plot_results(dict_lst, bar_labels, title, xlabel, ylabel)
return sample_dict, all_outputs
# 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.
"""
此模块包含电路模型和 MBQC 测量模式的转义工具。
"""
from paddle_quantum.mbqc.qobject import Circuit
from paddle_quantum.mbqc.mcalculus import MCalculus
__all__ = [
"transpile"
]
def transpile(circuit, track=False):
r"""将输入的量子电路翻译为等价的测量模式。
Args:
circuit (Circuit): 量子电路,包含可能的测量部分
track (bool): 是否显示翻译进度条的开关
Returns:
pattern (Pattern): 等价的测量模式
"""
assert isinstance(circuit, Circuit), "'circuit' must be of type 'Circuit'."
assert isinstance(track, bool), "'track' must be a 'bool'."
mc = MCalculus()
if track:
mc.track_progress()
mc.set_circuit(circuit)
mc.standardize()
mc.shift_signals()
mc.optimize_by_row()
pattern = mc.get_pattern()
return pattern
# 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.
"""
此模块包含计算所需的各种常用类和函数。
"""
from numpy import array, exp, pi, linalg
from numpy import sqrt as np_sqrt
from numpy import random as np_random
from paddle import Tensor, to_tensor, t, cos, eye, sin
from paddle import kron as pp_kron
from paddle import matmul, conj, real
from paddle import reshape, transpose
from paddle import sqrt as pp_sqrt
from paddle import multiply
from paddle_quantum.mbqc.qobject import State
import matplotlib.pyplot as plt
__all__ = ["plus_state",
"minus_state",
"zero_state",
"one_state",
"h_gate",
"s_gate",
"t_gate",
"cz_gate",
"cnot_gate",
"swap_gate",
"pauli_gate",
"rotation_gate",
"to_projector",
"basis",
"kron",
"permute_to_front",
"permute_systems",
"compare_by_density",
"compare_by_vector",
"random_state_vector",
"div_str_to_float",
"int_to_div_str",
"print_progress",
"plot_results",
"write_running_data",
"read_running_data"
]
def plus_state():
r"""定义加态。
其矩阵形式为:
.. math::
\frac{1}{\sqrt{2}} \begin{bmatrix} 1 \\ 1 \end{bmatrix}
Returns:
Tensor: 加态对应的 ``Tensor`` 形式
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.utils import plus_state
print("State vector of plus state: \n", plus_state().numpy())
::
State vector of plus state:
[[0.70710678]
[0.70710678]]
"""
return to_tensor([[1 / np_sqrt(2)], [1 / np_sqrt(2)]], dtype='float64')
def minus_state():
r"""定义减态。
其矩阵形式为:
.. math::
\frac{1}{\sqrt{2}} \begin{bmatrix} 1 \\ -1 \end{bmatrix}
Returns:
Tensor: 减态对应的 ``Tensor`` 形式
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.utils import minus_state
print("State vector of minus state: \n", minus_state().numpy())
::
State vector of minus state:
[[ 0.70710678]
[-0.70710678]]
"""
return to_tensor([[1 / np_sqrt(2)], [-1 / np_sqrt(2)]], dtype='float64')
def zero_state():
r"""定义零态。
其矩阵形式为:
.. math::
\begin{bmatrix} 1 \\ 0 \end{bmatrix}
Returns:
Tensor: 零态对应的 ``Tensor`` 形式
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.utils import zero_state
print("State vector of zero state: \n", zero_state().numpy())
::
State vector of zero state:
[[1.]
[0.]]
"""
return to_tensor([[1], [0]], dtype='float64')
def one_state():
r"""定义一态。
其矩阵形式为:
.. math::
\begin{bmatrix} 0 \\ 1 \end{bmatrix}
Returns:
Tensor: 一态对应的 ``Tensor`` 形式
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.utils import one_state
print("State vector of one state: \n", one_state().numpy())
::
State vector of one state:
[[0.]
[1.]]
"""
return to_tensor([[0], [1]], dtype='float64')
def h_gate():
r"""定义 ``Hadamard`` 门。
其矩阵形式为:
.. math::
\frac{1}{\sqrt{2}} \begin{bmatrix} 1 & 1 \\ 1 & -1 \end{bmatrix}
Returns:
Tensor: ``Hadamard`` 门对应矩阵的 ``Tensor`` 形式
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.utils import h_gate
print("Matrix of Hadamard gate: \n", h_gate().numpy())
::
Matrix of Hadamard gate:
[[ 0.70710678 0.70710678]
[ 0.70710678 -0.70710678]]
"""
return to_tensor((1 / np_sqrt(2)) * array([[1, 1], [1, -1]]), dtype='float64')
def s_gate():
r"""定义 ``S`` 门。
其矩阵形式为:
.. math::
\begin{bmatrix} 1 & 0 \\ 0 & i \end{bmatrix}
Returns:
Tensor: ``S`` 门矩阵对应的 ``Tensor`` 形式
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.utils import s_gate
print("Matrix of S gate:\n", s_gate().numpy())
::
Matrix of S gate:
[[1.+0.j 0.+0.j]
[0.+0.j 0.+1.j]]
"""
return to_tensor([[1, 0], [0, 1j]], dtype='complex128')
def t_gate():
r"""定义 ``T`` 门。
其矩阵形式为:
.. math::
\begin{bmatrix} 1 & 0 \\ 0 & e^{i \pi / 4} \end{bmatrix}
Returns:
Tensor: ``T`` 门矩阵对应的 ``Tensor`` 形式
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.utils import t_gate
print("Matrix of T gate: \n", t_gate().numpy())
::
Matrix of T gate:
[[1. +0.j 0. +0.j ]
[0. +0.j 0.70710678+0.70710678j]]
"""
return to_tensor([[1, 0], [0, exp(1j * pi / 4)]], dtype='complex128')
def cz_gate():
r"""定义 ``Control Z`` 门。
其矩阵形式为:
.. math::
\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & -1 \end{bmatrix}
Returns:
Tensor: ``Control Z`` 门矩阵对应的 ``Tensor`` 形式
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.utils import cz_gate
print("Matrix of CZ gate: \n", cz_gate().numpy())
::
Matrix of CZ gate:
[[ 1. 0. 0. 0.]
[ 0. 1. 0. 0.]
[ 0. 0. 1. 0.]
[ 0. 0. 0. -1.]]
"""
return to_tensor([[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, -1]], dtype='float64')
def cnot_gate():
r"""定义 ``Control NOT (CNOT)`` 门。
其矩阵形式为:
.. math::
\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \end{bmatrix}
Returns:
Tensor: ``Control NOT (CNOT)`` 门矩阵对应的 ``Tensor`` 形式
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.utils import cnot_gate
print("Matrix of CNOT gate: \n", cnot_gate().numpy())
::
Matrix of CNOT gate:
[[1. 0. 0. 0.]
[0. 1. 0. 0.]
[0. 0. 0. 1.]
[0. 0. 1. 0.]]
"""
return to_tensor([[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 1],
[0, 0, 1, 0]], dtype='float64')
def swap_gate():
r"""定义 ``SWAP`` 门。
其矩阵形式为:
.. math::
\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}
Returns:
Tensor: ``SWAP`` 门矩阵对应的 ``Tensor`` 形式
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.utils import swap_gate
print("Matrix of Swap gate: \n", swap_gate().numpy())
::
Matrix of Swap gate:
[[1. 0. 0. 0.]
[0. 0. 1. 0.]
[0. 1. 0. 0.]
[0. 0. 0. 1.]]
"""
return to_tensor([[1, 0, 0, 0],
[0, 0, 1, 0],
[0, 1, 0, 0],
[0, 0, 0, 1]], dtype='float64')
def pauli_gate(gate):
r"""定义 ``Pauli`` 门。
单位阵 ``I`` 的矩阵形式为:
.. math::
\begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix}
``Pauli X`` 门的矩阵形式为:
.. math::
\begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix}
``Pauli Y`` 门的矩阵形式为:
.. math::
\begin{bmatrix} 0 & - i \\ i & 0 \end{bmatrix}
``Pauli Z`` 门的矩阵形式为:
.. math::
\begin{bmatrix} 1 & 0 \\ 0 & - 1 \end{bmatrix}
Args:
gate (str): Pauli 门的索引字符,"I", "X", "Y", "Z" 分别表示对应的门
Returns:
Tensor: Pauli 门对应的矩阵
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.utils import pauli_gate
I = pauli_gate('I')
X = pauli_gate('X')
Y = pauli_gate('Y')
Z = pauli_gate('Z')
print("Matrix of Identity gate: \n", I.numpy())
print("Matrix of Pauli X gate: \n", X.numpy())
print("Matrix of Pauli Y gate: \n", Y.numpy())
print("Matrix of Pauli Z gate: \n", Z.numpy())
::
Matrix of Identity gate:
[[1. 0.]
[0. 1.]]
Matrix of Pauli X gate:
[[0. 1.]
[1. 0.]]
Matrix of Pauli Y gate:
[[ 0.+0.j -0.-1.j]
[ 0.+1.j 0.+0.j]]
Matrix of Pauli Z gate:
[[ 1. 0.]
[ 0. -1.]]
"""
if gate == 'I': # Identity gate
return to_tensor(eye(2, 2), dtype='float64')
elif gate == 'X': # Pauli X gate
return to_tensor([[0, 1], [1, 0]], dtype='float64')
elif gate == 'Y': # Pauli Y gate
return to_tensor([[0, -1j], [1j, 0]], dtype='complex128')
elif gate == 'Z': # Pauli Z gate
return to_tensor([[1, 0], [0, -1]], dtype='float64')
else:
print("The Pauli gate must be 'I', 'X', 'Y' or 'Z'.")
raise KeyError("invalid Pauli gate index: %s" % gate + ".")
def rotation_gate(axis, theta):
r"""定义旋转门矩阵。
.. math::
R_{x}(\theta) = \cos(\theta / 2) I - i\sin(\theta / 2) X
R_{y}(\theta) = \cos(\theta / 2) I - i\sin(\theta / 2) Y
R_{z}(\theta) = \cos(\theta / 2) I - i\sin(\theta / 2) Z
Args:
axis (str): 旋转轴,绕 ``X`` 轴旋转输入 'x',绕 ``Y`` 轴旋转输入 'y',绕 ``Z`` 轴旋转输入 'z'
theta (Tensor): 旋转的角度
Returns:
Tensor: 旋转门对应的矩阵
代码示例:
.. code-block:: python
from numpy import pi
from paddle import to_tensor
from paddle_quantum.mbqc.utils import rotation_gate
theta = to_tensor([pi / 6], dtype='float64')
Rx = rotation_gate('x', theta)
Ry = rotation_gate('y', theta)
Rz = rotation_gate('z', theta)
print("Matrix of Rotation X gate with angle pi/6: \n", Rx.numpy())
print("Matrix of Rotation Y gate with angle pi/6: \n", Ry.numpy())
print("Matrix of Rotation Z gate with angle pi/6: \n", Rz.numpy())
::
Matrix of Rotation X gate with angle pi/6:
[[0.96592583+0.j 0. -0.25881905j]
[0. -0.25881905j 0.96592583+0.j ]]
Matrix of Rotation Y gate with angle pi/6:
[[ 0.96592583+0.j -0.25881905+0.j]
[ 0.25881905+0.j 0.96592583+0.j]]
Matrix of Rotation Z gate with angle pi/6:
[[0.96592583-0.25881905j 0. +0.j ]
[0. +0.j 0.96592583+0.25881905j]]
"""
# Check if the input theta is a Tensor and has 'float64' datatype
float64_tensor = to_tensor([], dtype='float64')
assert isinstance(theta, Tensor) and theta.dtype == float64_tensor.dtype, \
"The rotation angle should be a Tensor and of type 'float64'."
# Calculate half of the input theta
half_theta = multiply(theta, to_tensor([0.5], dtype='float64'))
if axis == 'x': # Define the rotation - x gate matrix
return multiply(pauli_gate('I'), cos(half_theta)) + \
multiply(pauli_gate('X'), multiply(sin(half_theta), to_tensor([-1j], dtype='complex128')))
elif axis == 'y': # Define the rotation - y gate matrix
return multiply(pauli_gate('I'), cos(half_theta)) + \
multiply(pauli_gate('Y'), multiply(sin(half_theta), to_tensor([-1j], dtype='complex128')))
elif axis == 'z': # Define the rotation - z gate matrix
return multiply(pauli_gate('I'), cos(half_theta)) + \
multiply(pauli_gate('Z'), multiply(sin(half_theta), to_tensor([-1j], dtype='complex128')))
else:
raise KeyError("invalid rotation gate index: %s, the rotation axis must be 'x', 'y' or 'z'." % axis)
def to_projector(vector):
r"""把列向量转化为密度矩阵(或测量基对应的投影算符)。
.. math::
|\psi\rangle \to |\psi\rangle\langle\psi|
Args:
vector (Tensor): 量子态列向量(或投影测量中的测量基向量)
Returns:
Tensor: 密度矩阵(或测量基对应的投影算符)
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.utils import zero_state, plus_state
from paddle_quantum.mbqc.utils import to_projector
zero_proj = to_projector(zero_state())
plus_proj = to_projector(plus_state())
print("The projector of zero state: \n", zero_proj.numpy())
print("The projector of plus state: \n", plus_proj.numpy())
::
The projector of zero state:
[[1. 0.]
[0. 0.]]
The projector of plus state:
[[0.5 0.5]
[0.5 0.5]]
"""
assert isinstance(vector, Tensor) and vector.shape[0] >= 1 and vector.shape[1] == 1, \
"'vector' must be a Tensor of shape (x, 1) with x >= 1."
return matmul(vector, t(conj(vector)))
def basis(label, theta=to_tensor([0], dtype='float64')):
r"""测量基。
Note:
常用的测量方式有 XY-平面测量,YZ-平面测量,X 测量,Y 测量,Z 测量。
.. math::
M^{XY}(\theta) = {R_{z}(\theta)|+\rangle, R_{z}(\theta)|-\rangle}
M^{YZ}(\theta) = {R_{x}(\theta)|0\rangle, R_{x}(\theta)|1\rangle}
X = M^{XY}(0)
Y = M^{YZ}(\pi / 2) = M^{XY}(-\pi / 2)
Z = M_{YZ}(0)
Args:
label (str): 测量基索引字符,"XY" 表示 XY-平面测量,"YZ" 表示 YZ-平面测量,"X" 表示 X 测量,"Y" 表示 Y 测量,"Z" 表示 Z 测量
theta (Tensor, optional): 测量角度,这里只有 XY-平面测量和 YZ-平面测量时需要
Returns:
list: 测量基向量构成的列表,列表元素为 ``Tensor`` 类型
代码示例:
.. code-block:: python
from numpy import pi
from paddle import to_tensor
from paddle_quantum.mbqc.utils import basis
theta = to_tensor(pi / 6, dtype='float64')
YZ_plane_basis = basis('YZ', theta)
XY_plane_basis = basis('XY', theta)
X_basis = basis('X')
Y_basis = basis('Y')
Z_basis = basis('Z')
print("Measurement basis in YZ plane: \n", YZ_plane_basis)
print("Measurement basis in XY plane: \n", XY_plane_basis)
print("Measurement basis of X: \n", X_basis)
print("Measurement basis of Y: \n", Y_basis)
print("Measurement basis of Z: \n", Z_basis)
::
Measurement basis in YZ plane:
[Tensor(shape=[2, 1], dtype=complex128, place=CPUPlace, stop_gradient=True,
[[(0.9659258262890683+0j)],
[-0.25881904510252074j ]]),
Tensor(shape=[2, 1], dtype=complex128, place=CPUPlace, stop_gradient=True,
[[-0.25881904510252074j ],
[(0.9659258262890683+0j)]])]
Measurement basis in XY plane:
[Tensor(shape=[2, 1], dtype=complex128, place=CPUPlace, stop_gradient=True,
[[(0.6830127018922193-0.1830127018922193j)],
[(0.6830127018922193+0.1830127018922193j)]]),
Tensor(shape=[2, 1], dtype=complex128, place=CPUPlace, stop_gradient=True,
[[ (0.6830127018922193-0.1830127018922193j)],
[(-0.6830127018922193-0.1830127018922193j)]])]
Measurement basis of X:
[Tensor(shape=[2, 1], dtype=float64, place=CPUPlace, stop_gradient=True,
[[0.70710678],
[0.70710678]]),
Tensor(shape=[2, 1], dtype=float64, place=CPUPlace, stop_gradient=True,
[[ 0.70710678],
[-0.70710678]])]
Measurement basis of Y:
[Tensor(shape=[2, 1], dtype=complex128, place=CPUPlace, stop_gradient=True,
[[(0.5-0.5j)],
[(0.5+0.5j)]]),
Tensor(shape=[2, 1], dtype=complex128, place=CPUPlace, stop_gradient=True,
[[ (0.5-0.5j)],
[(-0.5-0.5j)]])]
Measurement basis of Z:
[Tensor(shape=[2, 1], dtype=float64, place=CPUPlace, stop_gradient=True,
[[1.],
[0.]]),
Tensor(shape=[2, 1], dtype=float64, place=CPUPlace, stop_gradient=True,
[[0.],
[1.]])]
"""
# Check the label and input angle
assert label in ['XY', 'YZ', 'X', 'Y', 'Z'], "the basis label must be 'XY', 'YZ', 'X', 'Y' or 'Z'."
float64_tensor = to_tensor([], dtype='float64')
assert isinstance(theta, Tensor) and theta.dtype == float64_tensor.dtype, \
"The input angle should be a tensor and of type 'float64'."
if label == 'YZ': # Define the YZ plane measurement basis
return [matmul(rotation_gate('x', theta), zero_state()),
matmul(rotation_gate('x', theta), one_state())]
elif label == 'XY': # Define the XY plane measurement basis
return [matmul(rotation_gate('z', theta), plus_state()),
matmul(rotation_gate('z', theta), minus_state())]
elif label == 'X': # Define the X-measurement basis
return [plus_state(), minus_state()]
elif label == 'Y': # Define the Y-measurement basis
return [matmul(rotation_gate('z', to_tensor([pi / 2], dtype='float64')), plus_state()),
matmul(rotation_gate('z', to_tensor([pi / 2], dtype='float64')), minus_state())]
elif label == 'Z': # Define the Z-measurement basis
return [zero_state(), one_state()]
def kron(tensor_list):
r"""把列表中的所有元素做张量积。
.. math::
[A, B, C, \cdots] \to A \otimes B \otimes C \otimes \cdots
Args:
tensor_list (list): 需要做张量积的元素组成的列表
Returns:
Tensor: 所有元素做张量积运算得到的 ``Tensor``,当列表中只有一个 ``Tensor`` 时,返回该 ``Tensor`` 本身
代码示例 1:
.. code-block:: python
from paddle import to_tensor
from paddle_quantum.mbqc.utils import pauli_gate, kron
tensor0 = pauli_gate('I')
tensor1 = to_tensor([[1, 1], [1, 1]], dtype='float64')
tensor2 = to_tensor([[1, 2], [3, 4]], dtype='float64')
tensor_list = [tensor0, tensor1, tensor2]
tensor_all = kron(tensor_list)
print("The tensor product result: \n", tensor_all.numpy())
::
The tensor product result:
[[1. 2. 1. 2. 0. 0. 0. 0.]
[3. 4. 3. 4. 0. 0. 0. 0.]
[1. 2. 1. 2. 0. 0. 0. 0.]
[3. 4. 3. 4. 0. 0. 0. 0.]
[0. 0. 0. 0. 1. 2. 1. 2.]
[0. 0. 0. 0. 3. 4. 3. 4.]
[0. 0. 0. 0. 1. 2. 1. 2.]
[0. 0. 0. 0. 3. 4. 3. 4.]]
代码示例 2:
.. code-block:: python
from paddle_quantum.mbqc.utils import pauli_gate, kron
tensor0 = pauli_gate('I')
tensor_list = [tensor0]
tensor_all = kron(tensor_list)
print("The tensor product result: \n", tensor_all.numpy())
::
The tensor product result:
[[1. 0.]
[0. 1.]]
"""
assert isinstance(tensor_list, list), "'tensor_list' must be a `list`."
assert all(isinstance(tensor, Tensor) for tensor in tensor_list), "each element in the list must be a `Tensor`."
kron_all = tensor_list[0]
if len(tensor_list) > 1: # Kron together
for i in range(1, len(tensor_list)):
tensor = tensor_list[i]
kron_all = pp_kron(kron_all, tensor)
return kron_all
def permute_to_front(state, which_system):
r"""将一个量子态中某个子系统的顺序变换到最前面。
假设当前系统的量子态列向量 :math:`\psi\rangle` 可以分解成多个子系统列向量的 tensor product 形式:
.. math::
|\psi\rangle = |\psi_1\rangle \otimes |\psi_2\rangle \otimes |\psi_3\rangle \otimes \cdots
每个 :math:`|\psi_i\rangle` 的系统标签为 :math:`i` ,则当前总系统的标签为:
.. math::
\text{label} = \{1, 2, 3, \cdots \}
假设需要操作的子系统的标签为:i
输出新系统量子态的列向量为:
.. math::
|\psi_i\rangle \otimes |\psi_1\rangle \otimes \cdots |\psi_{i-1}\rangle \otimes |\psi_{i+1}\rangle \otimes \cdots
Args:
state (State): 需要操作的量子态
which_system (str): 要变换到最前面的子系统标签
Returns:
State: 系统顺序变换后的量子态
"""
assert which_system in state.system, 'the system to permute must be in the state systems.'
system_idx = state.system.index(which_system)
if system_idx == 0: # system in the front
return state
elif system_idx == state.size - 1: # system in the end
new_shape = [2 ** (state.size - 1), 2]
new_axis = [1, 0]
new_system = [which_system] + state.system[: system_idx]
else: # system in the middle
new_shape = [2 ** system_idx, 2, 2 ** (state.size - system_idx - 1)]
new_axis = [1, 0, 2]
new_system = [which_system] + state.system[: system_idx] + state.system[system_idx + 1:]
new_vector = reshape(transpose(reshape(state.vector, new_shape), new_axis), [state.length, 1])
return State(new_vector, new_system)
def permute_systems(state, new_system):
r"""变换量子态的系统到指定顺序。
假设当前系统的量子态列向量 :math:`|\psi\rangle` 可以分解成多个子系统列向量的 tensor product 形式:
.. math::
|\psi\rangle = |\psi_1\rangle \otimes |\psi_2\rangle \otimes |\psi_3\rangle \otimes \cdots
每个 :math:`\psi_i\rangle` 的系统标签为 :math:`i` ,则当前总系统的标签为:
.. math::
\text{label} = \{1, 2, 3, \cdots \}
给定新系统的标签顺序为:
.. math::
\{i_1, i_2, i_3, \cdots \}
输出新系统量子态的列向量为:
.. math::
|\psi_{i_1}\rangle \otimes |\psi_{i_2}\rangle \otimes |\psi_{i_3}\rangle \otimes \cdots
Args:
state (State): 需要操作的量子态
new_system (list): 目标系统顺序
Returns:
State: 系统顺序变换后的量子态
"""
for label in reversed(new_system):
state = permute_to_front(state, label)
return state
def compare_by_density(state1, state2):
r"""通过密度矩阵形式比较两个量子态是否相同。
Args:
state1 (State): 第一个量子态
state2 (State): 第二个量子态
"""
assert state1.size == state2.size, "two state vectors compared are not of the same length."
# Permute the system order
new_state1 = permute_systems(state1, state2.system)
# Transform the vector to density
density1 = to_projector(new_state1.vector)
density2 = to_projector(state2.vector)
error = linalg.norm(density1.numpy() - density2.numpy())
print("Norm difference of the given states is: \r\n", error)
eps = 1e-12 # Error criterion
if error < eps:
print("They are exactly the same states.")
elif 1e-10 > error >= eps:
print("They are probably the same states.")
else:
print("They are not the same states.")
def compare_by_vector(state1, state2):
r"""通过列向量形式比较两个量子态是否相同。
Args:
state1 (State): 第一个量子态
state2 (State): 第二个量子态
"""
assert state1.size == state2.size, "two state vectors compared are not of the same length."
# Check if they are normalized quantum states
eps = 1e-12 # Error criterion
if state1.norm >= 1 + eps or state1.norm <= 1 - eps:
raise ValueError("the first state is not normalized.")
elif state2.norm >= 1 + eps or state2.norm <= 1 - eps:
raise ValueError("the second state is not normalized.")
else:
new_state1 = permute_systems(state1, state2.system)
vector1_list = list(new_state1.vector.numpy())
idx = vector1_list.index(max(vector1_list, key=abs))
if - eps <= state2.vector[idx].numpy() <= eps:
print("They are not the same states.")
else:
# Calculate the phase and erase it
phase = new_state1.vector[idx] / state2.vector[idx]
vector1_phase = new_state1.vector / phase
error = linalg.norm(vector1_phase - state2.vector)
print("Norm difference of the given states is: \r\n", error)
if error < eps:
print("They are exactly the same states.")
elif 1e-10 > error >= eps:
print("They are probably the same states.")
else:
print("They are not the same states.")
def random_state_vector(n, is_real=False):
r"""随机生成一个量子态列向量。
Args:
n (int): 随机生成的量子态的比特数
is_real (int, optional): ``True`` 表示实数量子态,``False`` 表示复数量子态,默认为 ``False``
Returns:
Tensor: 随机生成量子态的列向量
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.utils import random_state_vector
random_vec = random_state_vector(2)
print(random_vec.numpy())
random_vec = random_state_vector(1, is_real=True)
print(random_vec.numpy())
::
[[-0.06831946+0.04548425j]
[ 0.60460088-0.16733175j]
[ 0.39185213-0.24831266j]
[ 0.45911355-0.41680807j]]
[[0.77421121]
[0.63292732]]
"""
assert isinstance(n, int) and n >= 1, "the number of qubit must be a int larger than one."
assert isinstance(is_real, bool), "'is_real' must be a bool."
if is_real:
psi = to_tensor(np_random.randn(2 ** n, 1), dtype='float64')
inner_prod = matmul(t(conj(psi)), psi)
else:
psi = to_tensor(np_random.randn(2 ** n, 1) + 1j * np_random.randn(2 ** n, 1), dtype='complex128')
inner_prod = real(matmul(t(conj(psi)), psi))
psi = psi / pp_sqrt(inner_prod) # Normalize the vector
return psi
def div_str_to_float(div_str):
r"""将除式字符串转化为对应的浮点数。
例如将字符串 '3/2' 转化为 1.5。
Args:
div_str (str): 除式字符串
Returns:
float: 除式对应的浮点数结果
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.utils import div_str_to_float
division_str = "1/2"
division_float = div_str_to_float(division_str)
print("The corresponding float value is: ", division_float)
::
The corresponding float value is: 0.5
"""
div_str = div_str.split("/")
return float(div_str[0]) / float(div_str[1])
def int_to_div_str(idx1, idx2=1):
r"""将两个整数转化为除式字符串。
Args:
idx1 (int): 第一个整数
idx2 (int): 第二个整数
Returns:
str: 对应的除式字符串
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.utils import int_to_div_str
one = 1
two = 2
division_string = int_to_div_str(one, two)
print("The corresponding division string is: ", division_string)
::
The corresponding division string is: 1/2
"""
assert isinstance(idx1, int) and isinstance(idx2, int), "two input parameters must be int."
return str(idx1) + "/" + str(idx2)
def print_progress(current_progress, progress_name, track=True):
r"""画出当前步骤的进度条。
Args:
current_progress (float / int): 当前的进度百分比
progress_name (str): 当前步骤的名称
track (bool): 是否绘图的布尔开关
代码示例:
.. code-block:: python
from paddle_quantum.mbqc.utils import print_progress
print_progress(14/100, "Current Progress")
::
Current Progress |■■■■■■■ | 14.00%
"""
assert 0 <= current_progress <= 1, "'current_progress' must be between 0 and 1"
assert isinstance(track, bool), "'track' must be a bool."
if track:
print("\r" + f"{progress_name.ljust(30)}"
f"|{'■' * int(50 * current_progress):{50}s}| "
f"\033[94m {'{:6.2f}'.format(100 * current_progress)}% \033[0m ", flush=True, end="")
if current_progress == 1:
print(" (Done)")
def plot_results(dict_lst, bar_label, title, xlabel, ylabel, xticklabels=None):
r"""根据字典的键值对,以键为横坐标,对应的值为纵坐标,画出柱状图。
Note:
该函数主要调用来画出采样分布或时间比较的柱状图。
Args:
dict_lst (list): 待画图的字典列表
bar_label (list): 每种柱状图对应的名称
title (str): 整个图的标题
xlabel (str): 横坐标的名称
ylabel (str): 纵坐标的名称
xticklabels (list, optional): 柱状图中每个横坐标的名称
"""
assert isinstance(dict_lst, list), "please input a list with dictionaries."
assert isinstance(bar_label, list), "please input a list with bar_labels."
assert len(dict_lst) == len(bar_label), \
"please check your input as the number of dictionaries and bar labels are not equal."
bars_num = len(dict_lst)
bar_width = 1 / (bars_num + 1)
plt.ion()
plt.figure()
for i in range(bars_num):
plot_dict = dict_lst[i]
# Obtain the y label and xticks in order
keys = list(plot_dict.keys())
values = list(plot_dict.values())
xlen = len(keys)
xticks = [((i) / (bars_num + 1)) + j for j in range(xlen)]
# Plot bars
plt.bar(xticks, values, width=bar_width, align='edge', label=bar_label[i])
plt.yticks()
if xticklabels is None:
plt.xticks(list(range(xlen)), keys, rotation=90)
else:
assert len(xticklabels) == xlen, "the 'xticklabels' should have the same length with 'x' length."
plt.xticks(list(range(xlen)), xticklabels, rotation=90)
plt.legend()
plt.title(title, fontproperties='SimHei', fontsize='x-large')
plt.xlabel(xlabel, fontproperties='SimHei')
plt.ylabel(ylabel, fontproperties='SimHei')
plt.ioff()
plt.show()
def write_running_data(textfile, eg, width, mbqc_time, reference_time):
r"""写入电路模拟运行的时间。
由于在许多电路模型模拟案例中,需要比较我们的 ``MBQC`` 模拟思路与 ``Qiskit`` 或量桨平台的 ``UAnsatz`` 电路模型模拟思路的运行时间。
因而单独定义了写入文件函数。
Hint:
该函数与 ``read_running_data`` 函数配套使用。
Warning:
在调用该函数之前,需要调用 ``open`` 打开 ``textfile``;在写入结束之后,需要调用 ``close`` 关闭 ``textfile``。
Args:
textfile (TextIOWrapper): 待写入的文件
eg (str): 当前案例的名称
width (float): 电路宽度(比特数)
mbqc_time (float): ``MBQC`` 模拟电路运行时间
reference_time (float): ``Qiskit`` 或量桨平台的 ``UAnsatz`` 电路模型运行时间
"""
textfile.write("The current example is: " + eg + "\n")
textfile.write("The qubit number is: " + str(width) + "\n")
textfile.write("MBQC running time is: " + str(mbqc_time) + " s\n")
textfile.write("Circuit model running time is: " + str(reference_time) + " s\n\n")
def read_running_data(file_name):
r"""读取电路模拟运行的时间。
由于在许多电路模型模拟案例中,需要比较我们的 ``MBQC`` 模拟思路与 ``Qiskit`` 或量桨平台的 ``UAnsatz`` 电路模型模拟思路的运行时间。
因而单独定义了读取文件函数读取运行时间,将其处理为一个列表,
列表中的两个元素分别为 ``Qiskit`` 或 ``UAnsatz`` 电路模型模拟思路运行时间的字典和 ``MBQC`` 模拟思路运行时间的字典。
Hint:
该函数与 ``write_running_data`` 函数配套使用。
Args:
file_name (str): 待读取的文件名
Returns:
list: 运行时间列表
"""
bit_num_lst = []
mbqc_list = []
reference_list = []
remainder = {2: bit_num_lst, 3: mbqc_list, 4: reference_list}
# Read data
with open(file_name, 'r') as file:
counter = 0
for line in file:
counter += 1
if counter % 5 in remainder.keys():
remainder[counter % 5].append(float(line.strip("\n").split(":")[1].split(" ")[1]))
# Transform the lists to dictionaries
mbqc_dict = {i: mbqc_list[i] for i in range(len(bit_num_lst))}
refer_dict = {i: reference_list[i] for i in range(len(bit_num_lst))}
dict_lst = [mbqc_dict, refer_dict]
return dict_lst
# 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.
"""
Quantum chemistry module
"""
import os
import re
import numpy as np
import psi4
import openfermion
from openfermion import MolecularData, transforms
from openfermion.ops import general_basis_change
from paddle_quantum.utils import Hamiltonian
__all__ = [
"geometry",
"get_molecular_data",
"active_space",
"fermionic_hamiltonian",
"spin_hamiltonian"
]
def _hamiltonian_transformation(spin_h, tol=1e-8):
r"""将哈密顿量从 openfermion 格式转换成 Paddle Quantum 格式。
Warning:
输入的哈密顿量必须为埃尔米特的,输入的哈密顿中虚数的系数会和实数一起转换成他们的范数 (norm)。
Args:
spin_h (openfermion.ops.operators.qubit_operator.QubitOperator): openfermion 格式的哈密顿量
tol (float, optional): 系数小于 tol 的值将被忽略掉,默认为 1e-8
Returns:
paddle_quantum.Hamiltonian object: Paddle Quantum 格式的哈密顿量
"""
terms = spin_h.__str__().split('+\n')
spin_h.compress(abs_tol=tol)
pauli_str = []
for term in terms:
decomposed_term = re.match(r"(.*) \[(.*)\].*", term).groups()
if decomposed_term[1] == '':
try:
pauli_str.append([float(decomposed_term[0]), 'I'])
except ValueError:
if complex(decomposed_term[0]).real > 0:
pauli_str.append([abs(complex(decomposed_term[0])), 'I'])
else:
pauli_str.append([-abs(complex(decomposed_term[0])), 'I'])
else:
term_str = ', '.join(re.split(r' ', decomposed_term[1]))
try:
pauli_str.append([float(decomposed_term[0]), term_str])
except ValueError:
if complex(decomposed_term[0]).real > 0:
pauli_str.append([abs(complex(decomposed_term[0])), term_str])
else:
pauli_str.append([-abs(complex(decomposed_term[0])), term_str])
return Hamiltonian(pauli_str)
def _geo_str(geometry):
r"""创建分子几何信息的字符串。
Args:
geometry (list): 包含了分子的几何信息,以 H2 分子为例
[['H', [-1.68666, 1.79811, 0.0]], ['H', [-1.12017, 1.37343, 0.0]]]。
Returns:
str: 分子几何信息的字符串。
"""
geo_str = ''
for item in geometry:
atom_symbol = item[0]
position = item[1]
line = '{} {} {} {}'.format(atom_symbol,
position[0],
position[1],
position[2])
if len(geo_str) > 0:
geo_str += '\n'
geo_str += line
geo_str += '\nsymmetry c1'
return geo_str
def _run_psi4(
molecule,
charge,
multiplicity,
method,
basis,
if_print,
if_save
):
r"""计算分子的必要信息,包括单体积分 (one-body integrations) 和双体积分 (two-body integrations),
以及用 scf 和 fci 的方法计算基态的能量。
Args:
molecule (MolecularData object): 包含分子所有信息的类 (class)。
charge (int): 分子的电荷。
multiplicity (int): 分子的多重度。
method (str): 用于计算基态能量的方法,包括 'scf'和 'fci'。
basis (str): 常用的基组是 'sto-3g', '6-31g'等。更多的基组选择可以参考网站。
https://psicode.org/psi4manual/master/basissets_byelement.html#apdx-basiselement。
if_print (Boolean): 是否需要打印出选定方法 (method) 计算出的分子基态能量。
if_save (Boolean): 是否需要将分子信息存储成 .hdf5 文件。
"""
psi4.set_memory('500 MB')
psi4.set_options({'soscf': 'false',
'scf_type': 'pk'})
geo = molecule.geometry
mol = psi4.geometry(_geo_str(geo))
mol.set_multiplicity(multiplicity)
mol.set_molecular_charge(charge)
if molecule.multiplicity == 1:
psi4.set_options({'reference': 'rhf',
'guess': 'sad'})
else:
psi4.set_options({'reference': 'rohf',
'guess': 'gwh'})
# HF calculation
hf_energy, hf_wfn = psi4.energy('scf/' + basis, molecule=mol, return_wfn='on')
# Get orbitals and Fock matrix.
molecule.hf_energy = hf_energy
molecule.nuclear_repulsion = mol.nuclear_repulsion_energy()
molecule.canonical_orbitals = np.asarray(hf_wfn.Ca())
molecule.overlap_integrals = np.asarray(hf_wfn.S())
molecule.n_orbitals = molecule.canonical_orbitals.shape[0]
molecule.n_qubits = 2 * molecule.n_orbitals
molecule.orbital_energies = np.asarray(hf_wfn.epsilon_a())
molecule.fock_matrix = np.asarray(hf_wfn.Fa())
# Get integrals using MintsHelper.
mints = psi4.core.MintsHelper(hf_wfn.basisset())
molecule.one_body_integrals = general_basis_change(
np.asarray(mints.ao_kinetic()), molecule.canonical_orbitals, (1, 0))
molecule.one_body_integrals += general_basis_change(
np.asarray(mints.ao_potential()), molecule.canonical_orbitals, (1, 0))
two_body_integrals = np.asarray(mints.ao_eri())
two_body_integrals.reshape((molecule.n_orbitals, molecule.n_orbitals,
molecule.n_orbitals, molecule.n_orbitals))
two_body_integrals = np.einsum('psqr', two_body_integrals)
two_body_integrals = general_basis_change(
two_body_integrals, molecule.canonical_orbitals, (1, 1, 0, 0))
molecule.two_body_integrals = two_body_integrals
# FCI calculation
psi4.set_options({'qc_module': 'detci'})
fci_energy, fci_wfn = psi4.energy('fci/' + basis, molecule=mol, return_wfn='on')
molecule.fci_energy = fci_energy
if if_save is True:
molecule.save()
if if_print is True:
if method == 'scf':
print('Hartree-Fock energy for {} ({} electrons) is {}.'.format(
molecule.name, molecule.n_electrons, hf_energy))
elif method == 'fci':
print('FCI energy for {} ({} electrons) is {}.'.format(
molecule.name, molecule.n_electrons, fci_energy))
elif method == '':
print('Calculation is done')
def geometry(structure=None, file=None):
r"""读取分子的几何信息。
Args:
structure (string, optional): 分子几何信息的字符串形式,以 H2 分子为例
``[['H', [-1.68666, 1.79811, 0.0]], ['H', [-1.12017, 1.37343, 0.0]]]``
file (string, optional): .xyz 文件的路径
Returns:
str: 分子的几何信息
Raises:
AssertionError: 两个输入参数不可以同时为 ``None`` 。
"""
if ((structure is None) and (file is None)):
raise AssertionError('Input must be structure or .xyz file')
elif file is None:
shape = np.array(structure).shape
assert shape[1] == 2, 'The shape of structure must be (n, 2)'
for i in range(shape[0]):
assert type(np.array(structure)[:, 0][i]) == str, 'The first position must be element symbol'
assert len(np.array(structure)[:, 1][i]) == 3, 'The second position represents coordinate ' \
'of particle: x, y, z'
geo = structure
elif structure is None:
assert file[-4:] == '.xyz', 'The file is supposed to be .xyz'
geo = []
with open(file) as f:
for line in f.readlines()[2:]:
one_geo = []
symbol, x, y, z = line.split()
one_geo.append(symbol)
one_geo.append([float(x), float(y), float(z)])
geo.append(one_geo)
return geo
def get_molecular_data(
geometry,
charge=0,
multiplicity=1,
basis='sto-3g',
method='scf',
if_save=True,
if_print=True,
name="",
file_path="."
):
r"""计算分子的必要信息,包括单体积分(one-body integrations)和双体积分(two-body integrations),
以及用选定的方法计算基态的能量。
Args:
geometry (str): 分子的几何信息
charge (int, optional): 分子的电荷,默认值为 0
multiplicity (int, optional): 分子的多重度,默认值为 1
basis (str, optional): 常用的基组是 ``'sto-3g'`` 、 ``'6-31g'`` 等,默认的基组是 ``'sto-3g'``,更多的基组选择可以参考网站
https://psicode.org/psi4manual/master/basissets_byelement.html#apdx-basiselement
method (str, optional): 用于计算基态能量的方法,包括 ``'scf'`` 和 ``'fci'`` ,默认方法为 ``'scf'``
if_save (bool, optional): 是否需要将分子信息存储成 .hdf5 文件,默认为 ``True``
if_print (bool, optional): 是否需要打印出选定方法 (method) 计算出的分子基态能量,默认为 ``True``
name (str, optional): 命名储存的文件,默认为 ``""``
file_path (str, optional): 文件的储存路径,默认为 ``"."``
Returns:
MolecularData: 包含分子所有信息的类
"""
methods = ['scf', 'fci']
assert method in methods, 'We provide 2 methods: scf and fci'
if if_save is True:
path = file_path + '/qchem_data/'
folder = os.path.exists(path)
if not folder:
os.makedirs(path)
if name == "":
elements = np.array(geometry)[:, 0]
symbol, counts = np.unique(elements, return_counts=True)
filename = path
for i in range(len(symbol)):
filename += symbol[i]+str(counts[i])+'_'
filename += basis + '_' + method + '.hdf5'
else:
if name[-5:] == '.hdf5':
filename = name
else:
filename = name + '.hdf5'
molecule = MolecularData(geometry,
basis=basis,
multiplicity=multiplicity,
charge=charge,
filename=filename)
_run_psi4(molecule,
charge,
multiplicity,
method,
basis,
if_print,
if_save)
return molecule
def active_space(electrons,
orbitals,
multiplicity=1,
active_electrons=None,
active_orbitals=None):
r"""对于给定的活跃电子和活跃轨道计算相应的活跃空间(active space)。
Args:
electrons (int): 电子数
orbitals (int): 轨道数
multiplicity (int, optional): 自旋多重度
active_electrons (int, optional): 活跃 (active) 电子数,默认情况为所有电子均为活跃电子
active_orbitals (int, optional): 活跃 (active) 轨道数,默认情况为所有轨道均为活跃轨道
Returns:
tuple: 核心轨道和活跃轨道的索引
"""
assert type(electrons) == int and electrons > 0, 'Number of electrons must be positive integer.'
assert type(orbitals) == int and orbitals > 0, 'Number of orbitals must be positive integer.'
assert type(multiplicity) == int and multiplicity >= 0, 'The multiplicity must be non-negative integer.'
if active_electrons is None:
no_core_orbitals = 0
core_orbitals = []
else:
assert type(active_electrons) == int, 'The number of active electrons must be integer.'
assert active_electrons > 0, 'The number of active electrons must be greater than 0.'
assert electrons >= active_electrons, 'The number of electrons should more than or equal ' \
'to the number of active electrons.'
assert active_electrons >= multiplicity - 1, 'The number of active electrons should greater than ' \
'or equal to multiplicity - 1.'
assert multiplicity % 2 != active_electrons % 2, 'Mulitiplicity and active electrons should be one odd ' \
'and the other one even.'
no_core_orbitals = (electrons - active_electrons) // 2
core_orbitals = list(np.arange(0, no_core_orbitals))
if active_orbitals is None:
active_orbitals = list(np.arange(no_core_orbitals, orbitals))
else:
assert type(active_orbitals) == int, 'The number of active orbitals must be integer.'
assert active_orbitals > 0, 'The number of active orbitals must be greater than 0.'
assert no_core_orbitals + active_orbitals <= orbitals, 'The summation of core orbitals and active ' \
'orbitals should be smaller than orbitals.'
assert no_core_orbitals + active_orbitals > (electrons + multiplicity - 1) / 2, \
'The summation of core orbitals and active orbitals should be greater than ' \
'(electrons + multiplicity - 1)/2.'
active_orbitals = list(np.arange(no_core_orbitals, no_core_orbitals + active_orbitals))
return core_orbitals, active_orbitals
def fermionic_hamiltonian(molecule,
filename=None,
multiplicity=1,
active_electrons=None,
active_orbitals=None):
r"""计算给定分子的费米哈密顿量。
Args:
molecule (MolecularData): 包含分子所有信息的类
filename (str, optional): 分子的 .hdf5 文件的路径
multiplicity (int, optional): 自旋多重度
active_electrons (int, optional): 活跃 (active) 电子数,默认情况为所有电子均为活跃电子
active_orbitals (int, optional): 活跃 (active) 轨道数,默认情况为所有轨道均为活跃轨道
Returns:
openfermion.ops.operators.qubit_operator.QubitOperator: openfermion 格式的哈密顿量
"""
if molecule is None:
assert type(filename) == str, 'Please provide the path of .hdf5 file.'
assert filename[-5:] == '.hdf5', 'The filename is supposed to be .hdf5 file'
molecule = MolecularData(filename=filename)
core_orbitals, active_orbitals = active_space(
molecule.n_electrons,
molecule.n_orbitals,
multiplicity,
active_electrons,
active_orbitals)
terms_molecular_hamiltonian = molecule.get_molecular_hamiltonian(
occupied_indices=core_orbitals, active_indices=active_orbitals
)
fermionic_hamiltonian = openfermion.transforms.get_fermion_operator(terms_molecular_hamiltonian)
return fermionic_hamiltonian
def spin_hamiltonian(molecule,
filename=None,
multiplicity=1,
mapping_method='jordan_wigner',
active_electrons=None,
active_orbitals=None):
r"""生成 Paddle Quantum 格式的哈密顿量
Args:
molecule (openfermion.ops.operators.qubit_operator.QubitOperator): openfermion 格式的哈密顿量
filename (str, optional): 分子的 .hdf5 文件的路径
multiplicity (int, optional): 自旋多重度
mapping_method (str, optional): 映射方法,这里默认为 ``'jordan_wigner'`` ,此外还提供 ``'bravyi_kitaev'``
active_electrons (int, optional): 活跃 (active) 电子数,默认情况为所有电子均为活跃电子
active_orbitals (int, optional): 活跃 (active) 轨道数默认情况为所有轨道均为活跃轨道
Returns:
paddle_quantum.utils.Hamiltonian: Paddle Quantum 格式的哈密顿量
"""
assert mapping_method in ['jordan_wigner', 'bravyi_kitaev'], "Please choose the mapping " \
"in ['jordan_wigner', 'bravyi_kitaev']."
fermionic_h = fermionic_hamiltonian(molecule,
filename,
multiplicity,
active_electrons,
active_orbitals)
if mapping_method == 'jordan_wigner':
spin_h = transforms.jordan_wigner(fermionic_h)
elif mapping_method == 'bravyi_kitaev':
spin_h = transforms.bravyi_kitaev(fermionic_h)
return _hamiltonian_transformation(spin_h, tol=1e-8)
...@@ -44,6 +44,7 @@ def shadow_sample(state, num_qubits, sample_shots, mode, hamiltonian=None, metho ...@@ -44,6 +44,7 @@ def shadow_sample(state, num_qubits, sample_shots, mode, hamiltonian=None, metho
代码示例: 代码示例:
.. code-block:: python .. code-block:: python
from paddle_quantum.shadow import shadow_sample from paddle_quantum.shadow import shadow_sample
from paddle_quantum.state import vec_random from paddle_quantum.state import vec_random
from paddle_quantum.utils import Hamiltonian from paddle_quantum.utils import Hamiltonian
...@@ -55,8 +56,8 @@ def shadow_sample(state, num_qubits, sample_shots, mode, hamiltonian=None, metho ...@@ -55,8 +56,8 @@ def shadow_sample(state, num_qubits, sample_shots, mode, hamiltonian=None, metho
ham = [[0.1, 'x1'], [0.2, 'y0']] ham = [[0.1, 'x1'], [0.2, 'y0']]
ham = Hamiltonian(ham) ham = Hamiltonian(ham)
sample_data_lbcs, beta_lbcs = shadow_sample(state, n_qubit, sample_shots, 'state_vector', "LBCS", ham) sample_data_lbcs, beta_lbcs = shadow_sample(state, n_qubit, sample_shots, 'state_vector', ham, "LBCS")
sample_data_aps = shadow_sample(state, n_qubit, sample_shots, 'state_vector', "APS", ham) sample_data_aps = shadow_sample(state, n_qubit, sample_shots, 'state_vector', ham, "APS")
print('sample data CS = ', sample_data_cs) print('sample data CS = ', sample_data_cs)
print('sample data LBCS = ', sample_data_lbcs) print('sample data LBCS = ', sample_data_lbcs)
......
# 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.
"""
Trotter Hamiltonian time evolution circuit module
"""
from paddle_quantum.utils import Hamiltonian
from paddle_quantum.circuit import UAnsatz
import warnings
import numpy as np
import re
import paddle
PI = paddle.to_tensor(np.pi, dtype='float64')
def construct_trotter_circuit(
circuit: UAnsatz,
hamiltonian: Hamiltonian,
tau: float,
steps: int,
method: str = 'suzuki',
order: int = 1,
grouping: str = None,
coefficient: np.ndarray or paddle.Tensor = None,
permutation: np.ndarray = None
):
r"""向 circuit 的后面添加 trotter 时间演化电路,即给定一个系统的哈密顿量 H,该电路可以模拟系统的时间演化 :math:`U_{cir}~ e^{-iHt}` 。
Args:
circuit (UAnsatz): 需要添加时间演化电路的 UAnsatz 对象
hamiltonian (Hamiltonian): 需要模拟时间演化的系统的哈密顿量 H
tau (float): 每个 trotter 块的演化时间长度
steps (int): 添加多少个 trotter 块(提示: ``steps * tau`` 即演化的时间总长度 t)
method (str): 搭建时间演化电路的方法,默认为 ``'suzuki'`` ,即使用 Trotter-Suzuki 分解。可以设置为 ``'custom'`` 来使用自定义的演化策略
(需要用 permutation 和 coefficient 来定义)
order (int): Trotter-Suzuki decomposition 的阶数,默认为 ``1`` ,仅在使用 ``method='suzuki'`` 时有效
grouping (str): 是否对哈密顿量进行指定策略的重新排列,默认为 ``None`` ,支持 ``'xyz'`` 和 ``'even_odd'`` 两种方法
coefficient (array or Tensor): 自定义时间演化电路的系数,对应哈密顿量中的各项,默认为 ``None`` ,仅在 ``method='custom'`` 时有效
permutation (array): 自定义哈密顿量的排列方式,默认为 ``None`` ,仅在 ``method='custom'`` 时有效
代码示例:
.. code-block:: python
from paddle_quantum.utils import Hamiltonian
from paddle_quantum.circuit import UAnsatz
from paddle_quantum.trotter import construct_trotter_circuit, get_1d_heisenberg_hamiltonian
import numpy as np
h = get_1d_heisenberg_hamiltonian(length=3)
cir = UAnsatz(h.n_qubits)
t = 1
r = 10
# 1st order product formula (PF) circuit
construct_trotter_circuit(cir, h, tau=t/r, steps=r)
# 2nd order product formula (PF) circuit
construct_trotter_circuit(cir, h, tau=t/r, steps=r, order=2)
# higher order product formula (PF) circuit
construct_trotter_circuit(cir, h, tau=t/r, steps=r, order=10)
# customize coefficient and permutation
# the following codes is equivalent to adding the 1st order PF
permutation = np.arange(h.n_terms)
coefficients = np.ones(h.n_terms)
construct_trotter_circuit(cir, h, tau=t/r, steps=r, method='custom',
permutation=permutation, coefficient=coefficients)
Hint:
对于该函数的原理以及其使用方法更详细的解释,可以参考量桨官网中量子模拟部分的教程 https://qml.baidu.com/tutorials/overview.html
"""
# check the legitimacy of the inputs (coefficient and permutation)
def check_input_legitimacy(arg_in):
if not isinstance(arg_in, np.ndarray) and not isinstance(arg_in, paddle.Tensor):
arg_out = np.array(arg_in)
else:
arg_out = arg_in
if arg_out.ndim == 1 and isinstance(arg_out, np.ndarray):
arg_out = arg_out.reshape(1, arg_out.shape[0])
elif arg_out.ndim == 1 and isinstance(arg_out, paddle.Tensor):
arg_out = arg_out.reshape([1, arg_out.shape[0]])
return arg_out
# check compatibility between input method and customization arguments
if (permutation is not None or coefficient is not None) and (method != 'custom'):
warning_message = 'method {} is not compatible with customized permutation ' \
'or coefficient and will be overlooked'.format(method)
method = 'custom'
warnings.warn(warning_message, RuntimeWarning)
# check the legitimacy of inputs
if method == 'suzuki':
if order > 2 and order % 2 != 0 and type(order) != int:
raise ValueError('The order of the trotter-suzuki decomposition should be either 1, 2 or 2k (k an integer)'
', got order = %i' % order)
# check and reformat inputs for 'custom' mode
elif method == 'custom':
# check permutation
if permutation is not None:
permutation = np.array(check_input_legitimacy(permutation), dtype=int)
# give a warning for using permutation and grouping at the same time
if grouping:
warning_message = 'Using specified permutation and automatic grouping {} at the same time, the ' \
'permutation will act on the grouped Hamiltonian'.format(grouping)
warnings.warn(warning_message, RuntimeWarning)
# check coefficient
if coefficient is not None:
coefficient = check_input_legitimacy(coefficient)
# if the permutation is not specified, then set it to [[1, 2, ...], ...]
if coefficient is not None and permutation is None:
permutation = np.arange(hamiltonian.n_terms) if coefficient.ndim == 1 \
else np.arange(hamiltonian.n_terms).reshape(1, hamiltonian.n_terms).repeat(coefficient.shape[0], axis=0)
permutation = np.arange(hamiltonian.n_terms).reshape(1, hamiltonian.n_terms)
permutation = permutation.repeat(coefficient.shape[0], axis=0)
# if the coefficient is not specified, set a uniform (normalized) coefficient
if permutation is not None and coefficient is None:
coefficient = 1 / len(permutation) * np.ones_like(permutation)
# the case that the shapes of input coefficient and permutations don't match
if tuple(permutation.shape) != tuple(coefficient.shape):
# case not-allowed
if permutation.shape[1] != coefficient.shape[1]:
raise ValueError('Shape of the permutation and coefficient array don\'t match, got {} and {}'.format(
tuple(permutation.shape), tuple(coefficient.shape)
))
# cases can be fixed by repeating one of the two inputs
elif permutation.shape[0] != coefficient.shape[0] and permutation[0] == 1:
permutation = permutation.repeat(coefficient.shape[0])
elif permutation.shape[0] != coefficient.shape[0] and coefficient[0] == 1:
if isinstance(coefficient, paddle.Tensor):
coefficient = paddle.stack([coefficient for _ in range(permutation.shape[0])])\
.reshape([permutation.shape[0], permutation.shape[1]])
elif isinstance((coefficient, np.ndarray)):
coefficient = coefficient.repeat(permutation.shape[0])
# group the hamiltonian according to the input
if not grouping:
grouped_hamiltonian = [hamiltonian]
elif grouping == 'xyz':
grouped_hamiltonian = __group_hamiltonian_xyz(hamiltonian=hamiltonian)
elif grouping == 'even_odd':
grouped_hamiltonian = __group_hamiltonian_even_odd(hamiltonian=hamiltonian)
else:
raise ValueError("Grouping method %s is not supported, valid key words: 'xyz', 'even_odd'" % grouping)
# apply trotter blocks
for step in range(steps):
if method == 'suzuki':
_add_trotter_block(circuit=circuit, tau=tau, grouped_hamiltonian=grouped_hamiltonian, order=order)
elif method == 'custom':
_add_custom_block(circuit=circuit, tau=tau, grouped_hamiltonian=grouped_hamiltonian,
custom_coefficients=coefficient, permutation=permutation)
else:
raise ValueError("The method %s is not supported, valid method keywords: 'suzuki', 'custom'" % method)
def __get_suzuki_num(order):
r"""计算阶数为 order 的 suzuki product formula 的 trotter 数。
"""
if order == 1 or order == 2:
n_suzuki = order
elif order > 2 and order % 2 == 0:
n_suzuki = 2 * 5 ** (order // 2 - 1)
else:
raise ValueError('The order of the trotter-suzuki decomposition should be either 1, 2 or 2k (k an integer)'
', got order = %i' % order)
return n_suzuki
def __sort_pauli_word(pauli_word, site):
r""" 将 pauli_word 按照 site 的大小进行排列,并同时返回排序后的 pauli_word 和 site。
Note:
这是一个内部函数,一般你不需要直接使用它。
"""
sort_index = np.argsort(np.array(site))
return ''.join(np.array(list(pauli_word))[sort_index].tolist()), np.array(site)[sort_index]
def _add_trotter_block(circuit, tau, grouped_hamiltonian, order):
r""" 添加一个 trotter 块,i.e. :math:`e^{-iH\tau}`,并使用 Trotter-Suzuki 分解对其进行展开。
Args:
circuit (UAnsatz): 需要添加 trotter 块的电路
tau (float or tensor): 该 trotter 块的演化时间
grouped_hamiltonian (list): 一个由 Hamiltonian 对象组成的列表,该函数会默认该列表中的哈密顿量为 Trotter-Suzuki 展开的基本项
order (int): Trotter-Suzuki 展开的阶数
Note (关于 grouped_hamiltonian 的使用方法):
以二阶的 trotter-suzki 分解 S2(t) 为例,若 grouped_hamiltonian = [H_1, H_2],则会按照
(H_1, t/2)(H_2, t/2)(H_2, t/2)(H_1, t/2) 的方法进行添加 trotter 电路
特别地,若用户没有预先对 Hamiltonian 进行 grouping 的话,传入一个单个的 Hamiltonian 对象,则该函数会按照该 Hamiltonian
的顺序进行正则(canonical)的分解:依然以二阶 trotter 为例,若传入单个 H,则添加 (H[0:-1:1], t/2)(H[-1:0:-1], t/2) 的电路
Warning:
本函数一般情况下为内部函数,不会对输入的合法性进行检测和尝试修正。推荐使用 construct_trotter_circuit() 来构建时间演化电路
"""
if order == 1:
__add_first_order_trotter_block(circuit, tau, grouped_hamiltonian)
elif order == 2:
__add_second_order_trotter_block(circuit, tau, grouped_hamiltonian)
else:
__add_higher_order_trotter_block(circuit, tau, grouped_hamiltonian, order)
pass
def _add_custom_block(circuit, tau, grouped_hamiltonian, custom_coefficients, permutation):
r""" 添加一个自定义形式的 trotter 块
Args:
circuit (UAnsatz): 需要添加 trotter 块的电路
tau (float or tensor): 该 trotter 块的演化时间
grouped_hamiltonian (list): 一个由 Hamiltonian 对象组成的列表,该函数会默认该列表中的哈密顿量为 trotter-suzuki 展开的基本项
order (int): trotter-suzuki 展开的阶数
permutation (np.ndarray): 自定义置换
custom_coefficients (np.ndarray or Tensor): 自定义系数
Warning:
本函数一般情况下为内部函数,不会对输入的合法性进行检测和尝试修正。推荐使用 construct_trotter_circuit() 来构建时间演化电路
"""
# combine the grouped hamiltonian into one single hamiltonian
hamiltonian = sum(grouped_hamiltonian, Hamiltonian([]))
# apply trotter circuit according to coefficient and
h_coeffs, pauli_words, sites = hamiltonian.decompose_with_sites()
for i in range(permutation.shape[0]):
for term_index in range(permutation.shape[1]):
custom_coeff = custom_coefficients[i][term_index]
term_index = permutation[i][term_index]
pauli_word, site = __sort_pauli_word(pauli_words[term_index], sites[term_index])
coeff = h_coeffs[term_index] * custom_coeff
add_n_pauli_gate(circuit, 2 * tau * coeff, pauli_word, site)
def __add_first_order_trotter_block(circuit, tau, grouped_hamiltonian, reverse=False):
r""" 添加一阶 trotter-suzuki 分解的时间演化块
Notes:
这是一个内部函数,你不需要使用它
"""
if not reverse:
for hamiltonian in grouped_hamiltonian:
assert isinstance(hamiltonian, Hamiltonian)
# decompose the Hamiltonian into 3 lists
coeffs, pauli_words, sites = hamiltonian.decompose_with_sites()
# apply rotational gate of each term
for term_index in range(len(coeffs)):
# get the sorted pauli_word and site (an array of qubit indices) according to their qubit indices
pauli_word, site = __sort_pauli_word(pauli_words[term_index], sites[term_index])
add_n_pauli_gate(circuit, 2 * tau * coeffs[term_index], pauli_word, site)
# in the reverse mode, if the Hamiltonian is a single element list, reverse the order its each term
else:
if len(grouped_hamiltonian) == 1:
coeffs, pauli_words, sites = grouped_hamiltonian[0].decompose_with_sites()
for term_index in reversed(range(len(coeffs))):
pauli_word, site = __sort_pauli_word(pauli_words[term_index], sites[term_index])
add_n_pauli_gate(circuit, 2 * tau * coeffs[term_index], pauli_word, site)
# otherwise, if it is a list of multiple Hamiltonian, only reverse the order of that list
else:
for hamiltonian in reversed(grouped_hamiltonian):
assert isinstance(hamiltonian, Hamiltonian)
coeffs, pauli_words, sites = hamiltonian.decompose_with_sites()
for term_index in range(len(coeffs)):
pauli_word, site = __sort_pauli_word(pauli_words[term_index], sites[term_index])
add_n_pauli_gate(circuit, 2 * tau * coeffs[term_index], pauli_word, site)
def __add_second_order_trotter_block(circuit, tau, grouped_hamiltonian):
r""" 添加二阶 trotter-suzuki 分解的时间演化块
Notes:
这是一个内部函数,你不需要使用它
"""
__add_first_order_trotter_block(circuit, tau / 2, grouped_hamiltonian)
__add_first_order_trotter_block(circuit, tau / 2, grouped_hamiltonian, reverse=True)
def __add_higher_order_trotter_block(circuit, tau, grouped_hamiltonian, order):
r""" 添加高阶(2k 阶) trotter-suzuki 分解的时间演化块
Notes:
这是一个内部函数,你不需要使用它
"""
assert order % 2 == 0
p_values = get_suzuki_p_values(order)
if order - 2 != 2:
for p in p_values:
__add_higher_order_trotter_block(circuit, p * tau, grouped_hamiltonian, order - 2)
else:
for p in p_values:
__add_second_order_trotter_block(circuit, p * tau, grouped_hamiltonian)
def add_n_pauli_gate(circuit, theta, pauli_word, which_qubits):
r""" 添加一个对应着 N 个泡利算符张量积的旋转门,例如 :math:`e^{-\theta/2 * X \otimes I \otimes X \otimes Y}`
Args:
circuit (UAnsatz): 需要添加门的电路
theta (tensor or float): 旋转角度
pauli_word (str): 泡利算符组成的字符串,例如 ``"XXZ"``
which_qubits (list or np.ndarray): ``pauli_word`` 中的每个算符所作用的量子比特编号
"""
if isinstance(which_qubits, tuple) or isinstance(which_qubits, list):
which_qubits = np.array(which_qubits)
elif not isinstance(which_qubits, np.ndarray):
raise ValueError('which_qubits should be either a list, tuple or np.ndarray')
if not isinstance(theta, paddle.Tensor):
theta = paddle.to_tensor(theta, dtype='float64')
# the following assert is not working properly
# assert isinstance(circuit, UAnsatz), 'the circuit should be an UAnstaz object'
# if it is a single-Pauli case, apply the single qubit rotation gate accordingly
if len(which_qubits) == 1:
if re.match(r'X', pauli_word[0], flags=re.I):
circuit.rx(theta, which_qubit=which_qubits[0])
elif re.match(r'Y', pauli_word[0], flags=re.I):
circuit.ry(theta, which_qubit=which_qubits[0])
elif re.match(r'Z', pauli_word[0], flags=re.I):
circuit.rz(theta, which_qubit=which_qubits[0])
# if it is a multiple-Pauli case, implement a Pauli tensor rotation
# we use a scheme described in 4.7.3 of Nielson & Chuang, that is, basis change + tensor Z rotation
# (tensor Z rotation is 2 layers of CNOT and a Rz rotation)
else:
which_qubits.sort()
# Change the basis for qubits on which the acting operators are not 'Z'
for qubit_index in range(len(which_qubits)):
if re.match(r'X', pauli_word[qubit_index], flags=re.I):
circuit.h(which_qubits[qubit_index])
elif re.match(r'Y', pauli_word[qubit_index], flags=re.I):
circuit.rx(PI / 2, which_qubits[qubit_index])
# Add a Z tensor n rotational gate
for i in range(len(which_qubits) - 1):
circuit.cnot([which_qubits[i], which_qubits[i + 1]])
circuit.rz(theta, which_qubits[-1])
for i in reversed(range(len(which_qubits) - 1)):
circuit.cnot([which_qubits[i], which_qubits[i + 1]])
# Change the basis for qubits on which the acting operators are not 'Z'
for qubit_index in range(len(which_qubits)):
if re.match(r'X', pauli_word[qubit_index], flags=re.I):
circuit.h(which_qubits[qubit_index])
elif re.match(r'Y', pauli_word[qubit_index], flags=re.I):
circuit.rx(- PI / 2, which_qubits[qubit_index])
def __group_hamiltonian_xyz(hamiltonian):
r""" 将哈密顿量拆分成 X、Y、Z 以及剩余项四个部分,并返回由他们组成的列表
Args:
hamiltonian (Hamiltonian): Paddle Quantum 中的 Hamiltonian 类
Notes:
X、Y、Z 项分别指的是该项的 Pauli word 只含有 X、Y、Z,例如 'XXXY' 就会被分类到剩余项
"""
grouped_hamiltonian = []
coeffs, pauli_words, sites = hamiltonian.decompose_with_sites()
grouped_terms_indices = []
left_over_terms_indices = []
for pauli in ['X', 'Y', 'Z']:
terms_indices_to_be_grouped = []
for term_index in range(len(coeffs)):
pauli_word = pauli_words[term_index]
assert isinstance(pauli_word, str), "Each pauli word should be a string type"
if pauli_word.count(pauli) == len(pauli_word) or pauli_word.count(pauli.lower()) == len(pauli_word):
terms_indices_to_be_grouped.append(term_index)
grouped_terms_indices.extend(terms_indices_to_be_grouped)
grouped_hamiltonian.append(hamiltonian[terms_indices_to_be_grouped])
for term_index in range(len(coeffs)):
if term_index not in grouped_terms_indices:
left_over_terms_indices.append(term_index)
if len(left_over_terms_indices):
for term_index in left_over_terms_indices:
grouped_hamiltonian.append(hamiltonian[term_index])
return grouped_hamiltonian
def __group_hamiltonian_even_odd(hamiltonian):
r""" 将哈密顿量拆分为奇数项和偶数项两部分
Args:
hamiltonian (Hamiltonian):
Warning:
注意该分解方法并不能保证拆分后的奇数项和偶数项内部一定相互对易,因此不正确的使用该方法反而会增加 trotter 误差。
请在使用该方法前检查哈密顿量是否为可以进行奇偶分解:例如一维最近邻相互作用系统的哈密顿量可以进行奇偶分解
"""
grouped_hamiltonian = []
coeffs, pauli_words, sites = hamiltonian.decompose_with_sites()
grouped_terms_indices = []
left_over_terms_indices = []
for offset in range(2):
terms_indices_to_be_grouped = []
for term_index in range(len(coeffs)):
if not isinstance(sites[term_index], np.ndarray):
site = np.array(sites[term_index])
else:
site = sites[term_index]
site.sort()
if site.min() % 2 == offset:
terms_indices_to_be_grouped.append(term_index)
grouped_terms_indices.extend(terms_indices_to_be_grouped)
grouped_hamiltonian.append(hamiltonian[terms_indices_to_be_grouped])
for term_index in range(len(coeffs)):
if term_index not in grouped_terms_indices:
left_over_terms_indices.append(term_index)
if len(left_over_terms_indices):
grouped_hamiltonian.append(hamiltonian[left_over_terms_indices])
return grouped_hamiltonian
def get_suzuki_permutation(length, order):
r""" 计算 Suzuki 分解对应的置换数组。
Args:
length (int): 对应哈密顿量中的项数,即需要置换的项数
order (int): Suzuki 分解的阶数
Returns:
np.ndarray : 置换数组
"""
if order == 1:
return np.arange(length)
if order == 2:
return np.vstack([np.arange(length), np.arange(length - 1, -1, -1)])
else:
return np.vstack([get_suzuki_permutation(length=length, order=order - 2) for _ in range(5)])
def get_suzuki_p_values(k):
r""" 计算 Suzuki 分解中递推关系中的因数 p(k)。
Args:
k (int): Suzuki 分解的阶数
Returns:
list : 一个长度为 5 的列表,其形式为 [p, p, (1 - 4 * p), p, p]
"""
p = 1 / (4 - 4 ** (1 / (k - 1)))
return [p, p, (1 - 4 * p), p, p]
def get_suzuki_coefficients(length, order):
r""" 计算 Suzuki 分解对应的系数数组。
Args:
length (int): 对应哈密顿量中的项数,即需要置换的项数
order (int): Suzuki 分解的阶数
Returns:
np.ndarray : 系数数组
"""
if order == 1:
return np.ones(length)
if order == 2:
return np.vstack([1 / 2 * np.ones(length), 1 / 2 * np.ones(length)])
else:
p_values = get_suzuki_p_values(order)
return np.vstack([get_suzuki_coefficients(length=length, order=order - 2) * p_value
for p_value in p_values])
def get_1d_heisenberg_hamiltonian(
length: int,
j_x: float = 1.,
j_y: float = 1.,
j_z: float = 1.,
h_z: float or np.ndarray = 0.,
periodic_boundary_condition: bool = True
):
r"""生成一个一维海森堡链的哈密顿量。
Args:
length (int): 链长
j_x (float): x 方向的自旋耦合强度 Jx,默认为 ``1``
j_y (float): y 方向的自旋耦合强度 Jy,默认为 ``1``
j_z (float): z 方向的自旋耦合强度 Jz,默认为 ``1``
h_z (float or np.ndarray): z 方向的磁场,默认为 ``0`` ,若输入为单个 float 则认为是均匀磁场(施加在每一个格点上)
periodic_boundary_condition (bool): 是否考虑周期性边界条件,即 l + 1 = 0,默认为 ``True``
Returns:
Hamiltonian :该海森堡链的哈密顿量
"""
# Pauli words for Heisenberg interactions and their coupling strength
interactions = ['XX', 'YY', 'ZZ']
interaction_strength = [j_x, j_y, j_z]
pauli_str = [] # The Pauli string defining the Hamiltonian
# add terms (0, 1), (1, 2), ..., (n - 1, n) by adding [j_x, 'X0, X1'], ... into the Pauli string
for i in range(length - 1):
for interaction_idx in range(len(interactions)):
term_str = ''
interaction = interactions[interaction_idx]
for idx_word in range(len(interaction)):
term_str += interaction[idx_word] + str(i + idx_word)
if idx_word != len(interaction) - 1:
term_str += ', '
pauli_str.append([interaction_strength[interaction_idx], term_str])
# add interactions on (0, n) for closed periodic boundary condition
if periodic_boundary_condition:
boundary_sites = [0, length - 1]
for interaction in interactions:
term_str = ''
for idx_word in range(len(interaction)):
term_str += interaction[idx_word] + str(boundary_sites[idx_word])
if idx_word != len(interaction) - 1:
term_str += ', '
pauli_str.append([1, term_str])
# add magnetic field, if h_z is a single value, then add a uniform field on each site
if isinstance(h_z, np.ndarray) or isinstance(h_z, list) or isinstance(h_z, tuple):
assert len(h_z) == length, 'length of the h_z array do not match the length of the system'
for i in range(length):
pauli_str.append([h_z[i], 'Z' + str(i)])
elif h_z:
for i in range(length):
pauli_str.append([h_z, 'Z' + str(i)])
# instantiate a Hamiltonian object with the Pauli string
h = Hamiltonian(pauli_str)
return h
...@@ -40,6 +40,7 @@ import matplotlib.animation as animation ...@@ -40,6 +40,7 @@ import matplotlib.animation as animation
__all__ = [ __all__ = [
"partial_trace", "partial_trace",
"state_fidelity", "state_fidelity",
"trace_distance",
"gate_fidelity", "gate_fidelity",
"purity", "purity",
"von_neumann_entropy", "von_neumann_entropy",
...@@ -53,6 +54,10 @@ __all__ = [ ...@@ -53,6 +54,10 @@ __all__ = [
"negativity", "negativity",
"logarithmic_negativity", "logarithmic_negativity",
"is_ppt", "is_ppt",
"haar_orthogonal",
"haar_unitary",
"haar_state_vector",
"haar_density_operator",
"Hamiltonian", "Hamiltonian",
"plot_state_in_bloch_sphere", "plot_state_in_bloch_sphere",
"plot_rotation_in_bloch_sphere", "plot_rotation_in_bloch_sphere",
...@@ -164,6 +169,26 @@ def state_fidelity(rho, sigma): ...@@ -164,6 +169,26 @@ def state_fidelity(rho, sigma):
return fidelity return fidelity
def trace_distance(rho, sigma):
r"""计算两个量子态的迹距离。
.. math::
D(\rho, \sigma) = 1 / 2 * \text{tr}|\rho-\sigma|
Args:
rho (numpy.ndarray): 量子态的密度矩阵形式
sigma (numpy.ndarray): 量子态的密度矩阵形式
Returns:
float: 输入的量子态之间的迹距离
"""
assert rho.shape == sigma.shape, 'The shape of two quantum states are different'
A = rho - sigma
distance = 1 / 2 * np.sum(np.abs(np.linalg.eigvals(A)))
return distance
def gate_fidelity(U, V): def gate_fidelity(U, V):
r"""计算两个量子门的保真度。 r"""计算两个量子门的保真度。
...@@ -521,6 +546,100 @@ def is_ppt(density_op): ...@@ -521,6 +546,100 @@ def is_ppt(density_op):
return ppt return ppt
def haar_orthogonal(n):
r"""生成一个服从 Haar random 的正交矩阵。采样算法参考文献:arXiv:math-ph/0609050v2
Args:
n (int): 正交矩阵对应的量子比特数
Returns:
numpy.ndarray: 一个形状为 ``(2**n, 2**n)`` 随机正交矩阵
"""
# Matrix dimension
d = 2 ** n
# Step 1: sample from Ginibre ensemble
g = (np.random.randn(d, d))
# Step 2: perform QR decomposition of G
q, r = np.linalg.qr(g)
# Step 3: make the decomposition unique
lam = np.diag(r) / abs(np.diag(r))
u = q @ np.diag(lam)
return u
def haar_unitary(n):
r"""生成一个服从 Haar random 的酉矩阵。采样算法参考文献:arXiv:math-ph/0609050v2
Args:
n (int): 酉矩阵对应的量子比特数
Returns:
numpy.ndarray: 一个形状为 ``(2**n, 2**n)`` 随机酉矩阵
"""
# Matrix dimension
d = 2 ** n
# Step 1: sample from Ginibre ensemble
g = (np.random.randn(d, d) + 1j * np.random.randn(d, d)) / np.sqrt(2)
# Step 2: perform QR decomposition of G
q, r = np.linalg.qr(g)
# Step 3: make the decomposition unique
lam = np.diag(r) / abs(np.diag(r))
u = q @ np.diag(lam)
return u
def haar_state_vector(n, real=False):
r"""生成一个服从 Haar random 的态矢量。
Args:
n (int): 量子态的量子比特数
real (bool): 生成的态矢量是否为实态矢量,默认为 ``False``
Returns:
numpy.ndarray: 一个形状为 ``(2**n, 1)`` 随机态矢量
"""
# Vector dimension
d = 2 ** n
if real:
# Generate a Haar random orthogonal matrix
o = haar_orthogonal(n)
# Perform u onto |0>, i.e., the first column of o
phi = o[:, 0]
else:
# Generate a Haar random unitary
u = haar_unitary(n)
# Perform u onto |0>, i.e., the first column of u
phi = u[:, 0]
return phi
def haar_density_operator(n, k=None, real=False):
r"""生成一个服从 Haar random 的密度矩阵。
Args:
n (int): 量子态的量子比特数
k (int): 密度矩阵的秩,默认为 ``None``,表示满秩
real (bool): 生成的密度矩阵是否为实矩阵,默认为 ``False``
Returns:
numpy.ndarray: 一个形状为 ``(2**n, 2**n)`` 随机密度矩阵
"""
d = 2 ** n
k = k if k is not None else d
assert 0 < k <= d, 'rank is an invalid number'
if real:
ginibre_matrix = np.random.randn(d, k)
rho = ginibre_matrix @ ginibre_matrix.T
else:
ginibre_matrix = np.random.randn(d, k) + 1j * np.random.randn(d, k)
rho = ginibre_matrix @ ginibre_matrix.conj().T
return rho / np.trace(rho)
class Hamiltonian: class Hamiltonian:
r""" Paddle Quantum 中的 Hamiltonian ``class``。 r""" Paddle Quantum 中的 Hamiltonian ``class``。
...@@ -630,7 +749,7 @@ class Hamiltonian: ...@@ -630,7 +749,7 @@ class Hamiltonian:
r""" 返回哈密顿量中的每一项对应的系数构成的列表 r""" 返回哈密顿量中的每一项对应的系数构成的列表
Returns: Returns:
list :哈密顿量中每一项的系数,i.e.``[1.0, 2.0]`` list :哈密顿量中每一项的系数,i.e. ``[1.0, 2.0]``
""" """
if self.__update_flag: if self.__update_flag:
self.__decompose() self.__decompose()
...@@ -769,25 +888,28 @@ class Hamiltonian: ...@@ -769,25 +888,28 @@ class Hamiltonian:
self.__update_flag = True self.__update_flag = True
def decompose_with_sites(self): def decompose_with_sites(self):
r"""将 pauli_str 分解为系数,泡利字符串的简化形式,以及它们分别作用的量子比特下标 r"""将 pauli_str 分解为系数、泡利字符串的简化形式以及它们分别作用的量子比特下标。
Returns: Returns:
tuple: 包含如下元素的 tuple tuple: 包含如下元素的 tuple:
coefficients (list): 元素为每一项的系数
pauli_words_r (list): 元素为每一项的泡利字符串的简化形式,例如 'Z0, Z1, X3' 这一项的泡利字符串为 'ZZX' - coefficients (list): 元素为每一项的系数
sites (list): 元素为每一项作用的量子比特下标,例如 'Z0, Z1, X3' 这一项的 site 为 [0, 1, 3] - pauli_words_r (list): 元素为每一项的泡利字符串的简化形式,例如 'Z0, Z1, X3' 这一项的泡利字符串为 'ZZX'
- sites (list): 元素为每一项作用的量子比特下标,例如 'Z0, Z1, X3' 这一项的 site 为 [0, 1, 3]
""" """
if self.__update_flag: if self.__update_flag:
self.__decompose() self.__decompose()
return self.coefficients, self.__pauli_words_r, self.__sites return self.coefficients, self.__pauli_words_r, self.__sites
def decompose_pauli_words(self): def decompose_pauli_words(self):
r"""将 pauli_str 分解为系数,泡利字符串,以及它们分别作用的量子比特下标 r"""将 pauli_str 分解为系数和泡利字符串。
Returns: Returns:
tuple: 包含如下元素的 tuple tuple: 包含如下元素的 tuple:
coefficients(list): 元素为每一项的系数
pauli_words(list): 元素为每一项的泡利字符串,例如 'Z0, Z1, X3' 这一项的泡利字符串为 'ZZIX' - coefficients(list): 元素为每一项的系数
- pauli_words(list): 元素为每一项的泡利字符串,例如 'Z0, Z1, X3' 这一项的泡利字符串为 'ZZIX'
""" """
if self.__update_flag: if self.__update_flag:
self.__decompose() self.__decompose()
...@@ -796,7 +918,7 @@ class Hamiltonian: ...@@ -796,7 +918,7 @@ class Hamiltonian:
return self.coefficients, self.__pauli_words return self.coefficients, self.__pauli_words
def construct_h_matrix(self): def construct_h_matrix(self):
r"""构建 Hamiltonian 在 Z 基底下的矩阵 r"""构建 Hamiltonian 在 Z 基底下的矩阵
Returns: Returns:
np.ndarray: Z 基底下的哈密顿量矩阵形式 np.ndarray: Z 基底下的哈密顿量矩阵形式
...@@ -822,29 +944,56 @@ class Hamiltonian: ...@@ -822,29 +944,56 @@ class Hamiltonian:
class SpinOps: class SpinOps:
r"""矩阵表示下的自旋算符,可以用来构建哈密顿量矩阵。 r"""矩阵表示下的自旋算符,可以用来构建哈密顿量矩阵或者自旋可观测量
""" """
def __init__(self, size: int, use_sparse=False): def __init__(self, size: int, use_sparse=False):
r"""SpinOps 的构造函数,用于实例化一个 SpinOps 对象 r"""SpinOps 的构造函数,用于实例化一个 SpinOps 对象
Args: Args:
size (int): 系统的大小(有几个量子比特) size (int): 系统的大小(有几个量子比特)
use_sparse (bool): 是否使用 sparse matrix 计算,默认为 True use_sparse (bool): 是否使用 sparse matrix 计算,默认为 ``False``
""" """
self.size = size self.size = size
self.id = sparse.eye(2, dtype='complex128') self.id = sparse.eye(2, dtype='complex128')
self.sigz = sparse.bsr.bsr_matrix([[1, 0], [0, -1]], dtype='complex64') 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.__sigy = sparse.bsr.bsr_matrix([[0, -1j], [1j, 0]], dtype='complex64')
self.sigx = sparse.bsr.bsr_matrix([[0, 1], [1, 0]], dtype='complex64') self.__sigx = sparse.bsr.bsr_matrix([[0, 1], [1, 0]], dtype='complex64')
self.sigz_p = [] self.__sigz_p = []
self.sigy_p = [] self.__sigy_p = []
self.sigx_p = [] self.__sigx_p = []
self.__sparse = use_sparse self.__sparse = use_sparse
for i in range(self.size): for i in range(self.size):
self.sigz_p.append(self.__direct_prod_op(spin_op=self.sigz, spin_index=i)) 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.__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)) self.__sigx_p.append(self.__direct_prod_op(spin_op=self.__sigx, spin_index=i))
@property
def sigz_p(self):
r""" :math:`Z` 基底下的 :math:`S^z_i` 算符。
Returns:
list : :math:`S^z_i` 算符组成的列表,其中每一项对应不同的 :math:`i`
"""
return self.__sigz_p
@property
def sigy_p(self):
r""" :math:`Z` 基底下的 :math:`S^y_i` 算符。
Returns:
list : :math:`S^y_i` 算符组成的列表,其中每一项对应不同的 :math:`i`
"""
return self.__sigy_p
@property
def sigx_p(self):
r""" :math:`Z` 基底下的 :math:`S^x_i` 算符。
Returns:
list : :math:`S^x_i` 算符组成的列表,其中每一项对应不同的 :math:`i`
"""
return self.__sigx_p
def __direct_prod_op(self, spin_op, spin_index): def __direct_prod_op(self, spin_op, spin_index):
r"""直积,得到第 n 个自旋(量子比特)上的自旋算符 r"""直积,得到第 n 个自旋(量子比特)上的自旋算符
...@@ -879,38 +1028,39 @@ def __input_args_dtype_check( ...@@ -879,38 +1028,39 @@ def __input_args_dtype_check(
该函数实现对输入默认参数的数据类型检查,保证输入函数中的参数为所允许的数据类型 该函数实现对输入默认参数的数据类型检查,保证输入函数中的参数为所允许的数据类型
Args: Args:
show_arrow (bool): 是否展示向量的箭头,默认为False show_arrow (bool): 是否展示向量的箭头,默认为 False
save_gif (bool): 是否存储gif动图,默认10帧,3帧长出bloch向量,7帧转动bloch视角 save_gif (bool): 是否存储 gif 动图
filename (str): 存储的gif动图的名字。 filename (str): 存储的 gif 动图的名字
view_angle (list or tuple): 视图的角度,list内第一个元素为关于xy平面的夹角[0-360],第二个元素为关于xz平面的夹角[0-360] view_angle (list or tuple): 视图的角度,
view_dist (int): 视图的距离,默认为7 第一个元素为关于xy平面的夹角[0-360],第二个元素为关于xz平面的夹角[0-360], 默认为 (30, 45)
view_dist (int): 视图的距离,默认为 7
""" """
if show_arrow is not None: if show_arrow is not None:
assert type(show_arrow) == bool, \ assert type(show_arrow) == bool, \
'the type of show_arrow should be "bool"' 'the type of "show_arrow" should be "bool".'
if save_gif is not None: if save_gif is not None:
assert type(save_gif) == bool, \ assert type(save_gif) == bool, \
'the type of save_gif should be "bool"' 'the type of "save_gif" should be "bool".'
if save_gif: if save_gif:
if filename is not None: if filename is not None:
assert type(filename) == str, \ assert type(filename) == str, \
'the type of filename should be "str"' 'the type of "filename" should be "str".'
other, ext = os.path.splitext(filename) other, ext = os.path.splitext(filename)
assert ext == '.gif', 'The suffix of the file name must be "gif"' assert ext == '.gif', 'The suffix of the file name must be "gif".'
# If it does not exist, create a folder # If it does not exist, create a folder
path, file = os.path.split(filename) path, file = os.path.split(filename)
if not os.path.exists(path): if not os.path.exists(path):
os.makedirs(path) os.makedirs(path)
if view_angle is not None: if view_angle is not None:
assert type(view_angle) == list or type(view_angle) == tuple, \ assert type(view_angle) == list or type(view_angle) == tuple, \
'the type of view_angle should be "list" or "tuple"' 'the type of "view_angle" should be "list" or "tuple".'
for i in range(2): for i in range(2):
assert type(view_angle[i]) == int, \ assert type(view_angle[i]) == int, \
'the type of view_angle[0] and view_angle[1] should be "int"' 'the type of "view_angle[0]" and "view_angle[1]" should be "int".'
if view_dist is not None: if view_dist is not None:
assert type(view_dist) == int, \ assert type(view_dist) == int, \
'the type of view_dist should be "int"' 'the type of "view_dist" should be "int".'
def __density_matrix_convert_to_bloch_vector(density_matrix): def __density_matrix_convert_to_bloch_vector(density_matrix):
...@@ -918,8 +1068,9 @@ def __density_matrix_convert_to_bloch_vector(density_matrix): ...@@ -918,8 +1068,9 @@ def __density_matrix_convert_to_bloch_vector(density_matrix):
Args: Args:
density_matrix (numpy.ndarray): 输入的密度矩阵 density_matrix (numpy.ndarray): 输入的密度矩阵
Returns: Returns:
bloch_vector (numpy.ndarray): 存储bloch向量的x,y,z坐标,向量的模长,向量的颜色 bloch_vector (numpy.ndarray): 存储bloch向量的 x,y,z 坐标,向量的模长,向量的颜色
""" """
# Pauli Matrix # Pauli Matrix
...@@ -956,26 +1107,29 @@ def __plot_bloch_sphere( ...@@ -956,26 +1107,29 @@ def __plot_bloch_sphere(
clear_plt=True, clear_plt=True,
rotating_angle_list=None, rotating_angle_list=None,
view_angle=None, view_angle=None,
view_dist=None view_dist=None,
set_color=None
): ):
r"""将 Bloch 向量展示在 Bloch 球面上 r"""将 Bloch 向量展示在 Bloch 球面上
Args: Args:
ax (Axes3D(fig)): 画布的句柄 ax (Axes3D(fig)): 画布的句柄
bloch_vectors (numpy.ndarray): 存储bloch向量的x,y,z坐标,向量的模长,向量的颜色 bloch_vectors (numpy.ndarray): 存储bloch向量的 x,y,z 坐标,向量的模长,向量的颜色
show_arrow (bool): 是否展示向量的箭头,默认为False show_arrow (bool): 是否展示向量的箭头,默认为 False
clear_plt (bool): 是否要清空画布,默认为True,每次画图的时候清空画布再画图 clear_plt (bool): 是否要清空画布,默认为 True,每次画图的时候清空画布再画图
rotating_angle_list (list): 旋转角度的列表,用于展示旋转轨迹 rotating_angle_list (list): 旋转角度的列表,用于展示旋转轨迹
view_angle (list): 视图的角度,list内第一个元素为关于xy平面的夹角[0-360],第二个元素为关于xz平面的夹角[0-360] view_angle (list): 视图的角度,
view_dist (int): 视图的距离,默认为7 第一个元素为关于xy平面的夹角[0-360],第二个元素为关于xz平面的夹角[0-360], 默认为 (30, 45)
view_dist (int): 视图的距离,默认为 7
set_color (str): 设置指定的颜色,请查阅cmap表,默认为 "红-黑-根据向量的模长渐变" 颜色方案
""" """
# Assign a value to an empty variable # Assign a value to an empty variable
if view_angle is None: if view_angle is None:
view_angle = [30, 45] view_angle = (30, 45)
if view_dist is None: if view_dist is None:
view_dist = 7 view_dist = 7
# Define my_color # Define my_color
if set_color is None:
color = 'rainbow' color = 'rainbow'
black_code = '#000000' black_code = '#000000'
red_code = '#F24A29' red_code = '#F24A29'
...@@ -987,6 +1141,8 @@ def __plot_bloch_sphere( ...@@ -987,6 +1141,8 @@ def __plot_bloch_sphere(
) )
map_vir = plt.get_cmap(black_to_red) map_vir = plt.get_cmap(black_to_red)
color = map_vir(bloch_vectors[:, 4]) color = map_vir(bloch_vectors[:, 4])
else:
color = set_color
# Set the view angle and view distance # Set the view angle and view distance
ax.view_init(view_angle[0], view_angle[1]) ax.view_init(view_angle[0], view_angle[1])
...@@ -1097,7 +1253,7 @@ def __plot_bloch_sphere( ...@@ -1097,7 +1253,7 @@ def __plot_bloch_sphere(
# Draw the data points # Draw the data points
if bloch_vectors is not None: if bloch_vectors is not None:
ax.scatter( ax.scatter(
bloch_vectors[:, 0], bloch_vectors[:, 1], bloch_vectors[:, 2], c=color, alpha=1 bloch_vectors[:, 0], bloch_vectors[:, 1], bloch_vectors[:, 2], c=color, alpha=1.0
) )
# if show the rotating angle # if show the rotating angle
...@@ -1124,7 +1280,7 @@ def __plot_bloch_sphere( ...@@ -1124,7 +1280,7 @@ def __plot_bloch_sphere(
if show_arrow: if show_arrow:
ax.quiver( ax.quiver(
0, 0, 0, bloch_vectors[:, 0], bloch_vectors[:, 1], bloch_vectors[:, 2], 0, 0, 0, bloch_vectors[:, 0], bloch_vectors[:, 1], bloch_vectors[:, 2],
arrow_length_ratio=0.05, color=color, alpha=1, arrow_length_ratio=0.05, color=color, alpha=1.0
) )
...@@ -1134,7 +1290,8 @@ def plot_state_in_bloch_sphere( ...@@ -1134,7 +1290,8 @@ def plot_state_in_bloch_sphere(
save_gif=False, save_gif=False,
filename=None, filename=None,
view_angle=None, view_angle=None,
view_dist=None view_dist=None,
set_color=None
): ):
r"""将输入的量子态展示在 Bloch 球面上 r"""将输入的量子态展示在 Bloch 球面上
...@@ -1143,27 +1300,32 @@ def plot_state_in_bloch_sphere( ...@@ -1143,27 +1300,32 @@ def plot_state_in_bloch_sphere(
show_arrow (bool): 是否展示向量的箭头,默认为 ``False`` show_arrow (bool): 是否展示向量的箭头,默认为 ``False``
save_gif (bool): 是否存储 gif 动图,默认为 ``False`` save_gif (bool): 是否存储 gif 动图,默认为 ``False``
filename (str): 存储的 gif 动图的名字 filename (str): 存储的 gif 动图的名字
view_angle (list or tuple): 视图的角度,list 内第一个元素为关于 xy 平面的夹角 [0-360],第二个元素为关于 xz 平面的夹角 [0-360] view_angle (list or tuple): 视图的角度,
第一个元素为关于 xy 平面的夹角 [0-360],第二个元素为关于 xz 平面的夹角 [0-360], 默认为 ``(30, 45)``
view_dist (int): 视图的距离,默认为 7 view_dist (int): 视图的距离,默认为 7
set_color (str): 若要设置指定的颜色,请查阅 ``cmap`` 表。默认为红色到黑色的渐变颜色
""" """
# Check input data # Check input data
__input_args_dtype_check(show_arrow, save_gif, filename, view_angle, view_dist) __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, \ 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"' 'the type of "state" must be "list" or "paddle.Tensor" or "np.ndarray".'
if type(state) == paddle.Tensor or type(state) == np.ndarray: if type(state) == paddle.Tensor or type(state) == np.ndarray:
state = [state] state = [state]
state_len = len(state) state_len = len(state)
assert state_len >= 1, 'input data is NULL.' assert state_len >= 1, '"state" is NULL.'
for i in range(state_len): for i in range(state_len):
assert type(state[i]) == paddle.Tensor or type(state[i]) == np.ndarray, \ assert type(state[i]) == paddle.Tensor or type(state[i]) == np.ndarray, \
'the type of input data should be "paddle.Tensor" or "numpy.ndarray".' 'the type of "state[i]" should be "paddle.Tensor" or "numpy.ndarray".'
if set_color is not None:
assert type(set_color) == str, \
'the type of "set_color" should be "str".'
# Assign a value to an empty variable # Assign a value to an empty variable
if filename is None: if filename is None:
filename = 'state_in_bloch_sphere.gif' filename = 'state_in_bloch_sphere.gif'
if view_angle is None: if view_angle is None:
view_angle = [30, 45] view_angle = (30, 45)
if view_dist is None: if view_dist is None:
view_dist = 7 view_dist = 7
...@@ -1187,41 +1349,36 @@ def plot_state_in_bloch_sphere( ...@@ -1187,41 +1349,36 @@ def plot_state_in_bloch_sphere(
# List must be converted to array for slicing. # List must be converted to array for slicing.
bloch_vectors = np.array(bloch_vector_list) 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 # A update function for animation class
def update(frame): def update(frame):
stretch = 3
view_rotating_angle = 5 view_rotating_angle = 5
if frame <= stretch: new_view_angle = [view_angle[0], view_angle[1] + view_rotating_angle * frame]
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( __plot_bloch_sphere(
ax, bloch_vectors, show_arrow, clear_plt=True, view_angle=new_view_angle, view_dist=view_dist ax, bloch_vectors, show_arrow, clear_plt=True,
view_angle=new_view_angle, view_dist=view_dist, set_color=set_color
) )
# Dynamic update and save # Dynamic update and save
if save_gif: if save_gif:
frames_num = 10 # 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')
frames_num = 7
anim = animation.FuncAnimation(fig, update, frames=frames_num, interval=600, repeat=False) anim = animation.FuncAnimation(fig, update, frames=frames_num, interval=600, repeat=False)
anim.save(filename, dpi=100, writer='Pillow') anim.save(filename, dpi=100, writer='pillow')
else: # close the plt
plt.close(fig)
# 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')
__plot_bloch_sphere( __plot_bloch_sphere(
ax, bloch_vectors, show_arrow, clear_plt=True, view_angle=view_angle, view_dist=view_dist ax, bloch_vectors, show_arrow, clear_plt=True,
view_angle=view_angle, view_dist=view_dist, set_color=set_color
) )
plt.show() plt.show()
...@@ -1234,7 +1391,8 @@ def plot_rotation_in_bloch_sphere( ...@@ -1234,7 +1391,8 @@ def plot_rotation_in_bloch_sphere(
save_gif=False, save_gif=False,
filename=None, filename=None,
view_angle=None, view_angle=None,
view_dist=None view_dist=None,
color_scheme=None,
): ):
r"""在 Bloch 球面上刻画从初始量子态开始的旋转轨迹 r"""在 Bloch 球面上刻画从初始量子态开始的旋转轨迹
...@@ -1244,8 +1402,10 @@ def plot_rotation_in_bloch_sphere( ...@@ -1244,8 +1402,10 @@ def plot_rotation_in_bloch_sphere(
show_arrow (bool): 是否展示向量的箭头,默认为 ``False`` show_arrow (bool): 是否展示向量的箭头,默认为 ``False``
save_gif (bool): 是否存储 gif 动图,默认为 ``False`` save_gif (bool): 是否存储 gif 动图,默认为 ``False``
filename (str): 存储的 gif 动图的名字 filename (str): 存储的 gif 动图的名字
view_angle (list or tuple): 视图的角度,list 内第一个元素为关于 xy 平面的夹角 [0-360],第二个元素为关于 xz 平面的夹角 [0-360] view_angle (list or tuple): 视图的角度,
第一个元素为关于 xy 平面的夹角 [0-360],第二个元素为关于 xz 平面的夹角 [0-360], 默认为 ``(30, 45)``
view_dist (int): 视图的距离,默认为 7 view_dist (int): 视图的距离,默认为 7
color_scheme (list(str,str,str)): 分别是初始颜色,轨迹颜色,结束颜色。若要设置指定的颜色,请查阅 ``cmap`` 表。默认为红色
""" """
# Check input data # Check input data
__input_args_dtype_check(show_arrow, save_gif, filename, view_angle, view_dist) __input_args_dtype_check(show_arrow, save_gif, filename, view_angle, view_dist)
...@@ -1253,20 +1413,30 @@ def plot_rotation_in_bloch_sphere( ...@@ -1253,20 +1413,30 @@ def plot_rotation_in_bloch_sphere(
assert type(init_state) == paddle.Tensor or type(init_state) == np.ndarray, \ assert type(init_state) == paddle.Tensor or type(init_state) == np.ndarray, \
'the type of input data should be "paddle.Tensor" or "numpy.ndarray".' 'the type of input data should be "paddle.Tensor" or "numpy.ndarray".'
assert type(rotating_angle) == tuple or type(rotating_angle) == list, \ assert type(rotating_angle) == tuple or type(rotating_angle) == list, \
'the type of rotating_angle should be "tuple" or "list"' 'the type of rotating_angle should be "tuple" or "list".'
assert len(rotating_angle) == 3, \ assert len(rotating_angle) == 3, \
'the rotating_angle must include [theta=paddle.Tensor, phi=paddle.Tensor, lam=paddle.Tensor].' 'the rotating_angle must include [theta=paddle.Tensor, phi=paddle.Tensor, lam=paddle.Tensor].'
for i in range(3): for i in range(3):
assert type(rotating_angle[i]) == paddle.Tensor or type(rotating_angle[i]) == float, \ 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].' 'the rotating_angle must include [theta=paddle.Tensor, phi=paddle.Tensor, lam=paddle.Tensor].'
if color_scheme is not None:
assert type(color_scheme) == list and len(color_scheme) <= 3, \
'the type of "color_scheme" should be "list" and ' \
'the length of "color_scheme" should be less than or equal to "3".'
for i in range(len(color_scheme)):
assert type(color_scheme[i]) == str, \
'the type of "color_scheme[i] should be "str".'
# Assign a value to an empty variable # Assign a value to an empty variable
if filename is None: if filename is None:
filename = 'rotation_in_bloch_sphere.gif' filename = 'rotation_in_bloch_sphere.gif'
if view_angle is None:
view_angle = [30, 45] # Assign colors to bloch vectors
if view_dist is None: color_list = ['orangered', 'lightsalmon', 'darkred']
view_dist = 7 if color_scheme is not None:
for i in range(len(color_scheme)):
color_list[i] = color_scheme[i]
set_init_color, set_trac_color, set_end_color = color_list
theta, phi, lam = rotating_angle theta, phi, lam = rotating_angle
...@@ -1303,38 +1473,52 @@ def plot_rotation_in_bloch_sphere( ...@@ -1303,38 +1473,52 @@ def plot_rotation_in_bloch_sphere(
# List must be converted to array for slicing. # List must be converted to array for slicing.
bloch_vectors = np.array(bloch_vector_list) 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 # A update function for animation class
def update(frame): def update(frame):
frame = frame + 1 frame = frame + 2
if frame <= len(bloch_vectors): if frame <= len(bloch_vectors) - 1:
__plot_bloch_sphere( __plot_bloch_sphere(
ax, bloch_vectors[:frame], show_arrow=show_arrow, clear_plt=True, ax, bloch_vectors[1:frame], show_arrow=show_arrow, clear_plt=True,
rotating_angle_list=rotating_angle_list, rotating_angle_list=rotating_angle_list,
view_angle=view_angle, view_dist=view_dist view_angle=view_angle, view_dist=view_dist, set_color=set_trac_color
) )
# The starting and ending bloch vector has to be shown # The starting and ending bloch vector has to be shown
# show starting vector # show starting vector
__plot_bloch_sphere( __plot_bloch_sphere(
ax, bloch_vectors[:1], show_arrow=True, clear_plt=False, view_angle=view_angle, view_dist=view_dist, ax, bloch_vectors[:1], show_arrow=True, clear_plt=False,
view_angle=view_angle, view_dist=view_dist, set_color=set_init_color
) )
# Show ending vector # Show ending vector
if frame == len(bloch_vectors): if frame == len(bloch_vectors):
__plot_bloch_sphere( __plot_bloch_sphere(
ax, bloch_vectors[frame - 1:frame], show_arrow=True, clear_plt=False, ax, bloch_vectors[frame - 1:frame], show_arrow=True, clear_plt=False,
view_angle=view_angle, view_dist=view_dist view_angle=view_angle, view_dist=view_dist, set_color=set_end_color
) )
if save_gif:
# 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')
# Dynamic update and save # Dynamic update and save
stop_frames = 10 stop_frames = 15
frames_num = len(bloch_vectors) + stop_frames frames_num = len(bloch_vectors) - 2 + stop_frames
anim = animation.FuncAnimation(fig, update, frames=frames_num, interval=100, repeat=False) anim = animation.FuncAnimation(fig, update, frames=frames_num, interval=100, repeat=False)
if save_gif: anim.save(filename, dpi=100, writer='pillow')
anim.save(filename, dpi=100, writer='Pillow') # close the plt
plt.close(fig)
# 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')
# Draw the penultimate bloch vector
update(len(bloch_vectors) - 3)
# Draw the last bloch vector
update(len(bloch_vectors) - 2)
plt.show() plt.show()
paddlepaddle>=2.1.1 paddlepaddle>=2.1.1
scipy scipy
networkx>=2.5 networkx>=2.5
matplotlib matplotlib>=3.3.0
interval interval
tqdm tqdm
\ No newline at end of file
fastdtw
\ No newline at end of file
...@@ -23,18 +23,32 @@ with open("README.md", "r", encoding="utf-8") as fh: ...@@ -23,18 +23,32 @@ with open("README.md", "r", encoding="utf-8") as fh:
setuptools.setup( setuptools.setup(
name='paddle-quantum', name='paddle-quantum',
version='2.1.1', version='2.1.2',
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.',
long_description=long_description, long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
url='http://qml.baidu.com', url='http://qml.baidu.com',
packages=['paddle_quantum', 'paddle_quantum.GIBBS', 'paddle_quantum.QAOA', 'paddle_quantum.SSVQE', packages=[
'paddle_quantum.VQE', 'paddle_quantum.VQSD', 'paddle_quantum.GIBBS.example', 'paddle_quantum', 'paddle_quantum.optimizer', 'paddle_quantum.mbqc',
'paddle_quantum.QAOA.example', 'paddle_quantum.SSVQE.example', 'paddle_quantum.VQE.example', 'paddle_quantum.GIBBS', 'paddle_quantum.GIBBS.example',
'paddle_quantum.VQSD.example'], 'paddle_quantum.SSVQE', 'paddle_quantum.SSVQE.example',
install_requires=['paddlepaddle>=2.1.1', 'scipy', 'networkx>=2.5', 'matplotlib', 'interval', 'tqdm', 'fastdtw'], 'paddle_quantum.VQE', 'paddle_quantum.VQE.example',
'paddle_quantum.QAOA', 'paddle_quantum.QAOA.example',
'paddle_quantum.VQSD', 'paddle_quantum.VQSD.example',
'paddle_quantum.mbqc.QAOA', 'paddle_quantum.mbqc.QAOA.example',
'paddle_quantum.mbqc.QKernel', 'paddle_quantum.mbqc.QKernel.example',
'paddle_quantum.mbqc.VQSVD', 'paddle_quantum.mbqc.VQSVD.example',
],
package_data={
'paddle_quantum.VQE': ['*.xyz'],
'paddle_quantum.VQE.example': ['*.xyz'],
'paddle_quantum.mbqc.QKernel.example': ['*.txt'],
'paddle_quantum.mbqc.VQSVD.example': ['*.txt'],
},
install_requires=['paddlepaddle>=2.1.2', 'scipy', 'networkx>=2.5', 'matplotlib>=3.3.0', 'interval', 'tqdm'],
python_requires='>=3.6, <4', python_requires='>=3.6, <4',
classifiers=[ classifiers=[
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
......
...@@ -153,7 +153,8 @@ ...@@ -153,7 +153,8 @@
"source": [ "source": [
"num_assets = 3 #可选择股票数目\n", "num_assets = 3 #可选择股票数目\n",
"stocks = [(\"TICKER%s\" % i) for i in range(num_assets)]\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)) # 根据指定的条件,随机生成测试所需的数据" "data = DataSimulator( stocks = stocks, start = datetime.datetime(2016, 1, 1), end = datetime.datetime(2016, 1, 30)) \n",
"data.randomly_generate() # 随机生成实验数据"
], ],
"outputs": [], "outputs": [],
"metadata": {} "metadata": {}
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
{ {
"cell_type": "markdown", "cell_type": "markdown",
"source": [ "source": [
"# Quantum Finance Application: Portfolio Diversification\n", "# Quantum Finance Application on Portfolio Diversification\n",
"\n", "\n",
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>" "<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
], ],
...@@ -153,8 +153,8 @@ ...@@ -153,8 +153,8 @@
"source": [ "source": [
"num_assets = 3 # Number of investable projects\n", "num_assets = 3 # Number of investable projects\n",
"stocks = [(\"TICKER%s\" % i) for i in range(num_assets)]\n", "stocks = [(\"TICKER%s\" % i) for i in range(num_assets)]\n",
"data = DataSimulator( stocks = stocks, start = datetime.datetime(2016, 1, 1),\n", "data = DataSimulator( stocks = stocks, start = datetime.datetime(2016, 1, 1), end = datetime.datetime(2016, 1, 30)) \n",
" end = datetime.datetime(2016, 1, 30)) " "data.randomly_generate() # Generate random data"
], ],
"outputs": [], "outputs": [],
"metadata": {} "metadata": {}
...@@ -392,7 +392,7 @@ ...@@ -392,7 +392,7 @@
"cell_type": "code", "cell_type": "code",
"execution_count": 11, "execution_count": 11,
"source": [ "source": [
"# Repeat the simulated measurement of the circuit output state 1024 times\n", "# Repeat the simulated measurement of the circuit output state 2048 times\n",
"\n", "\n",
"prob_measure = cir.measure(shots=2048)\n", "prob_measure = cir.measure(shots=2048)\n",
"investment = max(prob_measure, key=prob_measure.get)\n", "investment = max(prob_measure, key=prob_measure.get)\n",
......
...@@ -132,8 +132,8 @@ ...@@ -132,8 +132,8 @@
"source": [ "source": [
"num_assets = 7 # 可投资的项目数量\n", "num_assets = 7 # 可投资的项目数量\n",
"stocks = [(\"STOCK%s\" % i) for i in range(num_assets)] \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", "data = DataSimulator(stocks=stocks, start=datetime.datetime(2016, 1, 1), end=datetime.datetime(2016, 1, 30)) \n",
"ds = data.randomly_generate()" "data.randomly_generate() # 随机生成实验数据"
], ],
"outputs": [], "outputs": [],
"metadata": {} "metadata": {}
......
...@@ -134,8 +134,8 @@ ...@@ -134,8 +134,8 @@
"source": [ "source": [
"num_assets = 7 # Number of investable projects\n", "num_assets = 7 # Number of investable projects\n",
"stocks = [(\"STOCK%s\" % i) for i in range(num_assets)] \n", "stocks = [(\"STOCK%s\" % i) for i in range(num_assets)] \n",
"data = DataSimulator( stocks = stocks, start = datetime.datetime(2016, 1, 1),\n", "data = DataSimulator( stocks = stocks, start = datetime.datetime(2016, 1, 1), end = datetime.datetime(2016, 1, 30))\n",
" end = datetime.datetime(2016, 1, 30))" "data.randomly_generate() # Generate random data"
], ],
"outputs": [], "outputs": [],
"metadata": {} "metadata": {}
...@@ -423,10 +423,10 @@ ...@@ -423,10 +423,10 @@
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"source": [ "source": [
"# Repeat the simulated measurement of the circuit output state 1024 times\n", "# Repeat the simulated measurement of the circuit output state 2048 times\n",
"prob_measure = cir.measure(shots=2048)\n", "prob_measure = cir.measure(shots=2048)\n",
"investment = max(prob_measure, key=prob_measure.get)\n", "investment = max(prob_measure, key=prob_measure.get)\n",
"print(\"The bit string form of the solution: \",investment)" "print(\"The bit string form of the solution: \", investment)"
], ],
"outputs": [ "outputs": [
{ {
......
# 基于测量的量子计算模块
## 概述
基于测量的量子计算(Measurement-Based Quantum Computation, MBQC)模块是基于量桨开发的通用量子计算模拟工具。与传统的量子电路模型不同,MBQC 具有其独特的运行方式 [1-8],因而常见的电路模拟工具无法直接用于该模型的模拟。为了促进对 MBQC 的进一步研究和探索,我们在模块中设计并提供了模拟 MBQC 所需的多个核心模块。
一方面,与其他常见的量子电路模拟器一样,我们可以利用 MBQC 模块快速实现量子神经网络的搭建与训练,在诸如量子化学,量子自然语言处理,量子金融等人工智能领域具有强大的应用前景。另一方面,MBQC 模块在某些特殊情形下可以展现出其运算效率上的独特优势,特别是在多比特浅层量子电路模拟时可以大幅度降低计算资源的消耗,从而有潜力实现更大规模的量子算法模拟。
我们的模块是业界首个、目前也是唯一一个支持模拟 MBQC 量子计算模型的系统性工具。我们由衷地期待广大的量子计算爱好者和开发者进行使用和学习。欢迎加入我们,共同探索 MBQC 中更多的奥秘!
## 特色
- 丰富的在线教学示例,理论结合实践,轻松上手
- 完备的核心模块组件,通用扩展性强,调用方便
- 高效的翻译模拟算法,加速执行效率,性能强大
## 零基础入门 MBQC
### 什么是 MBQC 量子计算模型
基于测量的量子计算是平行于量子电路模型的一种通用量子计算技术路线。其核心思想在于对一个量子纠缠态的部分比特进行测量,未被测量的量子系统将会实现相应的演化,并且通过对测量方式的控制,我们可以实现任意需要的演化 [1-8]。
MBQC 的标准计算过程主要分为三个步骤:量子图态准备,单比特测量,副产品纠正。与此过程等价的描述方式还有 MBQC 的测量模式 (或称为 "EMC" 语言)[9]。 详细内容介绍请参见 [MBQC 入门介绍](MBQC_CN.ipynb)
### 子模块
|子模块|描述|
|:---|:---|
|`simulator`|包含构造 MBQC 模型的常用类和配套的运算模拟工具。|
|`mcalculus`|包含处理 MBQC 测量模式的相关操作。|
|`transpiler`|包含电路模型和 MBQC 测量模式的转义工具。|
|`qobject`|包含量子信息处理的常用对象,如量子态、量子电路、测量模式等。|
|`utils`|包含计算所需的各种常用类和函数。|
### 模块的基本使用方法
下面我们简单介绍一下模块的基本使用方法。
#### 基础调用方法
`simulator` 中,我们定义了 `MBQC` 类,用户可以实例化该类来搭建属于自己的 MBQC 模型。大多数 MBQC 模型下的算法都是基于此类的方法来实现的。用户输入图的信息,调用 `measure` 类方法对图中节点进行测量,最后进行相应副产品纠正,即可完成 MBQC 模型的计算过程。这里我们以实现单比特 `Hadamard` 门为例,简要展示其基础用法。
```python
# 引入模块
from paddle_quantum.mbqc.simulator import MBQC
from paddle_quantum.mbqc.utils import basis
# 构造计算相关的图
vertices = ["1", "2"]
edges = [("1", "2")]
graph = [vertices, edges]
# 搭建 MBQC 模型
mbqc = MBQC()
# 输入图信息
mbqc.set_graph(graph)
# 对节点 “1” 进行 X 测量
mbqc.measure("1", basis("X"))
# 对节点 “2” 进行副产品处理
mbqc.correct_byproduct('X', "2", mbqc.sum_outcomes(["1"]))
# 获得量子输出态
state_out = mbqc.get_quantum_output()
```
#### 测量模式调用方法
MBQC 和电路模型一样都可以实现通用量子计算,并且两者之间存在一一对应的关系。然而将电路模型转化到等价的 MBQC 测量模式过程复杂且计算量大 [9]。为此,我们在模块中提供了由量子电路模型自动翻译为测量模式的转义模块 `transpiler` 。我们可以直接使用类似于 [UAnstaz](https://qml.baidu.com/api/paddle_quantum.circuit.uansatz.html) 的调用格式来构建量子电路,将其自动翻译成测量模式,再接入模拟模块中运行。同样地,这里我们以实现单比特 `Hadamard` 门为例,简要展示其用法。
```python
# 引入模块
from paddle_quantum.mbqc.qobject import Circuit
from paddle_quantum.mbqc.transpiler import transpile
from paddle_quantum.mbqc.simulator import MBQC
# 构造量子电路
width = 1
cir = Circuit(width)
cir.h(0)
# 将电路翻译成测量模式
pat = transpile(cir)
# 调用模拟模块运行
mbqc = MBQC()
mbqc.set_pattern(pat)
mbqc.run_pattern()
# 获得量子输出态
state_out = mbqc.get_quantum_output()
```
以上即为 MBQC 模块当前的主要内容。在实际案例及应用中,我们可以根据需要调用相关模块。另外,模块内部更多丰富的功能请参见示例教程及 API 文档。
### 示例教程
在这里,我们提供了三个示例教程,其中每个教程都包含了关于 MBQC 的基础理论讲解和详细的代码演示。用户可以通过教程学习和代码练习,熟练掌握 MBQC 的运算逻辑和模块的调用方式,为后续进一步探索 MBQC 打下基础。
- [MBQC 入门介绍](MBQC_CN.ipynb)
- [基于测量的量子近似优化算法](QAOA_CN.ipynb)
- [MBQC 模型下求解多项式组合优化问题](PUBO_CN.ipynb)
## 常见问题
- 问:为什么要研究 MBQC ?它有哪些应用场景?
答:MBQC
是平行于量子电路模型的一种通用量子计算技术路线。物理实现上,与电路模型相比,单比特测量操作在实验上更容易实现,保真度更高,无适应性的测量部分则可同时进行,从而大幅度减小算法深度,降低相干时间对保真度的影响。经典模拟上,由于不同比特间的测量步骤可以交换次序且不影响测量结果,在模拟时可以通过交换测量顺序来优化计算的执行路线,降低计算资源消耗,提高运算效率。此外,MBQC 中的资源态可以与具体计算任务无关,因此可以应用在量子互联网中用于安全代理计算,保护用户的计算和数据隐私 [10,11]。
- 问:现阶段 MBQC 是通过什么技术手段进行物理实现的?
答:MBQC 在物理实现上的难点主要是资源态的制备。与量子电路模型中使用的超导技术不同,资源态的制备大多采用线性光学技术或冷原子技术,目前现有的资源态的制备技术请参见 [2,12,13]。
## 参考文献
[1] Gottesman, Daniel, and Isaac L. Chuang. "Demonstrating the viability of universal quantum computation using teleportation and single-qubit operations." [Nature 402.6760 (1999): 390-393.](https://www.nature.com/articles/46503?__hstc=13887208.d9c6f9c40e1956d463f0af8da73a29a7.1475020800048.1475020800050.1475020800051.2&__hssc=13887208.1.1475020800051&__hsfp=1773666937)
[2] Robert Raussendorf, et al. "A one-way quantum computer." [Physical Review Letters 86.22 (2001): 5188.](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.86.5188)
[3] Raussendorf, Robert, and Hans J. Briegel. "Computational model underlying the one-way quantum computer." [Quantum Information & Computation 2.6 (2002): 443-486.](https://dl.acm.org/doi/abs/10.5555/2011492.2011495)
[4] Robert Raussendorf, et al. "Measurement-based quantum computation on cluster states." [Physical Review A 68.2 (2003): 022312.](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.68.022312)
[5] Nielsen, Michael A. "Quantum computation by measurement and quantum memory." [Physics Letters A 308.2-3 (2003): 96-100.](https://www.sciencedirect.com/science/article/abs/pii/S0375960102018030)
[6] Leung, Debbie W. "Quantum computation by measurements." [International Journal of Quantum Information 2.01 (2004): 33-43.](https://www.worldscientific.com/doi/abs/10.1142/S0219749904000055)
[7] Briegel, Hans J., et al. "Measurement-based quantum computation." [Nature Physics 5.1 (2009): 19-26.](https://www.nature.com/articles/nphys1157)
[8] Aliferis, Panos, and Debbie W. Leung. "Computation by measurements: a unifying picture." [Physical Review A 70.6 (2004): 062314.](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.70.062314)
[9] Danos, Vincent, et al. "The measurement calculus." [Journal of the ACM (JACM) 54.2 (2007): 8-es.](https://dl.acm.org/doi/abs/10.1145/1219092.1219096)
[10] Broadbent, Anne, et al. "Universal blind quantum computation." [2009 50th Annual IEEE Symposium on Foundations of Computer Science. IEEE, 2009.](https://arxiv.org/abs/0807.4154)
[11] Morimae, Tomoyuki. "Verification for measurement-only blind quantum computing." [Physical Review A 89.6 (2014): 060302.](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.89.060302)
[12] Larsen, Mikkel V., et al. "Deterministic generation of a two-dimensional cluster state." [Science 366.6463 (2019): 369-372.](https://science.sciencemag.org/content/366/6463/369)
[13] Asavanant, Warit, et al. "Generation of time-domain-multiplexed two-dimensional cluster state." [Science 366.6463 (2019): 373-376.](https://science.sciencemag.org/content/366/6463/373)
# Measurement-Based Quantum Computation Module
## Introduction
Measurement-Based Quantum Computation (MBQC) module is a simulation tool for universal quantum computing, developed based on Baidu PaddlePaddle. Unlike the conventional quantum circuit model, MBQC has its unique way of computing [1-8] and thus common circuit simulation tools cannot be directly used for the simulation of this model. To facilitate further research and exploration of MBQC, we have designed and provided several core modules in the module that are required for simulation.
On the one hand, as with common quantum circuit simulators, we can use the module to quickly build and train quantum neural networks, which have powerful application prospects in the field of artificial intelligence such as quantum chemistry, quantum natural language processing, and quantum finance. On the other hand, the MBQC module demonstrates its unique advantages in computational efficiency in some special cases, especially in quantum shallow circuit simulations, where the required computational resource can be significantly reduced, leading to a potential opportunity for larger-scale quantum algorithm simulations.
Our module is currently the first and the only systematic tool in the industry that supports the simulation of MBQC. We sincerely look forward to its learning and usage by quantum computing enthusiasts and researchers. Welcome to join us and discover the infinite possibilities of MBQC together!
## Features
- Plenty of online tutorials with rich theory illustration and code demonstrations, making it easy to get started
- Complete core modules for universal simulation with strong extendability and friendly user experience
- Efficient transpiling and simulation algorithms to accelerate the code execution with high performance
## Getting Started with MBQC from Scratch
### What is MBQC
Measurement-based quantum computing is a technique route for universal quantum computing, different from the quantum circuit model. As its name suggests, MBQC controls the computation by measuring part of the qubits of an entangled state, with those remaining unmeasured undergoing the evolution correspondingly. By controlling measurements, we can complete any desired evolution [1-8].
The standard computational process of MBQC is divided into three main steps: graph state preparation, single-qubit measurement, and byproduct correction. An equivalent way to describe this process is the measurement pattern (or "EMC" language) of MBQC [9]. Please refer to [MBQC Quick Start Guide](MBQC_CN.ipynb) for more details.
### Submodules
|Submodule|Description|
|:---|:---|
|`simulator`|contains a frequently used class and accompanying simulation tools for constructing MBQC models.|
|`mcalculus`|contains operations for the manipulation of measurement patterns.|
|`transpiler`|contains tools for transpiling circuits to measurement patterns.|
|`qobject`|contains common objects for quantum information processing, such as quantum states, quantum circuits, measurement patterns, etc.|
|`utils`|contains various common classes and functions used for computation.|
### Basic usage of the module
Here we briefly introduce the basic usage of the module.
#### Running with standard processes
In the `simulator`, we define a class `MBQC` to be instantiated to build your own MBQC model. Most of the MBQC algorithms are implemented by this class. We can first set the underlying graph of their MBQC model, then call the class method `measure` to measure each vertex in the graph, and finally correct the corresponding byproducts to finish the computation. Here we take the implementation of a single-qubit `Hadamard` gate as an example to briefly demonstrate its basic usage.
```python
# Import modules
from paddle_quantum.mbqc.simulator import MBQC
from paddle_quantum.mbqc.utils import basis
# Construct the underlying graph
vertices = ["1", "2"]
edges = [("1", "2")]
graph = [vertices, edges]
# Construct an MBQC model
mbqc = MBQC()
# Set the underlying graph
mbqc.set_graph(graph)
# Measure vertex '1' in Pauli X basis
mbqc.measure("1", basis("X"))
# Correct byproduct on vertex '2'
mbqc.correct_byproduct('X', "2", mbqc.sum_outcomes(["1"]))
# Obtain the quantum output
state_out = mbqc.get_quantum_output()
```
#### Running with measurement patterns
Both MBQC and circuit models can realize universal quantum computation and there is a one-to-one correspondence between them. However, the process of converting a quantum circuit to its MBQC equivalent is complicated and requires numerous calculations [9]. For this, we provide a module `transpiler` in our module for automatic translation from quantum circuits to measurement patterns. We can first call the ``Circuit`` in `qobject` to build a quantum circuit, just like calling [UAnstaz](https://qml.baidu.com/api/paddle_quantum.circuit.uansatz.html), then use the `transpiler` module to get an equivalent measurement pattern of the circuit, and finally send this pattern into the ``simulator`` module to run it. Again, here we briefly demonstrate the usage by the implementation of a single-qubit `Hadamard` gate as an example.
```python
# Import modules
from paddle_quantum.mbqc.qobject import Circuit
from paddle_quantum.mbqc.transpiler import transpile
from paddle_quantum.mbqc.simulator import MBQC
# Construct a circuit
width = 1
cir = Circuit(width)
cir.h(0)
# Transpile it to a pattern
pat = transpile(cir)
# Run the pattern in MBQC
mbqc = MBQC()
mbqc.set_pattern(pat)
mbqc.run_pattern()
# Obtain the quantum output
state_out = mbqc.get_quantum_output()
```
The above are the main contents of the current MBQC module. We can call relevant modules as needed in practice. In terms
of more functionalities of each module, please refer to the tutorials and the API documentation.
### Tutorials
Here, we provide three tutorials, each of which contains basic theory explanations and detailed code demonstrations. By following the tutorials and code practice, you will get familiar with the MBQC model as well as our module
shortly.
- [MBQC Quick Start Guide](MBQC_EN.ipynb)
- [Measurement-based Quantum Approximate Optimization Algorithm](QAOA_EN.ipynb)
- [Polynomial Unconstrained Boolean Optimization Problem in MBQC](PUBO_EN.ipynb)
## Frequently Asked Questions
- Q: Why should I study MBQC? What application scenarios does it have?
A: MBQC is a universal quantum computing model parallel to the quantum circuit model. Compared to quantum gates, single-qubit measurements are easier to implement in practice with higher fidelity, and the part of non-adaptive measurements can even be realized simultaneously, drastically reducing the algorithm depth and thus the effect of decoherence on fidelity. In terms of classical simulation, since measurements commute between different qubits, the simulation process can be optimized by measurements reordering, leading to less consumption of the computational resource and thus improving the efficiency. In addition, the resource states in MBQC can be independent of specific computational tasks, which can be applied in quantum Internet for secure delegated quantum computing to protect users' privacy [10,11].
- Q: How is the MBQC physically implemented?
A: The difficulty in the physical implementation of MBQC is mainly the preparation of resource states. Unlike the superconducting techniques used in the quantum circuit model, the resource states are mostly prepared using linear optics or cold atoms, see e.g. [2,12,13] for the currently available techniques for the resource states preparation.
## References
[1] Gottesman, Daniel, and Isaac L. Chuang. "Demonstrating the viability of universal quantum computation using teleportation and single-qubit operations." [Nature 402.6760 (1999): 390-393.](https://www.nature.com/articles/46503?__hstc=13887208.d9c6f9c40e1956d463f0af8da73a29a7.1475020800048.1475020800050.1475020800051.2&__hssc=13887208.1.1475020800051&__hsfp=1773666937)
[2] Robert Raussendorf, et al. "A one-way quantum computer." [Physical Review Letters 86.22 (2001): 5188.](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.86.5188)
[3] Raussendorf, Robert, and Hans J. Briegel. "Computational model underlying the one-way quantum computer." [Quantum Information & Computation 2.6 (2002): 443-486.](https://dl.acm.org/doi/abs/10.5555/2011492.2011495)
[4] Robert Raussendorf, et al. "Measurement-based quantum computation on cluster states." [Physical Review A 68.2 (2003): 022312.](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.68.022312)
[5] Nielsen, Michael A. "Quantum computation by measurement and quantum memory." [Physics Letters A 308.2-3 (2003): 96-100.](https://www.sciencedirect.com/science/article/abs/pii/S0375960102018030)
[6] Leung, Debbie W. "Quantum computation by measurements." [International Journal of Quantum Information 2.01 (2004): 33-43.](https://www.worldscientific.com/doi/abs/10.1142/S0219749904000055)
[7] Briegel, Hans J., et al. "Measurement-based quantum computation." [Nature Physics 5.1 (2009): 19-26.](https://www.nature.com/articles/nphys1157)
[8] Aliferis, Panos, and Debbie W. Leung. "Computation by measurements: a unifying picture." [Physical Review A 70.6 (2004): 062314.](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.70.062314)
[9] Danos, Vincent, et al. "The measurement calculus." [Journal of the ACM (JACM) 54.2 (2007): 8-es.](https://dl.acm.org/doi/abs/10.1145/1219092.1219096)
[10] Broadbent, Anne, et al. "Universal blind quantum computation." [2009 50th Annual IEEE Symposium on Foundations of Computer Science. IEEE, 2009.](https://arxiv.org/abs/0807.4154)
[11] Morimae, Tomoyuki. "Verification for measurement-only blind quantum computing." [Physical Review A 89.6 (2014): 060302.](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.89.060302)
[12] Larsen, Mikkel V., et al. "Deterministic generation of a two-dimensional cluster state." [Science 366.6463 (2019): 369-372.](https://science.sciencemag.org/content/366/6463/369)
[13] Asavanant, Warit, et al. "Generation of time-domain-multiplexed two-dimensional cluster state." [Science 366.6463 (2019): 373-376.](https://science.sciencemag.org/content/366/6463/373)
{
"cells": [
{
"cell_type": "markdown",
"source": [
"# MBQC 入门介绍 \n",
"\n",
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
],
"metadata": {
"tags": []
}
},
{
"cell_type": "markdown",
"source": [
"## 概述\n",
"\n",
"量子计算利用量子世界中特有的运行规律,为我们提供了一条全新的并且非常有前景的信息处理方式。其计算的本质是通过特定的方式将初始制备的量子态演化成我们预期的另一个量子态,然后在该量子态上做测量以获得计算结果。但是在不同模型下,量子态的演化方式各异。我们比较常用的**量子电路模型 (quantum circuit model)** [1,2] 是通过对量子态进行量子门操作来完成演化,该模型可以理解为经典计算模型的量子版本,并且被广泛地应用在量子计算领域中。**基于测量的量子计算 (measurement-based quantum computation, MBQC)** 是一种完全不同于量子电路模型的计算方式。\n",
"\n",
"顾名思义,MBQC 模型的计算过程是通过量子测量完成的。基于测量的量子计算主要有两种子模型:**隐形传态量子计算 (teleportation-based quantum computing, TQC)** 模型 [3-5] 和**单向量子计算机 (one-way quantum computer, 1WQC)** 模型 [6-9]。其中,前者需要用到多量子比特的联合测量,而后者只需要单比特测量即可。有趣的是,这两种子模型在被分别提出后,被证明是高度相关并且一一对应的 [10]。所以我们后面将不加声明地,**默认讨论的 MBQC 模型为 1WQC 模型。**\n",
"\n",
"与电路模型不同,MBQC 是量子计算特有的一种模型,没有经典计算的对应。该模型的核心思想在于对一个量子纠缠态的部分比特进行测量,未被测量的量子系统将会实现相应的演化,并且通过对测量方式的控制,我们可以实现任意需要的演化。MBQC 模型下的计算过程主要分为三个步骤:第一步,准备一个**资源态 (resource state)**,即一个高度纠缠的多体量子态,该量子态可以在计算开始之前准备好,且可以与具体计算任务无关;第二步,对准备好的资源态的每个比特依次做单比特测量,其中后续比特的测量方式可以根据已经被测量的比特的测量结果做出相应调整,即允许**适应性测量 (adaptive measurement)**;第三步,对测量后得到的量子态进行 **副产品纠正 (byproduct correction)**。最后,我们对所有比特的测量结果进行经典数据处理,即可得到需要的计算结果。\n",
"\n",
"图 1 给出了一个 MBQC 模型下的算法示例。图中的网格代表了一种常用的量子资源态,(称为**团簇态,cluster state**,详见下文),网格上的每个节点都代表了一个量子比特,整个网格则代表了一个高度纠缠的量子态。我们依次对每个比特进行测量(节点中的 $X, Y, Z, XY$ 等表示对应的测量基),对测量后的量子态进行副产品纠正(消除 Pauli $X$ 算符和 Pauli $Z$ 算符),即可完成计算。\n",
"\n",
"![MBQC example](./figures/mbqc-fig-general_pattern.jpg)\n",
"<div style=\"text-align:center\">图 1: 通过对网格上的每个比特进行测量来完成计算 </div>\n",
"\n",
"MBQC 模型的计算方式给我们带来了诸多好处。比如说,如果第一步制备的量子态噪声太大,我们则可以**在计算开始之前(即测量之前)** 丢弃这个态,并重新制备,以此保证计算结果的准确性;由于资源态可以与计算任务无关,因此可以应用在安全代理计算中 [11,12],保护计算的隐私;另外,单比特量子测量在实验上比量子门更容易实现,保真度更高,无适应性依赖关系的量子测量则可以同步进行,从而降低整个计算的深度,对量子系统相干时间要求更低。MBQC 模型实现上的技术难点主要在于第一步资源态的制备,该量子态高度纠缠,并且所需的比特数比通常电路模型的多很多。关于资源态制备的相关进展,有兴趣的读者可以参见 [13,14] 。表格 1 概括了 MBQC 模型与量子电路模型的优势和限制。\n",
"\n",
"| | 量子电路模型 | 基于测量的量子计算模型 |\n",
"|:---: | :---: | :---: |\n",
"| 优势| 与经典计算模型对应 <br/>易于理解和拓展应用 | 资源态可与计算无关 <br/> 单比特测量易于操作 <br/> 可并行测量,算法深度低 |\n",
"|限制| 量子门执行顺序固定 <br/> 电路深度受相干时间限制| 无经典对应,不直观 <br/> 资源态比特数多,制备难度高 | \n",
"\n",
"<div style=\"text-align:center\">表 1:基于测量的量子计算模型与量子电路模型的优势和限制比较 </div>\n",
"\n",
"MBQC 模型因为没有经典对应,初学者可能有些难以用直觉去理解,但也正是这种超乎直觉的计算方式,给该模型带来了更多探索的空间和无穷的乐趣。接下来,就让我们共同走进 MBQC 模型的世界,一起来探索其中的奥秘吧!"
],
"metadata": {
"tags": []
}
},
{
"cell_type": "markdown",
"source": [
"## 预备知识\n",
"\n",
"在正式介绍 MBQC 模型及其我们的模块之前,我们首先回顾一下掌握 MBQC 模型需要用到的两个核心知识点。\n",
"\n",
"### 1. 图与图态\n",
" \n",
"对于任意给定一个图 $G=(V, E)$,其中,$V$ 是点的集合,$E$ 是边的集合,我们可以定义一个量子态与之对应。具体的做法为,将图 $G$ 中每个节点对应一个加态 $|+\\rangle = (|0\\rangle + |1\\rangle) / \\sqrt{2}$,如果图中两个节点之间有边相连,则将对应节点上的加态之间作用控制 Z 门, $CZ = |0\\rangle\\langle0| \\otimes I + |1\\rangle\\langle1|\\otimes Z$。由此步骤生成的量子态称为图 $G$ 的**图态** (graph state),记为 $|G\\rangle$,具体数学表达式如下:\n",
" \n",
"$$\n",
"|G\\rangle = \\prod_{(a,b) \\in E} CZ_{ab} \\left(\\bigotimes_{v \\in V}|+\\rangle_v\\right). \\tag{1}\n",
"$$\n",
"\n",
"图态其实并不是一个陌生的概念。通过局部的酉变换,我们所熟知的 Bell 态、GHZ 态等都可以表示为一个图对应的图态;此外,如果我们考虑的图具有周期网状的晶格结构(简单理解为二维坐标系的网格图),那么其对应的图态称为**团簇态 (cluster state)**,如图 2 所示。\n",
"\n",
"![Graph states](./figures/mbqc-fig-graph_states.jpg)\n",
"<div style=\"text-align:center\">图 2:图 (i) 对应的图态为 $Bell$ 态,图 (ii) 对应的图态为一个 4-qubit 的 $GHZ$ 态,图 (iii) 对应一个团簇态 </div>\n",
"\n",
"\n",
"### 2. 投影测量\n",
"\n",
"量子测量是量子信息处理中的核心概念之一,在电路模型中,量子测量往往出现在电路末端,用于从量子态中解码出我们需要的经典结果。但是在 MBQC 模型中,量子测量不仅用于解码算法答案,还用于控制量子态的演化过程,即:通过对纠缠的多体量子态进行部分测量,驱动未测量的量子态进行演化。在 MBQC 模型中,我们默认使用单比特测量,且以 0/1 投影测量为主。根据测量公理 [17],假设待测量的量子态为 $|\\phi\\rangle$,投影测量由一对正交基 $\\{|\\psi_0\\rangle, |\\psi_1\\rangle\\}$ 给出,那么测量结果为 $s \\in \\{0,1\\}$ 的概率为 $p(s) = |\\langle \\psi_s|\\phi\\rangle|^2$,测量后对应的量子态坍缩为 $|\\psi_s\\rangle\\langle\\psi_s|\\phi\\rangle / \\sqrt{p(s)}$,即被测量的比特坍缩为 $|\\psi_s\\rangle$,其他比特演化为 $\\langle\\psi_s|\\phi\\rangle / \\sqrt{p(s)}$。\n",
"\n",
"特别地,我们常用到的单比特测量为 $XY$, $YZ$, $XZ$ 三个平面上的投影测量,它们分别由如下的正交基给出,\n",
"\n",
"- XY 平面测量:$M^{XY}(\\theta) = \\{R_z(\\theta) |+\\rangle, R_z(\\theta) |-\\rangle \\}$,其中,当 $\\theta = 0$ 时为 $X$ 测量;当 $\\theta = \\frac{\\pi}{2}$ 时为 $Y$ 测量;\n",
"\n",
"- YZ 平面测量:$M^{YZ}(\\theta) = \\{R_x(\\theta)|0\\rangle, R_x(\\theta)|1\\rangle\\}$,其中,当 $\\theta = 0$ 时为 $Z$ 测量;\n",
"\n",
"- XZ 平面测量:$M^{XZ}(\\theta) = \\{R_y(\\theta)|0\\rangle, R_y(\\theta)|1\\rangle\\}$,其中,当 $\\theta = 0$ 时为 $Z$ 测量;\n",
"\n",
"以上 $|+\\rangle = (|0\\rangle + |1\\rangle)/ \\sqrt{2},|-\\rangle = (|0\\rangle - |1\\rangle)/ \\sqrt{2}$, 且 $R_x, R_y, R_z$ 分别为绕 $x,y,z$ 轴旋转的单比特旋转门。"
],
"metadata": {
"tags": []
}
},
{
"cell_type": "markdown",
"source": [
"## 框架介绍\n",
"\n",
"\n",
"### 1. 技术路线及代码实现\n",
"\n",
"#### “三步走”流程\n",
"\n",
"前面提到,MBQC 模型不同于常见的量子电路模型,该模型中量子态的演化是通过对量子图态上的部分比特进行测量来实现的。具体地,MBQC 模型由以下三个步骤构成。\n",
"\n",
"- **量子图态准备**:即准备一个多体纠缠态。一般地,我们给出图(点和边)的信息,初始化图中节点为加态,根据图中节点的连线方式作用控制 Z 门,便可以生成量子图态。以此对应关系,每当我们给定一个图的信息,我们便可以在其上定义对应的量子图态。此外,我们还可以根据需要选择性替换图态中某些节点上的加态为指定的输入态。\n",
"- **单比特测量**:按照特定的测量方式对上一步准备好的量子图态进行单比特测量,测量角度可以根据已获得的测量结果进行动态调整。无适应性依赖关系的测量可以交换顺序或同时进行。\n",
"- **副产品纠正**:由于测量结果的随机性,未测量量子态的演化方式不能唯一确定,换句话说,未测量的量子态有可能会进行一些多余的演化。我们称这些多余的演化为副产品(byproduct)。因而算法的最后一步就是对副产品进行纠正,得到我们预期的演化结果。如果算法最后要求输出的不是一个量子态,而是对演化完的量子态继续进行测量并获取经典结果的话,副产品的影响只需要通过经典数据处理来修正即可。因此,MBQC 模型的主要步骤为前两步,第三步是否进行则是取决于我们想要获得的是量子态的输出还是测量结果的经典输出。\n",
"\n",
"依次进行上述三个步骤,我们可以概括出 MBQC 模型“三步走”的流程,即:量子图态准备、单比特测量和副产品纠正。\n",
"\n",
"#### 测量模式与 \"EMC\" 语言\n",
"\n",
"除了常用的“三步走”流程之外,一个 MBQC 模型还可以用 \"EMC\" 语言 [18] 来描述。如前所述,MBQC 模型与电路模型具有一一对应关系。我们可以把由电路模型对应的 MBQC 模型称为该电路模型的测量**模式 (pattern)** ,把电路中的单个量子门或对输出态的单个测量对应的 MBQC 模型称为该量子门或测量对应的**子模式 (subpattern)** [18]。在描述 MBQC 的 \"EMC\" 语言中,我们将纠缠操作对应 “纠缠命令”,用符号 \"E\" 来表示;将测量操作对应 “测量命令”,用符号 \"M\" 来表示;将副产品纠正操作对应 “副产品纠正命令”,用符号 \"C\" 来表示。于是,对应于上述“三步走”流程,一个完整的 MBQC 运算过程还可以用“命令列表” \\[EMC\\] 来表示。运算过程则是按照命令列表从左至右的顺序执行各个命令。为了让大家快速地熟悉 MBQC 模型,在本教程中,我们采用经典的“三步走”流程来描述 MBQC 模型的运算过程。\n",
"\n",
"#### 代码实现\n",
"\n",
"代码实现上,我们将模拟 MBQC 模型的过程整合为一个模块 ``simulator``,该模块的主要内容是 ``MBQC`` 类,该类具有与 MBQC 模型相关的属性和类方法。我们可以根据具体情况自行实例化 MBQC 类,从而建立并模拟对应的 MBQC 模型算法。\n",
"\n",
"```python\n",
"# 代码实现\n",
"from paddle_quantum.mbqc.simulator import MBQC\n",
"\n",
"class MBQC:\n",
" def __init__():\n",
" ...\n",
"```\n",
"\n",
"实例化 MBQC 类之后,我们通过依次调用相关类方法就可以完成 MBQC 模型的运算过程。接下来我们通过一个表格(如表 2 所示)简单介绍一下常用的类方法及其功能。更为详细和全面的介绍请参考相关 API 文档。\n",
"\n",
"|类方法|功能|\n",
"|:---:|:---:|\n",
"|set_graph|输入图的信息|\n",
"|set_pattern|输入测量模式的信息|\n",
"|set_input_state|输入初始量子态的信息|\n",
"|draw_process|画出 MBQC 模型运算过程的动态图|\n",
"|track_progress|查看 MQBC 模型运算的进度条|\n",
"|measure|执行单比特测量|\n",
"|sum_outcomes|对指定节点的测量结果进行求和|\n",
"|correct_byproduct|纠正副产品|\n",
"|run_pattern|运行测量模式|\n",
"|get_classical_output|获取经典输出结果|\n",
"|get_quantum_output|获取量子输出结果|\n",
"\n",
"<div style=\"text-align:center\">表 2:MBQC 类中常用的类方法及其功能 </div>\n",
"\n",
"在 MBQC 模拟模块中,为了方便大家使用,我们设置了“图”(graph)和“模式”(pattern)的两种输入方式,分别对应于 MBQC 模型运算过程的两种描述方式。如果输入为图,则后续运算过程需要我们自行按照“三步走”流程完成。值得一提的是,我们设计了**节点动态分类算法**来模拟 MBQC 的运算过程,简单来说,就是将 MBQC “三步走”流程中的第一、二步进行整合,交换某些纠缠和测量操作,从而降低实际参与运算的比特数,提高运算效率。MBQC 模拟模块具体调用格式如下:\n",
"\n",
"```python\n",
"\"\"\"\n",
"MBQC 模拟模块调用格式(以图为输入,进行“三步走”流程)\n",
"\"\"\"\n",
"from paddle_quantum.mbqc.simulator import MBQC\n",
"\n",
"# 实例化 MBQC 类创建一个模型\n",
"mbqc = MBQC()\n",
"\n",
"# “三步走”中第一步,设置图\n",
"mbqc.set_graph(graph)\n",
"\n",
"# 设置初始态 (可选)\n",
"mbqc.set_input_state(input_state)\n",
"\n",
"# “三步走”中第二步,单比特测量\n",
"mbqc.measure(which_qubit, basis)\n",
"mbqc.measure(which_qubit, basis)\n",
"......\n",
"\n",
"# “三步走”中第三步,纠正副产品\n",
"mbqc.correct_byproduct(gate, which_qubit, power)\n",
"\n",
"# 输出运行后的经典和量子输出结果\n",
"classical_output = mbqc.get_classical_output()\n",
"quantum_output = mbqc.get_quantum_output()\n",
"```\n",
"\n",
"如果输入为测量模式,只需调用 `run_pattern` 类方法即可完成 MBQC 模型运算过程,格式如下:\n",
"\n",
"```python\n",
"\"\"\"\n",
"MBQC 模拟模块调用格式(以测量模式为输入,执行 \"EMC\" 命令)\n",
"\"\"\"\n",
"from paddle_quantum.mbqc.simulator import MBQC\n",
"\n",
"# 实例化 MBQC 类创建一个模型\n",
"mbqc = MBQC()\n",
"\n",
"# 设置测量模式\n",
"mbqc.set_pattern(pattern)\n",
"\n",
"# 设置初始态 (可选)\n",
"mbqc.set_input_state(input_state)\n",
"\n",
"# 运行测量模式\n",
"mbqc.run_pattern()\n",
"\n",
"# 输出运行后的经典和量子输出结果\n",
"classical_output = mbqc.get_classical_output()\n",
"quantum_output = mbqc.get_quantum_output()\n",
"```"
],
"metadata": {
"tags": []
}
},
{
"cell_type": "markdown",
"source": [
"跟据前面的介绍,相信大家对 MBQC 模型以及我们设计的 MBQC 模拟模块有了大致的了解。下面我们用两个示例带领大家进行一些实战。"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### 2. 使用示例:用 MBQC 实现任意单比特量子门\n",
"\n",
"跟据量子门的分解,我们知道任意单比特量子门 $U$ 都可以分解为 $ U = R_x(\\gamma)R_z(\\beta)R_x(\\alpha)$ 的形式(忽略全局相位)[17] 。在 MBQC 模型中,这样的单比特量子门可以按如下的方式实现(参见图 3) [15] :准备五个量子比特,最左侧是输入比特,最右侧为输出比特。输入量子态 $|\\psi\\rangle$,其余量子比特初始化为加态,相邻比特作用控制 Z 门,对第一个比特作 $X$ 测量,对中间三个比特依次进行适应性测量,前四个比特的测量结果依次记为 $s_1$, $s_2$, $s_3$, $s_4$,根据测量结果对得到的量子态进行副产品修正,则在第五个比特上输出的结果为 $U|\\psi\\rangle$。\n",
"\n",
"![Single qubit pattern](./figures/mbqc-fig-single_qubit_pattern_CN.jpg)\n",
"<div style=\"text-align:center\">图 3: MBQC 模型下任意单比特量子门的实现方式 </div>\n",
"\n",
"**注意**:测量完前四个比特后,第五个量子比特的状态为 $X^{s_2 + s_4}Z^{s_1 + s_3} U|\\psi\\rangle$,其中 $X^{s_2 + s_4}Z^{s_1 + s_3}$ 就是所谓的副产品,我们需要跟据测量结果,对此进行修正,才能得到想要的 $U|\\psi\\rangle$。\n",
"\n",
"以下是代码展示:"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"#### 引入计算所需要的模块\n",
"\n",
"一方面,我们需要引入 `numpy` 和 `paddle` 两个常用的计算模块;另一方面,我们需要引入 MBQC 模拟的相关模块,其中 `simulator` 为模拟的核心模块,主要包含 `MBQC` 类,我们可以实例化这个类,搭建属于自己的 MBQC 模型;`qobject` 包含量子信息处理常用的量子对象(如:`State`,`Circuit`,`Pattern`等);`utils` 包含计算所需要的常用函数(如:`plus_state` 加态,`basis` 常用测量基等)。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# 引入常用的计算模块\n",
"from numpy import pi\n",
"from paddle import to_tensor, matmul\n",
"# 引入 MBQC 模拟相关模块\n",
"from paddle_quantum.mbqc.simulator import MBQC\n",
"from paddle_quantum.mbqc.qobject import State\n",
"from paddle_quantum.mbqc.utils import rotation_gate, basis, random_state_vector, compare_by_vector"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"#### 输入图和量子态\n",
"\n",
"接下来,我们可以自定义要输入的图,在此例中,如图 $3$ 所示,我们需要输入的是五个带标签的节点(记作 `['1', '2', '3', '4', '5']` )和图中的四条边(记作 `[('1', '2'), ('2', '3'), ('3', '4'), ('4', '5')]` ),并在最左侧的比特 `'1'` 上输入量子态,同时初始化测量的角度。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# 构造用于 MBQC 计算的图\n",
"V = ['1', '2', '3', '4', '5']\n",
"E = [('1', '2'), ('2', '3'), ('3', '4'), ('4', '5')]\n",
"G = [V, E]\n",
"# 生成一个随机的量子态向量\n",
"input_psi = random_state_vector(1)\n",
"# 用量子态向量和系统标签,在第一个节点上构造一个量子态\n",
"input_state = State(input_psi, ['1'])\n",
"# 初始化角度,注意为 Tensor 形式\n",
"alpha = to_tensor([pi / 6], dtype='float64')\n",
"beta = to_tensor([pi / 4], dtype='float64')\n",
"gamma = to_tensor([pi / 3], dtype='float64')"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"#### 初始化 MBQC 模型\n",
"\n",
"实例化 `MBQC` 类并设置图和输入量子态的信息,就可以搭建属于自己的 MBQC 模型了。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# 实例化 MBQC 类\n",
"mbqc = MBQC()\n",
"# 输入图的信息\n",
"mbqc.set_graph(G)\n",
"# 输入初始量子态的信息\n",
"mbqc.set_input_state(input_state)"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"之后,我们依次对四个节点进行测量。"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"#### 测量第一个节点\n",
"\n",
"根据图 3,第一个比特的测量方式为 $X$ 测量,也就是 $XY$ 平面测量角度为 $0$ 的情形,即 $\\theta_1 = 0$。 "
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# 计算第一个比特的测量角度\n",
"theta1 = to_tensor([0], dtype='float64')\n",
"# 对第一个比特进行测量\n",
"mbqc.measure('1', basis('XY', theta1))"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"第一个比特的测量不涉及适应性的问题,所以比较简单,但对于第二、三和四个比特而言,其测量角度就需要考虑前面的测量结果了。"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"#### 测量第二个节点\n",
"\n",
"根据图 3,第二个比特的测量方式为 $M^{XY}(\\theta_2)$ 测量,其中,\n",
"\n",
"$$\n",
"\\theta_2 = (-1)^{s_1 + 1} \\alpha, \\tag{2}\n",
"$$\n",
"\n",
"也就是 $XY$ 平面测量角度为 $(-1)^{s_1 + 1} \\alpha$ 的测量,其中 $s_1$ 为第一个节点的测量结果。 \n",
"\n",
"在 `MBQC` 类中,我们定义了类方法 `sum_outcomes` ,可以对指定输入标签的量子比特的测量结果进行求和运算,如果想要对求和结果额外加上一个数字 $x$,则可在第二个参数处赋值 $x$,否则为 $0$。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# 计算第二个比特的测量角度\n",
"theta2 = to_tensor((-1) ** mbqc.sum_outcomes(['1'], 1), dtype='float64') * alpha\n",
"# 对第二个比特进行测量\n",
"mbqc.measure('2', basis('XY', theta2))"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"#### 测量第三个节点\n",
"\n",
"根据图 3,第三个比特的测量方式为 $M^{XY}(\\theta_3)$ 测量,其中,\n",
"\n",
"$$\n",
"\\theta_3 = (-1)^{s_2 + 1} \\beta, \\tag{3}\n",
"$$\n",
"\n",
"也就是 $XY$ 平面测量角度为 $(-1)^{s_2 + 1} \\beta$ 的测量,其中 $s_2$ 为第二个节点的测量结果。 "
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# 计算第三个比特的测量角度\n",
"theta3 = to_tensor((-1) ** mbqc.sum_outcomes(['2'], 1), dtype='float64') * beta\n",
"# 对第三个比特进行测量\n",
"mbqc.measure('3', basis('XY', theta3))"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"#### 测量第四个节点\n",
"\n",
"根据图 3,第四个比特的测量方式为 $M^{XY}(\\theta_4)$ 测量,其中,\n",
"\n",
"$$\n",
"\\theta_4 = (-1)^{s_1 + s_3 + 1} \\gamma, \\tag{4}\n",
"$$\n",
"\n",
"也就是 $XY$ 平面测量角度为 $(-1)^{s_1 + s_3 + 1} \\gamma$ 的测量,其中 $s_1$ 为第一个节点的测量结果,其中 $s_3$ 为第三个节点的测量结果。 "
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# 计算第四个比特的测量角度\n",
"theta4 = to_tensor((-1) ** mbqc.sum_outcomes(['1', '3'], 1), dtype='float64') * gamma\n",
"# 对第四个比特进行测量\n",
"mbqc.measure('4', basis('XY', theta4))"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"#### 对第五个节点输出的量子态进行修正\n",
"\n",
"前四个节点测量结束之后,第五个节点上的输出量子态并不是 $U|\\psi\\rangle$,而是附带有副产品的量子态 $X^{s_2 + s_4}Z^{s_1 + s_3} U|\\psi\\rangle$, 如果希望输出量子态为 $U|\\psi\\rangle$,需要在测量结束之后对副产品进行修正。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# 对量子态的副产品进行修正\n",
"mbqc.correct_byproduct('X','5', mbqc.sum_outcomes(['2','4']))\n",
"mbqc.correct_byproduct('Z','5', mbqc.sum_outcomes(['1','3']))"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"#### 读取修正后的量子态并与预期的量子态进行比较\n",
"\n",
"调用 `get_classical_output` 和 `get_quantum_output` 分别获取经典和量子输出结果。为了方便检验修正之后的量子态是否为我们预期的单比特量子门演化后的量子态 $U|\\psi\\rangle$,我们在 `utils` 模块中定义了两种比较两个量子态是否相同的函数 `compare_by_vector` 和 `compare_by_density`,前者是通过量子态列向量进行比较,后者是将量子态转化为密度矩阵进行比较。实际使用的时候,我们可以根据自己的需求调用这两个函数。若两个量子态相同,则该函数输出误差范数,并打印 \"They are exactly the same states.\" 字样。(注意:我们默认误差范数在 1e-14 到 1e-16 之间的两个量子态为同一量子态。)"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# 读取经典输出结果\n",
"classical_output = mbqc.get_classical_output()\n",
"# 读取量子输出结果\n",
"quantum_output = mbqc.get_quantum_output()\n",
"\n",
"# 计算预期的量子态列向量\n",
"vector_std = matmul(rotation_gate('x', gamma),\n",
" matmul(rotation_gate('z', beta),\n",
" matmul(rotation_gate('x', alpha), input_psi)))\n",
"# 构造预期的量子态\n",
"state_std = State(vector_std, ['5'])\n",
"\n",
"# 与预期的输出态进行比较\n",
"compare_by_vector(quantum_output, state_std)"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### 3. 使用示例: 用 MBQC 实现 CNOT 门\n",
"\n",
"CNOT 门是电路模型中常用的两比特门,在 MBQC 模型中, CNOT 门的实现方案如下(参见图 4) [7]:准备 $15$ 个量子比特,第 $1$、$9$ 比特是输入比特,最右侧 $7$ 和 $15$ 为输出比特。输入量子态 $|\\psi\\rangle$,其余量子比特初始化为加态,图中相连接的比特作用控制 Z 门。对第 $1, 9, 10, 11, 13, 14$ 做 $X$ 测量,对 $2, 3, 4, 5, 6, 8, 12$ 做 $Y$ 测量(注意:这些测量的角度无依赖关系,交换测量顺序对测量结果没有影响),对副产品算符进行修正后,在 $7$ 和 $15$ 输出的量子比特将会为 $\\text{CNOT}|\\psi\\rangle$。\n",
"\n",
"![CNOT pattern](./figures/mbqc-fig-cnot_pattern.jpg)\n",
"<div style=\"text-align:center\">图 4: MBQC 模型下 CNOT 门的一种实现方式 </div>\n",
"\n",
"**注意**:与前面的单比特量子门类似,我们需要在测量完之后对副产品进行修正才能得到预期的 $\\text{CNOT}|\\psi\\rangle$。\n",
"\n",
"以下是完整的代码展示:"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# 引入常用的计算模块\n",
"from paddle import to_tensor, matmul\n",
"# 引入 MBQC 模拟相关模块\n",
"from paddle_quantum.mbqc.simulator import MBQC\n",
"from paddle_quantum.mbqc.qobject import State\n",
"from paddle_quantum.mbqc.utils import pauli_gate, cnot_gate, basis, random_state_vector, compare_by_vector\n",
"\n",
"# 定义 X 门和 Z 门,X 测量和 Z 测量\n",
"X = pauli_gate('X')\n",
"Z = pauli_gate('Z')\n",
"X_basis = basis('X')\n",
"Y_basis = basis('Y')\n",
"\n",
"# 定义用于 MBQC 计算的图\n",
"V = [str(i) for i in range(1, 16)]\n",
"E = [('1', '2'), ('2', '3'), ('3', '4'), ('4', '5'), \n",
" ('5', '6'), ('6', '7'), ('4', '8'), ('8', '12'),\n",
" ('9', '10'), ('10', '11'), ('11', '12'), \n",
" ('12', '13'), ('13', '14'), ('14', '15')]\n",
"G = [V, E]\n",
"\n",
"# 生成一个随机的量子态列向量\n",
"input_psi = random_state_vector(2)\n",
"# 用量子态向量和系统标签,在指定节点上构造一个量子态\n",
"input_state = State(input_psi, ['1','9'])\n",
"\n",
"# 初始化 MBQC 类\n",
"mbqc = MBQC()\n",
"# 输入图的信息\n",
"mbqc.set_graph(G)\n",
"# 输入初始量子态的信息\n",
"mbqc.set_input_state(input_state)\n",
"\n",
"# 依次对节点进行测量,注意以下测量顺序可以任意交换\n",
"mbqc.measure('1', X_basis)\n",
"mbqc.measure('2', Y_basis)\n",
"mbqc.measure('3', Y_basis)\n",
"mbqc.measure('4', Y_basis)\n",
"mbqc.measure('5', Y_basis)\n",
"mbqc.measure('6', Y_basis)\n",
"mbqc.measure('8', Y_basis)\n",
"mbqc.measure('9', X_basis)\n",
"mbqc.measure('10', X_basis)\n",
"mbqc.measure('11', X_basis)\n",
"mbqc.measure('12', Y_basis)\n",
"mbqc.measure('13', X_basis)\n",
"mbqc.measure('14', X_basis)\n",
"\n",
"# 计算副产品的系数\n",
"cx = mbqc.sum_outcomes(['2', '3', '5', '6'])\n",
"tx = mbqc.sum_outcomes(['2', '3', '8', '10', '12', '14'])\n",
"cz = mbqc.sum_outcomes(['1', '3', '4', '5', '8', '9', '11'], 1)\n",
"tz = mbqc.sum_outcomes(['9', '11', '13'])\n",
"\n",
"# 对测量后的量子态进行副产品修正\n",
"mbqc.correct_byproduct('X', '7', cx)\n",
"mbqc.correct_byproduct('X', '15', tx)\n",
"mbqc.correct_byproduct('Z', '7', cz)\n",
"mbqc.correct_byproduct('Z', '15', tz)\n",
"\n",
"# 读取经典输出结果\n",
"classical_output = mbqc.get_classical_output()\n",
"# 读取量子输出结果\n",
"quantum_output = mbqc.get_quantum_output()\n",
"\n",
"# 构造预期的量子态\n",
"vector_std = matmul(to_tensor(cnot_gate()), input_psi)\n",
"state_std = State(vector_std, ['7', '15'])\n",
"\n",
"# 与预期的量子态作比较\n",
"compare_by_vector(quantum_output, state_std)"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## 欢迎使用 MBQC 模块!\n",
"\n",
"在介绍完上述 MBQC 计算模型以及相关模块的简单示例之后,我们建议您参阅下面的教程进一步学习。\n",
"\n",
"- [基于测量的量子近似优化算法](QAOA_CN.ipynb)\n",
"- [MBQC 模型下求解多项式组合优化问题](PUBO_CN.ipynb)\n",
"\n",
"我们开发的 MBQC 模块作为量桨平台的新功能,它所能做的远不止上述这几个例子,我们真诚希望您可以使用 MBQC 模型和我们开发的模块去探索更多有趣的实例!关于 MBQC 计算模型本身更为详细的学习,有兴趣的读者可以参考 [15,16]。"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"---\n",
"\n",
"## 参考文献\n",
"\n",
"[1] Deutsch, David Elieser. \"Quantum computational networks.\" [Proceedings of the Royal Society of London. A. 425.1868 (1989): 73-90.](https://royalsocietypublishing.org/doi/abs/10.1098/rspa.1989.0099)\n",
"\n",
"[2] Barenco, Adriano, et al. \"Elementary gates for quantum computation.\" [Physical review A 52.5 (1995): 3457.](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.52.3457)\n",
"\n",
"[3] Gottesman, Daniel, and Isaac L. Chuang. \"Demonstrating the viability of universal quantum computation using teleportation and single-qubit operations.\" [Nature 402.6760 (1999): 390-393.](https://www.nature.com/articles/46503?__hstc=13887208.d9c6f9c40e1956d463f0af8da73a29a7.1475020800048.1475020800050.1475020800051.2&__hssc=13887208.1.1475020800051&__hsfp=1773666937)\n",
"\n",
"[4] Nielsen, Michael A. \"Quantum computation by measurement and quantum memory.\" [Physics Letters A 308.2-3 (2003): 96-100.](https://www.sciencedirect.com/science/article/abs/pii/S0375960102018030)\n",
"\n",
"[5] Leung, Debbie W. \"Quantum computation by measurements.\" [International Journal of Quantum Information 2.01 (2004): 33-43.](https://www.worldscientific.com/doi/abs/10.1142/S0219749904000055)\n",
"\n",
"[6] Robert Raussendorf, et al. \"A one-way quantum computer.\" [Physical Review Letters 86.22 (2001): 5188.](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.86.5188)\n",
"\n",
"[7] Raussendorf, Robert, and Hans J. Briegel. \"Computational model underlying the one-way quantum computer.\" [Quantum Information & Computation 2.6 (2002): 443-486.](https://dl.acm.org/doi/abs/10.5555/2011492.2011495)\n",
"\n",
"[8] Robert Raussendorf, et al. \"Measurement-based quantum computation on cluster states.\" [Physical Review A 68.2 (2003): 022312.](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.68.022312)\n",
"\n",
"[9] Briegel, Hans J., et al. \"Measurement-based quantum computation.\" [Nature Physics 5.1 (2009): 19-26.](https://www.nature.com/articles/nphys1157)\n",
"\n",
"[10] Aliferis, Panos, and Debbie W. Leung. \"Computation by measurements: a unifying picture.\" [Physical Review A 70.6 (2004): 062314.](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.70.062314)\n",
"\n",
"[11] Broadbent, Anne, et al. \"Universal blind quantum computation.\" [2009 50th Annual IEEE Symposium on Foundations of Computer Science. IEEE, 2009.](https://arxiv.org/abs/0807.4154)\n",
"\n",
"[12] Morimae, Tomoyuki. \"Verification for measurement-only blind quantum computing.\" [Physical Review A 89.6 (2014): 060302.](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.89.060302)\n",
"\n",
"[13] Larsen, Mikkel V., et al. \"Deterministic generation of a two-dimensional cluster state.\" [Science 366.6463 (2019): 369-372.](https://science.sciencemag.org/content/366/6463/369)\n",
"\n",
"[14] Asavanant, Warit, et al. \"Generation of time-domain-multiplexed two-dimensional cluster state.\" [Science 366.6463 (2019): 373-376.](https://science.sciencemag.org/content/366/6463/373)\n",
"\n",
"[15] Richard Jozsa, et al. \"An introduction to measurement based quantum computation.\" [arXiv:quant-ph/0508124](https://arxiv.org/abs/quant-ph/0508124v2)\n",
"\n",
"[16] Nielsen, Michael A. \"Cluster-state quantum computation.\" [Reports on Mathematical Physics 57.1 (2006): 147-161.](https://www.sciencedirect.com/science/article/abs/pii/S0034487706800145)\n",
"\n",
"[17] Nielsen, Michael A., and Isaac Chuang. \"Quantum computation and quantum information.\"[Cambridge university press (2010).](https://www.cambridge.org/core/books/quantum-computation-and-quantum-information/01E10196D0A682A6AEFFEA52D53BE9AE)\n",
"\n",
"[18] Danos, Vincent, et al. \"The measurement calculus.\" [Journal of the ACM (JACM) 54.2 (2007): 8-es.](https://dl.acm.org/doi/abs/10.1145/1219092.1219096)"
],
"metadata": {}
}
],
"metadata": {
"interpreter": {
"hash": "07efdcd4b820c98a756949507a4d29d7862823915ec7477944641bea022f4f62"
},
"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": {
"height": "calc(100% - 180px)",
"left": "10px",
"top": "150px",
"width": "268.448px"
},
"toc_section_display": true,
"toc_window_display": false
},
"toc-autonumbering": false,
"toc-showcode": false,
"toc-showmarkdowntxt": false,
"toc-showtags": false,
"varInspector": {
"cols": {
"lenName": 16,
"lenType": 16,
"lenVar": 40
},
"kernels_config": {
"python": {
"delete_cmd_postfix": "",
"delete_cmd_prefix": "del ",
"library": "var_list.py",
"varRefreshCmd": "print(var_dic_list())"
},
"r": {
"delete_cmd_postfix": ") ",
"delete_cmd_prefix": "rm(",
"library": "var_list.r",
"varRefreshCmd": "cat(var_dic_list()) "
}
},
"types_to_exclude": [
"module",
"function",
"builtin_function_or_method",
"instance",
"_Feature"
],
"window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}
\ No newline at end of file
{
"cells": [
{
"cell_type": "markdown",
"source": [
"# MBQC Quick Start Guide\n",
"\n",
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
],
"metadata": {
"tags": []
}
},
{
"cell_type": "markdown",
"source": [
"## Introduction\n",
"\n",
"Quantum computation utilizes the peculiar laws in the quantum world and provides us with a novel and promising way of information processing. The essence of quantum computation is to evolve the initially prepared quantum state into another expected one, and then make measurements on it to obtain the required classical results. However, the approaches of quantum state evolution are varied in different computation models. The widely used **quantum circuit model** [1,2] completes the evolution by performing quantum gate operations, which can be regarded as a quantum analog of the classical computing model. In contrast, **measurement-based quantum computation (MBQC)** provides a completely different approach for quantum computing.\n",
"\n",
"As its name suggests, the entire evolution in MBQC is completed via quantum measurements. There are mainly two variants of measurement-based quantum computation in the literature: **teleportation-based quantum computing (TQC)** model[3-5] and **one-way quantum computer (1WQC)** model [6-9]. The former requires joint measurements on multiple qubits, while the latter only requires single-qubit measurements. After these two variants were proposed, they were proved to be highly correlated and admit a one-to-one correspondence [10]. So without further declaration, **all of the following discussions about MBQC will refer to the 1WQC model.**\n",
"\n",
"MBQC is a unique model in quantum computation and has no classical analog. The model controls the computation by measuring part of the qubits of an entangled state, with those remaining unmeasured undergoing the evolution correspondingly. By controlling measurements, we can complete any desired evolution. The computation in MBQC is mainly divided into three steps. The first step is to prepare a resource state, which is a highly entangled many-body quantum state. This state can be prepared offline and can be independent of specific computational tasks. The second step is to sequentially perform single-qubit measurements on each qubit of the prepared resource state, where subsequent measurements can depend on previous measurement outcomes, that is, measurements can be adaptive. The third step is to perform byproduct corrections on the final state. Finally, we do classical data processing on measurement outcomes to obtain the required computation results. \n",
"\n",
"A typical example of MBQC algorithms is shown in Figure 1. The grid represents a commonly used quantum resource state (called cluster state, see below for details). Each vertex on the grid represents a qubit, while the entire grid represents a highly entangled quantum state. We measure each qubit one by one in a specific measurement basis (In the vertices, X, Y, Z, XY, etc. represent the corresponding measurement basis), and then perform byproduct corrections (to eliminate the effect of Pauli X and Pauli Z operators), to complete the computation.\n",
"\n",
"![MBQC example](./figures/mbqc-fig-general_pattern.jpg)\n",
"<div style=\"text-align:center\">Figure 1: A typical example of MBQC algorithm where computation is proceeded by measuring each qubit on the vertex. </div>\n",
"\n",
"The \"three-step\" process of MBQC has brought us quantities of benefits. For example, if the quantum state prepared in the first step is too noisy, we can simply discard this state **before computation begins** (that is, before any measurement is implemented), and prepare it again to ensure the accuracy of the computational results. Since the resource state can be prepared offline and independent of specific computing tasks, it can also be applied to secure delegated quantum computation [11,12] to protect clients' privacy. In addition, single-qubit measurement is easier to be implemented in practice than quantum gates. Non-adaptive quantum measurements can even be carried out simultaneously, thereby, reducing the computation depth and requiring less coherence time of the quantum system. The difficulty of realizing MBQC mainly lies in resource state preparation in the first step. Such a quantum state is highly entangled and the number of qubits required is much larger than that of the usual circuit model. For recent progress on the resource state preparation, please refer to [13,14]. Table 1 briefly summarizes both advantages and limitations of MBQC and quantum circuit models.\n",
"\n",
"| | Quantum circuit model | MBQC model |\n",
"|:---: | :---: | :---: |\n",
"| Pros| has classical analog <br/> easy to understand <br/> and develop applications | resource state can be prepared offline <br/> easy to implement single-qubit measurement <br/> measurements can be implemented simultaneously <br/> leading to lower implementation depth |\n",
"|Cons| implementation order fixed <br/> depth restricted by coherence time| no classical analog thus super-intuitive <br/> resource state requires a large number of qubits <br/> thus hard to prepare in practice| \n",
"\n",
"<div style=\"text-align:center\">Table 1: Advantages and limitations of MBQC and quantum circuit models </div>\n",
"\n",
"Since MBQC does not have a classical analog, it may be difficult for beginners to understand it intuitively. However, it is this super-intuitive approach that brings a wide range of opportunities to explore the unknowns. So, let's dive into the world of MBQC and explore the mysteries together!"
],
"metadata": {
"tags": []
}
},
{
"cell_type": "markdown",
"source": [
"## Prerequisites\n",
"\n",
"Before introducing MBQC and our module in more detail, let's briefly review the two building blocks of MBQC.\n",
"\n",
"### 1. Graph and graph state\n",
" \n",
"Given a graph $G=(V, E)$ with vertices set $V$ and edges set $E$, we can prepare an entangled quantum state by initializing a plus state $|+\\rangle = (|0\\rangle + |1\\rangle) / \\sqrt{2}$ to each vertex of $G$ and performing a control Z operation $CZ = |0\\rangle\\langle 0| \\otimes I + |1\\rangle\\langle1|\\otimes Z$ between each connected qubit pair. The resulting quantum state is called the **graph state** of $G$, denoted by $|G\\rangle$, such that:\n",
" \n",
"$$\n",
"|G\\rangle = \\prod_{(a,b) \\in E} CZ_{ab} \\left(\\bigotimes_{v \\in V}|+\\rangle_v\\right). \\tag{1}\n",
"$$\n",
"\n",
"The concept of graph state is nothing particular. Actually, the well-known Bell state and GHZ state are both graph states up to local unitary transformations. Besides, if the underlying graph we consider is a 2D grid then the corresponding graph state is called **cluster state**, depicted in Figure 2.\n",
"\n",
"![Graph states](./figures/mbqc-fig-graph_states.jpg)\n",
"<div style=\"text-align:center\">Figure 2:(i) the graph of a $Bell$ state; (ii) the graph of a 4-qubit $GHZ$ state; (iii) the graph of a cluster state </div>\n",
"\n",
"### 2. Projective measurement\n",
"\n",
"Quantum measurement is one of the main concepts in quantum information processing. In the circuit model, measurements are performed usually at the end of the circuit to extract classical results from the quantum state. However, in MBQC, quantum measurements are also used to drive the computation. In the MBQC model, we use single-qubit measurements by default and mainly use 0/1 projection measurement. According to Born's rule [17], given a projective measurement basis $\\{|\\psi_0\\rangle, |\\psi_1\\rangle\\}$ and a quantum state $|\\phi\\rangle$, the probability that the outcome $s \\in \\{0,1\\}$ occurs is given by $p(s) = |\\langle \\psi_s|\\phi\\rangle|^2$, and the corresponding post-measurement state is $| \\psi_s\\rangle\\langle\\psi_s|\\phi\\rangle / \\sqrt{p(s)}$. In other words, the state of the measured qubit collapses into $|\\psi_s\\rangle$, while the state of other qubits evolves to $\\langle\\psi_s|\\phi\\rangle / \\sqrt{p(s)}$.\n",
"\n",
"Single-qubit measurements are commonly used, especially the binary projective measurements on the $XY$, $YZ$ and $XZ$ planes, defined respectively by the following orthonormal bases,\n",
"\n",
"- XY-plane measurement: $M^{XY}(\\theta) = \\{R_z(\\theta) |+\\rangle, R_z(\\theta) |-\\rangle \\}$, reducing to $X$ measurement if $\\theta = 0$ and $Y$ measurement if $\\theta = \\frac{\\pi}{2}$;\n",
"\n",
"- YZ-plane measurement: $M^{YZ}(\\theta) = \\{R_x(\\theta)|0\\rangle, R_x(\\theta)|1\\rangle\\}$, reducing to $Z$ measurement if $\\theta = 0$;\n",
"\n",
"- XZ-plane measurement: $M^{XZ}(\\theta) = \\{R_y(\\theta)|0\\rangle, R_y(\\theta)|1\\rangle\\}$, reducing to $Z$ measurement if $\\theta = 0$.\n",
"\n",
"In the above definitions, we use $|+\\rangle = (|0\\rangle + |1\\rangle)/ \\sqrt{2},|-\\rangle = (|0\\rangle - |1\\rangle)/ \\sqrt{2}$, and $R_x, R_y, R_z$ are rotation gates around $x,y,z$ axes respectively."
],
"metadata": {
"tags": []
}
},
{
"cell_type": "markdown",
"source": [
"## MBQC Module Framework\n",
"\n",
"\n",
"### 1. Model and code implementation\n",
"\n",
"#### \"Three-step\" process\n",
"\n",
"As is mentioned above, MBQC is different from the quantum circuit model. The computation in MBQC is driven by measuring each qubit on a graph state. To be specific, the MBQC model consists of the following three steps.\n",
"\n",
"- **Graph state preparation**: that is, to prepare a many-body entangled state. Given vertices and edges in a graph, we can prepare a graph state by initializing a plus state on each vertex and performing a control Z operation between each connected qubit pair. Since a graph state and its underlying graph have a one-to-one correspondence, it suffices to work with the graph only. In addition, we can selectively replace some of the plus states in the graph with a customized input state if necessary.\n",
"\n",
"- **Single-qubit measurement**: that is, to perform single-qubit measurements on the prepared graph state with specific measurement bases. The measurement angles can be adaptively adjusted according to previous outcomes. Non-adaptive measurements commute with each other in simulation and can even be performed simultaneously in experiments. \n",
"\n",
"- **Byproduct correction**: Due to the random nature of quantum measurement, the evolution of the unmeasured quantum state cannot be uniquely determined. In other words, the unmeasured quantum state may undergo some extra evolutions, called **byproducts**. So the last step is to correct these to obtain the expected result. If the final output is not a quantum state but the measurement outcomes, it suffices to eliminate the effect of byproducts via classical data processing only.\n",
"\n",
"In conclusion, the \"three-step\" process of MBQC includes graph state preparation, single-qubit measurement, and byproduct correction. The first two steps are indispensable while the implementation of the third step depends on the form of expected results.\n",
"\n",
"#### Measurement pattern and \"EMC\" language\n",
"\n",
"Besides the \"three-step\" process, an MBQC model can also be described by the **EMC** language from the measurement calculus [18]. As is mentioned above, MBQC admits a one-to-one correspondence to the circuit model. We can usually call the MBQC equivalent of a quantum circuit as a measurement **pattern** while the equivalent of a specific gate/measurement is called **subpattern** [18]. In the \"EMC\" language, we usually call an entanglement operation \"an entanglement command\", denoted by \"E\"; call a measurement operation \"a measurement command\", denoted by \"M\"; call a byproduct correction operation \"a byproduct correction command\", denoted by \"C\". Therefore, in parallel with the\"three-step\" process, MBQC is also characterized by an \"EMC\" command list. The process of computation is to execute commands in the command list in order. However, to familiarize ourselves with MBQC quickly, we will adopt the conventional \"three-step\" process to describe MBQC in this tutorial.\n",
"\n",
"#### Code implementation\n",
"\n",
"In terms of code implementation, we provide a simulation module ``simulator`` that mainly consists of a class `MBQC` with attributes and methods necessary for MBQC simulation. We can instantiate an MBQC class, create and perform our MBQC-based algorithms with it.\n",
"\n",
"```python\n",
"# code implementation\n",
"from paddle_quantum.mbqc.simulator import MBQC\n",
"\n",
"class MBQC:\n",
" def __init__():\n",
" ...\n",
"```\n",
"\n",
"After instantiation, we can call class methods step by step to complete the MBQC computation process. Here, we briefly introduce some frequently used methods and their functionalities in Table 2. Please refer to the API documentation for details.\n",
"\n",
"|MBQC class method|Functionality|\n",
"|:---:|:---:|\n",
"|set_graph|input a graph for MBQC|\n",
"|set_pattern|input a measurement pattern for MBQC|\n",
"|set_input_state|input initial quantum state|\n",
"|draw_process|draw the dymanical process of MBQC computation|\n",
"|track_progress|track the running progress of MBQC computation|\n",
"|measure|perform single-qubit measurement|\n",
"|sum_outcomes|sum outcomes of the measured qubits|\n",
"|correct_byproduct|correct byproduct operators|\n",
"|run_pattern|run the input measurement pattern|\n",
"|get_classical_output|return classical results|\n",
"|get_quantum_output|return quantum results|\n",
"\n",
"<div style=\"text-align:center\">Table 2: Frequently used methods of the class MBQC and their functionalities </div>\n",
"<br/>\n",
"\n",
"In the ``simulator`` module, we provide two simulation modes, \"graph\" and \"pattern\", corresponding to the two equivalent descriptions of the MBQC computation process respectively. If we set a graph, the whole computation needs to follow the \"three-step\" process. It is worth mentioning that we design a ** vertex dynamic classification algorithm** to simulate the MBQC computation process efficiently. Roughly speaking, we integrate the first two steps of the process, change the execution order of entanglement and measurement operations automatically to reduce the number of effective qubits involved in the computation and thereby improve the efficiency. The outline to use the simulation module is as follows:\n",
"\n",
"```python\n",
"\"\"\"\n",
"MBQC simulation module usage (set a graph and proceed with the \"three-step\" process)\n",
"\"\"\"\n",
"from paddle_quantum.mbqc.simulator import MBQC\n",
"\n",
"# Instantiate MBQC and create an MBQC model\n",
"mbqc = MBQC()\n",
"\n",
"# First step of the \"three-step\" process, set a graph\n",
"mbqc.set_graph(graph)\n",
"\n",
"# Set an initial input state (optional)\n",
"mbqc.set_input_state(input_state)\n",
"\n",
"# Second step of the \"three-step\" process, perform single-qubit measurements\n",
"mbqc.measure(which_qubit, basis)\n",
"mbqc.measure(which_qubit, basis)\n",
"......\n",
"\n",
"# Third step of the \"three-step\" process, correct byproducts\n",
"mbqc.correct_byproduct(gate, which_qubit, power)\n",
"\n",
"# Obtain the classical and quantum outputs\n",
"classical_output = mbqc.get_classical_output()\n",
"quantum_output = mbqc.get_quantum_output()\n",
"```\n",
"\n",
"If we set a pattern to the ``MBQC`` class, we need to call the `run_pattern` method to complete the simulation.\n",
"\n",
"```python\n",
"\"\"\"\n",
"MBQC simulation module usage (set a pattern and simulate by \"EMC\" commands)\n",
"\"\"\"\n",
"from paddle_quantum.mbqc.simulator import MBQC\n",
"\n",
"# Instantiate MBQC and create an MBQC model\n",
"mbqc = MBQC()\n",
"\n",
"# Set a measurement pattern\n",
"mbqc.set_pattern(pattern)\n",
"\n",
"# Set an initial input state (optional) \n",
"mbqc.set_input_state(input_state)\n",
"\n",
"# Run the measurement pattern\n",
"mbqc.run_pattern()\n",
"\n",
"# Obtain the classical and quantum outputs\n",
"classical_output = mbqc.get_classical_output()\n",
"quantum_output = mbqc.get_quantum_output()\n",
"```"
],
"metadata": {
"tags": []
}
},
{
"cell_type": "markdown",
"source": [
"After going through the above introduction, I am sure you already have a basic understanding of MBQC and our simulation module. Now, let's do some combat exercises with the following two examples!"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### 2. Example: general single-qubit unitary gate in MBQC\n",
"\n",
"For a general single-qubit unitary gate $U$, it can be decomposed to $ U = R_x(\\gamma)R_z(\\beta)R_x(\\alpha)$ up to a global phase [17]. In MBQC, this unitary gate can be realized in the following way [15]. As shown in Figure 3: prepare five qubits, with input on the leftmost qubit while output on the rightmost qubit; input a state $|\\psi\\rangle$ and initialize other qubits with $|+\\rangle$; apply a $CZ$ operation to each connected qubit pair; perform $X$-measurement on the first qubit and adaptive measurements in the $XY$-plane on the middle three qubits, with the four measured qubits' outcomes recorded as $s_1$, $s_2$, $s_3$, $s_4$; correct byproducts to the state on qubit $5$ after all measurements. Then, the output state on qubit 5 will be $U|\\psi\\rangle$.\n",
"\n",
"\n",
"![Single qubit pattern](./figures/mbqc-fig-single_qubit_pattern_EN.jpg)\n",
"<div style=\"text-align:center\">Figure 3: Realizing a general single-qubit unitary gate in MBQC </div>\n",
"\n",
"**Note**: after measuring the first four qubits, state on qubit $5$ has the form of $X^{s_2 + s_4}Z^{s_1 + s_3} U|\\psi\\rangle$, where $X^{s_2 + s_4}$ and $Z^{s_1 + s_3}$ are the so-called byproducts. We need to correct them according to the measurement outcomes to get the desired state of $U|\\psi\\rangle$.\n",
"\n",
"Here is the code implementation:"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"#### Import relevant modules\n",
"\n",
"We first import two common modules `numpy` and `paddle`. Then we need to import the MBQC simulation module ``simulator`` which mainly contains the class ``MBQC``. We can instantiate this class and create an MBQC model. We also need to import the ``qobject`` module which contains quantum objects that are frequently used in quantum information processing (e.g. ``State``, ``Circuit``, ``Pattern``). Finally, we import the ``utils`` module that provides commonly used functions (e.g. ``plus_state``, ``basis`` etc.). "
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Import common calculation modules\n",
"from numpy import pi\n",
"from paddle import to_tensor, matmul\n",
"# Import relevant modules for MBQC simulation\n",
"from paddle_quantum.mbqc.simulator import MBQC\n",
"from paddle_quantum.mbqc.qobject import State\n",
"from paddle_quantum.mbqc.utils import rotation_gate, basis, random_state_vector, compare_by_vector"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"#### Set graph and state\n",
"\n",
"Then, we can set the graph on our own. For this instance in Figure 3, we need five vertices (recorded as `['1', '2', '3', '4', '5']`) and four edges (recorded as (`[('1', '2'), ('2', '3'), ('3', '4'), ('4', '5')]`)). We need to set an input the state on vertex `'1'` and initialize measurement angles."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Construct the underlying graph\n",
"V = ['1', '2', '3', '4', '5']\n",
"E = [('1', '2'), ('2', '3'), ('3', '4'), ('4', '5')]\n",
"G = [V, E]\n",
"# Generate a random state vector\n",
"input_psi = random_state_vector(1)\n",
"# Construct a quantum state on vertex '1'\n",
"input_state = State(input_psi, ['1'])\n",
"# Initialize measurement angles of type Tensor\n",
"alpha = to_tensor([pi / 6], dtype='float64')\n",
"beta = to_tensor([pi / 4], dtype='float64')\n",
"gamma = to_tensor([pi / 3], dtype='float64')"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"#### Instantiate an MBQC model\n",
"\n",
"Then we can construct our own MBQC model by instantiating the class `MBQC` and setting the graph and input state."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Instantiate MBQC\n",
"mbqc = MBQC()\n",
"# Set the graph\n",
"mbqc.set_graph(G)\n",
"# Set the input state\n",
"mbqc.set_input_state(input_state)"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"Then, we perform measurements on the first four vertices."
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"#### Measure the first vertex\n",
"\n",
"As shown in Figure 3, we perform $X$-measurement on the first vertex, that is, the measurement in $XY$-plane with an angle of $\\theta_1 = 0$。 "
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Calculate the angle for the first measurement\n",
"theta1 = to_tensor([0], dtype='float64')\n",
"# Measure the first vertex\n",
"mbqc.measure('1', basis('XY', theta1))"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"Measurement on the first vertex is straightforward because it is not adaptive. However, things will be tougher to the second, third, and fourth vertices, as the measurement angles are set adaptively according to the previous measurement outcomes."
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"#### Measure the second vertex\n",
"\n",
"As shown in Figure 3, the measurement on the second vertex has a form of $M^{XY}(\\theta_2)$, where\n",
"\n",
"$$\n",
"\\theta_2 = (-1)^{s_1 + 1} \\alpha, \\tag{2}\n",
"$$\n",
"\n",
"This is a measurement in the $XY$-plane with an adaptive angle $(-1)^{s_1 + 1} \\alpha$, where $s_1$ is the outcome of the first vertex. \n",
"\n",
"There is a method `sum_outcomes` in the class `MBQC` to calculate the summation of outcomes for vertices in the first argument. If we want to add an extra value \"$x$\" on top of the summation, we can set the second argument to be $x$.Otherwise, the default value of the second argument is $0$."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Calculate the angle for the second measurement\n",
"theta2 = to_tensor((-1) ** mbqc.sum_outcomes(['1'], 1), dtype='float64') * alpha\n",
"# Measure the second vertex\n",
"mbqc.measure('2', basis('XY', theta2))"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"#### Measure the third vertex\n",
"\n",
"As shown in Figure 3, the measurement on the third vertex has a form of $M^{XY}(\\theta_3)$, where\n",
"\n",
"$$\n",
"\\theta_3 = (-1)^{s_2 + 1} \\beta, \\tag{3}\n",
"$$\n",
"\n",
"This is a measurement in the $XY$-plane with an adaptive angle $(-1)^{s_2 + 1} \\beta$, where $s_2$ is the outcome of the second vertex."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Calculate the angle for the third measurement\n",
"theta3 = to_tensor((-1) ** mbqc.sum_outcomes(['2'], 1), dtype='float64') * beta\n",
"# Measure the third vertex\n",
"mbqc.measure('3', basis('XY', theta3))"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"#### Measure the fourth vertex\n",
"\n",
"As shown in Figure 3, the measurement on the fourth vertex has a form of $M^{XY}(\\theta_4)$, where\n",
"\n",
"$$\n",
"\\theta_4 = (-1)^{s_1 + s_3 + 1} \\gamma, \\tag{4}\n",
"$$\n",
"\n",
"This is a measurement in the $XY$-plane with an adaptive angle $(-1)^{s_1 + s_3 + 1} \\gamma$, where $s_1$ and $s_3$ are respectively the outcomes of the first and the third vertices."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Calculate the angle for the fourth measurement\n",
"theta4 = to_tensor((-1) ** mbqc.sum_outcomes(['1', '3'], 1), dtype='float64') * gamma\n",
"# Measure the fourth vertex\n",
"mbqc.measure('4', basis('XY', theta4))"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"#### Correct byproducts on the fifth vertex\n",
"\n",
"After measurements on the first four vertices, the state on the fifth vertex is not exactly $U|\\psi\\rangle$, but a state with byproducts $X^{s_2 + s_4}Z^{s_1 + s_3} U|\\psi\\rangle$. To obtain the desired $U|\\psi\\rangle$, we must correct byproducts on the fifth vertex."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Correct byproducts on the fifth vertex\n",
"mbqc.correct_byproduct('X','5', mbqc.sum_outcomes(['2','4']))\n",
"mbqc.correct_byproduct('Z','5', mbqc.sum_outcomes(['1','3']))"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"#### Obtain the final output state and compare it with the expected one\n",
"\n",
"We can call `get_classical_output` and `get_quantum_output` to obtain the classical and quantum outputs after simulation. We also provide in the module ``utils`` two functions `compare_by_vector` and `compare_by_density` to check if two given quantum states are identical. The former function compares two states by their state vectors, while the second function compares their density matrices. If two states are identical, both functions will return the norm difference of these two states, and print a statement: \"They are exactly the same states.\" (Note: we regard two states as the same if their norm difference is within the range of 1e-14 and 1e-16.)"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Obtain the classcial result\n",
"classical_output = mbqc.get_classical_output()\n",
"# Obtain the quantum result\n",
"quantum_output = mbqc.get_quantum_output()\n",
"\n",
"# Compute the expected state vector\n",
"vector_std = matmul(rotation_gate('x', gamma),\n",
" matmul(rotation_gate('z', beta),\n",
" matmul(rotation_gate('x', alpha), input_psi)))\n",
"# Construct the expected state on vertex '5'\n",
"state_std = State(vector_std, ['5'])\n",
"\n",
"# Compare with the expected state\n",
"compare_by_vector(quantum_output, state_std)"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### 3. Example: CNOT gate in MBQC\n",
"\n",
"CNOT gate is one of the most frequently used gates in the circuit model. In MBQC, the realization of a CNOT gate is shown in Figure 4 [7]: prepare $15$ qubits, with $1$, $9$ being the input qubits and $7$, $15$ being the output qubits; input a state $|\\psi\\rangle$ and initialize other vertices to $|+\\rangle$; apply a CZ operator to each connected qubit pairs; perform $X$-measurements on the vertices $1, 9, 10, 11, 13, 14$ and $Y$-measurement on the vertices $2, 3, 4, 5, 6, 8, 12$ (Note: All of these measurements are non-adaptive measurements, so the order of their executions can be permuted); correct byproducts on $7$ and $15$ to obtain the output state $\\text{CNOT}|\\psi\\rangle$.\n",
"\n",
"![CNOT pattern](./figures/mbqc-fig-cnot_pattern.jpg)\n",
"<div style=\"text-align:center\">Figure 4: Realization of CNOT gate in MBQC </div>\n",
"\n",
"**Note**: Similar to the first example, byproduct corrections are necessary to get the desired $\\text{CNOT}|\\psi\\rangle$.\n",
"\n",
"Here is a complete code implementation:"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Import common calculation modules\n",
"from paddle import to_tensor, matmul\n",
"\n",
"# Import relevant modules for MBQC simulation\n",
"from paddle_quantum.mbqc.simulator import MBQC\n",
"from paddle_quantum.mbqc.qobject import State\n",
"from paddle_quantum.mbqc.utils import pauli_gate, cnot_gate, basis, random_state_vector, compare_by_vector\n",
"\n",
"# Define Pauli X and Pauli Z gates and X, Y measurement bases\n",
"X = pauli_gate('X')\n",
"Z = pauli_gate('Z')\n",
"X_basis = basis('X')\n",
"Y_basis = basis('Y')\n",
"\n",
"# Define the underlying graph for computation\n",
"V = [str(i) for i in range(1, 16)]\n",
"E = [('1', '2'), ('2', '3'), ('3', '4'), ('4', '5'), \n",
" ('5', '6'), ('6', '7'), ('4', '8'), ('8', '12'),\n",
" ('9', '10'), ('10', '11'), ('11', '12'), \n",
" ('12', '13'), ('13', '14'), ('14', '15')]\n",
"G = [V, E]\n",
"\n",
"# Generate a random state vector\n",
"input_psi = random_state_vector(2)\n",
"# Construct a quantum state on vertices '1' and '9'\n",
"input_state = State(input_psi, ['1','9'])\n",
"\n",
"# Instantiate a MBQC class\n",
"mbqc = MBQC()\n",
"# Set the graph state\n",
"mbqc.set_graph(G)\n",
"# Set the input state\n",
"mbqc.set_input_state(input_state)\n",
"\n",
"# Measure each qubit step by step\n",
"mbqc.measure('1', X_basis)\n",
"mbqc.measure('2', Y_basis)\n",
"mbqc.measure('3', Y_basis)\n",
"mbqc.measure('4', Y_basis)\n",
"mbqc.measure('5', Y_basis)\n",
"mbqc.measure('6', Y_basis)\n",
"mbqc.measure('8', Y_basis)\n",
"mbqc.measure('9', X_basis)\n",
"mbqc.measure('10', X_basis)\n",
"mbqc.measure('11', X_basis)\n",
"mbqc.measure('12', Y_basis)\n",
"mbqc.measure('13', X_basis)\n",
"mbqc.measure('14', X_basis)\n",
"\n",
"# Compute the power of byproduct operators\n",
"cx = mbqc.sum_outcomes(['2', '3', '5', '6'])\n",
"tx = mbqc.sum_outcomes(['2', '3', '8', '10', '12', '14'])\n",
"cz = mbqc.sum_outcomes(['1', '3', '4', '5', '8', '9', '11'], 1)\n",
"tz = mbqc.sum_outcomes(['9', '11', '13'])\n",
"\n",
"# Correct the byproduct operators\n",
"mbqc.correct_byproduct('X', '7', cx)\n",
"mbqc.correct_byproduct('X', '15', tx)\n",
"mbqc.correct_byproduct('Z', '7', cz)\n",
"mbqc.correct_byproduct('Z', '15', tz)\n",
"\n",
"# Obtain the classcial result\n",
"classical_output = mbqc.get_classical_output()\n",
"# Obtain the quantum result\n",
"quantum_output = mbqc.get_quantum_output()\n",
"\n",
"# Construct the expected result\n",
"vector_std = matmul(to_tensor(cnot_gate()), input_psi)\n",
"state_std = State(vector_std, ['7', '15'])\n",
"\n",
"# Compare with the expected result\n",
"compare_by_vector(quantum_output, state_std)"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## Welcome Aboard!\n",
"\n",
"After this tutorial, we highly recommend learning the following ones for further exploration:\n",
"\n",
"- [Measurement-based Quantum Approximate Optimization Algorithm](QAOA_EN.ipynb)\n",
"- [Polynomial Unconstrained Boolean Optimization Problem in MBQC](PUBO_EN.ipynb)\n",
"\n",
"Our MBQC module provides all the essential building blocks for the implementation of a general MBQC algorithm. It can do much beyond than what we have listed above. We sincerely encourage you to explore more potential applications with MBQC and our module! If you are interested in a more detailed study of MBQC itself, please refer to [15,16]."
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"---\n",
"\n",
"## References\n",
"\n",
"[1] Deutsch, David Elieser. \"Quantum computational networks.\" [Proceedings of the Royal Society of London. A. 425.1868 (1989): 73-90.](https://royalsocietypublishing.org/doi/abs/10.1098/rspa.1989.0099)\n",
"\n",
"[2] Barenco, Adriano, et al. \"Elementary gates for quantum computation.\" [Physical review A 52.5 (1995): 3457.](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.52.3457)\n",
"\n",
"[3] Gottesman, Daniel, and Isaac L. Chuang. \"Demonstrating the viability of universal quantum computation using teleportation and single-qubit operations.\" [Nature 402.6760 (1999): 390-393.](https://www.nature.com/articles/46503?__hstc=13887208.d9c6f9c40e1956d463f0af8da73a29a7.1475020800048.1475020800050.1475020800051.2&__hssc=13887208.1.1475020800051&__hsfp=1773666937)\n",
"\n",
"[4] Nielsen, Michael A. \"Quantum computation by measurement and quantum memory.\" [Physics Letters A 308.2-3 (2003): 96-100.](https://www.sciencedirect.com/science/article/abs/pii/S0375960102018030)\n",
"\n",
"[5] Leung, Debbie W. \"Quantum computation by measurements.\" [International Journal of Quantum Information 2.01 (2004): 33-43.](https://www.worldscientific.com/doi/abs/10.1142/S0219749904000055)\n",
"\n",
"[6] Robert Raussendorf, et al. \"A one-way quantum computer.\" [Physical Review Letters 86.22 (2001): 5188.](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.86.5188)\n",
"\n",
"[7] Raussendorf, Robert, and Hans J. Briegel. \"Computational model underlying the one-way quantum computer.\" [Quantum Information & Computation 2.6 (2002): 443-486.](https://dl.acm.org/doi/abs/10.5555/2011492.2011495)\n",
"\n",
"[8] Robert Raussendorf, et al. \"Measurement-based quantum computation on cluster states.\" [Physical Review A 68.2 (2003): 022312.](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.68.022312)\n",
"\n",
"[9] Briegel, Hans J., et al. \"Measurement-based quantum computation.\" [Nature Physics 5.1 (2009): 19-26.](https://www.nature.com/articles/nphys1157)\n",
"\n",
"[10] Aliferis, Panos, and Debbie W. Leung. \"Computation by measurements: a unifying picture.\" [Physical Review A 70.6 (2004): 062314.](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.70.062314)\n",
"\n",
"[11] Broadbent, Anne, et al. \"Universal blind quantum computation.\" [2009 50th Annual IEEE Symposium on Foundations of Computer Science. IEEE, 2009.](https://arxiv.org/abs/0807.4154)\n",
"\n",
"[12] Morimae, Tomoyuki. \"Verification for measurement-only blind quantum computing.\" [Physical Review A 89.6 (2014): 060302.](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.89.060302)\n",
"\n",
"[13] Larsen, Mikkel V., et al. \"Deterministic generation of a two-dimensional cluster state.\" [Science 366.6463 (2019): 369-372.](https://science.sciencemag.org/content/366/6463/369)\n",
"\n",
"[14] Asavanant, Warit, et al. \"Generation of time-domain-multiplexed two-dimensional cluster state.\" [Science 366.6463 (2019): 373-376.](https://science.sciencemag.org/content/366/6463/373)\n",
"\n",
"[15] Richard Jozsa, et al. \"An introduction to measurement based quantum computation.\" [arXiv:quant-ph/0508124](https://arxiv.org/abs/quant-ph/0508124v2)\n",
"\n",
"[16] Nielsen, Michael A. \"Cluster-state quantum computation.\" [Reports on Mathematical Physics 57.1 (2006): 147-161.](https://www.sciencedirect.com/science/article/abs/pii/S0034487706800145)\n",
"\n",
"[17] Nielsen, Michael A., and Isaac Chuang. \"Quantum computation and quantum information.\"[Cambridge university press (2010).](https://www.cambridge.org/core/books/quantum-computation-and-quantum-information/01E10196D0A682A6AEFFEA52D53BE9AE)\n",
"\n",
"[18] Danos, Vincent, et al. \"The measurement calculus.\" [Journal of the ACM (JACM) 54.2 (2007): 8-es.](https://dl.acm.org/doi/abs/10.1145/1219092.1219096)"
],
"metadata": {}
}
],
"metadata": {
"interpreter": {
"hash": "07efdcd4b820c98a756949507a4d29d7862823915ec7477944641bea022f4f62"
},
"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
},
"toc-autonumbering": false,
"toc-showcode": false,
"toc-showmarkdowntxt": false,
"toc-showtags": false,
"varInspector": {
"cols": {
"lenName": 16,
"lenType": 16,
"lenVar": 40
},
"kernels_config": {
"python": {
"delete_cmd_postfix": "",
"delete_cmd_prefix": "del ",
"library": "var_list.py",
"varRefreshCmd": "print(var_dic_list())"
},
"r": {
"delete_cmd_postfix": ") ",
"delete_cmd_prefix": "rm(",
"library": "var_list.r",
"varRefreshCmd": "cat(var_dic_list()) "
}
},
"types_to_exclude": [
"module",
"function",
"builtin_function_or_method",
"instance",
"_Feature"
],
"window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}
\ No newline at end of file
{
"cells": [
{
"cell_type": "markdown",
"source": [
"# MBQC 模型下求解多项式组合优化问题\n",
"\n",
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"在[基于测量的量子近似优化算法](QAOA_CN.ipynb)中,我们介绍了多项式无约束布尔优化问题(polynomial unconstrained boolean optimization problem, PUBO),并提出了**基于测量的量子近似优化算法(MB-QAOA)** 对该类问题的求解方式,感兴趣的读者可以参阅前面的内容。这里我们给出两个示例作为 MB-QAOA 的实战演示:一个具体的 PUBO 问题和一个最大割问题。\n",
"\n",
"## 示例:PUBO 问题\n",
"\n",
"我们首先简单回顾一下什么是 PUBO 问题。给定一个变量为 $x = \\{x_1,\\cdots,x_n\\}$ 的 $n$ 元多项式,\n",
"\n",
"$$\n",
"C(x) = \\sum_{\\lambda \\in \\Lambda } \\alpha_{\\lambda} \\prod_{j \\in \\lambda} x_j,\\tag{1}\n",
"$$\n",
"\n",
"其中每个变量 $x_i \\in \\{0,1\\}$,$\\prod_{j \\in \\lambda} x_j$ 为一个单项式,$\\lambda \\subseteq [n]:= \\{1, 2, ..., n\\}$ 为一个指标集,$\\Lambda$ 为指标集的集合,$\\alpha_\\lambda$ 为每个单项式对应的实系数。在 PUBO 问题中,$C(x)$ 称为目标多项式。PUBO 问题就是寻找一组最优解 $x^* = \\{x_1^*, x_2^*, ..., x_n^*\\} $ 使得目标多项式的取值最大,即\n",
"\n",
"$$\n",
"x^* =\\underset{x}{\\text{argmax}} \\ C(x).\\tag{2}\n",
"$$\n",
"\n",
"代码实现上,我们规定输入多项式的标准形式为一个列表,列表中的第一个元素为多项式的最大变量数,列表的第二个元素为一个字典,字典的键为每个单项式涉及的变量(常数则为 ‘cons’),变量之间用逗号隔开,值为该单项式的系数。比如,我们希望输入一个三元多项式 $x_1 + x_2 - x_3 + x_1 x_2 - x_1 x_2 x_3 + 0.5$,应输入如下代码:"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# 变量数\n",
"var_num = 3\n",
"# 以字典格式存储多项式的各个单项式\n",
"poly_dict = {'x_1': 1, 'x_2': 1, 'x_3': -1, 'x_1,x_2': 1, 'x_1,x_2,x_3': -1, 'cons':0.5}\n",
"# 构造符合约定的多项式\n",
"polynomial = [var_num, poly_dict]"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"**注意:** 由于我们考虑的变量为布尔值,所以每个单项式中变量指数最大为 1,即输入的多项式字典的键中,每个变量最多出现一次。例如:{'x_1,x_1,x_2': 1} 为不符合规范的输入。我们同样约定自变量的下角标从 \"1\" 开始,与数学习惯相一致。并且为了统一输入规范,自变量需要连续编号,不能出现输入多项式为 $x_1x_2 + x_6$ 的情况,应该改为 $x_1x_2 + x_3$,否则我们会以报错的形式提醒输入不符合规范。\n",
"\n",
"代码实现上,我们在 `pubo` 中定义了 `is_poly_valid` 函数,用于检查输入多项式的合法性。如果合法,将会打印 \"The polynomial is valid.\" 字样,否则会报错。\n",
"\n",
"```python\n",
"from paddle_quantum.mbqc.QAOA.pubo import is_poly_valid\n",
"```\n",
"我们同样定义了一个函数 `random_poly` 根据变量个数随机生成一个布尔型多项式。\n",
"```python\n",
"from paddle_quantum.mbqc.QAOA.pubo import random_poly\n",
"```\n",
"**注意:** 随机生成的多项式并不一定是合法的!因此我们需要在计算之前检查多项式的合法性。"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### 求解 PUBO 问题的代码实现"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# 引入记时模块\n",
"from time import perf_counter\n",
"# 引入符号计算模块\n",
"from sympy import symbols\n",
"#引入 paddle 相关模块\n",
"from paddle import seed, optimizer\n",
"# 引入 pubo 模块\n",
"from paddle_quantum.mbqc.QAOA.pubo import dict_to_symbol, is_poly_valid, brute_force_search\n",
"# 引入 qaoa 模块 \n",
"from paddle_quantum.mbqc.QAOA.qaoa import MBQC_QAOA_Net, get_solution_string"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"利用 MB-QAOA 算法,我们可以定义 PUBO 主函数 ``mbqc_pubo``,输入目标函数和 QAOA 算法深度,最终返回最优解对应的比特串。在主函数中,**最核心的是 MBQC_QAOA_Net 类**,其集成了 MB-QAOA 以及优化网络,感兴趣的读者可以返回到 [基于测量的量子近似优化算法](QAOA_CN.ipynb) 中查看,这里我们直接调用。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# 定义 MBQC 模型求解 PUBO 问题的主函数\n",
"def mbqc_pubo(OBJ_POLY, DEPTH, SEED, LR, ITR, EPOCH, SHOTS=1024):\n",
" \n",
" # 将字典类型的目标多项式转化为 sympy 能处理的符号多项式\n",
" obj_poly = dict_to_symbol(OBJ_POLY)\n",
" var_num, poly_symbol = obj_poly\n",
"\n",
" # 打印当前 QAOA 算法电路深度\n",
" print(\"QAOA 算法深度为:\", DEPTH)\n",
"\n",
" # 开始计时器\n",
" start_time = perf_counter()\n",
" \n",
" # 初始化一个 MB-QAOA 训练网络\n",
" seed(SEED)\n",
" mbqc_net = MBQC_QAOA_Net(DEPTH)\n",
" \n",
" # 选择 Adams 优化器\n",
" opt = optimizer.Adam(learning_rate=LR, parameters=mbqc_net.parameters())\n",
"\n",
" # 开始训练\n",
" for epoch in range(EPOCH):\n",
" # 每次迭代后更新训练参数\n",
" for itr in range(1, ITR + 1):\n",
" # 用网络进行训练并返回损失函数值和当前输出量子态\n",
" loss, vector_out = mbqc_net(poly=obj_poly)\n",
" # 根据损失函数值优化参数\n",
" loss.backward()\n",
" opt.minimize(loss)\n",
" opt.clear_grad()\n",
" if itr % 10 == 0:\n",
" print(\"iter:\", itr, \" loss_MBQC:\", \"%.4f\" % loss.numpy())\n",
" \n",
" # 停止计时器并打印训练用时\n",
" end_time = perf_counter()\n",
" print(\"MBQC 运行时间为: \", end_time - start_time)\n",
"\n",
" # 打印优化后的训练参数 gamma, beta\n",
" print(\"最优参数 gamma 为: \", mbqc_net.gamma.numpy())\n",
" print(\"最优参数 beta 为: \", mbqc_net.beta.numpy())\n",
"\n",
" # 解码原问题的答案\n",
" solution_str = get_solution_string(vector_out, SHOTS)\n",
"\n",
" # 将最优解带回目标函数,求出对应的最优值\n",
" relation = {symbols('x_' + str(j + 1)): int(solution_str[j]) for j in range(var_num)}\n",
" value = poly_symbol.evalf(subs=relation)\n",
" \n",
" # 返回最优解和最优值\n",
" opt = [solution_str, value]\n",
" return opt"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"为了检验训练结果的正确性,我们在 `pubo` 中定义了 `brute_force_search` 函数,用遍历的方法在解空间里暴力搜索 PUBO 问题的解,方便与 MBQC 模型训练得到的解进行对比。\n",
"\n",
"```python\n",
"from paddle_quantum.mbqc.QAOA.pubo import brute_force_search\n",
"```"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Main 函数\n",
"\n",
"主函数定义后,我们就可以输入参数运行啦!"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# 定义主函数\n",
"def main():\n",
" \n",
" # 以上面的例子 x_1 + x_2 - x_3 + x_1*x_2 -x_1*x_2*x_3 + 0.5 为例,求解 PUBO 问题\n",
" var_num = 3 \n",
" poly_dict = {'x_1': 1, 'x_2': 1, 'x_3': -1, 'x_1,x_2': 1, 'x_1,x_2,x_3': -1, 'cons':0.5}\n",
" polynomial = [var_num, poly_dict]\n",
" \n",
" # 打印输入的多项式\n",
" print(\"输入的多项式为:\", polynomial)\n",
" \n",
" # 我们也可以随机生成一个目标多项式\n",
" # polynomial = random_poly(var_num)\n",
"\n",
" # 检查输入多项式的合法性\n",
" is_poly_valid(polynomial)\n",
"\n",
" # 进行训练并返回最优结果\n",
" mbqc_result = mbqc_pubo(\n",
" OBJ_POLY=polynomial, # 目标多项式函数\n",
" DEPTH=6, # QAOA 算法电路深度\n",
" SEED=1024, # 随机种子\n",
" LR=0.1, # 学习率\n",
" ITR=120, # 训练迭代次数\n",
" EPOCH=1 # 迭代周期\n",
" )\n",
"\n",
" # 打印 MBQC 模型训练出来的最优结果\n",
" print(\"MBQC 模型得到的最优解为:\", mbqc_result[0])\n",
" print(\"MBQC 模型得到的最优值为:\", mbqc_result[1])\n",
" \n",
" # 通过暴力搜索计算 PUBO 问题的最优结果并打印\n",
" brute_result = brute_force_search(polynomial)\n",
" print(\"暴力搜索得到的最优解为:\", brute_result[0])\n",
" print(\"暴力搜索得到的最优值为:\", brute_result[1])\n",
" \n",
" # 将两种结果进行比较\n",
" print(\"MBQC 模型和暴力搜索的差值为:\", mbqc_result[1] - brute_result[1])\n",
"\n",
"\n",
"if __name__ == '__main__':\n",
" main()"
],
"outputs": [],
"metadata": {
"tags": []
}
},
{
"cell_type": "markdown",
"source": [
"## 示例:最大割问题\n",
"\n",
"### 图与割\n",
"\n",
"最大割问题(MaxCut Problem)是图论中常见的一个组合优化问题,在统计物理学和电路设计中都有重要应用。\n",
"\n",
"在图论中,一个图由 $G = (V, E)$ 表示,其中 $V$ 为图的点集合,$E$ 为边集合。例如一个正方形就可以由 $G = (V,E)$ 其中 $V = [1,2,3,4]$, $E = [(1,2),(2,3),(3,4),(1,4)]$ 来表示。\n",
"\n",
"代码上,我们可以用 `maxcut` 中的 `plot_graph` 画出给定的图。"
],
"metadata": {
"tags": []
}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"from paddle_quantum.mbqc.QAOA.maxcut import plot_graph\n",
"V = [1,2,3,4]\n",
"E = [(1,2),(2,3),(3,4),(1,4)]\n",
"G = [V, E] \n",
"plot_graph(G,\"A square\")"
],
"outputs": [],
"metadata": {
"tags": []
}
},
{
"cell_type": "markdown",
"source": [
"一个图的割是指将该图的顶点集 $V$ 分割成两个互不相交的子集合 $S_0$ 和 $S_1$ 的一种划分。如果图中边的两个顶点被划分到不同集合中,那么记录得一分,总得分则定义为这个割的大小。最大割问题就是要找到一个割使得该割的大小最大。\n",
"\n",
"比如对应如上所述正方形图 $G$, 其中一个最大割就是将点 $1$ 和 $3$ 分为同一个集合 $S_0$ 中,点 $2$ 和 $4$ 分到另一个集合 $S_1$ 中。 "
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### 转化为 PUBO 问题\n",
"\n",
"最大割问题其实可以转化为一个 PUBO 问题。假设待分割的图 $G = (V, E)$ 有 $n=|V|$ 个顶点和 $m =|E|$ 条边,那么我们可以将最大割问题等价为拥有 $n$ 个自变量的组合优化问题。每个变量 $x_v$ 对应图 $G$ 中的一个顶点 $v \\in V$,其取值 $x_v \\in \\{0,1\\}$,分别对应于顶点属于集合 $S_0$ 或 $S_1$,因此 $x = \\{x_1,\\cdots,x_n\\}$ 的每一种取值方式都对应一个割。由于最大割问题中有效计数的边为那些顶点属于不同集合的边,即顶点 $u$ 和 $v$ 对应的变量取值不同 $x_u \\neq x_v$ 才会被记一分。所以对于给定的割 $x$,其对应的割的大小为\n",
"\n",
"$$\n",
"C(x) = \\sum_{(u,v) \\in E} (x_u \\oplus x_v),\\tag{3}\n",
"$$\n",
"\n",
"其中 $\\oplus$ 为异或运算。那么最大割问题等价于求解 $ \\underset{x}{\\max} \\ C(x)$。进一步,上述函数 $C(x)$ 可以写成多项式形式\n",
"\n",
"$$\n",
"C(x) = \\sum_{(u, v) \\in E} (x_u + x_v - 2 x_u x_v),\\tag{4}\n",
"$$\n",
"\n",
"从而最大割问题等价于一个 $n$ 元 $2$ 次多项式的 PUBO 问题。我们希望找到最优解 $x^{*}$ 使得目标多项式最大,\n",
"\n",
"$$\n",
"x^* = \\underset{x}{\\text{argmax}} \\ \\left( \\sum_{(u, v) \\in E} (x_u + x_v - 2 x_u x_v) \\right).\\tag{5}\n",
"$$\n",
"\n",
"在 `maxcut` 当中,我们定义了 `graph_to_poly` 函数,可以输入待分割的图,返回等价的 PUBO 问题的目标多项式。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# 引入 maxcut 模块\n",
"from paddle_quantum.mbqc.QAOA.maxcut import graph_to_poly\n",
"\n",
"# 输入点和边的信息\n",
"V = [1,2,3,4]\n",
"E = [(1,2),(2,3),(3,4),(1,4)]\n",
"# 构造待分割的图\n",
"G = [V, E] \n",
"\n",
"# 将图转化为对应 PUBO 问题的目标多项式\n",
"poly = graph_to_poly(G)\n",
"print(\"与图等价的目标多项式为:\\n\", poly)"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### 求解最大割问题的代码实现\n",
"\n",
"得到等价的目标多项式之后,我们便可以仿照 PUBO 的流程,把最大割问题作为 PUBO 问题来求解。具体代码如下:"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# 引入符号运算模块\n",
"from sympy import symbols\n",
"# 引入 paddle 模块\n",
"from paddle import seed, optimizer\n",
"# 引入 qaoa 模块\n",
"from paddle_quantum.mbqc.QAOA.qaoa import MBQC_QAOA_Net, get_solution_string\n",
"# 引入 maxcut 模块\n",
"from paddle_quantum.mbqc.QAOA. maxcut import plot_graph, graph_to_poly, plot_solution"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"利用 MB-QAOA 算法,我们可以定义 MaxCut 主函数,将图输入到 MB-QAOA 算法中,最终返回求得的解并画出割的方式。"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# 定义 MaxCut 主函数\n",
"def mbqc_maxcut(GRAPH, DEPTH, SEED, LR, ITR, EPOCH, SHOTS=1024):\n",
" \n",
" # 打印待分割的图\n",
" plot_graph(graph=GRAPH, title=\"Graph to be cut\")\n",
"\n",
" # 找到图对应的目标多项式\n",
" polynomial = graph_to_poly(GRAPH)\n",
" print(\"与图等价的目标多项式为:\", polynomial[1])\n",
"\n",
" # 开始算法的记时\n",
" start_time = perf_counter()\n",
"\n",
" # 实例化一个 MB-QAOA 训练网络\n",
" seed(SEED)\n",
" mbqc_net = MBQC_QAOA_Net(DEPTH)\n",
" # 选择 Adams 优化器\n",
" opt = optimizer.Adam(learning_rate=LR, parameters=mbqc_net.parameters())\n",
"\n",
" # 开始训练\n",
" for epoch in range(EPOCH):\n",
" # 每次迭代后更新训练参数\n",
" for itr in range(1, ITR + 1):\n",
" # 训练参数并返回损失函数值和当前输出量子态\n",
" loss, state_out = mbqc_net(poly=polynomial)\n",
" # 根据损失函数值优化训练参数\n",
" loss.backward()\n",
" opt.minimize(loss)\n",
" opt.clear_grad()\n",
" if itr % 10 == 0:\n",
" print(\"iter:\", itr, \" loss_MBQC:\", \"%.4f\" % loss.numpy())\n",
"\n",
" # 停止算法的记时并打印用时\n",
" end_time = perf_counter()\n",
" print(\"MBQC 运行时间为:\", end_time - start_time)\n",
" \n",
" # 打印训练得到的最优参数\n",
" print(\"最优参数 gamma 为: \", mbqc_net.gamma.numpy())\n",
" print(\"最优参数 beta 为:\", mbqc_net.beta.numpy())\n",
"\n",
" # 从量子态中解码问题的答案\n",
" mbqc_solution = get_solution_string(state_out, SHOTS)\n",
" # 绘制割的方式\n",
" plot_solution(GRAPH, mbqc_solution)\n",
"\n",
" # 将最优解带入目标函数,求得对应最优值,并返回\n",
" var_num, poly_symbol = polynomial\n",
" relation = {symbols('x_' + str(j + 1)): int(mbqc_solution[j]) for j in range(var_num)}\n",
" \n",
" mbqc_value = int(poly_symbol.evalf(subs=relation))\n",
" mbqc_opt = [mbqc_solution, mbqc_value]\n",
"\n",
" # 返回最优解和对应的最优值\n",
" return mbqc_opt"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Main 函数\n",
"\n",
"主函数定义后,我们就可以输入参数运行啦!"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"def main():\n",
" # 以正方形的 MaxCut 问题为例,输入节点和边信息\n",
" V = [1, 2, 3, 4]\n",
" E = [(1, 2), (2, 3), (3, 4), (4, 1)]\n",
" G = [V, E]\n",
" \n",
" # 进行训练并返回最优结果\n",
" mbqc_result = mbqc_maxcut(\n",
" GRAPH=G, # 待分割的图\n",
" DEPTH=6, # QAOA 算法电路深度\n",
" SEED=1024, # 随机种子\n",
" LR=0.1, # 学习率\n",
" ITR=120, # 训练迭代次数\n",
" EPOCH=1, # 迭代周期\n",
" SHOTS=1024 # 解码答案时的采样数\n",
" )\n",
"\n",
" # 打印训练的最优结果\n",
" print(\"最优解为:\", mbqc_result[0])\n",
" print(\"最优值为: \", mbqc_result[1])\n",
"\n",
"if __name__ == '__main__':\n",
" main()"
],
"outputs": [],
"metadata": {
"tags": []
}
},
{
"cell_type": "markdown",
"source": [
"至此,我们完成了本教程的两个完整示例。MB-QAOA 算法实现预示着 MBQC 模型在量子机器学习领域强大的潜力。很显然,MBQC 模型所能够处理的算法不仅有 QAOA,我们非常期待 MBQC 这种非同寻常的计算方式能在某些特殊情境下展现出无与伦比的优势!\n",
"\n",
"接下来的一个教程将会给出我们在 MBQC 模型优势上的一个探索,一起去学习吧!"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"---\n",
"\n",
"## 参考文献\n",
"\n",
"[1] Farhi, Edward, et al. \"A quantum approximate optimization algorithm.\" [arXiv preprint arXiv:1411.4028 (2014).](https://arxiv.org/abs/1411.4028)"
],
"metadata": {
"tags": []
}
}
],
"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"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": false,
"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
},
"varInspector": {
"cols": {
"lenName": 16,
"lenType": 16,
"lenVar": 40
},
"kernels_config": {
"python": {
"delete_cmd_postfix": "",
"delete_cmd_prefix": "del ",
"library": "var_list.py",
"varRefreshCmd": "print(var_dic_list())"
},
"r": {
"delete_cmd_postfix": ") ",
"delete_cmd_prefix": "rm(",
"library": "var_list.r",
"varRefreshCmd": "cat(var_dic_list()) "
}
},
"types_to_exclude": [
"module",
"function",
"builtin_function_or_method",
"instance",
"_Feature"
],
"window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}
\ No newline at end of file
{
"cells": [
{
"cell_type": "markdown",
"source": [
"# Polynomial Unconstrained Boolean Optimization Problem in MBQC\n",
"\n",
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"In the tutorial [Measurement-based Quantum Approximate Optimization Algorithm](QAOA_EN.ipynb), we give a brief introduction to the **polynomial unconstrained boolean optimization (PUBO) problem** and propose the **measurement-based quantum approximate optimization algorithm (MB-QAOA)** to solve it. For interested readers, please refer to the previous tutorial for more information. In this tutorial, we will showcase two specific examples as practical demonstrations of MB-QAOA. The first one is a concrete PUBO problem, while the second one is a **maximum cut (MaxCut)** problem.\n",
"\n",
"## Example: PUBO Problem\n",
"\n",
"Let us first briefly review what a PUBO problem is. Consider a polynomial of $n$ variables $x = \\{x_1,\\cdots,x_n\\}$, \n",
"\n",
"$$\n",
"C(x) = \\sum_{\\lambda \\in \\Lambda } \\alpha_{\\lambda} \\prod_{j \\in \\lambda} x_j,\\tag{1}\n",
"$$\n",
"\n",
"where $x_i \\in \\{0,1\\}$ is a boolean variable, $\\underset{j \\in \\lambda}{\\prod} x_j$ is a monomial, $\\lambda \\subseteq [n]:= \\{1, 2, ..., n\\}$ is a set of indexes, $\\Lambda$ is the set of index sets, $\\alpha_\\lambda$ is the real coefficient of monomial. In PUBO, $C(x)$ is called the objective polynomial. We hope to find an optimal solution $x^* = \\{x_1^*, x_2^*, ..., x_n^*\\} $ maximizing the value of objective polynomial. That is to find\n",
"\n",
"$$\n",
"x^* = \\underset{x}{\\text{argmax}} \\ C(x).\\tag{2}\n",
"$$\n",
"\n",
"For code implementation, we require that a standard polynomial is input as a list whose first item is the number of variables and the second item is a dictionary of all the monomials ('cons' stands for the constant item). In the dictionary, we make monomial variables split with ',' as keys and the corresponding coefficients as values. For example, suppose we want to input a polynomial $x_1 + x_2 - x_3 + x_1 x_2 - x_1 x_2 x_3 + 0.5$, we need to code as follows: "
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Number of variables\n",
"var_num = 3\n",
"# Polynomial as a dictionary\n",
"poly_dict = {'x_1': 1, 'x_2': 1, 'x_3': -1, 'x_1,x_2': 1, 'x_1,x_2,x_3': -1, 'cons':0.5}\n",
"# Construct the list required\n",
"polynomial = [var_num, poly_dict]"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"**Note:** As the variables are boolean, the power of variables in a monomial should be no greater than 1. That is, each variable should appear at most once in a key of the dictionary. For instance, it is not a valid to input something like {'x_1,x_1,x_2': 1}. Also, we set variable subscripts by consecutive numbers starting from '1' to be consistent with math conventions. A polynomial like $x_1 x_2 + x_6$ will raise an error automatically. A valid polynomial should be like $x_1x_2 + x_3$. \n",
"\n",
"For convenience, we provide a function `is_poly_valid` in `pubo` to check the validity of the user's input. If the polynomial is valid, it will print a statement \"The polynomial is valid.\". Otherwise, an error will be raised.\n",
"\n",
"```python\n",
"from paddle_quantum.mbqc.QAOA.pubo import is_poly_valid\n",
"```\n",
"We also provide a function `random_poly` to generate a random boolean polynomial with a given number of variables.\n",
"\n",
"```python\n",
"from paddle_quantum.mbqc.QAOA.pubo import random_poly\n",
"```\n",
"**Note:** The randomly generated polynomial is not always valid and we still need to check the validity before calculation."
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Code implementation to slove PUBO problem"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Import time module\n",
"from time import perf_counter\n",
"# Import sympy module for syntax calculation\n",
"from sympy import symbols\n",
"# Import paddle module\n",
"from paddle import seed, optimizer\n",
"# Import pubo module\n",
"from paddle_quantum.mbqc.QAOA.pubo import dict_to_symbol,is_poly_valid,brute_force_search\n",
"# Import qaoa module\n",
"from paddle_quantum.mbqc.QAOA.qaoa import MBQC_QAOA_Net, get_solution_string"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"We define a function ``mbqc_pubo`` which takes in the objective polynomial and returns an optimal solution. **The core part of ``mbqc_pubo`` is the ``MBQC_QAOA_Net`` class**, which integrates MB-QAOA and the optimization net. Please refer to [Measurement-Based Quantum Approximate Optimization Algorithm](QAOA_EN.ipynb) for more details. Here we directly call the function."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Define the PUBO main function\n",
"def mbqc_pubo(OBJ_POLY, DEPTH, SEED, LR, ITR, EPOCH, SHOTS=1024):\n",
" \n",
" # Symbolize the polynomial\n",
" obj_poly = dict_to_symbol(OBJ_POLY)\n",
" var_num, poly_symbol = obj_poly\n",
"\n",
" # Print the QAOA depth\n",
" print(\"QAOA depth is:\", DEPTH)\n",
"\n",
" # Start timing\n",
" start_time = perf_counter()\n",
"\n",
" # Instaniate a MB-QAOA traning net\n",
" seed(SEED)\n",
" mbqc_net = MBQC_QAOA_Net(DEPTH)\n",
" \n",
" # Choose Adams optimizer (or SGD optimizer)\n",
" opt = optimizer.Adam(learning_rate=LR, parameters=mbqc_net.parameters())\n",
"\n",
" # Start training\n",
" for epoch in range(EPOCH):\n",
" # Update parameters for each iter\n",
" for itr in range(1, ITR + 1):\n",
" # Train with mbqc_net and return the loss\n",
" loss, state_out = mbqc_net(poly=obj_poly)\n",
" # Propagate loss backwards and optimize the parameters\n",
" loss.backward()\n",
" opt.minimize(loss)\n",
" opt.clear_grad()\n",
" if itr % 10 == 0:\n",
" print(\"iter:\", itr, \" loss_MBQC:\", \"%.4f\" % loss.numpy())\n",
" \n",
" # Stop timing and print the running time\n",
" end_time = perf_counter()\n",
" print(\"MBQC running time is: \", end_time - start_time)\n",
"\n",
" # Print the optimization parameters\n",
" print(\"Optimal parameter gamma: \", mbqc_net.gamma.numpy())\n",
" print(\"Optimal parameter beta: \", mbqc_net.beta.numpy())\n",
"\n",
" # Decode the solution from the quantum state\n",
" solution_str = get_solution_string(state_out, SHOTS)\n",
"\n",
" # Evaluate the corresponding value\n",
" relation = {symbols('x_' + str(j + 1)): int(solution_str[j]) for j in range(var_num)}\n",
" value = poly_symbol.evalf(subs=relation)\n",
" \n",
" # Return the solution and its corresponding value\n",
" opt = [solution_str, value]\n",
"\n",
" return opt"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"To check the correctness of the training result, we provide a `brute_force_search` function in `pubo` that finds a global optimal value by brute force search. We can compare the training result with the optimal one.\n",
"\n",
"```python\n",
"from paddle_quantum.mbqc.QAOA.pubo import brute_force_search\n",
"```"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Main function\n",
"\n",
"After defining the main function, let's input the parameters to run the code!"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Define the main function\n",
"def main():\n",
" \n",
" # Choose the example x_1 + x_2 - x_3 + x_1*x_2 -x_1*x_2*x_3 + 0.5\n",
" var_num = 3 \n",
" poly_dict = {'x_1': 1, 'x_2': 1, 'x_3': -1, 'x_1,x_2': 1, 'x_1,x_2,x_3': -1, 'cons':0.5}\n",
" polynomial = [var_num, poly_dict]\n",
" \n",
" # Print the input polynomial\n",
" print(\"The input polynomial is: \", polynomial)\n",
" \n",
" # We can also randomly generate an objective function\n",
" # polynomial = random_poly(var_num)\n",
"\n",
" # Check the validity of the input polynomial\n",
" is_poly_valid(polynomial)\n",
"\n",
" # Starting training and obtain the result\n",
" mbqc_result = mbqc_pubo(\n",
" OBJ_POLY=polynomial, # Objective Function\n",
" DEPTH=6, # QAOA Depth\n",
" SEED=1024, # Plant Seed\n",
" LR=0.1, # Learning Rate\n",
" ITR=120, # Training Iters\n",
" EPOCH=1 # Epoch Times\n",
" )\n",
"\n",
" # Print the result from MBQC model\n",
" print(\"Optimal solution by MBQC: \", mbqc_result[0])\n",
" print(\"Optimal value by MBQC: \", mbqc_result[1])\n",
" \n",
" # Compute the optimal result by brute-force search and print the result\n",
" brute_result = brute_force_search(polynomial)\n",
" print(\"Optimal solution by brute force search: \", brute_result[0])\n",
" print(\"Optimal value by brute force search: \", brute_result[1])\n",
" \n",
" # Compare the training result with the optimal one\n",
" print(\"Difference between optimal values from MBQC and brute force search: \", mbqc_result[1] - brute_result[1])\n",
"\n",
"\n",
"if __name__ == '__main__':\n",
" main()"
],
"outputs": [],
"metadata": {
"tags": []
}
},
{
"cell_type": "markdown",
"source": [
"## Example: MaxCut\n",
"\n",
"### Graph and cut\n",
"\n",
"Maximum cut problem(MaxCut Problem)is a combinatorial optimization problem in graph theory, with plenty of applications in e.g. statistic physics and circuit design.\n",
"\n",
"In graph theory, a graph is represented by $G = (V, E)$, where $V$ is a set of vertices and $E$ is a set of edges. For example, a square can be characterized by the graph $G = (V,E)$ with $V = [1,2,3,4]$ and $E = [(1,2),(2,3),(3,4),(1,4)]$.\n",
"\n",
"For code implementation, we can use the `plot_graph` function in `maxcut` to plot a graph."
],
"metadata": {
"tags": []
}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"from paddle_quantum.mbqc.QAOA.maxcut import plot_graph\n",
"V = [1,2,3,4]\n",
"E = [(1,2),(2,3),(3,4),(1,4)]\n",
"G = [V, E] \n",
"plot_graph(G,\"A square\")"
],
"outputs": [],
"metadata": {
"tags": []
}
},
{
"cell_type": "markdown",
"source": [
"A cut in the graph is a partition separating the vertices set $V$ into two complementary subsets $S_0$ and $S_1$. If two vertices of an edge in the graph are separated into different subsets, we score a goal. The size of a cut is defined by the total scores that we get. Then the MaxCut problem is to find a cut of graph with maximal size. \n",
"\n",
"As for the above square $G$, one of the optimal solutions to the MaxCut problem is to put $1$ and $3$ into subset $S_0$ and put $2$ and $4$ into subset $S_1$. "
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Transformation to a PUBO problem\n",
"\n",
"A MaxCut problem can be transformed into a PUBO problem. Assume the graph to be cut $G = (V, E)$ has $n=|V|$ vertices and $m =|E|$ edges, we can transform the MaxCut problem into a PUBO problem of $n$ variables. Each variable $x_v$ corresponds to a vertex $v \\in V$ in the graph $G$, with its domain $x_v \\in \\{0,1\\}$ corresponding to its belonging to subset $S_0$ or subset $S_1$. So, each value of the string $x = \\{x_1,\\cdots,x_n\\}$ corresponds to a cut. As a valid edge to score a goal is the one whose vertices $u$ and $v$ belong to different subsets, given a cut $x$, its size can be defined as: \n",
"\n",
"$$\n",
"C(x) = \\sum_{(u,v) \\in E} (x_u \\oplus x_v),\\tag{3}\n",
"$$\n",
"\n",
"where $\\oplus$ represents XOR operation. Then the MaxCut problem is equivalent to solve the optimization $\\underset{x}{\\max} \\ C(x)$. Since $C(x)$ can be written as a polynomial: \n",
"\n",
"$$\n",
"C(x) = \\sum_{(u, v) \\in E} (x_u + x_v - 2 x_u x_v).\\tag{4}\n",
"$$\n",
"\n",
"this optimization is essentially a quadratic PUBO problem of $n$ variables. We hope to find an optimal solution $x^{*}$ maximizing the value of objective polynomial, that is,\n",
"\n",
"$$\n",
"x^* = \\underset{x}{\\text{argmax}} \\left( \\sum_{(u, v) \\in E} (x_u + x_v - 2 x_u x_v) \\right).\\tag{5}\n",
"$$\n",
"\n",
"We provide a function `graph_to_poly` in `maxcut` which takes in the graph to be cut and returns the equivalent objective polynomial in PUBO."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Import maxcut module\n",
"from paddle_quantum.mbqc.QAOA.maxcut import graph_to_poly\n",
"\n",
"# Input the vertices and edges\n",
"V = [1,2,3,4]\n",
"E = [(1,2),(2,3),(3,4),(1,4)]\n",
"# Construct the graph to be cut\n",
"G = [V, E] \n",
"\n",
"# Transform the graph to the equivalent polynomial\n",
"poly = graph_to_poly(G)\n",
"print(\"The equivalent objective polynomial is:\\n\", poly)"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Code implementation to solve MaxCut problem\n",
"\n",
"Once obtaining the objective polynomial, we can follow the same process as the previous example and solve the MaxCut problem as a special case of PUBO. The complete code implementation is as follows:"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Import symbol calculaion module\n",
"from sympy import symbols\n",
"# Import paddle module\n",
"from paddle import seed, optimizer\n",
"# Import qaoa module\n",
"from paddle_quantum.mbqc.QAOA.qaoa import MBQC_QAOA_Net, get_solution_string\n",
"# Import maxcut module\n",
"from paddle_quantum.mbqc.QAOA.maxcut import plot_graph, graph_to_poly, plot_solution"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"We define the main function for MaxCut that takes in the graph to be cut and returns the optimal training results."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"# Define the MaxCut main function\n",
"def mbqc_maxcut(GRAPH, DEPTH, SEED, LR, ITR, EPOCH, SHOTS=1024):\n",
" \n",
" # Plot the graph to be cut\n",
" plot_graph(graph=GRAPH, title=\"Graph to be cut\")\n",
"\n",
" # Obtain the objective polynomial\n",
" polynomial = graph_to_poly(GRAPH)\n",
" print(\"Corresponding objective polynomial of the graph is:\", polynomial[1])\n",
"\n",
" # Start timing\n",
" start_time = perf_counter()\n",
" \n",
" # Instantiate a MB-QAOA training net\n",
" seed(SEED)\n",
" mbqc_net = MBQC_QAOA_Net(DEPTH)\n",
" \n",
" # Choose Adams optimizer (or SGD optimizer)\n",
" opt = optimizer.Adam(learning_rate=LR, parameters=mbqc_net.parameters())\n",
"\n",
" # Start training\n",
" for epoch in range(EPOCH):\n",
" # Update parameters for each iter\n",
" for itr in range(1, ITR + 1):\n",
" # Train with mbqc_net and return the loss\n",
" loss, state_out = mbqc_net(poly=polynomial)\n",
" # Propagate loss backwards and optimize the parameters\n",
" loss.backward()\n",
" opt.minimize(loss)\n",
" opt.clear_grad()\n",
" if itr % 10 == 0:\n",
" print(\"iter:\", itr, \" loss_MBQC:\", \"%.4f\" % loss.numpy())\n",
"\n",
" # Stop timing and print the running time\n",
" end_time = perf_counter()\n",
" print(\"MBQC running time: \", end_time - start_time)\n",
" \n",
" # Print the optimized parameters\n",
" print(\"Optimal parameter gamma: \", mbqc_net.gamma.numpy())\n",
" print(\"Optimal parameter beta: \", mbqc_net.beta.numpy())\n",
"\n",
" # Decode the MaxCut solution from the final state\n",
" mbqc_solution = get_solution_string(state_out, SHOTS)\n",
" # Plot the MaxCut solution\n",
" plot_solution(GRAPH, mbqc_solution)\n",
"\n",
" # Evaluate the number of cuts\n",
" var_num, poly_symbol = polynomial\n",
" relation = {symbols('x_' + str(j + 1)): int(mbqc_solution[j]) for j in range(var_num)}\n",
" \n",
" mbqc_value = int(poly_symbol.evalf(subs=relation))\n",
" mbqc_opt = [mbqc_solution, mbqc_value]\n",
"\n",
" return mbqc_opt"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"### Main function\n",
"\n",
"After defining the main function, let's input the parameters to run the code!"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"source": [
"def main():\n",
" # A graph to be cut\n",
" V = [1, 2, 3, 4]\n",
" E = [(1, 2), (2, 3), (3, 4), (4, 1)]\n",
" G = [V, E]\n",
" \n",
" # MaxCut under MBQC\n",
" mbqc_result = mbqc_maxcut(\n",
" GRAPH=G, # Graph to be cut\n",
" DEPTH=6, # Depth\n",
" SEED=1024, # Plant Seed\n",
" LR=0.1, # Learning Rate\n",
" ITR=120, # Training Iters\n",
" EPOCH=1, # Epoch Times\n",
" SHOTS=1024 # Shots for decoding the solution\n",
" )\n",
"\n",
" # Print the result from MBQC model\n",
" print(\"Optimal solution by MBQC: \", mbqc_result[0])\n",
" print(\"Optimal value by MBQC: \", mbqc_result[1])\n",
"\n",
"if __name__ == '__main__':\n",
" main()"
],
"outputs": [],
"metadata": {
"tags": []
}
},
{
"cell_type": "markdown",
"source": [
"Now, we have completed the demonstration of two examples. The implementation of MB-QAOA indicates a great potential of MBQC in the field of quantum machine learning. Apparently, MBQC model can realize quantities of algorithms far beyond QAOA. We therefore are \n",
"looking forward to exploring more on this and to show some unparalleled advantages in practice. \n",
"\n",
"As a matter of fact, we have found an interesting application of MBQC in simulating a special class of quantum circuits! So, let's continue our exploration to the next tutorial together."
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"---\n",
"\n",
"## References\n",
"\n",
"[1] Farhi, Edward, et al. \"A quantum approximate optimization algorithm.\" [arXiv preprint arXiv:1411.4028 (2014).](https://arxiv.org/abs/1411.4028)"
],
"metadata": {
"tags": []
}
}
],
"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"
},
"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
},
"varInspector": {
"cols": {
"lenName": 16,
"lenType": 16,
"lenVar": 40
},
"kernels_config": {
"python": {
"delete_cmd_postfix": "",
"delete_cmd_prefix": "del ",
"library": "var_list.py",
"varRefreshCmd": "print(var_dic_list())"
},
"r": {
"delete_cmd_postfix": ") ",
"delete_cmd_prefix": "rm(",
"library": "var_list.r",
"varRefreshCmd": "cat(var_dic_list()) "
}
},
"types_to_exclude": [
"module",
"function",
"builtin_function_or_method",
"instance",
"_Feature"
],
"window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}
\ No newline at end of file
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 基于测量的量子近似优化算法\n",
"\n",
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 多项式无约束布尔优化问题\n",
"\n",
"在应用数学和理论计算机科学中,**组合优化问题(combinatorial optimization problem)** 是在一个离散的解空间中寻找最优解的一类问题。在[量桨平台教程:量子近似优化算法](https://qml.baidu.com/tutorials/combinatorial-optimization/quantum-approximate-optimization-algorithm.html)中,已经介绍过了一般的组合优化问题。在这里,我们关注一类具体的问题:**多项式无约束布尔优化问题(polynomial unconstrained boolean optimization problem, PUBO)**。\n",
"\n",
"给定一个变量为 $x = \\{x_1,\\cdots,x_n\\}$ 的 $n$ 元多项式,\n",
"\n",
"$$\n",
"C(x) = \\sum_{\\lambda \\in \\Lambda } \\alpha_{\\lambda} \\prod_{j \\in \\lambda} x_j,\\tag{1}\n",
"$$\n",
"\n",
"其中每个变量 $x_i \\in \\{0,1\\}$,$\\underset{j \\in \\lambda}{\\prod} x_j$ 为一个单项式,$\\lambda \\subseteq [n]:= \\{1, 2, ..., n\\}$ 为一个指标集,$\\Lambda$ 为指标集的集合,$\\alpha_\\lambda$ 为每个单项式对应的实系数。在 PUBO 中,$C(x)$ 称为目标多项式,我们需要寻找一组最优解 $x^* = \\{x_1^*, x_2^*, ..., x_n^*\\} $ 使得目标多项式的取值最大,即\n",
"\n",
"$$\n",
"x^* = \\underset{x}{\\text{argmax}} \\ C(x).\\tag{2}\n",
"$$\n",
"\n",
"多项式无约束布尔优化问题是一种应用极为广泛的数学优化模型,对这种模型的高效求解有助于解决很多现实中的问题。如果需要求解的目标多项式次数为二,则称为二次多项式组合优化。这类模型可以描述例如最大独立集(MIS)、最大割(Max-Cut)、最大点集覆盖(Max-Coverage)等很多图论中的组合优化问题,并在诸如统计物理,网络设计,超大规模集成电路(VLSI)设计,数据聚类分析,金融分析和机器调度等方面有广泛应用。如果目标多项式次数大于二,这样的多项式组合优化则在信号处理(SP)和计算机视觉(CV)中图像重构等领域有重要应用。但是一般 PUBO 的求解是 NP-困难的,目前,精确求解这类问题没有有效的多项式时间复杂度的算法。"
]
},
{
"cell_type": "markdown",
"metadata": {
"tags": []
},
"source": [
"## 量子近似优化算法\n",
"\n",
"在 2014 年 Farhi 及其合作者通过经典计算与量子计算混合迭代的思路,提出了量子近似优化算法 **(quantum approximate optimization algorithm, QAOA)** [1],一方面希望利用量子计算机的能力更好地解决组合优化问题,另一方面也希望通过此问题展现量子计算机的优势。关于 QAOA 原理的详细论述请参见[量桨平台教程:量子近似优化算法](https://qml.baidu.com/tutorials/combinatorial-optimization/quantum-approximate-optimization-algorithm.html),此处我们简要回顾一下该算法的基本思想和实现步骤。\n",
"\n",
"要将组合优化问题翻译为量子版本,我们需要先将待优化的变量编码为量子态,以及将目标多项式编码为系统的哈密顿量。接下来,我们对这两点逐一进行讲解。\n",
"\n",
"### 变量编码为量子态\n",
"\n",
"对于变量 $x$,根据定义,它的每个比特的取值均为 $0$ 或 $1$,这便很自然地与量子态的 $|0\\rangle$, $|1\\rangle$ 系统相对应。于是长度为 $n$ 的布尔变量 $x$ 可以对应于一个 $n$ 量子比特构成的量子态,即:\n",
"\n",
"$$\n",
"|x\\rangle = |x_1x_2...x_n\\rangle,\\tag{3}\n",
"$$\n",
"\n",
"从而寻找原问题的最优解 $x^{*}$ 就相当于寻找某一个量子态 $|x^{*} \\rangle$。\n",
"\n",
"### 目标多项式编码为系统哈密顿量\n",
"\n",
"对于目标多项式 $C(x)$,我们可以将其编码到一个系统哈密顿量 $H_C$ 的对角元上,并使其满足对任意的 $x$,\n",
"\n",
"$$\n",
"H_C |x\\rangle = C(x) |x\\rangle.\\tag{4}\n",
"$$\n",
"\n",
"值得注意的是,假设原问题的一个最优解是 $x^{*}$,那么我们有:\n",
"\n",
"$$\n",
"\\langle x^{*} | H_{C} |x^{*} \\rangle = C(x^{*}).\\tag{5}\n",
"$$\n",
"\n",
"于是寻找原组合优化问题的最优解 $x^{*}$ 等同于寻找系统哈密顿量 $H_C$ 的最大本征值对应的本征态 $|x^{*} \\rangle$,即:\n",
"\n",
"$$\n",
"|x^{*}\\rangle = \\underset{|x\\rangle}{\\text{argmax}} \\ \\langle x | H_C | x \\rangle.\\tag{6}\n",
"$$\n",
"\n",
"**注意**:以上给出了编码目标多项式到系统哈密顿量的方式,但是如何更方便的找到 $H_C$ 的表达式呢?我们不妨先考虑一个简单的例子。假设目标多项式为 $C(x) = 1-2x$,即 $C(0) = 1, C(1) = -1$,则可以跟据定义快速找到对应的系统哈密顿量为 $H_C = Z$,其中 $Z$ 为 Pauli $Z$ 门。那么一般地,我们可以先对目标多项式 $C(x)$ 进行变量替换 $1-2x_i = z_i$ (即 $x_i = (1-z_i)/2$),其中 $z_i \\in \\{-1, 1\\} $,然后将 $z_i$ 变量替换为 Pauli $Z_i$ 算符,其中下角标 $i$ 表示对应的量子系统。可以验证这样构造的哈密顿量 $H_C$ 刚好满足 $H_C |x\\rangle = C(x) |x\\rangle$。\n",
"\n",
"为了方便使用,我们在 `qaoa` 中,定义了 `get_cost_hamiltonian` 函数,用来获取给定多项式的系统哈密顿量,我们可以直接调用它。\n",
"\n",
"```python\n",
"from paddle_quantum.mbqc.QAOA.qaoa import get_cost_hamiltonian\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### QAOA 电路\n",
"\n",
"在将变量和目标多项式分别编码到量子态和系统哈密顿量上之后,我们引入辅助哈密顿量 $H_B$,具有如下形式:\n",
"\n",
"$$\n",
"H_B = \\bigotimes_{j=1}^n X_j,\\tag{7}\n",
"$$\n",
"\n",
"其中,$X_j$ 为作用到第 $j$ 个比特上的 Pauli X 门。依据绝热定理 [2,3],在 QAOA 中,我们希望将 $H_B$ 最大本征值对应的本征态 $|+\\rangle^{\\otimes n}$ 演化到 $H_C$ 最大本征值对应的本征态。因此,我们可以构造量子电路实现如下的量子态演化,\n",
"\n",
"$$\n",
"|\\gamma,\\beta\\rangle := \\left(\\prod_{i=1}^p U_B(\\beta_i)U_{C}(\\gamma_i)\\right)|+\\rangle^{\\otimes n},\\tag{8}\n",
"$$\n",
"\n",
"其中 $U_{C}(\\gamma) = e^{-i\\gamma H_{C}}$,$U_B(\\beta) = e^{-i \\beta H_B}$,$\\beta,\\gamma$ 为待训练的参数 ,$p$ 为给定的算法深度,$p$ 越大,算法求得的解越精确,但是计算量也越大。\n",
"\n",
"关于电路模型下 QAOA 的详细介绍,请参见[量桨平台教程:量子近似优化算法](https://qml.baidu.com/tutorials/combinatorial-optimization/quantum-approximate-optimization-algorithm.html),我们这里不做赘述。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 基于测量的量子近似优化算法\n",
"\n",
"如前所述,[基于测量的量子计算](MBQC_CN.ipynb)提供了一种不同于量子电路模型的计算方式。由于该模型的通用性,任何量子电路模型都可以找到与其相对应的 MBQC 版本。文献 [4] 提出了一种基于测量的变分量子本征求解器(measurement-based variational quantum eigensolver, MB-VQE),类似地,我们在此给出**基于测量的量子近似优化算法(measurement-based quantum approximate optimization Algorithm, MB-QAOA)** 作为 MBQC 模型的入门算法教程之一。注意到量子电路模型中,不同的电路可以实现完全相同的演化效果。同样地,我们也可以找到很多种不同的 MBQC 算法完成相同的功能。在这里,我们给出一种较为简洁的 MBQC 版本的 QAOA 算法,并使用我们设计的 MBQC 模块对该算法进行模拟。\n",
"\n",
"\n",
"### 技术思路\n",
"\n",
"注意到 QAOA 的核心是对初始态完成 $U_{C}$ 和 $U_B$ 的交替演化。我们先用下面两个引理分别讲解在 MBQC 模型中该如何简单地完成这两种演化过程。感兴趣的读者可以尝试自行证明或者参见 [5]。\n",
"\n",
"**引理 1:** 假设 $|\\psi\\rangle_{1 \\cdots k}$ 为一个 $k$ 比特的量子态,对其进行 $e^{i\\theta Z_1Z_2\\cdots Z_k}$ 的演化,可以通过如下测量方案实现:\n",
"\n",
"$$\n",
"M_0^{YZ}(2\\theta) \\left(\\prod_{j=1}^{k} CZ_{0,j}\\right) \\Big(|+\\rangle_0 \\otimes |\\psi\\rangle_{1 \\cdots k}\\Big) \\longrightarrow \\left(\\prod_{j=1}^{k} Z_j\\right)^{s_0}\\, e^{i\\theta Z_1Z_2\\cdots Z_k}\\, |\\psi\\rangle_{1 \\cdots k},\\tag{9}\n",
"$$\n",
"\n",
"即我们先在 $0$ 系统上准备一个加态,然后对这个比特和输入态 $|\\psi\\rangle_{1 \\cdots k}$ 的每一个比特做 CZ 门,最后用 YZ 平面上的投影测量来测量 $0$ 系统上的比特,测量角度为 $2\\theta$,测量完成后,处于 $1,\\cdots,k$ 系统上的量子态将演化为箭头右边的状态,也就是 $e^{i\\theta Z_1Z_2\\cdots Z_k}\\, |\\psi\\rangle_{1 \\cdots k}$ 附加上 $\\left(\\prod_{j=1}^{k} Z_j\\right)^{s_0}$ 的副产品,其中 $s_0 \\in \\{0,1\\}$ 为 $0$ 系统的测量结果。\n",
"\n",
"注意到 $U_C$ 的本质是跟据目标函数连续完成多个不同的 $e^{i\\theta Z_1Z_2\\cdots Z_k}$ 的演化,因此将引理 1 中的实现方式重复使用多次,即可实现 $U_C$。\n",
"\n",
"**引理 2:** 设 $1$ 系统的量子态 $|\\psi\\rangle_1$ 为输入的单比特量子态,对其进行 $R_x(\\theta_2)R_z(\\theta_1)$ 的演化,可以通过如下测量方案实现:\n",
"\n",
"$$\n",
"M_2^{XY}\\big((-1)^{1+s_1}\\theta_2\\big) M_1^{XY}(-\\theta_1) \\Big(CZ_{23} CZ_{12}\\Big) \\Big(|\\psi\\rangle_1 \\otimes |+\\rangle_2 \\otimes |+\\rangle_3 \\Big) \\longrightarrow Z^{s_1} X^{s_2} R_{x}(\\theta_2) R_z(\\theta_1) |\\psi\\rangle_3.\\tag{10}\n",
"$$\n",
"\n",
"即我们先在 $2$ 和 $3$ 系统上分别准备一个加态,对 $1$, $2$ 系统和 $2$, $3$ 系统分别作用 $CZ$ 门,然后用 $XY$ 平面上的投影测量来测量 $1$ 系统上的比特,测量角度为 $-\\theta_1$, 记录测量结果为 $s_1$,再用 $XY$ 平面上的投影测量来测量 $2$ 系统上的比特,测量角度为 $(-1)^{1+s_1}\\theta_2$, 记录测量结果为 $s_2$,测量完成后在 $3$ 系统上的量子态将演化为箭头右边的状态,也就是 $R_{x}(\\theta_2) R_z(\\theta_1) |\\psi\\rangle_3$ 附加上副产品 $Z^{s_1} X^{s_2}$。\n",
"\n",
"注意到 $U_B$ 的本质是在不同比特上实现 $R_x$ 旋转门,因此我们可以利用引理 2 中的方式,令 $\\theta_1 = 0$ 来实现 $U_B$。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"通过以上两个引理,相信大家对 MB-QAOA 的实现有了一些想法。接下来我们跟据 MBQC 模型“三步走”流程(量子图态准备、单比特测量、副产品纠正)进行详细介绍。\n",
"\n",
"### 量子图态准备\n",
"\n",
"由于量子图态和图一一对应,所以我们给出对应的图即可,我们称这个图为 **MB-QAOA 图**。\n",
"\n",
"#### 单层 MB-QAOA 图的构造\n",
"\n",
"根据目标多项式 $C(x)$ 中变量的个数 $n$,依次纵向排列 $n$ 个绿色、 $n$ 个蓝色节点和 $n$ 个灰色节点,并依次记绿色节点为 $G^v$,蓝色节点为 $B^v$,灰色节点为 $H^v$,其中 $1 \\leq v \\leq n$。连接并排的绿色和蓝色节点,以及蓝色和灰色节点;\n",
"对目标多项式 $C(x)$ 做变量替换 $x = (1-z)/2$,得到新的目标多项式记为 \n",
"\n",
"$$\n",
"C(z) = c + \\sum_{v} \\eta_v z_v + \\sum_{S} \\eta_S \\prod_{j \\in S} z_j,\\tag{11}\n",
"$$\n",
"\n",
"其中 $c$ 为常数项,$1 \\leq v \\leq n$ 为线性项的指标,对应系数为 $\\eta_v$,$S$ 为非线性项的指标集,对应系数为 $\\eta_S$。我们要求补齐线性项中未出现的元素,并且设其对应系数为 $0$。那么对于 $C(z)$ 中除常数项外的每个非线性项的单项式 $\\prod_{j \\in S} z_j$,我们在绿色节点的左边添加一个新的红色节点,并记为 $R^S$,连接红色节点 $R^S$ 和绿色节点 $G^v$ ($ \\forall v\\in S$)。\n",
"\n",
"以上构造出来的图称为单层 MB-QAOA 图,根据之前讨论,我们将会通过测量红色节点来完成 $U_C$ 的演化,通过测量绿色和蓝色节点完成 $U_B$ 的演化,并且演化后的态存储在灰色节点中。为了方便理解,以下图 1 给出了一个具体的单层 MB-QAOA 图的例子。\n",
"\n",
"![QAOA graph](./figures/mbqc-fig-qaoa_graph_1.jpg)\n",
"<div style=\"text-align:center\">图 1:一个单层 MB-QAOA 图的例子,其中变量替换后的目标多项式为 $C(z) = z_2 + z_1 z_3 + 5 z_3 z_4 - 2 z_1 z_2 z_4$。 </div>\n",
"\n",
"注:单项式的系数对 MB-QAOA 图结构的构造没有影响,但这些系数会影响到测量节点时的角度(见下文)。\n",
"\n",
"#### $p$ 层 MB-QAOA 图的构造\n",
"\n",
"由于电路模型下的 QAOA 会对 $U_C$,$U_B$ 交替演化 $p$ 次,MBQC 模型下的 $p$ 层 QAOA 也是如此。图 $1$ 所示为 $p=1$ 情况,对于一般的 $p>1$,我们需要在右侧将单层 MB-QAOA 图重复 $p$ 次,并且下一层的绿色节点(对应输入态)要和上一层的灰色节点(对应输出态)重合,以此保证量子态可以进行连续的演化。最终的量子态会保存在最右端的灰色节点上。为了方便理解,以下图 2 给出了一个具体的 $p$ 层 MB-QAOA 图的例子。\n",
"\n",
"![QAOA graph](./figures/mbqc-fig-qaoa_graph_p.jpg)\n",
"<div style=\"text-align:center\">图 2:一个 $p$ 层 MB-QAOA 图的例子,其中变量替换后的目标多项式为$C(z) = z_2 + z_1 z_3 + 5 z_3 z_4 - 2 z_1 z_2 z_4$。 </div>\n",
"\n",
"在 `qaoa` 中,我们定义了 `preprocess` 函数,用于生成上述的 MB-QAOA 图,我们可以直接调用:\n",
"\n",
"```python\n",
"from paddle_quantum.mbqc.QAOA.qaoa import preprocess\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 单比特测量\n",
"\n",
"量子图态构造好之后,下一步就是设计每个比特上的测量方式了。根据以上两个引理,我们可以计算出每一步测量对应的角度,在这之中要注意的是测量角度对前面测量结果的依赖关系。\n",
"\n",
"假设 MB-QAOA 电路总深度为 $p$ 层,测量顺序按 MB-QAOA 图从左往右,从上到下的顺序按列进行测量,考虑第 $1 \\leq l \\leq p$ 层的 MB-QAOA图,具体每个节点的测量方式见表 1:\n",
"\n",
"|节点|测量基|测量角度|测量结果|\n",
"|:---:|:---:|:---:|:---:|\n",
"|$$R_l^S$$|$$M^{YZ}$$|$$T \\Big(1+\\sum_{v \\in S}\\sum_{k=1}^{l-1}s(B_k^v)\\Big) \\cdot 2 \\eta_{S} \\gamma_{l} $$|$$s(R_l^{S})$$|\n",
"|$$G_l^v$$|$$M^{XY}$$|$$T \\Big(1+\\sum_{k=1}^{l-1}s(B_k^v)\\Big) \\cdot 2 \\eta_v \\gamma_l $$|$$s(G_l^{v})$$|\n",
"|$$B_l^v$$|$$M^{XY}$$|$$T \\Big(1+\\sum_{k=1}^{l}\\sum_{S:v \\in S}s(R_k^S) + \\sum_{k=1}^{l}s(G_k^v)\\Big) 2 \\beta_l$$|$$s(B_l^{v})$$|\n",
"\n",
"<div style=\"text-align:center\">表 1:MB-QAOA 详细的测量方式 </div>\n",
"\n",
"其中 $1 \\leq v \\leq n$ 为变量替换后目标多项式的线性项指标,对应系数为 $\\eta_v$,$S$ 为变量替换后目标多项式的非线性项指标集,对应系数为 $\\eta_S$,$\\beta_l,\\gamma_l$ ($1 \\leq l \\leq p$)为待训练的参数,$M^{XY}$ 表示在 $XY$ 平面内的测量,$M^{YZ}$ 表示在 $YZ$ 平面内的测量,求和 $\\sum_{k=1}^0$ 约定为 $0$,函数 $T(x) = (-1)^x$。$R_k^S$ 代表图 2 中的红色节点,$s(R_k^S)$ 代表其对应测量结果;$G_k^v$ 代表图 2 中的绿色节点,$s(G_k^v)$ 代表其对应测量结果;$B_k^v$ 代表图 2 中的蓝色节点,$s(B_k^v)$ 代表其对应测量结果。\n",
"\n",
"为了方便 MB-QAOA 中测量角度的计算,我们在 `qaoa` 中,定义了测量角度函数 `adaptive_angle`。我们只需输入测量结果的字典, 待测量的比特标签, 待训练的角度参数和多项式系数等信息,即可按上表自动计算出测量角度。\n",
"\n",
"我们可以直接调用:\n",
"\n",
"```python\n",
"from paddle_quantum.mbqc.QAOA.qaoa import adaptive_angle\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 副产品纠正\n",
"\n",
"通过以上全部测量后(除最后一列灰色节点外),MB-QAOA 图中最末端灰色节点对应的量子态会演化为 $|\\gamma,\\beta\\rangle$ 附加上测量中产生的一些副产品。在第 $v$ 个节点上的副产品为 $X^{x}Z^{z} $,其中指数:\n",
"\n",
"$$\n",
"x = \\sum_{k=1}^{p} s(B_{k}^{v}), \\quad z = \\sum_{k=1}^{p} \\sum_{S: v\\in S} s(R_k^S) + \\sum_{k=1}^{p} s(G_k^v).\\tag{12}\n",
"$$\n",
"\n",
"我们需要在测量完成后对这些副产品进行纠正,最后获得的量子态将为我们预期的 $|\\gamma,\\beta\\rangle$。为了方便使用,我们在 `qaoa` 中定义了 `byproduct_power` 用于求解副产品纠正的指数。我们只需要输入待纠正的副产品项,待纠正的比特位置,MB-QAOA 图,测量结果的字典,以及电路深度,即可计算出对应纠正项的指数:\n",
"\n",
"```python\n",
"from paddle_quantum.mbqc.QAOA.qaoa import byproduct_power\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 代码实现\n",
"\n",
"下面我们用 MBQC 模块,完整地实现上述 MB-QAOA 算法。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 量子态的演化"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 引入模拟相关的模块\n",
"from paddle_quantum.mbqc.simulator import MBQC\n",
"from paddle_quantum.mbqc.utils import pauli_gate, kron, basis, permute_systems\n",
"from paddle_quantum.mbqc.QAOA.qaoa import preprocess, adaptive_angle, byproduct_power"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 定义 MBQC 模型下的 QAOA 函数\n",
"def mbqc_qaoa(poly_x, depth, gamma, beta):\n",
" \n",
" # 对目标多项式函数进行预处理,获得变量替换后的多项式 C(z) 和 MB-QAOA 图\n",
" poly_classified, qaoa_graph = preprocess(poly_x, depth)\n",
" var_num, cons_item, linear_items, non_linear_items = poly_classified\n",
"\n",
" # 实例化一个 MBQC 模型并设置图\n",
" mbqc = MBQC()\n",
" mbqc.set_graph(graph=qaoa_graph)\n",
"\n",
" # 测量每一个节点\n",
" for i in range(1, depth + 1):\n",
" \n",
" # 测量红色节点\n",
" for item in non_linear_items:\n",
" angle_r = adaptive_angle(which_qubit=('R', item[0], i),\n",
" graph=mbqc.get_graph(),\n",
" outcome=mbqc.get_classical_output(),\n",
" theta=gamma[i - 1],\n",
" eta=to_tensor(item[1], dtype='float64')\n",
" )\n",
" mbqc.measure(which_qubit=('R', item[0], i), basis_list=basis('YZ', angle_r))\n",
"\n",
" # 测量绿色节点\n",
" for v in range(1, var_num + 1):\n",
" angle_g = adaptive_angle(which_qubit=('G', v, i),\n",
" graph=mbqc.get_graph(),\n",
" outcome=mbqc.get_classical_output(),\n",
" theta=gamma[i - 1],\n",
" eta=linear_items[v])\n",
" mbqc.measure(which_qubit=('G', v, i), basis_list=basis('XY', angle_g))\n",
"\n",
" # 测量蓝色节点\n",
" for v in range(1, var_num + 1):\n",
" angle_b = adaptive_angle(which_qubit=('B', v, i),\n",
" graph=mbqc.get_graph(),\n",
" outcome=mbqc.get_classical_output(),\n",
" theta=beta[i - 1],\n",
" eta=to_tensor([1], dtype='float64'))\n",
" mbqc.measure(which_qubit=('B', v, i), basis_list=basis('XY', angle_b))\n",
"\n",
" # 纠正副产品\n",
" for v in range(1, var_num + 1):\n",
" pow_x = byproduct_power(gate='X', v=v, graph=mbqc.get_graph(), outcome=mbqc.get_classical_output(), depth=depth)\n",
" pow_z = byproduct_power(gate='Z', v=v, graph=mbqc.get_graph(), outcome=mbqc.get_classical_output(), depth=depth)\n",
" mbqc.correct_byproduct(gate='X', which_qubit=('H', v, depth), power=pow_x)\n",
" mbqc.correct_byproduct(gate='Z', which_qubit=('H', v, depth), power=pow_z)\n",
" \n",
" output_label = [('H', i, depth) for i in range(1, var_num + 1)]\n",
"\n",
" # 对输出量子态进行系统顺序调整并返回\n",
" state_out = permute_systems(mbqc.get_quantum_output(), output_label)\n",
" \n",
" return state_out.vector"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### MB-QAOA 优化网络的搭建\n",
"\n",
"优化网络搭建的流程与量桨上大部分机器学习的教程类似,唯一不同的是这里需要使用 `mbqc_qaoa` 函数作为前向传播函数。当算法完成后,我们会得到演化后的量子态,计算系统哈密顿量 $H_{C}$ 在该量子态下的期望值,将其作为损失函数接入飞桨优化器,对测量角度参数 $\\gamma_1, ... \\gamma_p, \\beta_1, ... \\beta_p$ 进行训练优化,最终得到优化后的参数。\n",
"\n",
"在 `qaoa` 中,我们定义了期望函数 `expecval`,用来计算系统哈密顿量 $H_C$ 在演化后的量子态 $|\\gamma,\\beta\\rangle$ 下的期望值 $\\langle \\gamma,\\beta| H_C| \\gamma,\\beta\\rangle$。\n",
"\n",
"```python\n",
"from paddle_quantum.mbqc.QAOA.qaoa import expecval\n",
"```\n",
"\n",
"MB-QAOA 优化网络搭建的代码实现如下:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 引入飞桨优化器模块\n",
"from paddle import nn\n",
"# 引入期望函数\n",
"from paddle_quantum.mbqc.QAOA.qaoa import expecval"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 定义 MB-QAOA 优化网络\n",
"class MB_QAOA_Net(nn.Layer):\n",
"\n",
" def __init__(self, depth, dtype=\"float64\"):\n",
" \n",
" super(MBQC_QAOA_Net, self).__init__()\n",
" \n",
" self.depth = depth\n",
" \n",
" # 定义训练参数\n",
" self.gamma = self.create_parameter(shape=[self.depth],\n",
" default_initializer=nn.initializer.Uniform(low=0.0, high=2 * pi),\n",
" dtype=dtype,\n",
" is_bias=False)\n",
" self.beta = self.create_parameter(shape=[self.depth],\n",
" default_initializer=nn.initializer.Uniform(low=0.0, high=2 * pi),\n",
" dtype=dtype,\n",
" is_bias=False)\n",
" \n",
" # 定义优化网络的前向传播机制,输入为目标多项式函数\n",
" def forward(self, poly):\n",
" \n",
" # 执行 MB-QAOA 算法并返回演化后的量子态\n",
" vector_out = mbqc_qaoa(poly, self.depth, self.gamma, self.beta)\n",
" \n",
" # 获取系统哈密顿量\n",
" HC = get_cost_hamiltonian(poly)\n",
" \n",
" # 计算系统哈密顿量在演化后量子态下的期望,作为损失函数\n",
" loss = - expecval(vector_out, HC)\n",
" \n",
" # 返回损失函数和量子态\n",
" return loss, vector_out"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 答案解码\n",
"\n",
"运行完 MB-QAOA 优化网络后,我们得到最优的参数 $\\gamma^*,\\beta^*$,以及对应的输出态 $|\\gamma^*,\\beta^*\\rangle$,但是我们还需要从输出态中获取 PUBO 问题的最终解,所以我们需要对量子态 $|\\gamma^*,\\beta^*\\rangle$ 重复测量多次后,统计对应结果的概率分布,并将最大概率对应的比特串作为原问题的最优解。在 `qaoa` 中我们定义了 `get_solution_string` 函数,用来解码量子答案,在具体的例子当中,我们可以根据自己的需要使用。\n",
"\n",
"```python\n",
"from paddle_quantum.mbqc.QAOA.qaoa import get_solution_string\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 示例\n",
"\n",
"在介绍完上述基于测量的量子近似优化算法之后,我们将该算法应用到两个具体的问题当中。在这两个例子中,我们可以直接调用 `qaoa` 中的 `MB_QAOA_Net` 运行 MB-QAOA 及参数的优化过程,具体示例请参见:\n",
"\n",
"- [MBQC 模型下求解多项式组合优化问题](PUBO_CN.ipynb)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## 参考文献\n",
"\n",
"[1] Farhi, Edward, et al. \"A quantum approximate optimization algorithm.\" [arXiv preprint arXiv:1411.4028 (2014).](https://arxiv.org/abs/1411.4028)\n",
"\n",
"[2] Farhi, Edward, et al. \"Quantum computation by adiabatic evolution.\" [arXiv preprint quant-ph/0001106 (2000).](https://arxiv.org/abs/quant-ph/0001106)\n",
"\n",
"[3] Duan, Runyao. \"Quantum adiabatic theorem revisited.\" [arXiv preprint arXiv:2003.03063 (2020).](https://arxiv.org/abs/2003.03063)\n",
"\n",
"[4] Ferguson, R. R., et al. \"Measurement-based variational quantum eigensolver.\" [Physical Review Letters 126.22 (2021): 220501-220501.](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.126.220501)\n",
"\n",
"[5] Browne, Dan, and Hans Briegel. \"One-way quantum computation.\" [Quantum Information: From Foundations to Quantum Technology Applications (2016): 449-473.](https://onlinelibrary.wiley.com/doi/abs/10.1002/9783527805785.ch21)"
]
}
],
"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"
},
"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
},
"varInspector": {
"cols": {
"lenName": 16,
"lenType": 16,
"lenVar": 40
},
"kernels_config": {
"python": {
"delete_cmd_postfix": "",
"delete_cmd_prefix": "del ",
"library": "var_list.py",
"varRefreshCmd": "print(var_dic_list())"
},
"r": {
"delete_cmd_postfix": ") ",
"delete_cmd_prefix": "rm(",
"library": "var_list.r",
"varRefreshCmd": "cat(var_dic_list()) "
}
},
"types_to_exclude": [
"module",
"function",
"builtin_function_or_method",
"instance",
"_Feature"
],
"window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Measurement-based Quantum Approximate Optimization Algorithm\n",
"\n",
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Polynomial Unconstrained Boolean Optimization Problem\n",
"\n",
"In the field of applied mathematics and theoretical computer science, **combinatorial optimization problem** is a class of problems aiming to find the optimal solution in a discrete solution space. In the tutorial [Quantum Approximate Optimization Algorithm](https://qml.baidu.com/tutorials/combinatorial-optimization/quantum-approximate-optimization-algorithm.html), we have already introduced general combinatorial optimization problems. Here we will focus on a specific type of combinatorial optimization problem: **polynomial unconstrained boolean optimization problem (PUBO)**.\n",
"\n",
"To be specific, a polynomial with $n$ variables of $x = \\{x_1,\\cdots,x_n\\}$ has the form of:\n",
"\n",
"$$\n",
"C(x) = \\sum_{\\lambda \\in \\Lambda } \\alpha_{\\lambda} \\prod_{j \\in \\lambda} x_j,\\tag{1}\n",
"$$\n",
"\n",
"where $x_i \\in \\{0,1\\}$ is a variable, $\\underset{j \\in \\lambda}{\\prod} x_j$ is a monomial, $\\lambda \\subseteq [n]:= \\{1, 2, ..., n\\}$ is a set of indexes, $\\Lambda$ is the set of index sets, $\\alpha_\\lambda$ is the real coefficient of monomial. In PUBO, $C(x)$ is called the objective polynomial. We hope to find an optimal solution $x^* = \\{x_1^*, x_2^*, ..., x_n^*\\} $ to maximize the value of $C(x)$, that is,\n",
"\n",
"$$\n",
"x^* = \\underset{x}{\\text{argmax}} \\ C(x).\\tag{2}\n",
"$$\n",
"\n",
"Polynomial unconstrained boolean optimization is a widely used optimization model. If the degree of the objective polynomial is two, it is called a quadratic polynomial combinatorial optimization problem, usually used to describe problems in graph theory, such as maximum independent set (MIS), maximum cut (Max-Cut), maximum set coverage (Max-Coverage), etc. It is also widely applicable in statistic physics, network design, Very Large Scale Integration Circuit (VLSI) design, cluster analysis, financial analysis, and machine scheduling. If the degree of the objective polynomial is larger than two, such a polynomial optimization will play an important role in signal processing (SP) and image reconstruction in computer vision (CV). However, it is NP-hard to find optimal solutions for PUBO in general."
]
},
{
"cell_type": "markdown",
"metadata": {
"tags": []
},
"source": [
"## Quantum Approximate Optimization Algorithm \n",
"\n",
"In 2014, Farhi et al. proposed the **quantum approximate optimization algorithm (QAOA)**, an iterative algorithm involving both classical and quantum computation [1]. This algorithm is designed to solve combinatorial optimization problems with the capability of a quantum computer, and also to demonstrate quantum advantages. For more details, please refer to the tutorial [Quantum Approximate Optimization Algorithm](https://qml.baidu.com/tutorials/combinatorial-optimization/quantum-approximate-optimization-algorithm.html). Here, we only briefly review the basic ideas and the implementation process of QAOA.\n",
"\n",
"To transform a classical combinatorial optimization problem into a quantum one, we need to encode variables to a quantum state and the objective polynomial to a Hamiltonian of a quantum system. We will explain these two steps as follows.\n",
"\n",
"### Encoding variables to a quantum state\n",
"\n",
"For variable $x$, each of its elements takes a value of either $0$ or $1$, which naturally corresponds to the quantum state $|0\\rangle$ and $|1\\rangle$. Thus, a string of boolean variables of length $n$ can be mapped to a quantum state with $n$ qubits, that is,\n",
"\n",
"$$\n",
"|x\\rangle = |x_1x_2...x_n\\rangle.\\tag{3}\n",
"$$\n",
"\n",
"Thereby, to find an optimal solution $x^{*}$ is to find a quantum state $|x^{*} \\rangle$.\n",
"\n",
"### Encoding the objective polynomial to a Hamiltonian \n",
"\n",
"For the objective polynomial $C(x)$, we can encode it to the diagonal of a Hamiltonian $H_C$, where for any $x$, it satisfies that\n",
"\n",
"$$\n",
"H_C |x\\rangle = C(x) |x\\rangle.\\tag{4}\n",
"$$\n",
"\n",
"It is worth noting that, if $x^{*}$ is one of the optimal solutions to the original problem, then it satisfies\n",
"\n",
"$$\n",
"\\langle x^{*} | H_{C} |x^{*} \\rangle = C(x^{*}).\\tag{5}\n",
"$$\n",
"\n",
"Therefore, to find an optimal solution $x^{*}$ of the original optimization problem is equivalent to find an eigenstate $|x^{*} \\rangle$ corresponding to a maximal eigenvalue of $H_C$. That is to solve\n",
"\n",
"$$\n",
"|x^{*}\\rangle = \\underset{|x\\rangle}{\\text{argmax}} \\ \\langle x | H_C | x \\rangle.\\tag{6}\n",
"$$\n",
"\n",
"**Note**: The above definition gives a way to encode an objective polynomial to Hamiltonian. But how can we quickly find the exact expression of $H_C$? Let us consider a simple example first. Assume $C(x) = 1-2x$, so $C(0) = 1$ and $C(1) = -1$. It is clear that we should take $H_C = Z$ as the corresponding Hamiltonian, where $Z$ denotes the Pauli Z gate. In a more general case, we can substitute the variable $x$ in the objective polynomial $C(x)$ to variable $z$ with the relation of $1-2x_i = z_i$ (or $x_i = (1-z_i)/2$) and then rewrite each scalar variable $z_i$ to Pauli operator $Z_i$, where $i$ stands for the underlying system. It is easy to check that the resulting Hamiltonian satisfies $H_C |x\\rangle = C(x) |x\\rangle$.\n",
"\n",
"For users' convenience, we provide a function `get_cost_hamiltonian` in `qaoa` to obtain the Hamiltonian of a given polynomial. We can directly import it by\n",
"\n",
"```python\n",
"from paddle_quantum.mbqc.QAOA.qaoa import get_cost_hamiltonian\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### QAOA circuit\n",
"\n",
"After encoding the variables and objective function, we introduce an auxiliary Hamiltonian\n",
"\n",
"$$\n",
"H_B = \\bigotimes_{j=1}^n X_j,\\tag{7}\n",
"$$\n",
"\n",
"where $X_j$ is the Pauli $X$ gate applied to qubit $j$. Inspired by the quantum adiabatic theorem [2,3], we hope to evolve the eigenstate $|+\\rangle^{\\otimes n}$ of $H_B$ 's maximal eigenvalue, to the eigenstate of $H_C$ 's maximal eigenvalue. This can be achieved by the following,\n",
"\n",
"$$\n",
"|\\gamma,\\beta\\rangle := \\left(\\prod_{i=1}^p U_B(\\beta_i)U_{C}(\\gamma_i)\\right)|+\\rangle^{\\otimes n},\\tag{8}\n",
"$$\n",
"\n",
"where $U_{C}(\\gamma) = e^{-i\\gamma H_{C}}$, $U_B(\\beta) = e^{-i \\beta H_B}$ are unitaries, $\\beta$, and $\\gamma$ are the training parameters, and $p$ is the algorithm's depth. The larger the depth $p$ is, the more accurate the final solution would be, but in the expense of more computational resources.\n",
"\n",
"Please refer to the tutorial [Quantum Approximate Optimization Algorithm](https://qml.baidu.com/tutorials/combinatorial-optimization/quantum-approximate-optimization-algorithm.html) for more details of QAOA in the circuit model."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Measurement-based QAOA\n",
"\n",
"[Measurement-based quantum computation (MBQC)](MBQC_EN.ipynb) provides a completely different computation model from the conventional circuit model. Due to the universality of MBQC, every quantum circuit model can be translated to its MBQC equivalent. In a recent work [4], a measurement-based variational quantum eigensolver (MB-VQE) is proposed. Following the same spirit, we propose a **measurement-based quantum approximate optimization algorithm (MB-QAOA)** here as an introductory tutorial of measurement-based quantum algorithms. Just like the circuit model, different MBQC algorithms may also be equivalent. Our proposed MB-QAOA is relatively concise and will be simulated by our MBQC module.\n",
"\n",
"\n",
"### Techniques\n",
"\n",
"The key point of QAOA is to perform the iterative operations of $U_{C}$ and $U_B$ to an initial quantum state. We will first introduce two lemmas to show how these two operations can be easily realized in MBQC. Interested readers can either prove these results on your own or simply refer to [5].\n",
"\n",
"**Lemma 1:** Let $|\\psi\\rangle_{1 \\cdots k}$ be a quantum state with $k$ qubits labeled as ${1, ..., k}$. The evolution under $e^{i\\theta Z_1Z_2\\cdots Z_k}$ can be realized by the following:\n",
"\n",
"$$\n",
"M_0^{YZ}(2\\theta) \\left(\\prod_{j=1}^{k} CZ_{0,j}\\right) \\Big(|+\\rangle_0 \\otimes |\\psi\\rangle_{1 \\cdots k}\\Big) \\longrightarrow \\left(\\prod_{j=1}^{k} Z_j\\right)^{s_0}\\, e^{i\\theta Z_1Z_2\\cdots Z_k}\\, |\\psi\\rangle_{1 \\cdots k}.\\tag{9}\n",
"$$\n",
"\n",
"That is, initialize a plus state on qubit $0$; apply CZ gates between qubit $0$ and each qubit of input state $|\\psi\\rangle_{1 \\cdots k}$; perform a projective measurement on qubit $0$ in the $YZ$ plane with an angle $2\\theta$. After this measurement, the state of qubit $1,\\cdots,k$ will be transformed into the state on the right side of the arrow, that is $e^{i\\theta Z_1Z_2\\cdots Z_k}|\\psi\\rangle_{1 \\cdots k}$, with byproducts $\\left(\\prod_{j=1}^{k} Z_j\\right)^{s_0}$ applied to it, where $s_0 \\in \\{0,1\\}$ is the measurement outcome of qubit $0$. \n",
"\n",
"Finally, the operation $U_C$ can be realized by successively performing $e^{i\\theta Z_1Z_2\\cdots Z_k}$ several times.\n",
"\n",
"**Lemma 2:** Let $|\\psi\\rangle_1$ be a single-qubit quantum state on qubit $1$. The evolution under $R_x(\\theta_2)R_z(\\theta_1)$ can be realized by the following:\n",
"\n",
"$$\n",
"M_2^{XY}\\big((-1)^{1+s_1}\\theta_2\\big) M_1^{XY}(-\\theta_1) \\Big(CZ_{23} CZ_{12}\\Big) \\Big(|\\psi\\rangle_1 \\otimes |+\\rangle_2 \\otimes |+\\rangle_3 \\Big) \\longrightarrow Z^{s_1} X^{s_2} R_{x}(\\theta_2) R_z(\\theta_1) |\\psi\\rangle_3.\\tag{10}\n",
"$$\n",
"\n",
"That is, initialize a plus state on qubit $2$ and qubit $3$ respectively; apply CZ gates to qubit $1$ and qubit $2$, qubit $2$ and qubit $3$; perform a projective measurement on qubit $1$ in the $XY$ plane with an angle of $-\\theta_1$ and record the measurement outcome as $s_1$; then perform a projective measurement on qubit $2$ in the $XY$ plane with an angle of $(-1)^{1+s_1}\\theta_2$ and record the measurement outcome as $s_2$. Finally, the state on qubit $3$ will be $R_{x}(\\theta_2) R_z(\\theta_1) |\\psi\\rangle_3$ with byproducts $Z^{s_1} X^{s_2}$ applied to it. \n",
"\n",
"The operation $U_B$ can be realized by using Lemma 2 multiple times with $\\theta_1 = 0$."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"With these two lemmas, I am sure you already have a rough idea of how to implement MB-QAOA. Next, we will give a concrete explanation by the standard \"three-step\" process of MBQC: graph state preparation, single-qubit measurement, and byproduct correction.\n",
"\n",
"\n",
"### Graph state preparation\n",
"\n",
"Due to the one-to-one correspondence between a graph and its graph state, it suffices to directly work with a graph in the following. We call it an **MB-QAOA graph**.\n",
"\n",
"#### One-layer MB-QAOA graph\n",
"\n",
"According to the number of variables $n$ of $C(x)$, we first arrange $n$ green vertices, $n$ blue vertices and $n$ gray vertices respectively in columns. Denote green vertices as $G^v$, blue vertices as $B^v$, and gray vertices as \n",
"$H^v$, where $1 \\leq v \\leq n$. Then connect green and blue vertices, blue and gray vertices on the same rows. Substitute the variables of $C(x)$ by $x_i = (1-z_i)/2$ and obtain a new function $C(z)$ which has a form of\n",
"\n",
"$$\n",
"C(z) = c + \\sum_{v} \\eta_v z_v + \\sum_{S} \\eta_S \\prod_{j \\in S} z_j,\\tag{11}\n",
"$$\n",
"\n",
"where $c$ is the constant item, $1 \\leq v \\leq n$ is the index of linear item with coefficient $\\eta_v$, $S$ is the index set of non-linear item with coefficient $\\eta_S$. We complement the absent linear items with coefficient $0$. For each non-linear monomial $\\prod_{j \\in S} z_j$ in $C(z)$, we add a new red vertex $R^S$ to the left of the green vertices, and connect $R^S$ to $G^v$ for all $v\\in S$.\n",
"\n",
"The resulting graph is called the \"one-layer MB-QAOA graph\". According to the Lemmas above, we will measure red vertices to realize the evolution of $U_C$, and measure green and blue vertices to realize the evolution of $U_B$. The gray vertices are left to store the post-measurement state. Figure 1 gives a specific example of a one-layer MB-QAOA graph. \n",
"\n",
"![QAOA graph](./figures/mbqc-fig-qaoa_graph_1.jpg)\n",
"<div style=\"text-align:center\">Figure 1: An example of one-layer MB-QAOA graph, with an objective function of $C(z) = z_2 + z_1 z_3 + 5 z_3 z_4 - 2 z_1 z_2 z_4$ after variable substitution.</div>\n",
"\n",
"#### $p$-layer MB-QAOA graph\n",
"\n",
"As is mentioned above, a $p$-layer QAOA circuit consists of iterative action of two operations ($U_C$ and $U_B$) $p$ times. This holds for MB-QAOA as well. For general depth $p$, we need to repeat the one-layer QAOA graph $p$ times, with the green vertices in the next layer (corresponding to the input state) overlapping to the gray vertices in a previous layer (corresponding to the output state), to ensure that the quantum state can evolve consecutively. The gray vertices on the rightmost are left to store the post-measurement state. Figure 2 gives a specific example of the $p$-layer MB-QAOA graph. \n",
"\n",
"![QAOA graph](./figures/mbqc-fig-qaoa_graph_p.jpg)\n",
"<div style=\"text-align:center\">Figure 2: An example of $p$-layer MB-QAOA graph, with an objective function of $C(z) = z_2 + z_1 z_3 + 5 z_3 z_4 - 2 z_1 z_2 z_4$ after variable substitution.</div>\n",
"\n",
"In `qaoa`, we provide a function `preprocess` to generate the MB-QAOA graph. We can import this function directly with the following line.\n",
"\n",
"```python\n",
"from paddle_quantum.mbqc.QAOA.qaoa import preprocess\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Single-qubit measurement\n",
"\n",
"After preparing a graph state, the next step is to design a specific measurement for each qubit. According to the Lemmas above, we are capable of calculating all measurement angles with previous measurement outcomes considered.\n",
"\n",
"Let $p$ be a given depth of the MB-QAOA circuit. We implement measurements on vertices of the MB-QAOA graph, from left to right and top to bottom in order. For vertices on the $l$-th layer, their measurement information is given in Table 1:\n",
"\n",
"|Vertex|Measurement Plane|Measurement Angle|Measurement Outcome|\n",
"|:---:|:---:|:---:|:---:|\n",
"|$$R_l^S$$|$$M^{YZ}$$|$$T \\Big(1+\\sum_{v \\in S}\\sum_{k=1}^{l-1}s(B_k^v)\\Big) \\cdot 2 \\eta_S \\gamma_l $$|$$s(R_l^{S})$$|\n",
"|$$G_l^v$$|$$M^{XY}$$|$$T \\Big(1+\\sum_{k=1}^{l-1}s(B_k^v)\\Big) \\cdot 2 \\eta_v \\gamma_l $$|$$s(G_l^{v})$$|\n",
"|$$B_l^v$$|$$M^{XY}$$|$$T \\Big(1+\\sum_{k=1}^{l}\\sum_{S:v \\in S}s(R_k^S) + \\sum_{k=1}^{l}s(G_k^v)\\Big) 2 \\beta_l$$|$$s(B_l^{v})$$|\n",
"\n",
"<div style=\"text-align:center\">Table 1: Detailed measurement information of MB-QAOA </div>\n",
"\n",
"where $1 \\leq v \\leq n$ is the index of linear item with coefficient $\\eta_v$ after variable substitution, $S$ is the index set of non-linear item with coefficient $\\eta_S$, $\\beta_l, \\gamma_l$ ($1 \\leq l \\leq p$) are the training parameters, $M^{XY}$ denotes measurements in the $XY$ plane, $M^{YZ}$ denotes measurements in the $YZ$ plane, the summation $\\sum_{k=1}^0$ is set to be $0$, $T(x)$ is a function $T(x) = (-1)^x$. Each $R_k^S$ denotes a red vertex in Figure 2, whose measurement outcome is $s(R_k^S)$. Each $G_k^v$ denotes a green vertex in Figure 2, whose measurement outcome is $s(G_k^v)$. Each $B^v$ denotes a blue vertex in Figure 2, whose measurement outcome is $s(B_k^v)$.\n",
"\n",
"For users' convenience, we provide a function `adaptive_angle` in `qaoa` to calculate measurement angles for MB-QAOA from Table 1, which takes arguments from the outcome dictionary, qubit's label, training parameters and polynomial's coefficients.\n",
"\n",
"We can directly import this function with the following line:\n",
"\n",
"```python\n",
"from paddle_quantum.mbqc.QAOA.qaoa import adaptive_angle\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Byproduct correction\n",
"\n",
"After the above measurements, the state on the rightmost gray vertices will be $|\\gamma,\\beta\\rangle$, with byproducts applied to it. The byproduct for the logical qubit $v$ is $X^{x}Z^{z} $ with\n",
"\n",
"$$\n",
"x = \\sum_{k=1}^{p} s(B_{k}^{v}), \\quad z = \\sum_{k=1}^{p} \\sum_{S: v\\in S} s(R_k^S) + \\sum_{k=1}^{p} s(G_k^v).\\tag{12}\n",
"$$\n",
"\n",
"To obtain the expected quantum state $|\\gamma,\\beta\\rangle$, we need to correct these byproducts after measurements. For users' convenience, we provide a function `byproduct_power` in `qaoa` to calculate the power of byproducts, which takes arguments from the type of byproducts, vertex, MB-QAOA graph, outcome dictionary, and circuit depth. \n",
"\n",
"We can directly import this function with the following line:\n",
"```python\n",
"from paddle_quantum.mbqc.QAOA.qaoa import byproduct_power\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Code Implementation\n",
"\n",
"Next, we will use our MBQC module to simulate MB-QAOA. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### State evolution"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Import the required modules\n",
"from paddle_quantum.mbqc.simulator import MBQC\n",
"from paddle_quantum.mbqc.utils import pauli_gate, kron, basis, permute_systems\n",
"from paddle_quantum.mbqc.QAOA.qaoa import preprocess, adaptive_angle, byproduct_power"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Define MB-QAOA\n",
"def mbqc_qaoa(poly_x, depth, gamma, beta):\n",
" \n",
" # Preprocess the objective function and obtain the MB-QAOA graph\n",
" poly_classified, QAOA_graph = preprocess(poly_x, depth)\n",
" var_num, cons_item, linear_items, non_linear_items = poly_classified\n",
"\n",
" # Instantiate a MBQC class and set the graph\n",
" mbqc = MBQC()\n",
" mbqc.set_graph(graph=QAOA_graph)\n",
"\n",
" # Measure every qubit\n",
" for i in range(1, depth + 1):\n",
" \n",
" # Measure red vertices\n",
" for item in non_linear_items:\n",
" angle_r = adaptive_angle(which_qubit=('R', item[0], i),\n",
" graph=mbqc.get_graph(),\n",
" outcome=mbqc.get_classical_output(),\n",
" theta=gamma[i - 1],\n",
" eta=to_tensor(item[1], dtype='float64')\n",
" )\n",
" mbqc.measure(which_qubit=('R', item[0], i), basis=basis('YZ', angle_r))\n",
"\n",
" # Measure green vertices\n",
" for v in range(1, var_num + 1):\n",
" angle_g = adaptive_angle(which_qubit=('G', v, i),\n",
" graph=mbqc.get_graph(),\n",
" outcome=mbqc.get_classical_output(),\n",
" theta=gamma[i - 1],\n",
" eta=linear_items[v])\n",
" mbqc.measure(which_qubit=('G', v, i), basis=basis('XY', angle_g))\n",
"\n",
" # Measure blue vertices\n",
" for v in range(1, var_num + 1):\n",
" angle_b = adaptive_angle(which_qubit=('B', v, i),\n",
" graph=mbqc.get_graph(),\n",
" outcome=mbqc.get_classical_output(),\n",
" theta=beta[i - 1],\n",
" eta=to_tensor([1], dtype='float64'))\n",
" mbqc.measure(which_qubit=('B', v, i), basis=basis('XY', angle_b))\n",
"\n",
" # Correct byproduct operators\n",
" for v in range(1, var_num + 1):\n",
" pow_x = byproduct_power(gate='X', v=v, graph=mbqc.get_graph(), outcome=mbqc.get_classical_output(), depth=depth)\n",
" pow_z = byproduct_power(gate='Z', v=v, graph=mbqc.get_graph(), outcome=mbqc.get_classical_output(), depth=depth)\n",
" mbqc.correct_byproduct(gate='X', which_qubit=('H', v, depth), power=pow_x)\n",
" mbqc.correct_byproduct(gate='Z', which_qubit=('H', v, depth), power=pow_z)\n",
" \n",
" output_label = [('H', i, depth) for i in range(1, var_num + 1)]\n",
"\n",
" # Permute the system order to fit output_label\n",
" state_out = permute_systems(mbqc.get_quantum_output(), output_label)\n",
" \n",
" return state_out.vector"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### MB-QAOA optimization neural network\n",
"\n",
"The procedure of building an optimization neural network is similar to most quantum machine learning tutorials on Paddle Quantum, with the only difference of setting the forward propagation by `mbqc_qaoa`. We will obtain the evolved state and calculate the expectation value of Hamiltonian $H_{C}$ in that state as the loss function. Then, we use Paddle optimizer to minimize loss with gradient descent algorithm and update the training parameters $\\gamma_1, ... \\gamma_p, \\beta_1, ... \\beta_p$ to the optimal ones.\n",
"\n",
"We provide a function `expecval` in `qaoa` to calculate the expectation value $\\langle \\gamma,\\beta| H_C| \\gamma,\\beta\\rangle$ of a Hamiltonian $H_C$ in the state $|\\gamma,\\beta\\rangle$ 。\n",
"\n",
"```python\n",
"from paddle_quantum.mbqc.QAOA.qaoa import expecval\n",
"```\n",
"\n",
"The code implementation of the MB-QAOA optimization net is as follows:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Import Paddle optimizer\n",
"from paddle import nn\n",
"# Import the expecval function\n",
"from paddle_quantum.mbqc.QAOA.qaoa import expecval"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Define the optimization net with MB-QAOA\n",
"class MBQC_QAOA_Net(nn.Layer):\n",
"\n",
" def __init__(self, depth, dtype=\"float64\"):\n",
" \n",
" super(MBQC_QAOA_Net, self).__init__()\n",
" \n",
" self.depth = depth\n",
" \n",
" # Define the training parameters\n",
" self.gamma = self.create_parameter(shape=[self.depth],\n",
" default_initializer=nn.initializer.Uniform(low=0.0, high=2 * pi),\n",
" dtype=dtype,\n",
" is_bias=False)\n",
" self.beta = self.create_parameter(shape=[self.depth],\n",
" default_initializer=nn.initializer.Uniform(low=0.0, high=2 * pi),\n",
" dtype=dtype,\n",
" is_bias=False)\n",
" \n",
" # Define the forward propagation with input polynomial\n",
" def forward(self, poly):\n",
" \n",
" # Run the MB-QAOA algorithm and return the ouput state vector\n",
" vector_out = mbqc_qaoa(poly, self.depth, self.gamma, self.beta)\n",
" \n",
" # Get the cost Hamiltonian\n",
" HC = get_cost_hamiltonian(poly)\n",
" \n",
" # Calculate expectation value and set it as a loss function\n",
" loss = - expecval(vector_out, HC)\n",
" \n",
" # Return loss and state\n",
" return loss, vector_out"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Decoding the solution\n",
"\n",
"After implementing the optimization neural network, we can obtain the optimal parameters $\\gamma^*,\\beta^*$, and the corresponding output state $|\\gamma^*,\\beta^*\\rangle$. It remains to extract the bit string as the final optimal solution to the original PUBO problem. For this, we need to measure the state $|\\gamma^*,\\beta^*\\rangle$ of a sufficient number of times to estimate the probability distribution of all binary strings and take the most probable one as our final solution. For this, we provide a function `get_solution_string` in `qaoa` to decode the quantum output.\n",
"\n",
"We can directly import this function with the following line:\n",
"\n",
"```python\n",
"from paddle_quantum.mbqc.QAOA.qaoa import get_solution_string\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Examples\n",
"\n",
"After introducing the above MB-QAOA algorithm, we will apply it to two specific examples which are then simulated by our MBQC module. In the examples, we will directly call `MBQC_QAOA_Net` in `qaoa` to instantiate an MB-QAOA optimization network. Please refer to the following tutorial for more details:\n",
"\n",
"- [Polynomial Unconstrained Boolean Optimization Problem in MBQC](PUBO_EN.ipynb)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## References\n",
"\n",
"[1] Farhi, Edward, et al. \"A quantum approximate optimization algorithm.\" [arXiv preprint arXiv:1411.4028 (2014).](https://arxiv.org/abs/1411.4028)\n",
"\n",
"[2] Farhi, Edward, et al. \"Quantum computation by adiabatic evolution.\" [arXiv preprint quant-ph/0001106 (2000).](https://arxiv.org/abs/quant-ph/0001106)\n",
"\n",
"[3] Duan, Runyao. \"Quantum adiabatic theorem revisited.\" [arXiv preprint arXiv:2003.03063 (2020).](https://arxiv.org/abs/2003.03063)\n",
"\n",
"[4] Ferguson, R. R., et al. \"Measurement-based variational quantum eigensolver.\" [Physical Review Letters 126.22 (2021): 220501-220501.](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.126.220501)\n",
"\n",
"[5] Browne, Dan, and Hans Briegel. \"One-way quantum computation.\" [Quantum Information: From Foundations to Quantum Technology Applications (2016): 449-473.](https://onlinelibrary.wiley.com/doi/abs/10.1002/9783527805785.ch21)"
]
}
],
"metadata": {
"interpreter": {
"hash": "73e152e9b81fe728e387c249fa9090f02d423820fe8ab6018c11ce80df71d553"
},
"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
},
"varInspector": {
"cols": {
"lenName": 16,
"lenType": 16,
"lenVar": 40
},
"kernels_config": {
"python": {
"delete_cmd_postfix": "",
"delete_cmd_prefix": "del ",
"library": "var_list.py",
"varRefreshCmd": "print(var_dic_list())"
},
"r": {
"delete_cmd_postfix": ") ",
"delete_cmd_prefix": "rm(",
"library": "var_list.r",
"varRefreshCmd": "cat(var_dic_list()) "
}
},
"types_to_exclude": [
"module",
"function",
"builtin_function_or_method",
"instance",
"_Feature"
],
"window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}
因为 它太大了无法显示 source diff 。你可以改为 查看blob
因为 它太大了无法显示 source diff 。你可以改为 查看blob
{
"cells": [
{
"cell_type": "markdown",
"id": "8a69d69c",
"metadata": {},
"source": [
"# 变分量子电路编译"
]
},
{
"cell_type": "markdown",
"id": "d17b0cc0",
"metadata": {},
"source": [
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
]
},
{
"cell_type": "markdown",
"id": "596b716a",
"metadata": {},
"source": [
"## 概览"
]
},
{
"cell_type": "markdown",
"id": "07a15fd1",
"metadata": {},
"source": [
"变分量子电路编译是一个通过优化参数化量子电路来模拟未知酉算子的过程,本教程我们将讨论两种未知酉算子的情况:一是给定酉算子 $U$ 的矩阵形式;二是给定一个实现 $U$ 的黑箱,可以将 $U$ 接入电路使用但不允许访问其内部构造。针对不同形式的 $U$,我们利用量桨构建量子线路训练损失函数,分别得到 $U$ 的近似酉算子 $V(\\vec{\\theta})$(这里我们用 $V(\\vec{\\theta})$ 表示参数化量子门序列所表示的酉算子,为简单起见下文我们用 $V$ 来表示)和 $V^{\\dagger}$ 的量子线路,并根据经过 $U$ 和 $V$ 演化后的量子态的迹距离对结果进行评估。"
]
},
{
"cell_type": "markdown",
"id": "166a3d1c",
"metadata": {},
"source": [
"## 背景\n",
"\n",
"经典计算机早期的编译过程是将二进制数字转变为高低电平驱动计算机的电子器件进行运算,随后逐渐发展为便于处理书写的汇编语言;与经典计算机类似,对于量子计算机而言,量子编译就是将量子算法中的酉变换转化为一系列量子门序列从而实现量子算法的过程。目前含噪的中等规模量子 (Noisy Intermediate-Scale Quantum, NISQ) 设备存在诸如在量子比特数量、电路深度等方面的限制,这些限制给量子编译算法带来了巨大挑战。文献 [1] 提出了一种量子编译算法——量子辅助量子编译算法 (Quantum-Assisted Quantum Compiling, QAQC),能够有效地在 NISQ 设备上实现。QAQC 的目的是将未知的目标酉算子 $U$ 编译成可训练的参数化量子门序列,利用门保真度定义损失函数,通过设计变分量子电路不断优化损失函数,得到近似目标酉算子 $U$ 的 $V$,但如何衡量两个酉算子的近似程度呢?这里我们考虑 $V$ 的酉演化能够模拟 $U$ 酉演化的概率,即对输入态 $|\\psi\\rangle$,$U|\\psi\\rangle$ 和 $V|\\psi\\rangle$ 的重叠程度,也就是哈尔( Haar )分布上的保真度平均值:\n",
"\n",
"$$\n",
"F(U,V)=\\int_{\\psi}|\\langle\\psi|V^{\\dagger}U|\\psi\\rangle|^2d\\psi,\n",
"\\tag{1}\n",
"$$\n",
"\n",
"当 $F(U,V)=1$ 时,存在$\\phi$,$V=e^{i\\phi}U$,即两个酉算子相差一个全局相位因子,此时我们称 $V$ 为 $U$ 的精确编译;当 $F(U,V)\\geq 1-\\epsilon$ 时,我们称 $V$ 为 $U$ 的近似编译,其中 $\\epsilon\\in[0,1]$ 为误差。基于此,我们可以构造以下的损失函数:\n",
"\n",
"$$\n",
"\\begin{aligned} C(U,V)&=\\frac{d+1}{d}(1-F(U,V))\\\\\n",
"&=1-\\frac{1}{d^2}|\\langle V,U\\rangle|^2\\\\\n",
"&=1-\\frac{1}{d^2}|\\text{tr}(V^{\\dagger} U)|^2,\n",
"\\end{aligned}\n",
"\\tag{2}\n",
"$$\n",
"\n",
"其中 $n$ 为量子比特数,$d=2^n$,$\\frac{1}{d^2}|\\text{tr}(V^{\\dagger} U)|^2$ 也被称为门保真度。\n",
"\n",
"由 (2) 式可得当且仅当 $F(U,V)=1$ 时,$C(V,U)=0$ ,因此我们通过训练一系列门序列来最小化损失函数,从而得到近似目标酉算子 $U$ 的 $V$。"
]
},
{
"cell_type": "markdown",
"id": "189c9359",
"metadata": {},
"source": [
"## 第一种情况 —— 矩阵形式的 $U$\n",
"\n",
"下面我们先分析已知 $U$ 矩阵形式的情况,以 Toffoli 门为例,已知其矩阵表示为 $U_0$,搭建量子神经网络(即参数化量子电路)通过训练优化得到 $U_0$ 的近似电路分解。\n",
"\n",
"我们在量桨中实现上述过程,首先引入需要的包:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "ae8f2fdb",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import paddle\n",
"from paddle_quantum.circuit import UAnsatz\n",
"from paddle_quantum.utils import dagger, trace_distance\n",
"from paddle_quantum.state import density_op_random"
]
},
{
"cell_type": "markdown",
"id": "83d5d086",
"metadata": {},
"source": [
"接下来将 Toffoli 门的矩阵形式 $U_0$ 输入电路中:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "4663732b",
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"n = 3 # 设定量子比特数\n",
"\n",
"# 输入 Toffoli 门的矩阵形式\n",
"U_0 = paddle.to_tensor(np.matrix([[1, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 1, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 1, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 1, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 1, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 1, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 1],\n",
" [0, 0, 0, 0, 0, 0, 1, 0]],\n",
" dtype=\"float64\"))"
]
},
{
"cell_type": "markdown",
"id": "bfc61ad3",
"metadata": {},
"source": [
"### 搭建量子电路\n",
"\n",
"不同的量子神经网络(Quantum Neural Network, QNN)表达能力不同,此处我们选择的是量桨中内置的 `complex_entangled_layer(theta, D)` 模板构造表达能力较强的电路模板搭建 QNN:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "4e400e2e",
"metadata": {},
"outputs": [],
"source": [
"# 构建量子电路\n",
"def Circuit(theta, n, D):\n",
" # 初始化 n 个量子比特的量子电路\n",
" cir = UAnsatz(n)\n",
" # 内置的包含 U3 门和 CNOT 门的强纠缠电路模板\n",
" cir.complex_entangled_layer(theta[:D], D)\n",
"\n",
" return cir"
]
},
{
"cell_type": "markdown",
"id": "d97698f3",
"metadata": {},
"source": [
"### 配置训练模型 —— 损失函数\n",
"\n",
"接下来进一步定义损失函数 $C(U,V) = 1-\\frac{1}{d^2}|\\text{tr}(V^{\\dagger} U)|^2$ 和训练参数。"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "29c9ed4a",
"metadata": {},
"outputs": [],
"source": [
"# 训练模型cost-function\n",
"class Net(paddle.nn.Layer):\n",
" def __init__(self, shape, dtype=\"float64\", ):\n",
" super(Net, self).__init__()\n",
"\n",
" self.theta = self.create_parameter(shape=shape,\n",
" default_initializer=paddle.nn.initializer.Uniform(low=0.0, high=2 * np.pi),\n",
" dtype=dtype, is_bias=False)\n",
"\n",
" def forward(self, n, D):\n",
" # 量子电路的矩阵表示\n",
" cir = Circuit(self.theta, n, D)\n",
" V = cir.U\n",
" # 直接构造 (1) 式为损失函数\n",
" loss =1 - (dagger(V).matmul(U_0).trace().abs() / V.shape[0]) ** 2\n",
"\n",
" return loss, cir "
]
},
{
"cell_type": "markdown",
"id": "8229500e",
"metadata": {},
"source": [
"### 配置训练模型 —— 模型参数\n",
"\n",
"对 QNN 进行训练前,我们还需要进行一些训练的超参数设置,主要是 QNN 计算模块的层数 $D$、学习速率 LR 以及训练的总迭代次数 ITR。此处我们设置学习速率为 0.1,迭代次数为 150 次。读者可以自行调整来直观感受下超参数调整对训练效果的影响。"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "4046e5b0",
"metadata": {},
"outputs": [],
"source": [
"D = 5 # 量子电路的层数\n",
"LR = 0.1 # 基于梯度下降的优化方法的学习率\n",
"ITR = 150 # 训练的总迭代次数"
]
},
{
"cell_type": "markdown",
"id": "fcde30e5",
"metadata": {},
"source": [
"### 进行训练"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "7103bf1c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"iter: 30 loss: 0.1571\n",
"iter: 60 loss: 0.0063\n",
"iter: 90 loss: 0.0004\n",
"iter: 120 loss: 0.0000\n",
"iter: 150 loss: 0.0000\n",
"\n",
"训练后的电路:\n",
"--U----*---------x----U----*---------x----U----*---------x----U----*---------x----U----*---------x--\n",
" | | | | | | | | | | \n",
"--U----x----*----|----U----x----*----|----U----x----*----|----U----x----*----|----U----x----*----|--\n",
" | | | | | | | | | | \n",
"--U---------x----*----U---------x----*----U---------x----*----U---------x----*----U---------x----*--\n",
" \n",
"优化后的参数 theta:\n",
" [[[ 6.283e+00 3.005e+00 2.493e+00]\n",
" [ 1.571e+00 3.141e+00 7.068e+00]\n",
" [-7.850e-01 3.141e+00 1.571e+00]]\n",
"\n",
" [[ 4.712e+00 1.571e+00 4.713e+00]\n",
" [ 1.571e+00 -1.000e-03 1.571e+00]\n",
" [ 6.283e+00 4.427e+00 1.857e+00]]\n",
"\n",
" [[ 6.283e+00 2.003e+00 3.494e+00]\n",
" [ 4.713e+00 -1.571e+00 -0.000e+00]\n",
" [ 3.142e+00 2.248e+00 5.390e+00]]\n",
"\n",
" [[ 3.142e+00 1.158e+00 5.085e+00]\n",
" [ 3.927e+00 3.142e+00 4.713e+00]\n",
" [ 1.571e+00 4.712e+00 2.356e+00]]\n",
"\n",
" [[ 3.140e+00 6.180e+00 6.180e+00]\n",
" [ 1.571e+00 1.571e+00 3.142e+00]\n",
" [ 0.000e+00 3.770e-01 2.764e+00]]\n",
"\n",
" [[ 6.107e+00 5.604e+00 5.597e+00]\n",
" [ 3.890e-01 5.391e+00 4.620e-01]\n",
" [ 3.935e+00 3.262e+00 9.850e-01]]]\n"
]
}
],
"source": [
"# 确定网络参数维度\n",
"net = Net(shape=[D + 1, n, 3])\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.forward(n, D)\n",
" loss.backward()\n",
" opt.minimize(loss)\n",
" opt.clear_grad()\n",
"\n",
" if itr % 30 == 0:\n",
" print(\"iter:\", itr, \"loss:\", \"%.4f\" % loss.numpy())\n",
" if itr == ITR:\n",
" print(\"\\n训练后的电路:\")\n",
" print(cir)\n",
"\n",
"theta_opt = net.theta.numpy()\n",
"print(\"优化后的参数 theta:\\n\", np.around(theta_opt, decimals=3))"
]
},
{
"cell_type": "markdown",
"id": "ca9f9196",
"metadata": {},
"source": [
"当已知目标酉算子的矩阵形式时,根据迭代过程及测试结果我们可以看到以 Toffoli 门的矩阵形式为例,搭建五层量子神经网络进行训练,迭代 150 次左右时,损失函数达到 0。"
]
},
{
"cell_type": "markdown",
"id": "0861f2ce",
"metadata": {},
"source": [
"### 结果验证\n",
"\n",
"下面我们随机选取 10 个密度矩阵,分别经过目标酉算子 $U$ 和近似酉算子 $V$ 的演化,计算真实的输出 `real_output` 和近似的输出 `simulated_output` 之间的迹距离 $ d(\\rho, \\sigma) = \\frac{1}{2}\\text{tr}\\sqrt{(\\rho-\\sigma)^{\\dagger}(\\rho-\\sigma)}$,迹距离越小,说明近似效果越好。"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "12678dff",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"sample: 1 :\n",
" trace distance is 0.00039\n",
"sample: 2 :\n",
" trace distance is 0.00038\n",
"sample: 3 :\n",
" trace distance is 0.00041\n",
"sample: 4 :\n",
" trace distance is 0.00043\n",
"sample: 5 :\n",
" trace distance is 0.00032\n",
"sample: 6 :\n",
" trace distance is 0.00036\n",
"sample: 7 :\n",
" trace distance is 0.0003\n",
"sample: 8 :\n",
" trace distance is 0.0004\n",
"sample: 9 :\n",
" trace distance is 0.00038\n",
"sample: 10 :\n",
" trace distance is 0.00042\n"
]
}
],
"source": [
"s = 10 # 定义随机生成密度矩阵的数量\n",
"\n",
"for i in range(s):\n",
" sampled = paddle.to_tensor(density_op_random(3).astype('complex128')) # 随机生成 3 量子比特的密度矩阵 sampled\n",
" simulated_output = paddle.matmul(paddle.matmul(cir.U, sampled), dagger(cir.U)) # sampled 经过近似酉算子演化后的结果\n",
" real_output = paddle.matmul(paddle.matmul(paddle.to_tensor(U_0), sampled), dagger(paddle.to_tensor(U_0))) # sampled 经过目标酉算子演化后的结果\n",
" print('sample:', i + 1, ':')\n",
" d = trace_distance(real_output.numpy(), simulated_output.numpy())\n",
" print(' trace distance is', np.around(d, decimals=5)) # 输出两种结果间的迹距离\n"
]
},
{
"cell_type": "markdown",
"id": "567a77a3",
"metadata": {},
"source": [
"可以看到各个样本分别经过 $U$ 和 $V$ 的演化后迹距离都接近 0, 说明 $V$ 近似 $U$ 的效果很好。"
]
},
{
"cell_type": "markdown",
"id": "f2f3d7d5",
"metadata": {},
"source": [
"## 第二种情况 —— 线路形式的 $U$\n",
"\n",
"第二种情况下,我们假设 $U$ 以黑盒的形式给出,其保真度不能再直接计算,因此它需要通过一个电路来计算。接下来我们将演示如何用量子电路计算保真度。\n",
"\n",
"### 利用量子电路图计算保真度"
]
},
{
"cell_type": "markdown",
"id": "7a62ac66",
"metadata": {},
"source": [
"在实现 QAQC 的过程中,我们需要设计量子电路图来训练损失函数。QAQC 的 QNN 是嵌套在一个更大的量子电路中,整个量子电路如下图所示,其中 $U$ 表示需要近似的酉算子,$V^{\\dagger}$ 是我们要训练的 QNN。这里我们利用 Toffoli 门作为黑箱。\n",
"\n",
"![circuit](./figures/vqcc-fig-circuit.png \"图1: QAQC 量子电路图 [1]。\")\n",
"<center> 图1: QAQC 量子电路图 [1]。 </center>\n",
"\n",
"电路总共需要 $2n$ 量子比特,我们称前 $n$ 个量子比特为系统 $A$,后 $n$ 个为系统 $B$,整个电路涉及以下三步:\n",
"\n",
"- 首先通过通过 Hadamard 门和 CNOT 门操作生成 $A、B$ 的最大纠缠态;\n",
"- 然后对 $A$ 系统进行 $U$ 操作,$B$ 系统执行 $V^{\\dagger}$(即 $V$ 的复共轭);\n",
"- 最后恢复第一步中的操作并在标准基下测量(也可以理解为在贝尔基下测量)。\n",
"\n",
"经过上述操作,测量得到的全零态的概率即为 $\\frac{1}{d^2}|\\text{tr}(V^{\\dagger} U)|^2$,关于图 1 的详细解释请参考文献 [1]。"
]
},
{
"cell_type": "markdown",
"id": "44f2ea35",
"metadata": {},
"source": [
"这里的 QNN 我们依旧采用第一种情况中的电路,黑箱为 Toffoli 门。\n",
"\n",
"下面我们在量桨上实现近似编译酉算子的过程:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "d6852694",
"metadata": {},
"outputs": [],
"source": [
"n = 3 # 设定量子比特数\n",
"\n",
"# 构建量子电路\n",
"def Circuit(theta, n, D):\n",
" \n",
" # 初始化 2n 个量子比特的量子电路\n",
" cir = UAnsatz(2 * n)\n",
" for i in range(n):\n",
" cir.h(i)\n",
" cir.cnot([i, n + i])\n",
" # 构建 U 的电路\n",
" cir.ccx([0, 1, 2])\n",
" \n",
" # 构建 QNN\n",
" cir.complex_entangled_layer(theta, D, [3, 4, 5])\n",
"\n",
" for l in range(n):\n",
" cir.cnot([n - 1 - l, 2 * n - 1 - l])\n",
" for m in range(n):\n",
" cir.h(m)\n",
"\n",
" return cir"
]
},
{
"cell_type": "markdown",
"id": "f0d141c7",
"metadata": {},
"source": [
"### 配置训练模型 —— 损失函数\n",
"\n",
"接下来进一步定义损失函数 $C(U,V) = 1-\\frac{1}{d^2}|\\text{tr}(V^{\\dagger} U)|^2$ 和训练参数:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "7ee10c61",
"metadata": {},
"outputs": [],
"source": [
"class Net(paddle.nn.Layer):\n",
" def __init__(self, shape, dtype=\"float64\", ):\n",
" super(Net, self).__init__()\n",
" \n",
" # 初始化层数以及各角度的参数,并用 [0, 2 * pi] 的均匀分布来填充角度的初始值\n",
" self.D = D\n",
" self.theta = self.create_parameter(shape=[D, n, 3],\n",
" default_initializer=paddle.nn.initializer.Uniform(low=0.0, high=2 * np.pi),\n",
" dtype=dtype, is_bias=False)\n",
"\n",
" # 定义损失函数和向前传播机制\n",
" def forward(self):\n",
" \n",
" # 量子电路的矩阵表示\n",
" cir = Circuit(self.theta, n, self.D)\n",
" # 输出经过线路后量子态的密度矩阵 rho\n",
" rho = cir.run_density_matrix()\n",
" # 计算损失函数 loss,其中输出密度矩阵的第一个元素即为全零态的概率\n",
" loss = 1 - paddle.real(rho[0][0])\n",
"\n",
" return loss, cir"
]
},
{
"cell_type": "markdown",
"id": "5bfa7b24",
"metadata": {},
"source": [
"### 配置训练模型 —— 模型参数\n",
"\n",
"我们设置学习速率为 0.1,迭代次数为 120 次。同样读者可以自行调整来直观感受下超参数调整对训练效果的影响。"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "280e2858",
"metadata": {},
"outputs": [],
"source": [
"D = 5 # 量子电路的层数\n",
"LR = 0.1 # 基于梯度下降的优化方法的学习率\n",
"ITR = 120 #训练的总迭代次数"
]
},
{
"cell_type": "markdown",
"id": "bb400510",
"metadata": {},
"source": [
"### 进行训练\n",
"\n",
"设置完训练模型的各项参数后,我们使用 Adam 优化器进行 QNN 的训练。"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "77919a34",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"iter: 20 loss: 0.2235\n",
"iter: 40 loss: 0.0187\n",
"iter: 60 loss: 0.0029\n",
"iter: 80 loss: 0.0004\n",
"iter: 100 loss: 0.0000\n",
"iter: 120 loss: 0.0000\n",
"\n",
"训练后的电路:\n",
"电路形式输入的 U 的近似电路:\n",
" --H----*------------------------*-------------------------------------------------------------------------------------------------------------*----H--\n",
" | | | \n",
"-------|----H----*--------------*--------------------------------------------------------------------------------------------------------*----|----H--\n",
" | | | | | \n",
"-------|---------|----H----*----X---------------------------------------------------------------------------------------------------*----|----|----H--\n",
" | | | | | | \n",
"-------x---------|---------|----U----*---------x----U----*---------x----U----*---------x----U----*---------x----U----*---------x----|----|----x-------\n",
" | | | | | | | | | | | | | | \n",
"-----------------x---------|----U----x----*----|----U----x----*----|----U----x----*----|----U----x----*----|----U----x----*----|----|----x------------\n",
" | | | | | | | | | | | | \n",
"---------------------------x----U---------x----*----U---------x----*----U---------x----*----U---------x----*----U---------x----*----x-----------------\n",
" \n"
]
}
],
"source": [
"# 确定网络的参数维度\n",
"net = Net(D)\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",
" \n",
" # 前向传播计算损失函数\n",
" loss, cir= net.forward()\n",
" \n",
" # 反向传播极小化损失函数\n",
" loss.backward()\n",
" opt.minimize(loss)\n",
" opt.clear_grad()\n",
"\n",
" # 打印训练结果\n",
" if itr % 20 == 0:\n",
" print(\"iter:\",itr,\"loss:\",\"%.4f\" % loss.numpy())\n",
" if itr == ITR:\n",
" print(\"\\n训练后的电路:\")\n",
" print('电路形式输入的 U 的近似电路:\\n', cir)\n"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "df546ac5",
"metadata": {},
"outputs": [],
"source": [
"# 存储优化后的参数\n",
"theta_opt = net.theta.numpy()"
]
},
{
"cell_type": "markdown",
"id": "c65d72dd",
"metadata": {},
"source": [
"当能够将 $U$ 的电路接入图 1 时,根据迭代过程及测试结果我们可以看到以 Toffoli 门为例,搭建一层量子神经网络进行训练,迭代 100 次左右时,损失函数趋近 0。"
]
},
{
"cell_type": "markdown",
"id": "040bf7d5",
"metadata": {},
"source": [
"### 结果验证\n",
"\n",
"与之前类似,我们同样随机选取 10 个密度矩阵,分别经过目标酉算子 $U$ 和近似酉算子 $V$ 的演化,计算真实的输出 `real_output` 和近似的输出 `simulated_output` 之间的迹距离,迹距离越小,说明近似效果越好。"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "0c339ca3",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"sample 1 :\n",
" trace distance is 0.00132\n",
"sample 2 :\n",
" trace distance is 0.00148\n",
"sample 3 :\n",
" trace distance is 0.00119\n",
"sample 4 :\n",
" trace distance is 0.00116\n",
"sample 5 :\n",
" trace distance is 0.00128\n",
"sample 6 :\n",
" trace distance is 0.00141\n",
"sample 7 :\n",
" trace distance is 0.00138\n",
"sample 8 :\n",
" trace distance is 0.00132\n",
"sample 9 :\n",
" trace distance is 0.00141\n",
"sample 10 :\n",
" trace distance is 0.00147\n"
]
}
],
"source": [
"s = 10 # 定义随机生成密度矩阵的数量\n",
"for i in range(s):\n",
" sampled = paddle.to_tensor(density_op_random(3).astype('complex128')) # 随机生成 4 量子比特的密度矩阵 sampled\n",
"\n",
" # 构造目标酉算子对应的电路\n",
" cir_1 = UAnsatz(3)\n",
" cir_1.ccx([0, 1, 2])\n",
" # sampled 经过目标酉算子演化后的结果\n",
" real_output = paddle.matmul(paddle.matmul(cir_1.U, sampled), dagger(cir_1.U))\n",
"\n",
" # 构造近似酉算子对应的电路\n",
" cir_2 = UAnsatz(3)\n",
" cir_2.complex_entangled_layer(paddle.to_tensor(theta_opt), D, [0, 1, 2])\n",
" # sampled 经过近似酉算子演化后的结果\n",
" simulated_output = paddle.matmul(paddle.matmul(cir_2.U, sampled), dagger(cir_2.U))\n",
"\n",
" d = trace_distance(real_output.numpy(), simulated_output.numpy())\n",
" print('sample', i + 1, ':')\n",
" print(' trace distance is', np.around(d, decimals=5)) # 输出两种结果间的迹距离"
]
},
{
"cell_type": "markdown",
"id": "e8b3d6c4",
"metadata": {},
"source": [
"可以看到各个样本分别经过 $U$ 和 $V$ 的演化后迹距离都接近 0, 说明 $V$ 近似 $U$ 的效果很好。"
]
},
{
"cell_type": "markdown",
"id": "afe8ea7c",
"metadata": {},
"source": [
"## 总结\n",
"\n",
"本教程从目标酉算子输入形式为矩阵和电路形式两种情况对其进行量子编译,通过两个简单的例子利用量桨展示了量子编译的效果,并随机生成量子态密度矩阵,对分别经过目标酉算子与近似酉算子演化后的结果求迹距离检验近似效果,结果表明量子编译效果较好。"
]
},
{
"cell_type": "markdown",
"id": "fc610a2f",
"metadata": {},
"source": [
"_______\n",
"\n",
"## 参考文献"
]
},
{
"cell_type": "markdown",
"id": "62dd936d",
"metadata": {},
"source": [
"[1] Khatri, Sumeet, et al. \"Quantum-assisted quantum compiling.\" [Quantum 3 (2019): 140](https://quantum-journal.org/papers/q-2019-05-13-140/)."
]
}
],
"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": "8a69d69c",
"metadata": {},
"source": [
"# Variational Quantum Circuit Compiling"
]
},
{
"cell_type": "markdown",
"id": "a24d6cf3",
"metadata": {},
"source": [
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
]
},
{
"cell_type": "markdown",
"id": "596b716a",
"metadata": {},
"source": [
"## Overview"
]
},
{
"cell_type": "markdown",
"id": "07a15fd1",
"metadata": {},
"source": [
"Variational quantum circuit compilation is the process of simulating an unknown unitary operator by optimizing a parameterized quantum circuit. In this tutorial we will discuss two cases of unknown unitary operators. One is that the target $U$ is given as a matrix form, the other is that the $U$ is given as a black-box. We show how to obtain the loss function in both cases in Paddle Quantum. With auto-differentiation and optimizer provided with PaddlePaddle, we could easily approximate $U$ into a trainable sequence of quantum gates (here we use $V(\\vec{\\theta})$ to denote the unitary operator represented by the sequence of parameterized quantum gates, and for simplicity, we use $V$ below). Finally, we validate the optimized circuit by comparing the trace distance of various output density matrices transformed by the approximate circuit and the target $U$."
]
},
{
"cell_type": "markdown",
"id": "166a3d1c",
"metadata": {},
"source": [
"## Background\n",
"\n",
"Earlier compilations of classical computers transformed binary numbers into electrical signals to drive the computer's electronic devices to perform operations, and then gradually developed into an assembly language for easy processing and writing. For quantum computers, similar to classical compilation, quantum compilation is a process of converting the unitary in a quantum algorithm into a series of the quantum gates to implement the algorithm. The current noisy intermediate-scale quantum (NISQ) devices have limitations such as the number of qubits, circuit depth, etc., which pose a great challenge to quantum compilation algorithms. In [1], a quantum compilation algorithm, the Quantum-assisted Quantum Compiling (QAQC), has been proposed for efficient implementation on NISQ devices. The idea of QAQC is to compile the unknown target unitary operator $U$ into the unitary $V$, define the loss function using the gate fidelity, and continuously optimize a variational quantum circuit by minimizing the loss function. But how to measure the similarity of the two unitary operators? Here we consider the probability that the unitary evolution of the $V$ can simulate the $U$, i.e., the degree of overlap between $U|\\psi\\rangle$ and $V|\\psi\\rangle$ for the input state $|\\psi\\rangle$, which is the average of the fidelity on the Haar distribution:\n",
"\n",
"$$\n",
"F(U,V)=\\int_{\\psi}|\\langle\\psi|V^{\\dagger}U|\\psi\\rangle|^2d\\psi,\n",
"\\tag{1}\n",
"$$\n",
"\n",
"When $F(U,V)=1$, there is a $\\phi$ such that $V=e^{i\\phi}U$, i.e., the two unitary operators differ by a global phase factor, at which point we call $V$ an exact compilation of $U$. When $F(U,V)\\geq 1-\\epsilon$, we call $V$ an approximate compilation of $U$, where $\\epsilon$ is an error and $\\epsilon\\in[0,1]$. Based on this, we can construct the following loss function:\n",
"\n",
"$$\n",
"\\begin{aligned} C(U,V)&=\\frac{d+1}{d}(1-F(U,V))\\\\\n",
"&=1-\\frac{1}{d^2}|\\langle V,U\\rangle|^2\\\\\n",
"&=1-\\frac{1}{d^2}|\\text{tr}(V^{\\dagger} U)|^2,\n",
"\\end{aligned}\n",
"\\tag{2}\n",
"$$\n",
"\n",
"where $n$ is the number of qubits, $d=2^n$ and $\\frac{1}{d^2}|\\text{tr}(V^{\\dagger} U)|^2$ is the gate fidelity.\n",
"\n",
"From (2), we have that $C(V,U)=0$ if and only if $F(U,V)=1$, so we can obtain $V$ that approximates the target unitary operator $U$ by training a sequence of rotational gates with adjustable angles to minimize the loss function."
]
},
{
"cell_type": "markdown",
"id": "189c9359",
"metadata": {},
"source": [
"## The First Scenario - Matrix Form of $U$\n",
"\n",
"In the first case, we suppose that $U$ is given in the form of a matrix. Taking the Toffoli gate as an example, we note its matrix representation as $U_0$. We wish to construct a quantum neural network (QNN, i.e., parameterized quantum circuit) to obtain an approximate circuit decomposition of $U_0$ by training.\n",
"\n",
"Let us import the necessary packages:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "ae8f2fdb",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import paddle\n",
"from paddle_quantum.circuit import UAnsatz\n",
"from paddle_quantum.utils import dagger, trace_distance\n",
"from paddle_quantum.state import density_op_random"
]
},
{
"cell_type": "markdown",
"id": "83d5d086",
"metadata": {},
"source": [
"We need to get the Toffoli gate's unitary matrix:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "4663732b",
"metadata": {},
"outputs": [],
"source": [
"n = 3 # Number of qubits\n",
"# The matrix form of Toffoli gate\n",
"U_0 = paddle.to_tensor(np.matrix([[1, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 1, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 1, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 1, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 1, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 1, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 1],\n",
" [0, 0, 0, 0, 0, 0, 1, 0]],\n",
" dtype=\"float64\"))"
]
},
{
"cell_type": "markdown",
"id": "bfc61ad3",
"metadata": {},
"source": [
"### Constructing quantum circuits\n",
"\n",
"Different QNNs have different expressibility. Here we choose the `complex_entangled_layer(theta, D)` function built-in in Paddle Quantum to construct QNN:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "4e400e2e",
"metadata": {},
"outputs": [],
"source": [
"# Constructing quantum circuit\n",
"def Circuit(theta, n, D):\n",
" # Initialize the circuit\n",
" cir = UAnsatz(n)\n",
" # Call the built-in QNN template\n",
" cir.complex_entangled_layer(theta[:D], D)\n",
"\n",
" return cir"
]
},
{
"cell_type": "markdown",
"id": "d97698f3",
"metadata": {},
"source": [
"\n",
"### Setting up the training model - loss function\n",
"\n",
"Next we define the loss function $C(U,V) = 1-\\frac{1}{d^2}|\\text{tr}(V^{\\dagger} U)|^2$ and training parameters in order to optimize the parameterized circuit. "
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "29c9ed4a",
"metadata": {},
"outputs": [],
"source": [
"# Training loss function\n",
"class Net(paddle.nn.Layer):\n",
" def __init__(self, shape, dtype=\"float64\", ):\n",
" super(Net, self).__init__()\n",
"\n",
" self.theta = self.create_parameter(shape=shape,\n",
" default_initializer=paddle.nn.initializer.Uniform(low=0.0, high=2 * np.pi),\n",
" dtype=dtype, is_bias=False)\n",
"\n",
" def forward(self, n, D):\n",
" # The matrix form of the circuit\n",
" cir = Circuit(self.theta, n, D)\n",
" V = cir.U\n",
" # Construct Eq.(1) as the loss function\n",
" loss =1 - (dagger(V).matmul(U_0).trace().abs() / V.shape[0]) ** 2\n",
"\n",
" return loss, cir "
]
},
{
"cell_type": "markdown",
"id": "8229500e",
"metadata": {},
"source": [
"### Setting up the training model - model parameters\n",
"\n",
"Before training the QNN, we also need to set some training hyperparameters, mainly the depth (D) of repeated blocks, the learning rate (LR), and the number of iterations (ITR). Here we set the learning rate to 0.1 and the number of iterations to 150. The reader can adjust the hyperparameters to observe the impact on the training effect."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "4046e5b0",
"metadata": {},
"outputs": [],
"source": [
"D = 5 # Set the depth of QNN\n",
"LR = 0.1 # Set the learning rate\n",
"ITR = 150 # Set the number of optimization iterations"
]
},
{
"cell_type": "markdown",
"id": "fcde30e5",
"metadata": {},
"source": [
"### Training"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "7103bf1c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"iter: 30 loss: 0.1627\n",
"iter: 60 loss: 0.0033\n",
"iter: 90 loss: 0.0001\n",
"iter: 120 loss: 0.0000\n",
"iter: 150 loss: 0.0000\n",
"\n",
"The trained circuit:\n",
"--U----*---------x----U----*---------x----U----*---------x----U----*---------x----U----*---------x--\n",
" | | | | | | | | | | \n",
"--U----x----*----|----U----x----*----|----U----x----*----|----U----x----*----|----U----x----*----|--\n",
" | | | | | | | | | | \n",
"--U---------x----*----U---------x----*----U---------x----*----U---------x----*----U---------x----*--\n",
" \n",
"The trained parameter theta:\n",
" [[[ 1.571 3.142 3.927]\n",
" [ 4.713 3.142 2.355]\n",
" [ 5.498 1.574 2.95 ]]\n",
"\n",
" [[ 3.927 6.284 1.571]\n",
" [ 5.498 3.142 4.712]\n",
" [ 5.498 2.961 1.571]]\n",
"\n",
" [[ 4.712 -1.571 6.281]\n",
" [ 6.284 4.77 4.655]\n",
" [ 3.143 1.93 0.359]]\n",
"\n",
" [[ 3.142 2.668 0.917]\n",
" [ 5.403 3.144 5.496]\n",
" [ 3.142 4.319 5.89 ]]\n",
"\n",
" [[ 1.571 0.881 1.571]\n",
" [ 1.571 6.972 1.571]\n",
" [ 4.712 2.452 1.57 ]]\n",
"\n",
" [[ 4.517 4.301 0.18 ]\n",
" [ 1.329 1.815 1.277]\n",
" [ 1.398 0.87 2.132]]]\n"
]
}
],
"source": [
"# Determine shape of parameter of the network\n",
"net = Net(shape=[D + 1, n, 3])\n",
"# Using Adam optimizer to obtain relatively good convergence\n",
"opt = paddle.optimizer.Adam(learning_rate=LR, parameters=net.parameters())\n",
"\n",
"# Optimization loop\n",
"for itr in range(1, ITR + 1):\n",
" loss, cir = net.forward(n, D)\n",
" loss.backward()\n",
" opt.minimize(loss)\n",
" opt.clear_grad()\n",
"\n",
" if itr % 30 == 0:\n",
" print(\"iter:\", itr, \"loss:\", \"%.4f\" % loss.numpy())\n",
" if itr == ITR:\n",
" print(\"\\nThe trained circuit:\")\n",
" print(cir)\n",
"\n",
"theta_opt = net.theta.numpy()\n",
"print(\"The trained parameter theta:\\n\", np.around(theta_opt, decimals=3))"
]
},
{
"cell_type": "markdown",
"id": "ca9f9196",
"metadata": {},
"source": [
"In this case, we construct a five-layer QNN and train it with an Adam optimizer. After around 150 iterations, the loss function reaches 0."
]
},
{
"cell_type": "markdown",
"id": "a5f13518",
"metadata": {},
"source": [
"### Validation of results\n",
"\n",
"In the following, we randomly select 10 density matrices, which are evolved by the target unitary operator $U$ and the approximate unitary operator $V$. Then we calculate the trace distance $ d(\\rho, \\sigma) = \\frac{1}{2}\\text{tr}\\sqrt{(\\rho-\\sigma)^{\\dagger}(\\rho-\\sigma)}$ between the real output `real_output` $\\rho$ and the approximate output `simulated_output` $\\sigma$. The smaller the trace distance, the better the approximation effect."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "12678dff",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"sample 1 :\n",
" trace distance is 0.00054\n",
"sample 2 :\n",
" trace distance is 0.00047\n",
"sample 3 :\n",
" trace distance is 0.00047\n",
"sample 4 :\n",
" trace distance is 0.00046\n",
"sample 5 :\n",
" trace distance is 0.0005\n",
"sample 6 :\n",
" trace distance is 0.00043\n",
"sample 7 :\n",
" trace distance is 0.00054\n",
"sample 8 :\n",
" trace distance is 0.00049\n",
"sample 9 :\n",
" trace distance is 0.00045\n",
"sample 10 :\n",
" trace distance is 0.00045\n"
]
}
],
"source": [
"s = 10 # Set the number of randomly generated density matrices\n",
"\n",
"for i in range(s):\n",
" sampled = paddle.to_tensor(density_op_random(3).astype('complex128')) # randomly generated density matrix of 3 qubits sampled\n",
" simulated_output = paddle.matmul(paddle.matmul(cir.U, sampled), dagger(cir.U)) # sampled after approximate unitary evolution\n",
" real_output = paddle.matmul(paddle.matmul(paddle.to_tensor(U_0), sampled), dagger(paddle.to_tensor(U_0))) # sampled after target unitary evolution\n",
" print('sample', i + 1, ':')\n",
" d = trace_distance(real_output.numpy(), simulated_output.numpy())\n",
" print(' trace distance is', np.around(d, decimals=5)) # print trace distance\n"
]
},
{
"cell_type": "markdown",
"id": "567a77a3",
"metadata": {},
"source": [
"We can see that the trace distance of each sample after the evolution of $U$ and $V$ is close to 0, which means the $V$ approximates $U$ very well."
]
},
{
"cell_type": "markdown",
"id": "f2f3d7d5",
"metadata": {},
"source": [
"## The Second Scenario - Circuit Form of $U$\n",
"\n",
"In the second case, we suppose the $U$ needs approximation is given in the form of a black-box, and we only have access to its input and output. As a results, the fidelity can no longer be computed directly. Instead, it needs to be evaluate by a circuit.\n",
"Next we will show how to calculate fidelity with a quantum circuit.\n",
"\n",
"### Calculate fidelity with a quantum circuit"
]
},
{
"cell_type": "markdown",
"id": "7a62ac66",
"metadata": {},
"source": [
"The QNN of QAQC that needs in a large quantum circuit. The whole circuit is shown below, where $U$ denotes the unitary operator to be approximated, and $V^{\\dagger}$ is the QNN we want to train. Here we use the Toffoli gate as the black-box.\n",
"\n",
"![circuit](./figures/vqcc-fig-circuit.png \"Figure 1: The circuit of the QAQC [1].\")\n",
"<center>Figure 1: The circuit of the QAQC [1].</center>\n",
"\n",
"The circuit requires a total of $2n$ qubits, and we call the first $n$ qubits system $A$ and the last $n$ qubits system $B$. The whole circuit involves the following three steps:\n",
"\n",
"- First creating a maximally entangled state between $A$ and $B$ by performing Hadamard and CNOT gates.\n",
"- Then acting with $U$ on system $A$ and with $V^{\\dagger}$ on system $B$ ($V^{\\dagger}$ is the complex conjugate of $V$), note that these two gates are performed in parallel.\n",
"- Finally measuring in the bell basis(i.e., undoing the CNOTS and Hadamards then measuring in the standard basis).\n",
"\n",
"After the above operation, the probability of the full zero state obtained by the measurement is $\\frac{1}{d^2}|\\text{tr}(V^{\\dagger} U)|^2$. For a detailed explanation of Figure 1 please refer to the literature [1]."
]
},
{
"cell_type": "markdown",
"id": "44f2ea35",
"metadata": {},
"source": [
"Here we use the same QNN that we used in the first case and use the Toffoli gate as the black-bx. \n",
"\n",
"Next we will implement variational quantum circuit compiling in Paddle Quantum as follows:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "d6852694",
"metadata": {},
"outputs": [],
"source": [
"n = 3 # Number of qubits\n",
"\n",
"# Construct the total quantum circuit\n",
"def Circuit(theta, n, D):\n",
" \n",
" # Initialize the circuit of 2n qubits \n",
" cir = UAnsatz(2 * n)\n",
" for i in range(n):\n",
" cir.h(i)\n",
" cir.cnot([i, n + i])\n",
" # Construct the circuit of U\n",
" cir.ccx([0, 1, 2])\n",
"\n",
" # Construct QNN\n",
" cir.complex_entangled_layer(theta, D, [3, 4, 5])\n",
" \n",
" for l in range(n):\n",
" cir.cnot([n - 1 - l, 2 * n - 1 - l])\n",
" for m in range(n):\n",
" cir.h(m)\n",
" \n",
" return cir"
]
},
{
"cell_type": "markdown",
"id": "f0d141c7",
"metadata": {},
"source": [
"### Setting up the training model - loss function\n",
"\n",
"Next we define the loss function $C(U,V) = 1-\\frac{1}{d^2}|\\text{tr}(V^{\\dagger} U)|^2$ and training parameters in order to optimize the QNN. "
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "7ee10c61",
"metadata": {},
"outputs": [],
"source": [
"class Net(paddle.nn.Layer):\n",
" def __init__(self, shape, dtype=\"float64\", ):\n",
" super(Net, self).__init__()\n",
" \n",
" # Initialize the theta parameter list and fill the initial value with the uniform distribution of [0, 2*pi]\n",
" self.D = D\n",
" self.theta = self.create_parameter(shape=[D, n, 3],\n",
" default_initializer=paddle.nn.initializer.Uniform(low=0.0, high=2 * np.pi),\n",
" dtype=dtype, is_bias=False)\n",
"\n",
" # Define loss function and forward propagation mechanism\n",
" def forward(self): \n",
" # The matrix form of circuit\n",
" cir = Circuit(self.theta, n, self.D)\n",
" # Output the density matrix rho of the quantum state after the circuit\n",
" rho = cir.run_density_matrix()\n",
" # Define loss function\n",
" loss = 1 - paddle.real(rho[0][0])\n",
"\n",
" return loss, cir"
]
},
{
"cell_type": "markdown",
"id": "5bfa7b24",
"metadata": {},
"source": [
"### Setting up the training model - model parameters\n",
"\n",
"Here we set the learning rate to 0.1 and the number of iterations to 120. The reader can also adjust them to observe the impact on the training effect."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "280e2858",
"metadata": {},
"outputs": [],
"source": [
"D = 5 # Set the depth of QNN\n",
"LR = 0.1 # Set the learning rate\n",
"ITR = 120 # Set the number of optimization iterations"
]
},
{
"cell_type": "markdown",
"id": "bb400510",
"metadata": {},
"source": [
"### Training\n",
"\n",
"Then we commence the training process with an Adam optimizer."
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "77919a34",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"iter: 20 loss: 0.1733\n",
"iter: 40 loss: 0.0678\n",
"iter: 60 loss: 0.0236\n",
"iter: 80 loss: 0.0020\n",
"iter: 100 loss: 0.0001\n",
"iter: 120 loss: 0.0000\n",
"\n",
"The trained circuit:\n",
"Approximate circuit of U with circuit form input:\n",
" --H----*------------------------*-------------------------------------------------------------------------------------------------------------*----H--\n",
" | | | \n",
"-------|----H----*--------------*--------------------------------------------------------------------------------------------------------*----|----H--\n",
" | | | | | \n",
"-------|---------|----H----*----X---------------------------------------------------------------------------------------------------*----|----|----H--\n",
" | | | | | | \n",
"-------x---------|---------|----U----*---------x----U----*---------x----U----*---------x----U----*---------x----U----*---------x----|----|----x-------\n",
" | | | | | | | | | | | | | | \n",
"-----------------x---------|----U----x----*----|----U----x----*----|----U----x----*----|----U----x----*----|----U----x----*----|----|----x------------\n",
" | | | | | | | | | | | | \n",
"---------------------------x----U---------x----*----U---------x----*----U---------x----*----U---------x----*----U---------x----*----x-----------------\n",
" \n"
]
}
],
"source": [
"# Determine the parameter dimension of the network\n",
"net = Net(D)\n",
"\n",
"# Use Adam optimizer for better performance\n",
"opt = paddle.optimizer.Adam(learning_rate=LR, parameters=net.parameters())\n",
"\n",
"# Optimization loop\n",
"for itr in range(1, ITR + 1):\n",
" \n",
" # Forward propagation calculates the loss function\n",
" loss, cir= net.forward()\n",
" \n",
" # Use back propagation to minimize the loss function\n",
" loss.backward()\n",
" opt.minimize(loss)\n",
" opt.clear_grad()\n",
"\n",
" # Print training results\n",
" if itr % 20 == 0:\n",
" print(\"iter:\",itr,\"loss:\",\"%.4f\" % loss.numpy())\n",
" if itr == ITR:\n",
" print(\"\\nThe trained circuit:\")\n",
" print('Approximate circuit of U with circuit form input:\\n', cir)\n"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "777ca58e",
"metadata": {},
"outputs": [],
"source": [
"# Storage optimized parameters\n",
"theta_opt = net.theta.numpy()"
]
},
{
"cell_type": "markdown",
"id": "c65d72dd",
"metadata": {},
"source": [
"In this case, we construct a one-layer QNN and train it with a Adam optimizer. After around 100 iterations, the loss function reaches 0."
]
},
{
"cell_type": "markdown",
"id": "040bf7d5",
"metadata": {},
"source": [
"### Validation of results\n",
"\n",
"Similar to before, we also randomly select 10 density matrices, which are evolved by the target unitary operator $U$ and the approximate unitary operator $V$. Then calculate the trace distance between the real output `real_output` and the approximate output `simulated_output`. The smaller the trace distance, the better the approximation."
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "0c339ca3",
"metadata": {
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"sample 1 :\n",
" trace distance is 0.00694\n",
"sample 2 :\n",
" trace distance is 0.00775\n",
"sample 3 :\n",
" trace distance is 0.00657\n",
"sample 4 :\n",
" trace distance is 0.00727\n",
"sample 5 :\n",
" trace distance is 0.00642\n",
"sample 6 :\n",
" trace distance is 0.00705\n",
"sample 7 :\n",
" trace distance is 0.00586\n",
"sample 8 :\n",
" trace distance is 0.00569\n",
"sample 9 :\n",
" trace distance is 0.00803\n",
"sample 10 :\n",
" trace distance is 0.00635\n"
]
}
],
"source": [
"s = 10 # Set the number of randomly generated density matrices\n",
"for i in range(s):\n",
" sampled = paddle.to_tensor(density_op_random(3).astype('complex128')) # randomly generated density matrix of 4 qubits sampled\n",
"\n",
" # Construct the circuit of target unitary\n",
" cir_1 = UAnsatz(3)\n",
" cir_1.ccx([0, 1, 2])\n",
" # sampled after target unitary evolution\n",
" real_output = paddle.matmul(paddle.matmul(cir_1.U, sampled), dagger(cir_1.U))\n",
"\n",
" # Construct the circuit of approximate unitary\n",
" cir_2 = UAnsatz(3)\n",
" cir_2.complex_entangled_layer(paddle.to_tensor(theta_opt), D, [0, 1, 2])\n",
" # sampled after approximate unitary evolution\n",
" simulated_output = paddle.matmul(paddle.matmul(cir_2.U, sampled), dagger(cir_2.U))\n",
"\n",
" d = trace_distance(real_output.numpy(), simulated_output.numpy())\n",
" print('sample', i + 1, ':')\n",
" print(' trace distance is', np.around(d, decimals=5)) # print trace distance"
]
},
{
"cell_type": "markdown",
"id": "e8b3d6c4",
"metadata": {},
"source": [
"We can see that the trace distance of each sample after the evolution of $U$ and $V$ is close to 0, which means the $V$ approximates $U$ very well."
]
},
{
"cell_type": "markdown",
"id": "afe8ea7c",
"metadata": {},
"source": [
"## Conclusion\n",
"\n",
"In this tutorial, the variational quantum circuit compiling is carried out from the input form of the target unitary operator as a matrix and as a circuit. The results of the quantum compilation are demonstrated by two simple examples using Paddle Quantum. Then the approximate effect is checked by the trace distance of the quantum states after the evolution of the target unitary and the approximate unitary respectively. Finally the results in Paddle Quantum show that the quantum compilation is good."
]
},
{
"cell_type": "markdown",
"id": "fc610a2f",
"metadata": {},
"source": [
"_______\n",
"\n",
"## References"
]
},
{
"cell_type": "markdown",
"id": "62dd936d",
"metadata": {},
"source": [
"[1] Khatri, Sumeet, et al. \"Quantum-assisted quantum compiling.\" [Quantum 3 (2019): 140](https://quantum-journal.org/papers/q-2019-05-13-140/)."
]
}
],
"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": "4b61fc87",
"metadata": {},
"source": [
"# 哈密顿量的构造\n",
"*Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.*"
]
},
{
"cell_type": "markdown",
"id": "52532fe2",
"metadata": {},
"source": [
"## 概览\n",
"\n",
"在本教程中,我们将展示如何使用 Paddle Quantum 的 `qchem` 模块通过化学分子式来构造适用于量子计算机的哈密顿量。我们将一步一步地学习如何从一个分子结构构造二次量子化的哈密顿量,以及如何将它转换成一组泡利矩阵。 \n",
"\n",
"哈密顿量是一个与物理系统总能量有关的物理量。一般来说,它可以表示为 \n",
"\n",
"$$\n",
"\\hat{H}=\\hat{T}+\\hat{V},\\tag{1}\n",
"$$\n",
"\n",
"其中 $\\hat{T}$ 代表动能,$\\hat{V}$ 代表势能。哈密顿量在变分量子算法中有着重要应用,如比[变分量子本征求解器](./VQE_CN.ipynb) 以及 [利用 Product Formula 模拟时间演化](./HamiltonianSimulation_CN.ipynb)。\n",
"\n",
"当我们用量子力学来解决化学问题时,需要写出一个哈密顿量来描述这个问题涉及的化学系统。我们可以根据这个哈密顿量计算该系统的基态和激发态,并利用这些信息进一步探索量子系统的所有物理性质。涉及电子结构问题的哈密顿量可以写成如下形式:\n",
"\n",
"$$\n",
"\\hat{H}=\\sum_{i=1}^N\\left(-\\frac{1}{2}\\nabla_{x_i}^2\\right)+\\sum_{i=1}^N\\sum_{j< i}\\frac{1}{|x_i-x_j|}-\\sum_{i=1}^N\\sum_{I=1}^M\\frac{Z_I}{|x_i-R_I|},\\tag{2}\n",
"$$\n",
"\n",
"该公式使用了[原子单位](https://en.wikipedia.org/wiki/Hartree_atomic_units)。该公式包含了 $N$ 个电子和 $M$ 个原子核,其中 $x_i$ 是第 $i$ 个电子的位置,$R_I$ 是第 $I$ 个原子核的位置。\n",
"\n",
"本教程分为四部分,首先我们先来讨论如何使用 `qchem` 模块构造一个分子。之后,我们将简要描述如何通过调用外部量子化学来计算\n",
"[Hartree Fock](https://en.wikipedia.org/wiki/Hartree%E2%80%93Fock_method)\n",
"单粒子轨道。接下来,我们展示如何得到二次量子化的哈密顿量。最后,我们将描述如何将费米子的哈密顿量 (Fermionic Hamiltonian) 转化为适用于量子计算机的泡利字符串 (Pauli strings)。 "
]
},
{
"cell_type": "markdown",
"id": "3160994c",
"metadata": {},
"source": [
"## 水分子的结构\n",
"\n",
"在该部分,我们将展示如何从水的分子式和原子坐标构造出水分子。\n",
"\n",
"![h2o.png](figures/buildingmolecule-fig-h2o.png)\n",
"\n",
"在量桨中,我们将分子写成一个列表的形式,第一个元素是原子符号,第二个元素是它的位置 (Cartesian coordinate)。因此,用于表述分子的列表是由原子列表组成的。\n",
"\n",
"**注意:关于环境设置,请参考 [README_CN.md](https://github.com/PaddlePaddle/Quantum/blob/master/README_CN.md).**"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "e6787ece",
"metadata": {},
"outputs": [],
"source": [
"# Eliminate noisy python warnings\n",
"import warnings\n",
"\n",
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "94b2032c",
"metadata": {},
"outputs": [],
"source": [
"# in Angstrom\n",
"h2o_structure_direct = [[\"H\", [-0.02111417,0.8350417,1.47688078]], # H 代表着水分子中的氢元素\n",
" [\"O\", [0.0, 0.0, 0.0]], # O 代表着水分子中的氧元素\n",
" [\"H\", [-0.00201087,0.45191737,-0.27300254]]]"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "d354c5dd",
"metadata": {},
"outputs": [],
"source": [
"from paddle_quantum.qchem import geometry\n",
"\n",
"h2o_structure_xyz = geometry(file=\"h2o.xyz\")\n",
"assert h2o_structure_xyz == h2o_structure_direct"
]
},
{
"cell_type": "markdown",
"id": "e996795a",
"metadata": {},
"source": [
"## Hartree Fock 轨道的计算\n",
"\n",
"Hartree Fock 方法使用 [Slater 行列式](https://en.wikipedia.org/wiki/Slater_determinant)来表示 $N$ 电子波函数。它可以为我们提供一套单粒子轨道,这些轨道通常被作为其他量子化学方法的输入。 \n",
"\n",
"量桨使用 psi4 [1] 作为它的量子化学引擎。我们可以使用 `qchem` 模块提供的 `get_molecular_data` 函数来执行相关计算,得到分子的所需信息。 `get_molecular_data` 函数以分子结构、分子总电荷和自旋多重性为主要输入,并返回一个 OpenFermion [2] `MolecularData` 对象。 \n",
"\n",
"我们继续以水分子为例。为了运行 Hartree Fock 计算,我们需要将 `method` 设置为 *scf* (Self - Consistent Field)。 我们还可以通过在 `basis` 参数中指定 [basis set](https://en.wikipedia.org/wiki/Basis_set_(chemistry)) 的类型来提高 Hartree Fock 计算的精度。"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "29945676",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Hartree-Fock energy for H2-O1_sto-3g_singlet (10 electrons) is -73.96770387867429.\n"
]
}
],
"source": [
"from paddle_quantum.qchem import get_molecular_data\n",
"\n",
"h2o_moledata = get_molecular_data(\n",
" h2o_structure_direct,\n",
" charge=0, # 水分子是中性的,不带电\n",
" multiplicity=1, # 水分子只有一个未配对电子\n",
" basis=\"sto-3g\",\n",
" method=\"scf\",\n",
" if_save=True, # 是否将 MolecularData 中的信息存储成 hdf5 文件\n",
" if_print=True, # 是否需要打印出水分子的基态能量\n",
" name=\"\", # 指定 hdf5 文件的名字\n",
" file_path=\".\" # 指定 hdf5 文件的路径 \n",
")\n",
"\n",
"from openfermion.chem import MolecularData\n",
"\n",
"assert isinstance(h2o_moledata, MolecularData)"
]
},
{
"cell_type": "markdown",
"id": "973af7d1",
"metadata": {},
"source": [
"## 哈密顿量的二次量子化形式\n",
"\n",
"当我们研究电子系统时,通常会将哈密顿量写成[二次量子化](https://en.wikipedia.org/wiki/Second_quantization)的形式\n",
"\n",
"$$\n",
"\\hat{H}=\\sum_{p,q}h_{pq}\\hat{c}^{\\dagger}_p\\hat{c}_q+\\frac{1}{2}\\sum_{p,q,r,s}v_{pqrs}\\hat{c}^{\\dagger}_p\\hat{c}^{\\dagger}_q\\hat{c}_r\\hat{c}_s,\\tag{3}$$\n",
"\n",
"其中 $p$, $q$, $r$ 和 $s$ 是 Hartree Fock 轨道,$\\hat{c}^{\\dagger}_p$ 和 $\\hat{c}_q$ 分别是生成算子 (creation operation) 和湮灭算子 (annihilation operation)。系数 $h_{pq}$ 和 $v_{pqrs}$ 分别是单体积分 (one-body integrations) 和双体积分 (two-body integrations),该信息可以用如下的方式从 `MolecularData` 提取出来。"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "a9a906ad",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[-3.2911e+01 5.5623e-01 2.8755e-01 9.7627e-16 -7.4568e-02 -9.4552e-02 2.8670e-01]\n",
" [ 5.5623e-01 -8.0729e+00 -4.0904e-02 4.2578e-16 1.7890e-01 3.5048e-01 -1.3460e+00]\n",
" [ 2.8755e-01 -4.0904e-02 -7.3355e+00 1.1465e-16 4.1911e-01 5.2109e-01 7.0928e-01]\n",
" [ 9.7627e-16 4.2578e-16 1.1465e-16 -7.5108e+00 4.1730e-15 8.3317e-15 -8.4993e-16]\n",
" [-7.4568e-02 1.7890e-01 4.1911e-01 4.1730e-15 -5.7849e+00 2.0887e+00 1.2427e-01]\n",
" [-9.4552e-02 3.5048e-01 5.2109e-01 8.3317e-15 2.0887e+00 -5.0803e+00 1.3967e-02]\n",
" [ 2.8670e-01 -1.3460e+00 7.0928e-01 -8.4993e-16 1.2427e-01 1.3967e-02 -5.0218e+00]]\n"
]
}
],
"source": [
"import numpy as np \n",
"np.set_printoptions(precision=4, linewidth=150)\n",
"\n",
"hpq, vpqrs = h2o_moledata.get_integrals()\n",
"assert np.shape(hpq)==(7, 7) # When use sto3g basis, the total number of molecular orbitals used in water calculation is 7\n",
"assert np.shape(vpqrs)==(7, 7, 7, 7)\n",
"\n",
"print(hpq)\n",
"# print(vpqrs)"
]
},
{
"cell_type": "markdown",
"id": "de4f1bf2",
"metadata": {},
"source": [
"大多数情况下,我们并不需要把这些积分信息手动的提取出来,*qchem* 模块已经将该步骤融入函数 `fermionic_hamiltonian` 中,我们可以直接进行下一步的运算,获得哈密顿量。"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "069cfcd5",
"metadata": {},
"outputs": [],
"source": [
"from paddle_quantum.qchem import fermionic_hamiltonian\n",
"\n",
"H_of_water = fermionic_hamiltonian(\n",
" h2o_moledata,\n",
" multiplicity=1,\n",
" active_electrons=4,\n",
" active_orbitals=4\n",
")\n",
"\n",
"from openfermion.ops import FermionOperator\n",
"\n",
"assert isinstance(H_of_water, FermionOperator)"
]
},
{
"cell_type": "markdown",
"id": "47f24b4b",
"metadata": {},
"source": [
"通过指定 `active_electrons` 和 `active_orbitals`,我们可以限制哈密顿的自由度,从而可以减少哈密顿量的的项数,加速运算。我们还可以使用 *qchem* 中 `active_space` 函数来查看哪些轨道是**核心 (core)** 轨道,哪些轨道是**活跃 (active)** 轨道。"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "c136344d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"List of core orbitals: [0, 1, 2]\n",
"List of active orbitals: [3, 4, 5, 6]\n"
]
}
],
"source": [
"from paddle_quantum.qchem import active_space\n",
"\n",
"core_orbits_list, act_orbits_list = active_space(\n",
" 10, # number of electrons in water molecule\n",
" 7, # number of molecular orbitals in water molecule\n",
" active_electrons=4,\n",
" active_orbitals=4\n",
")\n",
"\n",
"print(\"List of core orbitals: {:}\".format(core_orbits_list))\n",
"print(\"List of active orbitals: {:}\".format(act_orbits_list))"
]
},
{
"cell_type": "markdown",
"id": "05b6aefc",
"metadata": {},
"source": [
"## 从费米子哈密顿量到自旋哈密顿量\n",
"\n",
"在量子计算中,我们只能使用由泡利矩阵构成的算符(operator)\n",
"\n",
"$$\n",
"\\boldsymbol{\\sigma}_x=\\begin{pmatrix}\n",
"0 & 1\\\\\n",
"1 & 0\n",
"\\end{pmatrix},\\quad \\boldsymbol{\\sigma}_y=\\begin{pmatrix}\n",
"0 & -i\\\\\n",
"i & 0\n",
"\\end{pmatrix},\\quad \\boldsymbol{\\sigma}_z=\\begin{pmatrix}\n",
"1 & 0\\\\\n",
"0 & -1\n",
"\\end{pmatrix}.\\tag{4}\n",
"$$\n",
"\n",
"因此,我们需要将现有的哈密顿量(二次量子化形式)变换成量子比特算符 (qubit operator),这种变换被称为 [Jordan-Wigner 变换](https://en.wikipedia.org/wiki/Jordan%E2%80%93Wigner_transformation)。\n",
"\n",
"> 此外,Bravyi-Kitaev 变换也可以达到相同的效果,只需要将参数 mapping_method 改成 'bravyi_kitaev' 即可。\n",
"\n",
"在量桨中,我们提供 `spin_hamiltonian` 函数来实现从费米子哈密顿量到自旋哈密顿量的变换,代码如下:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "7a831c80",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"There are 193 terms in H2O Hamiltonian in total.\n",
"The first 10 terms are \n",
" -72.10615980544183 I\n",
"-0.007310917992546845 X0, X1, Y2, Y3\n",
"0.005246087073083395 X0, X1, Y2, Z3, Z4, Y5\n",
"0.0016283548447088131 X0, X1, Y2, Z3, Z4, Z5, Z6, Y7\n",
"0.005246087073083395 X0, X1, X3, X4\n",
"0.0016283548447088131 X0, X1, X3, Z4, Z5, X6\n",
"-0.005994544380559041 X0, X1, Y4, Y5\n",
"0.0013876441781026563 X0, X1, Y4, Z5, Z6, Y7\n",
"0.001387644178102656 X0, X1, X5, X6\n",
"-0.009538223793221256 X0, X1, Y6, Y7\n"
]
}
],
"source": [
"from paddle_quantum.qchem import spin_hamiltonian\n",
"\n",
"pauli_H_of_water_ = spin_hamiltonian(\n",
" h2o_moledata,\n",
" multiplicity=1,\n",
" active_electrons=4,\n",
" active_orbitals=4,\n",
" mapping_method='jordan_wigner'\n",
")\n",
"\n",
"print('There are', pauli_H_of_water_.n_terms, 'terms in H2O Hamiltonian in total.')\n",
"print('The first 10 terms are \\n', pauli_H_of_water_[:10])"
]
},
{
"cell_type": "markdown",
"id": "f5af604d",
"metadata": {},
"source": [
"现在,我们知道了如何从分子结构构造出相应的哈密顿量,接下来不妨去看一看如何使用[变分量子本征求解器](./VQE_CN.ipynb) (VQE)去计算氢分子的基态能量。"
]
},
{
"cell_type": "markdown",
"id": "d7417802",
"metadata": {},
"source": [
"---\n",
"## 参考文献\n",
"\n",
"[1] [Psi4: an open-source ab initio electronic structure program](https://wires.onlinelibrary.wiley.com/doi/abs/10.1002/wcms.93)\n",
"\n",
"[2] [OpenFermion: the electronic structure package for quantum computers\n",
"](https://iopscience.iop.org/article/10.1088/2058-9565/ab8ebc)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
{
"cells": [
{
"cell_type": "markdown",
"source": [
"# Building Molecular Hamiltonian\n",
"*Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.*"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## Overview\n",
"\n",
"In this tutorial, we will demonstrate how to use Paddle Quantum's `qchem` module to build valid Hamiltonian for simulating chemical molecules on a quantum computer. We will go step by step how to build the second quantized Hamiltonian from a molecular structure and how to transform it to a set of Pauli matrices. \n",
"\n",
"Hamiltonian is a physical quantity related to the total energy of a physical system. In general, it can be represented as \n",
"\n",
"$$\n",
"\\hat{H}=\\hat{T}+\\hat{V},\\tag{1}\n",
"$$\n",
"\n",
"where $\\hat{T}$ is the kinetic energy and $\\hat{V}$ is the potential energy. Hamiltonian is useful for various quantum algorithms, such as [variational quantum eigensolver](./VQE_EN.ipynb) and [Hamiltonian Simulation with Product Formula](./HamiltonianSimulation_EN.ipynb).\n",
"\n",
"When trying to solve a chemistry problem with quantum mechanics, we also need to write down a Hamiltonian that describes the chemical system involved in the problem. Starting from this Hamiltonian, we can, in principle, calculate the ground state and excited states, and use the information to further explore all the physical properties of the quantum system. The dominant Hamiltonian of electronic problems has the form\n",
"\n",
"$$\n",
"\\hat{H}=\\sum_{i=1}^N\\left(-\\frac{1}{2}\\nabla_{x_i}^2\\right)+\\sum_{i=1}^N\\sum_{j< i}\\frac{1}{|x_i-x_j|}-\\sum_{i=1}^N\\sum_{I=1}^M\\frac{Z_I}{|x_i-R_I|},\\tag{2}\n",
"$$\n",
"\n",
"when we use [atomic units](https://en.wikipedia.org/wiki/Hartree_atomic_units). Our electronic problem contains $N$ electrons and $M$ nucleus. We use $x_i$ to denote position of the $i$-th electron, and use $R_I$ to denote position of the $I$-th nuclei. \n",
"\n",
"This tutorial will have the following parts. Let's first talk about how to construct a molecule in `qchem`. After that, we will briefly describe how to calculate [Hartree Fock](https://en.wikipedia.org/wiki/Hartree%E2%80%93Fock_method) single particle orbitals by calling external quantum chemistry within Paddle Quantum. Next, we show how we can obtain the Hamiltonian in second quantization representation. Finally, we describe how to transform the Fermionic Hamiltonian to Pauli strings recognized by quantum computer."
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## Defining the molecular structure\n",
"In this example, we show how to construct water molecule from its chemical formula and coordinates of atoms. \n",
"\n",
"![h2o.png](figures/buildingmolecule-fig-h2o.png)\n",
"\n",
"Within Paddle Quantum, we specify the atom as a list whose first element is the atomic symbol and the second element is another list that contains its Cartesian coordinate. The molecule is thus a bigger list composed of atoms' list.\n",
"\n",
"**Note: As to the environment setting, please refer to [README.md](https://github.com/PaddlePaddle/Quantum/blob/master/README.md).**"
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 1,
"source": [
"# Eliminate noisy python warnings\n",
"import warnings\n",
"\n",
"warnings.filterwarnings(\"ignore\")"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 2,
"source": [
"# in Angstrom\n",
"h2o_structure_direct = [[\"H\", [-0.02111417,0.8350417,1.47688078]], # H stands for hydrogen element in water\n",
" [\"O\", [0.0, 0.0, 0.0]], # O stands for oxygen element in water\n",
" [\"H\", [-0.00201087,0.45191737,-0.27300254]]]"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"Instead of specifying molecular structure directly, we can also pass the \\*.xyz file to the `geometry` function to get the same structure."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 3,
"source": [
"from paddle_quantum.qchem import geometry\n",
"\n",
"h2o_structure_xyz = geometry(file=\"h2o.xyz\")\n",
"assert h2o_structure_xyz == h2o_structure_direct"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## Calculate Hartree Fock orbitals\n",
"Hartree Fock method uses the [Slater determinant](https://en.wikipedia.org/wiki/Slater_determinant) to represent the $N$-electron wavefunction. It could provide us with a set of single particle orbitals which are often taken as input to more advanced quantum chemistry methods. \n",
"\n",
"Paddle Quantum uses psi4 [1] as its quantum chemistry engine. We could use the `get_molecular_data` function provided in `qchem` module to manage the quantum chemistry calculation and get the necessary information about the molecule. `get_molecular_data` function takes molecular structure, total molecular charge, and spin multiplicity as its major inputs, it will return an OpenFermion [2] `MolecularData` object. \n",
"\n",
"Let's continue with our water molecule example. To run the Hartree Fock calculation, we need to set the `method` keyword argument to *scf* (Self Consistent Field). We can also improve the quality of Hartree Fock calculation by specifying the type of [basis set](https://en.wikipedia.org/wiki/Basis_set_(chemistry)) in the `basis` argument. "
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 4,
"source": [
"from paddle_quantum.qchem import get_molecular_data\n",
"\n",
"h2o_moledata = get_molecular_data(\n",
" h2o_structure_direct,\n",
" charge=0, # Water molecule is charge neutral\n",
" multiplicity=1, # In the ground state, the lowest 5 molecular orbitals of water molecular will be occupied by a pair of electrons with opposite spin\n",
" basis=\"sto-3g\",\n",
" method=\"scf\",\n",
" if_save=True, # Whether to save information contained in MolecularData object to a hdf5 file\n",
" if_print=True, # Wheter to print the ground state energy of water molecule\n",
" name=\"\", # Specifies the name of the hdf5 file\n",
" file_path=\".\" # Specifies where to store the hdf5 file \n",
")\n",
"\n",
"from openfermion.chem import MolecularData\n",
"\n",
"assert isinstance(h2o_moledata, MolecularData)"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Hartree-Fock energy for H2-O1_sto-3g_singlet (10 electrons) is -73.96770387867429.\n"
]
}
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## Molecular Hamiltonian in second quantization form\n",
"When we study many electron quantum systems, it's often convenient to write Hamiltonian at the beginning of this tutorial in [second quantization](https://en.wikipedia.org/wiki/Second_quantization) representation \n",
"\n",
"$$\n",
"\\hat{H}=\\sum_{p,q}h_{pq}\\hat{c}^{\\dagger}_p\\hat{c}_q+\\frac{1}{2}\\sum_{p,q,r,s}v_{pqrs}\\hat{c}^{\\dagger}_p\\hat{c}^{\\dagger}_q\\hat{c}_r\\hat{c}_s,\\tag{3}$$\n",
"\n",
"where $p$, $q$, $r$ and $s$ are Hartree Fock orbitals computed in the previous section. $\\hat{c}^{\\dagger}_p$ and $\\hat{c}_q$ are creation and annihilation operations, respectively. The two coefficients $h_{pq}$ and $v_{pqrs}$ are called molecular integrals, and can be obtained from `MolecularData` object in the following way."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 5,
"source": [
"import numpy as np \n",
"np.set_printoptions(precision=4, linewidth=150)\n",
"\n",
"hpq, vpqrs = h2o_moledata.get_integrals()\n",
"assert np.shape(hpq)==(7, 7) # When use sto3g basis, the total number of molecular orbitals used in water calculation is 7\n",
"assert np.shape(vpqrs)==(7, 7, 7, 7)\n",
"\n",
"print(hpq)\n",
"# print(vpqrs)"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"[[-3.2911e+01 5.5623e-01 2.8755e-01 9.7627e-16 -7.4568e-02 -9.4552e-02 2.8670e-01]\n",
" [ 5.5623e-01 -8.0729e+00 -4.0904e-02 4.2578e-16 1.7890e-01 3.5048e-01 -1.3460e+00]\n",
" [ 2.8755e-01 -4.0904e-02 -7.3355e+00 1.1465e-16 4.1911e-01 5.2109e-01 7.0928e-01]\n",
" [ 9.7627e-16 4.2578e-16 1.1465e-16 -7.5108e+00 4.1730e-15 8.3317e-15 -8.4993e-16]\n",
" [-7.4568e-02 1.7890e-01 4.1911e-01 4.1730e-15 -5.7849e+00 2.0887e+00 1.2427e-01]\n",
" [-9.4552e-02 3.5048e-01 5.2109e-01 8.3317e-15 2.0887e+00 -5.0803e+00 1.3967e-02]\n",
" [ 2.8670e-01 -1.3460e+00 7.0928e-01 -8.4993e-16 1.2427e-01 1.3967e-02 -5.0218e+00]]\n"
]
}
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"Most of the time, we don't need to extract those integrals and assemble the Hamiltonian manually, *qchem* module has already helped us take care of this by providing the `fermionic_hamiltonian` function."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 6,
"source": [
"from paddle_quantum.qchem import fermionic_hamiltonian\n",
"\n",
"H_of_water = fermionic_hamiltonian(\n",
" h2o_moledata,\n",
" multiplicity=1,\n",
" active_electrons=4,\n",
" active_orbitals=4\n",
")\n",
"\n",
"from openfermion.ops import FermionOperator\n",
"\n",
"assert isinstance(H_of_water, FermionOperator)"
],
"outputs": [],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"By specifying `active_electrons` and `active_orbitals` keyword arguments, we can reduce the number of freedom of our Hamiltonian and thus reduce the number of terms in the spin Hamiltonian described in the next section. We can also use `active_space` function in *qchem* to return a list of *core* orbitals and *active* orbitals. "
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 7,
"source": [
"from paddle_quantum.qchem import active_space\n",
"\n",
"core_orbits_list, act_orbits_list = active_space(\n",
" 10, # number of electrons in water molecule\n",
" 7, # number of molecular orbitals in water molecule\n",
" active_electrons=4,\n",
" active_orbitals=4\n",
")\n",
"\n",
"print(\"List of core orbitals: {:}\".format(core_orbits_list))\n",
"print(\"List of active orbitals: {:}\".format(act_orbits_list))"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"List of core orbitals: [0, 1, 2]\n",
"List of active orbitals: [3, 4, 5, 6]\n"
]
}
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"## From Fermionic Hamiltonian to spin Hamiltonian\n",
"In quantum computing, we only have qubit operators composed of Pauli matrices\n",
"\n",
"$$\n",
"\\boldsymbol{\\sigma}_x=\\begin{pmatrix}\n",
"0 & 1\\\\\n",
"1 & 0\n",
"\\end{pmatrix},\\quad \\boldsymbol{\\sigma}_y=\\begin{pmatrix}\n",
"0 & -i\\\\\n",
"i & 0\n",
"\\end{pmatrix},\\quad \\boldsymbol{\\sigma}_z=\\begin{pmatrix}\n",
"1 & 0\\\\\n",
"0 & -1\n",
"\\end{pmatrix}.\\tag{4}\n",
"$$\n",
"\n",
"Therefore, we need to transform our Hamiltonian in the previous section to qubit operators, [Jordan-Wigner transform](https://en.wikipedia.org/wiki/Jordan%E2%80%93Wigner_transformation) is one of the well-known methods to realize the transformation.\n",
"> Alternatively, we also provide Bravyi-Kitaev transformation, by changing the argument, mapping_method, to 'bravyi_kitaev'.\n",
"\n",
"In *paddle quantum*, Hamiltonian is encoded in *pauli_str*. To avoid tedious manipulation of *string* object, we have provided `spin_hamiltonian` function which can generate the needed *pauli_str* from molecular structure on the fly."
],
"metadata": {}
},
{
"cell_type": "code",
"execution_count": 9,
"source": [
"from paddle_quantum.qchem import spin_hamiltonian\n",
"\n",
"pauli_H_of_water_ = spin_hamiltonian(\n",
" h2o_moledata,\n",
" multiplicity=1,\n",
" active_electrons=4,\n",
" active_orbitals=4,\n",
" mapping_method='jordan_wigner'\n",
")\n",
"\n",
"print('There are ', pauli_H_of_water_.n_terms, 'terms in H2O Hamiltonian in total.')\n",
"print('The first 10 terms are \\n', pauli_H_of_water_[:10])"
],
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"There are 193 terms in H2O Hamiltonian in total.\n",
"The first 10 terms are \n",
" -72.10615980544183 I\n",
"-0.007310917992546845 X0, X1, Y2, Y3\n",
"0.005246087073083395 X0, X1, Y2, Z3, Z4, Y5\n",
"0.0016283548447088131 X0, X1, Y2, Z3, Z4, Z5, Z6, Y7\n",
"0.005246087073083395 X0, X1, X3, X4\n",
"0.0016283548447088131 X0, X1, X3, Z4, Z5, X6\n",
"-0.005994544380559041 X0, X1, Y4, Y5\n",
"0.0013876441781026563 X0, X1, Y4, Z5, Z6, Y7\n",
"0.001387644178102656 X0, X1, X5, X6\n",
"-0.009538223793221256 X0, X1, Y6, Y7\n"
]
}
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"Great! Now you know how to build a proper Hamiltonian from a given molecular structure, let's move further and see how to use [variational quantum eigensolver](./VQE_EN.ipynb) (VQE) to determine the ground state of hydrogen molecule.\n"
],
"metadata": {}
},
{
"cell_type": "markdown",
"source": [
"---\n",
"## References\n",
"\n",
"[1] [Psi4: an open-source ab initio electronic structure program](https://wires.onlinelibrary.wiley.com/doi/abs/10.1002/wcms.93)\n",
"\n",
"[2] [OpenFermion: the electronic structure package for quantum computers\n",
"](https://iopscience.iop.org/article/10.1088/2058-9565/ab8ebc)"
],
"metadata": {}
}
],
"metadata": {
"interpreter": {
"hash": "33c5d9dde9504794a92749aa5ec8a05187e008f4d7e74daf7ce0e98d5c6e004a"
},
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.0"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
\ No newline at end of file
{
"cells": [
{
"cell_type": "markdown",
"id": "4f560a55",
"metadata": {},
"source": [
"# 基于经典影子的量子态性质估计"
]
},
{
"cell_type": "markdown",
"id": "a894cbba",
"metadata": {},
"source": [
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
]
},
{
"cell_type": "markdown",
"id": "e4cdaa03",
"metadata": {},
"source": [
"## 概览"
]
},
{
"cell_type": "markdown",
"id": "3b3ea48a",
"metadata": {},
"source": [
"在[未知量子态的经典影子](./ClassicalShadow_Intro_CN.ipynb)教程中,我们介绍了经典影子的一些理论知识并展示了如何构建一个量子态 $\\rho$ 的经典影子。根据经典影子的理论推导可以看出,它十分适合用来估计量子态线性的性质,目前其基本应用场景有如下四个:量子态的保真度估计、纠缠验证、局部可观测量期望的估计、全局可观测量期望的估计 [1]。其中可观测量期望值的估计广泛地出现在当前的量子算法中,例如用于估计复杂分子哈密顿量(Hamiltonian)基态能量的[变分量子本征求解器](./VQE_CN.ipynb)(variational quantum eigensolver, VQE)。接下来我们将重点讨论基于经典影子的可观测量期望值估计算法,并展示如何使用量桨中的 shadow 功能来进行可观测量期望值的估计。"
]
},
{
"cell_type": "markdown",
"id": "db8b954f",
"metadata": {},
"source": [
"## 可观测量期望值估计"
]
},
{
"cell_type": "markdown",
"id": "e2869ea5",
"metadata": {},
"source": [
"### 问题描述"
]
},
{
"cell_type": "markdown",
"id": "a6adea50",
"metadata": {},
"source": [
"在量子化学领域,核心任务之一是求解一个量子尺度上封闭物理系统的哈密顿量 $\\hat{H}$ 的基态能量及其对应的基态,主要的实现方法是通过在量子设备上准备一个参数化的试探波函数 $|\\Psi(\\theta)\\rangle$,然后结合经典机器学习中的优化算法(例如梯度下降法)去不断地调整、优化参数 $\\theta$ 使得期望值 $\\langle\\Psi(\\theta)|\\hat{H}| \\Psi(\\theta)\\rangle$ 最小化。这套方案的基本原理是基于 Rayleigh-Ritz 变分原理。\n",
"\n",
"$$\n",
"E_{0}=\\min _{\\theta}\\langle\\Psi(\\theta)|\\hat{H}| \\Psi(\\theta)\\rangle,\\tag{1}\n",
"$$\n",
"\n",
"其中 $E_{0}$ 表示该系统的基态能量。从数值分析的角度来看,该问题可以被理解为求解一个离散化哈密顿量 $\\hat{H}$ (埃尔米特矩阵)的最小本征值 $\\lambda_{\\min }$ 和其对应的本征向量 $\\left|\\Psi_{0}\\right\\rangle$ 。经典影子发挥作用的场景就是每一次优化中计算 $\\langle\\Psi(\\theta)|\\hat{H}| \\Psi(\\theta)\\rangle = \\operatorname{tr}(\\hat{H}\\rho )$ 的部分(其中 $ \\rho = | \\Psi(\\theta)\\rangle\\langle\\Psi(\\theta)| $)。\n",
"\n",
"于是问题转化为:对于一个 $n$ 个量子比特的量子态 $\\rho$ 和一个可写成一组泡利算子$\\{I,X,Y,Z\\}^{\\otimes n}$线性组合的可观测量(哈密顿量)$\\hat{H}$,\n",
"\n",
"$$\n",
"\\hat{H}=\\sum_{Q \\in\\{I, X, Y, Z\\} ^{\\otimes n}} \\alpha_{Q} Q \\quad \\text{where} \\quad \\alpha_{Q} \\in \\mathbb{R} ,\\tag{2}\n",
"$$\n",
"\n",
"如何用经典影子来估计可观测量期望值 $\\operatorname{tr}(\\hat{H}\\rho )$ ?\n",
"\n",
"最直观的方法是将哈密顿量的每一项分别作为测量基,对量子态 $\\rho$ 进行相应的泡利测量,并对每一项的测量进行一定次数的重复,再统计测量结果得到估计值。在这里,我们称此方法为逐项测量的方法。\n",
"\n",
"读者可以看到当哈密顿量 $\\hat{H}$ 项数较少,$n$ 也较小时,我们可以通过逐项测量的方法来得到 $\\operatorname{tr}(\\hat{H}\\rho )$,但当 $\\hat{H}$ 项数增多,且 $n$ 较大时,逐项测量的方法所需要的代价将大大增加。而将要介绍的基于经典影子的方法,可以用更少的代价得到同等精度的 $\\operatorname{tr}(\\hat{H}\\rho )$ 的估计。"
]
},
{
"cell_type": "markdown",
"id": "ff8af318",
"metadata": {},
"source": [
"### 基于经典影子的改进算法"
]
},
{
"cell_type": "markdown",
"id": "a3db3328",
"metadata": {},
"source": [
"在经典影子的构建中,一个关键的步骤是从固定集合中均匀随机采样酉变换,[未知量子态的经典影子](./ClassicalShadow_Intro_CN.ipynb)教程展示了所选集合为 Clifford 群时的情况。当所选的集合是作用在单量子比特上的 Clifford 群时,构建时的采样与测量步骤就相当于对量子态做泡利测量。量桨中提供了使用随机泡利测量的经典影子算法(Classical shadows using random Pauli measurements,CS)。简单来说,在 CS 算法中,我们重复地为每个量子比特均匀随机选择一个泡利基来测量量子态 $\\rho$,根据测量结果估计可观测量期望值,具体的原理,读者可参考 [1-2] 学习。进一步地,同样是选取泡利测量基,当选取的方式不再是均匀随机时,基于经典影子的改进算法被先后提出 [2-3]。量桨中也提供了相关的算法功能:局部偏置的经典影子算法(Locally-biased classical shadows,LBCS)[2],自适应泡利影子算法(Adaptive Pauli shadows,APS)[3]。感兴趣的读者可以参考 [1-3] 来详细学习这些算法。"
]
},
{
"cell_type": "markdown",
"id": "1b772de7",
"metadata": {},
"source": [
"## Paddle Quantum 代码实现"
]
},
{
"cell_type": "markdown",
"id": "e094e752",
"metadata": {},
"source": [
"在量桨中,我们基于经典影子提供了 shadow 功能,主要包含两个函数,支持用户使用上述基于经典影子的三种算法来估计可观测量的期望值,以及获取未知量子态的经典影子数据。下面我们将展示如何基于量桨中的 shadow 功能来实现氢分子($H_{2}$)和氢化锂($LiH$)基态能量估计。"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "efc769b4",
"metadata": {
"scrolled": false
},
"outputs": [],
"source": [
"# 导入需要的包\n",
"import numpy as np\n",
"from numpy import pi as PI\n",
"import paddle\n",
"from paddle_quantum.circuit import UAnsatz\n",
"from paddle_quantum.VQE.chemistrysub import H2_generator\n",
"from paddle_quantum.utils import Hamiltonian"
]
},
{
"cell_type": "markdown",
"id": "a58e8871",
"metadata": {},
"source": [
"### 估计氢分子($H_{2}$)基态能量"
]
},
{
"cell_type": "markdown",
"id": "ebda5aeb",
"metadata": {},
"source": [
"导入拥有 4 个量子比特的氢分子($H_{2}$)的哈密顿量(用户具体可以参考[变分量子本征求解器](./VQE_CN.ipynb) 教程,来获得氢分子($H_{2}$)哈密顿量)。"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "70b8fdef",
"metadata": {
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"H2 hamiltonian = -0.04207897647782277 I0\n",
"0.17771287465139946 Z0\n",
"0.1777128746513994 Z1\n",
"-0.2427428051314046 Z2\n",
"-0.24274280513140462 Z3\n",
"0.17059738328801055 Z0, Z1\n",
"0.04475014401535163 Y0, X1, X2, Y3\n",
"-0.04475014401535163 Y0, Y1, X2, X3\n",
"-0.04475014401535163 X0, X1, Y2, Y3\n",
"0.04475014401535163 X0, Y1, Y2, X3\n",
"0.12293305056183797 Z0, Z2\n",
"0.1676831945771896 Z0, Z3\n",
"0.1676831945771896 Z1, Z2\n",
"0.12293305056183797 Z1, Z3\n",
"0.1762764080431959 Z2, Z3\n"
]
}
],
"source": [
"# 导入量桨中预处理好的氢分子哈密顿量\n",
"H2_pauli_str, H2_qubit = H2_generator()\n",
"# 根据 H2_pauli_str 创建哈密顿量类\n",
"H2_hamiltonian = Hamiltonian(H2_pauli_str)\n",
"print('H2 hamiltonian = ', H2_hamiltonian)"
]
},
{
"cell_type": "markdown",
"id": "0d6a6532",
"metadata": {},
"source": [
"为了展示如何用基于经典影子的算法来估计基态能量,我们首先通过量桨中的 VQE 来估计氢分子($H_{2}$)的基态。"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "1bc042c1",
"metadata": {},
"outputs": [],
"source": [
"def U_theta(theta, hamiltonian, N, D):\n",
" \"\"\"\n",
" Quantum Neural Network\n",
" \"\"\"\n",
" \n",
" # 按照量子比特数量/网络宽度初始化量子神经网络\n",
" cir = UAnsatz(N)\n",
" \n",
" # 内置的 {R_y + CNOT} 电路模板\n",
" cir.real_entangled_layer(theta[:D], D)\n",
" \n",
" # 铺上最后一列 R_y 旋转门\n",
" for i in range(N):\n",
" cir.ry(theta=theta[D][i][0], which_qubit=i)\n",
" \n",
" # 量子神经网络作用在默认的初始态 |0000> 上\n",
" cir.run_state_vector()\n",
" \n",
" # 计算给定哈密顿量的期望值\n",
" expectation_val = cir.expecval(hamiltonian)\n",
"\n",
" return expectation_val, cir\n",
"\n",
"class StateNet(paddle.nn.Layer):\n",
" \"\"\"\n",
" Construct the model net\n",
" \"\"\"\n",
"\n",
" def __init__(self, shape, dtype=\"float64\"):\n",
" super(StateNet, self).__init__()\n",
" \n",
" # 初始化 theta 参数列表,并用 [0, 2*pi] 的均匀分布来填充初始值\n",
" self.theta = self.create_parameter(shape=shape, \n",
" default_initializer=paddle.nn.initializer.Uniform(low=0.0, high=2*PI),\n",
" dtype=dtype, is_bias=False)\n",
" \n",
" # 定义损失函数和前向传播机制\n",
" def forward(self, hamiltonian, N, D):\n",
" \n",
" # 计算损失函数/期望值\n",
" loss, cir = U_theta(self.theta, hamiltonian, N, D)\n",
"\n",
" return loss, cir"
]
},
{
"cell_type": "markdown",
"id": "1c53b204",
"metadata": {},
"source": [
"在搭建了 VQE 算法的量子神经网络和定义了损失函数后,我们可以通过训练量子神经网络估计氢分子($H_{2}$)的基态能量,及基态所对应的量子电路,"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "40036ae4",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"iter: 20 loss: -1.0193\n",
"iter: 20 Ground state energy: -1.0193 Ha\n",
"iter: 40 loss: -1.1227\n",
"iter: 40 Ground state energy: -1.1227 Ha\n",
"iter: 60 loss: -1.1342\n",
"iter: 60 Ground state energy: -1.1342 Ha\n",
"iter: 80 loss: -1.1359\n",
"iter: 80 Ground state energy: -1.1359 Ha\n",
"\n",
"训练后的电路:\n",
"--Ry(6.288)----*--------------x----Ry(0.019)----*--------------x----Ry(3.133)--\n",
" | | | | \n",
"--Ry(3.143)----x----*---------|----Ry(3.337)----x----*---------|----Ry(3.139)--\n",
" | | | | \n",
"--Ry(6.278)---------x----*----|----Ry(6.287)---------x----*----|----Ry(3.140)--\n",
" | | | | \n",
"--Ry(3.147)--------------x----*----Ry(3.130)--------------x----*----Ry(6.277)--\n",
" \n"
]
}
],
"source": [
"ITR = 80 # 设置训练的总迭代次数\n",
"LR = 0.4 # 设置学习速率\n",
"D = 2 # 设置量子神经网络中重复计算模块的深度 Depth\n",
"N = H2_hamiltonian.n_qubits \n",
"\n",
"# 确定网络的参数维度\n",
"net = StateNet(shape=[D + 1, N, 1])\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(H2_hamiltonian, N, D)\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)"
]
},
{
"cell_type": "markdown",
"id": "a6c9d23f",
"metadata": {},
"source": [
"#### shadow 功能介绍"
]
},
{
"cell_type": "markdown",
"id": "c9891733",
"metadata": {},
"source": [
"此时,我们获得了生成氢分子($H_{2}$)的基态所对应的量子电路。在此电路上,我们可以直接运行 `shadow_trace` 函数,来获得使用经典影子算法估计得到的基态能量。\n",
"在 `shadow_trace` 函数中,我们的输入为需要估计的哈密顿量、采样次数、以及所选择使用的采样算法。用户可以通过指定 `method` 参数来选择想要使用的采样算法。其中 CS 的适用范围更广,速度最快,但是其估计精度可能稍差;LBCS 的精度更高,但在哈密顿量的项数较高时运行偏慢;APS 的精度也会更高,但是量子比特数目较大时运行偏慢。 "
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "3b31e57d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"H2 ground state energy = -1.135877449718475\n",
"H2 ground state energy CS= -1.161406335762599\n",
"H2 ground state energy LBCS= -1.113975649597432\n",
"H2 ground state energy APS= -1.1028385525105113\n"
]
}
],
"source": [
"# 估计出的基态对应的实际能量值\n",
"H2_energy = cir.expecval(H2_hamiltonian).numpy()[0]\n",
"\n",
"# 采样次数\n",
"sample = 1500 \n",
"# 分别采用三种算法估计可观测量期望值,即基态能量\n",
"H2_energy_CS = cir.shadow_trace(H2_hamiltonian, sample, method=\"CS\")\n",
"H2_energy_LBCS = cir.shadow_trace(H2_hamiltonian, sample, method=\"LBCS\")\n",
"H2_energy_APS = cir.shadow_trace(H2_hamiltonian, sample, method=\"APS\")\n",
"\n",
"print('H2 ground state energy = ', H2_energy)\n",
"print('H2 ground state energy CS= ', H2_energy_CS)\n",
"print('H2 ground state energy LBCS= ', H2_energy_LBCS)\n",
"print('H2 ground state energy APS= ', H2_energy_APS)"
]
},
{
"cell_type": "markdown",
"id": "1cb856da",
"metadata": {},
"source": [
"现在让我们采用对哈密顿量逐项测量的传统方法来估计氢分子($H_{2}$)的基态能量,"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "3ae0242c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"H2 ground state energy traditional = -1.1376572756490493\n"
]
}
],
"source": [
"# 使用哈密顿量逐项测量方法来估计基态能量\n",
"H2_energy_traditional = cir.expecval(H2_hamiltonian, shots=100).numpy()[0]\n",
"print('H2 ground state energy traditional = ', H2_energy_traditional)"
]
},
{
"cell_type": "markdown",
"id": "b2e1ad02",
"metadata": {},
"source": [
"我们可以看到在 1500 次采样下,三种算法估计的基态能量与 VQE 算法估计出的基态的实际能量已经十分接近,而逐项测量的方法是针对哈密顿量中的每一项各进行 100 次测量,氢分子($H_{2}$)哈密顿量有15项,相当于测量了 1500 次,得到的结果与 VQE 算法估计出的基态的实际能量的差距也较小。在这种小规模的情况,基于经典影子的算法与逐项测量的方法比并没有体现出明显的优势,但在大规模量子系统场景下,该类算法需要的采样次数仅仅是关于哈密顿量项数的常数级别的增长,逐项测量或已有的一些方法则需要关于哈密顿量项数呈多项式甚至指数级别增长的采样次数,来达到同样精度 [1]。事实上,[2] 中指出对于 CS 算法以及 LBCS 算法,我们得到的估计的平均误差 $\\epsilon$,方差 $\\operatorname{var}(\\nu)$ 以及采样次数 $S$ 有如下关系关系:\n",
"\n",
"$$\n",
"S = O(\\epsilon^{-2} \\operatorname{var}(\\nu) ), \\tag{3}\n",
"$$\n",
"\n",
"其中方差 $\\operatorname{var}(\\nu)$ 与采样次数相互独立,且方差与哈密顿量的项数有关。于是根据我们想要达到的精度(平均误差),可以计算出实际所需要的采样次数。同样,根据采样次数,也可以定义我们的平均误差:\n",
"\n",
"$$\n",
"\\epsilon = \\sqrt{\\frac{\\operatorname{var}}{S}}. \\tag{4}\n",
"$$\n",
"\n",
"可以看到我们上述的实验中,CS 算法和 LBCS 算法得到的误差,均在文献 [3] 理论估计的精度之内。"
]
},
{
"cell_type": "markdown",
"id": "5e0e174d",
"metadata": {},
"source": [
"同时,量桨提供了与经典影子有关的采样函数 `shadow_sample` ,支持对未知量子态的事先采样,方便读者探索经典影子的其他应用,具体使用方式如下:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "fc6b465c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[('yyxz', '1100'), ('xyxz', '1110'), ('zyyy', '1111'), ('zzxy', '1110'), ('xxyx', '1110'), ('zzyz', '1100'), ('yzyx', '0101'), ('yxxy', '0001'), ('zyzx', '1101'), ('xyyy', '1011')]\n"
]
}
],
"source": [
"from paddle_quantum.shadow import shadow_sample\n",
"\n",
"# 在态矢量模式下运行电路,获得当前电路输出态\n",
"H2_rho = np.array(cir.run_state_vector())\n",
"# 获得经典影子的数据,输出成 list 的形式\n",
"H2_sample_data_CS = shadow_sample(H2_rho, H2_qubit, sample_shots=10, mode='state_vector', \n",
" hamiltonian=H2_hamiltonian, method='CS')\n",
"print(H2_sample_data_CS)"
]
},
{
"cell_type": "markdown",
"id": "e74ffd18",
"metadata": {},
"source": [
"### 估计氢化锂($LiH$)基态能量"
]
},
{
"cell_type": "markdown",
"id": "e4480cb6",
"metadata": {},
"source": [
"接下来考虑氢化锂($LiH$)的基态能量,这里我们直接读取预先计算好的文件,以此来生成具有 12 个量子比特的氢化锂($LiH$)的泡利形式的分子哈密顿量。"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "6916c8a4",
"metadata": {},
"outputs": [],
"source": [
"with open('./LiH_hamiltonian.txt', 'r') as lih_file:\n",
" unprocessed_pauli_str = lih_file.read()\n",
" LiH_pauli_str = [term.split(maxsplit=1) for term in unprocessed_pauli_str.split('\\n')]\n",
" LiH_pauli_str = [[float(term[0]), term[1]] for term in LiH_pauli_str]\n",
" LiH_hamiltonian = Hamiltonian(LiH_pauli_str)"
]
},
{
"cell_type": "markdown",
"id": "61d8fd59",
"metadata": {},
"source": [
"接下来,我们同样可以通过运行 VQE 电路估计其分子哈密顿量的基态。这里,由于该分子哈密顿量较大,VQE 所需要的训练时间较长,我们直接提供了已经训练后的 VQE 电路的参数,用户可以通过它直接获得估计的 $LiH$ 的基态并在其上测试基于经典影子的方法。"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "fe326409",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"预训练 VQE 得到的基态能量为:-7.7720 \n"
]
}
],
"source": [
"# 读取事先训练好的参数\n",
"pretrained_parameters = paddle.load('LiH_VQE_parameters.pdtensor')\n",
"N = LiH_hamiltonian.n_qubits\n",
"# 根据该参数运行 VQE 电路\n",
"energy, cir = U_theta(pretrained_parameters, LiH_hamiltonian, N, D)\n",
"print('预训练 VQE 得到的基态能量为:%.4f ' % energy.numpy())"
]
},
{
"cell_type": "markdown",
"id": "8f8ff501",
"metadata": {},
"source": [
"得到了估计的氢化锂分子($LiH$)基态所对应的电路后,我们直接使用 `shadow_trace` 函数进行随机测量即可。同时,由于该分子哈密顿量有 631 项,为了保证两类方法测量次数一致,我们规定函数 `shadow_trace` 的 `sample = 1262`,函数 `expecval` 的 `shots = 2`。\n",
"\n",
"又因为 $LiH$ 基态的量子比特数为 12,所以对 $LiH$ 的基态做不同的泡利测量时,共有 $3^{12}$ 种可能的测量组合,那么仅仅进行 1262 次采样从而得到估值,具有随机性。于是,我们分别运行 20 次上述四种方法,取这 20 个样本数据的均值作为做为各个算法的估计值,并计算样本方差,对算法进行简单的比较。(运行下述代码块需要的时间可能较长,通常至少需要 1 个小时)"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "8f3684ce",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"LiH ground state energy = -7.771980394176657\n",
"ave LiH ground state energy CS = -7.835791570579005\n",
"ave LiH ground state energy LBCS = -7.7622296662623445\n",
"ave LiH ground state energy APS = -7.762836542787509\n",
"ave LiH ground state energy traditional = -7.8964746269601465\n",
"time = 4206.216086864471\n"
]
}
],
"source": [
"import time\n",
"\n",
"begin = time.time()\n",
"estimator_CS = []\n",
"estimator_LBCS = []\n",
"estimator_APS = []\n",
"estimator_traditional = []\n",
"\n",
"# 估计出的基态对应的实际能量值\n",
"LiH_energy = cir.expecval(LiH_hamiltonian).numpy()[0]\n",
"\n",
"# 运行算法次数\n",
"n = 20 \n",
"\n",
"for i in range(n):\n",
" LiH_energy_CS = cir.shadow_trace(LiH_hamiltonian, 1262, method=\"CS\")\n",
" LiH_energy_LBCS = cir.shadow_trace(LiH_hamiltonian, 1262, method=\"LBCS\")\n",
" LiH_energy_APS = cir.shadow_trace(LiH_hamiltonian, 1262, method=\"APS\")\n",
" LiH_energy_traditional = cir.expecval(LiH_hamiltonian, shots=2).numpy()[0]\n",
"\n",
" estimator_CS.append(LiH_energy_CS) \n",
" estimator_LBCS.append(LiH_energy_LBCS) \n",
" estimator_APS.append(LiH_energy_APS) \n",
" estimator_traditional.append(LiH_energy_traditional) \n",
"\n",
"ave_LiH_energy_CS = np.mean(estimator_CS)\n",
"ave_LiH_energy_LBCS = np.mean(estimator_LBCS)\n",
"ave_LiH_energy_APS = np.mean(estimator_APS)\n",
"ave_LiH_energy_traditional = np.mean(estimator_traditional)\n",
"end = time.time() \n",
"\n",
"print(\"LiH ground state energy = \", LiH_energy)\n",
"print(\"ave LiH ground state energy CS = \", ave_LiH_energy_CS)\n",
"print(\"ave LiH ground state energy LBCS = \", ave_LiH_energy_LBCS)\n",
"print(\"ave LiH ground state energy APS = \", ave_LiH_energy_APS)\n",
"print('ave LiH ground state energy traditional = ', ave_LiH_energy_traditional)\n",
"print('time = ', end-begin)"
]
},
{
"cell_type": "markdown",
"id": "89469273",
"metadata": {},
"source": [
"从结果来看,基于经典影子算法得到的均值比逐项测量的更接近 VQE 算法估计出的 $LiH$ 基态的实际能量,且算法的误差均在文献 [3] 理论估计的精度之内。那么各算法的样本方差又是怎样的呢?"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "742f4f15",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"LiH variance CS = 0.034596840359755784\n",
"LiH variance LBCS = 0.016602696085670984\n",
"LiH variance APS = 0.0016603026356630662\n",
"LiH variance traditional = 0.13200055652163223\n"
]
}
],
"source": [
"# 计算样本方差\n",
"\n",
"variance_CS = []\n",
"variance_LBCS = []\n",
"variance_APS = []\n",
"variance_traditional = []\n",
"\n",
"for i in range(n):\n",
" variance_CS.append((estimator_CS[i] - ave_LiH_energy_CS) ** 2)\n",
" variance_LBCS.append((estimator_LBCS[i] - ave_LiH_energy_LBCS) ** 2)\n",
" variance_APS.append((estimator_APS[i] - ave_LiH_energy_APS) ** 2)\n",
" variance_traditional.append((estimator_traditional[i] - ave_LiH_energy_traditional) ** 2)\n",
"\n",
"var_CS = sum(variance_CS)/(n-1)\n",
"var_LBCS = sum(variance_LBCS)/(n-1)\n",
"var_APS = sum(variance_APS)/(n-1)\n",
"var_traditional = sum(variance_traditional)/(n-1)\n",
"\n",
"print('LiH variance CS = ', var_CS)\n",
"print('LiH variance LBCS = ', var_LBCS)\n",
"print('LiH variance APS = ', var_APS)\n",
"print('LiH variance traditional = ', var_traditional)"
]
},
{
"cell_type": "markdown",
"id": "d40ef806",
"metadata": {},
"source": [
"可以看到,APS 算法的样本方差是最低的,其次是 LBCS 算法,接着是 CS 算法,最后是逐项测量的方法。据此,我们可以发现哈密顿量的项数规模增大后,基于经典影子的算法与逐项测量的方法相比,在同等代价下精度更高,且更加稳定。其中 APS 算法是最稳定的。\n",
"\n",
"值得一提的是,对于经典影子算法来说 12 个量子比特的场景仍不能较好地展现出其与现有一些算法相比的巨大优势。在具有更多量子比特的大规模系统中,其在算法代价上的优势才能更好地被展现 [1]。"
]
},
{
"cell_type": "markdown",
"id": "fa9e6a26",
"metadata": {},
"source": [
"## 总结"
]
},
{
"cell_type": "markdown",
"id": "91a60770",
"metadata": {},
"source": [
"本教程讨论了如何用基于经典影子的改进算法来得到可观测量期望值的估计,并展示了如何使用量桨中的 shadow 功能。可以看到,基于经典影子的改进算法可以对可观测量期望得到很好的估计。相比逐项测量的方法,在采样次数一致的情况下,它的估计值更精确,且算法更稳定。在大规模量子系统场景下,经典影子方法在一些问题中所需要的采样次数与系统大小无关,故对于系统大小来说仅仅是常数级别的增长 [1]。所以它在 NISQ(noisy intermediate-scale quantum)时代所能发挥的作用将继续地被不断挖掘。"
]
},
{
"cell_type": "markdown",
"id": "5fcdcad3",
"metadata": {},
"source": [
"_______\n",
"\n",
"## 参考文献"
]
},
{
"cell_type": "markdown",
"id": "947b52db",
"metadata": {},
"source": [
"[1] Huang, Hsin-yuan, R. Kueng and J. Preskill. “Predicting many properties of a quantum system from very few measurements.” [Nature Physics (2020): 1-8.](https://www.nature.com/articles/s41567-020-0932-7?proof=t)\n",
"\n",
"[2] Hadfield, Charles, et al. \"Measurements of quantum hamiltonians with locally-biased classical shadows.\" [arXiv preprint arXiv:2006.15788 (2020).](https://arxiv.org/abs/2006.15788)\n",
"\n",
"[3] Hadfield, Charles. \"Adaptive Pauli Shadows for Energy Estimation.\" [arXiv preprint arXiv:2105.12207 (2021).](https://arxiv.org/abs/2105.12207)"
]
}
],
"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.11"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
{
"cells": [
{
"cell_type": "markdown",
"id": "4f560a55",
"metadata": {},
"source": [
"# Estimation of Quantum State Properties Based on the Classical Shadow"
]
},
{
"cell_type": "markdown",
"id": "a894cbba",
"metadata": {},
"source": [
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
]
},
{
"cell_type": "markdown",
"id": "e4cdaa03",
"metadata": {},
"source": [
"## Overview"
]
},
{
"cell_type": "markdown",
"id": "3b3ea48a",
"metadata": {},
"source": [
"In [The Classical Shadow of Unknown Quantum States](./ClassicalShadow_Intro_EN.ipynb), we introduced some theoretical knowledge of the classical shadow and showed how to construct the classical shadow of a quantum state $\\rho$. According to the theoretical derivation of the classical shadow, it is very suitable for estimating the linear properties of quantum states. At present, its basic applications are: quantum fidelity estimation, entanglement verification, the estimation of local observables' expectation values, and the estimation of global observables' expectation values [1]. Among them, the estimation of observables' expectation values widely appears in current quantum algorithms, such as [Variational Quantum Eigensolver](./VQE_EN.ipynb) (VQE) for estimating the ground state energy of complex molecular Hamiltonian. Next, we will focus on the estimation of observables' expectation values algorithms based on the classical shadow and show how to use the shadow function in Paddle Quantum to do so."
]
},
{
"cell_type": "markdown",
"id": "db8b954f",
"metadata": {},
"source": [
"## Estimation of Observables' Expectation Values"
]
},
{
"cell_type": "markdown",
"id": "e2869ea5",
"metadata": {},
"source": [
"### Problem description"
]
},
{
"cell_type": "markdown",
"id": "a6adea50",
"metadata": {},
"source": [
"In the field of quantum chemistry, one of the core tasks is to solve for ground state energy of the Hamiltonian $\\hat{H}$ on a closed physical system on a quantum scale and its corresponding ground state. The main method is to prepare a parameterized trial wave function $|\\Psi(\\theta)\\rangle$. Then, the parameter $\\theta$ is being continuously adjusted and optimized to minimize the expected value $\\langle\\Psi(\\theta)|\\hat{H}| \\Psi(\\theta)\\rangle$ using classical optimization algorithms (such as gradient descent method). The principle of this scheme is based on Rayleigh-Ritz variational principle,\n",
"\n",
"$$\n",
"E_{0}=\\min _{\\theta}\\langle\\Psi(\\theta)|\\hat{H}| \\Psi(\\theta)\\rangle \\tag{1}\n",
"$$\n",
"\n",
"where $E_{0}$ represents the ground state energy of the system. Numerically, the problem can be understood as solving for the minimum eigenvalue $\\lambda_{\\min }$ of a discretized Hamiltonian $\\hat{H}$ (Hermitian matrix) and its corresponding eigenvector $\\left|\\Psi_{0}\\right\\rangle$. Where the classical shadow comes into play is to calculate the $\\langle\\Psi(\\theta)|\\hat{H}| \\Psi(\\theta)\\rangle = \\operatorname{tr}(\\hat{H}\\rho )$ part in every optimization iteration where $ \\rho = | \\Psi(\\theta)\\rangle\\langle\\Psi(\\theta)|$.\n",
"\n",
"The problem is then transformed into: for a quantum state $\\rho$ of $n$ qubits and an observable (Hamiltonian) $\\hat{H}$ that can be written as a linear combination of a set of Pauli operators $\\{I, X, Y, Z\\}^{\\otimes n}$,\n",
"\n",
"$$\n",
"\\hat{H}=\\sum_{Q \\in\\{I, X, Y, Z\\} ^{\\otimes n}} \\alpha_{Q} Q \\quad \\text{where} \\quad \\alpha_{Q} \\in \\mathbb{R}, \\tag{2}\n",
"$$\n",
"\n",
"how to estimate the observable's expectation value $\\operatorname{tr}(\\hat{H}\\rho )$ using the classical shadow?\n",
"\n",
"\n",
"The most intuitive method is to use each term of the Hamiltonian as a measurement base, and the corresponding Pauli measurements are made for the quantum state $\\rho$. A certain number of repetitions are made for each of the measurements, and then the measurement results are being processed to obtain the estimation value. Here we refer to this method as the item-by-item measurement method.\n",
"\n",
"Readers can see that when both $n$ and the number of terms of the Hamiltonian $\\hat{H}$ are small, we can get $\\operatorname{tr}(\\hat{H}\\rho )$ through the item-by-item measurement method. However, when $n$ is large and the number of terms of $\\hat{H}$ increases, the cost of the this method will increase significantly. The classical-shadow-based method that will be introduced can obtain the same precision estimation of $\\operatorname{tr}(\\hat{H}\\rho )$ with less cost."
]
},
{
"cell_type": "markdown",
"id": "ff8af318",
"metadata": {},
"source": [
"### The improved algorithm based on the classical shadow"
]
},
{
"cell_type": "markdown",
"id": "a3db3328",
"metadata": {},
"source": [
"When constructing the classical shadow, a critical step is to uniformly and randomly sample the unitary transformation from the fixed set. In [The Classical Shadow of Unknown Quantum States](./ClassicalShadow_Intro_EN.ipynb), we showed the case when the selected set is the Clifford group. When the selected set is a Clifford group on single qubit, the process of sampling and measurement is equivalent to making Pauli measurements on the quantum states. The classical shadow algorithm using random Pauli measurements (CS) is provided in Paddle Quantum. Briefly, in the CS algorithm, we repeatedly choose a Pauli basis for each qubit uniformly at random to measure the quantum state $\\rho$ and estimate the observable's expectation value based on the measurement results, and the reader may refer to [1-2] to learn the details of the principle. Further, when the Pauli measurement base is no longer selected uniformly and randomly, improved algorithms are proposed [2-3]. Relevant algorithm functions are also provided in Paddle Quantum: Locally-biased classical shadows (LBCS) [2], Adaptive Pauli shadows (APS) [3]. Readers can refer to [1-3] to learn these algorithms in detail."
]
},
{
"cell_type": "markdown",
"id": "1b772de7",
"metadata": {},
"source": [
"## Paddle Quantum Implementation"
]
},
{
"cell_type": "markdown",
"id": "e094e752",
"metadata": {},
"source": [
"In Paddle Quantum, we provide the shadow function, which mainly includes two parts to use the above three algorithms to estimate the expectation value of the observable and obtain the classical shadow data of unknown quantum states. Next, we will show how to implement finding the ground state energy estimation of $H_{2}$ and $LiH$ based on the shadow function in Paddle Quantum."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "efc769b4",
"metadata": {
"scrolled": false
},
"outputs": [],
"source": [
"# Import all the dependencies\n",
"import numpy as np\n",
"from numpy import pi as PI\n",
"import paddle\n",
"from paddle_quantum.circuit import UAnsatz\n",
"from paddle_quantum.VQE.chemistrysub import H2_generator\n",
"from paddle_quantum.utils import Hamiltonian"
]
},
{
"cell_type": "markdown",
"id": "a58e8871",
"metadata": {},
"source": [
"### Estimate the ground state energy of hydrogen molecule ($H_{ 2}$)"
]
},
{
"cell_type": "markdown",
"id": "ebda5aeb",
"metadata": {},
"source": [
"Import the Hamiltonian of $H_2$ with 4 qubits (for details, please refer to [Variational Quantum Eigensolver](./VQE_CN.ipynb) tutorial to obtain $H_2$ molecular Hamiltonian)."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "70b8fdef",
"metadata": {
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"H2 hamiltonian = -0.04207897647782277 I0\n",
"0.17771287465139946 Z0\n",
"0.1777128746513994 Z1\n",
"-0.2427428051314046 Z2\n",
"-0.24274280513140462 Z3\n",
"0.17059738328801055 Z0, Z1\n",
"0.04475014401535163 Y0, X1, X2, Y3\n",
"-0.04475014401535163 Y0, Y1, X2, X3\n",
"-0.04475014401535163 X0, X1, Y2, Y3\n",
"0.04475014401535163 X0, Y1, Y2, X3\n",
"0.12293305056183797 Z0, Z2\n",
"0.1676831945771896 Z0, Z3\n",
"0.1676831945771896 Z1, Z2\n",
"0.12293305056183797 Z1, Z3\n",
"0.1762764080431959 Z2, Z3\n"
]
}
],
"source": [
"# Set up the Hamiltonian of hydrogen molecule\n",
"H2_pauli_str, H2_qubit = H2_generator()\n",
"# Construct a Hamiltonian class instance using the H2_pauli_str\n",
"H2_hamiltonian = Hamiltonian(H2_pauli_str)\n",
"print('H2 hamiltonian = ', H2_hamiltonian)"
]
},
{
"cell_type": "markdown",
"id": "0d6a6532",
"metadata": {},
"source": [
"To show how to estimate the ground state energy using the classical-shadow-based algorithm, we first get the estimated ground state of $H_{2}$ using the VQE algorithm in Paddle Quantum."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "1bc042c1",
"metadata": {},
"outputs": [],
"source": [
"def U_theta(theta, hamiltonian, N, D):\n",
" \"\"\"\n",
" Quantum Neural Network\n",
" \"\"\"\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",
" 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 |0000>\n",
" cir.run_state_vector()\n",
" \n",
" # Calculate the expected value of a given Hamiltonian\n",
" expectation_val = cir.expecval(hamiltonian)\n",
"\n",
" return expectation_val, cir\n",
"\n",
"class StateNet(paddle.nn.Layer):\n",
" \"\"\"\n",
" Construct the model net\n",
" \"\"\"\n",
"\n",
" def __init__(self, shape, dtype=\"float64\"):\n",
" super(StateNet, self).__init__()\n",
" \n",
" # Assign the theta parameter list to be the trainable parameter list of the circuit\n",
" self.theta = self.create_parameter(shape=shape, \n",
" default_initializer=paddle.nn.initializer.Uniform(low=0.0, high=2*PI),\n",
" dtype=dtype, is_bias=False)\n",
" \n",
" # Define loss function and forward propagation mechanism\n",
" def forward(self, hamiltonian, N, D):\n",
" \n",
" # Calculate the loss function/expected value\n",
" loss, cir = U_theta(self.theta, hamiltonian, N, D)\n",
"\n",
" return loss, cir"
]
},
{
"cell_type": "markdown",
"id": "1c53b204",
"metadata": {},
"source": [
"After building the quantum neural network of the VQE algorithm and defining the loss function, we can estimate the ground state energy of $H_{2}$ and the quantum circuit corresponding to the ground state by training the quantum neural network."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "40036ae4",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"iter: 20 loss: -1.0865\n",
"iter: 20 Ground state energy: -1.0865 Ha\n",
"iter: 40 loss: -1.1284\n",
"iter: 40 Ground state energy: -1.1284 Ha\n",
"iter: 60 loss: -1.1355\n",
"iter: 60 Ground state energy: -1.1355 Ha\n",
"iter: 80 loss: -1.1360\n",
"iter: 80 Ground state energy: -1.1360 Ha\n",
"\n",
"The trained circuit:\n",
"--Ry(-1.57)----*--------------x----Ry(4.713)----*--------------x----Ry(3.129)--\n",
" | | | | \n",
"--Ry(5.091)----x----*---------|----Ry(1.768)----x----*---------|----Ry(3.711)--\n",
" | | | | \n",
"--Ry(0.997)---------x----*----|----Ry(4.696)---------x----*----|----Ry(3.180)--\n",
" | | | | \n",
"--Ry(4.753)--------------x----*----Ry(4.701)--------------x----*----Ry(-0.37)--\n",
" \n"
]
}
],
"source": [
"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 = H2_hamiltonian.n_qubits \n",
"\n",
"# Determine the parameter dimension of the network\n",
"net = StateNet(shape=[D + 1, N, 1])\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(H2_hamiltonian, N, D)\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)"
]
},
{
"cell_type": "markdown",
"id": "a6c9d23f",
"metadata": {},
"source": [
"#### Introduction to shadow function"
]
},
{
"cell_type": "markdown",
"id": "c9891733",
"metadata": {},
"source": [
"At this point, we've obtained the quantum circuit for generating the ground state of $H_{2}$. We can run the `shadow_trace` function on this circuit to obtain the estimated ground state energy. In the `shadow_trace` function, our inputs are the Hamiltonian to be estimated, the number of samples, and the method selected. You can choose the sampling mode you want by specifying the parameter `method`. Among them, CS has a broader application range and the fastest speed, but its estimation accuracy may be slightly poor; LBCS has a higher accuracy, but it runs slower as the number of terms of Hamiltonian grows; APS also has higher accuracy, but it runs slower when the number of qubits is large."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "3b31e57d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"H2 ground state energy = -1.1360331063822373\n",
"H2 ground state energy CS= -1.1652093330967717\n",
"H2 ground state energy LBCS= -1.1213982275348622\n",
"H2 ground state energy APS= -1.137720214050516\n"
]
}
],
"source": [
"# The actual energy value corresponding to the estimated ground state\n",
"H2_energy = cir.expecval(H2_hamiltonian).numpy()[0]\n",
"\n",
"# Sampling times\n",
"sample = 1500 \n",
"# Three algorithms are used to estimate the expectation value of observable.\n",
"H2_energy_CS = cir.shadow_trace(H2_hamiltonian, sample, method=\"CS\")\n",
"H2_energy_LBCS = cir.shadow_trace(H2_hamiltonian, sample, method=\"LBCS\")\n",
"H2_energy_APS = cir.shadow_trace(H2_hamiltonian, sample, method=\"APS\")\n",
"\n",
"print('H2 ground state energy = ', H2_energy)\n",
"print('H2 ground state energy CS= ', H2_energy_CS)\n",
"print('H2 ground state energy LBCS= ', H2_energy_LBCS)\n",
"print('H2 ground state energy APS= ', H2_energy_APS)"
]
},
{
"cell_type": "markdown",
"id": "1cb856da",
"metadata": {},
"source": [
"Now let's use the item-by-item measurement method to estimate the ground state energy of $H_{2}$."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "3ae0242c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"H2 ground state energy traditional = -1.1367622727687419\n"
]
}
],
"source": [
"# Use the item-by-item measurement method to estimate the ground state energy\n",
"H2_energy_traditional = cir.expecval(H2_hamiltonian, shots=100).numpy()[0]\n",
"print('H2 ground state energy traditional = ', H2_energy_traditional)"
]
},
{
"cell_type": "markdown",
"id": "b2e1ad02",
"metadata": {},
"source": [
"We can see that under 1500 samples, the estimated ground state energy by these three algorithms are very close to the energy of the estimated ground state by VQE. The item-by-item measurement method is to make 100 measurements for each item of the Hamiltonian, and there are 15 items of the Hamiltonian of $H_{2}$, which is equivalent to 1500 measurements in total. The difference between the obtained results and the energy of the estimated ground state by VQE is also tiny. In this small-scale situation, the classical-shadow-based algorithms do not show significant advantages over the item-by-item measurement method. But in large-scale qubits scenarios, this algorithm requires only constant-level growth of the number of the Hamiltonian terms. In contrast, the item-by-item measurement method or some other methods require polynomial-level or even exponential-level growth in the number of samples to get the same accuracy[1]. In fact, it is pointed out in [2] that for CS algorithm and LBCS algorithm, the average error of estimation $\\epsilon$, variance $\\operatorname {var}(\\nu)$ and number of samples $S$ are related as follows:\n",
"\n",
"$$\n",
"S = O(\\epsilon^{-2} \\operatorname{var}(\\nu) ), \\tag{3}\n",
"$$\n",
"\n",
"where the variance $\\operatorname{var} (\\nu) $ is independent of the number of samples, and the variance is related to the number of terms of the Hamiltonian. Therefore, knowing our expected precision, we can calculate the required number of samples. Similarly, our average error can also be defined according to the number of samples: \n",
"\n",
"$$\n",
"\\epsilon = \\sqrt{\\frac{\\operatorname{var}}{S}}. \\tag{4}\n",
"$$\n",
"\n",
"We can be seen that in the above experiments, the errors obtained by the CS algorithm and LBCS algorithm are within the accuracy of theoretical estimation in reference [3]."
]
},
{
"cell_type": "markdown",
"id": "5e0e174d",
"metadata": {},
"source": [
"At the same time, Paddle Quantum provides a sampling function ` shadow_sample`, which supports pre-sampling of unknown quantum states, which is convenient for readers to explore other applications of the classical shadow. The specific usage is as follows:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "fc6b465c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[('yxxz', '0010'), ('xyzx', '0101'), ('yxyx', '1010'), ('yxyy', '1101'), ('yyyx', '0101'), ('xyxz', '0010'), ('zyxz', '1010'), ('xzyz', '0110'), ('zzzx', '1101'), ('xyxx', '0101')]\n"
]
}
],
"source": [
"from paddle_quantum.shadow import shadow_sample\n",
"\n",
"# Run the circuit in the vector form\n",
"H2_rho = np.array(cir.run_state_vector())\n",
"# Get the data of classical shadow and output it in the form of a list\n",
"H2_sample_data_CS = shadow_sample(H2_rho, H2_qubit, sample_shots=10, mode='state_vector', \n",
" hamiltonian=H2_hamiltonian, method='CS')\n",
"print(H2_sample_data_CS)"
]
},
{
"cell_type": "markdown",
"id": "e74ffd18",
"metadata": {},
"source": [
"### Estimate the ground state energy of lithium hydride ($LiH$)"
]
},
{
"cell_type": "markdown",
"id": "e4480cb6",
"metadata": {},
"source": [
"Next, we consider the ground state energy of $LiH$. First, we load from a pre-computed file to generate the molecular Pauli Hamiltonian of $LiH$ with 12 qubits."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "6916c8a4",
"metadata": {},
"outputs": [],
"source": [
"with open('./LiH_hamiltonian.txt', 'r') as lih_file:\n",
" unprocessed_pauli_str = lih_file.read()\n",
" LiH_pauli_str = [term.split(maxsplit=1) for term in unprocessed_pauli_str.split('\\n')]\n",
" LiH_pauli_str = [[float(term[0]), term[1]] for term in LiH_pauli_str]\n",
" LiH_hamiltonian = Hamiltonian(LiH_pauli_str)"
]
},
{
"cell_type": "markdown",
"id": "61d8fd59",
"metadata": {},
"source": [
"Then, we also use the VQE algorithm to obtain the ground state energy and the ground state of $LiH $ and then use the classical shadow algorithm to estimate the ground state energy. Due to the large size of the $LiH$ molecular Hamiltonian, it will take a long time to train the VQE circuit. Hence we provide a set of pre-trained parameters, using which the users could test the classical-shadow-based algorithms on $LiH$ Hamiltonian directly."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "fe326409",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The pre-trained VQE gets a ground state energy of: -7.7720 \n"
]
}
],
"source": [
"# Load the pre-trained parameters\n",
"pretrained_parameters = paddle.load('LiH_VQE_parameters.pdtensor')\n",
"N = LiH_hamiltonian.n_qubits\n",
"# Run the VQE circuit with pre-trained parameters\n",
"energy, cir = U_theta(pretrained_parameters, LiH_hamiltonian, N, D)\n",
"print('The pre-trained VQE gets a ground state energy of: %.4f ' % energy.numpy())"
]
},
{
"cell_type": "markdown",
"id": "8f8ff501",
"metadata": {},
"source": [
"Once we have the circuit corresponding to the $LiH$ ground state, we can directly use the `shadow_trace` function to perform random measurements. Also, since this molecular Hamiltonian has 631 terms, we specify `sample = 1262` for the function `shadow_trace` and `shots = 2` for the function `expecval` in order to ensure that the number of measurements is the same for both types of methods.\n",
"\n",
"Since ground state of $LiH$ has 12 qubits, there are $3^{12}$ possible combinations of measurements when doing different Pauli measurements on the ground state of $LiH$. So it is too random to just perform 1262 samples to get the valuation. Thus, we run each of the above four methods 20 times. Then take the mean of these 20 samples of data as the estimation for each algorithm, and calculate the sample variance for a simple comparison of the algorithms.(It may take at least 1 hour to run the following code blocks.)"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "8f3684ce",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"LiH ground state energy = -7.771980394176657\n",
"ave LiH ground state energy CS = -7.835791570579005\n",
"ave LiH ground state energy LBCS = -7.7622296662623445\n",
"ave LiH ground state energy APS = -7.762836542787509\n",
"ave LiH ground state energy traditional = -7.8964746269601465\n",
"time = 4206.216086864471\n"
]
}
],
"source": [
"import time\n",
"\n",
"begin = time.time()\n",
"estimator_CS = []\n",
"estimator_LBCS = []\n",
"estimator_APS = []\n",
"estimator_traditional = []\n",
"\n",
"# The actual energy value corresponding to the estimated ground state\n",
"LiH_energy = cir.expecval(LiH_hamiltonian).numpy()[0]\n",
"\n",
"# Number of repetition times\n",
"n = 20 \n",
"\n",
"for i in range(n):\n",
" LiH_energy_CS = cir.shadow_trace(LiH_hamiltonian, 1262, method=\"CS\")\n",
" LiH_energy_LBCS = cir.shadow_trace(LiH_hamiltonian, 1262, method=\"LBCS\")\n",
" LiH_energy_APS = cir.shadow_trace(LiH_hamiltonian, 1262, method=\"APS\")\n",
" LiH_energy_traditional = cir.expecval(LiH_hamiltonian, shots=2).numpy()[0]\n",
"\n",
" estimator_CS.append(LiH_energy_CS) \n",
" estimator_LBCS.append(LiH_energy_LBCS) \n",
" estimator_APS.append(LiH_energy_APS) \n",
" estimator_traditional.append(LiH_energy_traditional) \n",
"\n",
"ave_LiH_energy_CS = np.mean(estimator_CS)\n",
"ave_LiH_energy_LBCS = np.mean(estimator_LBCS)\n",
"ave_LiH_energy_APS = np.mean(estimator_APS)\n",
"ave_LiH_energy_traditional = np.mean(estimator_traditional)\n",
"end = time.time() \n",
"\n",
"print(\"LiH ground state energy = \", LiH_energy)\n",
"print(\"ave LiH ground state energy CS = \", ave_LiH_energy_CS)\n",
"print(\"ave LiH ground state energy LBCS = \", ave_LiH_energy_LBCS)\n",
"print(\"ave LiH ground state energy APS = \", ave_LiH_energy_APS)\n",
"print('ave LiH ground state energy traditional = ', ave_LiH_energy_traditional)\n",
"print('time = ', end-begin)"
]
},
{
"cell_type": "markdown",
"id": "89469273",
"metadata": {},
"source": [
"From the results, the mean values obtained by the classical-shadow-based algorithms are closer to the energy of the estimated ground state of $LiH$ by VQE than the item-by-item measurement method, and the errors of the algorithms are all within the accuracy of theoretical estimation in reference [3]. So, what about the sample variance of each algorithm?"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "742f4f15",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"LiH variance CS = 0.034596840359755784\n",
"LiH variance LBCS = 0.016602696085670984\n",
"LiH variance APS = 0.0016603026356630662\n",
"LiH variance traditional = 0.13200055652163223\n"
]
}
],
"source": [
"# Calculate the sample variance\n",
"variance_CS = []\n",
"variance_LBCS = []\n",
"variance_APS = []\n",
"variance_traditional = []\n",
"\n",
"for i in range(n):\n",
" variance_CS.append((estimator_CS[i] - ave_LiH_energy_CS) ** 2)\n",
" variance_LBCS.append((estimator_LBCS[i] - ave_LiH_energy_LBCS) ** 2)\n",
" variance_APS.append((estimator_APS[i] - ave_LiH_energy_APS) ** 2)\n",
" variance_traditional.append((estimator_traditional[i] - ave_LiH_energy_traditional) ** 2)\n",
"\n",
"var_CS = sum(variance_CS)/(n-1)\n",
"var_LBCS = sum(variance_LBCS)/(n-1)\n",
"var_APS = sum(variance_APS)/(n-1)\n",
"var_traditional = sum(variance_traditional)/(n-1)\n",
"\n",
"print('LiH variance CS = ', var_CS)\n",
"print('LiH variance LBCS = ', var_LBCS)\n",
"print('LiH variance APS = ', var_APS)\n",
"print('LiH variance traditional = ', var_traditional)"
]
},
{
"cell_type": "markdown",
"id": "d40ef806",
"metadata": {},
"source": [
"It can be seen that the APS algorithm has the lowest sample variance, followed by the LBCS algorithm, then the CS algorithm, and finally the item-by-item measurement method. Accordingly, we can find that after the increase in the number of terms of the Hamiltonian, the classical-shadow-based algorithm has higher accuracy and more stability at the same cost than the item-by-item measurement method. Among them, the APS algorithm is the most stable one.\n",
"\n",
"It is worth mentioning that for the classical shadow algorithm, the scene of 12 qubits still can not show its significant advantages compared with some existing algorithms. In large-scale systems with more qubits, its advantages can be better demonstrated [1]."
]
},
{
"cell_type": "markdown",
"id": "fa9e6a26",
"metadata": {},
"source": [
"## Conclusion"
]
},
{
"cell_type": "markdown",
"id": "91a60770",
"metadata": {},
"source": [
"This tutorial discusses how to use the classical-shadow-based algorithms to estimate the observable's expectation value and shows how to use the `shadow` function in Paddle Quantum. It can be seen that the improved algorithm based on the classical shadow can get a good estimate of the observable's expectation value. Compared with the item-by-item measurement method, when the number of samples are the same, its estimated value is more accurate and the algorithm is more stable. In the large-scale qubit scenario, the number of samples required by the classical shadow method is only a constant increase in some tasks. So its role in the NISQ (noisy intermediate-scale quantum) era will continue to be explored."
]
},
{
"cell_type": "markdown",
"id": "5fcdcad3",
"metadata": {},
"source": [
"_______\n",
"\n",
"## References"
]
},
{
"cell_type": "markdown",
"id": "947b52db",
"metadata": {},
"source": [
"[1] Huang, Hsin-yuan, R. Kueng and J. Preskill. “Predicting many properties of a quantum system from very few measurements.” [Nature Physics (2020): 1-8.](https://www.nature.com/articles/s41567-020-0932-7?proof=t)\n",
"\n",
"[2] Hadfield, Charles, et al. \"Measurements of quantum hamiltonians with locally-biased classical shadows.\" [arXiv preprint arXiv:2006.15788 (2020).](https://arxiv.org/abs/2006.15788)\n",
"\n",
"[3] Hadfield, Charles. \"Adaptive Pauli Shadows for Energy Estimation.\" [arXiv preprint arXiv:2105.12207 (2021).](https://arxiv.org/abs/2105.12207)"
]
}
],
"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.11"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
{
"cells": [
{
"cell_type": "markdown",
"id": "prescription-lighter",
"metadata": {},
"source": [
"# 未知量子态的经典影子"
]
},
{
"cell_type": "markdown",
"id": "loaded-consultation",
"metadata": {},
"source": [
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
]
},
{
"cell_type": "markdown",
"id": "comfortable-guidance",
"metadata": {},
"source": [
"## 概览"
]
},
{
"cell_type": "markdown",
"id": "cellular-armstrong",
"metadata": {},
"source": [
"对于一个未知量子系统中的量子态 $\\rho$,如何去获取它所包含的信息是一个十分基础且重要的问题。本教程将讨论如何通过经典影子(classical shadow) 技术用经典的数据来描述一个未知的量子态,从而可以高效地对该量子态的诸多性质进行有效估计。在 NISQ (noisy intermediate-scale quantum)时代,这个技术可以很好地帮助我们用经典资源来交换量子资源,在将量子态的信息用经典数据描述后,可以用经典机器学习等方法来解决一些量子问题。并且利用这个方法,一些现有的变分量子算法(例如变分量子本征求解器(variational quantum eigensolver, VQE))实现中的量子电路运行次数等代价将减小,进而提升算法的速度。"
]
},
{
"cell_type": "markdown",
"id": "stainless-enemy",
"metadata": {},
"source": [
"## 经典影子"
]
},
{
"cell_type": "markdown",
"id": "7020ecd5",
"metadata": {},
"source": [
"经典影子的直觉来源于现实生活中的影子。我们用一束光垂直照向一个多面体,会在桌面上得到一个它的影子,旋转这个多面体,我们可以看到它不同形状的影子。在多次旋转后,这一系列的影子很自然地就反映了这个多面体的一些信息。类似地,在量子世界,如果我对一个量子态进行一次酉变换,然后进行一次测量,是否也能得到一个量子态的“影子”?经典影子的构建就和这个例子有相似之处,它的过程如下:\n",
"\n",
"首先,我们对 $n$ 个量子比特系统中的一个未知量子态 $\\rho$ 作用一个酉变换:$\\rho \\mapsto U \\rho U^{\\dagger}$ ,然后用计算基态去对每个量子位做测量。对于测量的结果这里用 $|\\hat{b}\\rangle$ 举例解释:对 $|\\hat{b}\\rangle$ 进行之前酉变换的逆变换得到 $U^{\\dagger}|\\hat{b}\\rangle\\langle\\hat{b}|U$。我们知道,测得的量子态 $|\\hat{b}\\rangle\\langle\\hat{b}|$在计算基态下期望为:\n",
"\n",
"$$\n",
"\\mathbb{E}(|\\hat{b}\\rangle\\langle\\hat{b}|) = \\sum_{b \\in \\{0,1\\}^{n}} \\operatorname{Pr}(|\\hat{b}\\rangle\\langle\\hat{b}| = |b\\rangle\\langle b|)\\cdot |b\\rangle \\langle b|= \\sum_{b \\in \\{0,1\\}^{n}}\\langle b|U\\rho U^{\\dagger} |b\\rangle |b\\rangle \\langle b| \\tag{1}\n",
"$$\n",
"\n",
"那么进行了逆操作后,$U^{\\dagger}|\\hat{b}\\rangle\\langle\\hat{b}|U$ 的期望就是 $\\sum_{b \\in \\{0,1\\}^{n}}\\langle b|U\\rho U^{\\dagger} |b\\rangle U^{\\dagger}|b\\rangle \\langle b|U$。在这个过程中,酉变换 $U$ 是从一个固定的集合中随机选取。当我们重复这一过程,对每次随机选取的 $U$ 也求平均时,可以得到:\n",
"\n",
"$$\n",
"\\mathbb{E}_{U \\sim \\mathcal{U}\\left(n\\right)}(\\mathbb{E}(U^{\\dagger}|\\hat{b}\\rangle\\langle\\hat{b}|U))=\\sum_{b \\in \\{0,1\\}^{n}}\\mathbb{E}_{U \\sim \\mathcal{U}\\left(n\\right)}(\\langle b|U\\rho U^{\\dagger} |b\\rangle U^{\\dagger}|b\\rangle \\langle b|U) \\tag{2}\n",
"$$ \n",
"\n",
"其中 $\\mathcal{U}\\left(n\\right)$ 是给定的一个作用在 $n$ 个量子比特上的酉变换集合。\n",
"\n",
"如果把这个期望的结果记作 $\\mathcal{M}(\\rho)$ ,则 $\\mathcal{M}$ 将会是一个从 $\\rho$ 到 $\\mathcal{M}(\\rho)$ 的映射。当 $\\mathcal{M}$ 线性可逆时 [1],初始的量子态 $\\rho$ 就可以表达为:\n",
"\n",
"$$\n",
"\\rho=\\mathcal{M}^{-1}(\\mathbb{E}_{U \\sim \\mathcal{U} \\left(n\\right)}(\\mathbb{E}(U^{\\dagger}|\\hat{b}\\rangle\\langle \\hat{b}|U))) = \\mathbb{E}_{U \\sim \\mathcal{U} \\left(n\\right)}(\\mathbb{E}(\\mathcal{M}^{-1} (U^{\\dagger}|\\hat{b}\\rangle\\langle \\hat{b}|U))) \\tag{3}\n",
"$$\n",
"\n",
"有了 $\\mathcal{M}^{-1}$后,每一次采样 $U$ ,对 $U^{\\dagger}|\\hat{b}\\rangle\\langle\\hat{b}|U$ 作用 $\\mathcal{M}^{-1}$ 的结果 $\\hat{\\rho} = \\mathcal{M}^{-1}(U^{\\dagger}|\\hat{b}\\rangle\\langle\\hat{b}|U)$ 被我们称为 $\\rho$ 的一个快照(snapshot),重复这个过程 $N$ 次后得到 $N$ 个关于 $\\rho$ 的快照的集合:\n",
"\n",
"$$\n",
"\\text{S}(\\rho ; N)=\\{\\hat{\\rho}_{1}=\\mathcal{M}^{-1}(U_{1}^{\\dagger}|\\hat{b}_{1}\\rangle\\langle\\hat{b}_{1}| U_{1}), \\ldots, \\hat{\\rho}_{N}=\\mathcal{M}^{-1}(U_{N}^{\\dagger}|\\hat{b}_{N}\\rangle\\langle\\hat{b}_{N}| U_{N})\\} \\tag{4}\n",
"$$\n",
"\n",
"我们把 $\\text{S}(\\rho; N)$ 就称为 $\\rho$ 的经典影子。值得一提的是,$\\mathcal{M}$ 具体是什么取决于我们选取的采样集合 $\\mathcal{U}$,例如当该集合选定为 Clifford 群时,我们有: \n",
"\n",
"$$\n",
"\\mathcal{M}(\\rho)=\\mathbb{E}_{U \\sim \\operatorname{Cl} \\left(n\\right)}(\\mathbb{E}(U^{\\dagger}|\\hat{b}\\rangle\\langle \\hat{b}|U)) = \\frac{1}{2^{n}+1}\\rho+\\frac{1}{2^{n}+1}I \\tag{5}\n",
"$$\n",
"(有关 $\\mathcal{M}$ 的表达式为何如(5)所示,读者可以参考 [1])。于是:\n",
"\n",
"$$\n",
"\\mathcal{M}^{-1}(\\frac{1}{2^{n}+1}\\rho+\\frac{1}{2^{n}+1}I)=\\rho \\Rightarrow \\mathcal{M}^{-1}(\\rho) = (2^{n}+1)\\rho-I \\tag{6}\n",
"$$\n",
"\n",
"构建了经典影子之后,它是如何帮助我们有效估计量子态的性质的呢?[1] 中指出,关于量子态 $\\rho$ 的一些线性性质就十分适合用经典影子技术来估计,例如 $\\rho$ 在某个可观测量 $\\mathcal{O}$ 下的期望:$o=\\operatorname{tr}\\left(\\mathcal{O} \\rho\\right)$,我们记 $\\hat{o}=\\operatorname{tr}\\left(\\mathcal{O} \\hat{\\rho}\\right)$,那么根据(3)式,就有 $\\mathbb{E}[\\hat{o}]=\\operatorname{tr}\\left(\\mathcal{O} \\rho\\right)$。与之相关的应用与实现,读者可以阅读另一篇教程:[基于经典影子的量子态性质估计](./ClassicalShadow_Application_CN.ipynb)。\n",
"\n",
"接下来,我们将在量桨中展示对一个随机生成的量子态 $\\rho$ 构建其经典影子的过程,帮助读者更好地理解与感受经典影子这个技术。其中选取 Clifford 群作为酉变换的采样集合。(具体有关 Clifford 群的性质以及如何从中均匀随机采样 Clifford 变换,感兴趣的读者可以参考 [1]、[3] 以及量桨中的 `Clifford` 类)"
]
},
{
"cell_type": "markdown",
"id": "chinese-talent",
"metadata": {},
"source": [
"## Paddle Quantum 代码实现"
]
},
{
"cell_type": "markdown",
"id": "proved-hypothesis",
"metadata": {},
"source": [
"首先,我们导入需要用到的包。"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "freelance-steps",
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"import numpy as np\n",
"import paddle\n",
"import matplotlib.pyplot as plt\n",
"from paddle_quantum.clifford import Clifford\n",
"from paddle_quantum.state import vec_random\n",
"from paddle_quantum.circuit import UAnsatz\n",
"from paddle_quantum.utils import trace_distance"
]
},
{
"cell_type": "markdown",
"id": "blessed-italian",
"metadata": {},
"source": [
"接下来,我们随机生成量子态 $\\rho$。"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "measured-boating",
"metadata": {},
"outputs": [],
"source": [
"# 设置量子比特数目\n",
"n_qubit = 2\n",
"\n",
"# 随机生成态矢量 phi\n",
"phi_random = vec_random(n_qubit) \n",
"# phi 的密度矩阵表示 rho\n",
"rho_random = np.outer(phi_random, phi_random.conj())\n",
"\n",
"# 定义 |0> 和 |1>\n",
"ket_0 = np.array([[1,0]]).T\n",
"ket_1 = np.array([[0,1]]).T\n",
"\n",
"# 定义单位矩阵与 M 逆的系数\n",
"I = np.identity(1<<n_qubit)\n",
"coefficient = float(1<<n_qubit) + 1.0"
]
},
{
"cell_type": "markdown",
"id": "painful-crossing",
"metadata": {},
"source": [
"下一步,我们定义在测量时所需要的电路,并对我们记录测量结果做一些准备。"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "dependent-abortion",
"metadata": {},
"outputs": [],
"source": [
"def measure_by_clifford(phi, num_qubit):\n",
" \"\"\"\n",
" 对 phi 进行 Clifford 变换后,进行计算基态下的测量\n",
" \"\"\"\n",
" # 通过 Paddle Quantum 的 Clifford 类,来随机选取一个 Clifford operator,并生成其电路\n",
" clif = Clifford(num_qubit)\n",
" cir = clif.circuit()\n",
" \n",
" # 将量子态输入电路并运行\n",
" cir.run_state_vector(paddle.to_tensor(phi))\n",
" \n",
" # 对这一次采样后的运行结果进行一次测量\n",
" bitstring, = cir.measure(shots=1)\n",
" cl = cir.U.numpy()\n",
" \n",
" # 将测量的比特串结果记成态矢量形式\n",
" bhat = np.eye(1) \n",
" for i in bitstring:\n",
" if i == '0':\n",
" bhat = np.kron(bhat, ket_0)\n",
" elif i == '1':\n",
" bhat = np.kron(bhat, ket_1)\n",
" return bhat, cl"
]
},
{
"cell_type": "markdown",
"id": "collect-fellow",
"metadata": {},
"source": [
"### 进行酉变换的采样与经典影子的构建"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "authorized-welcome",
"metadata": {},
"outputs": [],
"source": [
"# 选定采样次数\n",
"S = 800"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "specified-balloon",
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"estimator_rho = []\n",
"tracedistance = []\n",
"\n",
"for sample in range(S):\n",
" \n",
" bhat, cl = measure_by_clifford(phi_random, n_qubit)\n",
" \n",
" # 根据推导的 M 逆来得到 shadow\n",
" hat_rho = coefficient * cl.conj().T @ np.kron(bhat, bhat.conj().T) @ cl - I\n",
" estimator_rho.append(hat_rho)\n",
" \n",
" # 对 shadow 求平均(因为在实际操作中,我们不能实现(3)式中的求期望,只能对得到的 classical shadow 求平均来近似)\n",
" ave_estimate = sum(estimator_rho) / len(estimator_rho)\n",
" \n",
" # 计算迹距离\n",
" tracedistance.append(trace_distance(rho_random, ave_estimate).real)"
]
},
{
"cell_type": "markdown",
"id": "boolean-overview",
"metadata": {},
"source": [
"最后,我们输出用经典影子近似的 $\\rho$ 与真实 $\\rho$ 的矩阵表示,以及他们的迹距离。迹距离越接近 0 则说明用经典影子近似的 $\\rho$ 越接近真实的量子态。"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "german-columbia",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"输出的量子态近似: [[-0.014+0.j -0.036+0.022j 0.02 -0.047j -0.005-0.075j]\n",
" [-0.036-0.022j 0.108+0.j -0.152+0.075j 0.064+0.284j]\n",
" [ 0.02 +0.047j -0.152-0.075j 0.158+0.j 0.114-0.334j]\n",
" [-0.005+0.075j 0.064-0.284j 0.114+0.334j 0.748+0.j ]]\n",
"--------------------------------------------------\n",
"初始输入的量子态: [[ 0.009+0.j -0.027+0.022j 0.018-0.035j -0.054-0.053j]\n",
" [-0.027-0.022j 0.141+0.j -0.148+0.065j 0.037+0.305j]\n",
" [ 0.018+0.035j -0.148-0.065j 0.184+0.j 0.102-0.335j]\n",
" [-0.054+0.053j 0.037-0.305j 0.102+0.335j 0.666+0.j ]]\n",
"量子态近似与真实量子态的迹距离: 0.112\n"
]
}
],
"source": [
"print('输出的量子态近似:', np.around(ave_estimate, decimals=3))\n",
"print('-' * 50)\n",
"print('初始输入的量子态:', np.around(rho_random, decimals=3))\n",
"print('量子态近似与真实量子态的迹距离:', np.around(tracedistance[-1], decimals=3))"
]
},
{
"cell_type": "markdown",
"id": "independent-inspiration",
"metadata": {},
"source": [
"可以看到,用经典影子近似的 $\\rho$ 与真实的量子态 $\\rho$ 在矩阵表示上已经十分接近,在 800 次的采样左右,两者的迹距离已经在 0.1 左右。下图展示了迹距离随着采样次数增多而下降的关系。"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "superb-merchandise",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAmEAAAJNCAYAAAB5m6IGAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8rg+JYAAAACXBIWXMAAAsTAAALEwEAmpwYAABJBElEQVR4nO3deXycVdn/8e+VpPtGF1pKC7Q8tGUpdAuliEDKWkCQTQVZHkCtIuiDCz9xxQUVUEFRARFRQKEgu+xFCKCytVBKoS0ti1AopXub7k3O74/rnmYaJs2knTN3kvm8X6+8Zuaee2bOmSzzzXXOfW4LIQgAAADFVZZ2AwAAAEoRIQwAACAFhDAAAIAUEMIAAABSQAgDAABIASEMAAAgBRVpN6C5+vTpEwYNGhT9dVatWqUuXbpEf52WqJT7LpV2/0u571Jp95++t46+d+vWTWeddZYGDhwoMyvIc4YQCvZcrU3MvocQNG/ePH39619fvHjx4j659ml1IWzQoEGaMmVK9Neprq5WVVVV9NdpiUq571Jp97+U+y6Vdv/pe1XazcjLW2+9pW7duql3794FCw8rV65Ut27dCvJcrU3MvocQtHjxYq1fv77RrMVwJAAArcTatWsLGsAQj5mpd+/eGjRoUKfG9iGEAQDQihDAWg8z2+L3ixAGAACQAkIYAADIy7Jly3T11Ven3QxVVVVtmh9+9NFHa9myZY3u++tf/1qrV68uUsuahxAGAADy0lgI27hxYwqtcQ8++KC22267Ru8nhAEAgFbvoosu0htvvKGRI0dq33331YEHHqjjjjtOe+65pyTp+OOP15gxY7TXXnvpuuuu2/S4hx9+WKNHj9aIESN06KGHSvKlQc455xyNHTtWo0aN0r333tvo665Zs0annHKK9thjD51wwglas2bNpvsGDRqkRYsWadWqVTrmmGM0YsQIDR8+XLfddpuuuuoqvf/++xo/frzGjx8vSTr33HNVWVmpvfbaSz/96U83e56LL75Yo0eP1t57761Zs2ZJkmpqanT22Wdr77331j777KM777xTkvToo49q//331+jRo/WpT31KNTU1zX4/W90SFQAAQNIFF0jTpm3z03SqrZXKy/3GyJHSr3/d6L6XXnqpZsyYoWnTpqm6ulrHHHOMZsyYocGDB0uSbrjhBvXq1Utr1qzRvvvuq5NOOkl1dXX6whe+oKeeekqDBw/WkiVLJEk//elPdcghh+iGG27QsmXLNHbsWB122GE512y75ppr1LlzZ82cOVPTp0/X6NGjP7LPww8/rB133FEPPPCAJGn58uXq0aOHrrjiCj3xxBPq06fPptft1auXamtrVVVVpenTp2ufffaRJPXp00cvvviirr76av3yl7/U9ddfr5/85Cfq0aOHXnnlFUnS0qVLtWjRIl1yySV67LHH1KVLF1122WW64oor9IMf/KBZ7z2VMAAAsFXGjh27KYBJ0lVXXaURI0Zo3LhxevfddzVnzhw9++yzOuiggzbt16tXL0leSbr00ks1cuRIVVVVae3atXrnnXdyvs5TTz2l008/XZK0zz77bApN2fbee29NnjxZ3/rWt/T000+rR48eOZ/r9ttv1+jRozVq1CjNnDlTr7322qb7TjzxREnSmDFj9Pbbb0uSHnvsMZ133nmb9unZs6eeffZZvfbaazrggAM0cuRI3Xjjjfrvf/+b79u2CZUwAABaoy1UrJpjzTYsWJpdtaqurtZjjz2mZ555Rp07d94UrBoTQtCdd96pYcOGbdVrNzR06FC9+OKLevDBB/W9731Phx566EcqU2+99ZZ++ctf6oUXXlDPnj112mmnbdbGDh06SJLKy8u3OM8thKDDDz9ct9566za1mUoYAADIS7du3bRy5cqc9y1fvlw9e/ZU586dNWvWLD377LOSpHHjxumpp57SW2+9JUmbhiOPPPJI/fa3v1UIQZL00ksvNfq6Bx10kG655RZJ0owZMzR9+vSP7PP++++rc+fOOv3003XhhRfqxRdf/EibV6xYoS5duqhHjx5asGCBJk+e3GSfDz/8cP3+97/fdHvp0qUaN26c/v3vf2vu3LmSfH7b66+/3uRzNUQlDAAA5KV379464IADNHz4cHXq1En9+vXbdN+ECRN07bXXao899tCwYcM0btw4SdL222+v6667TieeeKLq6urUt29fTZ48Wd///vd1wQUXaJ999lFdXZ0GDx6s+++/P+frnnvuuTr77LO1xx57aI899tCYMWM+ss8rr7yiCy+8UGVlZWrXrp2uueYaSdLEiRM1YcIE7bjjjnriiSc0atQo7b777tppp502tXFLvve97+m8887T8OHDVV5erosvvlgnnnii/vKXv+jUU0/VunXrJEmXXHKJhg4d2qz30zIJ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ST171t8mhAEAgG1ECMtXWdZbtXZteu0AAABtAiEsX7W19dephAEAgG1ECMtXdgijEgYAALYRISxf3/9+/XUqYQAAYBsRwvJ14YW+Wn7XroQwAACwzQhhzdWhA8ORAABgmxHCmqtDByphAABgmxHCmqtjRyphAABgmxHCmotKGAAAKABCWHN17CjdcYd0+eVptwQAALRihLDmatfOLydNSrcdAACgVSOENdeqVX750kvSokXptgUAALRahLDmWrmy/vrjj6fXDgAA0KoRwpprxYr66489ll47AABAq0YIa65MJWynnaT//CfdtgAAgFaLENZcmRN577OPtHChNGOG9Npr6bYJAAC0OoSwrTV0qLR0qbT33tJee6XdGgAA0MoQwppryBC/3GEHacOGdNsCAABaLUJYc/3nP9LLL0u9eqXdEgAA0IpVpN2AVqdPH/+aMyftlgAAgFaMStjWalgJY2gSAAA0AyFsa/XsufntpUvTaQcAAGiVCGFbq2ElbNmyVJoBAABaJ0LY1moYwqiEAQCAZiCEba2uXaXbb5f+/ne/TQgDAADNwNGR2+JTn5JmzfLrhDAAANAMVMK2VbdufllTk247AABAq0II21adO/vl6tXptgMAALQqhLBt1amTX65Zk247AABAq0II21YdOkhmVMIAAECzEMK2lZlXw6iEAQCAZiCEFUKnTlTCAABAsxDCCqFzZyphAACgWQhhhcBwJAAAaCZCWCF07sxwJAAAaBZCWCFQCQMAAM1ECCsEKmEAAKCZCGGFQCUMAAA0EyGsEFiiAgAANBMhrBBYogIAADQTIawQqIQBAIBmIoQVApUwAADQTISwQshMzA8h7ZYAAIBWghBWCJ07S7W10oYNabcEAAC0EoSwQujUyS8ZkgQAAHkihBVC585+yeR8AACQJ0JYITSshP3tb9Ivf5leewAAQItXkXYD2oRMCMtUwk4/3S+/+c102gMAAFo8KmGFkBmObHiEJBP1AQBAIwhhhZA9HDl/fv32//wnnfYAAIAWjxBWCNkT8x9+uH57VZW0cGEqTQIAAC0bIawQMpWwVaukr31N6tq1/r65c9NpEwAAaNEIYYWQqYS98Ya0YoV0ySX19731VjptAgAALRohrBAylbBXXvHLPff0qphECAMAADlFDWFmNsHMZpvZXDO7KMf9Z5nZQjOblnx9PmZ7oslUwqZP98vddvNt/foRwgAAQE7R1gkzs3JJv5d0uKR5kl4ws/tCCK812PW2EML5sdpRFJlK2PTpUrt20k47+e1Bg6T//je1ZgEAgJYrZiVsrKS5IYQ3QwjrJU2S9MmIr5eejh3rr594olSRZNu+fTk6EgAA5BQzhA2Q9G7W7XnJtoZOMrPpZnaHme0UsT3xmNVfv+qq+uvbb+8hrK5OWrpUuv9+3/fddz/6HAAAoKRYyF7hvZBPbHaypAkhhM8nt8+QtF/20KOZ9ZZUE0JYZ2ZflPSZEMIhOZ5roqSJktSvX78xkyZNitLmbDU1NeqavdREE6rGj5ckVT/++KZQtusf/qCdJ03SyiFD1G3OHNVVVKhs40a9fPnlWrrvvlHaXQjN7XtbU8r9L+W+S6Xdf/pemn2XSrv/xej7+PHjp4YQKnPeGUKI8iVpf0mPZN3+tqRvb2H/cknLm3reMWPGhGJ44oknmvcAP2HR5tt+8Yv67dlfDzxQsHbG0Oy+tzGl3P9S7nsIpd1/+l66Srn/xei7pCmhkUwTczjyBUlDzGywmbWXdIqk+7J3MLP+WTePkzQzYnviKi+XDjxw8219+tRfz75v6dLitAkAALRY0Y6ODCFsNLPzJT0ir3LdEEJ41cx+LE+F90n6qpkdJ2mjpCWSzorVnujWrfvotu2398t+/Xwl/aef9tuEMAAASl60ECZJIYQHJT3YYNsPsq5/Wz5M2fqVl390W/fufrnDDtIJJ0gLFnggW7KkuG0DAAAtDivmx/Q//+OX3/ymX/bt6+eVpBIGAEDJi1oJK3k77ijV1kplWVm3Z09CGAAAoBIWXVmDt5gQBgAARAgrPkIYAAAQIaz4+vSRFi1KuxUAACBlhLBiGzBAeu+93PfV1krf+Y40d25x2wQAAIqOifnFNmCAtGKFVFPjR0pmvPyyNHKkX//3v6Unn0yleQAAoDiohBXbgOQc5g2rYXfcUX/9ww+L1x4AAJAKQlixZULYvHmbb+/Uqf76G2/kXoEfAAC0GYSwYmusErZ4sdS+vXTzzdKGDdKcOcVvGwAAKBpCWLFtKYT16ycNH+63Z80qbrsAAEBREcKKrXNnXyssVwjr3VsaOtRvE8IAAGjTCGFpyLVMRSaEde4s7bKLNG1aKk0DAADFQQhLw4ABm0/Mv/NO6ZlnPIRJ0gknSHff7ctWzJ0rVVen0kwAABAPISwNDSthJ5/sl5lg9oMf+JDl174mnX66NH689M9/Fr+dAAAgGkJYGgYMkBYskDZu9NtDhvjloYf6Zc+e0gUXSE88IT33nG+75ZaiNxMAAMRDCEvDPvtIdXXS/ff77VWrvBp28cX1+3zsY5s/5sUXi9c+AAAQHSEsDccfLw0aJP3xj14N++ADaffdpfLy+n3GjKm/fuyx0owZ0tq1xW4pAACIhBCWhooKad99fUHWDz7wqlhm/bCMHj2kM8+UfvUr6YwzPKzNnJlOewEAQMERwtKy667Sf/8rvfuu3x448KP73Hij9PWve5VMkmbP9ssQpJtukl59VZo/X7r8cun554vTbgAAUBAVaTegZA0eLK1fL73wgt9uWAnLtttukpmHsLo66fe/l776VamqypewmDdP6tpVeu01aaeditJ8AACwbaiEpWXXXf3yr3/1yy2FsE6dfAHX2bOlgw/2ACb5+mHz5knXXy/V1Ei33x61yQAAoHAIYWkZMULq2NErYWZSnz5b3n/33aWpU6V//Wvz7fvuK33uc37eyVdfjddeAABQUISwtPTtK115pV8PQSpr4lsxbJj0+ut+ffhw6dxz/fq++9Zve+WVOG0FAAAFRwhL04EH5r/vsGH116ur6+d+ZU74PXy4NGWKdN55BWseAACIhxCWpuxg1ZTMEZKSn2PyK1+RfvQj6Ytf9G3nnOOXV18tHXCA9Pe/F66dAACg4Ahhaaqo8AVbG87zymWvvfzyoov8smtXP8dkx45+e599/CTgkvSf//h5J9etK3ybAQBAQbBERdo+//n89uvb14+E3HHHxvcZN873ee016YgjpD//WfrSlxrfPwS/NPPrdXWbr9oPAACioRLWmgwY4IGpqX0OO0waO9aHJrPdeKN02mkeuJ580oPdxz8uTZ/ulbOddqo/qTgAAIiKSlhbZOZB7LLLfEX+gQOladOks87y+485Rrr0UmnRIv8aMaL+sffe63POAABAVFTC2qrhw6XaWmnnnX35i9Gj6+877TRfzuJvf/MFYEeNqr/v/vs3f55775Vefrn+9saN9adPAgAAW40Q1lYNH15/vW9f6aqrpJtvlt580ytfY8dKp5ziS1w895z09NPS0Udvfg7K5cul44+XRo702w895Cv27767HxSQ7a23fB7ahAl+KiUAALBFDEe2VcOGST16+AnAv/Y1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\n",
"text/plain": [
"<Figure size 720x720 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"# 打印出量子态的近似与初始量子态的迹距离结果图\n",
"fig,ax = plt.subplots(figsize=(10, 10))\n",
"plt.xlabel('number of samples')\n",
"plt.ylabel('trace distance')\n",
"j = range(len(tracedistance)) \n",
"plt.plot(j, tracedistance, 'r', label=\"trace_distance\")\n",
"\"\"\"open the grid\"\"\"\n",
"plt.grid(True)\n",
"plt.legend(bbox_to_anchor=(1.0, 1), loc=1, borderaxespad=0.)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "graphic-oklahoma",
"metadata": {},
"source": [
"## 总结"
]
},
{
"cell_type": "markdown",
"id": "unavailable-health",
"metadata": {},
"source": [
"本教程介绍了经典影子的基本原理与一些理论知识。在展示的案例中,我们基于量桨对一个随机生成的 2 个量子比特量子态构建了其经典影子。直观地感受了经典影子可以对一个未知的量子态做较好的近似。事实上,[2] 中指出在许多场景下,要求对量子系统进行完整的经典描述可能是多余的。相反,准确地估计量子系统的某些特性通常就足够了,这也是经典影子真正的重要性所在。在经典影子应用的教程([基于经典影子的量子态性质估计](./ClassicalShadow_Application_CN.ipynb))中,将具体介绍经典影子的应用以及如何在量桨中使用 shadow 功能。"
]
},
{
"cell_type": "markdown",
"id": "adopted-taste",
"metadata": {},
"source": [
"## 参考文献"
]
},
{
"cell_type": "markdown",
"id": "described-swiss",
"metadata": {},
"source": [
"[1] Huang, Hsin-Yuan, Richard Kueng, and John Preskill. \"Predicting many properties of a quantum system from very few measurements.\" [Nature Physics 16.10 (2020): 1050-1057.](https://authors.library.caltech.edu/102787/1/2002.08953.pdf)\n",
"\n",
"[2] Aaronson, Scott. \"Shadow tomography of quantum states.\" [SIAM Journal on Computing 49.5 (2019): STOC18-368.](https://dl.acm.org/doi/abs/10.1145/3188745.3188802) \n",
"\n",
"[3] Bravyi, Sergey, and Dmitri Maslov. \"Hadamard-free circuits expose the structure of the Clifford group.\" [IEEE Transactions on Information Theory 67.7 (2021): 4546-4563.](https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=9435351)"
]
}
],
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
{
"cells": [
{
"cell_type": "markdown",
"id": "damaged-friendship",
"metadata": {},
"source": [
"# The Classical Shadow of Unknown Quantum States"
]
},
{
"cell_type": "markdown",
"id": "quarterly-leone",
"metadata": {},
"source": [
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
]
},
{
"cell_type": "markdown",
"id": "widespread-clone",
"metadata": {},
"source": [
"## Overview\n",
"\n",
"For the quantum state $\\rho$ in an unknown quantum system, obtaining the information it contains is a fundamental and essential problem. This tutorial will discuss how to use the classical shadow to describe an unknown quantum state with classical data so that many quantum state properties can be efficiently estimated. In the era of NISQ (noisy intermediate-scale quantum), the classical shadow helps us trade quantum for classical resources. After describing the information of the quantum state with classical data, some quantum problems can be solved by methods such as classical machine learning. And using this method, the cost of the quantum circuit running times in some existing variational quantum algorithms like variational quantum eigensolver (VQE) may be reduced so that an acceleration can be achieved."
]
},
{
"cell_type": "markdown",
"id": "remarkable-season",
"metadata": {},
"source": [
"## The Classical Shadow\n",
"\n",
"The intuition of the classic shadow comes from shadows in real life. If we use a beam of light to illuminate a polyhedron vertically, we get a shadow of it. Rotating this polyhedron, we can see its different shadows. After many rotations, this series of shadows naturally reflect some information about this polyhedron. Similarly, in the quantum world, if we perform a unitary transformation on a quantum state and then perform a measurement, can we also get a \"shadow\" of the quantum state? The construction of the classical shadow is analogous to this example. Its construction process is as follows:\n",
"\n",
"First, we apply a unitary transformation to an unknown quantum state $\\rho$ in a $n$ qubits system: $\\rho \\mapsto U \\rho U^{\\dagger}$, and then measure each qubit in the computational basis. For the measurement result, we use $|\\hat{b}\\rangle$ as an example. We perform the inverse transformation of the previous unitary on $|\\hat{b}\\rangle$ to get $U^{\\dagger}|\\hat{b}\\rangle\\langle\\hat{b}|U$. We know that the expectation value of measured quantum state $|\\hat{b}\\rangle\\langle\\hat{b}|$ is: \n",
"\n",
"$$\n",
"\\mathbb{E}(|\\hat{b}\\rangle\\langle\\hat{b}|) = \\sum_{b \\in \\{0,1\\}^{n}} \\operatorname{Pr}(|\\hat{b}\\rangle\\langle\\hat{b}| = |b\\rangle\\langle b|)\\cdot |b\\rangle \\langle b|= \\sum_{b \\in \\{0,1\\}^{n}}\\langle b|U\\rho U^{\\dagger} |b\\rangle |b\\rangle \\langle b| \\tag{1}\n",
"$$\n",
"\n",
"Then after the reverse operation, the expectation value of $U^{\\dagger}|\\hat{b}\\rangle\\langle\\hat{b}|U$ is $\\sum_{b \\in \\{0,1\\}^{n}}\\langle b|U\\rho U^{\\dagger} |b\\rangle U^{\\dagger}|b\\rangle \\langle b|U$. In this process, the unitary transformation $U$ is randomly selected from a fixed set. \n",
"When we repeat this process and average $U$, we can get:\n",
"\n",
"$$\n",
"\\mathbb{E}_{U \\sim \\mathcal{U}\\left(n\\right)}(\\mathbb{E}(U^{\\dagger}|\\hat{b}\\rangle\\langle\\hat{b}|U))=\\sum_{b \\in \\{0,1\\}^{n}}\\mathbb{E}_{U \\sim \\mathcal{U}\\left(n\\right)}(\\langle b|U\\rho U^{\\dagger} |b\\rangle U^{\\dagger}|b\\rangle \\langle b|U) \\tag{2}\n",
"$$ \n",
"where $\\mathcal{U}\\left(n\\right)$ is a given unitary transformation set on $n$ qubits.\n",
"\n",
"If this expectation value is recorded as $\\mathcal{M}(\\rho)$, then $\\mathcal{M}$ will be a map from $\\rho$ to $\\mathcal{M}(\\rho)$. When $\\mathcal{M}$ is linear and reversible [1], the initial quantum state $\\rho$ can be expressed as:\n",
"\n",
"$$\n",
"\\rho=\\mathcal{M}^{-1}(\\mathbb{E}_{U \\sim \\mathcal{U} \\left(n\\right)}(\\mathbb{E}(U^{\\dagger}|\\hat{b}\\rangle\\langle \\hat{b}|U))) = \\mathbb{E}_{U \\sim \\mathcal{U} \\left(n\\right)}(\\mathbb{E}(\\mathcal{M}^{-1} (U^{\\dagger}|\\hat{b}\\rangle\\langle \\hat{b}|U))) \\tag{3}\n",
"$$\n",
"\n",
"\n",
"With $\\mathcal{M}^{-1}$, every time $U$ is sampled, we compute $\\hat{\\rho} = \\mathcal{M }^{-1}(U^{\\dagger}|\\hat{b}\\rangle\\langle\\hat{b}|U)$ and name it a snapshot. After repeating this process $N$ times, we get $N$ collections of snapshots about $\\rho$:\n",
"\n",
"\n",
"$$\n",
"\\text{S}(\\rho ; N)=\\{\\hat{\\rho}_{1}=\\mathcal{M}^{-1}(U_{1}^{\\dagger}|\\hat{b}_{1}\\rangle\\langle\\hat{b}_{1}| U_{1}), \\ldots, \\hat{\\rho}_{N}=\\mathcal{M}^{-1}(U_{N}^{\\dagger}|\\hat{b}_{N}\\rangle\\langle\\hat{b}_{N}| U_{N})\\} .\\tag{4}\n",
"$$\n",
"\n",
"We call $\\text{S}(\\rho; N)$ the classical shadow of $\\rho$. It is worth mentioning that what exactly $\\mathcal{M}$ is depends on the sampling set $\\mathcal{U}$ we select. For example, when we select the Clifford group as the sampling set $\\mathcal{U}$, we have:\n",
"\n",
"$$\n",
"\\mathcal{M}(\\rho)=\\mathbb{E}_{U \\sim \\operatorname{Cl} \\left(n\\right)}(\\mathbb{E}(U^{\\dagger}|\\hat{b}\\rangle\\langle \\hat{b}|U)) = \\frac{1}{2^{n}+1}\\rho+\\frac{1}{2^{n}+1}I. \\tag{5}\n",
"$$\n",
"\n",
"Readers may refer to [1] for details about why $\\mathcal{M}$ is as (5). It follows:\n",
"\n",
"$$\\mathcal{M}^{-1}(\\frac{1}{2^{n}+1}\\rho+\\frac{1}{2^{n}+1}I)=\\rho \\Rightarrow \\mathcal{M}^{-1}(\\rho) = (2^{n}+1)\\rho-I \\tag{6}$$\n",
"\n",
"After constructing the classical shadow, how does it help us to effectively estimate the properties of the quantum state? Some linear properties of quantum state $\\rho$ are very suitable to be estimated by the classical shadow, for example, the expectation value of $\\rho$ of an observable $\\mathcal{O}$: $o =\\operatorname{tr}\\left(\\mathcal{O} \\rho\\right)$ [1]. Let $\\hat{o}=\\operatorname{tr}\\left(\\mathcal{O} \\hat{\\rho}\\right)$, then according to (3), there is $\\mathbb{E}[\\hat{o}]=\\operatorname{tr}\\left(\\mathcal{O} \\rho\\right)$. We provide detailed applications and implementations in our following tutorial: [Estimation of Quantum State Properties Based on the Classical Shadow](./ClassicalShadow_Application_EN.ipynb)."
]
},
{
"cell_type": "markdown",
"id": "metropolitan-subdivision",
"metadata": {},
"source": [
"Next, we will show the process of constructing the classical shadow for a randomly selected quantum state $\\rho$ in Paddle Quantum to help readers understand how the classical shadow works. And we will use the Clifford group as the sampling set of the unitary transformation (specifically about the properties of the Clifford group and how to sample uniformly distributed Clifford operators from it randomly, readers can refer to [1], [3], and the `Clifford` class in Paddle Quantum)."
]
},
{
"cell_type": "markdown",
"id": "regional-binary",
"metadata": {},
"source": [
"## Paddle Quantum Implementation"
]
},
{
"cell_type": "markdown",
"id": "military-project",
"metadata": {},
"source": [
"First, we need to import all the dependencies:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "stopped-kennedy",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import paddle\n",
"import matplotlib.pyplot as plt\n",
"from paddle_quantum.clifford import Clifford\n",
"from paddle_quantum.state import vec_random\n",
"from paddle_quantum.circuit import UAnsatz\n",
"from paddle_quantum.utils import trace_distance"
]
},
{
"cell_type": "markdown",
"id": "activated-found",
"metadata": {},
"source": [
"Next, we randomly generate a quantum state $\\rho$."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "union-freedom",
"metadata": {},
"outputs": [],
"source": [
"# Number of qubits\n",
"n_qubit = 2\n",
"\n",
"# Randomly generate a state vector\n",
"phi_random = vec_random(n_qubit) \n",
"# Its density matrix form\n",
"rho_random = np.outer(phi_random, phi_random.conj())\n",
"\n",
"# Define |0> and |1>\n",
"ket_0 = np.array([[1,0]]).T\n",
"ket_1 = np.array([[0,1]]).T\n",
"I = np.identity(1<<n_qubit)\n",
"coefficient = float(1<<n_qubit) + 1.0"
]
},
{
"cell_type": "markdown",
"id": "turkish-danish",
"metadata": {},
"source": [
"In the next step, we define the circuit required for the measurement and prepare us to record the results."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "threatened-coach",
"metadata": {},
"outputs": [],
"source": [
"def measure_by_clifford(phi, num_qubit):\n",
" \"\"\"\n",
" After applying Clifford operation on phi, obtain measurement in the computational basis\n",
" \"\"\"\n",
" # Use the Clifford class of Paddle Quantum to randomly select a Clifford operator and generate its circuit\n",
" clif = Clifford(num_qubit)\n",
" cir = clif.circuit()\n",
" \n",
" # Use phi as the input state and run it\n",
" cir.run_state_vector(paddle.to_tensor(phi))\n",
" # Single measurement\n",
" bitstring, = cir.measure(shots=1)\n",
" cl = cir.U.numpy()\n",
" \n",
" # Use this to record results of measurement\n",
" bhat = np.eye(1) \n",
" for i in bitstring:\n",
" if i == '0':\n",
" bhat = np.kron(bhat, ket_0)\n",
" elif i == '1':\n",
" bhat = np.kron(bhat, ket_1)\n",
" return bhat, cl"
]
},
{
"cell_type": "markdown",
"id": "neutral-sender",
"metadata": {},
"source": [
"### Sampling unitary and building the classical shadow"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "dietary-lucas",
"metadata": {},
"outputs": [],
"source": [
"# Select the number of samples\n",
"S = 800\n",
"estimator_rho = []\n",
"tracedistance = []\n",
"\n",
"for sample in range(S):\n",
"\n",
" bhat, cl = measure_by_clifford(phi_random, n_qubit)\n",
" \n",
" # Get the shadow according to the deduced M inverse\n",
" hat_rho = coefficient * cl.conj().T @ np.kron(bhat, bhat.conj().T) @ cl - I\n",
" estimator_rho.append(hat_rho)\n",
" \n",
" # Compute the average of the shadows \n",
" # Because in actual operation, we cannot achieve the expectation value in (3), we can only approximate rho by averaging the classical shadow obtained.\n",
" ave_estimate = sum(estimator_rho) / len(estimator_rho)\n",
" \n",
" # Calculate trace distance\n",
" tracedistance.append(trace_distance(rho_random, ave_estimate).real)"
]
},
{
"cell_type": "markdown",
"id": "excited-dividend",
"metadata": {},
"source": [
"Finally, we output the matrix representation of an approximation of $\\rho$ by the classical shadow and the real $\\rho$. Also, we output their trace distance. The closer the trace distance is to 0, the closer the approximation is to the real quantum state."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "frank-sussex",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Approximation: [[ 0.166+0.j -0.178+0.288j -0.145+0.027j 0.058+0.092j]\n",
" [-0.178-0.288j 0.716+0.j 0.192+0.211j 0.027-0.217j]\n",
" [-0.145-0.027j 0.192-0.211j 0.081+0.j -0.069-0.116j]\n",
" [ 0.058-0.092j 0.027+0.217j -0.069+0.116j 0.037+0.j ]]\n",
"--------------------------------------------------\n",
"Real state: [[ 0.147+0.j -0.189+0.247j -0.143+0.014j 0.053+0.075j]\n",
" [-0.189-0.247j 0.656+0.j 0.207+0.221j 0.058-0.185j]\n",
" [-0.143-0.014j 0.207-0.221j 0.14 +0.j -0.044-0.078j]\n",
" [ 0.053-0.075j 0.058+0.185j -0.044+0.078j 0.057+0.j ]]\n",
"Trace distance between approximation and real state: 0.122\n"
]
}
],
"source": [
"print('Approximation: ', np.around(ave_estimate, decimals=3))\n",
"print('-' * 50)\n",
"print('Real state: ', np.around(rho_random, decimals=3))\n",
"print('Trace distance between approximation and real state: ', np.around(tracedistance[-1], decimals=3))"
]
},
{
"cell_type": "markdown",
"id": "governing-springer",
"metadata": {},
"source": [
"As you can see, our approximation of $\\rho$ is very close to the real quantum state $\\rho$ in the matrix representation. With about 800 samplings, the trace distance between the approximation and the real state is already around 0.1. The relation between their trace distance and the number of samplings is shown below."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "hungarian-bangladesh",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAmEAAAJNCAYAAAB5m6IGAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8rg+JYAAAACXBIWXMAAAsTAAALEwEAmpwYAABKDUlEQVR4nO3de5xVVf3/8fdnLtyHi6CIgIKiiCIKDAhealBTwm+aZqZdzLtp5tfqa1l90/SbWWqalln+zMwysbyUoamYTN4SBUVEUcNLinhFGBjuw6zfH589zHA8wxzgrLPPzHk9H495nHP22efszzozw7xZa+21LYQgAAAAFFZZ2gUAAACUIkIYAABACghhAAAAKSCEAQAApIAQBgAAkAJCGAAAQAoq0i5gc/Xr1y8MGTIk+nFWrFih7t27Rz9OMSrltkul3f5SbrtU2u2n7e2j7VVVVTrxxBM1aNAgmVle3jOEkLf3am9itj2EoIULF+ob3/jG4sWLF/fLtk+7C2FDhgzRrFmzoh+ntrZWNTU10Y9TjEq57VJpt7+U2y6Vdvtpe03aZeTktddeU1VVlfr27Zu38LB8+XJVVVXl5b3am5htDyFo8eLFWrt2batZi+FIAADaidWrV+c1gCEeM1Pfvn01ZMiQrq3tQwgDAKAdIYC1H2a2ye8XIQwAACAF0UKYmXUxsyfN7Fkze97MLsqyz4lm9r6ZzUm+To1VDwAA2DpLly7VL3/5y7TLUE1NzYb54VOmTNHSpUtb3fdnP/uZVq5cWaDKNk/MnrA1kg4KIewtaR9Jk81sQpb9bgsh7JN83RCxHgAAsBVaC2ENDQ0pVOPuvfde9e7du9XnSzKEBVefPKxMvkKs4wEAgLjOP/98vfLKK9pnn300btw4HXjggTriiCO0xx57SJI+/elPa+zYsdpzzz11/fXXb3jdfffdpzFjxmjvvffWwQcfLMmXBjn55JM1fvx4jR49Wn/9619bPe6qVat03HHHacSIETrqqKO0atWqDc8NGTJEH3zwgVasWKHDDz9ce++9t0aOHKnbbrtN11xzjRYtWqRJkyZp0qRJkqQzzzxT1dXV2nPPPXXJJZds9D4XXnihxowZo7322ksvvviiJKm+vl4nnXSS9tprL40aNUp33HGHJOmBBx7QxIkTNWbMGH32s59VfX29NlfUJSrMrFzSbEnDJF0bQpiZZbfPmNnHJL0s6eshhDdj1gQAQIdw7rnSnDlb/TZd16+Xysv9wT77SD/7Wav7/vjHP9a8efM0Z84c1dbW6vDDD9e8efM0dOhQSdKNN96obbbZRqtWrdK4ceP0mc98Ro2NjTrttNP08MMPa+jQofrwww8lSZdccokOOugg3XjjjVq6dKnGjx+vQw45JOuabdddd526deum+fPna+7cuRozZsxH9rnvvvu0ww476J577pEk1dXVqVevXrryyis1Y8YM9evXb8Nxt9lmG61fv141NTWaO3euRo0aJUnq16+fnn76af3yl7/UFVdcoRtuuEH/93//p169eum5556TJC1ZskQffPCBfvjDH+rBBx9U9+7d9ZOf/ERXXnmlLrjggs367KOGsBDCekn7mFlvSXeZ2cgQwrwWu/xN0q0hhDVmdoak30k6KPN9zOx0SadLUv/+/VVbWxuzbEmefAtxnGJUym2XSrv9pdx2qbTbT9tr0y4jJ7169dLy5cslSZ3XrlXZ+vVb/6YhqCF5n8a1a7Umef9s6uvr1djYqOXLl2vlypUaO3as+vXrt6Gmyy+/XNOmTZMkvfnmm5ozZ44WL16siRMnbtivsrJSy5cv13333ae//OUvuuyyyyR5b9f8+fM1fPjwjxz3oYce0le+8hUtX75cQ4cO1ciRI7VixQotX75cIQTV19dr6NCheuCBB/T1r39dkydP1n777bfR8507d5Yk3XzzzbrpppvU0NCgd955R7Nnz9bQoUMVQtChhx6q5cuXa/fdd9ef//xnLV++XA888IBuvPHGDW2sqKjQ9OnT9fzzz2vixImSpLVr12r8+PEb9slVQRZrDSEsNbMZkiZLmtdi++IWu90g6bJWXn+9pOslqbq6OhRiUb32tHhfvpVy26XSbn8pt10q7fbT9pq0y8jJ/PnzmxcXzdME+cwFSzttYt8ePXqorKxMVVVV6tatm3r27LnhtbW1tXrkkUc0c+ZMdevWTTU1NSovL1fXrl1VWVn5kUVRzUx33XVX1tCVqaKiQt26ddvwHmVlZerevbuqqqpkZurRo4eGDBmiZ555Rvfee69+9KMf6eCDD9YFF1yw4fmqqiq99tpr+sUvfqGnnnpKffr00Re+8AWZ2Yb36du3r6qqqtSzZ0+FEFRVVaWysrINr2/StWtXHXroobr11ls345P+qJhnR26b9IDJzLpK+oSkFzP2GdDi4RGS5seqBwAAbJ2qqqpWe3vq6urUp08fdevWTS+++KKeeOIJSdKECRP08MMP67XXXpOkDcORhx12mH7+858rBJ8u/swzz7R63I997GP64x//KEmaN2+e5s6d+5F9Fi1apG7duumLX/yizjvvPD399NMfqXnZsmXq3r27evXqpXfffVfTp09vs82f+MQndO211254vGTJEk2YMEGPPfaYFixYIMnnt7388sttvlemmD1hAyT9LpkXVibpTyGEaWZ2saRZIYS7JZ1jZkdIapD0oaQTI9YDAAC2Qt++fbX//vtr5MiR6tq1q/r377/hucmTJ+tXv/qVRowYoeHDh2vCBF8QYdttt9X111+vo48+Wo2Njdpuu+00ffp0ff/739e5556rUaNGqbGxUUOHDt0wlJnpzDPP1EknnaQRI0ZoxIgRGjt27Ef2ee6553TeeeeprKxMlZWVuu666yRJp59+uiZPnqwddthBM2bM0OjRo7X77rtr8ODBG2rclP/93//VV7/6VY0cOVLl5eW68MILdfTRR+umm27S8ccfrzVr1kiSfvjDH2q33XbbrM/TmhJoe1FdXR24dmRcpdx2qbTbX8ptl0q7/bS9Ju0ycjJ//nyNGDEir+/JtSPjtv3BBx9ce8ghh3TO9hwr5gMAAKSgIBPzAQAA2nL//ffr29/+9kbbhg4dqrvuuiuliuIihAEAgKJw2GGH6bDDDku7jIJhOBIAgHakvc3lLmUhhE1+vwhhAAC0E126dNHixYsJYu1ACEGLFy/W66+/vqq1fRiOBACgnRg0aJAWLlyo999/P2/vuXr1anXp0iVv79eexG57ly5d9L3vfe/10047LevzhDAAANqJysrKDddpzJfa2lqNHj06r+/ZXhSi7e+//35Da88xHAkAAJACQhgAAEAKCGEAAAApIIRlmj9f2n139Zk9O+1KAABAB0YIy7RmjfTSSypfuTLtSgAAQAdGCMtk5reswQIAACIihGUqSz4SQhgAAIiIEJYp6QkzQhgAAIiIEJaJnjAAAFAAhLBM9IQBAIACIIRlYmI+AAAoAEJYJoYjAQBAARDCMjEcCQAACoAQlonhSAAAUACEsEwMRwIAgAIghGViOBIAABQAISwTPWEAAKAACGGZmuaENTamWwcAAOjQCGGZmoYjUy4DAAB0bISwTAxHAgCA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\n",
"text/plain": [
"<Figure size 720x720 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"# Print out the result\n",
"fig,ax = plt.subplots(figsize=(10, 10))\n",
" \n",
"plt.xlabel('number of samples')\n",
"plt.ylabel('trace distance')\n",
"j = range(len(tracedistance)) \n",
"plt.plot(j, tracedistance, 'r', label=\"trace_distance\")\n",
"\"\"\"open the grid\"\"\"\n",
"plt.grid(True)\n",
"plt.legend(bbox_to_anchor=(1.0, 1), loc=1, borderaxespad=0.)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "ambient-actor",
"metadata": {},
"source": [
"## Conclusion\n",
"\n",
"This tutorial introduces some theoretical knowledge of the classical shadow. In this example, we constructed the classical shadow of a random 2-qubit quantum state in Paddle Quantum. We can intuitively understand that the classic shadow can make a good approximation to an unknown quantum state. In fact, [2] pointed out demanding full classical descriptions of quantum systems may be excessive for many concrete tasks. Instead, it is often sufficient to accurately predict certain properties of the quantum system. This is where the fundamental importance of the classical shadow lies. Another tutorial ([Estimation of Quantum State Properties Based on the Classical Shadow](./ClassicalShadow_Application_EN.ipynb)) will continue to introduce the applications of the classic shadow and how to use shadow function in Paddle Quantum."
]
},
{
"cell_type": "markdown",
"id": "through-order",
"metadata": {},
"source": [
"## References\n",
"[1] Huang, Hsin-Yuan, Richard Kueng, and John Preskill. \"Predicting many properties of a quantum system from very few measurements.\" [Nature Physics 16.10 (2020): 1050-1057.](https://authors.library.caltech.edu/102787/1/2002.08953.pdf) \n",
"\n",
"[2] Aaronson, Scott. \"Shadow tomography of quantum states.\" [SIAM Journal on Computing 49.5 (2019): STOC18-368.](https://dl.acm.org/doi/abs/10.1145/3188745.3188802) \n",
"\n",
"[3] Bravyi, Sergey, and Dmitri Maslov. \"Hadamard-free circuits expose the structure of the Clifford group.\" [IEEE Transactions on Information Theory 67.7 (2021): 4546-4563.](https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=9435351)"
]
}
],
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
{
"cells": [
{
"cell_type": "markdown",
"id": "fffbbaec",
"metadata": {},
"source": [
"# 利用 Product Formula 模拟时间演化\n",
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
]
},
{
"cell_type": "markdown",
"id": "e6f9a147",
"metadata": {},
"source": [
"## 概述\n",
"\n",
"量子力学中系统的能量由哈密顿量算符 $H$ 描述,求解给定系统的哈密顿量的全部或者部分性质,构成了凝聚态物理、计算化学和高能物理等一系列学科的核心问题。然而,由于系统的自由度随系统增大呈指数级增加,导致一般情况下无法利用经典计算机有效模拟量子系统——即便用光全世界的内存和硬盘也不能直接储存一个仅几百量子比特的系统的态矢量。与经典计算机不同,量子计算机由于其所有的操作都是直接作用在同样为指数级别的态空间上,因而在模拟一个量子系统上量子计算机具有经典计算机无法比拟的优势。实际上,设计一个可控的量子系统来高效模拟自然界中的量子系统,正是费曼在上世纪 80 年代提出量子计算这一概念时的最初想法:\n",
" \n",
"> _\"Nature isn't classical, dammit, and if you want to make a simulation of nature, you'd better make it quantum mechanical, and by golly it's a wonderful problem, because it doesn't look so easy.\"_\n",
">\n",
"> --- \"Simulating physics with computers\", 1982, Richard P. Feynman [1]\n",
"\n",
"通用量子计算机以及一系列量子模拟器的发展令费曼的设想有了实现的可能。在通用量子计算机上进行数字量子模拟(digital quantum simulation)—— 利用量子门构造量子线路从而实现量子模拟,由于该方法具有较高的可拓展性和通用性,因而被认为是最有潜力的技术路线。\n",
"\n",
"本教程讲述了如何利用 Paddle Quantum 模拟量子系统的时间演化过程,主要分为以下三个部分:\n",
"1. 如何在 Paddle Quantum 中创建并操作一个 `Hamiltonian` 对象\n",
"2. 如何利用 `construct_trotter_circuit` 来构建时间演化电路\n",
"3. Suzuki product formula 方法的原理,以及如何进一步搭建任意阶的 Trotter-Suzuki 电路\n",
"\n",
"\n",
"## 定义系统哈密顿量\n",
"\n",
"在介绍如何搭建时间演化电路之前,读者可以先熟悉一下 Paddle Quantum 中的哈密顿量。目前用户需要通过定义一个列表的形式来创建哈密顿量,列表中的元素应为哈密顿量中的每一项的系数与其泡利算符。作为教程,我们首先考虑一个简单的哈密顿量:\n",
"\n",
"$$\n",
"H = Z \\otimes Z\n",
"\\tag{1}\n",
"$$\n",
"\n",
"该哈密顿量描述了两个量子比特之间的一种简单相互作用:当两个量子比特同时处于 $|0\\rangle$ 态或者 $|1\\rangle$ 态时,系统的能量为 $+1$;相反,当两个量子比特的态不同时,系统的能量为 $-1$。\n",
"\n",
"接下来,用户可以在 Paddle Quantum 中创建这一哈密顿量:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "1a17d7d8",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1.0 Z0, Z1\n"
]
}
],
"source": [
"from paddle_quantum.utils import Hamiltonian\n",
"\n",
"h = Hamiltonian([[1, 'Z0, Z1']])\n",
"print(h)"
]
},
{
"cell_type": "markdown",
"id": "64ef6a63",
"metadata": {},
"source": [
"目前,Paddle Quantum 中的哈密顿量类 `Hamiltonian` 支持自动合并同类项,加减法、索引以及拆分等操作:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "ff08a2a7",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1.0 Z0, Z1\n"
]
}
],
"source": [
"h = Hamiltonian([[0.5, 'Z0, Z1'], [0.5, 'Z1, Z0']], compress=True)\n",
"print(h)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "fec891a5",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"h + h: 2.0 Z0, Z1\n",
"h * 2: 2.0 Z0, Z1\n",
"h: 1.0 Z0, Z1\n"
]
}
],
"source": [
"print('h + h:', h + h)\n",
"print('h * 2:', h * 2)\n",
"print('h:', h[:])"
]
},
{
"cell_type": "markdown",
"id": "d2be41b1",
"metadata": {},
"source": [
"同时,内置的 `decompose_pauli_words()` 和 `decompose_with_sites()` 方法可以将哈密顿量分解为更加方便处理的形式:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "40fcc0b6",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Pauli words 分解: ([1.0], ['ZZ'])\n",
"Pauli with sites 分解: ([1.0], ['ZZ'], [[0, 1]])\n"
]
}
],
"source": [
"print('Pauli words 分解:', h.decompose_pauli_words())\n",
"print('Pauli with sites 分解:', h.decompose_with_sites())"
]
},
{
"cell_type": "markdown",
"id": "95945ddd",
"metadata": {},
"source": [
"除此之外,`construct_h_matrix()` 还可以创建其在泡利 $Z$ 基底下的矩阵形式:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "497ff8fd",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"matrix([[ 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]])"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"h.construct_h_matrix()"
]
},
{
"cell_type": "markdown",
"id": "719696dd",
"metadata": {},
"source": [
"## 模拟时间演化\n",
"\n",
"根据量子力学的基本公理,在确定了一个系统的哈密顿量之后,该系统随时间演化的过程可以由如下方程描述\n",
"\n",
"$$\n",
"i \\hbar \\frac{\\partial}{\\partial t} | \\psi \\rangle = H | \\psi \\rangle,\n",
"\\tag{2}\n",
"$$\n",
"\n",
"$\\hbar$ 为约化普朗克常数。该方程正是著名的薛定谔方程。因此,对于一个不含时的哈密顿量,系统的时间演化方程可以写为\n",
"\n",
"$$\n",
"|\\psi(t) \\rangle = U(t) | \\psi (0) \\rangle, ~ U(t) = e^{- i H t}.\n",
"\\tag{3}\n",
"$$\n",
"\n",
"这里我们取自然单位 $\\hbar=1$,$U(t)$ 为时间演化算符。利用量子线路来模拟时间演化过程的核心思想是利用量子电路构建出的酉变换模拟和近似该时间演化算符。在量桨中,我们提供了 `construct_trotter_circuit(circuit, Hamiltonian)` 函数来构建对应某一个哈密顿量的时间演化电路。下面,就让我们用刚刚创建的哈密顿量来测试一下:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "3449029b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--*-----------------*--\n",
" | | \n",
"--x----Rz(2.000)----x--\n",
" \n"
]
}
],
"source": [
"from paddle_quantum.trotter import construct_trotter_circuit\n",
"from paddle_quantum.circuit import UAnsatz\n",
"\n",
"cir = UAnsatz(2)\n",
"construct_trotter_circuit(cir, h, tau=1, steps=1) \n",
"print(cir)"
]
},
{
"cell_type": "markdown",
"id": "e3dc6c7f",
"metadata": {},
"source": [
"可以看到,我们已经创建了针对 `h` 的基本量子模拟电路,它可以根据输入的 `tau` 来模拟任意时间长度的时间演化。\n",
"\n",
"此时,如果检查该电路对应的酉变换的矩阵形式,会发现它与时间演化算符 $e^{-iHt}$ 完全一致。这里,我们先使用 `gate_fidelity` 来计算量子电路的酉矩阵和时间演化算符的酉矩阵之间的保真度。保真度越接近 1 时代表两个酉矩阵代表的演化过程越相似。在下文,我们还会引入更加严格的误差定义。"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "8d312985",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"电路的酉算符和时间演化算符之间的保真度为: 1.000\n"
]
}
],
"source": [
"from scipy import linalg\n",
"from paddle_quantum.utils import gate_fidelity\n",
"\n",
"# 计算 e^{-iHt} 和电路的酉矩阵之间的保真度\n",
"print('电路的酉算符和时间演化算符之间的保真度为: %.3f' \n",
" % gate_fidelity(cir.U.numpy(), linalg.expm(-1 * 1j * h.construct_h_matrix())))"
]
},
{
"cell_type": "markdown",
"id": "fa34ba8a",
"metadata": {},
"source": [
"实际上,这是因为泡利算符组成的张量积对应的任意角度旋转算符都可以被很好的分解为门电路的形式。比如在这个例子中我们只需要改变电路中 Rz 门的角度,就可以实现任意 $e^{-i Z\\otimes Z t}$ 演化算符的模拟。那么,这是否意味着可以用类似的电路形式来对任意的写成泡利形式的哈密顿量进行完美的模拟呢?答案是否定的。考虑一个稍微复杂一些的哈密顿量:\n",
"\n",
"$$\n",
"H = Z \\otimes Z + X \\otimes I + I \\otimes X.\n",
"\\tag{4}\n",
"$$\n",
"\n",
"同样地,让我们用 `construct_trotter_circuit` 来构建它所对应的时间演化电路:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "40bcf1e4",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--*-----------------*----Rx(2.000)--\n",
" | | \n",
"--x----Rz(2.000)----x----Rx(2.000)--\n",
" \n",
"电路的酉算符和时间演化算符之间的保真度为: 0.681\n"
]
}
],
"source": [
"h_2 = Hamiltonian([[1, 'Z0, Z1'], [1, 'X0'], [1, 'X1']]) # 不需要写出单位算符\n",
"cir = UAnsatz(2)\n",
"construct_trotter_circuit(cir, h_2, tau=1, steps=1)\n",
"print(cir)\n",
"print('电路的酉算符和时间演化算符之间的保真度为: %.3f' \n",
" % gate_fidelity(cir.U.numpy(), linalg.expm(-1 * 1j * h_2.construct_h_matrix())))"
]
},
{
"cell_type": "markdown",
"id": "27f0a4ed",
"metadata": {},
"source": [
"可以看到,此时电路对应的酉矩阵和时间演化矩阵之间的保真度 $<1$,说明该电路无法正确地模拟系统的时间演化过程。\n",
"\n",
"其实,对于我们此时构建的电路而言,它对应的酉变换为 $e^{-iZ\\otimes Z t} e^{-i (X\\otimes I + I\\otimes X)t}$,而时间演化算符为 $e^{-iZ\\otimes Z t - i(X\\otimes I + I\\otimes X)t}$。而对于一个量子系统而言,当两个算符不对易,即 $[A, B] \\neq 0$ 时,$e^{A+B} \\neq e^A e^B$。在这里,因为泡利算符 $X$ 和 $Z$ 之间并不对易,所以电路对应的酉变换并不等于正确的时间演化算符。\n",
"\n",
"除了利用保真度来描述量子电路和希望模拟的时间演化算符之间的相似性之外,我们还可以定义如下的误差 $\\epsilon$ 来刻画模拟演化电路和演化算符之间的距离,\n",
"\n",
"$$\n",
"\\epsilon(U) = \\Vert e^{-iHt} - U \\Vert,\n",
"\\tag{5}\n",
"$$\n",
"\n",
"其中 $\\Vert \\cdot \\Vert$ 表示取最大的本征(奇异)值的模。这样的定义比起保真度而言更好的描述了量子态在不同的演化算符作用下产生的偏差,是一种更加严谨的定义模拟时间演化误差的方式。我们在下文中也会多次用到该误差。"
]
},
{
"cell_type": "markdown",
"id": "9a0f9969",
"metadata": {},
"source": [
"### Product formula 与 Suzuki 分解\n",
"\n",
"Seth Lloyd 在 1996 年的文章中指出,可以将一整段的演化时间 $t$ 拆分为 $r$ 份较短的“时间块”来减小模拟时间演化的误差 [2]。考虑一个更一般的哈密顿量形式 $H = \\sum_{k=1}^{L} h_k$,其中 $h_k$ 是作用在一部分系统上的子哈密顿量。通过泰勒展开,不难发现其模拟误差是一个二阶项,即\n",
"\n",
"$$\n",
"e^{-iHt} = \\prod_{k=1}^{L} e^{-i h_k t} + O(t^2).\n",
"\\tag{6}\n",
"$$\n",
"\n",
"那么,我们令 $\\tau = t/r$,并考虑演化算符 $\\left(e^{-iH \\tau}\\right)^r$,其演化误差为\n",
"\n",
"$$\n",
"e^{-iHt} = \\left(e^{-iH \\tau}\\right)^r = \\left(\\prod_{k=1}^{L} e^{-i h_k \\tau} + O(\\tau^2) \\right)^r = \\left(\\prod_{k=1}^{L} e^{-i h_k \\tau} \\right)^r + O\\left(\\frac{t^2}{r}\\right).\n",
"\\tag{7}\n",
"$$\n",
"\n",
"上式告诉我们,只要将一整段演化时间拆为足够多的“片段”,就可以达到任意高的模拟精度,这就是 product formula 的基本思想。不过,(7) 中给出的误差只是一个粗略的估计。在实际情况中,为了估计达到某一模拟精度所需要的量子电路深度,就需要进一步推导其更严格的误差上界。下面,我们展示一个比较简略的误差上界计算过程,对具体的计算过程不感兴趣的读者可以直接跳至 (11) 阅读这部分的结论。\n",
"\n",
"记函数 $f$ 泰勒展开至 $k$ 阶之后的余项为 $\\mathcal{R}_k(f)$, 我们需要用到如下两个结论:\n",
"\n",
"$$\n",
"\\left\\Vert \\mathcal{R}_k \\left( \\prod_{k=1}^{L} \\exp (-i h_k \\tau) \\right) \\right\\Vert\n",
"\\leq\n",
"\\mathcal{R}_k \\left( \\exp \\left( \\sum_{k=1}^{L} \\vert \\tau \\vert \\cdot \\Vert h_k \\Vert \\right) \\right),\n",
"\\tag{8}\n",
"$$\n",
"\n",
"$$\n",
"\\vert \\mathcal{R}_k(\\exp (\\alpha)) \\vert \\leq \\frac{\\vert \\alpha \\vert^{k+1}}{(k+1)!} \\exp ( \\vert \\alpha \\vert ), ~\n",
"\\forall \\alpha \\in \\mathbb{C}.\n",
"\\tag{9}\n",
"$$\n",
"\n",
"这两个结论的证明略微有一些繁琐,故在此略去,感兴趣的读者可以参考 [3] 中的 F.1 节。利用 (5) 中的定义,模拟误差可以写为\n",
"\n",
"$$\n",
"\\epsilon\\left(e^{-iH\\tau}, U_{\\rm circuit}\\right) = \\left \\Vert \\exp\\left(-i\\sum_{k=1}^L h_k \\tau\\right) - \\prod_{k=1}^{L} \\exp\\left(-i h_k \\tau \\right) \\right \\Vert.\n",
"\\tag{10}\n",
"$$\n",
"\n",
"我们已经知道,此时的误差是泰勒展开至一阶后的余项,再利用 (8),(9) 和三角不等式,可以将 (10) 中的误差上界进行计算:\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"\\left \\Vert \\exp\\left(-i\\sum_{k=1}^L h_k \\tau\\right) - \\prod_{k=1}^{L} \\exp\\left(-i h_k \\tau \\right) \\right \\Vert\n",
"=~&\n",
"\\left \\Vert \\mathcal{R}_1 \\left( \\exp \\left( -i \\sum_{k=1}^{L} h_k \\tau \\right) - \\prod_{k=1}^{L} \\exp (-i h_k \\tau) \\right) \\right \\Vert\n",
"\\\\\n",
"\\leq~&\n",
"\\left \\Vert \\mathcal{R}_1 \\left( \\exp \\left( -i \\sum_{k=1}^{L} h_k \\tau \\right) \\right) \\right \\Vert\n",
"+\n",
"\\left \\Vert \\mathcal{R}_1 \\left( \\prod_{k=1}^{L} \\exp (-i h_k \\tau) \\right) \\right \\Vert\n",
"\\\\\n",
"\\leq~ &\n",
"\\left \\Vert \\mathcal{R}_1 \\left( \\exp \\left( \\vert \\tau \\vert \\cdot \\left \\Vert \\sum_{k=1}^{L} h_k \\right \\Vert \\right) \\right) \\right \\Vert\n",
"+ \n",
"\\left \\Vert \\mathcal{R}_1 \\left( \\exp \\sum_{k=1}^{L} \\left( \\vert \\tau \\vert \\cdot \\Vert h_k \\Vert \\right) \\right) \\right \\Vert\n",
"\\\\\n",
"\\leq~&\n",
"2 \\mathcal{R}_1 \\left( \\exp ( \\vert \\tau \\vert L \\Lambda ) \\right )\n",
"\\\\\n",
"\\leq~&\n",
" ( \\vert \\tau \\vert L \\Lambda )^2 \\exp ( \\vert \\tau \\vert L \\Lambda ),\n",
"\\end{aligned}\n",
"\\tag{11}\n",
"$$\n",
"\n",
"其中 $\\Lambda = \\max_k \\Vert h_k \\Vert$。随后考虑完整的演化时间 $t = r \\cdot \\tau$,那么模拟长度为 $t$ 的时间演化算符时的误差为:\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"\\left \\Vert \\left ( \\exp\\left(-i\\sum_{k=1}^L h_k \\tau \\right)\\right)^r - \\left (\\prod_{k=1}^{L} \\exp\\left(-i h_k \\tau \\right) \\right)^r \\right \\Vert\n",
"\\leq ~&\n",
"r \\left \\Vert \\exp\\left(-i\\sum_{k=1}^L h_k \\tau\\right) - \\prod_{k=1}^{L} \\exp\\left(-i h_k \\tau \\right) \\right \\Vert\n",
"\\\\\n",
"=~& r ( \\tau L \\Lambda )^2 \\exp ( \\vert \\tau \\vert L \\Lambda )\n",
"\\\\\n",
"=~& \\frac{( t L \\Lambda )^2}{r} \\exp \\left( \\frac{\\vert t \\vert L \\Lambda}{r} \\right).\n",
"\\end{aligned}\n",
"\\tag{12}\n",
"$$\n",
"\n",
"这里用到了量子电路中误差线性累积的结论,即 $\\Vert U^r - V^r \\Vert \\leq r\\Vert U - V \\Vert$,不熟悉这一结论的读者可以参考 [4] 中的 4.5.3 节。至此,我们就计算出了 product formula 对于一段完整的演化时间 $t$ 的模拟误差上界,即 (7) 式中的二阶项 $O(t^2/r)$。 \n",
"\n",
"实际上,我们还可以通过 Suzuki 分解的方法来进一步优化对每一个“时间块”内的时间演化算符 $e^{-iH\\tau}$ 的模拟精度。对于哈密顿量 $H = \\sum_{k=1}^{L} a_k h_k$,其时间演化算符的 Suzuki 分解可以写为\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"S_1(\\tau) &= \\prod_{k=0}^L \\exp ( -i h_k \\tau),\n",
"\\\\\n",
"S_2(\\tau) &= \\prod_{k=0}^L \\exp ( -i h_k \\frac{\\tau}{2})\\prod_{k=L}^0 \\exp ( -i h_k \\frac{\\tau}{2}),\n",
"\\\\\n",
"S_{2k}(\\tau) &= [S_{2k - 2}(p_k\\tau)]^2S_{2k - 2}\\left( (1-4p_k)\\tau\\right)[S_{2k - 2}(p_k\\tau)]^2,\n",
"\\end{aligned}\n",
"\\tag{13}\n",
"$$\n",
"\n",
"对于 $k > 1, k\\in\\mathbb{Z}$,其中 $p_k = 1/(4 - 4^{1/(2k - 1)})$。先前推导的 product formula 实际上只使用了一阶的 Suzuki 分解 $S_1(\\tau)$ 来对每个“时间块”进行模拟,因此也被称为一阶 Suzuki product formula,或者简称一阶 product formula。Suzuki product formula 在物理中也经常被称为 Trotter-Suzuki 分解方法。对于更高阶的 product formula 而言,使用 (10-12) 中类似的计算,可以证明第 $2k$ 阶 product formula 的整体误差上界为:\n",
"\n",
"$$\n",
"\\epsilon\\left(e^{-iHt}, \\left(S_{2k}(\\tau)\\right)^r\\right)\n",
"\\leq\n",
"\\frac{(2L5^{k-1}\\Lambda\\vert t \\vert)^{2k+1}}{3r^{2k}} \\exp \\left( \\frac{2L5^{k-1}\\Lambda\\vert t \\vert}{r} \\right),\n",
"~ k > 1.\n",
"\\tag{14}\n",
"$$\n",
"\n",
"在得到了模拟误差上界的基础上,还可以进一步计算达到一定最小精度 $\\epsilon$ 时所需要的电路深度的下界。需要指出的是,(12) 和 (14) 中给出的误差上界的计算是十分宽松的。近年来,许多工作都进一步地给出了更加紧致的上界 [3, 5-6]。此外,也有人提出了不基于 Suzuki 分解的 product formula [7]。"
]
},
{
"cell_type": "markdown",
"id": "f3bf6fb2",
"metadata": {},
"source": [
"![image.png](./figures/trotter_suzuki_circuit.png)\n",
"<div style=\"text-align:center\">图1:Suzuki product formula 电路的示意图</div>"
]
},
{
"cell_type": "markdown",
"id": "e0c26758",
"metadata": {},
"source": [
"### 利用 Paddle Quantum 验证基于 Suzuki-product formula 的时间演化电路\n",
"\n",
"尽管人们不断的在优化 Suzuki-product formula 的误差上界,但是在实际使用中,真实的误差往往和理论上给出的上界有一定差距,也就是说,我们现在能计算的理论上的 product formula 误差依然只是一个十分宽松的上界 [3]。因此,对于一个真实的物理系统,我们往往需要通过数值实验来计算其真实的误差,从而给出一个经验性的误差上界(empirical bound)。这样的数值试验对于计算在一定精度下模拟某一时间演化过程所需要的电路深度十分重要。\n",
"\n",
"在刚刚介绍的 `construct_trotter_circuit` 函数中,用户也可以通过改变传入参数 `tau`、`steps`、`order` 来实现对任意阶 Suzuki product formula 电路的创建。下面,我们就利用该函数的这一功能来验证一下 Suzuki product formula 的误差。\n",
"\n",
"继续使用先前的哈密顿量:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "86de7992",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"H = 1.0 Z0, Z1\n",
"1.0 X0\n",
"1.0 X1\n"
]
}
],
"source": [
"print('H =', h_2)"
]
},
{
"cell_type": "markdown",
"id": "2e183412",
"metadata": {},
"source": [
"这里我们通过改变 `tau` 和 `steps` 两个参数来将 $t=1$ 的演化过程进行拆分。(提示:$\\tau \\cdot n_{\\rm steps} = t$)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "240ed09f",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--*-----------------*----Rx(0.667)----*-----------------*----Rx(0.667)----*-----------------*----Rx(0.667)--\n",
" | | | | | | \n",
"--x----Rz(0.667)----x----Rx(0.667)----x----Rz(0.667)----x----Rx(0.667)----x----Rz(0.667)----x----Rx(0.667)--\n",
" \n",
"电路的酉算符和时间演化算符之间的保真度为: 0.984\n"
]
}
],
"source": [
"# 将长度为 t 的时间演化过程拆为 r 份\n",
"r = 3\n",
"t = 1\n",
"cir = UAnsatz(2)\n",
"# 搭建时间演化电路,tau 为每个“时间块”的演化时间,故为 t/r,steps 是“时间块”的重复次数 r\n",
"construct_trotter_circuit(cir, h_2, tau=t/r, steps=r)\n",
"print(cir)\n",
"print('电路的酉算符和时间演化算符之间的保真度为: %.3f' \n",
" % gate_fidelity(cir.U.numpy(), linalg.expm(-1 * 1j * h_2.construct_h_matrix())))"
]
},
{
"cell_type": "markdown",
"id": "4708cad9",
"metadata": {},
"source": [
"我们发现,通过将 $t=1$ 的模拟时间拆分为三个“时间块”,模拟误差被成功减小了。\n",
"\n",
"如果进一步地增加拆分后的“时间块”的数量的话,误差还可以被进一步减小:"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "abc3742d",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"# 导入一些作图和计算误差需要的额外包\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"\n",
"def get_fid(n_steps):\n",
" t = 1\n",
" cir = UAnsatz(2)\n",
" construct_trotter_circuit(cir, h_2, tau=t/n_steps, steps=n_steps)\n",
" return gate_fidelity(cir.U.numpy(), linalg.expm(-1 * 1j * h_2.construct_h_matrix()))\n",
"plt.axhline(1, ls='--', color='black')\n",
"plt.semilogy(np.arange(1, 11), [get_fid(r) for r in np.arange(1, 11)], 'o-')\n",
"plt.xlabel('number of steps', fontsize=12)\n",
"plt.ylabel('fidelity', fontsize=12)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "f8381126",
"metadata": {},
"source": [
"不仅如此,我们还可以通过改变 product formula 的阶数来减小模拟误差。目前,`construct_trotter_circuit()` 函数支持通过参数 `order` 来实现任意阶数的 Suzuki product formula。下面,就让我们来分别计算一下一阶和二阶时间演化电路的误差随着 $\\tau$ 大小的变化,并和上文中计算的理论误差上界进行对比:"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "d3256b18",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"# 计算两个酉矩阵之间的 L1 谱距离,即 (5) 中定义的误差\n",
"def calculate_error(U1, U2):\n",
" return np.abs(linalg.eig(U1 - U2)[0]).max()\n",
"\n",
"# 封装计算误差的函数,自由参数为每个时间块的长度 tau 和 product formula 的阶数 order\n",
"def calculate_total_error(tau, order=1):\n",
" # 注意因为在电路中的多 Pauli 旋转门时会引入一个额外的全局相位,需要在计算误差时扣除\n",
" # 补充:该全局相位不会对实际的量子态产生任何可观测的影响,只是计算理论误差时需要扣除\n",
" h_2 = Hamiltonian([[1, 'Z0, Z1'], [1, 'X0'], [1, 'X1']])\n",
" cir = UAnsatz(2)\n",
" # 应为总时长为 1,故 steps = int(1/tau),注意传入的 tau 需要可以整除 1\n",
" construct_trotter_circuit(cir, h_2, tau=tau, steps=int(1/tau), order=order)\n",
" cir_U = cir.U.numpy()\n",
" U_evolve = np.exp(1j) * linalg.expm(-1 * 1j * h_2.construct_h_matrix()) #理论上的时间演化算符加上一个全局相位\n",
" return calculate_error(cir_U, U_evolve)\n",
"\n",
"# 不同的参数 tau,注意它们需要可以整除 1\n",
"taus = np.array([0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1])\n",
"errors = []\n",
"\n",
"# 记录对应不同 tau 的总误差\n",
"for tau in taus:\n",
" errors.append(calculate_total_error(tau)) \n",
"\n",
"# 可视化结果\n",
"plt.loglog(taus, errors, 'o-', label='error')\n",
"plt.loglog(taus, (3 * taus**2 * (1/taus) * np.exp(3 * taus)), label='bound') # 按照 (10) 计算的一阶误差上界\n",
"plt.legend()\n",
"plt.xlabel(r'$\\tau$', fontsize=12)\n",
"plt.ylabel(r'$\\Vert U_{\\rm cir} - \\exp(-iHt) \\Vert$', fontsize=12)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "2fdbf168",
"metadata": {},
"source": [
"下面,我们将 `order` 设置为 2,并计算二阶 product formula 的误差:"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "a3db610c",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"taus = np.array([0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1])\n",
"errors = []\n",
"\n",
"for tau in taus:\n",
" errors.append(calculate_total_error(tau, order=2)) # 通过 order=2 指定 Suzuki 分解的阶数 \n",
"\n",
"plt.loglog(taus, errors, 'o-', label='error')\n",
"plt.loglog(taus, (2 * taus * 3 )**3 / 3 * (1/taus) * np.exp(3 * taus), '-', label='bound') # 按照 (12) 计算的二阶误差上界\n",
"plt.legend()\n",
"plt.xlabel(r'$\\tau$', fontsize=12)\n",
"plt.ylabel(r'$\\Vert U_{\\rm cir} - \\exp(-iHt) \\Vert$', fontsize=12)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "8c468fd4",
"metadata": {},
"source": [
"可以看到,实际计算出的模拟误差都小于其理论上界,说明这样的结果是符合预期的。\n",
"\n",
"## 小结\n",
"\n",
"本教程介绍了 Paddle Quantum 中构建模拟时间演化电路的功能,并对其背后的理论基础——Suzuki product formula 进行了介绍。利用该功能,用户可以通过搭建对于自定义哈密顿量的任意阶 product formula 电路来模拟不同物理系统的时间演化过程。\n",
"\n",
"量子模拟本身是一个比较宽泛的话题,其应用也十分广泛:凝聚态物理中的多体局域化、时间晶体、高温超导、拓扑序的研究;量子化学中的分子动力学模拟、反应模拟;高能物理中的场论模拟;乃至核物理和宇宙学中的相关研究。本教程中介绍的 Suzuki product formula 与基于通用量子计算机的数字量子模拟只是量子模拟的一部分,许多不基于通用量子计算机的量子模拟器,例如在冷原子、超导、离子阱以及光子等平台上做的模拟量子模拟(analogue quantum simulation)也是一个十分重要的方向。鉴于篇幅,本教程中无法进一步对这些内容逐一展开介绍,感兴趣的读者可以阅读 14 年的这篇综述文章 [8]。对于一些更新的结果,也可以参考这篇中文综述 [9]。\n",
"\n",
"在后续的教程 [模拟一维海森堡链的自旋动力学](./SimulateHeisenberg_CN.ipynb) 中,我们以凝聚态物理中的自旋模型为例,进一步地展示了如何利用本文中介绍的内容来进行量子多体模型的动力学模拟。同时,该教程也将介绍如何搭建不基于 Suzuki 分解的自定义时间演化电路。"
]
},
{
"cell_type": "markdown",
"id": "01705a8d",
"metadata": {},
"source": [
"---\n",
"\n",
"## 参考资料\n",
"\n",
"[1] Feynman, R. P. \"Simulating physics with computers.\" International Journal of Theoretical Physics 21.6 (1982).\n",
" \n",
"[2] Lloyd, Seth. \"Universal quantum simulators.\" [Science (1996): 1073-1078](https://www.jstor.org/stable/2899535).\n",
"\n",
"[3] Childs, Andrew M., et al. \"Toward the first quantum simulation with quantum speedup.\" [Proceedings of the National Academy of Sciences 115.38 (2018): 9456-9461](https://www.pnas.org/content/115/38/9456.short).\n",
"\n",
"[4] Nielsen, Michael A., and Isaac Chuang. \"Quantum computation and quantum information.\" (2002): 558-559.\n",
"\n",
"[5] Tran, Minh C., et al. \"Destructive error interference in product-formula lattice simulation.\" [Physical Review Letters 124.22 (2020): 220502](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.124.220502).\n",
"\n",
"[6] Childs, Andrew M., and Yuan Su. \"Nearly optimal lattice simulation by product formulas.\" [Physical Review Letters 123.5 (2019): 050503](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.123.050503).\n",
"\n",
"[7] Campbell, Earl. \"Random compiler for fast Hamiltonian simulation.\" [Physical Review Letters 123.7 (2019): 070503](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.123.070503).\n",
"\n",
"[8] Georgescu, Iulia M., Sahel Ashhab, and Franco Nori. \"Quantum simulation.\" [Reviews of Modern Physics 86.1 (2014): 153](https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.86.153).\n",
"\n",
"[9] 范桁. \"量子计算与量子模拟.\" [物理学报 67.12(2018):16-25](http://wulixb.iphy.ac.cn/article/id/72211)."
]
}
],
"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.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
{
"cells": [
{
"cell_type": "markdown",
"id": "7d09a637",
"metadata": {},
"source": [
"# Hamiltonian Simulation with Product Formula\n",
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
]
},
{
"cell_type": "markdown",
"id": "280b8be1",
"metadata": {},
"source": [
"## Overview\n",
"\n",
"In quantum mechanics, a system's energy is described with a Hamiltonian operator $H$. Solving all or partial properties of the Hamiltonian of a given system constitutes a central problem in many disciplines, including condensed matter physics, computational chemistry, high-energy physics, etc. However, the degrees of freedom of a quantum system grow exponentially with its system size, which leads to the inability to effectively simulate quantum systems using classical computers - the quantum state of several hundred qubits cannot be directly stored even with all the storage resources in the world. Unlike classical computers, quantum computers perform all operations directly on the exponentially large Hilbert space, thus having a natural advantage over classical computer when simulating a quantum system. Matter of fact, designing a controlled quantum system to efficiently simulate quantum systems in nature was Feynman's original idea when he first introduced the concept of quantum computing in the 1980s:\n",
" \n",
"> _\"Nature isn't classical, dammit, and if you want to make a simulation of nature, you'd better make it quantum mechanical, and by golly it's a wonderful problem, because it doesn't look so easy.\"_\n",
">\n",
"> --- \"Simulating physics with computers\", 1982, Richard P. Feynman [1]\n",
"\n",
"\n",
"The development of universal quantum computers and a series of quantum simulators has made it possible to realize Feynman's vision. Digital quantum simulation on a universal quantum computer (i.e. quantum simulation by constructing quantum circuits through quantum gates) is considered to be to have the largest potential due to its scalability and generality.\n",
"\n",
"In this tutorial, we introduce Hamiltonian simulation in Paddle Quantum. It will be divided into three parts:\n",
"1. How to construct a system's Hamiltonian using `Hamiltonian` class.\n",
"2. How to create the time-evolving circuit with `construct_trotter_circuit()` function.\n",
"3. The Suzuki product formula algorithm and how to create its corresponding circuit up to arbitrary order.\n",
"\n",
"## Define the system's Hamiltonian \n",
"Before demoing how to construct a time-evolving circuit, we will first introduce to readers how to construct a `Hamiltonian` object in Paddle Quantum. Users could create a `Hamiltonian` object by specifying a list of Pauli string containing the coefficients and Pauli operators of each term. First let's consider a simple Hamiltonian:\n",
"\n",
"$$\n",
"H = Z \\otimes Z\n",
"\\tag{1}\n",
"$$\n",
"\n",
"This Hamiltonian describes a simple interaction between two qubits: when both qubits are in $|0\\rangle$ or $|1\\rangle$ , the energy of the system is $+1$; conversely when the two qubits are in different states, the energy of the system is $-1$.\n",
"\n",
"The user could construct this Hamiltonian by:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "b1546d21",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1.0 Z0, Z1\n"
]
}
],
"source": [
"from paddle_quantum.utils import Hamiltonian\n",
"\n",
"h = Hamiltonian([[1, 'Z0, Z1']])\n",
"print(h)"
]
},
{
"cell_type": "markdown",
"id": "99ff7830",
"metadata": {},
"source": [
"The `Hamiltonian` class in Paddle Quantum supports automatic merging of equal terms, addition, subtraction, indexing, and splitting:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "82fae2f8",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1.0 Z0, Z1\n"
]
}
],
"source": [
"h = Hamiltonian([[0.5, 'Z0, Z1'], [0.5, 'Z1, Z0']], compress=True)\n",
"print(h)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "37ea39a0",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"h + h: 2.0 Z0, Z1\n",
"h * 2: 2.0 Z0, Z1\n",
"h: 1.0 Z0, Z1\n"
]
}
],
"source": [
"print('h + h:', h + h)\n",
"print('h * 2:', h * 2)\n",
"print('h:', h[:])"
]
},
{
"cell_type": "markdown",
"id": "34438c9e",
"metadata": {},
"source": [
"The `decompose_pauli_words()` and `decompose_with_sites()` methods can decompose the Hamiltonian into more manageable forms:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "8f292d61",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Pauli words decomposition: ([1.0], ['ZZ'])\n",
"Pauli with sites decomposition: ([1.0], ['ZZ'], [[0, 1]])\n"
]
}
],
"source": [
"print('Pauli words decomposition:', h.decompose_pauli_words())\n",
"print('Pauli with sites decomposition:', h.decompose_with_sites())"
]
},
{
"cell_type": "markdown",
"id": "10254485",
"metadata": {},
"source": [
"In addition, `construct_h_matrix()` will construct its matrix in the $Z$ Pauli basis:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "73dac520",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"matrix([[ 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]])"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"h.construct_h_matrix()"
]
},
{
"cell_type": "markdown",
"id": "3a320cb9",
"metadata": {},
"source": [
"## Simulate the time evolution\n",
"\n",
"According to one of the fundamental axioms of quantum mechanics, the evolution of a system over time can be described by\n",
"\n",
"$$\n",
"i \\hbar \\frac{\\partial}{\\partial t} | \\psi \\rangle = H | \\psi \\rangle,\n",
"\\tag{2}\n",
"$$\n",
"\n",
"$\\hbar$ is the reduced Planck constant. This equation is the well-known Schrödinger equation. Thus, for a time independent Hamiltonian, the time evolution equation of the system can be written as\n",
"\n",
"$$\n",
"|\\psi(t) \\rangle = U(t) | \\psi (0) \\rangle, ~ U(t) = e^{- i H t}.\n",
"\\tag{3}\n",
"$$\n",
"\n",
"Here we take the natural unit $\\hbar=1$ and $U(t)$ is the time evolution operator. The idea of simulating the time evolution process with quantum circuits is to approximate this time evolution operator using the unitary transformation constructed by quantum circuits. In Paddle Quantum, we provide the `construct_trotter_circuit(circuit, Hamiltonian)` function to construct a time-evolving circuit corresponding to a Hamiltonian. Now, let us test it with the Hamiltonian we just constructed:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "f983834e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--*-----------------*--\n",
" | | \n",
"--x----Rz(2.000)----x--\n",
" \n"
]
}
],
"source": [
"from paddle_quantum.trotter import construct_trotter_circuit\n",
"from paddle_quantum.circuit import UAnsatz\n",
"\n",
"cir = UAnsatz(2)\n",
"construct_trotter_circuit(cir, h, tau=1, steps=1) \n",
"print(cir)"
]
},
{
"cell_type": "markdown",
"id": "988ffa8b",
"metadata": {},
"source": [
"We can see that a quantum circuit has been constructed for `h`, which can simulate the time evolution of arbitrary time length based on the input `tau`.\n",
"\n",
"By calculating the matrix form of this circuit, one can see that it is identical to the time evolution operator $e^{-iHt}$. Here, we use `gate_fidelity` to calculate the fidelity between the unitary matrix of the quantum circuit and the unitary matrix of the time evolution operator. These two processes are identical when the fidelity is equal to 1. We note that a more formal definition of simulation error will be introduced at the end of this section. For now, let's consider fidelity as the criteria of similarity between two evolution processes (unitary operators)."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "52f3dff9",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The fidelity is: 1.000\n"
]
}
],
"source": [
"from scipy import linalg\n",
"from paddle_quantum.utils import gate_fidelity\n",
"\n",
"# calculate the fidelity between e^{-iHt} and the unitary matrix of circuit\n",
"print('The fidelity is: %.3f' \n",
" % gate_fidelity(cir.U.numpy(), linalg.expm(-1 * 1j * h.construct_h_matrix())))"
]
},
{
"cell_type": "markdown",
"id": "075faf9a",
"metadata": {},
"source": [
"Actually, this is because any rotation associated with a tensor product of the pauli operators can be efficiently decomposed into a circuit. In this example, we could change the angle of the Rz gate to simulate any $e^{-i Z\\otimes Z t}$ evolutionary operator. Does this mean that the time-evolving operator of any Pauli Hamiltonian could be perfectly simulated with a quantum circuit? Unfortunately, the answer is negative. Let us consider a slightly more complicated Hamiltonian:\n",
"\n",
"$$\n",
"H = Z \\otimes Z + X \\otimes I + I \\otimes X.\n",
"\\tag{4}\n",
"$$\n",
"\n",
"Similarly, let's use `construct_trotter_circuit` to construct its corresponding time-evolving circuit:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "8ec1213d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--*-----------------*----Rx(2.000)--\n",
" | | \n",
"--x----Rz(2.000)----x----Rx(2.000)--\n",
" \n",
"The fidelity is: 0.681\n"
]
}
],
"source": [
"h_2 = Hamiltonian([[1, 'Z0, Z1'], [1, 'X0'], [1, 'X1']]) # no need to write out unit operator\n",
"cir = UAnsatz(2)\n",
"construct_trotter_circuit(cir, h_2, tau=1, steps=1)\n",
"print(cir)\n",
"print('The fidelity is: %.3f' \n",
" % gate_fidelity(cir.U.numpy(), linalg.expm(-1 * 1j * h_2.construct_h_matrix())))"
]
},
{
"cell_type": "markdown",
"id": "f75bd0c7",
"metadata": {},
"source": [
"This time the fidelity is less than $1$, which means the circuit cannot correctly simulate the time-evolution process of the system.\n",
"\n",
"The reason is that, the unitary transformation of the circuit is $e^{-iZ\\otimes Z t} e^{-i (X\\otimes I + I\\otimes X)t}$, while the time evolution operator is $e^{-iZ\\otimes Z t - i(X\\otimes I + I\\otimes X)t}$. And for a quantum system, $e^{A+B} \\neq e^A e^B$ when $[A, B] \\neq 0$. Here, since $[X, Z] \\neq 0$, the corresponding unitary transformation of the circuit is not equal to the correct time-evolution operator.\n",
"\n",
"In addition to using the fidelity to describe the similarity between the quantum circuit and the time-evolving operator that one wishes to simulate, one can define the error $\\epsilon$ as follows\n",
"\n",
"$$\n",
"\\epsilon(U) = \\Vert e^{-iHt} - U \\Vert,\n",
"\\tag{5}\n",
"$$\n",
"\n",
"where $\\Vert \\cdot \\Vert$ denotes the mode of the largest eigen (singular) value. Such a definition better describes the deviation of the quantum state under different evolution operators and it is a more rigorous way to define the simulation time evolution error. We note that the simulation error of this form will be used repeatedly in the next section."
]
},
{
"cell_type": "markdown",
"id": "3109b3f5",
"metadata": {},
"source": [
"### Product formula and Suzuki decomposition\n",
"\n",
"In 1996, Seth Lloyd showed that the error in simulating time evolution can be reduced by splitting a whole evolution time $t$ into $r$ shorter \"time blocks\" [2]. Consider a more general Hamiltonian of the form $H = \\sum_{k=1}^{L} h_k$, where $h_k$ acts on a part of the system. By Taylor expansion, it is not difficult to find that the simulation error is a second-order term, i.e.\n",
"\n",
"$$\n",
"e^{-iHt} = \\prod_{k=1}^{L} e^{-i h_k t} + O(t^2).\n",
"\\tag{6}\n",
"$$\n",
"\n",
"Let $\\tau = t/r$ and consider the evolution operator $\\left(e^{-iH \\tau}\\right)^r$, then its error can be derived from \n",
"\n",
"$$\n",
"e^{-iHt} = \\left(e^{-iH \\tau}\\right)^r = \\left(\\prod_{k=1}^{L} e^{-i h_k \\tau} + O(\\tau^2) \\right)^r = \\left(\\prod_{k=1}^{L} e^{-i h_k \\tau} \\right)^r + O\\left(\\frac{t^2}{r}\\right).\n",
"\\tag{7}\n",
"$$\n",
"\n",
"The above equation states that an arbitrarily high simulation accuracy can be achieved by splitting the whole evolution time into enough \"pieces\". This is the basic idea of the product formula. However, the error given in (7) is only a rough estimate. In practice, in order to estimate the depth of the quantum circuit required to achieve a certain simulation accuracy, an exact upper bound on the error needs to be further computed. In the following, we show a relatively abbreviated procedure for calculating the error upper bound and readers who are not interested in details can skip to the conclusion on the error bound in (11).\n",
"\n",
"Let us note the remainder Taylor expansion of the function $f$ up to order $k$ to be $\\mathcal{R}_k(f)$. And the two following statements are needed for the calculating of the error bound:\n",
"\n",
"$$\n",
"\\left\\Vert \\mathcal{R}_k \\left( \\prod_{k=1}^{L} \\exp (-i h_k \\tau) \\right) \\right\\Vert\n",
"\\leq\n",
"\\mathcal{R}_k \\left( \\exp \\left( \\sum_{k=1}^{L} \\vert \\tau \\vert \\cdot \\Vert h_k \\Vert \\right) \\right),\n",
"\\tag{8}\n",
"$$\n",
"\n",
"$$\n",
"\\vert \\mathcal{R}_k(\\exp (\\alpha)) \\vert \\leq \\frac{\\vert \\alpha \\vert^{k+1}}{(k+1)!} \\exp ( \\vert \\alpha \\vert ), ~\n",
"\\forall \\alpha \\in \\mathbb{C}.\n",
"\\tag{9}\n",
"$$\n",
"\n",
"We omit the proofs of these two statements due to length limitations and interested reader could refer to Section F.1 in [3]. As defined in (5), the simulation error can be written as\n",
"\n",
"$$\n",
"\\epsilon\\left(e^{-iH\\tau}, U_{\\rm circuit}\\right) = \\left \\Vert \\exp\\left(-i\\sum_{k=1}^L h_k \\tau\\right) - \\prod_{k=1}^{L} \\exp\\left(-i h_k \\tau \\right) \\right \\Vert.\n",
"\\tag{10}\n",
"$$\n",
"\n",
"We already know that simulation error is the reminder of the time-evolving operators' Taylor expansion to the first order. Then using (8), (9) and the triangular inequality, the upper bound on the error in (10) can be calculated as follows\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"\\left \\Vert \\exp\\left(-i\\sum_{k=1}^L h_k \\tau\\right) - \\prod_{k=1}^{L} \\exp\\left(-i h_k \\tau \\right) \\right \\Vert\n",
"=~&\n",
"\\left \\Vert \\mathcal{R}_1 \\left( \\exp \\left( -i \\sum_{k=1}^{L} h_k \\tau \\right) - \\prod_{k=1}^{L} \\exp (-i h_k \\tau) \\right) \\right \\Vert\n",
"\\\\\n",
"\\leq~&\n",
"\\left \\Vert \\mathcal{R}_1 \\left( \\exp \\left( -i \\sum_{k=1}^{L} h_k \\tau \\right) \\right) \\right \\Vert\n",
"+\n",
"\\left \\Vert \\mathcal{R}_1 \\left( \\prod_{k=1}^{L} \\exp (-i h_k \\tau) \\right) \\right \\Vert\n",
"\\\\\n",
"\\leq~ &\n",
"\\left \\Vert \\mathcal{R}_1 \\left( \\exp \\left( \\vert \\tau \\vert \\cdot \\left \\Vert \\sum_{k=1}^{L} h_k \\right \\Vert \\right) \\right) \\right \\Vert\n",
"+ \n",
"\\left \\Vert \\mathcal{R}_1 \\left( \\exp \\sum_{k=1}^{L} \\left( \\vert \\tau \\vert \\cdot \\Vert h_k \\Vert \\right) \\right) \\right \\Vert\n",
"\\\\\n",
"\\leq~&\n",
"2 \\mathcal{R}_1 \\left( \\exp ( \\vert \\tau \\vert L \\Lambda ) \\right )\n",
"\\\\\n",
"\\leq~&\n",
" ( \\vert \\tau \\vert L \\Lambda )^2 \\exp ( \\vert \\tau \\vert L \\Lambda ),\n",
"\\end{aligned}\n",
"\\tag{11}\n",
"$$\n",
"\n",
"with $\\Lambda = \\max_k \\Vert h_k \\Vert$. Considering the complete evolution time $t = r \\cdot \\tau$, the error when simulating a time evolution operator of length $t$ is\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"\\left \\Vert \\left ( \\exp\\left(-i\\sum_{k=1}^L h_k \\tau \\right)\\right)^r - \\left (\\prod_{k=1}^{L} \\exp\\left(-i h_k \\tau \\right) \\right)^r \\right \\Vert\n",
"\\leq ~&\n",
"r \\left \\Vert \\exp\\left(-i\\sum_{k=1}^L h_k \\tau\\right) - \\prod_{k=1}^{L} \\exp\\left(-i h_k \\tau \\right) \\right \\Vert\n",
"\\\\\n",
"=~& r ( \\tau L \\Lambda )^2 \\exp ( \\vert \\tau \\vert L \\Lambda )\n",
"\\\\\n",
"=~& \\frac{( t L \\Lambda )^2}{r} \\exp \\left( \\frac{\\vert t \\vert L \\Lambda}{r} \\right).\n",
"\\end{aligned}\n",
"\\tag{12}\n",
"$$\n",
"\n",
"Here we use the conclusion of linear accumulation of errors in quantum circuits, i.e. $\\Vert U^r - V^r \\Vert \\leq r\\Vert U - V \\Vert$, and the reader who is not familiar with this conclusion can refer to Section 4.5.3 in [4]. At this point, we have calculated an upper bound on the simulation error of the product formula for a complete period of evolution time $t$, i.e., the second-order term $O(t^2/r)$ in Eq. (7). \n",
"\n",
"In fact, we can further optimize the simulation accuracy for the time-evolution operator $e^{-iH\\tau}$ within each \"time block\" by the Suzuki decomposition. For the Hamiltonian $H = \\sum_{k=1}^{L} h_k$, the Suzuki decomposition of the time evolution operator can be written as\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"S_1(\\tau) &= \\prod_{k=0}^L \\exp ( -i h_k \\tau),\n",
"\\\\\n",
"S_2(\\tau) &= \\prod_{k=0}^L \\exp ( -i h_k \\frac{\\tau}{2})\\prod_{k=L}^0 \\exp ( -i h_k \\frac{\\tau}{2}),\n",
"\\\\\n",
"S_{2k}(\\tau) &= [S_{2k - 2}(p_k\\tau)]^2S_{2k - 2}\\left( (1-4p_k)\\tau\\right)[S_{2k - 2}(p_k\\tau)]^2,\n",
"\\end{aligned}\n",
"\\tag{13}\n",
"$$\n",
"\n",
"For $k > 1, k\\in\\mathbb{Z}$, where $p_k = 1/(4 - 4^{1/(2k - 1)})$. The previously derived product formula actually uses only the first-order Suzuki decomposition $S_1(\\tau)$ to simulate each \"time block\". Therefore it's also known as the first-order Suzuki product formula, or simply the first-order product formula. In some scenarios, the Suzuki product formula is also referred to as the Trotter-Suzuki decomposition. For higher-order product formulas, using similar calculations as in (10-12), it can be shown that the error bound on the error for the $2k$th order product formula is:\n",
"\n",
"$$\n",
"\\epsilon\\left(e^{-iHt}, \\left(S_{2k}(\\tau)\\right)^r\\right)\n",
"\\leq\n",
"\\frac{(2L5^{k-1}\\Lambda\\vert t \\vert)^{2k+1}}{3r^{2k}} \\exp \\left( \\frac{2L5^{k-1}\\Lambda\\vert t \\vert}{r} \\right),\n",
"~ k > 1.\n",
"\\tag{14}\n",
"$$\n",
"\n",
"With the upper bound on the simulation error obtained, it is possible to further calculate the lower bound on the circuit depth required to reach a certain minimum accuracy $\\epsilon$. It should be noted that the error bounds given in (12) and (14) are calculated very loosely. In recent years, many works have gone further to give tighter upper bounds [3, 5-6]. In addition, product formulas that are not based on the Suzuki decomposition have also been proposed [7]."
]
},
{
"cell_type": "markdown",
"id": "f3bf6fb2",
"metadata": {},
"source": [
"![image.png](./figures/trotter_suzuki_circuit.png)\n",
"<div style=\"text-align:center\">Fig 1: The circuit of Suzuki product formula </div>"
]
},
{
"cell_type": "markdown",
"id": "0b3ff06a",
"metadata": {},
"source": [
"### Verification of Suzuki-product formula-based time-evolving circuits using Paddle Quantum\n",
"\n",
"Although the upper bound on the error of the Suzuki-product formula has been continuously optimized, in practice, the real error is often different from the theoretical upper bound, i.e., the theoretical product formula error that we can calculate now is still only a very loose upper bound [3]. Therefore, for a real physical system, we often need to calculate the real error through numerical experiments to give an empirical bound on the error. Such numerical experiments are important as they could be used to determine the circuit depth needed to simulate a certain time evolution process at a certain accuracy.\n",
"\n",
"In the `construct_trotter_circuit` function, It constructs by default a circuit based on the first order product formula. Users can create simulation circuits of higher order product formulas with more time blocks by manipulating the arguments `tau`, `steps`, `order`. As the last part of this tutorial, we will demonstrate how these options work in Paddle Quantum.\n",
"\n",
"Using the previous Hamiltonian:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "b1609917",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"H = 1.0 Z0, Z1\n",
"1.0 X0\n",
"1.0 X1\n"
]
}
],
"source": [
"print('H =', h_2)"
]
},
{
"cell_type": "markdown",
"id": "a6ecb816",
"metadata": {},
"source": [
"Here we split the evolution of $t=1$ by changing the `tau` and `steps`. (Hint: $\\tau \\cdot n_{\\rm steps} = t$)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "e559b726",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--*-----------------*----Rx(0.667)----*-----------------*----Rx(0.667)----*-----------------*----Rx(0.667)--\n",
" | | | | | | \n",
"--x----Rz(0.667)----x----Rx(0.667)----x----Rz(0.667)----x----Rx(0.667)----x----Rz(0.667)----x----Rx(0.667)--\n",
" \n",
"The fidelity is: 0.984\n"
]
}
],
"source": [
"# Split the time evolution process of length t into r blocks\n",
"r = 3\n",
"t = 1\n",
"cir = UAnsatz(2)\n",
"# Construct the time evolution circuit, tau is the evolution time of each \"time block\", i.e. t/r\n",
"# Steps is the number of repetitions of the \"time block\" r\n",
"construct_trotter_circuit(cir, h_2, tau=t/r, steps=r)\n",
"print(cir)\n",
"print('The fidelity is: %.3f' \n",
" % gate_fidelity(cir.U.numpy(), linalg.expm(-1 * 1j * h_2.construct_h_matrix())))"
]
},
{
"cell_type": "markdown",
"id": "5b94d8ec",
"metadata": {},
"source": [
"We can see that by splitting the simulation time of $t=1$ into three \"time blocks\", the simulation error was successfully reduced.\n",
"\n",
"The error could be further reduced if we further split the evolution process into more 'pieces':"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "95e891f9",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAZ4AAAEJCAYAAACkH0H0AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8rg+JYAAAACXBIWXMAAAsTAAALEwEAmpwYAAAjuklEQVR4nO3deZhcZZn38e9d1XvS6YTsGySQEAhRyYCoOKCvgWExCOMCRMFBHEEcZwZfwAFceB0QGVGHmVEYcWBwhbBmAFEQGGFEXgyQKKksJIQl6UrIXtXd6U53V93zR1V3ek91uvucWn6f6+qrqs45dc5dlU79+nnOU88xd0dERCQokbALEBGR0qLgERGRQCl4REQkUAoeEREJlIJHREQCVRZ2AYVgwoQJPmvWrLDLEBEpKC+99NIOd5/Yc7mCJwezZs3ixRdfDLsMEZGCYmZv9rVcXW0iIhIoBY+IiARKwSMiIoFS8IiISKAUPCIiEqiSG9VmZqOAW4FW4Lfu/vOQSxIRKSlFETxmdiewGNjm7gu6LD8d+BcgCvyHu98EfBS4390fMbOlgIJHRFi2op6bH19HfE8z08ZWc9Vp8zhn4fSSqyGIOqwYLotgZicDjcBPOoLHzKLAq8CpwGZgObAEOBv4lbuvNLNfuPsnD7T/2tpaP+6447otO/fcc/nCF77A3r17OfPMM3s956KLLuKiiy5ix44dfPzjH++1/rLLLuO8885j06ZNXHjhhb3WX3HFFZx11lmsW7eOSy+9tNf6r371q5xyyimsXLmSyy+/vNf6G2+8kRNPPJHf//73XHvttb3W33LLLRx77LE8+eST3HDDDb3W//CHP2TevHk88sgjfPe73+21/qc//SkzZ85k6dKl3Hbbbb3W33///UyYMIG77rqLu+66q9f6xx57jJqaGm699VbuvffeXut/+9vfAvCd73yHRx99tNu66upqfvWrXwFw/fXX89RTT3VbP378eB544AEArrnmGp5//vlu62fMmMHPfvYzAC6//HJWrlzZbf2RRx7J7bffDsAll1zCq6++2m39scceyy233ALABRdcwObNm7utf9/73se3vvUtAD72sY+xc+fObusXLVrE1772NQDOOOMMmpubu61fvHgxV155JQAf/OAH6Wm4fvfueOpP3PjoKlIVY4i2Jhn31rOM3rk28N+9xvFHsfvQkzvr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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"# Import the required packages\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"\n",
"def get_fid(n_steps):\n",
" t = 1\n",
" cir = UAnsatz(2)\n",
" construct_trotter_circuit(cir, h_2, tau=t/n_steps, steps=n_steps)\n",
" return gate_fidelity(cir.U.numpy(), linalg.expm(-1 * 1j * h_2.construct_h_matrix()))\n",
"plt.axhline(1, ls='--', color='black')\n",
"plt.semilogy(np.arange(1, 11), [get_fid(r) for r in np.arange(1, 11)], 'o-')\n",
"plt.xlabel('number of steps', fontsize=12)\n",
"plt.ylabel('fidelity', fontsize=12)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "a6b86ebb",
"metadata": {},
"source": [
"In addition, we can reduce the simulation error by changing the order of the product formula. Currently, the `construct_trotter_circuit()` supports the Suzuki product formula of any order using the argument `order`. Let us calculate the errors of the first and second-order time-evolving circuits separately, observe their variation with $\\tau$. Then compare them with the theoretical upper bounds calculated above:"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "23fd4bee",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"# Calculate the L1 spectral distance between the two unitary matrices, i.e. the error defined in (5)\n",
"def calculate_error(U1, U2):\n",
" return np.abs(linalg.eig(U1 - U2)[0]).max()\n",
"\n",
"# Encapsulates the function that calculates the error, \n",
"# with the free parameters being the length of each time block tau and the order of the product formula\n",
"def calculate_total_error(tau, order=1):\n",
" # An additional global phase needs to be subtracted from the error calculation because of the multiple Pauli rotating gates in the circuit\n",
" # This global phase does not have any observable effect on the actual quantum state, but only needs to be subtracted when calculating the theoretical error\n",
" h_2 = Hamiltonian([[1, 'Z0, Z1'], [1, 'X0'], [1, 'X1']])\n",
" cir = UAnsatz(2)\n",
" # A total time of 1, so steps = int(1/tau), the input tau needs to be divisible by 1\n",
" construct_trotter_circuit(cir, h_2, tau=tau, steps=int(1/tau), order=order)\n",
" cir_U = cir.U.numpy()\n",
" U_evolve = np.exp(1j) * linalg.expm(-1 * 1j * h_2.construct_h_matrix()) # Theoretical time evolution operator plus a global phase\n",
" return calculate_error(cir_U, U_evolve)\n",
"\n",
"# Different parameters tau, they need to be divisible by 1\n",
"taus = np.array([0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1])\n",
"errors = []\n",
"\n",
"# Record the total error corresponding to different tau\n",
"for tau in taus:\n",
" errors.append(calculate_total_error(tau))\n",
"\n",
"# print the graph\n",
"plt.loglog(taus, errors, 'o-', label='error')\n",
"plt.loglog(taus, (3 * taus**2 * (1/taus) * np.exp(3 * taus)), label='bound') # The first-order error upper bound calculated according to (10)\n",
"plt.legend()\n",
"plt.xlabel(r'$\\tau$', fontsize=12)\n",
"plt.ylabel(r'$\\Vert U_{\\rm cir} - \\exp(-iHt) \\Vert$', fontsize=12)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "9c30f384",
"metadata": {},
"source": [
"Next, we set `order` to 2 and calculate the error of the second-order product formula:"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "f9fd7378",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"taus = np.array([0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1])\n",
"errors = []\n",
"\n",
"for tau in taus:\n",
" errors.append(calculate_total_error(tau, order=2)) # Specify the order of the Suzuki decomposition \n",
"\n",
"plt.loglog(taus, errors, 'o-', label='error')\n",
"plt.loglog(taus, (2 * taus * 3 )**3 / 3 * (1/taus) * np.exp(3 * taus), '-', label='bound') # The upper bound of the second-order error calculated according to (12)\n",
"plt.legend()\n",
"plt.xlabel(r'$\\tau$', fontsize=12)\n",
"plt.ylabel(r'$\\Vert U_{\\rm cir} - \\exp(-iHt) \\Vert$', fontsize=12)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "f7fff06b",
"metadata": {},
"source": [
"As expected, the actual calculated simulation errors are all smaller than their theoretical upper bounds.\n",
"\n",
"## Conclusion\n",
"\n",
"This tutorial introduces how to construct a time-evolving circuit with Paddle Quantum and the theoretical basis behind it, i.e.the Suzuki product formula. Users can construct arbitrary order product formula circuits with custom Hamiltonian to simulate the time evolution of different physical systems.\n",
"\n",
"Quantum simulation is a vast subject and it covers a wide range of applications: the study of many-body localization, time crystals, high-temperature superconductivity, and topological order in condensed matter physics; molecular dynamics simulations and reaction simulations in quantum chemistry; field theory simulations in high-energy physics; even related applications in nuclear physics and cosmology. The Suzuki product formula and digital quantum simulations based on general-purpose quantum computers presented in this tutorial are only part of the quantum simulations. The quantum simulator not based on general-purpose quantum computers, such as analogue quantum simulations on cold atom, superconductor, ion trap and photon platforms also constitute very important topics. For readers who are interested on its applications and general background, we recommend this review [8]. \n",
"\n",
"In the subsequent tutorial [Simulating spin dynamics in one-dimensional Heisenberg chains](./SimulateHeisenberg_EN.ipynb), using the spin model in condensed matter physics as an example, we further show how to perform dynamics simulations of quantum many-body models. In the meantime, we also demonstrate how to design a customized time evolving circuit not based on the Suzuki decomposition.\n"
]
},
{
"cell_type": "markdown",
"id": "0705577b",
"metadata": {},
"source": [
"---\n",
"\n",
"## References\n",
"\n",
"[1] Feynman, R. P. \"Simulating physics with computers.\" International Journal of Theoretical Physics 21.6 (1982).\n",
" \n",
"[2] Lloyd, Seth. \"Universal quantum simulators.\" [Science (1996): 1073-1078](https://www.jstor.org/stable/2899535).\n",
"\n",
"[3] Childs, Andrew M., et al. \"Toward the first quantum simulation with quantum speedup.\" [Proceedings of the National Academy of Sciences 115.38 (2018): 9456-9461](https://www.pnas.org/content/115/38/9456.short).\n",
"\n",
"[4] Nielsen, Michael A., and Isaac Chuang. \"Quantum computation and quantum information.\" (2002): 558-559.\n",
"\n",
"[5] Tran, Minh C., et al. \"Destructive error interference in product-formula lattice simulation.\" [Physical Review Letters 124.22 (2020): 220502](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.124.220502).\n",
"\n",
"[6] Childs, Andrew M., and Yuan Su. \"Nearly optimal lattice simulation by product formulas.\" [Physical Review Letters 123.5 (2019): 050503](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.123.050503).\n",
"\n",
"[7] Campbell, Earl. \"Random compiler for fast Hamiltonian simulation.\" [Physical Review Letters 123.7 (2019): 070503](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.123.070503).\n",
"\n",
"[8] Georgescu, Iulia M., Sahel Ashhab, and Franco Nori. \"Quantum simulation.\" [Reviews of Modern Physics 86.1 (2014): 153](https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.86.153)."
]
}
],
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
-4.1404421397293865 I
-0.003292486727604039 X0, X1, Y2, Y3
0.0027936047318008615 X0, X1, Y2, Z3, Z4, Y5
0.002245954284966122 X0, X1, Y2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, Z10, Y11
0.0027936047318008615 X0, X1, X3, X4
0.0022459542849661215 X0, X1, X3, Z4, Z5, Z6, Z7, Z8, Z9, X10
-0.0054202368775521145 X0, X1, Y4, Y5
-0.0006223124637956709 X0, X1, Y4, Z5, Z6, Z7, Z8, Z9, Z10, Y11
-0.0006223124637956709 X0, X1, X5, Z6, Z7, Z8, Z9, X10
-0.0024544286331452587 X0, X1, Y6, Y7
-0.0024544286331452587 X0, X1, Y8, Y9
-0.0021781803494141097 X0, X1, Y10, Y11
0.003292486727604039 X0, Y1, Y2, X3
-0.0027936047318008615 X0, Y1, Y2, Z3, Z4, X5
-0.002245954284966122 X0, Y1, Y2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, Z10, X11
0.0027936047318008615 X0, Y1, Y3, X4
0.0022459542849661215 X0, Y1, Y3, Z4, Z5, Z6, Z7, Z8, Z9, X10
0.0054202368775521145 X0, Y1, Y4, X5
0.0006223124637956709 X0, Y1, Y4, Z5, Z6, Z7, Z8, Z9, Z10, X11
-0.0006223124637956709 X0, Y1, Y5, Z6, Z7, Z8, Z9, X10
0.0024544286331452587 X0, Y1, Y6, X7
0.0024544286331452587 X0, Y1, Y8, X9
0.0021781803494141097 X0, Y1, Y10, X11
0.014212924082351756 X0, Z1, X2
0.0008275419052443474 X0, Z1, X2, X3, Z4, X5
0.001138507213375294 X0, Z1, X2, X3, Z4, Z5, Z6, Z7, Z8, Z9, Z10, X11
0.0008275419052443474 X0, Z1, X2, Y3, Z4, Y5
0.001138507213375294 X0, Z1, X2, Y3, Z4, Z5, Z6, Z7, Z8, Z9, Z10, Y11
-0.0015182344970129586 X0, Z1, X2, Z3
0.0013341650712146113 X0, Z1, X2, X4, Z5, Z6, Z7, Z8, Z9, X10
0.0010793325643585813 X0, Z1, X2, Y4, Z5, Z6, Z7, Z8, Z9, Y10
0.0027794471332139347 X0, Z1, X2, Z4
0.0008981432433547795 X0, Z1, X2, X5, Z6, Z7, Z8, Z9, Z10, X11
0.0008981432433547795 X0, Z1, X2, Y5, Z6, Z7, Z8, Z9, Z10, Y11
0.002737536679929071 X0, Z1, X2, Z5
0.0029502410101886686 X0, Z1, X2, Z6
0.0010810228556545672 X0, Z1, X2, Z7
0.0029502410101886686 X0, Z1, X2, Z8
0.0010810228556545672 X0, Z1, X2, Z9
-0.0007615223847089812 X0, Z1, X2, Z10
-0.0007860222944566732 X0, Z1, X2, Z11
0.00025483250685603065 X0, Z1, Y2, Y4, Z5, Z6, Z7, Z8, Z9, X10
-4.191045328486368e-05 X0, Z1, Z2, X3, Y4, Y5
0.00018118932100380194 X0, Z1, Z2, X3, Y4, Z5, Z6, Z7, Z8, Z9, Z10, Y11
0.000436021827859832 X0, Z1, Z2, X3, X5, Z6, Z7, Z8, Z9, X10
-0.0018692181545341014 X0, Z1, Z2, X3, Y6, Y7
-0.0018692181545341014 X0, Z1, Z2, X3, Y8, Y9
-2.449990974769152e-05 X0, Z1, Z2, X3, Y10, Y11
4.191045328486368e-05 X0, Z1, Z2, Y3, Y4, X5
-0.00018118932100380194 X0, Z1, Z2, Y3, Y4, Z5, Z6, Z7, Z8, Z9, Z10, X11
0.000436021827859832 X0, Z1, Z2, Y3, Y5, Z6, Z7, Z8, Z9, X10
0.0018692181545341014 X0, Z1, Z2, Y3, Y6, X7
0.0018692181545341014 X0, Z1, Z2, Y3, Y8, X9
2.449990974769152e-05 X0, Z1, Z2, Y3, Y10, X11
-0.02529257759921138 X0, Z1, Z2, Z3, X4
0.0010951171602220137 X0, Z1, Z2, Z3, X4, X5, Z6, Z7, Z8, Z9, Z10, X11
0.0010951171602220137 X0, Z1, Z2, Z3, X4, Y5, Z6, Z7, Z8, Z9, Z10, Y11
0.00044990608835842813 X0, Z1, Z2, Z3, X4, Z5
-0.0038099599110423352 X0, Z1, Z2, Z3, X4, Z6
-0.0012449194492991436 X0, Z1, Z2, Z3, X4, Z7
-0.0038099599110423352 X0, Z1, Z2, Z3, X4, Z8
-0.0012449194492991436 X0, Z1, Z2, Z3, X4, Z9
-0.003912147523847341 X0, Z1, Z2, Z3, X4, Z10
-0.0028333077583638697 X0, Z1, Z2, Z3, X4, Z11
0.002565040461743191 X0, Z1, Z2, Z3, Z4, X5, Y6, Y7
0.002565040461743191 X0, Z1, Z2, Z3, Z4, X5, Y8, Y9
0.0010788397654834714 X0, Z1, Z2, Z3, Z4, X5, Y10, Y11
-0.002565040461743191 X0, Z1, Z2, Z3, Z4, Y5, Y6, X7
-0.002565040461743191 X0, Z1, Z2, Z3, Z4, Y5, Y8, X9
-0.0010788397654834714 X0, Z1, Z2, Z3, Z4, Y5, Y10, X11
-0.0015308305961943033 X0, Z1, Z2, Z3, Z4, Z5, X6, X7, Z8, Z9, Z10, X11
-0.0015308305961943033 X0, Z1, Z2, Z3, Z4, Z5, X6, Y7, Z8, Z9, Z10, Y11
-0.0015308305961943033 X0, Z1, Z2, Z3, Z4, Z5, Z6, Z7, X8, X9, Z10, X11
-0.0015308305961943033 X0, Z1, Z2, Z3, Z4, Z5, Z6, Z7, X8, Y9, Z10, Y11
-0.0018597599595985982 X0, Z1, Z2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, X10
0.0007955178141925985 X0, Z1, Z2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, X10, Z11
-0.00016055712026931797 X0, Z1, Z2, Z3, Z4, Z5, Z6, Z7, Z8, X10
-0.0016913877164636216 X0, Z1, Z2, Z3, Z4, Z5, Z6, Z7, Z9, X10
-0.00016055712026931797 X0, Z1, Z2, Z3, Z4, Z5, Z6, Z8, Z9, X10
-0.0016913877164636216 X0, Z1, Z2, Z3, Z4, Z5, Z7, Z8, Z9, X10
-0.0026357203426690735 X0, Z1, Z2, Z3, Z4, Z6, Z7, Z8, Z9, X10
-0.0015406031824470607 X0, Z1, Z2, Z3, Z5, Z6, Z7, Z8, Z9, X10
-0.003925635716000991 X0, Z1, Z2, X4
0.0017319528638563967 X0, Z1, Z2, Z4, Z5, Z6, Z7, Z8, Z9, X10
-0.003098093810756644 X0, Z1, Z3, X4
0.0028704600772316903 X0, Z1, Z3, Z4, Z5, Z6, Z7, Z8, Z9, X10
0.027763126875232892 X0, X2
-0.03467415516652979 X0, Z2, Z3, X4
-0.013546869617431397 X0, Z2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, X10
0.003292486727604039 Y0, X1, X2, Y3
-0.0027936047318008615 Y0, X1, X2, Z3, Z4, Y5
-0.002245954284966122 Y0, X1, X2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, Z10, Y11
0.0027936047318008615 Y0, X1, X3, Y4
0.0022459542849661215 Y0, X1, X3, Z4, Z5, Z6, Z7, Z8, Z9, Y10
0.0054202368775521145 Y0, X1, X4, Y5
0.0006223124637956709 Y0, X1, X4, Z5, Z6, Z7, Z8, Z9, Z10, Y11
-0.0006223124637956709 Y0, X1, X5, Z6, Z7, Z8, Z9, Y10
0.0024544286331452587 Y0, X1, X6, Y7
0.0024544286331452587 Y0, X1, X8, Y9
0.0021781803494141097 Y0, X1, X10, Y11
-0.003292486727604039 Y0, Y1, X2, X3
0.0027936047318008615 Y0, Y1, X2, Z3, Z4, X5
0.002245954284966122 Y0, Y1, X2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, Z10, X11
0.0027936047318008615 Y0, Y1, Y3, Y4
0.0022459542849661215 Y0, Y1, Y3, Z4, Z5, Z6, Z7, Z8, Z9, Y10
-0.0054202368775521145 Y0, Y1, X4, X5
-0.0006223124637956709 Y0, Y1, X4, Z5, Z6, Z7, Z8, Z9, Z10, X11
-0.0006223124637956709 Y0, Y1, Y5, Z6, Z7, Z8, Z9, Y10
-0.0024544286331452587 Y0, Y1, X6, X7
-0.0024544286331452587 Y0, Y1, X8, X9
-0.0021781803494141097 Y0, Y1, X10, X11
0.00025483250685603065 Y0, Z1, X2, X4, Z5, Z6, Z7, Z8, Z9, Y10
0.014212924082351756 Y0, Z1, Y2
0.0008275419052443474 Y0, Z1, Y2, X3, Z4, X5
0.001138507213375294 Y0, Z1, Y2, X3, Z4, Z5, Z6, Z7, Z8, Z9, Z10, X11
0.0008275419052443474 Y0, Z1, Y2, Y3, Z4, Y5
0.001138507213375294 Y0, Z1, Y2, Y3, Z4, Z5, Z6, Z7, Z8, Z9, Z10, Y11
-0.0015182344970129586 Y0, Z1, Y2, Z3
0.0010793325643585813 Y0, Z1, Y2, X4, Z5, Z6, Z7, Z8, Z9, X10
0.0013341650712146113 Y0, Z1, Y2, Y4, Z5, Z6, Z7, Z8, Z9, Y10
0.0027794471332139347 Y0, Z1, Y2, Z4
0.0008981432433547795 Y0, Z1, Y2, X5, Z6, Z7, Z8, Z9, Z10, X11
0.0008981432433547795 Y0, Z1, Y2, Y5, Z6, Z7, Z8, Z9, Z10, Y11
0.002737536679929071 Y0, Z1, Y2, Z5
0.0029502410101886686 Y0, Z1, Y2, Z6
0.0010810228556545672 Y0, Z1, Y2, Z7
0.0029502410101886686 Y0, Z1, Y2, Z8
0.0010810228556545672 Y0, Z1, Y2, Z9
-0.0007615223847089812 Y0, Z1, Y2, Z10
-0.0007860222944566732 Y0, Z1, Y2, Z11
4.191045328486368e-05 Y0, Z1, Z2, X3, X4, Y5
-0.00018118932100380194 Y0, Z1, Z2, X3, X4, Z5, Z6, Z7, Z8, Z9, Z10, Y11
0.000436021827859832 Y0, Z1, Z2, X3, X5, Z6, Z7, Z8, Z9, Y10
0.0018692181545341014 Y0, Z1, Z2, X3, X6, Y7
0.0018692181545341014 Y0, Z1, Z2, X3, X8, Y9
2.449990974769152e-05 Y0, Z1, Z2, X3, X10, Y11
-4.191045328486368e-05 Y0, Z1, Z2, Y3, X4, X5
0.00018118932100380194 Y0, Z1, Z2, Y3, X4, Z5, Z6, Z7, Z8, Z9, Z10, X11
0.000436021827859832 Y0, Z1, Z2, Y3, Y5, Z6, Z7, Z8, Z9, Y10
-0.0018692181545341014 Y0, Z1, Z2, Y3, X6, X7
-0.0018692181545341014 Y0, Z1, Z2, Y3, X8, X9
-2.449990974769152e-05 Y0, Z1, Z2, Y3, X10, X11
-0.02529257759921138 Y0, Z1, Z2, Z3, Y4
0.0010951171602220137 Y0, Z1, Z2, Z3, Y4, X5, Z6, Z7, Z8, Z9, Z10, X11
0.0010951171602220137 Y0, Z1, Z2, Z3, Y4, Y5, Z6, Z7, Z8, Z9, Z10, Y11
0.00044990608835842813 Y0, Z1, Z2, Z3, Y4, Z5
-0.0038099599110423352 Y0, Z1, Z2, Z3, Y4, Z6
-0.0012449194492991436 Y0, Z1, Z2, Z3, Y4, Z7
-0.0038099599110423352 Y0, Z1, Z2, Z3, Y4, Z8
-0.0012449194492991436 Y0, Z1, Z2, Z3, Y4, Z9
-0.003912147523847341 Y0, Z1, Z2, Z3, Y4, Z10
-0.0028333077583638697 Y0, Z1, Z2, Z3, Y4, Z11
-0.002565040461743191 Y0, Z1, Z2, Z3, Z4, X5, X6, Y7
-0.002565040461743191 Y0, Z1, Z2, Z3, Z4, X5, X8, Y9
-0.0010788397654834714 Y0, Z1, Z2, Z3, Z4, X5, X10, Y11
0.002565040461743191 Y0, Z1, Z2, Z3, Z4, Y5, X6, X7
0.002565040461743191 Y0, Z1, Z2, Z3, Z4, Y5, X8, X9
0.0010788397654834714 Y0, Z1, Z2, Z3, Z4, Y5, X10, X11
-0.0015308305961943033 Y0, Z1, Z2, Z3, Z4, Z5, Y6, X7, Z8, Z9, Z10, X11
-0.0015308305961943033 Y0, Z1, Z2, Z3, Z4, Z5, Y6, Y7, Z8, Z9, Z10, Y11
-0.0015308305961943033 Y0, Z1, Z2, Z3, Z4, Z5, Z6, Z7, Y8, X9, Z10, X11
-0.0015308305961943033 Y0, Z1, Z2, Z3, Z4, Z5, Z6, Z7, Y8, Y9, Z10, Y11
-0.0018597599595985982 Y0, Z1, Z2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, Y10
0.0007955178141925985 Y0, Z1, Z2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, Y10, Z11
-0.00016055712026931797 Y0, Z1, Z2, Z3, Z4, Z5, Z6, Z7, Z8, Y10
-0.0016913877164636216 Y0, Z1, Z2, Z3, Z4, Z5, Z6, Z7, Z9, Y10
-0.00016055712026931797 Y0, Z1, Z2, Z3, Z4, Z5, Z6, Z8, Z9, Y10
-0.0016913877164636216 Y0, Z1, Z2, Z3, Z4, Z5, Z7, Z8, Z9, Y10
-0.0026357203426690735 Y0, Z1, Z2, Z3, Z4, Z6, Z7, Z8, Z9, Y10
-0.0015406031824470607 Y0, Z1, Z2, Z3, Z5, Z6, Z7, Z8, Z9, Y10
-0.003925635716000991 Y0, Z1, Z2, Y4
0.0017319528638563967 Y0, Z1, Z2, Z4, Z5, Z6, Z7, Z8, Z9, Y10
-0.003098093810756644 Y0, Z1, Z3, Y4
0.0028704600772316903 Y0, Z1, Z3, Z4, Z5, Z6, Z7, Z8, Z9, Y10
0.027763126875232892 Y0, Y2
-0.03467415516652979 Y0, Z2, Z3, Y4
-0.013546869617431397 Y0, Z2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, Y10
1.0059431824845906 Z0
0.027763126875232892 Z0, X1, Z2, X3
-0.03467415516652979 Z0, X1, Z2, Z3, Z4, X5
-0.013546869617431397 Z0, X1, Z2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, Z10, X11
0.027763126875232892 Z0, Y1, Z2, Y3
-0.03467415516652979 Z0, Y1, Z2, Z3, Z4, Y5
-0.013546869617431397 Z0, Y1, Z2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, Z10, Y11
0.41465214181640725 Z0, Z1
0.0006463168035769322 Z0, X2, Z3, X4
0.008543512872996603 Z0, X2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, X10
0.0006463168035769322 Z0, Y2, Z3, Y4
0.008543512872996603 Z0, Y2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, Y10
0.0879440958723637 Z0, Z2
0.0034399215353777936 Z0, X3, Z4, X5
0.010789467157962725 Z0, X3, Z4, Z5, Z6, Z7, Z8, Z9, Z10, X11
0.0034399215353777936 Z0, Y3, Z4, Y5
0.010789467157962725 Z0, Y3, Z4, Z5, Z6, Z7, Z8, Z9, Z10, Y11
0.09123658259996774 Z0, Z3
0.005054189394333308 Z0, X4, Z5, Z6, Z7, Z8, Z9, X10
0.005054189394333308 Z0, Y4, Z5, Z6, Z7, Z8, Z9, Y10
0.09347296941444333 Z0, Z4
0.004431876930537636 Z0, X5, Z6, Z7, Z8, Z9, Z10, X11
0.004431876930537636 Z0, Y5, Z6, Z7, Z8, Z9, Z10, Y11
0.09889320629199544 Z0, Z5
0.09662573466425403 Z0, Z6
0.09908016329739928 Z0, Z7
0.09662573466425403 Z0, Z8
0.09908016329739928 Z0, Z9
0.08824360016418324 Z0, Z10
0.09042178051359735 Z0, Z11
-0.0008275419052443474 X1, X2, Y3, Y4
-0.001138507213375294 X1, X2, Y3, Z4, Z5, Z6, Z7, Z8, Z9, Y10
-4.191045328486368e-05 X1, X2, X4, X5
0.000436021827859832 X1, X2, X4, Z5, Z6, Z7, Z8, Z9, Z10, X11
0.00018118932100380194 X1, X2, Y5, Z6, Z7, Z8, Z9, Y10
-0.0018692181545341014 X1, X2, X6, X7
-0.0018692181545341014 X1, X2, X8, X9
-2.4499909747691524e-05 X1, X2, X10, X11
0.0008275419052443474 X1, Y2, Y3, X4
0.001138507213375294 X1, Y2, Y3, Z4, Z5, Z6, Z7, Z8, Z9, X10
-4.191045328486368e-05 X1, Y2, Y4, X5
0.000436021827859832 X1, Y2, Y4, Z5, Z6, Z7, Z8, Z9, Z10, X11
-0.00018118932100380194 X1, Y2, Y5, Z6, Z7, Z8, Z9, X10
-0.0018692181545341014 X1, Y2, Y6, X7
-0.0018692181545341014 X1, Y2, Y8, X9
-2.4499909747691524e-05 X1, Y2, Y10, X11
0.014212924082351761 X1, Z2, X3
0.0008981432433547795 X1, Z2, X3, X4, Z5, Z6, Z7, Z8, Z9, X10
0.0008981432433547795 X1, Z2, X3, Y4, Z5, Z6, Z7, Z8, Z9, Y10
0.002737536679929071 X1, Z2, X3, Z4
0.0013341650712146113 X1, Z2, X3, X5, Z6, Z7, Z8, Z9, Z10, X11
0.0010793325643585813 X1, Z2, X3, Y5, Z6, Z7, Z8, Z9, Z10, Y11
0.0027794471332139347 X1, Z2, X3, Z5
0.0010810228556545672 X1, Z2, X3, Z6
0.0029502410101886686 X1, Z2, X3, Z7
0.0010810228556545672 X1, Z2, X3, Z8
0.0029502410101886686 X1, Z2, X3, Z9
-0.0007860222944566732 X1, Z2, X3, Z10
-0.0007615223847089812 X1, Z2, X3, Z11
0.00025483250685603065 X1, Z2, Y3, Y5, Z6, Z7, Z8, Z9, Z10, X11
-0.0010951171602220134 X1, Z2, Z3, X4, Y5, Z6, Z7, Z8, Z9, Y10
0.0025650404617431916 X1, Z2, Z3, X4, X6, X7
0.0025650404617431916 X1, Z2, Z3, X4, X8, X9
0.0010788397654834714 X1, Z2, Z3, X4, X10, X11
0.0010951171602220134 X1, Z2, Z3, Y4, Y5, Z6, Z7, Z8, Z9, X10
0.0025650404617431916 X1, Z2, Z3, Y4, Y6, X7
0.0025650404617431916 X1, Z2, Z3, Y4, Y8, X9
0.0010788397654834714 X1, Z2, Z3, Y4, Y10, X11
-0.02529257759921141 X1, Z2, Z3, Z4, X5
-0.0012449194492991436 X1, Z2, Z3, Z4, X5, Z6
-0.0038099599110423352 X1, Z2, Z3, Z4, X5, Z7
-0.0012449194492991436 X1, Z2, Z3, Z4, X5, Z8
-0.0038099599110423352 X1, Z2, Z3, Z4, X5, Z9
-0.0028333077583638697 X1, Z2, Z3, Z4, X5, Z10
-0.003912147523847341 X1, Z2, Z3, Z4, X5, Z11
0.0015308305961943033 X1, Z2, Z3, Z4, Z5, X6, Y7, Z8, Z9, Y10
-0.0015308305961943033 X1, Z2, Z3, Z4, Z5, Y6, Y7, Z8, Z9, X10
0.0015308305961943033 X1, Z2, Z3, Z4, Z5, Z6, Z7, X8, Y9, Y10
-0.0015308305961943033 X1, Z2, Z3, Z4, Z5, Z6, Z7, Y8, Y9, X10
-0.001859759959598601 X1, Z2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, Z10, X11
0.0007955178141925984 X1, Z2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, X11
-0.0016913877164636216 X1, Z2, Z3, Z4, Z5, Z6, Z7, Z8, Z10, X11
-0.00016055712026931797 X1, Z2, Z3, Z4, Z5, Z6, Z7, Z9, Z10, X11
-0.0016913877164636216 X1, Z2, Z3, Z4, Z5, Z6, Z8, Z9, Z10, X11
-0.00016055712026931797 X1, Z2, Z3, Z4, Z5, Z7, Z8, Z9, Z10, X11
-0.0015406031824470607 X1, Z2, Z3, Z4, Z6, Z7, Z8, Z9, Z10, X11
0.0004499060883584282 X1, Z2, Z3, X5
-0.0026357203426690735 X1, Z2, Z3, Z5, Z6, Z7, Z8, Z9, Z10, X11
-0.003098093810756644 X1, Z2, Z4, X5
0.0028704600772316903 X1, Z2, Z4, Z5, Z6, Z7, Z8, Z9, Z10, X11
-0.0015182344970129586 X1, X3
-0.003925635716000991 X1, Z3, Z4, X5
0.0017319528638563967 X1, Z3, Z4, Z5, Z6, Z7, Z8, Z9, Z10, X11
0.0008275419052443474 Y1, X2, X3, Y4
0.001138507213375294 Y1, X2, X3, Z4, Z5, Z6, Z7, Z8, Z9, Y10
-4.191045328486368e-05 Y1, X2, X4, Y5
0.000436021827859832 Y1, X2, X4, Z5, Z6, Z7, Z8, Z9, Z10, Y11
-0.00018118932100380194 Y1, X2, X5, Z6, Z7, Z8, Z9, Y10
-0.0018692181545341014 Y1, X2, X6, Y7
-0.0018692181545341014 Y1, X2, X8, Y9
-2.4499909747691524e-05 Y1, X2, X10, Y11
-0.0008275419052443474 Y1, Y2, X3, X4
-0.001138507213375294 Y1, Y2, X3, Z4, Z5, Z6, Z7, Z8, Z9, X10
-4.191045328486368e-05 Y1, Y2, Y4, Y5
0.000436021827859832 Y1, Y2, Y4, Z5, Z6, Z7, Z8, Z9, Z10, Y11
0.00018118932100380194 Y1, Y2, X5, Z6, Z7, Z8, Z9, X10
-0.0018692181545341014 Y1, Y2, Y6, Y7
-0.0018692181545341014 Y1, Y2, Y8, Y9
-2.4499909747691524e-05 Y1, Y2, Y10, Y11
0.00025483250685603065 Y1, Z2, X3, X5, Z6, Z7, Z8, Z9, Z10, Y11
0.014212924082351761 Y1, Z2, Y3
0.0008981432433547795 Y1, Z2, Y3, X4, Z5, Z6, Z7, Z8, Z9, X10
0.0008981432433547795 Y1, Z2, Y3, Y4, Z5, Z6, Z7, Z8, Z9, Y10
0.002737536679929071 Y1, Z2, Y3, Z4
0.0010793325643585813 Y1, Z2, Y3, X5, Z6, Z7, Z8, Z9, Z10, X11
0.0013341650712146113 Y1, Z2, Y3, Y5, Z6, Z7, Z8, Z9, Z10, Y11
0.0027794471332139347 Y1, Z2, Y3, Z5
0.0010810228556545672 Y1, Z2, Y3, Z6
0.0029502410101886686 Y1, Z2, Y3, Z7
0.0010810228556545672 Y1, Z2, Y3, Z8
0.0029502410101886686 Y1, Z2, Y3, Z9
-0.0007860222944566732 Y1, Z2, Y3, Z10
-0.0007615223847089812 Y1, Z2, Y3, Z11
0.0010951171602220134 Y1, Z2, Z3, X4, X5, Z6, Z7, Z8, Z9, Y10
0.0025650404617431916 Y1, Z2, Z3, X4, X6, Y7
0.0025650404617431916 Y1, Z2, Z3, X4, X8, Y9
0.0010788397654834714 Y1, Z2, Z3, X4, X10, Y11
-0.0010951171602220134 Y1, Z2, Z3, Y4, X5, Z6, Z7, Z8, Z9, X10
0.0025650404617431916 Y1, Z2, Z3, Y4, Y6, Y7
0.0025650404617431916 Y1, Z2, Z3, Y4, Y8, Y9
0.0010788397654834714 Y1, Z2, Z3, Y4, Y10, Y11
-0.02529257759921141 Y1, Z2, Z3, Z4, Y5
-0.0012449194492991436 Y1, Z2, Z3, Z4, Y5, Z6
-0.0038099599110423352 Y1, Z2, Z3, Z4, Y5, Z7
-0.0012449194492991436 Y1, Z2, Z3, Z4, Y5, Z8
-0.0038099599110423352 Y1, Z2, Z3, Z4, Y5, Z9
-0.0028333077583638697 Y1, Z2, Z3, Z4, Y5, Z10
-0.003912147523847341 Y1, Z2, Z3, Z4, Y5, Z11
-0.0015308305961943033 Y1, Z2, Z3, Z4, Z5, X6, X7, Z8, Z9, Y10
0.0015308305961943033 Y1, Z2, Z3, Z4, Z5, Y6, X7, Z8, Z9, X10
-0.0015308305961943033 Y1, Z2, Z3, Z4, Z5, Z6, Z7, X8, X9, Y10
0.0015308305961943033 Y1, Z2, Z3, Z4, Z5, Z6, Z7, Y8, X9, X10
-0.001859759959598601 Y1, Z2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, Z10, Y11
0.0007955178141925984 Y1, Z2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, Y11
-0.0016913877164636216 Y1, Z2, Z3, Z4, Z5, Z6, Z7, Z8, Z10, Y11
-0.00016055712026931797 Y1, Z2, Z3, Z4, Z5, Z6, Z7, Z9, Z10, Y11
-0.0016913877164636216 Y1, Z2, Z3, Z4, Z5, Z6, Z8, Z9, Z10, Y11
-0.00016055712026931797 Y1, Z2, Z3, Z4, Z5, Z7, Z8, Z9, Z10, Y11
-0.0015406031824470607 Y1, Z2, Z3, Z4, Z6, Z7, Z8, Z9, Z10, Y11
0.0004499060883584282 Y1, Z2, Z3, Y5
-0.0026357203426690735 Y1, Z2, Z3, Z5, Z6, Z7, Z8, Z9, Z10, Y11
-0.003098093810756644 Y1, Z2, Z4, Y5
0.0028704600772316903 Y1, Z2, Z4, Z5, Z6, Z7, Z8, Z9, Z10, Y11
-0.0015182344970129586 Y1, Y3
-0.003925635716000991 Y1, Z3, Z4, Y5
0.0017319528638563967 Y1, Z3, Z4, Z5, Z6, Z7, Z8, Z9, Z10, Y11
1.0059431824845906 Z1
0.0034399215353777936 Z1, X2, Z3, X4
0.010789467157962725 Z1, X2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, X10
0.0034399215353777936 Z1, Y2, Z3, Y4
0.010789467157962725 Z1, Y2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, Y10
0.09123658259996774 Z1, Z2
0.0006463168035769322 Z1, X3, Z4, X5
0.008543512872996603 Z1, X3, Z4, Z5, Z6, Z7, Z8, Z9, Z10, X11
0.0006463168035769322 Z1, Y3, Z4, Y5
0.008543512872996603 Z1, Y3, Z4, Z5, Z6, Z7, Z8, Z9, Z10, Y11
0.0879440958723637 Z1, Z3
0.004431876930537636 Z1, X4, Z5, Z6, Z7, Z8, Z9, X10
0.004431876930537636 Z1, Y4, Z5, Z6, Z7, Z8, Z9, Y10
0.09889320629199544 Z1, Z4
0.005054189394333308 Z1, X5, Z6, Z7, Z8, Z9, Z10, X11
0.005054189394333308 Z1, Y5, Z6, Z7, Z8, Z9, Z10, Y11
0.09347296941444333 Z1, Z5
0.09908016329739928 Z1, Z6
0.09662573466425403 Z1, Z7
0.09908016329739928 Z1, Z8
0.09662573466425403 Z1, Z9
0.09042178051359735 Z1, Z10
0.08824360016418324 Z1, Z11
-0.0033029385877508606 X2, X3, Y4, Y5
-0.008694194774706057 X2, X3, Y4, Z5, Z6, Z7, Z8, Z9, Z10, Y11
-0.008694194774706057 X2, X3, X5, Z6, Z7, Z8, Z9, X10
-0.00583584572367298 X2, X3, Y6, Y7
-0.00583584572367298 X2, X3, Y8, Y9
-0.031021678457097135 X2, X3, Y10, Y11
0.0033029385877508606 X2, Y3, Y4, X5
0.008694194774706057 X2, Y3, Y4, Z5, Z6, Z7, Z8, Z9, Z10, X11
-0.008694194774706057 X2, Y3, Y5, Z6, Z7, Z8, Z9, X10
0.00583584572367298 X2, Y3, Y6, X7
0.00583584572367298 X2, Y3, Y8, X9
0.031021678457097135 X2, Y3, Y10, X11
0.007429919944069738 X2, Z3, X4
0.0023898129843256525 X2, Z3, X4, X5, Z6, Z7, Z8, Z9, Z10, X11
0.0023898129843256525 X2, Z3, X4, Y5, Z6, Z7, Z8, Z9, Z10, Y11
0.0019190021785390446 X2, Z3, X4, Z5
-0.003340489663042734 X2, Z3, X4, Z6
0.001481985133988225 X2, Z3, X4, Z7
-0.003340489663042734 X2, Z3, X4, Z8
0.001481985133988225 X2, Z3, X4, Z9
-0.002871027827537987 X2, Z3, X4, Z10
-0.010881455068851156 X2, Z3, X4, Z11
0.004822474797030959 X2, Z3, Z4, X5, Y6, Y7
0.004822474797030959 X2, Z3, Z4, X5, Y8, Y9
-0.008010427241313166 X2, Z3, Z4, X5, Y10, Y11
-0.004822474797030959 X2, Z3, Z4, Y5, Y6, X7
-0.004822474797030959 X2, Z3, Z4, Y5, Y8, X9
0.008010427241313166 X2, Z3, Z4, Y5, Y10, X11
-0.0048929684894925924 X2, Z3, Z4, Z5, X6, X7, Z8, Z9, Z10, X11
-0.0048929684894925924 X2, Z3, Z4, Z5, X6, Y7, Z8, Z9, Z10, Y11
-0.0048929684894925924 X2, Z3, Z4, Z5, Z6, Z7, X8, X9, Z10, X11
-0.0048929684894925924 X2, Z3, Z4, Z5, Z6, Z7, X8, Y9, Z10, Y11
0.005108097505288088 X2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, X10
-0.03331517330276744 X2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, X10, Z11
0.004256730045576267 X2, Z3, Z4, Z5, Z6, Z7, Z8, X10
-0.000636238443916325 X2, Z3, Z4, Z5, Z6, Z7, Z9, X10
0.004256730045576267 X2, Z3, Z4, Z5, Z6, Z8, Z9, X10
-0.000636238443916325 X2, Z3, Z4, Z5, Z7, Z8, Z9, X10
0.0031976266647805703 X2, Z3, Z4, Z6, Z7, Z8, Z9, X10
0.00558743964910622 X2, Z3, Z5, Z6, Z7, Z8, Z9, X10
-0.012206805663285261 X2, X4
-0.03151400210074103 X2, Z4, Z5, Z6, Z7, Z8, Z9, X10
0.0033029385877508606 Y2, X3, X4, Y5
0.008694194774706057 Y2, X3, X4, Z5, Z6, Z7, Z8, Z9, Z10, Y11
-0.008694194774706057 Y2, X3, X5, Z6, Z7, Z8, Z9, Y10
0.00583584572367298 Y2, X3, X6, Y7
0.00583584572367298 Y2, X3, X8, Y9
0.031021678457097135 Y2, X3, X10, Y11
-0.0033029385877508606 Y2, Y3, X4, X5
-0.008694194774706057 Y2, Y3, X4, Z5, Z6, Z7, Z8, Z9, Z10, X11
-0.008694194774706057 Y2, Y3, Y5, Z6, Z7, Z8, Z9, Y10
-0.00583584572367298 Y2, Y3, X6, X7
-0.00583584572367298 Y2, Y3, X8, X9
-0.031021678457097135 Y2, Y3, X10, X11
0.007429919944069738 Y2, Z3, Y4
0.0023898129843256525 Y2, Z3, Y4, X5, Z6, Z7, Z8, Z9, Z10, X11
0.0023898129843256525 Y2, Z3, Y4, Y5, Z6, Z7, Z8, Z9, Z10, Y11
0.0019190021785390446 Y2, Z3, Y4, Z5
-0.003340489663042734 Y2, Z3, Y4, Z6
0.001481985133988225 Y2, Z3, Y4, Z7
-0.003340489663042734 Y2, Z3, Y4, Z8
0.001481985133988225 Y2, Z3, Y4, Z9
-0.002871027827537987 Y2, Z3, Y4, Z10
-0.010881455068851156 Y2, Z3, Y4, Z11
-0.004822474797030959 Y2, Z3, Z4, X5, X6, Y7
-0.004822474797030959 Y2, Z3, Z4, X5, X8, Y9
0.008010427241313166 Y2, Z3, Z4, X5, X10, Y11
0.004822474797030959 Y2, Z3, Z4, Y5, X6, X7
0.004822474797030959 Y2, Z3, Z4, Y5, X8, X9
-0.008010427241313166 Y2, Z3, Z4, Y5, X10, X11
-0.0048929684894925924 Y2, Z3, Z4, Z5, Y6, X7, Z8, Z9, Z10, X11
-0.0048929684894925924 Y2, Z3, Z4, Z5, Y6, Y7, Z8, Z9, Z10, Y11
-0.0048929684894925924 Y2, Z3, Z4, Z5, Z6, Z7, Y8, X9, Z10, X11
-0.0048929684894925924 Y2, Z3, Z4, Z5, Z6, Z7, Y8, Y9, Z10, Y11
0.005108097505288088 Y2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, Y10
-0.03331517330276744 Y2, Z3, Z4, Z5, Z6, Z7, Z8, Z9, Y10, Z11
0.004256730045576267 Y2, Z3, Z4, Z5, Z6, Z7, Z8, Y10
-0.000636238443916325 Y2, Z3, Z4, Z5, Z6, Z7, Z9, Y10
0.004256730045576267 Y2, Z3, Z4, Z5, Z6, Z8, Z9, Y10
-0.000636238443916325 Y2, Z3, Z4, Z5, Z7, Z8, Z9, Y10
0.0031976266647805703 Y2, Z3, Z4, Z6, Z7, Z8, Z9, Y10
0.00558743964910622 Y2, Z3, Z5, Z6, Z7, Z8, Z9, Y10
-0.012206805663285261 Y2, Y4
-0.03151400210074103 Y2, Z4, Z5, Z6, Z7, Z8, Z9, Y10
-0.11891656951480156 Z2
-0.012206805663285263 Z2, X3, Z4, X5
-0.03151400210074103 Z2, X3, Z4, Z5, Z6, Z7, Z8, Z9, Z10, X11
-0.012206805663285263 Z2, Y3, Z4, Y5
-0.03151400210074103 Z2, Y3, Z4, Z5, Z6, Z7, Z8, Z9, Z10, Y11
0.12157586773359427 Z2, Z3
-0.004167261481459921 Z2, X4, Z5, Z6, Z7, Z8, Z9, X10
-0.004167261481459921 Z2, Y4, Z5, Z6, Z7, Z8, Z9, Y10
0.0524967290376484 Z2, Z4
-0.012861456256165978 Z2, X5, Z6, Z7, Z8, Z9, Z10, X11
-0.012861456256165978 Z2, Y5, Z6, Z7, Z8, Z9, Z10, Y11
0.05579966762539926 Z2, Z5
0.06152799188603612 Z2, Z6
0.0673638376097091 Z2, Z7
0.06152799188603612 Z2, Z8
0.0673638376097091 Z2, Z9
0.08229275101239056 Z2, Z10
0.11331442946948768 Z2, Z11
-0.0023898129843256525 X3, X4, Y5, Z6, Z7, Z8, Z9, Y10
0.004822474797030959 X3, X4, X6, X7
0.004822474797030959 X3, X4, X8, X9
-0.008010427241313168 X3, X4, X10, X11
0.0023898129843256525 X3, Y4, Y5, Z6, Z7, Z8, Z9, X10
0.004822474797030959 X3, Y4, Y6, X7
0.004822474797030959 X3, Y4, Y8, X9
-0.008010427241313168 X3, Y4, Y10, X11
0.007429919944069745 X3, Z4, X5
0.001481985133988225 X3, Z4, X5, Z6
-0.003340489663042734 X3, Z4, X5, Z7
0.001481985133988225 X3, Z4, X5, Z8
-0.003340489663042734 X3, Z4, X5, Z9
-0.010881455068851156 X3, Z4, X5, Z10
-0.002871027827537987 X3, Z4, X5, Z11
0.0048929684894925924 X3, Z4, Z5, X6, Y7, Z8, Z9, Y10
-0.0048929684894925924 X3, Z4, Z5, Y6, Y7, Z8, Z9, X10
0.0048929684894925924 X3, Z4, Z5, Z6, Z7, X8, Y9, Y10
-0.0048929684894925924 X3, Z4, Z5, Z6, Z7, Y8, Y9, X10
0.00510809750528809 X3, Z4, Z5, Z6, Z7, Z8, Z9, Z10, X11
-0.03331517330276744 X3, Z4, Z5, Z6, Z7, Z8, Z9, X11
-0.000636238443916325 X3, Z4, Z5, Z6, Z7, Z8, Z10, X11
0.004256730045576267 X3, Z4, Z5, Z6, Z7, Z9, Z10, X11
-0.000636238443916325 X3, Z4, Z5, Z6, Z8, Z9, Z10, X11
0.004256730045576267 X3, Z4, Z5, Z7, Z8, Z9, Z10, X11
0.00558743964910622 X3, Z4, Z6, Z7, Z8, Z9, Z10, X11
0.0019190021785390446 X3, X5
0.0031976266647805703 X3, Z5, Z6, Z7, Z8, Z9, Z10, X11
0.0023898129843256525 Y3, X4, X5, Z6, Z7, Z8, Z9, Y10
0.004822474797030959 Y3, X4, X6, Y7
0.004822474797030959 Y3, X4, X8, Y9
-0.008010427241313168 Y3, X4, X10, Y11
-0.0023898129843256525 Y3, Y4, X5, Z6, Z7, Z8, Z9, X10
0.004822474797030959 Y3, Y4, Y6, Y7
0.004822474797030959 Y3, Y4, Y8, Y9
-0.008010427241313168 Y3, Y4, Y10, Y11
0.007429919944069745 Y3, Z4, Y5
0.001481985133988225 Y3, Z4, Y5, Z6
-0.003340489663042734 Y3, Z4, Y5, Z7
0.001481985133988225 Y3, Z4, Y5, Z8
-0.003340489663042734 Y3, Z4, Y5, Z9
-0.010881455068851156 Y3, Z4, Y5, Z10
-0.002871027827537987 Y3, Z4, Y5, Z11
-0.0048929684894925924 Y3, Z4, Z5, X6, X7, Z8, Z9, Y10
0.0048929684894925924 Y3, Z4, Z5, Y6, X7, Z8, Z9, X10
-0.0048929684894925924 Y3, Z4, Z5, Z6, Z7, X8, X9, Y10
0.0048929684894925924 Y3, Z4, Z5, Z6, Z7, Y8, X9, X10
0.00510809750528809 Y3, Z4, Z5, Z6, Z7, Z8, Z9, Z10, Y11
-0.03331517330276744 Y3, Z4, Z5, Z6, Z7, Z8, Z9, Y11
-0.000636238443916325 Y3, Z4, Z5, Z6, Z7, Z8, Z10, Y11
0.004256730045576267 Y3, Z4, Z5, Z6, Z7, Z9, Z10, Y11
-0.000636238443916325 Y3, Z4, Z5, Z6, Z8, Z9, Z10, Y11
0.004256730045576267 Y3, Z4, Z5, Z7, Z8, Z9, Z10, Y11
0.00558743964910622 Y3, Z4, Z6, Z7, Z8, Z9, Z10, Y11
0.0019190021785390446 Y3, Y5
0.0031976266647805703 Y3, Z5, Z6, Z7, Z8, Z9, Z10, Y11
-0.11891656951480153 Z3
-0.012861456256165978 Z3, X4, Z5, Z6, Z7, Z8, Z9, X10
-0.012861456256165978 Z3, Y4, Z5, Z6, Z7, Z8, Z9, Y10
0.05579966762539926 Z3, Z4
-0.004167261481459921 Z3, X5, Z6, Z7, Z8, Z9, Z10, X11
-0.004167261481459921 Z3, Y5, Z6, Z7, Z8, Z9, Z10, Y11
0.0524967290376484 Z3, Z5
0.0673638376097091 Z3, Z6
0.06152799188603612 Z3, Z7
0.0673638376097091 Z3, Z8
0.06152799188603612 Z3, Z9
0.11331442946948768 Z3, Z10
0.08229275101239056 Z3, Z11
-0.010318484943526222 X4, X5, Y6, Y7
-0.010318484943526222 X4, X5, Y8, Y9
-0.006621086977631776 X4, X5, Y10, Y11
0.010318484943526222 X4, Y5, Y6, X7
0.010318484943526222 X4, Y5, Y8, X9
0.006621086977631776 X4, Y5, Y10, X11
0.003423817531560868 X4, Z5, X6, X7, Z8, Z9, Z10, X11
0.003423817531560868 X4, Z5, X6, Y7, Z8, Z9, Z10, Y11
0.003423817531560868 X4, Z5, Z6, Z7, X8, X9, Z10, X11
0.003423817531560868 X4, Z5, Z6, Z7, X8, Y9, Z10, Y11
-0.01471267354185983 X4, Z5, Z6, Z7, Z8, Z9, X10
-0.011037021734454231 X4, Z5, Z6, Z7, Z8, Z9, X10, Z11
0.0005914827407169591 X4, Z5, Z6, Z7, Z8, X10
0.0040153002722778264 X4, Z5, Z6, Z7, Z9, X10
0.0005914827407169591 X4, Z5, Z6, Z8, Z9, X10
0.0040153002722778264 X4, Z5, Z7, Z8, Z9, X10
0.008993475017393919 X4, Z6, Z7, Z8, Z9, X10
0.010318484943526222 Y4, X5, X6, Y7
0.010318484943526222 Y4, X5, X8, Y9
0.006621086977631776 Y4, X5, X10, Y11
-0.010318484943526222 Y4, Y5, X6, X7
-0.010318484943526222 Y4, Y5, X8, X9
-0.006621086977631776 Y4, Y5, X10, X11
0.003423817531560868 Y4, Z5, Y6, X7, Z8, Z9, Z10, X11
0.003423817531560868 Y4, Z5, Y6, Y7, Z8, Z9, Z10, Y11
0.003423817531560868 Y4, Z5, Z6, Z7, Y8, X9, Z10, X11
0.003423817531560868 Y4, Z5, Z6, Z7, Y8, Y9, Z10, Y11
-0.01471267354185983 Y4, Z5, Z6, Z7, Z8, Z9, Y10
-0.011037021734454231 Y4, Z5, Z6, Z7, Z8, Z9, Y10, Z11
0.0005914827407169591 Y4, Z5, Z6, Z7, Z8, Y10
0.0040153002722778264 Y4, Z5, Z6, Z7, Z9, Y10
0.0005914827407169591 Y4, Z5, Z6, Z8, Z9, Y10
0.0040153002722778264 Y4, Z5, Z7, Z8, Z9, Y10
0.008993475017393919 Y4, Z6, Z7, Z8, Z9, Y10
-0.19817773588492643 Z4
0.008993475017393919 Z4, X5, Z6, Z7, Z8, Z9, Z10, X11
0.008993475017393919 Z4, Y5, Z6, Z7, Z8, Z9, Z10, Y11
0.08443134597954689 Z4, Z5
0.06017013500452548 Z4, Z6
0.0704886199480517 Z4, Z7
0.06017013500452548 Z4, Z8
0.0704886199480517 Z4, Z9
0.05371410339203929 Z4, Z10
0.06033519036967106 Z4, Z11
-0.003423817531560868 X5, X6, Y7, Z8, Z9, Y10
0.003423817531560868 X5, Y6, Y7, Z8, Z9, X10
-0.003423817531560868 X5, Z6, Z7, X8, Y9, Y10
0.003423817531560868 X5, Z6, Z7, Y8, Y9, X10
-0.014712673541859836 X5, Z6, Z7, Z8, Z9, Z10, X11
-0.01103702173445423 X5, Z6, Z7, Z8, Z9, X11
0.0040153002722778264 X5, Z6, Z7, Z8, Z10, X11
0.0005914827407169591 X5, Z6, Z7, Z9, Z10, X11
0.0040153002722778264 X5, Z6, Z8, Z9, Z10, X11
0.0005914827407169591 X5, Z7, Z8, Z9, Z10, X11
0.003423817531560868 Y5, X6, X7, Z8, Z9, Y10
-0.003423817531560868 Y5, Y6, X7, Z8, Z9, X10
0.003423817531560868 Y5, Z6, Z7, X8, X9, Y10
-0.003423817531560868 Y5, Z6, Z7, Y8, X9, X10
-0.014712673541859836 Y5, Z6, Z7, Z8, Z9, Z10, Y11
-0.01103702173445423 Y5, Z6, Z7, Z8, Z9, Y11
0.0040153002722778264 Y5, Z6, Z7, Z8, Z10, Y11
0.0005914827407169591 Y5, Z6, Z7, Z9, Z10, Y11
0.0040153002722778264 Y5, Z6, Z8, Z9, Z10, Y11
0.0005914827407169591 Y5, Z7, Z8, Z9, Z10, Y11
-0.19817773588492646 Z5
0.0704886199480517 Z5, Z6
0.06017013500452548 Z5, Z7
0.0704886199480517 Z5, Z8
0.06017013500452548 Z5, Z9
0.06033519036967106 Z5, Z10
0.05371410339203929 Z5, Z11
-0.00421728487842276 X6, X7, Y8, Y9
-0.0049364527150651 X6, X7, Y10, Y11
0.00421728487842276 X6, Y7, Y8, X9
0.0049364527150651 X6, Y7, Y10, X11
0.00421728487842276 Y6, X7, X8, Y9
0.0049364527150651 Y6, X7, X10, Y11
-0.00421728487842276 Y6, Y7, X8, X9
-0.0049364527150651 Y6, Y7, X10, X11
-0.23068647306930556 Z6
0.0782363777898523 Z6, Z7
0.06558452315458405 Z6, Z8
0.06980180803300681 Z6, Z9
0.06204672473607123 Z6, Z10
0.06698317745113633 Z6, Z11
-0.2306864730693056 Z7
0.06980180803300681 Z7, Z8
0.06558452315458405 Z7, Z9
0.06698317745113633 Z7, Z10
0.06204672473607123 Z7, Z11
-0.0049364527150651 X8, X9, Y10, Y11
0.0049364527150651 X8, Y9, Y10, X11
0.0049364527150651 Y8, X9, X10, Y11
-0.0049364527150651 Y8, Y9, X10, X11
-0.23068647306930556 Z8
0.0782363777898523 Z8, Z9
0.06204672473607123 Z8, Z10
0.06698317745113633 Z8, Z11
-0.2306864730693056 Z9
0.06698317745113633 Z9, Z10
0.06204672473607123 Z9, Z11
-0.3829674472334882 Z10
0.11331643654043766 Z10, Z11
-0.38296744723348813 Z11
\ No newline at end of file
{
"cells": [
{
"cell_type": "markdown",
"id": "9d3716bb",
"metadata": {},
"source": [
"# 模拟一维海森堡链的自旋动力学\n",
"\n",
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
]
},
{
"cell_type": "markdown",
"id": "c0832e9f",
"metadata": {},
"source": [
"## 概述\n",
"\n",
"模拟一个量子系统的性质,是量子计算机的重要应用之一。一般来说,分析一个量子系统的性质需要先写出其哈密顿量 $H$,而对于不同尺度下的物理系统而言,这个哈密顿量往往具有不同的形式。以量子化学为例,一个分子的性质主要由电子-电子之间的库伦相互作用而决定,因此其哈密顿量中的每一项都是由作用在电子波函数上的费米子算符写成的。而量子计算机的基本组成单元量子比特(qubit)以及常用的泡利算符,对应着物理上的自旋和自旋算符。因此,若想在量子计算机上对分子性质进行模拟,则往往需要进行从费米子算符到泡利算符的转换,例如 Jordan-Wigner 变换、Bravyi-Kitaev 变换等等。这也就使得量子计算机需要消耗更多的资源来进行分子哈密顿量的模拟。因此,对于近期的量子设备而言,最有可能率先实现的便是对量子自旋系统的量子模拟——因为这些系统的哈密顿量可以直接写成泡利算符的形式。\n",
"\n",
"在本教程中,我们选取了一个比较经典的量子自旋模型——海森堡模型,并将展示如何利用 Paddle Quantum 来进行一维海森堡自旋链的时间演化模拟。我们主要会使用 `construct_trotter_circuit()` 函数来搭建基于 product formula 的模拟时间演化电路,在先前的教程 [利用 Product Formula 模拟时间演化](./HamiltonianSimulation_CN.ipynb) 中有对该方法较为详细的理论介绍,在本教程中也会有较为简略的回顾。本教程将主要着眼于实际的应用,可以分为两个部分:\n",
"- 海森堡模型的物理背景以及利用 Paddle Quantum 对其时间演化进行模拟 \n",
"- 基于随机置换来搭建自定义时间演化电路"
]
},
{
"cell_type": "markdown",
"id": "988b3a47",
"metadata": {},
"source": [
"---\n",
"在进一步介绍本教程中涉及的物理背景之前,我们先来回顾一下利用量子电路来模拟时间演化的基本思想,对这部分内容已经比较熟悉的读者可以直接跳至 **海森堡自旋链与其动力学模拟** 继续阅读。\n",
"\n",
"### 利用 Suzuki product formula 模拟时间演化\n",
"\n",
"让我们先回顾一下使用 Suzuki product formula 来模拟时间演化的基本思想:对于一个被不含时的哈密顿量 $H = \\sum_k^L h_k$ 描述的量子系统,其时间演化算符可以写为\n",
"\n",
"$$\n",
"U(t) = e^{-iHt},\n",
"\\tag{1}\n",
"$$\n",
"\n",
"该算符可以被进一步拆分为 $r$ 份,即\n",
"\n",
"$$\n",
"e^{-iHt} = \\left( e^{-iH \\tau} \\right)^r, ~\\tau=\\frac{t}{r}.\n",
"\\tag{2}\n",
"$$\n",
"\n",
"对于每一个 $e^{-iH \\tau}$ 算符而言,其 Suzuki 分解为\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"S_1(\\tau) &= \\prod_{k=0}^L \\exp ( -i h_k \\tau),\n",
"\\\\\n",
"S_2(\\tau) &= \\prod_{k=0}^L \\exp ( -i h_k \\frac{\\tau}{2})\\prod_{k=L}^0 \\exp ( -i h_k \\frac{\\tau}{2}),\n",
"\\\\\n",
"S_{2k+2}(\\tau) &= [S_{2k}(p_k\\tau)]^2S_{2k}\\left( (1-4p_k)\\tau\\right)[S_{2k}(p_k\\tau)]^2.\n",
"\\end{aligned}\n",
"\\tag{3}\n",
"$$\n",
"\n",
"回到完整的时间演化算符 $U(t)$,利用第 $k$ 阶的 Suzuki 分解,它可以被写为\n",
"\n",
"$$\n",
"U(t) = e^{-iHt} = \\left( S_{k}\\left(\\frac{t}{r}\\right) \\right)^r.\n",
"\\tag{4}\n",
"$$\n",
"\n",
"这种模拟时间演化的方法被称为 Suzuki product formula,它可以有效地模拟时间演化过程至任意精度 [1]。在另一份教程 [利用 Product Formula 模拟时间演化](./HamiltonianSimulation_CN.ipynb) 中,我们展示了其误差上界的计算过程,感兴趣的读者可以前往阅读。\n",
"\n",
"---"
]
},
{
"cell_type": "markdown",
"id": "ab3f7311",
"metadata": {},
"source": [
"## 海森堡模型与其动力学模拟\n",
"\n",
"海森堡(Heisenberg)模型,是量子磁性以及量子多体物理研究中十分重要的一个模型。它的哈密顿量为\n",
"\n",
"$$\n",
"H = \\sum_{\\langle i, j\\rangle} \n",
"\\left( J_x S^x_{i} S^x_{j} + J_y S^y_{i} S^y_{j} + J_z S^z_{i} S^z_{j} \\right)\n",
"+\n",
"\\sum_{i} h_z S^z_i, \n",
"\\tag{5}\n",
"$$\n",
"\n",
"其中 $\\langle i, j\\rangle$ 取决于具体的格点几何结构,$J_x, J_y, J_z$ 分别为 $xyz$ 三个方向上的自旋耦合强度,$h_z$ 是 $z$ 方向上的外加磁场。若取 $J_z = 0$,(5) 式也可以用来描述 XY 模型的哈密顿量;取 $J_x = J_y = 0$,(5) 式则可以用来描述伊辛模型(Ising model)的哈密顿量。注意在这里,我们使用了量子多体物理里面比较常用的多体自旋算符 $S^x_i, S^y_i, S^z_i$,它是一个作用在多体波函数上的算符。\n",
"对于自旋-1/2 系统而言,多体自旋算符可以被简单地写为泡利算符的张量积形式(省略一个 $\\hbar/2$ 的系数)\n",
"\n",
"$$\n",
"S^P_{i} = \\left ( \\otimes_{j=0}^{i-1} I \\right ) \\otimes \\sigma_{P} \\otimes \\left ( \\otimes_{j=i+1}^{L} I \\right ),\n",
"P \\in \\{ x, y, z \\},\n",
"\\tag{6}\n",
"$$\n",
"\n",
"其中 $\\sigma_{P}$ 是泡利算符,我们也经常用 $XYZ$ 算符来表示它们。需要说明的是,海森堡模型并不是一个假想模型:从描述电子在格点系统上运动的赫巴德模型(Hubbard model)出发,在一定的极限条件下,电子会被固定在格点上并形成半满填充。此时,描述电子的赫巴德模型就退化为了描述自旋的海森堡模型,而 (5) 式中的自旋-自旋相互作用则是电子-电子之间的相互作用在这个极限下的一种有效交换相互作用 [2]。尽管做了许多的近似,但是海森堡模型依然成功地预言了许多实际材料在低温下的性质 [3]。比如读者可能在高中课本上就学习过的 $\\rm Cu(NO_3)_2 \\cdot 2.5 H_2 O$ 二点五水合硝酸铜在 $\\sim 3K$ 的低温下的行为就可以被自旋-1/2 一维交错海森堡链所描述 [4]。\n",
"\n",
"取决于其具体的格点结构,海森堡模型上可以展示出丰富的量子现象。一维海森堡链可以被用来描述铁磁性与反铁磁性,对称性破缺以及无能隙激发。在二维阻挫格点系统上,海森堡模型可以被用来描述量子自旋液体态-这是一种包含了长程纠缠的新奇量子物态 [5]。若考虑一个外加的无序磁场时,海森堡模型还可以用来研究多体局域化现象(many-body localization, MBL),这是一种违反了热化假说的奇特现象,指的是一个量子多体系统经过了无穷长的时间演化后也不会热化,依然保留着其初态有关的信息 [6]。\n",
"\n",
"模拟海森堡模型的时间演化过程,也被称为动力学模拟,可以帮助人们探索量子系统非平衡态相关的性质,从而用来寻找新奇的量子物相:例如前文提到的多体局域相,又或者更加有趣的时间晶体相 [7]。除了理论,动力学模拟对于实际的物理实验也有着重要的意义。这是因为自旋关联函数(也通常被称为动力学结构因子)直接决定了散射实验中的截面,或者是核磁共振实验的结果 [3],该函数则是由含时的自旋算符 $\\langle S(t) S(0) \\rangle$ 的积分决定的。因此,通过计算不同理论模型的动力学演化,人们可以进一步对真实材料中的物理模型进行分析。\n",
"\n",
"### 利用 Paddle Quantum 实现海森堡链的动力学模拟"
]
},
{
"cell_type": "markdown",
"id": "aea361f7",
"metadata": {},
"source": [
"下面,我们则会通过一个实际的例子:链长为 5 的含有无序外磁场的海森堡链,来展示如何在 Paddle Quantum 中搭建其时间演化电路。首先,我们引入相关的包。"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "5c873819",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import scipy\n",
"from scipy import linalg\n",
"import matplotlib.pyplot as plt\n",
"from paddle_quantum.circuit import UAnsatz\n",
"from paddle_quantum.utils import SpinOps, Hamiltonian, gate_fidelity\n",
"from paddle_quantum.trotter import construct_trotter_circuit, get_1d_heisenberg_hamiltonian"
]
},
{
"cell_type": "markdown",
"id": "6c81929b",
"metadata": {},
"source": [
"接下来,我们利用 `get_1d_heisenberg_hamiltonian()` 函数来得到一个一维海森堡链的哈密顿量: "
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "88fa56fe",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"系统的哈密顿量为:\n",
"1.0 X0, X1\n",
"1.0 Y0, Y1\n",
"2.0 Z0, Z1\n",
"1.0 X1, X2\n",
"1.0 Y1, Y2\n",
"2.0 Z1, Z2\n",
"1.0 X2, X3\n",
"1.0 Y2, Y3\n",
"2.0 Z2, Z3\n",
"1.0 X3, X4\n",
"1.0 Y3, Y4\n",
"2.0 Z3, Z4\n",
"-0.8540490813629811 Z0\n",
"-0.017499184685274338 Z1\n",
"0.08600328703303406 Z2\n",
"0.9440767245343289 Z3\n",
"-0.9640203537370211 Z4\n"
]
}
],
"source": [
"h = get_1d_heisenberg_hamiltonian(length=5, j_x=1, j_y=1, j_z=2, h_z=2 * np.random.rand(5) - 1,\n",
"periodic_boundary_condition=False)\n",
"print('系统的哈密顿量为:')\n",
"print(h)"
]
},
{
"cell_type": "markdown",
"id": "0793414b",
"metadata": {},
"source": [
"得到了哈密顿量之后,可以进一步通过 `construct_trotter_circuit()` 来构建时间演化电路。此外,若直接写出演化算符的矩阵形式,也可以计算系统随时间演化的精确解。这里我们用到了量桨中的 `Hamiltonian.construct_h_matrix()` 方法,它可以计算给定哈密顿量在泡利 $Z$ 基底下的矩阵形式。通过比较 `cir.U`,即电路的酉矩阵形式,以及精确的演化算符,可以计算出该电路模拟时间演化的保真度。"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "5052fb32",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"电路的酉矩阵与正确的演化算符之间的保真度为:0.55\n"
]
}
],
"source": [
"# 计算演化时长为 t 时的精确演化算符\n",
"def get_evolve_op(t): return scipy.linalg.expm(-1j * t * h.construct_h_matrix())\n",
"\n",
"# 设置演化时长以及模拟的步长\n",
"t = 3\n",
"r = 10\n",
"# 搭建模拟演化电路\n",
"cir_evolve = UAnsatz(5)\n",
"construct_trotter_circuit(cir_evolve, h, tau=t/r, steps=r, order=2)\n",
"# 得到电路的酉矩阵并计算与精确演化算符之间的保真度\n",
"U_cir = cir_evolve.U.numpy()\n",
"print('电路的酉矩阵与正确的演化算符之间的保真度为:%.2f' % gate_fidelity(get_evolve_op(t), U_cir))"
]
},
{
"cell_type": "markdown",
"id": "ce487d74",
"metadata": {},
"source": [
"#### 根据对易关系重新排列哈密顿量\n",
"\n",
"对于 product formula 而言,可以通过重新排列哈密顿量中的每一项减小其模拟误差。因为 product formula 的误差是由哈密顿量中不对易项所产生的,所以一种自然的重新排列思路就是将哈密顿量中相互对易的项放在一起。比如,我们可以将哈密顿量分解为四个部分\n",
"\n",
"$$\n",
"H = H_x + H_y + H_z + H_{\\rm other},\n",
"\\tag{7}\n",
"$$\n",
"\n",
"其中 $H_x, H_y, H_z$ 分别为仅由泡利 $X, Y, Z$ 算符构成的项,$H_{\\rm other}$ 为剩余项。对于 (5) 中的海森堡链的哈密顿量而言,所有的项都可以被分类为 $H_x, H_y, H_z$ 三项。不仅如此,对于一维最近邻相互作用系统而言,它也可以被分为奇偶两个部分\n",
"\n",
"$$\n",
"H = H_{\\rm even} + H_{\\rm odd},\n",
"\\tag{8}\n",
"$$\n",
"\n",
"其中 $H_{\\rm even}$ 为 $(0, 1), (2, 3), ...$ 格点上的相互作用项,$H_{\\rm odd}$ 为 $(1, 2), (3, 4), ...$ 格点上的相互作用项。 不过需要指出的是,这两种排列方式都不能减少其理论上的误差上界。并且从经验的角度来说,它们也不是总能减小实际的模拟误差。实际上,确定对于某一类哈密顿量而言模拟误差的排列方式,是一个十分值得探索的问题。对于量桨中的 `construct_h_matrix()` 函数而言,用户可以通过指定 `grouping='xyz'` 或者 `grouping='even_odd'` 来实现上文中提到的两种重新排列方式,此外,通过传入参数 `permutation` 也可以指定自定义排列顺序。关于后一点,本教程将在下文章节 **设计基于随机置换的自定义时间演化电路** 中进一步介绍。下面,先让我们来看一下关于 `grouping` 参数的使用方法:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "b2eaca4c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"原始保真度为: 0.5515968012823682\n",
"XYZ 排列后的模拟保真度为: 0.703033891885116\n",
"奇偶排列后的模拟保真度为: 0.744121841784116\n"
]
}
],
"source": [
"# 保持同样的时间演化参数,但是在通过 'grouping=\"xyz\"' 和 'groping=\"even_odd\"' 指定哈密顿量排列\n",
"cir_evolve_xyz = UAnsatz(5)\n",
"cir_evolve_even_odd = UAnsatz(5)\n",
"construct_trotter_circuit(cir_evolve_xyz, h, tau=t/r, steps=r, order=2, grouping='xyz')\n",
"construct_trotter_circuit(cir_evolve_even_odd, h, tau=t/r, steps=r, order=2, grouping='even_odd')\n",
"U_cir_xyz = cir_evolve_xyz.U.numpy()\n",
"U_cir_even_odd = cir_evolve_even_odd.U.numpy()\n",
"print('原始保真度为:', gate_fidelity(get_evolve_op(t), U_cir))\n",
"print('XYZ 排列后的模拟保真度为:', gate_fidelity(get_evolve_op(t), U_cir_xyz))\n",
"print('奇偶排列后的模拟保真度为:', gate_fidelity(get_evolve_op(t), U_cir_even_odd))"
]
},
{
"cell_type": "markdown",
"id": "f18e3f16",
"metadata": {},
"source": [
"#### 初态制备以及对演化后的末态进行观测\n",
"\n",
"下面,我们来制备系统的初态。一般来说,在研究量子多体系统的动力学行为时,一种做法是将系统的初态制备为各种不同的直积态。在量桨中,我们默认的初态为 $\\vert 0...0 \\rangle$,这里我们可以通过 $X$ 门来将奇数格点上的自旋进行翻转,这样系统的初态就制备为了 $\\vert 01010 \\rangle$ 态,用自旋来标记的话则是 $\\vert \\downarrow \\uparrow \\downarrow \\uparrow \\downarrow \\rangle$ 态。"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "e0ff6736",
"metadata": {},
"outputs": [],
"source": [
"# 创建一个用于制备初态的电路,并通过演化得到初态\n",
"cir = UAnsatz(5)\n",
"cir.x(1)\n",
"cir.x(3)\n",
"init_state = cir.run_state_vector()"
]
},
{
"cell_type": "markdown",
"id": "ffa62418",
"metadata": {},
"source": [
"通过将系统的初态 `init_state` 传入方法 `UAnsatz.run_state_vector(init_state)`,我们可以利用刚刚定义的量子线路来演化该初态,并得到演化后的末态。对于演化后的末态,可以使用 `UAnsatz.expecval()` 方法来测量其上的可观测量。这里我们简单地考虑对每个格点上的自旋状态进行观测,即测量可观测量 $\\langle S^z_i \\rangle$,其对应的 Pauli string 为 `[[1, 'Zi']]`(i 为格点下标)。"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "88d5e1b9",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"演化后格点 0 上自旋的 z 方向期望为: 0.9206501927076486\n"
]
}
],
"source": [
"cir_evolve_even_odd.run_state_vector(init_state)\n",
"print('演化后格点 0 上自旋的 z 方向期望为:', cir_evolve_even_odd.expecval([[1, 'Z0']]).numpy()[0])"
]
},
{
"cell_type": "markdown",
"id": "e70d9fba",
"metadata": {},
"source": [
"类似地,通过调整模拟演化的时间长度以及测量的量子比特编号,我们可以绘制出系统中的每个自旋的状态随着时间的完整变化过程。注意这里为了计算理论上的精确解,我们使用了 `SpinOps` 类来构建 $S_i^z$ 算符的矩阵形式,并通过 $\\langle \\psi(t) \\vert S_i^z \\vert \\psi(t) \\rangle$ 来计算其期望值。"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "6c4c03ea",
"metadata": {},
"outputs": [],
"source": [
"def get_evolution_z_obs(h, t_total, order=None, n_steps=None, exact=None):\n",
" \"\"\"\n",
" 该函数可以计算演化过程 t 中系统每个格点上的 Sz 可观测量的变化过程\n",
" 通过 order, n_steps 控制 trotter-suzuki 分解的步长和阶数\n",
" 通过设置 exact=True 可以计算对应的精确解\n",
" \"\"\"\n",
" z_obs_total = []\n",
" for t in np.linspace(0., t_total, t_total * 3 + 1):\n",
" z_obs = []\n",
" # 通过演化算符或者运行电路得到末态\n",
" if exact:\n",
" spin_operators = SpinOps(h.n_qubits)\n",
" fin_state = get_evolve_op(t).dot(init_state)\n",
" else:\n",
" cir_evolve = UAnsatz(5)\n",
" construct_trotter_circuit(cir_evolve, h, tau=t/n_steps, steps=n_steps, order=order, grouping='even_odd')\n",
" fin_state = cir_evolve.run_state_vector(init_state)\n",
" # 对每个格点上的可观测量进行观测\n",
" for site in range(h.n_qubits):\n",
" if exact:\n",
" z_obs.append(fin_state.conj().T.dot(spin_operators.sigz_p[site]).dot(fin_state))\n",
" else:\n",
" z_obs.append(cir_evolve.expecval([[1, 'Z' + str(site)]]).numpy()[0])\n",
" z_obs_total.append(z_obs)\n",
" return np.array(z_obs_total).real \n",
"\n",
"def plot_comparison(**z_obs_to_plot):\n",
" \"\"\" \n",
" 绘制不同的演化结果进行对比,默认每个传入的参数都是 get_evolution_z_obs() 函数的输出并具有同样的演化时间\n",
" \"\"\"\n",
" fig, axes = plt.subplots(1, len(z_obs_to_plot), figsize = [len(z_obs_to_plot) * 3, 5.5])\n",
" \n",
" ax_idx = 0\n",
" for label in z_obs_to_plot.keys():\n",
" im = axes[ax_idx].imshow(z_obs_to_plot[label], cmap='coolwarm_r', interpolation='kaiser', origin='lower')\n",
" axes[ax_idx].set_title(label, fontsize=15)\n",
" ax_idx += 1\n",
"\n",
" for ax in axes:\n",
" ax.set_xlabel('site', fontsize=15)\n",
" ax.set_yticks(np.arange(0, z_obs_total_exact.shape[0], 3))\n",
" ax.set_yticklabels(np.arange(0, z_obs_total_exact.shape[0]/3, 1))\n",
" ax.set_xticks(np.arange(z_obs_total_exact.shape[1]))\n",
" ax.set_xticklabels(np.arange(z_obs_total_exact.shape[1]))\n",
"\n",
" axes[0].set_ylabel('t', fontsize=15)\n",
" cax = fig.add_axes([0.92, 0.125, 0.02, 0.755])\n",
" \n",
" \n",
" fig.colorbar(im, cax)\n",
" cax.set_ylabel(r'$\\langle S^z_i (t) \\rangle$', fontsize=15)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "3735e79a",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 648x396 with 4 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"# 分别计算时长为 3 时,通过步长为 25、5 的电路得到的演化过程,以及精确解\n",
"z_obs_total_exact = get_evolution_z_obs(h, t_total=3, exact=True)\n",
"z_obs_total_cir = get_evolution_z_obs(h, order=1, n_steps=25, t_total=3)\n",
"z_obs_total_cir_short = get_evolution_z_obs(h, order=1, n_steps=5, t_total=3)\n",
"\n",
"plot_comparison(\n",
" Exact=z_obs_total_exact,\n",
" L25_Circuit=z_obs_total_cir,\n",
" L5_Circuit=z_obs_total_cir_short)"
]
},
{
"cell_type": "markdown",
"id": "707ecfdf",
"metadata": {},
"source": [
"我们观察到当线路的深度为 25 时(注意这里的深度指的是时间块的数量而不是量子门的层数),量子电路可以较好的模拟系统在完整演化时间内的自旋动力学。若使用较浅的量子线路,则只能正确模拟系统的行为至一定的时间。\n",
"\n",
"**思考:** 读者是否可以尝试来测量自旋空间关联函数 $\\langle S_i^z S_j^{z} \\rangle$ 并观察其随时间的变化?"
]
},
{
"cell_type": "markdown",
"id": "af97d494",
"metadata": {},
"source": [
"## 设计基于随机置换的自定义时间演化电路\n",
"\n",
"### 随机置换\n",
"\n",
"尽管从物理的角度上看来,将哈密顿量中的对易项重新排列在一起来减小模拟误差是符合直觉的,但是许多证据都表明,固定一种哈密顿量排列的演化策略将会导致模拟误差不断地累积,反而不如将哈密顿量的排列顺序在每个“时间块”中都进行随机置换来得有效 [8, 9]。人们发现,通过不断地将哈密顿量的排列顺序进行随机置换,其演化过程中造成的随机误差比起固定排列时的累积误差来说更加“无害” [8]。无论是在理论上的误差上界与经验性的实验都表明,这种随机排列的演化策略比起固定排列的 Suzuki product formula 具有更小的误差 [9]。"
]
},
{
"cell_type": "markdown",
"id": "63f289c5",
"metadata": {},
"source": [
"### 搭建自定义时间演化电路\n",
"\n",
"量桨中的 `construct_trotter_circuit()` 函数会默认根据 Suzuki product formula 以及输入哈密顿量的顺序来添加时间演化电路。同时,用户可以通过设置 `method='custom'` 并同时向参数 `permutation` 以及 `coefficient` 传入数组的方式来自定义时间演化策略。\n",
"\n",
"**提醒:** 用户在使用 `coefficient`、`tau` 以及 `steps` 参数时需要小心它们之间的关系。一般情况下,传入 `coefficient` 的数组应当是归一化的,即它本身描述的是 $t=1$ 的时间演化过程。在这个基础上,通过设置更多的 `steps`,该函数会将传入的自定义参数所描述的时间演化策略作为一个基本的“时间块”并进行重复,其中每个时间块的演化时长由参数 `tau` 决定。举个例子,若设置 `permutation=np.arange(h.n_qubits)` 且 `coefficient=np.ones(h.n_qubits)`,此时通过 `tau` 与 `steps` 来定义的时间演化电路与一阶 product formula 电路是完全一致的。"
]
},
{
"cell_type": "markdown",
"id": "e72345ac",
"metadata": {},
"source": [
"让我们进一步实际展示一下该自定义功能:考虑和之前相同的哈密顿量,现在我们通过设计一个时间演化电路来测试上文提到的随机置换的结论,即我们希望搭建一个类似于一阶 product formula 的电路,只不过在每个”时间块“内的哈密顿量排列是完全随机且独立的。通过传入一个形状为 `(n_steps, h.n_terms)` 且其每一行都是一个随机置换 $P(N)$ 的数组至参数 `permutation`,就可以实现这一想法:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "1740ec7f",
"metadata": {},
"outputs": [],
"source": [
"# 自定义 permutation 参数的一个例子\n",
"permutation = np.vstack([np.random.permutation(h.n_terms) for i in range(100)])"
]
},
{
"cell_type": "markdown",
"id": "23a1eb6d",
"metadata": {},
"source": [
"接下来,为了验证,可以分别计算该随机电路以及一阶 product formula 在不同电路深度下与精确解之间的保真度来进行比较:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "73084cfc",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"def compare(n_steps):\n",
" \"\"\"\n",
" 比较一阶 product formula 以及随机置换方法在同样的步长的情况下对于固定演化时长 t=2 时的保真度\n",
" 输入参数控制步长,输出分别为一阶 product formula 以及随机置换的保真度\n",
" \"\"\"\n",
" t = 2\n",
" cir_evolve = UAnsatz(5)\n",
" construct_trotter_circuit(cir_evolve, h, tau=t/n_steps, steps=n_steps, order=1)\n",
" U_cir = cir_evolve.U.numpy()\n",
" fid_suzuki = gate_fidelity(get_evolve_op(t), U_cir)\n",
" cir_permute = UAnsatz(5)\n",
" permutation = np.vstack([np.random.permutation(h.n_terms) for i in range(n_steps)])\n",
" # 当不指定 coefficient 参数时,会默认根据 permutation 的形状设置一个归一化且均匀的 coefficient\n",
" construct_trotter_circuit(cir_permute, h, tau=t, steps=1, method='custom', permutation=permutation)\n",
" U_cir = cir_permute.U.numpy()\n",
" fid_random = gate_fidelity(get_evolve_op(t), U_cir)\n",
" return fid_suzuki, fid_random\n",
"\n",
"# 比较在不同步长时的两种方案的保真度\n",
"# 出于运行时间的考虑,只进行一次试验,感兴趣的读者可以进行多次重复实验并计算其 error bar\n",
"n_range = [100, 200, 500, 1000]\n",
"result = [compare(n) for n in n_range]\n",
"\n",
"result = 1 - np.array(result)\n",
"plt.loglog(n_range, result[:, 0], 'o-', label='1st order PF')\n",
"plt.loglog(n_range, result[:, 1], 'o-', label='Random')\n",
"plt.xlabel(r'Trotter number $r$', fontsize=12)\n",
"plt.ylabel(r'Error: $1 - {\\rm Fid}$', fontsize=12)\n",
"plt.legend()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "85f72361",
"metadata": {},
"source": [
"图中,“1st order PF” 指按照固定顺序搭建的一阶 product formula 电路。与预期一样,随机置换确实可以在相同的电路深度下达到比一阶 product formula 更好的模拟效果。\n",
"\n",
"**思考:** 在 [9] 中,作者指出这种随机的策略在没有利用任何与哈密顿量有关的信息的前提下就取得了更小的误差,那么有理由相信存在一种方法可以在利用哈密顿量信息的同时进一步减小该误差。这对于人们设计更好的模拟时间演化策略带来了启发。"
]
},
{
"cell_type": "markdown",
"id": "4f193c11",
"metadata": {},
"source": [
"## 小结\n",
"\n",
"对于量子多体系统的动力学性质进行研究,是理解新奇量子物态的重要手段。由于其高度纠缠的量子力学本质,无论是在理论上还是在实验上的研究都是十分困难的。时至今日,人们对于不同几何结构,不同相互作用下的二维,乃至包含了无序性的一维系统上的物理现象都没能完全理解。另一方面,通用量子计算机以及量子模拟器的快速发展给这一问题的解决带来了新的希望。以通用量子计算机为例,通过搭建量子电路,其优势在于可以模拟各种复杂情况下的系统演化过程,例如,模拟其哈密顿量随时间周期性变化的系统从而寻找“时间晶体”的存在。随着量子比特数目和控制能力的进一步提高,通用量子计算机有望在近未来内在模拟量子系统时间演化这一任务上超越经典计算机,这其中,最有希望最先取得进展的就是量子自旋系统的模拟。\n",
"\n",
"本教程主要介绍了如何在量桨中模拟一个真实量子自旋模型的时间演化过程,并且进一步探讨了基于量桨来设计新的时间演化策略的可能性。通过 `construct_trotter_circuit()` 函数以及 `Hamiltonian` 和 `SpinOps` 类中提供的各种方法,用户现在可以简单地设计并测试不同搭建时间演化的策略。我们也鼓励读者在更多的物理系统上尝试不同的时间演化策略,并一起探索更加高效的量子模拟电路。"
]
},
{
"cell_type": "markdown",
"id": "ff5b39fa",
"metadata": {},
"source": [
"---\n",
"\n",
"## 参考文献\n",
"\n",
"[1] Childs, Andrew M., et al. \"Toward the first quantum simulation with quantum speedup.\" [Proceedings of the National Academy of Sciences 115.38 (2018): 9456-9461](https://www.pnas.org/content/115/38/9456.short).\n",
"\n",
"[2] Eckle, Hans-Peter. Models of Quantum Matter: A First Course on Integrability and the Bethe Ansatz. [Oxford University Press, 2019](https://oxford.universitypressscholarship.com/view/10.1093/oso/9780199678839.001.0001/oso-9780199678839).\n",
"\n",
"[3] Mikeska, Hans-Jürgen, and Alexei K. Kolezhuk. \"One-dimensional magnetism.\" Quantum magnetism. Springer, Berlin, Heidelberg, 2004. 1-83.\n",
"\n",
"[4] Berger, L., S. A. Friedberg, and J. T. Schriempf. \"Magnetic Susceptibility of $\\rm Cu(NO_3)_2·2.5 H_2O$ at Low Temperature.\" [Physical Review 132.3 (1963): 1057](https://journals.aps.org/pr/abstract/10.1103/PhysRev.132.1057).\n",
"\n",
"[5] Broholm, C., et al. \"Quantum spin liquids.\" [Science 367.6475 (2020)](https://science.sciencemag.org/content/367/6475/eaay0668).\n",
"\n",
"[6] Abanin, Dmitry A., et al. \"Colloquium: Many-body localization, thermalization, and entanglement.\" [Reviews of Modern Physics 91.2 (2019): 021001](https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.91.021001).\n",
"\n",
"[7] Medenjak, Marko, Berislav Buča, and Dieter Jaksch. \"Isolated Heisenberg magnet as a quantum time crystal.\" [Physical Review B 102.4 (2020): 041117](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.102.041117).\n",
"\n",
"[8] Wallman, Joel J., and Joseph Emerson. \"Noise tailoring for scalable quantum computation via randomized compiling.\" [Physical Review A 94.5 (2016): 052325](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.94.052325).\n",
"\n",
"[9] Childs, Andrew M., Aaron Ostrander, and Yuan Su. \"Faster quantum simulation by randomization.\" [Quantum 3 (2019): 182](https://quantum-journal.org/papers/q-2019-09-02-182/)."
]
}
],
"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.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
{
"cells": [
{
"cell_type": "markdown",
"id": "9d3716bb",
"metadata": {},
"source": [
"# Simulate the Spin Dynamics on a Heisenberg Chain\n",
"\n",
"<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
]
},
{
"cell_type": "markdown",
"id": "c0832e9f",
"metadata": {},
"source": [
"## Introduction\n",
"\n",
"The simulation of quantum systems is one of the many important applications of quantum computers. In general, the system's properties are characterized by its Hamiltonian operator $H$. For physical systems at different scales, their Hamiltonian takes different forms. For example in quantum chemistry, where we are often interested in the properties of molecules, which are determined mostly by electron-electron Coulomb interactions. As a consequence, a molecular Hamiltonian is usually written in the form of fermionic operators which act on the electron's wave function. On the other hand, the basic computational unit of a quantum computer - qubit, and its corresponding operations, correspond to spin and spin operators in physics. So in order to simulate a molecular Hamiltonian on a quantum computer, one needs to first map fermionic operators into spin operators with mappings such as Jordan-Wigner or Bravyi-Kitaev transformation, etc. Those transformations often create additional overhead for quantum simulation algorithms, make them more demanding in terms of a quantum computer's number of qubits, connectivity, and error control. It was commonly believed that one of the most near-term applications for quantum computers it the simulation of quantum spin models, whose Hamiltonian are natively composed of Pauli operators. \n",
"\n",
"This tutorial will demonstrate how to simulate the time evolution process of a one-dimensional Heisenberg chain, one of the most commonly studied quantum spin models. This tutorial is based on the `construct_trotter_circuit()`, which can construct the Trotter-Suzuki or any custom trotterization circuit to simulate the time-evolving operator. We have already covered some of the basic usage as well as the theoretical background in another tutorial [Hamiltonian Simulation with Product Formula](./HamiltonianSimulation_EN.ipynb). A brief introduction of the Suzuki product formula is provided below for readers who are not familiar with it. In the remainder of this tutorial, we will be focusing on two parts:\n",
"- Simulating the spin dynamics on a Heisenberg chain\n",
"- Using randomized permutation to build a custom trotter circuit"
]
},
{
"cell_type": "markdown",
"id": "988b3a47",
"metadata": {},
"source": [
"---\n",
"Before discussing the physical background of the Heisenberg model, let's go over the basic concepts of time evolution simulation with a quantum circuit. Readers already familiar with this or uninterested in such details could choose to skip to the section of **Heisenberg model and its dynamical simulation** to continue reading.\n",
"\n",
"### Simulate the time evolution with Suzuki product formula\n",
"\n",
"The core idea of the Suzuki product formula can be described as follows: First, for a time-independent Hamiltonian $H = \\sum_k^L h_k$, the system's time evolution operator is \n",
"\n",
"$$\n",
"U(t) = e^{-iHt}.\n",
"\\tag{1}\n",
"$$\n",
"\n",
"Further dividing it into $r$ pieces, we have\n",
"\n",
"$$\n",
"e^{-iHt} = \\left( e^{-iH \\tau} \\right)^r, ~\\tau=\\frac{t}{r}.\n",
"\\tag{2}\n",
"$$\n",
"\n",
"This strategy is sometimes referred to as \"Totterization\". \n",
"\n",
"And for each $e^{-iH \\tau}$ operator, its Suzuki decompositions are\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"S_1(\\tau) &= \\prod_{k=0}^L \\exp ( -i h_k \\tau),\n",
"\\\\\n",
"S_2(\\tau) &= \\prod_{k=0}^L \\exp ( -i h_k \\frac{\\tau}{2})\\prod_{k=L}^0 \\exp ( -i h_k \\frac{\\tau}{2}),\n",
"\\\\\n",
"S_{2k+2}(\\tau) &= [S_{2k}(p_k\\tau)]^2S_{2k}\\left( (1-4p_k)\\tau\\right)[S_{2k}(p_k\\tau)]^2.\n",
"\\end{aligned}\n",
"\\tag{3}\n",
"$$\n",
"\n",
"Back to the original time evolution operator $U(t)$, with the $k$-th order Suzuki decomposition, it can be reformulated as\n",
"\n",
"$$\n",
"U(t) = e^{-iHt} = \\left( S_{k}\\left(\\frac{t}{r}\\right) \\right)^r.\n",
"\\tag{4}\n",
"$$\n",
"\n",
"The above scheme is referred to as the Suzuki product formula or Trotter-Suzuki decomposition. It is proven that it could efficiently simulate any time evolution process of a system with a k-local Hamiltonian up to arbitrary precision [1]. In another tutorial [Hamiltonian Simulation with Product Formula](./HamiltonianSimulation_EN.ipynb), we have shown how to calculate its error upper bound.\n",
"\n",
"---"
]
},
{
"cell_type": "markdown",
"id": "ab3f7311",
"metadata": {},
"source": [
"## Heisenberg Model and Its Dynamic Simulation\n",
"\n",
"The Heisenberg model is arguably one of the most commonly used model in the research of quantum magnetism and quantum many-body physics. Its Hamiltonian can be expressed as \n",
"\n",
"$$\n",
"H = \\sum_{\\langle i, j\\rangle} \n",
"\\left( J_x S^x_{i} S^x_{j} + J_y S^y_{i} S^y_{j} + J_z S^z_{i} S^z_{j} \\right)\n",
"+\n",
"\\sum_{i} h_z S^z_i, \n",
"\\tag{5}\n",
"$$\n",
"\n",
"with $\\langle i, j\\rangle$ depends on the specific lattice structure, $J_x, J_y, J_z$ describe the spin coupling strength respectively in the $xyz$ directions and $h_z$ is the magnetic field applied along the $z$ direction. When taking $J_z = 0$, the Hamiltonian in (5) can be used to describe the XY model; or when taking $J_x = J_y = 0$, then (5) is reduced to the Hamiltonian of Ising model. Note that here we used a notation of many-body spin operators $S^x_i, S^y_i, S^z_i$ which act on each of the local spins, this is slightly different from our usual notations but are very common in the field of quantum many-body physics. For a spin-1/2 system, when neglecting a coefficient of $\\hbar/2$, the many-body spin operators are simple tensor products of Pauli operators, i.e.\n",
"\n",
"$$\n",
"S^P_{i} = \\left ( \\otimes_{j=0}^{i-1} I \\right ) \\otimes \\sigma_{P} \\otimes \\left ( \\otimes_{j=i+1}^{L} I \\right ),\n",
"P \\in \\{ x, y, z \\},\n",
"\\tag{6}\n",
"$$\n",
"\n",
"where the $\\sigma_{P}$ are Pauli operators, which can also be represented as $XYZ$. It is worth noting that while the Heisenberg model is an important theoretical model, but it also describes the physics in realistic materials (crystals). Starting from the Hubbard model, which describes the interactions and movement of electrons on a lattice, under certain conditions, the electrons are fixed to each site and form a half-filling case. In this case, the only left-over interaction is an effective spin-spin exchange interaction and the Hubbard model is reduced to the Heisenberg model [2]. While it seems that many approximations are made, the Heisenberg model has successfully described the properties of many crystal materials at low temperatures [3]. For example, many readers might be familiar with the copper nitrate crystal ($\\rm Cu(NO_3)_2 \\cdot 2.5 H_2 O$), and its behavior at $\\sim 3k$ can be described by an alternating spin-1/2 Heisenberg chain [4].\n",
"\n",
"Depending on the lattice structure, the Heisenberg model can host highly non-trivial quantum phenomena. As a one-dimensional chain, it demonstrates ferromagnetism and anti-ferromagnetism, symmetry breaking and gapless excitations [3]. On frustrated two-dimension lattices, some Heisenberg models constitute candidate models for quantum spin liquids, a long-range entangled quantum matter [5]. When under a disordered external magnet field, the Heisenberg model also can be used in the research of a heated topic, many-body localization, where the system violates the thermalization hypothesis and retains memories of its initial state after infinitely long time's evolution [6]. \n",
"\n",
"Simulating the time evolution of a Heisenberg model, i.e. the dynamical simulation, could help us to investigate the non-equilibrium properties of the system, and it might help us to locate novel quantum phases such as the many-body localized (MBL) phase introduced above or even more interestingly, time crystal phases [7]. Other than developing theories, the dynamic simulation plays a vital role for experimentalists, as the spin correlation function (also referred to as dynamical structure factors) is directly linked to the cross sections for scattering experiments or line shapes in nuclear magnetic resonance (NMR) experiments [3]. And this function, which we omit its exact form here, is a function of integration over $\\langle S(t) S(0) \\rangle$. So that in order to bridge the experiment and theory, one also need to compute the system's evolution in time.\n",
"\n",
"### Use Paddle Quantum to simulate and observe the time evolution process of a Heisenberg chain"
]
},
{
"cell_type": "markdown",
"id": "ca09d58d",
"metadata": {},
"source": [
"Now, we will take a one dimensional Heisenberg chain under disordered field of length 5 as an example, and demonstrate how the construct its time evolving circuit in Paddle Quantum. First we need to import relevant packages."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "5c873819",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import scipy\n",
"from scipy import linalg\n",
"import matplotlib.pyplot as plt\n",
"from paddle_quantum.circuit import UAnsatz\n",
"from paddle_quantum.utils import SpinOps, Hamiltonian, gate_fidelity\n",
"from paddle_quantum.trotter import construct_trotter_circuit, get_1d_heisenberg_hamiltonian"
]
},
{
"cell_type": "markdown",
"id": "d96d3bcb",
"metadata": {},
"source": [
"Then we use `get_1d_heisenberg_hamiltonian()` function to generate the Hamiltonian of a Heisenberg chain."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "88fa56fe",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The Hamiltoninan is:\n",
"1.0 X0, X1\n",
"1.0 Y0, Y1\n",
"2.0 Z0, Z1\n",
"1.0 X1, X2\n",
"1.0 Y1, Y2\n",
"2.0 Z1, Z2\n",
"1.0 X2, X3\n",
"1.0 Y2, Y3\n",
"2.0 Z2, Z3\n",
"1.0 X3, X4\n",
"1.0 Y3, Y4\n",
"2.0 Z3, Z4\n",
"0.30554627625735065 Z0\n",
"0.6025258365109716 Z1\n",
"-0.1545550348192246 Z2\n",
"0.9823722719316286 Z3\n",
"-0.6157481417955128 Z4\n"
]
}
],
"source": [
"h = get_1d_heisenberg_hamiltonian(length=5, j_x=1, j_y=1, j_z=2, h_z=2 * np.random.rand(5) - 1,\n",
" periodic_boundary_condition=False)\n",
"print('The Hamiltoninan is:')\n",
"print(h)"
]
},
{
"cell_type": "markdown",
"id": "0793414b",
"metadata": {},
"source": [
"After obtaining its Hamiltonian, we can then pass it to the `construct_trotter_circuit()` function to construct its time evolution circuit. Also, with `Hamiltonian.construct_h_matrix()` who returns the matrix form of a `Hamiltonian` object, we can calculate its exponential, i.e. the exact time-evolving operator. By taking the quantum circuit's unitary matrix `UAnsatz.U` and comparing it to the exact time-evolving operator by calculating their fidelity, we can evaluate how well the constructed circuit could describe the correct time evolution process."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "5052fb32",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The fidelity between the circuit's unitary and the exact evolution operator is : 0.58\n"
]
}
],
"source": [
"# calculate the exact evolution operator of time t\n",
"def get_evolve_op(t): return scipy.linalg.expm(-1j * t * h.construct_h_matrix())\n",
"\n",
"# set the total evolution time and the number of trotter steps\n",
"t = 3\n",
"r = 10\n",
"# construct the evolution circuit\n",
"cir_evolve = UAnsatz(5)\n",
"construct_trotter_circuit(cir_evolve, h, tau=t/r, steps=r, order=2)\n",
"# get the circuit's unitary matrix and calculate its fidelity to the exact evolution operator\n",
"U_cir = cir_evolve.U.numpy()\n",
"print('The fidelity between the circuit\\'s unitary and the exact evolution operator is : %.2f' % gate_fidelity(get_evolve_op(t), U_cir))"
]
},
{
"cell_type": "markdown",
"id": "ce487d74",
"metadata": {},
"source": [
"#### Permute the Hamiltonian according to commutation relationships\n",
"\n",
"It has been shown that the product formula's simulating error can be reduced by rearranging different terms. Since the error of simulation arises from the non-commuting terms in the Hamiltonian, one natural idea is to permute the Hamiltonian so that commuting terms are put together. For example, we could divide a Hamiltonian into four parts,\n",
"\n",
"$$\n",
"H = H_x + H_y + H_z + H_{\\rm other},\n",
"\\tag{7}\n",
"$$\n",
"\n",
"where $H_x, H_y, H_z$ contain terms only composed of $X, Y, Z$ operators, and $H_{\\rm other}$ are all the other terms. For Hamiltonian describe in (5), all terms can be grouped into $H_x, H_y, H_z$.\n",
"\n",
"Another approach is to decompose the Hamiltonian according to the system geometry. Especially for one-dimensional nearest-neighbor systems, the Hamiltonian can be divided into even and odd terms, \n",
"\n",
"$$\n",
"H = H_{\\rm even} + H_{\\rm odd}.\n",
"\\tag{8}\n",
"$$\n",
"\n",
"where $H_{\\rm even}$ are interactions on sites $(0, 1), (2, 3), ...$ and $H_{\\rm odd}$ are interactions on sites $(1, 2), (3, 4), ...$.\n",
"\n",
"Note that these two permutation strategies do **not** reduce the bound on simulation error, and empirical results return a more case-by-case effect on the error. Nevertheless, we provide the above two decompositions as a built-in option of the `construct_trotter_circuit()` function. By setting the argument `grouping='xyz'` or `grouping='even_odd'`, the function will automatically try to rearrange the Hamiltonian when adding the trotter circuit. Besides, users can also customize permutation by using the argument `permutation`, which we will introduce shortly in the next section. For now, let's test the `grouping` option and check the variations in fidelity:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "b2eaca4c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Original fidelity: 0.5777594967189371\n",
"XYZ permuted fidelity: 0.7009189629323416\n",
"Even-odd permuted fidelity: 0.7301965802081132\n"
]
}
],
"source": [
"# using the same evolution parameters, but set 'grouping=\"xyz\"' and 'grouping=\"even_odd\"'\n",
"cir_evolve_xyz = UAnsatz(5)\n",
"cir_evolve_even_odd = UAnsatz(5)\n",
"construct_trotter_circuit(cir_evolve_xyz, h, tau=t/r, steps=r, order=2, grouping='xyz')\n",
"construct_trotter_circuit(cir_evolve_even_odd, h, tau=t/r, steps=r, order=2, grouping='even_odd')\n",
"U_cir_xyz = cir_evolve_xyz.U.numpy()\n",
"U_cir_even_odd = cir_evolve_even_odd.U.numpy()\n",
"print('Original fidelity:', gate_fidelity(get_evolve_op(t), U_cir))\n",
"print('XYZ permuted fidelity:', gate_fidelity(get_evolve_op(t), U_cir_xyz))\n",
"print('Even-odd permuted fidelity:', gate_fidelity(get_evolve_op(t), U_cir_even_odd))"
]
},
{
"cell_type": "markdown",
"id": "f18e3f16",
"metadata": {},
"source": [
"#### Initial state preparation and final state observation\n",
"\n",
"Now let's prepare the system's initial state. Generally speaking, one common approach when studying the dynamics of a quantum system is to start the evolution from different direct product states. In Paddle Quantum, the default initial state is $\\vert 0...0 \\rangle$, so we can simply apply $X$ gate to different qubits to get a direct product initial state. For example, here we apply $X$ gate to qubits representing spins on odd sites, so the initial state will become $\\vert 01010 \\rangle$, as in spin notation, $\\vert \\downarrow \\uparrow \\downarrow \\uparrow \\downarrow \\rangle$."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "e0ff6736",
"metadata": {},
"outputs": [],
"source": [
"# create a circuit used for initial state preparation\n",
"cir = UAnsatz(5)\n",
"cir.x(1)\n",
"cir.x(3)\n",
"# run the circuit the get the initial state\n",
"init_state = cir.run_state_vector()"
]
},
{
"cell_type": "markdown",
"id": "e7d5b832",
"metadata": {},
"source": [
"By passing the initial state `init_state` into the method `UAnsatz.run_state_vector(init_state)`, we can evolve the initial state with a quantum circuit. Then by `UAnsatz.expecval()` method, the expectation value of a user-specified observable on the final state could be measured. For simplicity, we only consider a single-spin observable $\\langle S_i^z \\rangle$ here, its corresponding Pauli string is `[[1, 'Zi']]` (i being an integer)."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "88d5e1b9",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Sz observable on the site 0 is: 0.6516225940072864\n"
]
}
],
"source": [
"cir_evolve_even_odd.run_state_vector(init_state)\n",
"print('Sz observable on the site 0 is:', cir_evolve_even_odd.expecval([[1, 'Z0']]).numpy()[0])"
]
},
{
"cell_type": "markdown",
"id": "e70d9fba",
"metadata": {},
"source": [
"Similarly, by adjusting the simulation time length and changing the observable, we could plot the entire evolution process of different spins. Note here in order to compute the exact solution, we need to construct the matrix form of each observable $S_i^z$ using `SpinOps` class and calculate their expectation value with $\\langle \\psi(t) \\vert S_i^z \\vert \\psi(t) \\rangle$."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "6c4c03ea",
"metadata": {},
"outputs": [],
"source": [
"def get_evolution_z_obs(h, t_total, order=None, n_steps=None, exact=None):\n",
" \"\"\" \n",
" a function to calculate a system's Sz observable on each site for an entire evolution process t\n",
" specify the order the trotter length by setting order and n_steps\n",
" set exact=True to get the exact results\n",
" \"\"\"\n",
" z_obs_total = []\n",
" for t in np.linspace(0., t_total, t_total * 3 + 1):\n",
" z_obs = []\n",
" # get the final state by either evolving with a circuit or the exact operator\n",
" if exact:\n",
" spin_operators = SpinOps(h.n_qubits)\n",
" fin_state = get_evolve_op(t).dot(init_state)\n",
" else:\n",
" cir_evolve = UAnsatz(5)\n",
" construct_trotter_circuit(cir_evolve, h, tau=t/n_steps, steps=n_steps, order=order, grouping='even_odd')\n",
" fin_state = cir_evolve.run_state_vector(init_state)\n",
" # measure the observable on each site\n",
" for site in range(h.n_qubits):\n",
" if exact:\n",
" z_obs.append(fin_state.conj().T.dot(spin_operators.sigz_p[site]).dot(fin_state))\n",
" else:\n",
" z_obs.append(cir_evolve.expecval([[1, 'Z' + str(site)]]).numpy()[0])\n",
" z_obs_total.append(z_obs)\n",
" return np.array(z_obs_total).real \n",
"\n",
"def plot_comparison(**z_obs_to_plot):\n",
" \"\"\"\n",
" plot comparison between different evolution results\n",
" assume each argument passed into it is returned from get_evolution_z_obs() function for the same t_total\n",
" \"\"\"\n",
" fig, axes = plt.subplots(1, len(z_obs_to_plot), figsize = [len(z_obs_to_plot) * 3, 5.5])\n",
" \n",
" ax_idx = 0\n",
" for label in z_obs_to_plot.keys():\n",
" im = axes[ax_idx].imshow(z_obs_to_plot[label], cmap='coolwarm_r', interpolation='kaiser', origin='lower')\n",
" axes[ax_idx].set_title(label, fontsize=15)\n",
" ax_idx += 1\n",
"\n",
" for ax in axes:\n",
" ax.set_xlabel('site', fontsize=15)\n",
" ax.set_yticks(np.arange(0, z_obs_total_exact.shape[0], 3))\n",
" ax.set_yticklabels(np.arange(0, z_obs_total_exact.shape[0]/3, 1))\n",
" ax.set_xticks(np.arange(z_obs_total_exact.shape[1]))\n",
" ax.set_xticklabels(np.arange(z_obs_total_exact.shape[1]))\n",
"\n",
" axes[0].set_ylabel('t', fontsize=15)\n",
" cax = fig.add_axes([0.92, 0.125, 0.02, 0.755])\n",
" \n",
" \n",
" fig.colorbar(im, cax)\n",
" cax.set_ylabel(r'$\\langle S^z_i (t) \\rangle$', fontsize=15)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "3735e79a",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 648x396 with 4 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"# calculate the evolution process with circuits of trotter number 25 and 5, and the exact result\n",
"z_obs_total_exact = get_evolution_z_obs(h, t_total=3, exact=True)\n",
"z_obs_total_cir = get_evolution_z_obs(h, order=1, n_steps=25, t_total=3)\n",
"z_obs_total_cir_short = get_evolution_z_obs(h, order=1, n_steps=5, t_total=3)\n",
"\n",
"plot_comparison(\n",
" Exact=z_obs_total_exact,\n",
" L25_Circuit=z_obs_total_cir,\n",
" L5_Circuit=z_obs_total_cir_short)"
]
},
{
"cell_type": "markdown",
"id": "707ecfdf",
"metadata": {},
"source": [
"Observed that with 25 trotter blocks, the circuit could very well simulate the spin dynamics for the entire period. In contrast, the shorter circuit with only 5 trotter blocks could only describe the system's behavior correctly up to a certain time until the simulation breaks down.\n",
"\n",
"**Exercise:** Could the readers try to observe the evolution of spatial spin correlation function $\\langle S_i^z S_j^{z} \\rangle$?"
]
},
{
"cell_type": "markdown",
"id": "af97d494",
"metadata": {},
"source": [
"## Design customized trotter circuit with random permutation\n",
"\n",
"### Random permutation\n",
"\n",
"Although it seems more physically reasonable to group the commuting terms in the Hamiltonian to achieve better simulation performance, many evidence has shown that using a fixed order Hamiltonian for each trotter block might cause the errors to accumulate. On the other hand, evolving the Hamiltonian according to an random ordering might \"wash-out\" some of the coherent error in the simulation process and replace it with less harmful stochastic noise [8]. Both theoretical analyses on the error upper bound and empirical evidences show that this randomization could effectively reduce the simulation error [9]."
]
},
{
"cell_type": "markdown",
"id": "9ecc2ea0",
"metadata": {},
"source": [
"### Customize trotter circuit construction\n",
"\n",
"By default, the function `construct_trotter_circuit()` constructs a time evolving circuit according to the Suzuki product formula. However, users could choose to customize both the coefficients and permutations by setting `method='custom'` and passing custom arrays to arguments `permutation` and `coefficient`. \n",
"\n",
"**Note:** The user should be very cautious when using arguments `coefficient`, `tau` and `steps` altogether. By setting `steps` other than 1 and `tau` other than $t$ (the total evolution time), it is possible to further trotterize the custom coefficient and permutation. For example, when setting `permutation=np.arange(h.n_qubits)` and `coefficient=np.ones(h.n_qubits)`, the effect of `tau` and `steps` is exactly the same as constructing the first-order product formula circuit."
]
},
{
"cell_type": "markdown",
"id": "8afa9fe1",
"metadata": {},
"source": [
"Let us further demonstrate the customization function with a concrete example. With the same spin chain Hamiltonian, now we wish to design an evolution strategy similar to the first-order product formula, however the ordering of the Hamiltonian terms within each trotter block is independently random. We could implement this by pass an arraying of shape `(n_steps, h.n_terms)` to the argument `permutation`, and each row of that array is a random permutation $P(N)$."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "f70ab81b",
"metadata": {},
"outputs": [],
"source": [
"# An example for customize permutation\n",
"permutation = np.vstack([np.random.permutation(h.n_terms) for i in range(100)])"
]
},
{
"cell_type": "markdown",
"id": "8d189064",
"metadata": {},
"source": [
"Then, we compare the fidelity of such strategy with the first order product formula under different trotter length."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "6d6910c9",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAY4AAAEOCAYAAACetPCkAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/Z1A+gAAAACXBIWXMAAAsTAAALEwEAmpwYAAAzT0lEQVR4nO3dd3hUZf7+8feTQhJqpAcQRZGeAC5dRZqgBBTLqljAxb4qblG/uq6Krv5gdXddUdayCtgVFREIlhVEEJEmmIBIESkJoUNoAVKe3x9nJg4hgUwyyZlyv66LC+Zk5sxnLHPzdGOtRUREpKyi3C5ARERCi4JDRET8ouAQERG/KDhERMQvCg4REfGLgkNERPwS43YBlckYMxQYWqtWrVtbtWrldjkiIiFl2bJlu6y1DYpfN5GwjqNLly526dKlbpchIhJSjDHLrLVdil9XV5WIiPhFwSEiIn5RcIiIiF8iYnC8ZcuWbpciIqeQl5dHZmYmR44ccbuUiBMfH0+zZs2IjY0t0/M1OF6KacuzeObzNWzdl0uTxATuH9SaYZ2bVlKFIvLLL79Qq1Yt6tWrhzHG7XIihrWW3bt3c+DAAVq0aHHczzQ47odpy7N4aGoGWftysUDWvlwemprBtOVZbpcmEraOHDmi0HCBMYZ69er51dJTcJTgmc/XkJtXcNy13LwCnvl8jUsViUQGhYY7/P3nruAowdZ9uaVeLywM/649kUg1atQoGjZsSIcOHU753Llz5/Ltt98G9P3PPPNMdu3aVe7X33TTTbRo0YJOnTpx7rnnsnDhwhOud+rUifHjx1eoTgVHCZokJpR43QK9xs3h8RmrWLpxj0JExEXTlmdx3rg5tHgwjfPGzQlIV/JNN93EZ599VqbnVjQ48vPzy/1ar4KCghOuPfPMM6xYsYJx48Zx++23n3B9xYoVjB49ukLvq+Aowf2DWpMQG33ctfjYKG7o0ZzkZnV4e9FmrnppIb3GzWHM9FUsUYiIVKnKGofs3bs3devWPeH6+PHjadeuHSkpKVx77bVs3LiRl156iWeffZZOnToxf/78456/Z88ehg0bRkpKCj169CA9PR2AMWPGcOONN3Leeedx4403snv3bgYOHEj79u255ZZb8J2s9NZbb9GtWzc6derE7bffXhQSNWvW5M9//jMdO3YsalGU9lnWr19foX8epdF03BJ4Z0+VNqvqwJE85vy0g7T0bN5ZvJnJ326kUe04LumQRGpKEr9pfhpRUeqrFSmvx2es4set+0v9+fLN+zhWUHjctdy8Ah74MJ13F28u8TXtmtTmsaHty1XPuHHj+OWXX4iLi2Pfvn0kJiZyxx13ULNmTe67774Tnv/YY4/RuXNnpk2bxpw5cxgxYgQrVqwA4Mcff+Sbb74hISGB0aNHc/755/Poo4+SlpbGa6+9BsDq1at5//33WbBgAbGxsfz+97/n7bffZsSIERw6dIju3bvzz3/+86Q1z5gxg+Tk5KLH999/P08++SQAb7755nE/81dYB4e1dgYwo0uXLrf6+9phnZuWOv22Vnwsl3VqymWdmnLwaD6zV29nVsaJITI4OYkuZyhERAKteGic6npFpaSkcP311zNs2DCGDRt2yud/8803fPTRRwD069eP3bt3s3+/E4SXXnopCQlOd/i8efOYOnUqAKmpqZx22mkAzJ49m2XLltG1a1cAcnNzadiwIQDR0dFceeWVpb63NyAaNGhQFETgdFVdddVVfn7ykoV1cFSFmnExJYbIu54QaVgrjks6NHZC5My6RCtERE7pVC2D88bNIauESSxNExN4//aeAa8nLS2NefPmMWPGDJ566ikyMjLKfa8aNWqc8jnWWkaOHMnYsWNP+Fl8fDzR0dElvMoRyIAojcY4AsgbIi/f2IVlj1zE+OGdObf5aby3ZAvXvPIdPcbO5tFPVrJow24KNCYiUm4ljUMmxEZz/6DWAX+vwsJCtmzZQt++ffn73/9OTk4OBw8epFatWhw4cKDE11xwwQW8/fbbgDOIXr9+fWrXrn3C83r37s0777wDwKeffsrevXsB6N+/Px9++CE7duwAnDGTTZs2BfyzlZdaHJWkZlwMl3ZswqUdm3DoaD5zftrBrIxs3l+yhTcWbqKBT0ukq1oiIn451ThkeQ0fPpy5c+eya9cumjVrxuOPP86IESO44YYbyMnJwVrL6NGjSUxMZOjQoVx11VV88sknPP/881xwwQVF9xkzZgyjRo0iJSWF6tWr8/rrr5f4fo899hjDhw+nffv29OrVi+bNmwPQrl07nnzySQYOHEhhYSGxsbFMmDCBM844o0KfL1C05UgV8w2Rr9bs4EheIQ1qxXFxeydEurVQiEhkWr16NW3btnW7jIhV0j//0rYcUYujitWIi2FoxyYM9bREvlrjhMgHy7bw5nebqF8zjos7NCI1uYlCRESCkoLDRTXiYhiS0oQhKU04fCyfr37aSVrGVj5clslb320uCpHByUl0b1FPISIiQSGsgyOUtlWvXi2G1BRnHYg3RGZlZPPRsixPiFRjUPvGpHq6s2KiNa9BRNwR1sFRkXUcbioeInPX7CQtI5up32fx9qLN1KtRjUEdnBDprhARkSoW1sERDqpXi2FwsrOYMPdYAXPX7GBmRjYff5/FO54QGdi+MUNSFCIiUjUUHCEkoVo0lyQncYlPiKRlZPPJiizeXbyZujV+7c7qcZZCREQqh4IjRBUPka/X7mBmevEQcQbWe55VTyEiUgbR0dEkJyeTn59PixYtePPNN0lMTKzwfSdPnszSpUt54YUXKl5kEFBwhIGEatFc3CGJizskcSTP2xLZxicrtvLu4i2cVj3WaYmkKEQkjKRPgdlPQE4m1GkG/R+FlKsrdMuEhISizQhHjhzJhAkTePjhhwNQbHhRcISZ+NjiIeLMzprxw1beW/JriAxOTqLn2fWIVYhIKEqfAjNGQ55nv6qcLc5jqHB4ePXs2bNoO/TFixdz7733cuTIERISEpg0aRKtW7dm8uTJTJ8+ncOHD/Pzzz9z+eWX8/TTTwMwadIkxo4dS2JiIh07diQuLg6AjRs3MmrUKHbt2kWDBg2YNGkSzZs356abbiIhIYHly5ezY8cOJk6cyBtvvMHChQvp3r07kydPDsjnCgQFRxhzQqQxF3dozJG8Ar5eu5O09F9DJLF6LIPaNWZwShK9FCISTD59ELadZCPBzCVQcPT4a3m58MndsKzk7T1onAyXjCvT2xcUFDB79mxuvvlmANq0acP8+fOJiYnhyy+/5C9/+UvR7rcrVqxg+fLlxMXF0bp1a+655x5iYmJ47LHHWLZsGXXq1KFv37507twZgHvuuYeRI0cycuRIJk6cyOjRo5k2bRoAe/fuZeHChUyfPp1LL72UBQsW8Oqrr9K1a1dWrFhBp06dylR/ZVNwRIj42GgGtW/MoPa/hsisjGxmpm/l/aVOiAxs14jUlCYKEQl+xUPjVNfLKDc3l06dOpGVlUXbtm256KKLAMjJyWHkyJGsW7cOYwx5eXlFr+nfvz916tQBnD2mNm3axK5du+jTpw8NGjQA4JprrmHt2rUALFy4sGgr9RtvvJEHHnig6F5Dhw7FGENycjKNGjUqOjOjffv2bNy4UcEh7ikeIvM8ITIrYxtTlmYWhcjg5CTOa1lfISJV71Qtg2c7ON1TxdU5HX6XVu639Y5xHD58mEGDBjFhwgRGjx7NI488Qt++ffn444/ZuHEjffr0KXqNtwsKnMH1ihwJ671XVFTUcfeNiooKyFGzgaJvhAgXHxvNwPaN+fe1nVn61wH8d0QX+rRqwKyMbdw0aQldnvyS+z/4ga/W7OBYfuUckiPit/6PQmzC8ddiE5zrAVC9enXGjx/PP//5T/Lz88nJyaFpU2fn3bKMNXTv3p2vv/6a3bt3k5eXxwcffFD0s169evHee+8B8Pbbbx+3q26oUItDisTHRnNRu0Zc1K4RR/IKmL9uF7Mysvls5TY+WJZJnYRYLmrXiFRPS6RajP7eIS7xDoAHeFaVr86dO5OSksK7777LAw88wMiRI3nyySdJTU095WuTkpIYM2YMPXv2JDEx8bgupueff57f/e53PPPMM0WD46Em5LZVN8a0Be4F6gOzrbUvnuo1wbSteig6ml/A/LVOiPzvx+0cOJpP7fgYLmrnrFhXiEggaFt1dwXtturGmInAEGCHtbaDz/WLgeeAaOBVa22pHZzW2tXAHcaYKOAN4JTBIRUTFxPNgHaNGNCuEUfzC/hm3S7S0rP5YtU2Pvo+syhEUlMac37LBgoRkTBX1V1Vk4EXcL7wATDGRAMTgIuATGCJMWY6TogUP3B3lLV2hzHmUuBO4M2qKFp+FRcTTf+2jejf1idEMrL54kcnRGrFxxR1Z51/Tn3iYko/G1lEQlOVBoe1dp4x5sxil7sB6621GwCMMe8Bl1lrx+K0Tkq6z3RgujEmDXinEkuWkygeIgvW7yItfRtf/LiNqd9nOSHSthGpKQoRkXASDIPjTQHfeXWZQPfSnmyM6QNcAcQBs07yvNuA24Cic3yl8sTFRNOvTSP6tWnEsfxkFqzfxcz0bP734zamLs+iVpzTEhmcnMQFrRQiUjJrLcbowLKq5u9YdzAEh1+stXOBuWV43ivAK+AMjlduVeKrWkwUfds0pG+bhkUhkpbhjIl4Q2SAN0TOqU98bDTTlmfxzOdr2LovlyaJCdw/qDXDOjd1+6NIFYqPj2f37t3Uq1dP4VGFrLXs3r2b+Pj4Mr8mGIIjCzjd53Ezz7UKC6UTAMPVcSFyeTILft7FrPRsvvhxOx8vz6JmXAytG9UkI2s/xwqcdSJZ+3J5aKqz3YTCI3I0a9aMzMxMdu7c6XYpESc+Pp5mzZqV+flVPh3XM8Yx0zuryhgTA6wF+uMExhLgOmvtqkC9p6bjBp9j+YV8+7MzO+vD7zMp6T/DpokJLHiwX9UXJyJA6dNxq3TepDHmXWAh0NoYk2mMudlamw/cDXwOrAamBDI0JDhVi4miT+uGPPPbjlDK312y9uXyxaptHMkrqNriROSkqnpW1fBSrs/iJAPd5aWuqtDQJDGBrH25J1w3Bm57cxk142Lo37Yhg5OTuLBVA+JjNbAu4qaQWzleHuqqCm7Tlmfx0NQMcn1aFgmx0Tw5rD31a8UzKz2bz3/cxr7DedSoFl00sK4QEalcQbFyXKQk3gHw0mZVXdiqAU8WdGDhz7udvbNWOacb1qjmrCMZnJxEn9YKEZGqEtYtDp+uqlvXrVvndjkSIHkFhUUh8vmqbez1tEQUIiKBVVqLI6yDw0tdVeErr6CQ7zbsJi39+BDp19bZ9kQhIlJ+Cg4FR9jzhoh3K/jjQ6QxfVo3VIiI+CEig0NdVZErv6CQ7zbsIS1jK5+v2s6eQ8eoXi2afm0aMiQlSSEiUgYRGRxeanFEtl9DxOnO8g0RpzurIQnVFCIixSk4FByCEyKLftnDzHSFiMipKDgUHFKMN0TSMrL5fOU2dntCpK8nRPoqRCTCRWRwVGiMI31KpZ5nLMElv6CQxb/sYaZPiCTERtOvrUJEIldEBoeX3y2O9CkwYzTk+WyDEZsAQ8crPCKAN0TSPLOzikKkTUNSUxQiEjkUHP4Ex7MdIGfLidfrnA5/XBm4wiTo+YbI56u2sevgryEyODmJvm0aUL2aNmCQ8KQtR/yRk1nK9S1w7DBUq1619YhrYqKj6NWyPr1a1ueJyzqw6Jdf14mkZWSTEBtN3zYNSE1uohCRiKEWR0lKa3EA1GwMve+Dc0dATFxgCpSQU1BojwuRXQePER8bVdQS6demoUJEQl5EdlWVe3C8tDGOHnfDpgWw+Vun2+rCB6DjdRCtL4hIVlBoPd1ZW/ls5XZ2HTyqEJGwEJHB4VWu6bilzaqyFjZ8BXOehKxlUPcs6PMX6HAFRGnANNJ5Q2RWRjafrtxWFCJ9W/8aIjXiFCISGhQcgV7HYS2s+RS+egq2r4QGbaHvX6DtUOcEIol4BYWWJRv3kJauEJHQpOCorAWAhYXw48fw1VjYvQ6SOkK/R6DlAAWIFPGGiLclsvOAEyJ9WjlTfBUiEowUHJW9crwgHzKmwNyxsG8znN4d+v0VWvSu3PeVkFNQaFm60Zni6w2RuBhPSyQlif4KEQkSCo6q2nIk/xgsfxPmPQMHsqHFhU4L5PSuVfP+ElK8IeJtiezwhEif1g1ITWmiEBFXRWRwuLqtel4uLJ0E8/8Jh3fBOYOg38NOV5ZICQoKLcs27SUtfesJITI4OYn+bRtRUyEiVSgig8PL1U0Ojx6ExS/DgufgSA60u8yZhdWwjTv1SEgoLLQs3bSXWRnZzMrIVoiIKxQcbu+Om7sPFk6A7/4Dxw45U3v7POhM5xU5iZJCpFpMFH1aNSA1RSEilUfB4XZweB3aDQv+DYv/CwXHoPMNzkLCOs3crkxCQGGhZdnmvZ4pvtls368Qkcqj4AiW4PA6sM0Z/1g6yZm222UUnP8nqNXI7cokRJQWIhe2asAQhYgEgIIj2ILDa99mZwbW8rchuhp0vw3O+wNUr+t2ZRJCCgst32/eS5qnO8s3RFKTk+jftiG14mPdLlNCjIIjWIPDa/fPMHccZHwA1WpCz7ug5+8hvo7blUmI8Q2RTzO2sW3/EarFRNH7HG9LRCEiZaPgCPbg8NqxGr76f7B6OsQnwnn3QvfboVoNtyuTEFRYaFm+ZS8z008MkdSUxgxo20ghIqVScIRKcHhtXe4EyLovoEYDuODP8JvfQWy825VJiPKGSFr6Nj5dmU12zhGqRUfRu5UTIv3bNqK2QkR8lCs4jDFPlOXm1tpHK1BbpXF1AWCgbF4Ec/4GG+dD7abQ+35nJla0/geX8nNCZF/RwPqvIVKfwclJDGinEJHyB8ckn4fxwJXAEmAT0BzoBnxkrR0e2HIDKyRbHMVt+NoJkMwlcNqZcOGDzloQbeUuFeQNkVkZ2Xyakc1WhYh4VLiryhjzHvCBtfYjn2tXAL9VcFQRa52uqzl/g20ZUL+VZyv3yyAqyu3qJAwUFlpWZHpaIj4hcsE59UlNUYhEmkAERw5Q11pb4HMtBthtrQ3qqT9hExxehYXw0wxnDGTnT9A4Gfr+FVoN0lbuEjDeEJmV7kzx9Q0Rb0ukToJCJJwFIjiWAa9ba8f7XLsH+J219tyAVVoJwi44vAoLIONDZyv3vb9A0y7OVu5n9VGASED5hsinK7eRtS+X2GjDBec460QUIuEpEMHRGfgYiAGygKZAPnCFtfb7ANYacGEbHF4FebDiHfj6adifCWecD/0fgeY93K5MwpC1lhWeMZFZGceHyODkJC5SiISNgEzHNcbEAj2AJkA2sNBamxewKitJ2AeHV/5RWDYZ5v0DDu1wTiHs+zA0DeoGoYQwf0Jk2vIsnvl8DVv35dIkMYH7B7VmWOemLn8CORmt44iE4PA6dhgWv+Jsppi7F9oMcQKkUTu3K5MwZq3lh8wcZmVkk5aeXRQi57esT+M68Xy8PIsjeYVFz0+IjWbsFckKjyBW3um4va218zx/7lfa86y1cwJSZSWJuODwOrIfvnsRFr4ARw9Ahyuhz0NQv6XblUmYKylEStI0MYEFD5b61SIuK29wrLTWdvD8+ZdSnmattUF9qETEBofX4T3w7XhY9LLTndVpOPR+AE47w+3KJAJYaznroVmU9k3zw6MDqVNdYyLBSF1VkRwcXgd3wDfPwpLXwBbCb0bCBfdB7SS3K5Mwd964OaW2OmKjDee1dKb4DmrXWCESRModHMaYxtbabZVWWRVQcBSTk+kMoC9/E6JioOstcP4foUZ9tyuTMDVteRYPTc0gN69oGRgJsVHc2edsDh0tIC0jm8y9ucREOSGSmpLEwHaNSKxezcWqpSLBsd9aW9vn8VRr7RWVUGOZGWNqAF8DY6y1M0/1fAVHKfb8Al//HdLfh5gE6HEn9LoHEhLdrkzC0MlmVVlrycjKIc0zJnJciCQnMbC9QsQNFQmOA9baWj6P91hry3XKkDFmIjAE2OEdO/Fcvxh4DogGXrXWjjvFfZ4ADgI/KjgCYOcaZxHhqo+d8z963QPd74S4mm5XJhHIN0RmZWSzZY9CxC2BbHFUJDh643zhv+Ez6B4NrAUuAjJxNlEcjhMiY4vdYhTQEaiHs+niLgVHAG3LgDlPwdpPoXo95yjbrjdDbILblUmEstayMms/MzO2HhcivVrWZ4hCpNJVJDgOA6mAdw+LacBlPo/9mo5rjDkTmOkTHD1xupwGeR4/5Lln8dDwvv4poAbQDsgFLrfWFpbwvNuA2wCaN2/+m02bNpW1RMlcCnOehA1fQc3G0Ps+OHckxOh/UHGPN0S8LZHNew4XhUhqcmMGtmvMaTX032ggVSQ4NkKpM+nAz+m4JQTHVcDF1tpbPI9vBLpba+8+xX1uQi2OyrXxGydANi+EOs2hz/9ByrUQHeN2ZRLhrLWs2rqfmenHh0jPs+sxJCVJIRIgQTMdN1DB4Q8FRwVYCz/PdgJk63Koe7azlXv7K7SVuwQFb4h4B9Y37zlMdJSh19n1SE1OYlB7hUh5BXNw+NVV5ed7hf4JgMHCWvgpDb56Cnb8CA3bOduYtEnVTrwSNHxDZFZGNpt2K0QqIpiDIwZncLw/zq67S4DrrLWrAvWeanEEUGEhrJrqzMLavR6adHa2cj+7vwJEgsqpQmRg+8bUVYicVFAEhzHmXaAPUB/YDjxmrX3NGDMY+DfOTKqJ1tqnAvR+anFUloJ8SH8P5v4dcjZD855OgJx5vtuViZzAGyKzPCGy0SdEBntaIgqREwU0OIwx86y1vQNSWRVQi6MS5R+D5W84K9EPZMNZfZ0AaXbCf2siQcFay4/Z+0lLPz5Eep5Vj9QUhYivQAdHgbU2OiCVVQEFRxXIy3X2wPrmX3B4N7S6BPo97BxrKxKkvCHi3cXXN0Sclkgj6tWMc7tM10RkcKirygVHD8Cil2DB83A0B9pfDn3+Ag1auV2ZyEn5hsisjG38sutQxIdIRAaHl1ocLsjdCwsnOOeB5B2GlGvgwv+Dui3crkzklKy1rM4+QFrG1uNCpMdZdUlNbhIxIaLgUHC449Auz1bur0JhPnS+EXrfD3V06puEBm+IeAfWN/iEyODkJC5u3zhsQ0TBoeBw1/5smP8PWPY6mChnD6zz/wg1G7pdmUiZWWv5aduBooH1DbsOEWWgh8/Aev0wCpFAB0ehtTbolw1rjCMI7d0E856GFe9CTBx0vx16jYb1X8LsJ5yzQuo0g/6PQsrVblcrUipviHgH1n1DZHByEhd3CP0QCXRwfGWt7RuQyqqAWhxBaNd6ZxHhyo8gOg5svtOV5RWbAEPHKzwkJBwXIhnZbNgZHiESFAsA3aLgCGLbV8F/+0N+CceK1jkd/riy6msSqQBrLWu2O91ZviHSvYXTnRVKIaLgUHAErzGJlLwBs4Ex+6q2FpEA8obILE+I/OwTIoNTnIH1BrWCN0QiMjg0xhEinu0AOVtOvB4VA1e/Aa0Hax8sCXnWWtZuP0ha+tbjQqRbi7qkpjQJyhCJyODwUosjyKVPgRmjndXnXtHVID4RDu2A07vDgMfhjJ6ulSgSSEUhkpFNWvrW40MkOYlBHRrTsFa822VWTnB4jn192Fr7REWKq2wKjhCQPuXEWVXtL4flb8HccXBwG7S6GPo/Bo3auV2tSMD4hsisjGzW7ziIMdA9CEKksoIjDjgc7Gs6FBwh7thhWPQifPMcHN0PHYdD34cgsbnblYkE3NrtB4pONvSGSLcz6zIkpepDpCJHx048yY9jgOsVHFIlDu9xNlFc9ApgodttcMGfoXpdtysTqRRrt/+62HCdT4h4Z2dVdohUJDiOAK8Be0r4cTTwf8EaHBocD1P7tjjdVz+8A9VqwnmjocfvoVoNtysTqTQlhUhXT0ukskKkIsGxBPibtXZ6CT+Lx+mqCupV5GpxhKkdq2H232BNGtRs5GyieO4IiI51uzKRSrVu+4GiM9Z9QyQ1OYlLOjTm259388zna9i6L5cmiQncP6g1wzr7vz9cRYLjLiDLWjuthJ9FA3+11j7ud0VVSMER5jZ/B1+Ogc0Loe7ZzkFS7S/XFF6JCN4QmZWRzdrtBwGIMlDo89WeEBvN2CuS/Q4PTcdVcIQ3a2HtZ/Dl47BztXMW+oAxcFYftysTqTLrdxzg8v98y4Ej+Sf8rGliAgse7OfX/UoLjqDuYhIpM2Og9SVw5wIY9qKznfsbl8Ebw2DrCrerE6kSLRvW4mAJoQGwdV8J2/qUk4JDwktUNHS6Du5eCgOfguwV8MqF8OEo2LPB7epEKl2TxAS/rpdHWAeHMWaoMeaVnJwct0uRqhYbD73uhnt/gAvug59mwQtdIe0+OLjD7epEKs39g1qTEHv8RNeE2GjuH9Q6YO+hMQ6JDAe2wdd/dw6SiomHnndBr3sgvrbblYkE3LTlWe7OqgoHCg4psvtnmPM3WPUxVK/nHGPbZZRzqJSIHCcgg+PGmBdKuPafihQmUqXqnQ2/nQy3fgWN2sNnD8ILXeCH96CwwO3qREKCv2McJU2M12R5CT1Nz4UR0+GGqc4uvB/fDi/3hrVfOFN7RaRUZQ4OY0yUtfau4tettXcGtiSRKmIMtOwPt30NV74Gxw7BO7+FyamwZYnb1YkErTIFh2eF+CHPbrgi4SUqCpKvgrsWw+B/wK618NoAeO962LnW7epEgk6ZgsNaWwCsBepVbjkiLoqpBt1uhdEroO/DsGEu/Kc7TL8H9m91uzqRoBHjx3PfBmYaY54DMvE5JNpaOyfQhYm4Jq4mXPiAM9tq3j9gyavOQVPdb4fz/wgJp7ldoYiryjwd1xjzSyk/stbaswJXUuBoW3UJiL2b4Kv/B+nvO+s+zv+TEyKxgVuJKxKMtI5D6zikorathNmPw7ovoFYT6PMgdLoeov1puIuEjkCt4zjHGPOoMeZlz+/nBK5EkSDXuANc/wHclAa1m8CM0fBiT1g9Q1N4JaL4Mx13KLAMaINzGmBrYKkx5tJKqk0kOJ15PtzyJVzzlvP4/RvgtYtg4zfu1iVSRfwZ48gARltrv/K51gd4wVrboVKqCxB1VUmlKciHFW87R9ke2ArnDIT+jzmtE5EQF4iuqmbA/GLXvvFcF4lM0THwm5Ew+nsY8DhsWQQvnQ9Tb3cG1UXCkD/BsQL4c7Frf/JcF4lssQlw/h+cbdzPGw0/TnP2wPr0QedQKZEw4k9XVVtgOlAD2AKcDhwGhlprV1dahQGgriqpcjlZ8PU4WP4WxNZwwqTH7501IiIhosLTcY0xUTgtlB5AE2ArsMhamxfIQiuDgkNcs3MNzH4CfpoJNRo6CwvPHemsUhcJchUa4/DuVQVEW2u/sdZO8fwe9KEh4qoGreHat+Hm/0H9c2DWfTChG2R8CIWFblcnUi7aq0qkKpzezVn/cd0HEFsdProZ/tsHftZuPRJ6/Bkc9+5VNdIY098Y08/7q7KKEwkrxkCrgXDHfLj8ZTi8F968HF6/FLK+d7s6kTILub2qPGtH/gasAt6z1s491Ws0xiFBKf8oLJ0I856Bw7uh/eXQ7xHnlEKRIBCIdRwtrbUtSvhV5tAwxkw0xuwwxqwsdv1iY8waY8x6Y8yDp7iNBQ4C8Ti79IqEppg46HGns4177wec0wcndIOZf4QD29yuTqRUZWpxeAbHDwKJ1tqj5X4zY3p77vOGd7W5595rgYtwgmAJMByIBsYWu8UoYJe1ttAY0wj4l7X2+lO9r1ocEhIO7oCvn4ZlkyC6mjN997zREF/H7cokQlWoxRGowXFr7Tycfa58dQPWW2s3WGuPAe8Bl1lrM6y1Q4r92mGt9U5F2QuUeiKhMeY2Y8xSY8zSnTt3VqRskapRsyGk/sM5ibD1JTD/H/BcJ/j2Bcg74nZ1IkWCYXC8Kc6CQq9Mz7USGWOuMMa8DLwJvFDa86y1r1hru1hruzRo0KCCJYpUoXpnw1UT4ba5kNQRvnjYWYW+4h0oLHC7OhG/TgC80/P7mGLXLVBlg+PW2qnA1LI81+cgp8otSqQyNOkMI6Y5R9j+7zGYdid8+7yziWKrQc4sLREXlLnFUcrAuF+D46XIwtm+xKuZ51qFWWtnWGtvq1NHfcQSws7qA7d+BVdNgvwj8O41MOkS2LzI7cokQp0yOIwx44s9vrnY448qWMMS4BxjTAtjTDXgWpw9sUTEKyoKOlzhjH+k/gv2bICJA+Hd62DHT25XJxGmLC2Om4o9fqbY44vK+mbGmHeBhUBrY0ymMeZma20+cDfwObAamGKtXVXWe57i/YYaY17JyckJxO1E3BcdC11vhtHLod9fYeN85xTCaXdBjmanS9U45XRcY8wBa20tn8d7rbWn+Tzeb62tXYk1Vpim40rYOrQbvvkXLH4FMND9Njj/T1C9rtuVSRioyHTc4smiw5VFgkWNejDoKbhnGXS40pm6+1wnmP8vOHbY7eokTJUlOGKMMX19pt4WfxxdyTWWm7qqJGIkNofLX4Q7F8AZPWH24/D8ubB0knO8rUgAlaWraiOnaGVYa1sEsKaAU1eVRJxNC+HLx5yjbOudA/0fgbaXagqv+KW0rqpTruOw1p5ZKRWJSOU5oyeM+hzWzHIOkpoyApr+BgaMgRa93a5OQpw/K8dDjrqqJKIZA21S4c5v4bIJzsaJrw+Ft66E7HS3q5MQVuZt1UOZuqpEgLxcWPxfmP9POLIPkq+Gfg/DaWe6XZkEqUBsqy4ioSw2wdlt994f4Pw/wuoZ8HwXmPUAHNRGoFJ2Cg6RSJOQ6Ix1jF4Ona+HJa/C+E4wdxwcPeBycRIKwjo4NMYhchK1k2Doc3DXIji7H8wd66wBWfQy5B9zuzoJYhrjEBFH5jJnCu/G+c64R9+/OosKo8L675dyEhrjEJGTa/YbGDkDrv8IqtWCqbfAK71h3ZcQAX/BlLJTcIjIr4yBcwbA7fPgilfhyH54+0pnGm/mMrerkyCh4BCRE0VFQcpv4e6lcMnTsGM1vNrPWUi4a73b1YnLwjo4NDguUkEx1aD77XDvCrjwQVg/GyZ0gxn3wv5st6sTl2hwXETK7uBOmPcMLJ0IUTHQ4044715niq+EHQ2Oi0jF1WwAg5+Gu5dA2yHOWSDPdYQF4yHviNvVSRVRcIiI/+q2gCtfhdvnQ7Mu8L9H4PnfwPK3oLDA7eqkkik4RKT8klLgho+cabw1G8Ind8GLveCnNE3hDWMKDhGpuBa94dY5cPUbUJgP710HEwc554JI2Anr4NCsKpEqZAy0uwx+vwiG/Bv2boJJF8M718L2HyF9CjzbAcYkOr+nT3G7YiknzaoSkcpx7DAsehG+eQ6O5oCJBusz/hGbAEPHQ8rV7tUoJ6VZVSJStapVhwv+7KwBiat1fGiAcz7I7CdcKU0qRsEhIpWrel04erDkn+VkahA9BCk4RKTy1WlWyg8svDoAVn0MBflVWpKUn4JDRCpf/0edMQ1fMQnQcTgc3g0f3ATPd4bvXtRhUiFAwSEilS/lamcgvM7pgHF+v3Q8XP4S3LMMrnkLajWBzx6Ef7WHLx6BnCy3q5ZSaFaViASPzKXw7fOwejqYKGh/BfS6G5I6ul1ZRCptVlWMG8WIiJSoWRe4+nXYuxG+ewmWvwkZU+DMC6DXPdDyIp1IGATCusVhjBkKDG3ZsuWt69atc7scEfFX7j5YNtk5B/3AVqjfGnr+HlKuhdh4t6sLe6W1OMI6OLzUVSUS4vKPOTOvFj4P2zKgen3odit0vQVq1He7urClBYAiErpiqkHHa5zdeEdMh6bnwtyx8Gx751CpXepRqEoa4xCR0GEMnHWh82vnGlg4AVa863RntbrEGUg/4zzneVJp1OIQkdDUoLUzpfePq5xjbTMXw+RUeKUPZHwIBXluVxi2FBwiEtpqNoC+DzkBMuTfcOwQfHQzPNfJOZnwiHbHDjQFh4iEh9gE6PI7uGsxDH/fOaXwf484Cwo/+wvs2+x2hWFDwSEi4SUqClpfDDfNhNvmOn9e9JLTAvngd5C1zO0KQ56CQ0TCV5POztnof0h31n+s/xL+2w8mXuIcb1tY6HaFIUnBISLhr04zGPikMw4y6P9BzhbneNsXusCSV51Dp6TMFBwiEjnia0PPu2D0CrhqIsTXgbQ/O+tB5jwJB3e4XWFIUHCISOSJjoEOV8Ktc+B3n0LznjDvH06AfHIX7FjtdoVBLeQWABpjooC/AbWBpdba110uSURClTFwRi/n16718N1/YMU7sPwtaDkAet4NZ/XRgsJiqrTFYYyZaIzZYYxZWez6xcaYNcaY9caYB09xm8uAZkAekFlZtYpIhKnfEob8yxkH6ftXyE6HN4fBSxc4q9Pzj7ldYdCo0k0OjTG9gYPAG9baDp5r0cBa4CKcIFgCDAeigbHFbjHK82uvtfZlY8yH1tqrTvW+2uRQRPyWdwQyPoCFL8DOn6BWEnS7zVkrknCa29VViaA4j8NaO88Yc2axy92A9dbaDQDGmPeAy6y1Y4Ehxe9hjMkEvNFfUInlikgki42Hc2+EzjfA+tnOzryzH3fGQjrfAD3udBYZRqBgGBxvCmzxeZzpuVaaqcAgY8zzwLzSnmSMuc0Ys9QYs3Tnzp2BqVREIo8xcM4AGPEJ3PENtLsUlk6E58+F92+ELYvdrrDKhdzguLX2MHBzGZ73CvAKOF1VlV2XiESAxsnOOen9H4XFrzgBsno6NOvm7MzbZghERbtdZaULhhZHFnC6z+NmnmsVZowZaox5JSdHm5yJSADVbgIDxsAff4RLnoaD22HKCKcVsuhlOHrQ7QorVZWfAOgZ45jpMzgegzM43h8nMJYA11lrVwXqPTU4LiKVqrAAfpoJ377gbO8eXwe6jIJut0PtJLerK7egOAHQGPMusBBobYzJNMbcbK3NB+4GPgdWA1MCGRoiIpUuKhraXQa3/A9u/h+0uBAWPAf/ToaP73COuw0jYX3muDFmKDC0ZcuWt65bp6MlRaQK7fkFvnvRWUyYd8hZSNjzHmjZP2QWFJbW4gjr4PBSV5WIuCZ3Lyyd5AymH8iGBm2d/bJSroaYOLerO6mg6KoSEYk4CafBBX+Ce9Nh2EtOt9b0u+HZDvD1M3Bot9sV+i2sWxzqqhKRoGMtbJjrrEhf/yXEJECn4dDjLmfbkyCirip1VYlIsNmx2gmQ9ClQkAetL3E2VjyjV1CMg6irSkQk2DRsC5dNgD+shN73webvYPJg+G9fyPgQCvLdrrBEYd3iUFeViISUY4fhh3dg4X9gz89Q53TofgecO8I5hKqKqatKXVUiEioKC2Htp86Cws3fQlxtJzx63Okcg1tF1FUlIhIqoqKgTSqM+tQ5pbDlAGdNyL9T4MObYetyV8sLuU0ORUQiStPfwG8nwb7N8N1L8P0bsPJDOON8Z2PFcwY5QVOFwrqrSmMcIhJ2juTAstdh0UuwPwvqnQM9fw8dh0NsQkDfSmMcGuMQkXBSkAerpjkHTGX/ANXrQddboOutsOErmP0E5GQ6YyL9H3VWqvtJwaHgEJFwZC1s/MZZD7L2MzCe80CszwGpsQkwdLzf4aHBcRGRcGQMtLgArnsf7lriHHlri52qnZfrtEACRMEhIhIuGrRy1oKUJCczYG8T1sGhEwBFJOKUts4jgOs/wjo4rLUzrLW31alTx+1SRESqRv9HT5xdFZvgXA+QsA4OEZGIk3K1MxBe53TAOL+XY2D8ZLQAUEQk3KRcHdCgKE4tDhER8YuCQ0RE/BLWwaFZVSIigRfWwaFZVSIigRfWwSEiIoEXEXtVGWN2AvuA8vRZ1Qd2BbQgOZk6lO/fUzAL1s/kVl2V/b6Bvn+g7lfR+5Tn9RX9/jrDWtug+MWICA4AY8wr1trbyvG6pSVt8iWVo7z/noJZsH4mt+qq7PcN9P0Ddb+K3qc8r6+s769I6qqa4XYBUibh+O8pWD+TW3VV9vsG+v6Bul9F7xM0/x1FTIujvNTiEJFQpRaHe15xuwARkXKqlO8vtThERMQvanGIiIhfFBwiIuIXBYeIiPhF26r7yRgzDEgFagOvWWu/cLciEZGyMca0Be7FWRg421r7YnnuoxYHYIyZaIzZYYxZWez6xcaYNcaY9caYBwGstdOstbcCdwDXuFGviIiXn99fq621dwBXA+eV9z0VHI7JwMW+F4wx0cAE4BKgHTDcGNPO5yl/9fxcRMRNk/Hj+8sYcymQBswq7xsqOABr7TxgT7HL3YD11toN1tpjwHvAZcbxd+BTa+33VV2riIgvf76/PM+fbq29BLi+vO+pMY7SNQW2+DzOBLoD9wADgDrGmJbW2pfcKE5E5CRK/P4yxvQBrgDiqECLQ8HhJ2vteGC823WIiPjLWjsXmFvR+6irqnRZwOk+j5t5romIBLtK/f5ScJRuCXCOMaaFMaYacC0w3eWaRETKolK/vxQcgDHmXWAh0NoYk2mMudlamw/cDXwOrAamWGtXuVmniEhxbnx/aZNDERHxi1ocIiLiFwWHiIj4RcEhIiJ+UXCIiIhfFBwiIuIXBYeIiPhFwSEiIn5RcIiEKWPMRmPMALfrkPCj4JCQZIw56POr0BiT6/PY7+2iS/qS1RevSMm0O66EJGttTe+fjTEbgVustV+W9FxjTIxnC4Yq48Z7VpayfBZjTLS1tqCqahJ3qcUhYcnTWvg/Y0w6cMgYE2OMaWuMmWuM2WeMWeU5CQ1jzJtAc2CGp8XyQEnXPM9tYoz5yBiz0xjzizFm9Mnes5S67jPGpBtjcowx7xtj4j0/s8aYlj7PnWyMebLYa+/3vPaQMeY1Y0wjY8ynxpgDxpgvjTGnFXvLrsaYH40xe40xk3zeq9TPUZbPYoy52RjzP08Ne4E/+f0vSUKWgkPC2XAgFUgEDDAD+AJoiHMg19vGmNbW2huBzcBQa21Na+3TJV0zxkR57vEDzkE5/YE/GGMGlfSeJ/lb+tU4R322AFKAm/z4TFcCFwGtgKHAp8BfgAY4/z+PLvb864FBwNme1/y1jJ/jVJ+lI9AD+ASoh86oiSgKDgln4621W6y1uThfcjWBcdbaY9baOcBMnC/HsuoKNLDWPuG5xwbgvzhbVpf0niera6u1dg/OF3gnP2p43lq73VqbBcwHFllrl1trjwAfA52LPf8FTz17gKdwPm9ZPsepPktH4B+eY0gLrbVH/fgMEuI0xiHhzPfozCbAFmttoc+1TTh/4y6rM4Amxph9Pteicb7AS3rP0mzz+fNhT21ltd3nz7klPK55/NOPq2eT573K8jmKv7a4FODOMtQrYUjBIeHM98yArcDpxpgon/BoDqwt4bklvR6cL9JfrLXnlPE9/XUYqO7zuDHOWdEV4XsKXHOcfw5l+RxQymcxxpwBxAI/VbA2CVHqqpJIsQjni/kBY0ysMaYPzhjBe56fbwfOKvaa4tcWAwc8g8YJxphoY0wHY0zXANW4ArjOc9+LgQsDcM+7jDHNjDF1gYeB96n45+gIZBRrvUkEUXBIRLDWHsMJikuAXcB/gBHWWu/fmsfiDBzvM8bcV9I1z3TTIThjEr947vMqUCdAZd7rqXEfzqD2tADc8x2cCQEbgJ+BJwPwOTrihJxEKJ0AKCIiflGLQ0RE/KLgEBERvyg4RETELwoOERHxi4JDRET8ouAQERG/KDhERMQvCg4REfGLgkNERPzy/wF14Zo9Nd7sTQAAAABJRU5ErkJggg==\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"def compare(n_steps):\n",
" \"\"\"\n",
" compare the first order product formula and random permutation's fidelity for a fixed evolution time t=2\n",
" input n_steps is the number of trotter steps\n",
" output is respectively the first order PF and random permutations' fidelity \n",
" \"\"\"\n",
" t = 2\n",
" cir_evolve = UAnsatz(5)\n",
" construct_trotter_circuit(cir_evolve, h, tau=t/n_steps, steps=n_steps, order=1)\n",
" U_cir = cir_evolve.U.numpy()\n",
" fid_suzuki = gate_fidelity(get_evolve_op(t), U_cir)\n",
" cir_permute = UAnsatz(5)\n",
" permutation = np.vstack([np.random.permutation(h.n_terms) for i in range(n_steps)])\n",
" # when coefficient is not specified, a normalized uniform coefficient will be automatically set\n",
" construct_trotter_circuit(cir_permute, h, tau=t, steps=1, method='custom', permutation=permutation)\n",
" U_cir = cir_permute.U.numpy()\n",
" fid_random = gate_fidelity(get_evolve_op(t), U_cir)\n",
" return fid_suzuki, fid_random\n",
"\n",
"# compare the two fidelity for different trotter steps\n",
"# as a demo, we only run the experiment once. Interested readers could run multiple times to calculate the error bar\n",
"n_range = [100, 200, 500, 1000]\n",
"result = [compare(n) for n in n_range]\n",
"\n",
"result = 1 - np.array(result)\n",
"plt.loglog(n_range, result[:, 0], 'o-', label='1st order PF')\n",
"plt.loglog(n_range, result[:, 1], 'o-', label='Random')\n",
"plt.xlabel(r'Trotter number $r$', fontsize=12)\n",
"plt.ylabel(r'Error: $1 - {\\rm Fid}$', fontsize=12)\n",
"plt.legend()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "48fc79ff",
"metadata": {},
"source": [
"The 1st order PF refers to the first order product formula circuit with a fixed order. As expected, there is a good improvement in the fidelity for randomized trotter circuit over the first order product formula. \n",
"\n",
"**Note:** In [9], the authors noted that the randomization achieved better performance without even utilizing any specific information about the Hamiltonian, and there should be a even more efficient algorithm compared to the simple randomization."
]
},
{
"cell_type": "markdown",
"id": "bfe2c24e",
"metadata": {},
"source": [
"## Conclusion"
]
},
{
"cell_type": "markdown",
"id": "92832953",
"metadata": {},
"source": [
"Dynamical simulation plays a central role in the research of exotic quantum states. Due to its highly entangled nature, both experimental and theoretical research constitute highly challenging topics. Up until now, people haven't been able to fully understand the physics on some of the two-dimensional or even one-dimensional spin systems. On the other hand, the rapid development of general quantum computers and a series of quantum simulators give researchers new tools to deal with these challenging problems. Take the general quantum computer as an example, it could use digital simulation to simulate almost any quantum system's evolution process under complex conditions (for example a time-dependent Hamiltonian), which is beyond the reach of any classical computer. As the number of qubits and their precisions grow, it seems more like a question of when will the quantum computer surpass its classical counterpart on the tasks of quantum simulation. And among those tasks, it is commonly believed that the simulation of quantum spin systems will be one of the few cases where this breakthrough will first happen. \n",
"\n",
"We have presented in this tutorial a hands-on case of simulating dynamical process on a quantum spin model with Paddle Quantum, and further discussed the possibility of designing new time-evolving strategies. Users can now easily design and benchmark their time evolution circuits with the `construct_trotter_circuit()` function and methods provided in the `Hamiltonian` and `SpinOps` class. We encourage our users to experiment and explore various time evolution strategies on different quantum systems. "
]
},
{
"cell_type": "markdown",
"id": "ff5b39fa",
"metadata": {},
"source": [
"---\n",
"\n",
"## References\n",
"\n",
"[1] Childs, Andrew M., et al. \"Toward the first quantum simulation with quantum speedup.\" [Proceedings of the National Academy of Sciences 115.38 (2018): 9456-9461](https://www.pnas.org/content/115/38/9456.short).\n",
"\n",
"[2] Eckle, Hans-Peter. Models of Quantum Matter: A First Course on Integrability and the Bethe Ansatz. [Oxford University Press, 2019](https://oxford.universitypressscholarship.com/view/10.1093/oso/9780199678839.001.0001/oso-9780199678839).\n",
"\n",
"[3] Mikeska, Hans-Jürgen, and Alexei K. Kolezhuk. \"One-dimensional magnetism.\" Quantum magnetism. Springer, Berlin, Heidelberg, 2004. 1-83.\n",
"\n",
"[4] Berger, L., S. A. Friedberg, and J. T. Schriempf. \"Magnetic Susceptibility of $\\rm Cu(NO_3)_2·2.5 H_2O$ at Low Temperature.\" [Physical Review 132.3 (1963): 1057](https://journals.aps.org/pr/abstract/10.1103/PhysRev.132.1057).\n",
"\n",
"[5] Broholm, C., et al. \"Quantum spin liquids.\" [Science 367.6475 (2020)](https://science.sciencemag.org/content/367/6475/eaay0668).\n",
"\n",
"[6] Abanin, Dmitry A., et al. \"Colloquium: Many-body localization, thermalization, and entanglement.\" [Reviews of Modern Physics 91.2 (2019): 021001](https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.91.021001).\n",
"\n",
"[7] Medenjak, Marko, Berislav Buča, and Dieter Jaksch. \"Isolated Heisenberg magnet as a quantum time crystal.\" [Physical Review B 102.4 (2020): 041117](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.102.041117).\n",
"\n",
"[8] Wallman, Joel J., and Joseph Emerson. \"Noise tailoring for scalable quantum computation via randomized compiling.\" [Physical Review A 94.5 (2016): 052325](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.94.052325).\n",
"\n",
"[9] Childs, Andrew M., Aaron Ostrander, and Yuan Su. \"Faster quantum simulation by randomization.\" [Quantum 3 (2019): 182](https://quantum-journal.org/papers/q-2019-09-02-182/)."
]
}
],
"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.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
...@@ -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",
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
"\\tag{3}\n", "\\tag{3}\n",
"$$\n", "$$\n",
"\n", "\n",
"在下一节,我们会补充一些关于电子结构问题的背景知识。本质上讨论的就是上述哈密顿量 $H$ 究竟是从哪里来的。对于熟悉相关背景的读者,或者主要关心如何在量桨上实现 VQE 的读者,请直接跳转至第三节分析氢分子($H_2$)基态的具体例子。 " "在下一节,我们会补充一些关于电子结构问题的背景知识。本质上讨论的就是上述哈密顿量 $H$ 是如何计算的。对于熟悉相关背景的读者,或者主要关心如何在量桨上实现 VQE 的读者,请直接跳转至第三节分析氢分子($H_2$)基态的具体例子。 "
] ]
}, },
{ {
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
"source": [ "source": [
"## 背景: 电子结构问题\n", "## 背景: 电子结构问题\n",
"\n", "\n",
"这本小节,我们集中讨论下量子化学中的一个基本问题 -- **电子结构问题**。更准确的说,我们关心的是给定分子(molecule)的低位能量本征态。这些信息可以帮助我们预测化学反应的速率和分子的稳定结构等等 [6]。假设一个分子由 $N_n$ 个原子核和 $N_e$ 个电子组成,描述该分子系统总能量的哈密顿量算符 $\\hat{H}_{mol}$ 在一次量子化表示下可以写为,\n", "这,我们集中讨论下量子化学中的一个基本问题 -- **电子结构问题**。更准确的说,我们关心的是给定分子(molecule)的低位能量本征态。这些信息可以帮助我们预测化学反应的速率和分子的稳定结构等等 [6]。假设一个分子由 $N_n$ 个原子核和 $N_e$ 个电子组成,描述该分子系统总能量的哈密顿量算符 $\\hat{H}_{mol}$ 在一次量子化表示下可以写为,\n",
"\n", "\n",
"$$\n", "$$\n",
"\\begin{align}\n", "\\begin{align}\n",
...@@ -54,9 +54,9 @@ ...@@ -54,9 +54,9 @@
"\\end{align}\n", "\\end{align}\n",
"$$\n", "$$\n",
"\n", "\n",
"其中 $R_i、M_i$ 和 $Z_i$ 分别表示第 $i$ 个原子核的位置、质量和原子序数(原子核内质子数),第 $i$ 个电子的位置则表示为 $r_i$。以上公式右边前两项分别代表原子核和电子的总动能。第三项表示带正电的质子和带负电的电子之间的库伦相互吸引作用。最后两项则表示原子核-原子核之间,电子-电子之间的相互排斥作用。这里,分子哈密顿量 $\\hat{H}_\\text{mol}$ 使用的是原子单位制能量 **哈特里能量**(Hartree),记为 Ha。1哈特里能量的大小为 $[\\hbar^2/(m_ee^2a_0^2)] = 27.2$ 电子伏或 630 千卡/摩尔,其中 $m_e、e$ 和 $a_0$ 分别表示电子质量、基本电荷和玻尔半径。\n", "其中 $R_i、M_i$ 和 $Z_i$ 分别表示第 $i$ 个原子核的位置、质量和原子序数(原子核内质子数),第 $i$ 个电子的位置则表示为 $r_i$。以上公式右边前两项分别代表原子核和电子的总动能。第三项表示带正电的质子和带负电的电子之间的库伦相互吸引作用。最后两项则表示原子核-原子核之间,电子-电子之间的相互排斥作用。这里,分子哈密顿量 $\\hat{H}_\\text{mol}$ 使用的是原子单位制能量 **哈特里能量**(Hartree),记为 Ha。$1$ 哈特里能量的大小为 $[\\hbar^2/(m_ee^2a_0^2)] = 27.2$ 电子伏或 $630$ 千卡/摩尔,其中 $m_e、e$ 和 $a_0$ 分别表示电子质量、基本电荷和玻尔半径。\n",
"\n", "\n",
"**注释1:** 在这个图景下,我们不考虑自旋-轨道耦合以及超精细结构。如果出于计算需要,可以作为微扰加入。" "**注释1:** 在处理电子结构问题时,我们不考虑自旋-轨道耦合以及超精细结构。如果出于计算需要,可以作为微扰加入。"
] ]
}, },
{ {
...@@ -65,7 +65,7 @@ ...@@ -65,7 +65,7 @@
"source": [ "source": [
"### 玻恩-奥本海默近似\n", "### 玻恩-奥本海默近似\n",
"\n", "\n",
"由于一般原子核的质量要远大于电子,因而在同样的相互作用下电子的运动速度会比原子核快很多。所以,将原子核所处的位置看成固定 $R_i =$常数 是一种合理的近似。这种通过在时间尺度上将电子行为和原子核行为去耦合的近似处理思想被称为玻恩-奥本海默近似。作为近似的直接结果,公式(4)中原子核的动能项会被消去并且表示原子核-原子核相互排斥作用的项可以被认为是一个能量移位(这个项是与电子位置 $r_i$ 无关的)从而也可以作为常数项被忽略。经过这些步骤后,我们可以把哈密顿量近似为:\n", "由于原子核的质量要远大于电子,因而在同样的相互作用下电子的运动速度会比原子核快很多。所以,将原子核所处的位置看成固定 $R_i =$常数 是一种合理的近似。这种通过在时间尺度上将电子行为和原子核行为去耦合的近似处理思想被称为玻恩-奥本海默近似。作为近似的直接结果,公式(4)中原子核的动能项会被消去并且表示原子核-原子核相互排斥作用的项可以被认为是一个能量移位(这个项是与电子位置 $r_i$ 无关的)从而也可以作为常数项被忽略。经过这些步骤后,我们可以把哈密顿量近似为:\n",
"\n", "\n",
"$$\n", "$$\n",
"\\begin{align}\n", "\\begin{align}\n",
...@@ -87,14 +87,14 @@ ...@@ -87,14 +87,14 @@
"> \n", "> \n",
"> -- Paul Dirac (1929)\n", "> -- Paul Dirac (1929)\n",
"\n", "\n",
"既然解析的做法因为太复杂了不太可行,那么我们可以采用数值方法来处理。一个最简单的数值方法(离散化方法)就是把上述作用中无限维度希尔伯特空间离散化为等间距排开的立方体晶格点。在这样一个离散化的空间里,主要运算规则为复数域的线性代数。假设空间的每个轴都离散为等间距排开的 $k$ 个点,则 $N$-电子(为了方便去掉下标 $e$)的多体波函数可以写为 [2]:\n", "由于解析的方法太复杂,那么我们可以采用数值方法来处理。一个最简单的数值方法(离散化方法)就是把上述作用中无限维度希尔伯特空间离散化为等间距排开的立方体晶格点。在这样一个离散化的空间里,主要运算规则为复数域的线性代数。假设空间的每个轴都离散为等间距排开的 $k$ 个点,则 $N$-电子(为了方便去掉下标 $e$)的多体波函数可以写为 [2]:\n",
"\n", "\n",
"$$\n", "$$\n",
"|\\Psi \\rangle = \\sum_{\\mathbf{x_1}, \\ldots, \\mathbf{x_N}} \\psi(\\mathbf{x_1}, \\ldots, \\mathbf{x_N}) \\mathcal{A}(|\\mathbf{x_1}, \\ldots, \\mathbf{x_N}\\rangle).\n", "|\\Psi \\rangle = \\sum_{\\mathbf{x_1}, \\ldots, \\mathbf{x_N}} \\psi(\\mathbf{x_1}, \\ldots, \\mathbf{x_N}) \\mathcal{A}(|\\mathbf{x_1}, \\ldots, \\mathbf{x_N}\\rangle).\n",
"\\tag{7}\n", "\\tag{7}\n",
"$$\n", "$$\n",
"\n", "\n",
"其中坐标 $|\\mathbf{x_j}\\rangle = |r_j\\rangle |\\sigma_j\\rangle$ 记录第 $j$ 个电子的空间位置信息和自旋,$|r_j\\rangle = |x_j,y_j,z_j\\rangle$ 且 $j\\in \\{1,2,\\cdots,N\\}$, $x_j,y_j,z_j \\in \\{0,1,\\cdots,k-1\\}$ 同时 $\\sigma_j \\in \\{\\downarrow,\\uparrow\\}$ 表示自旋向下和向上。这样一种离散化方式共计需要 $k^{3N}\\times 2^{N}$ 个数据来表示波函数。在这里,$\\mathcal{A}$ 表示反对称化操作(出于泡利不相容原理)并且 $\\psi(\\mathbf{x_1}, \\mathbf{x_2}, \\ldots, \\mathbf{x_N})=\\langle\\mathbf{x_1}, \\mathbf{x_2}, \\ldots, \\mathbf{x_N}|\\Psi\\rangle$。 可以看出,经典计算机存储这样一个波函数需要的内存是随着电子个数呈指数增长的。这使得基于这种离散化的经典数值方法,无法模拟超过几十个电子的系统。那么,我们是不是能够通过量子设备来存储和准备这样一个波函数然后求解基态能量 $E_0$ 呢?在下一节中,我们将以最简单的分子系统 -- 氢分子($H_2$)为例,讲解 VQE 算法。\n", "其中坐标 $|\\mathbf{x_j}\\rangle = |r_j\\rangle |\\sigma_j\\rangle$ 记录第 $j$ 个电子的空间位置信息和自旋,$|r_j\\rangle = |x_j,y_j,z_j\\rangle$ 且 $j\\in \\{1,2,\\cdots,N\\}$, $x_j,y_j,z_j \\in \\{0,1,\\cdots,k-1\\}$ 同时 $\\sigma_j \\in \\{\\downarrow,\\uparrow\\}$ 表示自旋向下和向上。这样一种离散化方式共计需要 $k^{3N}\\times 2^{N}$ 个数据来表示波函数。在这里,$\\mathcal{A}$ 表示反对称化操作(根据泡利不相容原理)并且 $\\psi(\\mathbf{x_1}, \\mathbf{x_2}, \\ldots, \\mathbf{x_N})=\\langle\\mathbf{x_1}, \\mathbf{x_2}, \\ldots, \\mathbf{x_N}|\\Psi\\rangle$。 可以看出,经典计算机存储这样一个波函数需要的内存是随着电子个数呈指数增长的。这使得基于这种离散化的经典数值方法,无法模拟超过几十个电子的系统。那么,我们是不是能够通过量子设备来存储和准备这样一个波函数然后求解基态能量 $E_0$ 呢?在下一节中,我们将以最简单的分子系统 -- 氢分子($H_2$)为例,讲解 VQE 算法。\n",
"\n", "\n",
"**注释2:** 关于量子化学和现有数值计算方法的综述也超过了本教程的处理范围,我们推荐感兴趣的读者去查阅以下经典教材 Helgaker 等人撰写的 *'Molecular Electronic-Structure Theory'* [6] 以及 Szabo & Ostlund 撰写的 *'Modern Quantum Chemistry: Introduction to Advanced Electronic Structure Theory'* [8]。 如果需要弥补量子计算和量子化学之间知识空缺,请参考以下综述文章 [Quantum chemistry in the age of quantum computing](https://pubs.acs.org/doi/10.1021/acs.chemrev.8b00803) [1] 和 [Quantum computational chemistry](https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.92.015003) [2] 。\n", "**注释2:** 关于量子化学和现有数值计算方法的综述也超过了本教程的处理范围,我们推荐感兴趣的读者去查阅以下经典教材 Helgaker 等人撰写的 *'Molecular Electronic-Structure Theory'* [6] 以及 Szabo & Ostlund 撰写的 *'Modern Quantum Chemistry: Introduction to Advanced Electronic Structure Theory'* [8]。 如果需要弥补量子计算和量子化学之间知识空缺,请参考以下综述文章 [Quantum chemistry in the age of quantum computing](https://pubs.acs.org/doi/10.1021/acs.chemrev.8b00803) [1] 和 [Quantum computational chemistry](https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.92.015003) [2] 。\n",
"\n", "\n",
...@@ -110,7 +110,9 @@ ...@@ -110,7 +110,9 @@
"\n", "\n",
"### 构造电子哈密顿量\n", "### 构造电子哈密顿量\n",
"\n", "\n",
"首先,让我们通过下面几行代码引入必要的 library 和 package。" "首先,让我们通过下面几行代码引入必要的 library 和 package。量桨的量子化学工具包是基于 `psi4` 和 `openfermion` 进行开发的,所以需要读者先行安装这两个语言包。在进入下面的教程之前,我们强烈建议您先阅读[哈密顿量的构造](./BuildingMolecule_CN.ipynb)教程,该教程介绍了如何使用量桨的量子化学工具包。\n",
"\n",
"**注意:关于环境设置,请参考 [README_CN.md](https://github.com/PaddlePaddle/Quantum/blob/master/README_CN.md).**"
] ]
}, },
{ {
...@@ -124,27 +126,28 @@ ...@@ -124,27 +126,28 @@
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"import paddle\n",
"import paddle_quantum.qchem as qchem\n",
"from paddle_quantum.utils import Hamiltonian\n",
"from paddle_quantum.circuit import UAnsatz\n",
"\n",
"import os\n", "import os\n",
"import platform\n",
"import matplotlib.pyplot as plt\n", "import matplotlib.pyplot as plt\n",
"from IPython.display import clear_output\n",
"\n", "\n",
"import numpy\n", "import numpy\n",
"from numpy import concatenate\n",
"from numpy import pi as PI\n", "from numpy import pi as PI\n",
"from numpy import savez, zeros\n", "from numpy import savez, zeros\n",
"\n", "\n",
"import paddle\n", "# 无视警告\n",
"from paddle_quantum.circuit import UAnsatz\n", "import warnings\n",
"from paddle_quantum.utils import pauli_str_to_matrix\n", "warnings.filterwarnings(\"ignore\")"
"from paddle_quantum.VQE.chemistrysub import H2_generator"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"对于具体需要分析的分子,我们需要其**几何构型** (geometry)、**基组**(basis set,例如 STO-3G 基于高斯函数)、**多重度**(multiplicity)以及**分子的净电荷数** (charge) 等多项信息来建模获取描述系统的哈密顿量。具体的,通过我们内置的量子化学工具包可以利用 fermion-to-qubit 映射的技术来输出目标分子的量子比特哈密顿量表示(泡利字符串)。" "对于具体需要分析的分子,我们需要其**几何构型** (geometry)、**基组**(basis set,例如 STO-3G 基于高斯函数)、**多重度**(multiplicity)以及**分子的净电荷数** (charge) 等多项信息来建模计算出该分子单体积分 (one-body integrations),双体积分(two-body integrations) 以及哈密顿量等信息。接下来,通过量桨的量子化学工具包将分子的哈密顿量提取出来并储存为 paddle quantum 的 `Hamiltonian` 类,方便我们下一步的操作。"
] ]
}, },
{ {
...@@ -156,104 +159,53 @@ ...@@ -156,104 +159,53 @@
"start_time": "2021-04-30T09:13:45.531302Z" "start_time": "2021-04-30T09:13:45.531302Z"
} }
}, },
"outputs": [],
"source": [
"Hamiltonian, N = H2_generator()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"面向更高级的用户,我们这里提供一个简单的生成氢分子 (H2)哈密顿量的教程。先安装以下两个package (**仅Mac/Linux用户可使用,Windows用户暂时不支持**):"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"ExecuteTime": {
"end_time": "2021-04-30T09:13:49.924911Z",
"start_time": "2021-04-30T09:13:45.604181Z"
}
},
"outputs": [],
"source": [
"!pip install openfermion\n",
"clear_output()"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"ExecuteTime": {
"end_time": "2021-04-30T09:13:57.494843Z",
"start_time": "2021-04-30T09:13:55.235443Z"
}
},
"outputs": [],
"source": [
"!pip install openfermionpyscf\n",
"clear_output()"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"ExecuteTime": {
"end_time": "2021-04-30T09:14:00.836635Z",
"start_time": "2021-04-30T09:13:58.092862Z"
}
},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"FCI energy for H2_sto-3g_singlet (2 electrons) is -1.137283834485513.\n",
"\n",
"The generated h2 Hamiltonian is \n", "The generated h2 Hamiltonian is \n",
" -0.042078976477822494 [] +\n", " -0.09706626861762556 I\n",
"-0.04475014401535163 [X0 X1 Y2 Y3] +\n", "-0.04530261550868938 X0, X1, Y2, Y3\n",
"0.04475014401535163 [X0 Y1 Y2 X3] +\n", "0.04530261550868938 X0, Y1, Y2, X3\n",
"0.04475014401535163 [Y0 X1 X2 Y3] +\n", "0.04530261550868938 Y0, X1, X2, Y3\n",
"-0.04475014401535163 [Y0 Y1 X2 X3] +\n", "-0.04530261550868938 Y0, Y1, X2, X3\n",
"0.17771287465139946 [Z0] +\n", "0.1714128263940239 Z0\n",
"0.17059738328801055 [Z0 Z1] +\n", "0.16868898168693286 Z0, Z1\n",
"0.12293305056183797 [Z0 Z2] +\n", "0.12062523481381837 Z0, Z2\n",
"0.1676831945771896 [Z0 Z3] +\n", "0.16592785032250773 Z0, Z3\n",
"0.17771287465139946 [Z1] +\n", "0.17141282639402394 Z1\n",
"0.1676831945771896 [Z1 Z2] +\n", "0.16592785032250773 Z1, Z2\n",
"0.12293305056183797 [Z1 Z3] +\n", "0.12062523481381837 Z1, Z3\n",
"-0.24274280513140462 [Z2] +\n", "-0.2234315367466399 Z2\n",
"0.1762764080431959 [Z2 Z3] +\n", "0.17441287610651626 Z2, Z3\n",
"-0.24274280513140462 [Z3]\n" "-0.2234315367466399 Z3\n"
] ]
} }
], ],
"source": [ "source": [
"# 操作系统信息\n", "geo = qchem.geometry(structure=[['H', [-0., 0., 0.0]], ['H', [-0., 0., 0.74]]])\n",
"sysStr = platform.system()\n", "# geo = qchem.geometry(file='h2.xyz')\n",
"\n", "\n",
"# 判断操作系统\n", "# 将分子信息存储在 molecule 里,包括单体积分(one-body integrations),双体积分(two-body integrations),分子的哈密顿量等\n",
"if sysStr in ('Linux', 'Darwin'):\n", "molecule = qchem.get_molecular_data(\n",
"\n", " geometry=geo,\n",
" import openfermion\n", " basis='sto-3g',\n",
" import openfermionpyscf\n", " charge=0,\n",
"\n", " multiplicity=1,\n",
" # 请检查是否正确下载了 h2 的几何构型文件\n", " method=\"fci\",\n",
" geometry = 'h2.xyz'\n", " if_save=True,\n",
" # geometry = [('H', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 0.74))]\n", " if_print=True\n",
" basis = 'sto-3g'\n", ")\n",
" charge = 0\n", "# 提取哈密顿量\n",
" multiplicity = 1\n", "molecular_hamiltonian = qchem.spin_hamiltonian(molecule=molecule,\n",
"\n", " filename=None, \n",
" # 生成哈密顿量\n", " multiplicity=1, \n",
" molecular_hamiltonian = openfermionpyscf.generate_molecular_hamiltonian(geometry, basis, multiplicity, charge)\n", " mapping_method = 'jordan_wigner',)\n",
" qubit_op = openfermion.transforms.jordan_wigner(molecular_hamiltonian)\n", "# 打印结果\n",
"\n", "print(\"\\nThe generated h2 Hamiltonian is \\n\", molecular_hamiltonian)"
" # 打印结果\n",
" print(\"The generated h2 Hamiltonian is \\n\", qubit_op)"
] ]
}, },
{ {
...@@ -262,46 +214,7 @@ ...@@ -262,46 +214,7 @@
"source": [ "source": [
"**注释4:** 生成这个哈密顿量的几何构型中,两个氢原子间的原子间隔(interatomic distance)为 $d = 74$ pm。\n", "**注释4:** 生成这个哈密顿量的几何构型中,两个氢原子间的原子间隔(interatomic distance)为 $d = 74$ pm。\n",
"\n", "\n",
"除了氢分子 (H2) 之外, 我们也提供了氟化氢 (HF) 分子的几何构型文件 `hf.xyz`。如果你需要测试更多分子的几何构型,请移步至这个[数据库](http://smart.sns.it/molecules/index.html)。此外,我们还需要把这些本质上由泡利算符表示的哈密顿量转化成量桨支持的数据格式,这里我们提供这个接口。" "除了输入分子的几何结构外,我们还支持读取分子的几何构型文件 (`.xyz` 文件),关于量子化学工具包更多的用法请参考[哈密顿量的构造](./BuildingMolecule_CN.ipynb)教程。如果你需要测试更多分子的几何构型,请移步至这个[数据库](http://smart.sns.it/molecules/index.html)。"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"ExecuteTime": {
"end_time": "2021-04-30T09:14:00.852699Z",
"start_time": "2021-04-30T09:14:00.842277Z"
}
},
"outputs": [],
"source": [
"def Hamiltonian_str_convert(qubit_op):\n",
" '''\n",
" 将上述提供的哈密顿量信息转为量桨支持的泡利字符串\n",
" H = [[1.0, \"z0,x1\"], [-1.0, \"y0,z1\"], ...]\n",
" '''\n",
" info_dic = qubit_op.terms\n",
" \n",
" def process_tuple(tup):\n",
" if len(tup) == 0:\n",
" return 'i0'\n",
" else:\n",
" res = ''\n",
" for ele in tup:\n",
" res += ele[1].lower()\n",
" res += str(ele[0])\n",
" res += ','\n",
" return res[:-1]\n",
" H_info = []\n",
" \n",
" for key, value in qubit_op.terms.items():\n",
" H_info.append([value.real, process_tuple(key)])\n",
" \n",
" return H_info\n",
"\n",
"if sysStr in ('Linux', 'Darwin'):\n",
" Hamiltonian = Hamiltonian_str_convert(qubit_op)"
] ]
}, },
{ {
...@@ -323,13 +236,8 @@ ...@@ -323,13 +236,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 7, "execution_count": 3,
"metadata": { "metadata": {},
"ExecuteTime": {
"end_time": "2021-04-30T09:14:01.746172Z",
"start_time": "2021-04-30T09:14:01.738712Z"
}
},
"outputs": [], "outputs": [],
"source": [ "source": [
"def U_theta(theta, Hamiltonian, N, D):\n", "def U_theta(theta, Hamiltonian, N, D):\n",
...@@ -347,13 +255,13 @@ ...@@ -347,13 +255,13 @@
" for i in range(N):\n", " for i in range(N):\n",
" cir.ry(theta=theta[D][i][0], which_qubit=i)\n", " cir.ry(theta=theta[D][i][0], which_qubit=i)\n",
" \n", " \n",
" # 量子神经网络作用在默认的初始态 |0000>上\n", " # 量子神经网络作用在默认的初始态 |0000> 上\n",
" cir.run_state_vector()\n", " fin_state = cir.run_state_vector()\n",
" \n", " \n",
" # 计算给定哈密顿量的期望值\n", " # 计算给定哈密顿量的期望值\n",
" expectation_val = cir.expecval(Hamiltonian)\n", " expectation_val = cir.expecval(Hamiltonian)\n",
"\n", "\n",
" return expectation_val, cir" " return expectation_val, cir, fin_state"
] ]
}, },
{ {
...@@ -373,13 +281,8 @@ ...@@ -373,13 +281,8 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 8, "execution_count": 4,
"metadata": { "metadata": {},
"ExecuteTime": {
"end_time": "2021-04-30T09:14:02.737957Z",
"start_time": "2021-04-30T09:14:02.728041Z"
}
},
"outputs": [], "outputs": [],
"source": [ "source": [
"class StateNet(paddle.nn.Layer):\n", "class StateNet(paddle.nn.Layer):\n",
...@@ -399,9 +302,9 @@ ...@@ -399,9 +302,9 @@
" def forward(self, N, D):\n", " def forward(self, N, D):\n",
" \n", " \n",
" # 计算损失函数/期望值\n", " # 计算损失函数/期望值\n",
" loss, cir = U_theta(self.theta, Hamiltonian, N, D)\n", " loss, cir, fin_state = U_theta(self.theta, molecular_hamiltonian.pauli_str, N, D)\n",
"\n", "\n",
" return loss, cir" " return loss, cir, fin_state"
] ]
}, },
{ {
...@@ -415,7 +318,7 @@ ...@@ -415,7 +318,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 9, "execution_count": 5,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T09:14:03.744957Z", "end_time": "2021-04-30T09:14:03.744957Z",
...@@ -426,7 +329,8 @@ ...@@ -426,7 +329,8 @@
"source": [ "source": [
"ITR = 80 # 设置训练的总迭代次数\n", "ITR = 80 # 设置训练的总迭代次数\n",
"LR = 0.4 # 设置学习速率\n", "LR = 0.4 # 设置学习速率\n",
"D = 2 # 设置量子神经网络中重复计算模块的深度 Depth" "D = 2 # 设置量子神经网络中重复计算模块的深度 Depth\n",
"N = molecular_hamiltonian.n_qubits # 设置参与计算的量子比特数"
] ]
}, },
{ {
...@@ -440,35 +344,30 @@ ...@@ -440,35 +344,30 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 12, "execution_count": 6,
"metadata": { "metadata": {},
"ExecuteTime": {
"end_time": "2021-04-30T09:14:58.180112Z",
"start_time": "2021-04-30T09:14:31.954222Z"
}
},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"iter: 20 loss: -0.9930\n", "iter: 20 loss: -0.8409\n",
"iter: 20 Ground state energy: -0.9930 Ha\n", "iter: 20 Ground state energy: -0.8409 Ha\n",
"iter: 40 loss: -1.1221\n", "iter: 40 loss: -1.1311\n",
"iter: 40 Ground state energy: -1.1221 Ha\n", "iter: 40 Ground state energy: -1.1311 Ha\n",
"iter: 60 loss: -1.1333\n", "iter: 60 loss: -1.1351\n",
"iter: 60 Ground state energy: -1.1333 Ha\n", "iter: 60 Ground state energy: -1.1351 Ha\n",
"iter: 80 loss: -1.1359\n", "iter: 80 loss: -1.1370\n",
"iter: 80 Ground state energy: -1.1359 Ha\n", "iter: 80 Ground state energy: -1.1370 Ha\n",
"\n", "\n",
"训练后的电路:\n", "训练后的电路:\n",
"--Ry(4.717)----*--------------X----Ry(4.718)----*--------------X----Ry(-0.02)--\n", "--Ry(1.578)----*--------------x----Ry(4.696)----*--------------x----Ry(9.430)--\n",
" | | | | \n", " | | | | \n",
"--Ry(4.733)----X----*---------|----Ry(4.486)----X----*---------|----Ry(4.828)--\n", "--Ry(3.986)----x----*---------|----Ry(1.331)----x----*---------|----Ry(4.274)--\n",
" | | | | \n", " | | | | \n",
"--Ry(-3.25)---------X----*----|----Ry(4.729)---------X----*----|----Ry(-0.01)--\n", "--Ry(3.601)---------x----*----|----Ry(1.547)---------x----*----|----Ry(6.260)--\n",
" | | | | \n", " | | | | \n",
"--Ry(-1.55)--------------X----*----Ry(4.704)--------------X----*----Ry(3.094)--\n", "--Ry(1.585)--------------x----*----Ry(4.713)--------------x----*----Ry(2.416)--\n",
" \n" " \n"
] ]
} }
...@@ -488,7 +387,7 @@ ...@@ -488,7 +387,7 @@
"for itr in range(1, ITR + 1):\n", "for itr in range(1, ITR + 1):\n",
"\n", "\n",
" # 前向传播计算损失函数\n", " # 前向传播计算损失函数\n",
" loss, cir = net(N, D)\n", " loss, cir, fin_state = net(N, D)\n",
"\n", "\n",
" # 在动态图机制下,反向传播极小化损失函数\n", " # 在动态图机制下,反向传播极小化损失函数\n",
" loss.backward()\n", " loss.backward()\n",
...@@ -519,12 +418,12 @@ ...@@ -519,12 +418,12 @@
"metadata": {}, "metadata": {},
"source": [ "source": [
"### 测试效果\n", "### 测试效果\n",
"我们现在已经完成了量子神经网络的训练,通过 VQE 得到的基态能量的估计值大致为 $E_0 \\approx -1.136$ Ha,这与通过全价构型相互作用(FCI)$E_0 = -1.13618$ Ha 计算得出的值是在化学精度 $\\varepsilon = 1.6 \\times 10^{-3}$ Ha 内相符合的。" "我们现在已经完成了量子神经网络的训练,通过 VQE 得到的基态能量的估计值大致为 $E_0 \\approx -1.137$ Ha,这与通过全价构型相互作用(FCI)$E_0 = -1.13728$ Ha 计算得出的值是在化学精度 $\\varepsilon = 1.6 \\times 10^{-3}$ Ha 内相符合的。"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 11, "execution_count": 7,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T09:14:21.341323Z", "end_time": "2021-04-30T09:14:21.341323Z",
...@@ -534,7 +433,7 @@ ...@@ -534,7 +433,7 @@
"outputs": [ "outputs": [
{ {
"data": { "data": {
"image/png": "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JqJZoihu48ZTDdmFGja0EoUxJqJZoiikCv/+96lqp0KWKIwxEc4SRaHsbDh48FRDdiFLFMYEbdeuXVx//fV06NCBlJQUBg4cyPTp06s1hszMTHr27Bn08QsXLuSbb76ptONqIksUhc45B/72N7jwwtP3x8dbG4UxQVBVRo0axaBBg9i8eTPp6elMmzaNrKysM44ta/a76hSOiaK63z9LFMU1bAj165++Lz4eDh2Ckye9icmYMPHFF18QExNTNDw3QLt27bj77rsBeOutt7jqqqsYMmQIQ4cOJTc3l1GjRtG7d2/OO+88MjIyAJg0aRJ//OMfi87Rs2dPMjMzyczMpFu3bkycOJEePXowYsQIjh49CriOdH369KFPnz68/PLLpcb45z//me7du9O7d2+uu+46MjMzeeWVV3j++edJSkpi8eLFzJo1iwEDBtC3b1+GDRvGrl27/B6XnZ3NNddcQ79+/ejXrx9ff/31GdcrKCjgwQcfpF+/fvTu3ZtXX30VcEnn4osvZsyYMUVjULlRNNzPctFFF5GSksIll1zCjh07ADet63333UdqaiovvPACS5cupXfv3iQlJfHggw8WlaIGDRrEihUrimK44IILWLlyZbl/n6cpbWyPcF3OeqynkmbNcuM97dtXuec1ppKVHL/nkUdU581z6ydOuO0vvnDbeXlue9Eit33okNv++mu3vX+/216yxG3n5pZ9/RdeeEHvu+++Up+fMmWKtm7dWnNyclRV9a677tJJkyapqur8+fO1T58+qqr6v//7v/rMM88Uva5Hjx66ZcsW3bJli0ZFReny5ctVVfXaa6/V//u//1NVN7bUl19+qaqqDzzwgPbo0cNvDC1bttS8vDxVVd27d6/f6+Xm5urJkydVVfX111/X+++/3+9x48aN08WLF6uq6tatW7Vr165nXO/VV1/Vxx9/XFVV8/LyNCUlRTdv3qwLFizQ+Ph43bZtmxYUFOh5552nixcv1uPHj+vAgQN19+7dqqo6bdo0vfnmm1XVjVt1xx13nPa+fPPNN6qq+vDDDxf9zG+99Zbee++9qqq6YcMG9fedWN6xnqxndlmKD+NRuG6MKdOdd97JV199RUxMTNGscMOHD6dJkyYAfPXVV/zjH/8AYMiQIeTk5HCgjGre9u3bF808l5KSQmZmJvv27WPfvn0MGjQIgJ///Od8+umn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\n", "image/png": "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",
"text/plain": [ "text/plain": [
"<Figure size 432x288 with 1 Axes>" "<Figure size 432x288 with 1 Axes>"
] ]
...@@ -549,7 +448,7 @@ ...@@ -549,7 +448,7 @@
"result = numpy.load('./output/summary_data.npz')\n", "result = numpy.load('./output/summary_data.npz')\n",
"\n", "\n",
"eig_val, eig_state = numpy.linalg.eig(\n", "eig_val, eig_state = numpy.linalg.eig(\n",
" pauli_str_to_matrix(Hamiltonian, N))\n", " Hamiltonian(molecular_hamiltonian.pauli_str).construct_h_matrix())\n",
"min_eig_H = numpy.min(eig_val.real)\n", "min_eig_H = numpy.min(eig_val.real)\n",
"min_loss = numpy.ones([len(result['iter'])]) * min_eig_H\n", "min_loss = numpy.ones([len(result['iter'])]) * min_eig_H\n",
"\n", "\n",
...@@ -585,7 +484,7 @@ ...@@ -585,7 +484,7 @@
"\n", "\n",
"![vqe-fig-dist](figures/vqe-fig-distance.png)\n", "![vqe-fig-dist](figures/vqe-fig-distance.png)\n",
"\n", "\n",
"从上述过程中可以看出,最小值确实发生在 $d = 74$ pm (1 pm = $1\\times 10^{-12}$m) 附近,这是与[实验测得数据](https://cccbdb.nist.gov/exp2x.asp?casno=1333740&charge=0)相符合的 $d_{exp} (H_2) = 74.14$ pm." "从上可以看出,最小值确实发生在 $d = 74$ pm (1 pm = $1\\times 10^{-12}$m) 附近,这是与[实验测得数据](https://cccbdb.nist.gov/exp2x.asp?casno=1333740&charge=0)相符合的 $d_{exp} (H_2) = 74.14$ pm."
] ]
}, },
{ {
...@@ -631,7 +530,7 @@ ...@@ -631,7 +530,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.9.5" "version": "3.7.10"
}, },
"toc": { "toc": {
"base_numbering": 1, "base_numbering": 1,
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
"\\tag{3}\n", "\\tag{3}\n",
"$$\n", "$$\n",
"\n", "\n",
"In the next section, we will provide a brief review on the electronic structure problem which essentially tells us where does the Hamiltonian $H$ come from. For those who are already familiar with this topic or only interested in how to implement VQE on Paddle Quantum, please skip this part and jump into the illustrative example of hydrogen molecule $H_2$.\n", "In the next section, we will provide a brief review on the electronic structure problem which essentially tells us how to calculate the Hamiltonian $H$. For those who are already familiar with this topic or only interested in how to implement VQE on Paddle Quantum, please skip this part and jump into the illustrative example of hydrogen molecule $H_2$.\n",
"\n", "\n",
"## Background: the electronic structure problem\n", "## Background: the electronic structure problem\n",
"\n", "\n",
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
"\\end{align}\n", "\\end{align}\n",
"$$\n", "$$\n",
"\n", "\n",
"where $R_i, M_i,$ and $Z_i$ denote the position, mass and atomic number (the number of protons) of the $i^{th}$ nucleus, and the positions of electrons are $r_i$. The first two sums describe the kinetic energy of nuclei and electrons, respectively. The third sum describes the attractive Coulomb interaction between the positively charged nuclei and the negatively charged electrons. The last two terms represent the repulsive nuclei-nuclei and electron-electron interactions. Here, the molecular Hamiltonian $\\hat{H}_\\text{mol}$ is already in atomic units of energy, **Hartree**. 1 Hartree is $[\\hbar^2/(m_ee^2a_0^2)] = 27.2$ eV or 630 kcal/mol, where $m_e, e,$ and $a_0$ stand for the mass of electron, charge of electron, and Bohr radius. \n", "where $R_i, M_i,$ and $Z_i$ denote the position, mass and atomic number (the number of protons) of the $i^{th}$ nucleus, and the positions of electrons are $r_i$. The first two sums describe the kinetic energy of nuclei and electrons, respectively. The third sum describes the attractive Coulomb interaction between the positively charged nuclei and the negatively charged electrons. The last two terms represent the repulsive nuclei-nuclei and electron-electron interactions. Here, the molecular Hamiltonian $\\hat{H}_\\text{mol}$ is already in atomic units of energy, **Hartree**. $1$ Hartree is $[\\hbar^2/(m_ee^2a_0^2)] = 27.2$ eV or $630$ kcal/mol, where $m_e, e,$ and $a_0$ stand for the mass of electron, charge of electron, and Bohr radius. \n",
"\n", "\n",
"\n", "\n",
"**Note:** The spin-orbit interaction and hyperfine interaction are not considered in this picture. They can be treated as perturbations if necessary. \n", "**Note:** The spin-orbit interaction and hyperfine interaction are not considered in this picture. They can be treated as perturbations if necessary. \n",
...@@ -102,8 +102,9 @@ ...@@ -102,8 +102,9 @@
"\n", "\n",
"### Building electronic Hamiltonian\n", "### Building electronic Hamiltonian\n",
"\n", "\n",
"First of all, let us import the necessary libraries and packages.\n", "First of all, let us import the necessary libraries and packages.`qchem` module in Paddle Quantum is developed basing on `psi4` and `openfermion`, so you need to install these two packages before executing the following codes. We strongly encourage you to read the tutorial [Building molecular Hamiltonian](./BuildingMolecule_EN.ipynb) first, which introduces how to utilize our quantum chemistry toolkit `qchem`.\n",
"\n" "\n",
"**Note: As to the environment setting, please refer to [README.md](https://github.com/PaddlePaddle/Quantum/blob/master/README.md).**"
] ]
}, },
{ {
...@@ -117,27 +118,28 @@ ...@@ -117,27 +118,28 @@
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"import paddle\n",
"import paddle_quantum.qchem as qchem\n",
"from paddle_quantum.utils import Hamiltonian\n",
"from paddle_quantum.circuit import UAnsatz\n",
"\n",
"import os\n", "import os\n",
"import platform\n",
"import matplotlib.pyplot as plt\n", "import matplotlib.pyplot as plt\n",
"from IPython.display import clear_output\n",
"\n", "\n",
"import numpy\n", "import numpy\n",
"from numpy import concatenate\n",
"from numpy import pi as PI\n", "from numpy import pi as PI\n",
"from numpy import savez, zeros\n", "from numpy import savez, zeros\n",
"\n", "\n",
"import paddle\n", "# Eliminate noisy python warnings\n",
"from paddle_quantum.circuit import UAnsatz\n", "import warnings\n",
"from paddle_quantum.utils import pauli_str_to_matrix\n", "warnings.filterwarnings(\"ignore\")"
"from paddle_quantum.VQE.chemistrysub import H2_generator"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"To analyze specific molecules, we need several key information such as **geometry**, **basis set** (such as STO-3G), **multiplicity**, and **charge** to obtain the discretized Hamiltonian $H$. Specifically, through our built-in quantum chemistry toolkit, fermion-to-qubit mapping technology can be used to output the qubit Hamiltonian of hydrogen molecule $H_2$," "To analyze specific molecules, we need several key information such as **geometry**, **basis set** (such as STO-3G), **multiplicity**, and **charge** to model the molecule and achieve more information about the molecule, such as one-body integrations, two-body integrations, and others. Next, through our built-in quantum chemistry toolkit, we could set a `Hamiltonian` class to carry molecule Hamiltonian information, which may simplify the following calculations."
] ]
}, },
{ {
...@@ -149,104 +151,53 @@ ...@@ -149,104 +151,53 @@
"start_time": "2021-04-30T09:14:44.975892Z" "start_time": "2021-04-30T09:14:44.975892Z"
} }
}, },
"outputs": [],
"source": [
"Hamiltonian, N = H2_generator()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For more advanced users, we provide a simple tutorial on how to generate such a Hamiltonian. Install the following two packages first (**only available for Mac/Linux users, not available to Windows users temporarily**):"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"ExecuteTime": {
"end_time": "2021-02-27T13:57:33.364017Z",
"start_time": "2021-02-27T13:57:30.969092Z"
}
},
"outputs": [],
"source": [
"!pip install openfermion\n",
"clear_output()"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"ExecuteTime": {
"end_time": "2021-02-27T13:57:35.854193Z",
"start_time": "2021-02-27T13:57:33.368266Z"
}
},
"outputs": [],
"source": [
"!pip install openfermionpyscf\n",
"clear_output()"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"ExecuteTime": {
"end_time": "2021-04-30T09:14:49.975774Z",
"start_time": "2021-04-30T09:14:45.126298Z"
}
},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"FCI energy for H2_sto-3g_singlet (2 electrons) is -1.137283834485513.\n",
"\n",
"The generated h2 Hamiltonian is \n", "The generated h2 Hamiltonian is \n",
" -0.042078976477822494 [] +\n", " -0.09706626861762556 I\n",
"-0.04475014401535163 [X0 X1 Y2 Y3] +\n", "-0.04530261550868938 X0, X1, Y2, Y3\n",
"0.04475014401535163 [X0 Y1 Y2 X3] +\n", "0.04530261550868938 X0, Y1, Y2, X3\n",
"0.04475014401535163 [Y0 X1 X2 Y3] +\n", "0.04530261550868938 Y0, X1, X2, Y3\n",
"-0.04475014401535163 [Y0 Y1 X2 X3] +\n", "-0.04530261550868938 Y0, Y1, X2, X3\n",
"0.17771287465139946 [Z0] +\n", "0.1714128263940239 Z0\n",
"0.17059738328801055 [Z0 Z1] +\n", "0.16868898168693286 Z0, Z1\n",
"0.12293305056183797 [Z0 Z2] +\n", "0.12062523481381837 Z0, Z2\n",
"0.1676831945771896 [Z0 Z3] +\n", "0.16592785032250773 Z0, Z3\n",
"0.17771287465139946 [Z1] +\n", "0.17141282639402394 Z1\n",
"0.1676831945771896 [Z1 Z2] +\n", "0.16592785032250773 Z1, Z2\n",
"0.12293305056183797 [Z1 Z3] +\n", "0.12062523481381837 Z1, Z3\n",
"-0.24274280513140462 [Z2] +\n", "-0.2234315367466399 Z2\n",
"0.1762764080431959 [Z2 Z3] +\n", "0.17441287610651626 Z2, Z3\n",
"-0.24274280513140462 [Z3]\n" "-0.2234315367466399 Z3\n"
] ]
} }
], ],
"source": [ "source": [
"# Operating system information\n", "geo = qchem.geometry(structure=[['H', [-0., 0., 0.0]], ['H', [-0., 0., 0.74]]])\n",
"sysStr = platform.system()\n", "# geo = qchem.geometry(file='h2.xyz')\n",
"\n", "\n",
"# Decide which operating system the user is using\n", "# Save molecule information in to variable molecule, including one-body integrations, one-body integrations, molecular and Hamiltonian\n",
"if sysStr in ('Linux', 'Darwin'):\n", "molecule = qchem.get_molecular_data(\n",
"\n", " geometry=geo,\n",
" import openfermion \n", " basis='sto-3g',\n",
" import openfermionpyscf \n", " charge=0,\n",
"\n", " multiplicity=1,\n",
" # Please check whether the geometric configuration file of h2 is downloaded correctly\n", " method=\"fci\",\n",
" geometry = 'h2.xyz'\n", " if_save=True,\n",
" # geometry = [('H', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 0.74))]\n", " if_print=True\n",
" basis = 'sto-3g'\n", ")\n",
" charge = 0\n", "# Recall Hamiltonian\n",
" multiplicity = 1\n", "molecular_hamiltonian = qchem.spin_hamiltonian(molecule=molecule,\n",
"\n", " filename=None, \n",
" # Generate Hamiltonian\n", " multiplicity=1, \n",
" molecular_hamiltonian = openfermionpyscf.generate_molecular_hamiltonian(geometry, basis, multiplicity, charge)\n", " mapping_method = 'jordan_wigner',)\n",
" qubit_op = openfermion.transforms.jordan_wigner(molecular_hamiltonian)\n", "# Print results\n",
" \n", "print(\"\\nThe generated h2 Hamiltonian is \\n\", molecular_hamiltonian)"
" # Print result\n",
" print(\"The generated h2 Hamiltonian is \\n\", qubit_op)"
] ]
}, },
{ {
...@@ -255,48 +206,7 @@ ...@@ -255,48 +206,7 @@
"source": [ "source": [
"**Note:** This Hamiltonian is generated with an interatomic distance of $d = 74$ pm. \n", "**Note:** This Hamiltonian is generated with an interatomic distance of $d = 74$ pm. \n",
"\n", "\n",
"In addition to hydrogen molecule $H_2$, we also provide the geometric configuration file of hydrogen fluoride (HF) molecule `hf.xyz`. If you need to test the geometric configuration of more molecules, please check out this [database](http://smart.sns.it/molecules/index.html). In addition, we also need to convert the Hamiltonian into the Pauli string format supported by Paddle Quantum. Here we provide this interface.\n", "In addition to inputting molecular geometry directly, inputting molecular geometry file (.xyz file) is also allowed. For more information about quantum chemistry toolkit, please refer to the tutorial [Building molecular Hamiltonian](./BuildingMolecule_EN.ipynb). If you need to test the geometric configuration of more molecules, please check out this [database](http://smart.sns.it/molecules/index.html)."
"\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"ExecuteTime": {
"end_time": "2021-04-30T09:14:50.043900Z",
"start_time": "2021-04-30T09:14:50.019574Z"
}
},
"outputs": [],
"source": [
"def Hamiltonian_str_convert(qubit_op):\n",
" '''\n",
" Convert the Hamiltonian information provided above into Pauli strings supported by Paddle Quantum\n",
"\n",
" H = [[1.0, \"z0,x1\"], [-1.0, \"y0,z1\"], ...]\n",
" '''\n",
" info_dic = qubit_op.terms\n",
" \n",
" def process_tuple(tup):\n",
" if len(tup) == 0:\n",
" return 'i0'\n",
" else:\n",
" res = ''\n",
" for ele in tup:\n",
" res += ele[1].lower()\n",
" res += str(ele[0])\n",
" res += ','\n",
" return res[:-1]\n",
" H_info = []\n",
" \n",
" for key, value in qubit_op.terms.items():\n",
" H_info.append([value.real, process_tuple(key)])\n",
" \n",
" return H_info\n",
"\n",
"if sysStr in ('Linux', 'Darwin'):\n",
" Hamiltonian = Hamiltonian_str_convert(qubit_op)"
] ]
}, },
{ {
...@@ -316,7 +226,7 @@ ...@@ -316,7 +226,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 5, "execution_count": 3,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T09:14:50.083041Z", "end_time": "2021-04-30T09:14:50.083041Z",
...@@ -341,12 +251,12 @@ ...@@ -341,12 +251,12 @@
" cir.ry(theta=theta[D][i][0], which_qubit=i)\n", " cir.ry(theta=theta[D][i][0], which_qubit=i)\n",
" \n", " \n",
" # The quantum neural network acts on the default initial state |0000>\n", " # The quantum neural network acts on the default initial state |0000>\n",
" cir.run_state_vector()\n", " fin_state = cir.run_state_vector()\n",
" \n", " \n",
" # Calculate the expected value of a given Hamiltonian\n", " # Calculate the expectated value of the given Hamiltonian\n",
" expectation_val = cir.expecval(Hamiltonian)\n", " expectation_val = cir.expecval(Hamiltonian)\n",
"\n", "\n",
" return expectation_val, cir" " return expectation_val, cir, fin_state"
] ]
}, },
{ {
...@@ -367,7 +277,7 @@ ...@@ -367,7 +277,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 6, "execution_count": 4,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T09:14:50.183996Z", "end_time": "2021-04-30T09:14:50.183996Z",
...@@ -377,11 +287,14 @@ ...@@ -377,11 +287,14 @@
"outputs": [], "outputs": [],
"source": [ "source": [
"class StateNet(paddle.nn.Layer):\n", "class StateNet(paddle.nn.Layer):\n",
" \"\"\"\n",
" Construct the model net\n",
" \"\"\"\n",
"\n", "\n",
" def __init__(self, shape, dtype=\"float64\"):\n", " def __init__(self, shape, dtype=\"float64\"):\n",
" super(StateNet, self).__init__()\n", " super(StateNet, self).__init__()\n",
" \n", " \n",
" # Initialize the theta parameter list and fill the initial value with a uniform distribution of [0, 2*pi]\n", " # Initialize the list of theta parameters, filling the initial values with a uniform distribution of [0, 2* PI] \n",
" self.theta = self.create_parameter(shape=shape, \n", " self.theta = self.create_parameter(shape=shape, \n",
" default_initializer=paddle.nn.initializer.Uniform(low=0.0, high=2*PI),\n", " default_initializer=paddle.nn.initializer.Uniform(low=0.0, high=2*PI),\n",
" dtype=dtype, is_bias=False)\n", " dtype=dtype, is_bias=False)\n",
...@@ -389,10 +302,10 @@ ...@@ -389,10 +302,10 @@
" # Define loss function and forward propagation mechanism\n", " # Define loss function and forward propagation mechanism\n",
" def forward(self, N, D):\n", " def forward(self, N, D):\n",
" \n", " \n",
" # Calculate the loss function/expected value\n", " # Calculate loss function/expected value\n",
" loss, cir = U_theta(self.theta, Hamiltonian, N, D)\n", " loss, cir, fin_state = U_theta(self.theta, molecular_hamiltonian.pauli_str, N, D)\n",
"\n", "\n",
" return loss, cir" " return loss, cir, fin_state"
] ]
}, },
{ {
...@@ -406,7 +319,7 @@ ...@@ -406,7 +319,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 7, "execution_count": 5,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T09:14:50.222465Z", "end_time": "2021-04-30T09:14:50.222465Z",
...@@ -417,7 +330,8 @@ ...@@ -417,7 +330,8 @@
"source": [ "source": [
"ITR = 80 # Set the number of optimization iterations\n", "ITR = 80 # Set the number of optimization iterations\n",
"LR = 0.4 # Set the learning rate\n", "LR = 0.4 # Set the learning rate\n",
"D = 2 # Set the depth of the repetitive calculation module in QNN" "D = 2 # Set the depth of the repetitive calculation module in QNN\n",
"N = molecular_hamiltonian.n_qubits # Set number of qubits"
] ]
}, },
{ {
...@@ -431,7 +345,7 @@ ...@@ -431,7 +345,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 10, "execution_count": 6,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T09:15:52.165788Z", "end_time": "2021-04-30T09:15:52.165788Z",
...@@ -443,64 +357,63 @@ ...@@ -443,64 +357,63 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"iter: 20 loss: -1.0687\n", "iter: 20 loss: -1.0467\n",
"iter: 20 Ground state energy: -1.0687 Ha\n", "iter: 20 Ground state energy: -1.0467 Ha\n",
"iter: 40 loss: -1.1313\n", "iter: 40 loss: -1.1185\n",
"iter: 40 Ground state energy: -1.1313 Ha\n", "iter: 40 Ground state energy: -1.1185 Ha\n",
"iter: 60 loss: -1.1355\n", "iter: 60 loss: -1.1337\n",
"iter: 60 Ground state energy: -1.1355 Ha\n", "iter: 60 Ground state energy: -1.1337 Ha\n",
"iter: 80 loss: -1.1360\n", "iter: 80 loss: -1.1370\n",
"iter: 80 Ground state energy: -1.1360 Ha\n", "iter: 80 Ground state energy: -1.1370 Ha\n",
"\n", "\n",
"The trained circuit:\n", "Circuit after training:\n",
"--Ry(1.557)----*--------------X----Ry(1.578)----*--------------X----Ry(3.142)--\n", "--Ry(7.841)----*--------------x----Ry(7.851)----*--------------x----Ry(0.021)--\n",
" | | | | \n", " | | | | \n",
"--Ry(1.571)----X----*---------|----Ry(4.916)----X----*---------|----Ry(1.390)--\n", "--Ry(1.407)----x----*---------|----Ry(4.485)----x----*---------|----Ry(5.357)--\n",
" | | | | \n", " | | | | \n",
"--Ry(2.972)---------X----*----|----Ry(7.847)---------X----*----|----Ry(3.104)--\n", "--Ry(6.923)---------x----*----|----Ry(1.570)---------x----*----|----Ry(6.209)--\n",
" | | | | \n", " | | | | \n",
"--Ry(4.663)--------------X----*----Ry(1.577)--------------X----*----Ry(3.147)--\n", "--Ry(1.648)--------------x----*----Ry(4.728)--------------x----*----Ry(-0.16)--\n",
" \n" " \n"
] ]
} }
], ],
"source": [ "source": [
"# Determine the parameter dimension of the network\n", "# Determine the parameter dimensions of the network \n",
"net = StateNet(shape=[D + 1, N, 1])\n", "net = StateNet(shape=[D + 1, N, 1])\n",
"\n", "\n",
"\n", "# In general, we use the Adam optimizer to obtain relatively good convergence,\n",
"# Generally speaking, we use Adam optimizer to obtain relatively good convergence,\n", "# Of course you can change it to SGD or RMS prop.\n",
"# You can change it to SGD or RMS prop.\n",
"opt = paddle.optimizer.Adam(learning_rate=LR, parameters=net.parameters())\n", "opt = paddle.optimizer.Adam(learning_rate=LR, parameters=net.parameters())\n",
"\n", "\n",
"# Record optimization results\n", "# Record optimization results\n",
"summary_iter, summary_loss = [], []\n", "summary_iter, summary_loss = [], []\n",
"\n", "\n",
"# Optimization loop\n", "# Optimize iterations\n",
"for itr in range(1, ITR + 1):\n", "for itr in range(1, ITR + 1):\n",
"\n", "\n",
" # Forward propagation to calculate loss function\n", " # Forward propagation calculates the loss function\n",
" loss, cir = net(N, D)\n", " loss, cir, fin_state = net(N, D)\n",
"\n", "\n",
" # Use back propagation to minimize the loss function\n", " # Back propagation minimizes the loss function\n",
" loss.backward()\n", " loss.backward()\n",
" opt.minimize(loss)\n", " opt.minimize(loss)\n",
" opt.clear_grad()\n", " opt.clear_grad()\n",
"\n", "\n",
" # Record optimization results\n", " # Update optimization results\n",
" summary_loss.append(loss.numpy())\n", " summary_loss.append(loss.numpy())\n",
" summary_iter.append(itr)\n", " summary_iter.append(itr)\n",
"\n", "\n",
" # Print result\n", " # Print results\n",
" if itr % 20 == 0:\n", " if itr % 20 == 0:\n",
" print(\"iter:\", itr, \"loss:\", \"%.4f\" % loss.numpy())\n", " print(\"iter:\", itr, \"loss:\", \"%.4f\" % loss.numpy())\n",
" print(\"iter:\", itr, \"Ground state energy:\", \"%.4f Ha\" \n", " print(\"iter:\", itr, \"Ground state energy:\", \"%.4f Ha\" \n",
" % loss.numpy())\n", " % loss.numpy())\n",
" if itr == ITR:\n", " if itr == ITR:\n",
" print(\"\\nThe trained circuit:\")\n", " print(\"\\nCircuit after training:\") \n",
" print(cir)\n", " print(cir)\n",
"\n", "\n",
"# Save the training results to the output folder\n", "# Save the training results in the Output folder\n",
"os.makedirs(\"output\", exist_ok=True)\n", "os.makedirs(\"output\", exist_ok=True)\n",
"savez(\"./output/summary_data\", iter = summary_iter, \n", "savez(\"./output/summary_data\", iter = summary_iter, \n",
" energy=summary_loss)" " energy=summary_loss)"
...@@ -511,13 +424,13 @@ ...@@ -511,13 +424,13 @@
"metadata": {}, "metadata": {},
"source": [ "source": [
"### Benchmarking\n", "### Benchmarking\n",
"We have now completed the training of the quantum neural network, and the estimated value of the ground state energy obtained is $E_0 \\approx -1.1361$ Hartree, we compare it with the theoretical value $E_0 = -1.13618$ to benchmark our model. The estimation obtained with VQE agree with a full configuration-interaction (FCI) calculation within chemical accuracy $\\varepsilon = 1.6 \\times 10^{-3}$ Hartree.\n", "We have now completed the training of the quantum neural network, and the estimated value of the ground state energy obtained is $E_0 \\approx -1.137 $ Hartree, we compare it with the theoretical value $E_0 = -1.1371$ to benchmark our model. The estimation obtained with VQE agree with a full configuration-interaction (FCI) calculation within chemical accuracy $\\varepsilon = 1.6 \\times 10^{-3}$ Hartree.\n",
"\n" "\n"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 9, "execution_count": 7,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2021-04-30T09:15:18.096944Z", "end_time": "2021-04-30T09:15:18.096944Z",
...@@ -527,7 +440,7 @@ ...@@ -527,7 +440,7 @@
"outputs": [ "outputs": [
{ {
"data": { "data": {
"image/png": "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vokpU6Ywf/786vKmTZvYsmULBx54YPW6yZMnM3v2bCZNmgTAxo0bWbx4Mbm5uQwfPpwePXoAUFxczLJly6onfDrllFOAUCMpiDeFA+eddx4AGzZsYPPmzRx77LEAXHjhhbz44osAnHvuudx8883ccccdPPzww/vl7vSohvC4AZjq7rea2Q2x8vV17Hsz0PQjbxUUKFFIs3bLLbuft25ds9y2bc1y+/Y1yx071ix36lT/+YqKiqp/+AHuvfde1q1bR0lJScJ52td7nNatW7Nr167qcuKkQm3btq1+npOTU930VJdLL72Ujz/+mEMPPZSXX36Zl156ibfeeosXXniB//3f/2XOnDl7vOaqq67ixz/+MWeccQbTp09n/PjxSY+9a9cu3n///T0mVErk7tx9992MGTOmxvrp06fv8W+prKzE3SkqKuJvf/tb0uOl8/61a9eOU045heeee46nnnoq5fhW6Yqqj+JM4LHY88eAscl2MrNhwCHA5KYJK4HmpRDZK6NGjWLbtm1MmDChet0XX3xR5/7HH388TzzxBBB+OLt27UrHjh0pLCzko48+AuCjjz5i6dKlKc+bn59Pfn5+9XwS8WMCPPLII8yaNYuXX36ZXbt2sWLFCk488URuu+02Nm7cyJYtW+jQoQObE65w3LhxI4cddhgAjz32WPX62vuNHj2au+++u7qc2C8QN2bMGCZMmMDOnTsB+Pvf/87WrVvr/Lf07duXtWvXVieKnTt3Mm/evKT/5g4dOvDBBx8AMHHixBrbL7vsMq6++mpKS0vplE6Wr0dUieIQd4//Cn9OSAY1mFkr4E7guvoOZmbfNbMyMytbu3bt/omwoAAqKsJIsiJSLzPj2Wef5c0336Rnz54MHz6cSy65hNtuuy3p/uPHj2fmzJkMGjSIG264ofpH+ZxzzqGiooKioiLuueeetIYYf+SRR7jiiisoLi6u7hSuraqqim9961sMHDiQIUOGcPXVV5Ofn883vvENnnnmmepO6vHjx3PuuecybNgwuiYMDFp7v9/97neUlZUxaNAgBgwYwH333bfHOS+77DIGDBjA0KFDOfroo/ne976X8oql3NxcJk2axPXXX8/gwYMpLi6u80qrP/zhD1x++eUUFxezdetWDjrooOptw4YNo2PHjlx66aX1vnfpsLre1AYf2GwKsOdUU3AT8Ji75yfsu97d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\n", "image/png": "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",
"text/plain": [ "text/plain": [
"<Figure size 432x288 with 1 Axes>" "<Figure size 432x288 with 1 Axes>"
] ]
...@@ -542,7 +455,7 @@ ...@@ -542,7 +455,7 @@
"result = numpy.load('./output/summary_data.npz')\n", "result = numpy.load('./output/summary_data.npz')\n",
"\n", "\n",
"eig_val, eig_state = numpy.linalg.eig(\n", "eig_val, eig_state = numpy.linalg.eig(\n",
" pauli_str_to_matrix(Hamiltonian, N))\n", " Hamiltonian(molecular_hamiltonian.pauli_str).construct_h_matrix())\n",
"min_eig_H = numpy.min(eig_val.real)\n", "min_eig_H = numpy.min(eig_val.real)\n",
"min_loss = numpy.ones([len(result['iter'])]) * min_eig_H\n", "min_loss = numpy.ones([len(result['iter'])]) * min_eig_H\n",
"\n", "\n",
...@@ -622,7 +535,7 @@ ...@@ -622,7 +535,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,
......
3
in angstrom
H -0.02111417 0.8350417 1.47688078
O 0.0000 0.0000 0.0000
H -0.00201087 0.45191737 -0.27300254
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册