{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Matrix product state simulation method\n",
    "\n",
    "## Simulation methods\n",
    "The `AerSimulator` has several simulation methods including `statevector`, `stabilizer`, `extended_stabilizer` and `matrix_product_state`. Each of these determines the internal representation of the quantum circuit and the algorithms used to process the quantum operations. They each have advantages and disadvantages, and choosing the best method is a matter of investigation.\n",
    "In this tutorial, we focus on the `matrix product state simulation method`.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Matrix product state simulation method\n",
    "This simulation method is based on the concept of `matrix product states`. This structure was initially proposed in the paper *Efficient classical simulation of slightly entangled quantum computations* by Vidal in https://arxiv.org/abs/quant-ph/0301063. There are additional papers that describe the structure in more detail, for example *The density-matrix renormalization group in the age of matrix product states* by Schollwoeck https://arxiv.org/abs/1008.3477."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A pure quantum state is usually described as a state vector, by the expression $|\\psi\\rangle =  \\sum_{i_1=0}^1 {\\ldots} \\sum_{i_n=0}^1 c_{i_1 \\ldots i_n} |i_i\\rangle {\\otimes} {\\ldots} {\\otimes} |i_n\\rangle$.\n",
    "\n",
    "The state vector representation implies an exponential size representation, regardless of the actual circuit. Every quantum gate operating on this representation requires exponential time and memory.\n",
    "\n",
    "The matrix product state (MPS) representation offers a local representation, in the form:\n",
    "$\\Gamma^{[1]} \\lambda^{[1]} \\Gamma^{[2]} \\lambda^{[2]}\\ldots \\Gamma^{[1]} \\lambda^{[n-1]} \\Gamma^{[n]}$, such that all the information contained in the $c_{i_1 \\ldots i_n}$, can be generated out of the MPS representation.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Every $\\Gamma^{[i]}$ is a tensor of complex numbers that represents qubit $i$. Every $\\lambda^{[i]}$ is a matrix of real numbers that is used to normalize the amplitudes of qubits $i$ and $i+1$. Single-qubit gates operate only on the relevant tensor. \n",
    "\n",
    "Two-qubit gates operate on consecutive qubits $i$ and $i+1$. This involves a tensor-contract operation over $\\lambda^{[i-1]}$, $\\Gamma^{[i-1]}$, $\\lambda^{[i]}$, $\\Gamma^{[i+1]}$ and  $\\lambda^{[i+1]}$, that creates a single tensor. We apply the gate to this tensor, and then decompose back to the original structure. This operation may increase the size of the respective tensors. Gates that involve two qubits that are not consecutive, require a series of swap gates to bring the two qubits next to each other and then the reverse swaps. \n",
    "\n",
    "In the worst case, the tensors may grow exponentially. However, the size of the overall structure remains 'small' for circuits that do not have 'many' two-qubit gates. This allows much more efficient operations in circuits with relatively 'low' entanglement. Characterizing when to use this method over other methods is a subject of current research."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Using the matrix product state simulation method\n",
    "The matrix product state simulation method is invoked in the `AerSimulator` by setting the simulation method. \n",
    "Other than that, all operations are controlled by the `AerSimulator` itself, as in the following example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-08-19T17:28:36.535599Z",
     "start_time": "2019-08-19T17:28:36.463583Z"
    },
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'11': 515, '00': 509}"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import numpy as np\n",
    "\n",
    "# Import Qiskit\n",
    "from qiskit import QuantumCircuit, transpile\n",
    "from qiskit_aer import AerSimulator\n",
    "\n",
    "# Construct quantum circuit\n",
    "circ = QuantumCircuit(2, 2)\n",
    "circ.h(0)\n",
    "circ.cx(0, 1)\n",
    "circ.measure([0,1], [0,1])\n",
    "\n",
    "# Select the AerSimulator from the Aer provider\n",
    "simulator = AerSimulator(method='matrix_product_state')\n",
    "\n",
    "# Run and get counts, using the matrix_product_state method\n",
    "tcirc = transpile(circ, simulator)\n",
    "result = simulator.run(tcirc).result()\n",
    "counts = result.get_counts(0)\n",
    "counts"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To see the internal state vector of the circuit we can use the `save_statevector` instruction. To return the full internal MPS structure we can also use the `save_matrix_product_state` instruction."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-08-19T17:28:31.715708Z",
     "start_time": "2019-08-19T17:28:31.646419Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'counts': {'0x0': 494, '0x3': 530},\n",
       " 'my_sv': array([0.70710678+0.j, 0.        +0.j, 0.        +0.j, 0.70710678+0.j]),\n",
       " 'my_mps': ([(array([[1.-0.j, 0.-0.j]]), array([[0.-0.j, 1.-0.j]])),\n",
       "   (array([[1.-0.j],\n",
       "           [0.-0.j]]),\n",
       "    array([[0.-0.j],\n",
       "           [1.-0.j]]))],\n",
       "  [array([0.70710678, 0.70710678])])}"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "circ = QuantumCircuit(2, 2)\n",
    "circ.h(0)\n",
    "circ.cx(0, 1)\n",
    "\n",
    "# Define a snapshot that shows the current state vector\n",
    "circ.save_statevector(label='my_sv')\n",
    "circ.save_matrix_product_state(label='my_mps')\n",
    "circ.measure([0,1], [0,1])\n",
    "\n",
    "# Execute and get saved data\n",
    "tcirc = transpile(circ, simulator)\n",
    "result = simulator.run(tcirc).result()\n",
    "data = result.data(0)\n",
    "\n",
    "#print the result data\n",
    "data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Running circuits using the matrix product state simulation method can be fast, relative to other methods. However, if we generate the state vector during the execution, then the conversion to state vector is, of course, exponential in memory and time, and therefore we don't benefit from using this method. We can benefit if we only do operations that don't require printing the full state vector. For example, if we run a circuit and then take measurement. The circuit below has 200 qubits. We create an `EPR state` involving all these qubits. Although this state is highly entangled, it is handled well by the matrix product state method, because there are effectively only two states. \n",
    "\n",
    "We can handle more qubits than this, but execution may take a few minutes. Try running a similar circuit with 500 qubits! Or maybe even 1000 (you can get a cup of coffee while waiting)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-08-19T17:29:38.913752Z",
     "start_time": "2019-08-19T17:29:04.306048Z"
    },
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Time taken: 0.31022214889526367 sec\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'11111111111111111111111111111111111111111111111111': 548,\n",
       " '00000000000000000000000000000000000000000000000000': 476}"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "num_qubits = 50\n",
    "circ = QuantumCircuit(num_qubits, num_qubits)\n",
    "\n",
    "# Create EPR state\n",
    "circ.h(0)\n",
    "for i in range (0, num_qubits-1):\n",
    "    circ.cx(i, i+1)\n",
    "\n",
    "# Measure\n",
    "circ.measure(range(num_qubits), range(num_qubits))\n",
    "\n",
    "tcirc = transpile(circ, simulator)\n",
    "result = simulator.run(tcirc).result()\n",
    "print(\"Time taken: {} sec\".format(result.time_taken))\n",
    "result.get_counts()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-08-19T17:39:56.328435Z",
     "start_time": "2019-08-19T17:39:56.320197Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<h3>Version Information</h3><table><tr><th>Qiskit Software</th><th>Version</th></tr><tr><td>Qiskit</td><td>0.25.0</td></tr><tr><td>Terra</td><td>0.17.0</td></tr><tr><td>Aer</td><td>0.8.0</td></tr><tr><td>Ignis</td><td>0.6.0</td></tr><tr><td>Aqua</td><td>0.9.0</td></tr><tr><td>IBM Q Provider</td><td>0.12.2</td></tr><tr><th>System information</th></tr><tr><td>Python</td><td>3.8.8 | packaged by conda-forge | (default, Feb 20 2021, 16:22:27) \n",
       "[GCC 9.3.0]</td></tr><tr><td>OS</td><td>Linux</td></tr><tr><td>CPUs</td><td>8</td></tr><tr><td>Memory (Gb)</td><td>31.38858413696289</td></tr><tr><td colspan='2'>Tue Apr 20 15:22:58 2021 UTC</td></tr></table>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div style='width: 100%; background-color:#d5d9e0;padding-left: 10px; padding-bottom: 10px; padding-right: 10px; padding-top: 5px'><h3>This code is a part of Qiskit</h3><p>&copy; Copyright IBM 2017, 2021.</p><p>This code is licensed under the Apache License, Version 2.0. You may<br>obtain a copy of this license in the LICENSE.txt file in the root directory<br> of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.<p>Any modifications or derivative works of this code must retain this<br>copyright notice, and modified files need to carry a notice indicating<br>that they have been altered from the originals.</p></div>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import qiskit\n",
    "qiskit.__version__\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "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.8"
  },
  "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
}
