{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Simulating Pauli noise and Qubit Loss\n",
    "This notebook shows how to run simulations with Pauli noise, such as bit-flip or depolarizing noise, as well as qubit loss.\n",
    "\n",
    "First, make sure prerequisites are available. The package and extra `qdk[jupyter]` must be already installed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from qdk import qsharp\n",
    "from qdk.widgets import Histogram"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Simulation with noise\n",
    "\n",
    "Define a simple program that creates a Bell state on two qubits and measures both qubits."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "qsharp"
    }
   },
   "outputs": [],
   "source": [
    "%%qsharp\n",
    "\n",
    "operation BellPair() : Result[] {\n",
    "    use q = Qubit[2];\n",
    "    H(q[0]);\n",
    "    CNOT(q[0], q[1]);\n",
    "    MResetEachZ(q)\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Run 20 shots without noise and display results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "results = qsharp.run(\"BellPair()\", 20)\n",
    "results"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that measurements always agree within a shot as expected. Now run 20 shots of the same program with 10% [depolarizing noise](https://en.wikipedia.org/wiki/Quantum_depolarizing_channel). Depolarizing noise is applied to each gate and each measurement."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "results = qsharp.run(\"BellPair()\", 20, noise=qsharp.DepolarizingNoise(0.1))\n",
    "results"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that measurements do not always agree within the shot."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Histograms\n",
    "\n",
    "Define a program to prepare a cat state on five qubits and measure each qubit."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "qsharp"
    }
   },
   "outputs": [],
   "source": [
    "%%qsharp\n",
    "\n",
    "operation Cat5() : Result[] {\n",
    "    use q = Qubit[5];\n",
    "    H(q[0]);\n",
    "    ApplyCNOTChain(q);\n",
    "    MResetEachZ(q)\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First, run this program without noise. Roughly half of the outcomes should be $\\ket{00000}$ and another half should be $\\ket{11111}$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "result = qsharp.run(\"Cat5()\", 1000)\n",
    "Histogram(result)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, run the same program with bit-flip noise of 1%, 5%, 10%, 25%."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for p in [0.01, 0.05, 0.1, 0.25]:\n",
    "    result = qsharp.run(\"Cat5()\", 1000, noise=qsharp.BitFlipNoise(p))\n",
    "    display(f\"Noise probability = {p}\")\n",
    "    display(Histogram(result))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can see that with 1% noise, cat state can still be clearly seen, but when noise approaches 25%, the cat state is indistinguishable from noise."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Arbitrary Pauli noise\n",
    "\n",
    "Standard bit-flip, phase-flip, and [depolarizing](https://en.wikipedia.org/wiki/Quantum_depolarizing_channel) noise are available, but arbitrary Pauli noise is also possible. The following example runs the same Cat5 program. First it applies noise with 20% probability (bit-flip half the time and phase-flip half the time). In a second experiment it applies Pauli-Y noise with 10% probability."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "result = qsharp.run(\"Cat5()\", 1000, noise=(0.1, 0.0, 0.1))\n",
    "display(Histogram(result))\n",
    "result = qsharp.run(\"Cat5()\", 1000, noise=(0.0, 0.1, 0.0))\n",
    "display(Histogram(result))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Simulating with Qubit Loss\n",
    "\n",
    "For some qubit modalities, a qubit may undergo \"loss\" during execution. This means the physical system backing the qubit is unable to be measured or operated on. To test behaviors of an algorithm in the presence of qubit loss, you can use the `qubit_loss` parameter to `qsharp.run` and set the probability of a qubit being lost on each operation. Qubit loss is reported only at measurement time, where a special `Loss` value is returned that is neither `One` nor `Zero`. During simulation, a lost qubit is no longer acted on by gates and remains in the $\\ket{0}$ state until it is measured or reset, at which point the simulation reloads a fresh qubit to support future operations.\n",
    "\n",
    "In this example, we'll set the qubit loss probability to high 50% to ensure some qubits are lost during simulation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "result = qsharp.run(\"BellPair()\", 100, qubit_loss=0.5)\n",
    "display(Histogram(result))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that the `Loss` value is not usable inside of the simulation, and any comparisons on that value will trigger a runtime failure. To avoid this failure, use `IsLossResult` to check whether the result value corresponds to qubit loss before using it in any branching logic:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "qsharp"
    }
   },
   "outputs": [],
   "source": [
    "%%qsharp\n",
    "\n",
    "operation CheckForLoss() : (Bool, Bool) {\n",
    "    use q = Qubit();\n",
    "    H(q);\n",
    "    let res = MResetZ(q);\n",
    "    if IsLossResult(res) {\n",
    "        return (true, false);\n",
    "    } elif res == One {\n",
    "        return (false, true);\n",
    "    } else {\n",
    "        return (false, false);\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Histogram(qsharp.run(\"CheckForLoss()\", 100, qubit_loss=0.5))"
   ]
  }
 ],
 "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.11.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
