{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"# 量子近似优化算法 (QAOA)\n",
"\n",
"## 简体中文 | [English](./QAOA_En.ipynb)\n",
"\n",
" Copyright (c) 2020 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 准备\n",
"\n",
"本文档演示 Paddle Quantum 上量子近似优化算法(QAOA,Quantum Approximate Optimization Algorithm)的工作流程 [1]。\n",
"\n",
"开始之前完成准备工作:\n",
"\n",
" - 调用飞桨 paddlepaddle\n",
"\n",
" - 调用常用的库, 例如画图工具库 networkx 和 matplotlib.pyplot\n",
"\n",
" - 调用自定义函数 "
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"pycharm": {
"is_executing": false
}
},
"outputs": [],
"source": [
"from paddle import fluid\n",
"\n",
"import os\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import networkx as nx\n",
"\n",
"from paddle_quantum.circuit import UAnsatz\n",
"from paddle_quantum.utils import pauli_str_to_matrix"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 背景\n",
"\n",
"\n",
"量子近似优化算法(QAOA,Quantum Approximate Optimization Algorithm)是可以在近期有噪中等规模(NISQ,Noisy Intermediate-Scale Quantum)量子计算机上运行且具有广泛应用前景的量子算法。例如,QAOA 可以用来处理压缩图信号和二次优化等领域常见的离散组合优化问题。这类优化问题通常可以归结为下面的数学模型:\n",
"\n",
"\n",
" $$F=\\max_{z_i\\in\\{-1,1\\}} \\sum_{i,j} q_{ij}(1-z_iz_j)=-\\min_{z_i\\in\\{-1,1\\}} \\sum_{i,j} q_{ij}z_iz_j+ \\sum_{i,j} q_{ij}. $$\n",
"\n",
"\n",
"其中, $z_i \\in \\{-1 ,1\\} $ 是待求的二元参数,系数 $q_{ij}$ 是 $z_i z_j$ 的权重 (weight)。一般地,精确求解该问题对于经典计算机是 NP-hard 的,而 QAOA 被认为对近似求解这类困难问题具有潜在速度优势。\n",
"\n",
"QAOA 的工作原理是把上述经典优化问题(例如组合优化问题)甚至量子优化问题(例如量子多体系统中 Ising 模型的求解)等价地转化为求解一个物理系统哈密顿量(Hamiltonian)的基态能量(对应优化问题的最优值)及其相应的基态(对应于优化问题的最优解)。接下来,我们通过图的最大割问题 (Max-Cut problem)来展示 QAOA 算法的工作流程和原理。在解决最大割问题时,QAOA 在数学形式上等价于求解一个实对角矩阵 $H$ 的最小特征值及其对应的特征向量。\n",
"\n",
"和另外一种常见的变分量子特征求解器(VQE, Variational Quantum Eigensolver) 一样,QAOA 也是一种量子-经典混杂算法。 然而 QAOA 参数化量子电路的实现更简单,仅需两个可以调节参数的量子电路模块组成。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"# 示例\n",
"\n",
"## 1. Max-Cut 问题\n",
"\n",
"图的 Max-Cut 问题可以描述为:对于一个给定的包含 $N$ 个顶点 (nodes or vertices)和 $M$ 条边 (edges) 的无向图,找到一种分割方案将图的顶点集合分割成两个无交子集合 $S$ 和 $S^\\prime$,使得连接这两个顶点集合之间边的数目尽可能多。如图所示,我们考虑含4个顶点且具有环形结构的图: \n",
"\n",
" ![ring4.png](https://release-data.cdn.bcebos.com/PIC%2FMaxCut.png) \n",
"\n",
"Max-Cut 问题建模:在做分割时,若顶点 $i$ 属于集合 $S$ 时,赋值 $z_i=1$;若顶点 $j$ 属于 $S^\\prime$ 时,则令 $z_j=-1$。那么对于图的任意连接顶点 $(i, j)$ 的边则满足:若顶点属于同一集合 $S$ 或 $S^\\prime$ 时,$z_iz_j=1$; 若顶点分别属于不同集合时,$z_izj=-1$。于是 Max-Cut 问题转化为如下优化问题:\n",
"\n",
"$$ F=\\min_{z_i\\in\\{-1, 1\\}} z_1 z_2+z_2z_3+z_3z_4+z_4z_1.$$\n",
"\n",
"这里所有 $q_{ij}$ 均设置为 1,表示每条边的权重相等。该问题的所有可行解由比特串 $ \\boldsymbol{z}=z_1z_2z_3z_4 \\in \\{-1, 1\\}^4$ 组成,而且通常需要遍历所有比特串才能得到问题的最小值(最优解)。容易看出,比特串的数目是顶点数目 $N$ 的指数级别,即 $2^N$。因此,随着顶点数目的增加,搜索的代价也会呈指数级别增加。\n",
"\n",
"接下来,我们提供两种方法来预处理编码经典优化问题的图,即如何通过 Paddle Quantum 输入和可视化无权(或带权重)图:\n",
"\n",
"- 方法1是通过指定图的顶点和相应的边(及其权重)\n",
"- 方法2是通过直接输入图的邻接矩阵。"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"pycharm": {
"is_executing": false
}
},
"outputs": [],
"source": [
"def generate_graph(N, GRAPHMETHOD):\n",
" \"\"\"\n",
" It plots an N-node graph which is specified by Method 1 or 2.\n",
" \n",
" Args:\n",
" N: number of nodes (vertices) in the graph\n",
" METHOD: choose which method to generate a graph\n",
" Returns:\n",
" the specific graph and its adjacency matrix\n",
" \"\"\"\n",
" # Method 1 generates a graph by self-definition\n",
" if GRAPHMETHOD == 1:\n",
" print(\"Method 1 generates the graph from self-definition using EDGE description\")\n",
" graph = nx.Graph()\n",
" graph_nodelist=range(N)\n",
" graph.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 0)])\n",
" graph_adjacency = nx.to_numpy_matrix(graph, nodelist=graph_nodelist)\n",
" # Method 2 generates a graph by using its adjacency matrix directly\n",
" elif GRAPHMETHOD == 2:\n",
" print(\"Method 2 generates the graph from networks using adjacency matrix\")\n",
" graph_adjacency = np.array([[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 1, 0]])\n",
" graph = nx.Graph(graph_adjacency)\n",
" else:\n",
" print(\"Method doesn't exist \")\n",
"\n",
" return graph, graph_adjacency"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"这里指定方法1来预处理图:\n",
"\n",
"- 图的顶点数目 $N=4$\n",
"- 图的输入方法 GRAPHMETHOD = 1 \n",
"\n",
"注意:上述两种方法给出的图的顶点均从 $0$ 开始计数。"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"pycharm": {
"is_executing": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Method 1 generates the graph from self-definition using EDGE description\n",
"[[0. 1. 0. 1.]\n",
" [1. 0. 1. 0.]\n",
" [0. 1. 0. 1.]\n",
" [1. 0. 1. 0.]]\n"
]
},
{
"data": {
"image/png": "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\n",
"text/plain": [
"