QDRIFT_EN.ipynb 31.9 KB
Newer Older
Q
Quleaf 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569
{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Hamiltonian Simulation with qDRIFT\n",
    "<em> Copyright (c) 2022 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Overview\n",
    "In quantum mechanics, the energy of the system is described by the Hamiltonian operator $H$, which determines the evolution of the system. So Hamiltonian simulation has great practical value in modeling complex chemical and physical systems. However, because the degree of freedom of the system increases exponentially with the increase of the system (such as the number of qubits), it is generally impossible to use classical computers to effectively simulate quantum systems. At present, the main technology of using quantum computers to simulate Hamiltonian is to use product formula method to simulate time evolution. This tutorial will introduce some basic theories and methods about product formula, and a random method named quantum stochastic drift protocol (qDRIFT) which is based on product formula. Then we give a code demonstration at the end of the article.\n",
    "\n",
    "\n",
    "## Product formula\n",
    "\n",
    "According to the basic axioms of quantum mechanics, the evolution of the system with Hamiltonian $H$ can be described by the following equation\n",
    "\n",
    "$$\n",
    "i \\hbar \\frac{d}{d t} | \\psi \\rangle = H | \\psi \\rangle,\n",
    "\\tag{1}\n",
    "$$\n",
    "\n",
    "where $\\hbar$ is the reduced Planck constant. Therefore, 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{2}\n",
    "$$\n",
    "\n",
    "Here we take the natural unit $\\hbar=1$, $U (t) $ as the time evolution operator. The core idea of using quantum circuits to simulate the time evolution process is to use the unitary transformation constructed by quantum circuits to simulate and approximate the time evolution operator. Seth Lloyd pointed out in his 1996 article that a whole evolution time of $t$ can be divided into $r$ shorter \"time blocks\" to reduce the error of simulation in time evolution[1]. Consider a general Hamiltonian form $H = \\sum_ {k=1}^{L} H_k$, of which $H_k$ is sub-Hamiltonian acting on a part of the system. We consider each sub-Hamiltonian $H_k$ whose evolution operator is $e^ {-i H_k t}$, and we can get $\\prod_{k=1}^{L} e^{-i H_k t}$ by simulating each sub-Hamiltonian in turn. Through Taylor expansion, it can be found that\n",
    "\n",
    "$$\n",
    "e^{-iHt} = \\prod_{k=1}^{L} e^{-i H_k t} + O(t^2).\n",
    "\\tag{3}\n",
    "$$\n",
    "\n",
    "Then let $\\tau = t/r$ and consider the evolution operator $\\left (e^ {-iH \\tau}\\right) ^r$, we can deduce that\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{4}\n",
    "$$\n",
    "\n",
    "This formula tells us that as long as the whole evolution time can be divided into enough \"fragments\", we can simulate with any high simulation accuracy. That is the basic idea of the product formula. However, what is given in (4) is only a rough estimate. If we want to estimate the depth of the quantum circuits required to achieve a certain simulation accuracy, we need to calculate its rigorous error upper bound. Specifically, we make $U_ {circuit}$ represent the circuit we construct, $\\Vert \\cdot \\Vert$ is the Schatten-$\\infty$ norm, that is, the [spectral norm](https://en.wikipedia.org/wiki/Schatten_norm). Then the simulation error $\\epsilon$ can be written as\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "\\epsilon\\left(e^{-iH\\tau}, U_{circuit}\\right)  & = \\Vert e^{-iH\\tau} - U_{circuit}\\Vert .\n",
    "\\end{aligned}\n",
    "\\tag{5}\n",
    "$$\n",
    "\n",
    "Next, we will show a brief calculation process of the upper bound of error. We give two conclusions (6) and (7) without proof, which will be used in proving (8). Interested readers can refer to section F.1 in [2] for details.\n",
    "\n",
    "$$\n",
    "\\left\\Vert \\mathcal{R}_k \\left( \\prod_{k=1}^{L} e^{-i H_k \\tau} \\right) \\right\\Vert\n",
    "\\leq\n",
    "\\mathcal{R}_k \\left( e^{\\vert \\tau \\vert   \\sum_{k=1}^{L} \\Vert H_k \\Vert } \\right),\n",
    "\\tag{6}\n",
    "$$\n",
    "\n",
    "$$\n",
    "\\vert \\mathcal{R}_k(e^\\alpha) \\vert \\leq \\frac{\\vert \\alpha \\vert^{k+1}}{(k+1)!}  e^{ \\vert \\alpha \\vert }, ~\n",
    "\\forall \\alpha \\in \\mathbb{C},\n",
    "\\tag{7}\n",
    "$$\n",
    "\n",
    "where $\\mathcal{R}_ k (f) $ is the remainder Taylor expansion to order $k$ of the function $f$, such as $\\mathcal{R}_1 (e^x)=\\mathcal{R}_1 (\\sum_{j=0}^\\infty \\frac{x^n}{n!})=\\sum_{j=2}^\\infty \\frac{x^n}{n!}$. \n",
    "Make $\\Lambda = \\max_ k \\Vert H_ k \\Vert$, considering the complete evolution time $t = r \\cdot \\tau$, the error of simulation in complete time $t$ is:\n",
    "$$\n",
    "\\begin{aligned}\n",
    "\\left \\Vert \\left ( e^{-i\\tau \\sum_{k=1}^L H_k  }\\right)^r - \\left (\\prod_{k=1}^{L} e^{-i H_k \\tau} \\right)^r \\right \\Vert \\leq &\n",
    "r \\left \\Vert e^{-i\\tau \\sum_{k=1}^L H_k } - \\prod_{k=1}^{L} e^{-i H_k \\tau } \\right \\Vert  \\\\\n",
    "=& r \\left \\Vert \\mathcal{R}_1 \\left(  e^{-i\\tau \\sum_{k=1}^L H_k} \\right)- \\mathcal{R}_1 \\left( \\prod_{k=1}^{L} e^{-i H_k \\tau } \\right) \\right \\Vert \\\\\n",
    "\\leq& r \\left \\Vert \\mathcal{R}_1 \\left(  e^{-i\\tau \\sum_{k=1}^L H_k} \\right) \\right \\Vert+ r\\left \\Vert \\mathcal{R}_1 \\left( \\prod_{k=1}^{L} e^{-i H_k \\tau } \\right) \\right \\Vert \\\\\n",
    "\\leq& 2r \\left \\Vert \\mathcal{R}_1 \\left(  e^{-i |\\tau | \\sum_{k=1}^L \\Vert H_k \\Vert} \\right) \\right \\Vert \\\\\n",
    "\\leq& 2r \\left \\Vert \\mathcal{R}_1 \\left(  e^{-i |\\tau | L \\Lambda} \\right) \\right \\Vert \\\\\n",
    "\\leq& r (  \\tau L \\Lambda )^2 e^{\\vert \\tau \\vert L \\Lambda } \\\\\n",
    "=&\\frac{(  t L \\Lambda )^2}{r} e^{\\frac{\\vert t \\vert L \\Lambda}{r} }.\n",
    "\\end{aligned}\n",
    "\\tag{8}\n",
    "$$\n",
    "\n",
    "The conclusion of linear accumulation of errors in quantum circuits is used here, that is, $\\Vert U^r - V^r \\Vert \\leq r\\Vert U - V \\Vert$. Readers who are not familiar with this conclusion can refer to section 4.5.3 in [3]; and also use the conclusion when $k=1$ in formula (7). So far, we have calculated the upper bound of the simulation error of the product formula for a complete evolution time $t$, that is, the second-order term $O (t^2/r)$ in equation (4).\n",
    "\n",
    "After obtaining the upper bound of the simulation error, the lower bound of the circuit depth required to reach a certain accuracy $\\epsilon$ can be calculated. From (8), we can find that the formula contains a $L$ term, which means that as the number of Hamiltonian terms increases, the upper bound of simulation error will become larger and larger, which will cause a deeper circuit if we need to control the accuracy. qDRIFT introduced in this tutorial optimizes this problem. qDRIFT focuses on the coefficients of the Hamiltonian itself and models it as a probability distribution. Each unitary gate is sampled from the probability distribution independently and repeated a certain number of times to form a quantum circuit. Finally, under a given accuracy, the depth of the quantum circuits will not explicitly contain the number of Hamiltonian items $L$. Now we will introduce it.\n",
    "\n",
    "\n",
    "## qDRIFT\n",
    "\n",
    "First, we give the form of the target Hamiltonian\n",
    "$$\n",
    "H=\\sum_{j=1}^L h_j H_j,\n",
    "\\tag{9}\n",
    "$$\n",
    "\n",
    "It contains $L$ sub-Hamiltonian $H_j$, note that here $H_j$ has been normalized, that is, $\\Vert H_j \\Vert = 1$, where $\\Vert\\cdot\\Vert$ is the Schatten-$\\infty$ norm. $h_j$ is the coefficient of each sub-Hamiltonian, which is a positive real number. Using these coefficients, we can construct a discrete probability distribution, and take the proportion of a single coefficient in the sum of the Hamiltonian coefficients as the probability of each unitary gate being sampled, which is $p_j =h_j / \\lambda $, where $\\lambda = \\sum_j h_j $ is the sum of the coefficients. Then the sampling will be repeated $ N $ times (to compared with product formula, we let $N=Lr$ here ), we will get an ordered list arranged by $j $ and can construct a unitary gate $U_j = e^{i\\tau H_j}$ according to the arrangement. Assuming $L = 3 $, $r = 2 $, we can sample an ordered list according to the above probability distribution, as shown in\n",
    "\n",
    "$$\n",
    "[ 3, 1, 2 ,3 ,3 ,1 ],\n",
    "$$\n",
    "\n",
    "then we can construct the quantum circuits as  \n",
    "\n",
    "$$\n",
    "U_{circuit} = e^{i\\tau H_1}e^{i\\tau H_3}e^{i\\tau H_3}e^{i\\tau H_2}e^{i\\tau H_1}e^{i\\tau H_3},\n",
    "$$\n",
    "\n",
    "$\\tau = t \\lambda / N$. That is an implementation of qDRIFT to simulate Hamiltonian.\n",
    "\n",
    "The implementation process of qDRIFT is very simple, and its advantage is that the complexity of the number of unitary gates is $O ((\\lambda t) ^ 2 / \\epsilon) $) when the target precision is $\\epsilon $. It can be seen that this is a result without $L$. In other words, the number of unitary gates is not explicitly related to the number of Hamiltonian terms, which can effectively reduce the length of the simulated circuit when the number of Hamiltonian terms is large. Next, we will give a proof.\n",
    "\n",
    "We model the process of sampling from probability distribution as a quantum channel. We use the curlicue letters $\\mathcal{E} $ and $\\mathcal{U}$ to represent the channel established through qDRIFT and the channel to be simulated, and use $\\mathcal{E}_ N $ and $\\mathcal{U}_N$ to represent one of the $n $ actions of their respective channels on the quantum state $\\rho $,\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "&\\mathcal{U}_N (\\rho) = e^{\\frac{it}{N}H} \\rho e^{\\frac{-it}{N}H}= e^{\\frac{t}{N}\\mathcal{L}} (\\rho),\n",
    "\\\\\n",
    "&\\mathcal{E}_N (\\rho)=p_j e^{i\\tau H_j} \\rho e^{-i\\tau H_j}=p_j e^{\\tau \\mathcal{L}_j}(\\rho).\n",
    "\\end{aligned}\n",
    "\\tag{10}\n",
    "$$\n",
    "\n",
    "Here we introduce the Liouvillian representation, that is, for the quantum channel $\\mathcal{P} (\\rho) = e^{iHt} \\rho e^{-iHt}$, there is\n",
    "$$\n",
    "\\mathcal{P}(\\rho)=e^{iHt}\\rho e^{-iHt}=e^{t\\mathcal{L}}(\\rho)=\\sum_{k=0}^\\infty \\frac{t^k \\mathcal{L}^k (\\rho)}{k!},\n",
    "\\tag{11}\n",
    "$$\n",
    "\n",
    "where $\\mathcal{L} (\\rho) =i (H\\rho - \\rho H) $, similarly, $\\mathcal{L}_ j(\\rho)=i(H_j\\rho - \\rho H_j)$. It should be noted that the operation rules of its series follow $\\mathcal{L}^ {n+1} (\\rho) =i (H\\mathcal{L}^n (\\rho) -\\mathcal{L}^n (\\rho) H) $. Specifically, $\\mathcal{U}_N = \\sum_{n=0}^\\infty \\frac{t^n\\mathcal{L}^n}{n!N^n}$, $\\mathcal{E}_N =\\sum_{j}p_j \\sum_{n=0}^\\infty \\frac{\\lambda^n t^n \\mathcal{L}_j^n}{n!N^n}$. Next, how do we measure the distance between two channels? Here we introduce the definition of [diamond norm](https://en.wikipedia.org/wiki/Diamond_norm)\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "\\Vert \\mathcal{P} \\Vert_\\Diamond :=\\sup_{\\rho ; \\Vert \\rho \\Vert _1 =1}\\Vert (\\mathcal{P} \\otimes \\mathbb{I})(\\rho )\\Vert _1 .\n",
    "\\end{aligned}\n",
    "\\tag{12}\n",
    "$$\n",
    "Here $\\mathbb {I} $ is the identity channel which has the same size as $\\mathcal{P}$ and $\\Vert \\cdot \\Vert$ is Schatten-$1$ norm or called [trace norm](https://en.wikipedia.org/wiki/Schatten_norm). We use the diamond norm to define the distance between two quantum channels\n",
    "$$\n",
    "\\begin{aligned}\n",
    "d_\\Diamond (\\mathcal{E},\\mathcal{U}) &=\\frac{1}{2} \\Vert \\mathcal{E} -\\mathcal{U} \\Vert_\\Diamond\n",
    "\\\\\n",
    "&=\\sup_{\\rho ; \\Vert \\rho \\Vert _1 =1} \\frac{1}{2} \\Vert ((\\mathcal{E}-\\mathcal{U}) \\otimes \\mathbb{I})(\\rho )\\Vert _1 .\n",
    "\\end{aligned}\n",
    "\\tag{13}\n",
    "$$\n",
    "The diamond norm represents the maximum possibility that the two channels can be distinguished in all quantum states. The larger its value, the greater the possibility that the two channels can be distinguished, which means that the two channels are far away and the simulation ability is poor; on the contrary, if its value is small, it means that the simulation ability is good. Next, we can calculate the upper bound of the distance of the channel with a single action of the channel\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "    \\Vert \\mathcal{U}_N-\\mathcal{E}_N \\Vert_\\Diamond &= \\left\\Vert \\sum_{n=2}^\\infty \\frac{t^n\\mathcal{L}^n}{n!N^n}-\\sum_{j}\\frac{h_j}{\\lambda} \\sum_{n=2}^\\infty \\frac{\\lambda^n t^n \\mathcal{L}_j^n}{n!N^n} \\right\\Vert_\\Diamond\\\\\n",
    "    &\\leq \\sum_{n=2}^\\infty \\frac{t^n\\Vert \\mathcal{L}^n \\Vert_\\Diamond }{n!N^n} + \\sum_{j}\\frac{h_j}{\\lambda} \\sum_{n=2}^\\infty \\frac{\\lambda^n t^n \\Vert\\mathcal{L}_j^n \\Vert_\\Diamond }{n!N^n}\\\\\n",
    "    &\\leq \\sum_{n=2}^\\infty \\frac{1}{n!}\\left( \\frac{2\\lambda t}{N}\\right)^n+\\sum_{j}\\frac{h_j}{\\lambda} \\sum_{n=2}^\\infty \\frac{1}{n!}\\left( \\frac{2\\lambda t}{N}\\right)^n\\\\\n",
    "    &=2\\sum_{n=2}^\\infty \\frac{1}{n!}\\left( \\frac{2\\lambda t}{N}\\right)^n .\n",
    "\\end{aligned}\n",
    "\\tag{14}\n",
    "$$\n",
    "\n",
    "The conclusion $\\Vert \\mathcal{L} \\Vert_ \\Diamond \\leq 2\\Vert H\\Vert \\leq 2\\lambda$ is used here. Similarly, $\\Vert \\mathcal{L}_ j \\Vert_ \\Diamond \\leq 2\\Vert H_ j\\Vert \\leq 2$ [4]. Then, we can use the conclusion of (7) mentioned above to make $k=1$, $\\alpha=2 \\lambda t /N$, and then we can get\n",
    "\n",
    "$$\n",
    "d_\\Diamond (\\mathcal{U}_N,\\mathcal{E}_N) \\leq \\frac{2\\lambda^2 t^2}{N^2} e^{2\\lambda t/N} .\n",
    "\\tag{15}\n",
    "$$\n",
    "\n",
    "Then using the conclusion of $\\Vert U^r - V^r \\Vert \\leq r\\Vert U - V \\Vert$ again (it should be noted that $U$ and $V$ in the formula are linear operators, but quantum channels $\\mathcal{U}_N$ and $\\mathcal{E}_N$ are still feasible. Refer to Chapter 3.3.2 in [6] to get more details.). With $2\\lambda t \\ll N$ generally, we can deduce\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "d_\\Diamond (\\mathcal{U},\\mathcal{E}) &\\leq N d_\\Diamond (\\mathcal{U}_N, \\mathcal{E}_N)\\\\\n",
    "    &=\\frac{2\\lambda^2 t^2}{N} e^{2\\lambda t/N} \\approx \\frac{2\\lambda^2 t^2}{N}.\n",
    "\\end{aligned}\n",
    "\\tag{16}\n",
    "$$\n",
    "\n",
    "So $N \\sim O ((\\lambda t) ^2 /\\epsilon) $. It can be seen from the above formula that under the condition of $\\lambda \\ll \\Lambda L $ (Hamiltonian is defined as $H=\\sum_{j=1}^L h_j H_j$ in qDRIFT, so $\\Lambda = \\max_k h_k $ here), the distance will not be explicitly related to $L$, which means that when $L$ is large, that is, the situation is complex, the quantum circuit will not be increased, and the number of unitary gates can be effectively controlled. Many systems satisfy the condition $\\lambda \\ll \\Lambda L$ such as carbon-dioxide, ethane. But not all cases satisfy. If $\\lambda = \\Lambda L$ or $\\lambda = \\Lambda \\sqrt{L}$, their upper bounds are $O (L^2 (\\Lambda t) ^2 /\\epsilon) $ and $O (L (\\Lambda t) ^2 /\\epsilon) $ respectively, we can see that they still increase with the number of Hamiltonian terms. Interested readers can refer to [4] for more details.\n",
    "\n",
    "\n",
    "## Code Demonstration\n",
    "We will implement qDRIFT in combination with code. We will first demonstrate the performance of its sampling results, and then calculate the simulation error of its channel. First, we need to import the required packages."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings\n",
    "\n",
    "import math\n",
    "import numpy as np                    \n",
    "import scipy                                          \n",
    "import paddle_quantum as pq         \n",
    "import paddle\n",
    "\n",
    "warnings.filterwarnings(\"ignore\")   # Hide warnings\n",
    "np.set_printoptions(suppress=True,linewidth=np.nan)        # Enable full display, so that line breaks would not appear\n",
    "                                                           # when viewing the matrix on the terminal print\n",
    "pq.set_backend('density_matrix')    # use density matrix representation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We assume that the system consists of two qubits. We can use the `hamiltonian` module of the  Paddle Quantum to construct a Hamiltonian with $L=4$ satisfied $\\lambda \\ll \\Lambda L$ to demonstrate, which is our target Hamiltonian, as follows\n",
    "$$\n",
    "\\begin{aligned}\n",
    "H&=I \\otimes X + 0.05 * X \\otimes Z + 0.05 * I \\otimes Y+0.05 * X \\otimes X   .\n",
    "\\end{aligned}\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Target Hamiltonian is :\n",
      " [[ 0.  +0.j    1.  -0.05j  0.05+0.j    0.05+0.j  ]\n",
      " [ 1.  +0.05j  0.  +0.j    0.05+0.j   -0.05+0.j  ]\n",
      " [ 0.05+0.j    0.05+0.j    0.  +0.j    1.  -0.05j]\n",
      " [ 0.05+0.j   -0.05+0.j    1.  +0.05j  0.  +0.j  ]]\n"
     ]
    }
   ],
   "source": [
    "qubits = 2  # Set the number of qubits\n",
    "H_j = [(1.0, 'I0,X1'),  # The Pauli string of target Hanmiltonian\n",
    "       (0.05, 'X0,Z1'),\n",
    "       (0.05, 'I0,Y1'),\n",
    "       (0.05, 'X0,X1'), ]\n",
    "\n",
    "H = pq.hamiltonian.Hamiltonian(H_j)    \n",
    "print(f'Target Hamiltonian is :\\n {H.construct_h_matrix(qubit_num=qubits)}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, we calculate probability according to $\\lambda = \\sum_ j h_ j$,$ p_ j=h_j/\\lambda $. In this experiment, we assume that our target accuracy $\\epsilon=0.1$, simulation time $t=1$, that is, we need to sample $n=\\lceil \\frac{2\\lambda^2 t^2} {\\epsilon}\\rceil = 27 $ times."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "To meet the accuracy of 0.1, there are 27 unitary gates needed.\n"
     ]
    }
   ],
   "source": [
    "h_j = np.array(H.coefficients)  # Get coeffcients\n",
    "lamda = h_j.sum()\n",
    "p_j = h_j/lamda  # Calculate the discrete probability distribution\n",
    "accuracy = 0.1\n",
    "t = 1\n",
    "gate_counts = math.ceil(2 * lamda**2 * t**2 / accuracy)\n",
    "\n",
    "print(f'To meet the accuracy of {accuracy}, there are {gate_counts} unitary gates needed.')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, we will sample 27 times independently from the probability distribution $p_j$ and construct a unitary circuit according to the sample results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Sample results:\n",
      " [1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 4 1]\n",
      "The simulation circuit matrix of qDRIFT is: \n",
      " [[ 0.51998969-0.02531459j  0.03408183+0.85099285j -0.03524884+0.02376227j -0.0353899 +0.02366261j]\n",
      " [-0.03408183+0.85099285j  0.51998969+0.02531459j  0.0353899 +0.02366261j -0.03524884-0.02376227j]\n",
      " [-0.03524884+0.02376227j -0.0353899 +0.02366261j  0.51998969-0.02531459j  0.03408183+0.85099285j]\n",
      " [ 0.0353899 +0.02366261j -0.03524884-0.02376227j -0.03408183+0.85099285j  0.51998969+0.02531459j]] \n",
      "The original circuit matrix is: \n",
      " [[ 0.53752508-0.00075235j  0.04202098+0.83966719j -0.04201839+0.04202098j -0.00075235+0.02697398j]\n",
      " [-0.04202098+0.83966719j  0.53752508+0.00075235j  0.00075235+0.02697398j -0.04201839-0.04202098j]\n",
      " [-0.04201839+0.04202098j -0.00075235+0.02697398j  0.53752508-0.00075235j  0.04202098+0.83966719j]\n",
      " [ 0.00075235+0.02697398j -0.04201839-0.04202098j -0.04202098+0.83966719j  0.53752508+0.00075235j]]\n"
     ]
    }
   ],
   "source": [
    "np.random.seed(666)  # Fix the random seed to demonstrate\n",
    "sample_list = np.random.choice(a=range(1, 5), size=gate_counts, replace=True, p=p_j)\n",
    "print(f'Sample results:\\n {sample_list}')\n",
    "\n",
    "# Calculate the sampled unitary circuit according to the sample result\n",
    "simulation = np.identity(2 ** qubits)  # Generate the identity matrix\n",
    "tau = 1j*lamda*t/gate_counts\n",
    "for i in sample_list:\n",
    "    pauli_str_j = (1.0, H_j[i-1][1])   # Get H_ j. Note that its original coefficient should be discarded\n",
    "    H_i = pq.hamiltonian.Hamiltonian([pauli_str_j]).construct_h_matrix(qubit_num=qubits)\n",
    "    simulation = np.matmul(scipy.linalg.expm(tau*H_i), simulation)  \n",
    "origin = scipy.linalg.expm(1j*t*H.construct_h_matrix(qubit_num=qubits))  # Calculate the original circuit of the target Hamiltonian\n",
    "print(f'The simulation circuit matrix of qDRIFT is: \\n {simulation} \\nThe original circuit matrix is: \\n {origin}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then we can calculate the simulation error $\\Vert e^{iHt}-U_{circuit} \\Vert $ between the unitary circuit sampled from qDRIFT and the original circuit, note that the norm here is the spectral norm."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Simulation error: 0.0309\n"
     ]
    }
   ],
   "source": [
    "distance = 0.5*np.linalg.norm(origin-simulation, ord=2)\n",
    "print(f'Simulation error: {distance:.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Of course, we can take a specific quantum state to test. Without losing generality, we assume that the initial quantum state is a zero state, that is, $\\rho(0)  = | 0 \\rangle \\langle 0 | $. We can let the quantum state evolve through the original circuit and the simulation circuit respectively. At the time of $t $, the quantum state is $\\rho(t)_{origin}$ and $\\rho(t)_{qDRIFT}$, we can use the fidelity between these two quantum states to measure the effect of simulation circuits."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Initial State: \n",
      " [[1.+0.j 0.+0.j 0.+0.j 0.+0.j]\n",
      " [0.+0.j 0.+0.j 0.+0.j 0.+0.j]\n",
      " [0.+0.j 0.+0.j 0.+0.j 0.+0.j]\n",
      " [0.+0.j 0.+0.j 0.+0.j 0.+0.j]]\n",
      "The fidelity between two states is 0.9989\n"
     ]
    }
   ],
   "source": [
    "rho_0 = pq.state.zero_state(qubits).numpy() # Generate the zero state density matrix\n",
    "print(f'Initial State: \\n {rho_0}')\n",
    "\n",
    "rho_t_origin = pq.state.to_state(origin @ rho_0 @ origin.T.conjugate())  # Evolve through the original circuit\n",
    "rho_t_qdrift = pq.state.to_state(simulation @ rho_0 @ simulation.T.conjugate())  # Evolve through the simulation circuit\n",
    "fidelity = pq.qinfo.state_fidelity(rho_t_origin, rho_t_qdrift)\n",
    "print(f'The fidelity between two states is {float(fidelity):.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It can be found that the above tests meet our accuracy requirements. However, different from a sampled unitary circuit, we regard the qDRIFT sampling method as a quantum channel, that is, a mapping of the quantum state $\\rho $. The above experiment is only a specific instance of this channel. Next, we will analyze the performance of this channel. First, we can define a function to describe the qDRIFT channel."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define qDRIFT channel\n",
    "def qdrift_channel(iter_num, sample_num, hamiltonian_list, coefficient_list, simulation_time, qubits, input_state):\n",
    "    '''\n",
    "    Input :\n",
    "        iter_num : the current number of iterations, as a label of recursion\n",
    "        sample_num : number of samples, N\n",
    "        hamiltonian_list : the list of Pauli string of the target Hamiltonian, H_j\n",
    "        coefficient_list : the coefficient list of sub-Hamiltonian, h_j\n",
    "        simulation_time : simulation time, t\n",
    "        qubits : the number of qubits \n",
    "        input_state : the input quantum state, which should be a density matrix\n",
    "    \n",
    "    Return :\n",
    "        The quantum state aftre the evolution of this channel, represented in density matrix\n",
    "    '''\n",
    "    lamda = coefficient_list.sum() \n",
    "    tau = lamda*simulation_time/sample_num\n",
    "    output = 0\n",
    "\n",
    "    if iter_num != 1:   # Enable recursion when iteration flag is not 1\n",
    "        input_state = qdrift_channel(iter_num-1, sample_num, hamiltonian_list,\n",
    "                                     coefficient_list, simulation_time, qubits, input_state)\n",
    "\n",
    "    # Calculate e^{iH\\tau} \\rho e^{-iH\\tau}                                 \n",
    "    for sub_H, sub_h in zip(hamiltonian_list, coefficient_list):\n",
    "        sub_H = pq.hamiltonian.Hamiltonian([sub_H]).construct_h_matrix(qubit_num=qubits)\n",
    "        unitary = scipy.linalg.expm(1j*tau*sub_H)  # Calculate e^{iH\\tau}\n",
    "        output += sub_h/lamda*unitary @ input_state @ unitary.conjugate().T\n",
    "    return output"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then we can calculate the distance between the two channels through the diamond norm, but the solution of the diamond norm is a positive semidefinite programming problem, that is\n",
    "\n",
    "$$\n",
    "d_\\Diamond(\\mathcal{U}- \\mathcal{E})=\\sup_{\\Omega \\geq 0 \\atop \\rho \\geq 0}\\{\\text{Tr}[\\Omega (\\Gamma_\\mathcal{U}-\\Gamma_\\mathcal{E})]: \\Omega \\leq \\rho \\otimes \\mathbb{I},\\text{Tr} (\\rho)=1\\},\n",
    "\\tag{17}\n",
    "$$\n",
    "where $\\Gamma_ \\mathcal{U} $ and $\\Gamma_ \\mathcal{E}$ are Choi representations of the original channel and the simulation channel. There are many forms of positive semidefinite programming and Choi representation of diamond norm, and interested readers can read [6-8] for more details. The Choi representation we use here is:\n",
    "$$\n",
    "\\Gamma_\\mathcal{P}=\\sum_{i,j=0}^{d-1} |i\\rangle \\langle j| \\otimes \\mathcal{P}(|i\\rangle \\langle j|),\n",
    "\\tag{18}\n",
    "$$\n",
    "where $\\mathcal {P} $ is the quantum channel and $d$ is the dimension of the input quantum state of the quantum channel. Here we first calculate the Choi representation of the two channels."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "# There is how to calculate the Choi representation of the original channel and the qDRIFT channel, \n",
    "# and the diamond norm can be calculated under this representation\n",
    "choi_qdrift = 0\n",
    "choi_origin = 0\n",
    "channel = scipy.linalg.expm(1j*t*H.construct_h_matrix(qubit_num=qubits))\n",
    "for i in range(2 ** qubits):\n",
    "    for k in range(2 ** qubits):\n",
    "        choi_temp = np.zeros((2 ** qubits, 2 ** qubits))\n",
    "        choi_temp[i][k] = 1  # Generate |i\\rangle \\langle k|\n",
    "\n",
    "        # Calculate the Choi matrix of channel E\n",
    "        # Calculate \\mathcal{E}(|i\\rangle \\langle k|)\n",
    "        choi_temp_qdrift = qdrift_channel(gate_counts, gate_counts, H_j, h_j, t, qubits, choi_temp)  \n",
    "        # Calculate |i\\rangle \\langle k| \\otimes \\mathcal{E}(|i\\rangle \\langle k|)\n",
    "        choi_qdrift += np.kron(choi_temp, choi_temp_qdrift)\n",
    "\n",
    "        # Calculate the Choi matrix of channel U\n",
    "        # Calculate \\mathcal{U}(|i\\rangle \\langle k|)\n",
    "        choi_temp_origin = channel @ choi_temp @ channel.T.conjugate()\n",
    "        # Calculate |i\\rangle \\langle k| \\otimes \\mathcal{U}(|i\\rangle \\langle k|)\n",
    "        choi_origin += np.kron(choi_temp, choi_temp_origin)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then we can calculate the diamond norm according to formula (17) and find the diamond distance of the two channels. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The distance between the two channels is: 0.0764\n"
     ]
    }
   ],
   "source": [
    "diamond_distance = 0.5 * pq.qinfo.diamond_norm(paddle.to_tensor(choi_origin-choi_qdrift))\n",
    "print(f'The distance between the two channels is: {diamond_distance:.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The calculation results are in line with our expectations. Note that this value represents the expectance of the worst performance of the channel sampling for a specific simulation circuit instance, so it can not guarantee that each sampled circuit can achieve this accuracy."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Summary\n",
    "Quantum simulation itself is a relatively broad topic, and its application is also very extensive. This tutorial introduces the basic theory of product formula and the qDRIFT method, but qDRIFT is not the only method for random product formulas. As a branch of the method of quantum simulation using product formula, random product formula has many methods worth exploring."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## Reference\n",
    " \n",
    "[1] Lloyd, Seth. \"Universal quantum simulators.\" [Science (1996): 1073-1078](https://www.jstor.org/stable/2899535).\n",
    "\n",
    "[2] 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",
    "[3] Nielsen, Michael A., and Isaac Chuang. \"Quantum computation and quantum information.\" (2002): 558-559.\n",
    "\n",
    "[4] Campbell, E. . \"Random Compiler for Fast Hamiltonian Simulation.\" [Physical Review Letters 123.7(2019):070503.1-070503.5](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.123.070503).\n",
    "\n",
    "[5] Khatri, Sumeet, and Mark M. Wilde. \"Principles of quantum communication theory: A modern approach.\" [arXiv preprint arXiv:2011.04672 (2020).](https://arxiv.org/abs/2011.04672)\n",
    "\n",
    "[6] Watrous, J. . [The Theory of Quantum Information](https://cs.uwaterloo.ca/~watrous/TQI/).  2018.\n",
    "\n",
    "[7] Watrous, J. . \"Simpler semidefinite programs for completely bounded norms.\" [Chicago Journal of Theoretical Computer Science (2012).](https://arxiv.org/abs/1207.5726)\n",
    "\n",
    "[8] Watrous, J. . \"Semidefinite Programs for Completely Bounded Norms.\" [Theory of Computing 5.1(2009):217-238.](https://arxiv.org/abs/0901.4709)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3.7.13 ('py3.7_pq2.2.1')",
   "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.13"
  },
  "vscode": {
   "interpreter": {
    "hash": "4e4e2eb86ad73936e915e7c7629a18a8ca06348106cf3e66676b9578cb1a47dd"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}