{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Simulating noise on Amazon Braket" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# Use Braket SDK Cost Tracking to estimate the cost to run this example\n", "from braket.tracking import Tracker\n", "t = Tracker().start()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This notebook gives a detailed overview of noise simulations on Amazon Braket. Amazon Braket provides two noise simulators: a local noise simulator that you can use for free as part of the Braket SDK and an on-demand, high-performing noise simulator, DM1. Both simulators are based on the density matrix formalism. After this tutorial, you will be able to define noise channels, apply noise to new or existing circuits, and run those circuits on the Braket noise simulators. \n", "\n", "### Table of contents:\n", "* [Background](#Background)\n", " * [Noise simulation based on the density matrix formalism](#density_matrix)\n", " * [Quantum channel and Kraus representation](#quantum_channel)\n", "* [General imports](#imports)\n", "* [Quick start](#start)\n", "* [Defining noise channels](#noise_channels)\n", " * [Pre-defined noise channels](#pre-defined)\n", " * [Defining custom noise channels](#self-defined)\n", "* [Adding noise to a circuit](#apply_noise)\n", " * [Build noisy circuits bottom-up](#apply_noise_directly)\n", " * [Applying noise to existing circuits with global methods](#apply_noise_globally)\n", " * [Applying gate noise to the circuit](#gate-noise)\n", " * [Applying initialization noise to the circuit](#initialization-noise)\n", " * [Applying readout noise to the circuit](#readout-noise)\n", " * [Using both the direct and global methods to apply noise](#both)\n", "* [Running a noisy circuit](#run)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Background \n", "\n", "### Noise simulation based on the density matrix formalism \n", "In an ideal case, a quantum state prepared by a noise-free circuit can be described by a state vector $|\\psi\\rangle$ -- we call it a 'pure state'. However, the presence of noise in realistic quantum devices will introduce classical uncertainty to the quantum state. For example, a bit flip error with 50% probability acting on a qubit flips the $|0\\rangle$ state into either $|0\\rangle$ or $|1\\rangle$ with a 50-50 chance. Note that this is different from an Hadamard-gate acting on $|0\\rangle$: The latter results in a coherent superposition of $|0\\rangle$ and $|1\\rangle$, whereas the former is a classical, so-called mixture of $|0\\rangle$ and $|1\\rangle$. The most general way of describing a quantum state in the presence of noise is through the so-called density matrix: $\\rho = \\sum_i p_i|\\psi_i\\rangle\\langle\\psi_i|$. It can be understood as a classical mixture of a series of pure states $|\\psi_i\\rangle$ (each of which could be highly entangled), where $p_i$ is the probability of the state being in $|\\psi_i\\rangle$. Because the $p_i$ are classical probabilities they have to sum up to 1: $\\sum_i p_i = 1$. The density matrix of a pure state is simply $\\rho = |\\psi\\rangle\\langle\\psi|$ and, in the bit-flip example from above, the density matrix would be $\\rho = 0.5|0\\rangle\\langle 0| + 0.5|1\\rangle\\langle 1|$. \n", "\n", "The density matrix formalism is a very useful way to describe a noisy system with probabilistic outcomes. It gives an exact description of a quantum system going through a quantum channel with noise. Besides, the expectation value of an observable $\\langle O\\rangle$ can be easily calculated by $\\rm{Tr}(O\\rho)$, where \"$\\rm{Tr}$\" is the trace operator. \n", "\n", "### Quantum channel and Kraus representation \n", "\n", "A [quantum channel](https://en.wikipedia.org/wiki/Quantum_channel) describes the time evolution of a quantum state which is expressed as a density matrix. For instance, to understand what a series of noisy gates does to the state of a quantum computer, you can apply a quantum channel corresponding to the different gate and noise operations. \n", "Mathematically speaking, a quantum channel is a completely positive and trace-preserving (CPTP) linear map acting on a density matrix. Completely positive means the channel maps positive operators into positive operators (even if the operator is applied to part of a larger system) to make sure the density matrix describes a proper quantum state after the map. Trace-preserving means the trace of the density matrix remains unchanged during the mapping process (this is so that after the map the classical probabilities $p_i$ still sum to 1). \n", "\n", "The so-called _Kraus representation_ is a commonly used representation for CPTP maps. [Kraus's theorem](https://en.wikipedia.org/wiki/Quantum_operation#Kraus_operators) states that any quantum operation acting on a quantum state $\\rho$ can be expressed as a map $\\varepsilon(\\rho) = \\sum_i K_i\\rho K_i^{\\dagger}$, and it satisfies: $\\sum_i K_i^{\\dagger}K_i = \\mathbb{1}$, where $\\mathbb{1}$ is the Identity operator.\n", "\n", "Let's get started and have a look how you can define and simulate noisy circuits on Amazon Braket." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## General imports " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's begin with the usual imports." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from braket.circuits import Circuit, Observable, Gate, Noise, FreeParameter\n", "from braket.devices import LocalSimulator\n", "from braket.aws import AwsDevice\n", "import numpy as np\n", "from scipy.stats import unitary_group" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Quick start \n", "\n", "Let's start with a simple example of running a noisy circuit on Amazon Braket. " ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "measurement results: Counter({'11': 441, '00': 381, '01': 94, '10': 84})\n" ] } ], "source": [ "# build a simple circuit\n", "circ = Circuit().h(0).cnot(0,1)\n", "\n", "# define a noise channel\n", "noise = Noise.BitFlip(probability=0.1)\n", "\n", "# add noise to every gate in the circuit\n", "circ.apply_gate_noise(noise)\n", "\n", "# select the local noise simulator\n", "device = LocalSimulator('braket_dm')\n", "\n", "# run the circuit on the local simulator\n", "task = device.run(circ, shots = 1000)\n", "\n", "# visualize the results\n", "result = task.result()\n", "measurement = result.measurement_counts\n", "print('measurement results:', measurement)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ideally, in the noise-free case, the circuit we defined prepares a Bell-state, and we would expect to measure only '00' and '11' outcomes. However, the presence of noise, in our case a bit flip error, means that sometimes we find the state in '01' and '10' instead.\n", "\n", "The local simulator is suitable for fast prototyping on small circuits. If you want to run a noisy circuit with more than 10~12 qubits, we recommend using the on-demand simulator DM1. Using DM1, you can run circuits with up to 17 qubits, and benefit from parallel execution for a group of circuits. The code below shows an example of preparing a 13-qubit GHZ state in the presence of noise." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "measurement results: Counter({'0000000000000': 2, '1100111111111': 1, '0000000000001': 1, '1000001111100': 1, '1111100000000': 1, '0000000010000': 1, '1111111111111': 1, '1000010000001': 1, '0011111111111': 1})\n" ] } ], "source": [ "def ghz_circuit(n_qubits: int) -> Circuit:\n", " \"\"\"\n", " Function to return simple GHZ circuit ansatz. Assumes all qubits in range(0, n_qubits-1)\n", " are entangled.\n", " \"\"\"\n", " circuit = Circuit().h(0) \n", "\n", " for ii in range(0, n_qubits-1):\n", " circuit.cnot(control=ii, target=ii+1) \n", " return circuit\n", "\n", "# build a 13-qubit GHZ circuit\n", "circ = ghz_circuit(13)\n", "\n", "# define a noise channel\n", "noise = Noise.Depolarizing(probability=0.1)\n", "\n", "# add noise to every gate in the circuit\n", "circ.apply_gate_noise(noise)\n", "\n", "# select the on-demand density matrix simulator DM1\n", "device = AwsDevice(\"arn:aws:braket:::device/quantum-simulator/amazon/dm1\")\n", "\n", "# run the circuit on DM1\n", "task = device.run(circ, shots = 10)\n", "\n", "# visualize the results\n", "result = task.result()\n", "measurement = result.measurement_counts\n", "print('measurement results:', measurement)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We now start exploring the detailed instructions and use cases of each step in the following sections." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Defining noise channels \n", "\n", "To apply noise to a quantum circuit, first, you need to define the noise channel, which is defined in Kraus representation. We offer many commonly-used noise channels in the `Noise` class of the [Amazon Braket SDK](https://amazon-braket-sdk-python.readthedocs.io/en/latest/_apidoc/braket.circuits.html). In addition, you can also define your own custom noise channel as a list of Kraus operators.\n", "\n", "### Pre-defined noise channels \n", "\n", "The pre-defined single-qubit noise channels include `BitFlip`, `PhaseFlip`, `Depolarizing`, `AmplitudeDamping`, `GeneralizedAmplitudeDamping`, `PhaseDamping` and `PauliChannel`. \n", "The pre-defined two-qubit noise channels include `TwoQubitDepolarizing` and `TwoQubitDephasing`. The Kraus representations for all of the pre-defined channels are summarized in the following table.\n", "\n", "__single-qubit noise channels__\n", "\n", "| Noise channel |
target_gates
must be a Gate
type. You can find all available gates with the following commands:\n",
" \n",
"\n",
"from braket.circuits import Gate\n",
"gate_set = [attr for attr in dir(Gate) if attr[0] in string.ascii_uppercase]\n",
"print(gate_set)\n",
"
\n",
"