{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Noise Transformation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Introduction\n",
    "\n",
    "This notebook shows how to use the Qiskit Aer utility functions `approximate_quantum_error` and `approximate_noise_model` to transform quantum noise channels into a different, more suitable, noise channel.\n",
    "\n",
    "Our guiding example is Clifford simulation. A Clifford simulator can efficiently simulate quantum computations which include gates only from a limited, non-universal set of gates (the Clifford gates). Not all quantum noises can be added to such simulations; hence, we aim to find a \"close\" noise channel which can be simulated in a Clifford simulator.\n",
    "\n",
    "We begin by importing the transformation functions from the Aer provider utilities"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-08-19T17:13:36.658036Z",
     "start_time": "2019-08-19T17:13:34.394912Z"
    }
   },
   "outputs": [],
   "source": [
    "from qiskit_aer.utils import approximate_quantum_error, approximate_noise_model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The name \"approximate\" suggests that these functions generate the closest (in the Hilbert-Schmidt metric) error possible to the given one.\n",
    "\n",
    "We demonstrate the approximation using several standard error channels defined in Qiskit."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-08-19T17:13:36.676647Z",
     "start_time": "2019-08-19T17:13:36.672883Z"
    }
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "# Import Aer QuantumError functions that will be used\n",
    "from qiskit_aer.noise import amplitude_damping_error, reset_error, pauli_error\n",
    "\n",
    "from qiskit.quantum_info import Kraus"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Overview\n",
    "\n",
    "A 1-qubit quantum channel is a function $\\mathcal{C}:\\mathbb{C}^{2\\times2}\\to\\mathbb{C}^{2\\times2}$ mapping density operators to density operators (to ensure the image is a density operator $\\mathcal{C}$ is required to be completely positive and trace preserving, **CPTP**).\n",
    "\n",
    "Given quantum channels $\\mathcal{E}_{1},\\dots,\\mathcal{E}_{r}$, and probabilities $p_1, p_2, \\dots, p_r$ such that $0\\le p_i \\le 1$ and $p_1+\\dots +p_r = 1$, a new quantum channel $\\mathcal{C}_\\mathcal{E}$ can be constructed such that $\\mathcal{C}_\\mathcal{E}(\\rho)$ has the effect of choosing the channel $\\mathcal{E}_i$ with probability $p_i$ and applying it to $\\rho$.\n",
    "\n",
    "The noise transformation function solves the following optimization problem: Given a channel $\\mathcal{C}$ (\"goal\") and a list of channels $\\mathcal{E}_{1},\\dots,\\mathcal{E}_{r}$, find the probabilities $p_1, p_2, \\dots, p_r$ minimizing $D(\\mathcal{C}, \\mathcal{C}_\\mathcal{E})$ according to some distance metric $D$ (the Hilbert-Schmidt metric is currently used).\n",
    "\n",
    "To ensure the approximation is honest, in the sense that the approximate error channel serves as an \"upper bound\" for the actual error channel, we add the additional honesty constraint:\n",
    "\n",
    "$$\\text{F}(I,\\mathcal{C})\\ge F(I,\\mathcal{C}_\\mathcal{E})$$\n",
    "\n",
    "Where $\\text{F}$ is a fidelity measure and $I$ is the identity channel."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example: Approximating amplitude damping noise with reset noise.\n",
    "\n",
    "**Amplitude damping** noise is described by a single parameter $0\\le \\gamma \\le 1$ and given by the Kraus operators:\n",
    "\n",
    "$$\\left(\\begin{array}{cc}\n",
    "1 & 0\\\\\n",
    "0 & \\sqrt{1-\\gamma}\n",
    "\\end{array}\\right),\\left(\\begin{array}{cc}\n",
    "0 & \\sqrt{\\gamma}\\\\\n",
    "0 & 0\n",
    "\\end{array}\\right)$$\n",
    "\n",
    "**Reset** error is described by probabilities $0\\le p, q\\le 1$ such that $p+q\\le 1$ and given by the Kraus operators:\n",
    "\n",
    "$$\\left(\\begin{array}{cc}\n",
    "\\sqrt{p} & 0\\\\\n",
    "0 & 0\n",
    "\\end{array}\\right),\\left(\\begin{array}{cc}\n",
    "0 & \\sqrt{p}\\\\\n",
    "0 & 0\n",
    "\\end{array}\\right),\\left(\\begin{array}{cc}\n",
    "0 & 0\\\\\n",
    "\\sqrt{q} & 0\n",
    "\\end{array}\\right),\\left(\\begin{array}{cc}\n",
    "0 & 0\\\\\n",
    "0 & \\sqrt{q}\n",
    "\\end{array}\\right)$$\n",
    "\n",
    "This can be thought of as \"resetting\" the quantum state of the affected qubit to $\\left|0\\right\\rangle$ with probability $p$, to $\\left|1\\right\\rangle$ with probability $q$, and do nothing with probability $1-(p+q)$.\n",
    "\n",
    "It is not too difficult to determine analytically the best values of $p,q$ to approximate a $\\gamma$ amplitude damping channel, see the details __[here](https://arxiv.org/abs/1207.0046)__. The best approximation is:\n",
    "\n",
    "$$p=\\frac{1}{2}\\left(1+\\gamma-\\sqrt{1-\\gamma}\\right), q=0$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-08-19T17:13:36.791986Z",
     "start_time": "2019-08-19T17:13:36.704432Z"
    }
   },
   "outputs": [],
   "source": [
    "gamma = 0.23\n",
    "error = amplitude_damping_error(gamma)\n",
    "results = approximate_quantum_error(error, operator_string=\"reset\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We only needed the above code to perform the actual approximation. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-08-19T17:13:36.812282Z",
     "start_time": "2019-08-19T17:13:36.805559Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "QuantumError on 1 qubits. Noise circuits:\n",
      "  P(0) = 0.8237482193044617, Circuit = \n",
      "   ┌───┐\n",
      "q: ┤ I ├\n",
      "   └───┘\n",
      "  P(1) = 0.17625178069553835, Circuit = \n",
      "        \n",
      "q: ─|0>─\n",
      "        \n",
      "  P(2) = 2.158685879252966e-23, Circuit = \n",
      "        ┌───┐\n",
      "q: ─|0>─┤ X ├\n",
      "        └───┘\n",
      "\n",
      "Expected results:\n",
      "P(0) = 0.8237482193696062\n",
      "P(1) = 0.17625178063039387\n",
      "P(2) = 0\n"
     ]
    }
   ],
   "source": [
    "print(results)\n",
    "\n",
    "p = (1 + gamma - np.sqrt(1 - gamma)) / 2\n",
    "q = 0\n",
    "\n",
    "print(\"\")\n",
    "print(\"Expected results:\")\n",
    "print(\"P(0) = {}\".format(1-(p+q)))\n",
    "print(\"P(1) = {}\".format(p))\n",
    "print(\"P(2) = {}\".format(q))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We got the results predicted analytically.\n",
    "\n",
    "## Different input types\n",
    "\n",
    "The approximation function is given two inputs: The error channel to approximate, and a set of error channels that can be used in constructing the approximation.\n",
    "\n",
    "The **error channel** to approximate can be given as any input that can be converted to the `QuantumError` object. \n",
    "\n",
    "As an example, we explicitly construct the Kraus matrices of amplitude damping and pass to the same approximation function as before:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-08-19T17:13:37.367331Z",
     "start_time": "2019-08-19T17:13:37.325594Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "QuantumError on 1 qubits. Noise circuits:\n",
      "  P(0) = 0.8237482193044623, Circuit = \n",
      "   ┌───┐\n",
      "q: ┤ I ├\n",
      "   └───┘\n",
      "  P(1) = 0.1762517806955376, Circuit = \n",
      "        \n",
      "q: ─|0>─\n",
      "        \n",
      "  P(2) = 6.463899246563026e-23, Circuit = \n",
      "        ┌───┐\n",
      "q: ─|0>─┤ X ├\n",
      "        └───┘\n"
     ]
    }
   ],
   "source": [
    "gamma = 0.23\n",
    "K0 = np.array([[1,0],[0,np.sqrt(1-gamma)]])\n",
    "K1 = np.array([[0,np.sqrt(gamma)],[0,0]])\n",
    "results = approximate_quantum_error(Kraus([K0, K1]), operator_string=\"reset\")\n",
    "print(results)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The **error operators** that are used to construct the approximating channel can be either given as a list, a dictionary or a string indicating hard-coded channels. \n",
    "\n",
    "Any channel can be either a list of Kraus operators, or 'QuantumError' objects.\n",
    "\n",
    "The identity channel does not need to be passed directly; it is always implicitly used.\n",
    "\n",
    "As an example, we approximate amplitude damping using an explicit Kraus representation for reset noises:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-08-19T17:13:38.083970Z",
     "start_time": "2019-08-19T17:13:38.046909Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "QuantumError on 1 qubits. Noise circuits:\n",
      "  P(0) = 0.8237482193044617, Circuit = \n",
      "   ┌───┐\n",
      "q: ┤ I ├\n",
      "   └───┘\n",
      "  P(1) = 0.17625178069553835, Circuit = \n",
      "   ┌───────┐\n",
      "q: ┤ kraus ├\n",
      "   └───────┘\n",
      "  P(2) = 2.158685879252966e-23, Circuit = \n",
      "   ┌───────┐\n",
      "q: ┤ kraus ├\n",
      "   └───────┘\n"
     ]
    }
   ],
   "source": [
    "reset_to_0 = Kraus([np.array([[1,0],[0,0]]), np.array([[0,1],[0,0]])])\n",
    "reset_to_1 = Kraus([np.array([[0,0],[1,0]]), np.array([[0,0],[0,1]])])\n",
    "reset_kraus = [reset_to_0, reset_to_1]\n",
    "\n",
    "gamma = 0.23\n",
    "error = amplitude_damping_error(gamma)\n",
    "results = approximate_quantum_error(error, operator_list=reset_kraus)\n",
    "print(results)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note the difference in the output channel: The probabilities are the same, but the input Kraus operators were converted to general Kraus channels, which cannot be used in a Clifford simulator. Hence, it is always better to pass a `QuantumError` object instead of the Kraus matrices, when possible."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-08-19T17:13:57.085790Z",
     "start_time": "2019-08-19T17:13:57.077225Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'1.1.0'"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import qiskit\n",
    "qiskit.__version__"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.12.3"
  },
  "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
  },
  "vscode": {
   "interpreter": {
    "hash": "f8729fd834348017bca17aea688b306f536a675180840f7307eb909fff39c285"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
