{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "dfb19657",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "# Getting started with the Qiskit-Braket provider"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "a95fc7c5",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### Qiskit to Braket mapping"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "447c275b",
   "metadata": {
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "![qiskit-to-braket-diagram](./data/qiskit-braket-mapping.png)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "04160f91",
   "metadata": {},
   "source": [
    "We first start by importing all required classes and functions for this notebook. We also start the cost tracker to print the costs at the end of this notebook."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "445cc5d9",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-06T06:26:03.828647Z",
     "start_time": "2024-03-06T06:26:02.319740Z"
    }
   },
   "outputs": [],
   "source": [
    "from qiskit import QuantumCircuit\n",
    "from qiskit.circuit.random import random_circuit\n",
    "from qiskit.visualization import plot_histogram\n",
    "\n",
    "from braket.tracking import Tracker\n",
    "\n",
    "from qiskit_braket_provider import BraketLocalBackend, BraketProvider, to_braket\n",
    "\n",
    "# Use Braket SDK Cost Tracking to estimate the cost to run this example\n",
    "t = Tracker().start()"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "6fea9c62",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### Access Braket devices from Qiskit \n",
    "\n",
    "`BraketProvider` class gives you a method `backends` to access backends that are available through Braket SDK."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "a59c288b",
   "metadata": {},
   "source": [
    "Let's get available devices to use by Qiskit"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2b47da63",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-06T06:26:13.290746Z",
     "start_time": "2024-03-06T06:26:08.276905Z"
    },
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "provider = BraketProvider()\n",
    "print(provider.backends())"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "cf366038",
   "metadata": {},
   "source": [
    "If you want to explore what is available by specific contraints, you can specify query arguments to `backends` method of provider.\n",
    "Arguments are fully compatible with Braket's `get_device` method. See the documentation at [braket.aws.aws_device.AwsDevice.get_devices](https://amazon-braket-sdk-python.readthedocs.io/en/stable/_apidoc/braket.aws.aws_device.html#braket.aws.aws_device.AwsDevice.get_devices). For example, you can retrieve the list of online simulators via:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e182e1a4",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-06T06:26:22.044814Z",
     "start_time": "2024-03-06T06:26:21.478978Z"
    }
   },
   "outputs": [],
   "source": [
    "online_simulators_backends = provider.backends(statuses=[\"ONLINE\"], types=[\"SIMULATOR\"])\n",
    "online_simulators_backends"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "edbae0ee",
   "metadata": {},
   "source": [
    "For prototyping it is usually a good practice to use simulators \n",
    "to set up workflow of your program and then change it to real device.\n",
    "We can access local simulator by creating instance of class `BraketLocalBackend`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2517f020",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-06T06:26:28.655697Z",
     "start_time": "2024-03-06T06:26:28.541800Z"
    },
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "outputs": [],
   "source": [
    "local_simulator = BraketLocalBackend()\n",
    "local_simulator"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "390c3a64",
   "metadata": {},
   "source": [
    "Any backend can be instantiated via the `get_backend` method of the provider. Here is an example where we create a Backend object for the IonQ `Aria 1` device."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "64a9cb24",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-06T06:26:38.862397Z",
     "start_time": "2024-03-06T06:26:36.334650Z"
    }
   },
   "outputs": [],
   "source": [
    "Aria_1 = provider.get_backend(\"Aria 1\")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "e91eeeee",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### Running circuits on Braket devices"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "137ae345",
   "metadata": {},
   "source": [
    "Let's first create Qiskit circuit. We will start with a Bell circuit."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "22d4a07a",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-06T06:26:46.139190Z",
     "start_time": "2024-03-06T06:26:46.133439Z"
    }
   },
   "outputs": [],
   "source": [
    "qc = QuantumCircuit(2)\n",
    "qc.h(0)\n",
    "qc.cx(0, 1)\n",
    "qc.draw()"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "512cfd00",
   "metadata": {},
   "source": [
    "This circuit can be used to submit a task to the local simulator. In the [tutorials](https://github.com/qiskit-community/qiskit-braket-provider/tree/main/docs/tutorials) associated to the Qiskit-Braket provider, we will use the Braket taxonomy:\n",
    "- a task is an atomic request to a device or a simulator. \n",
    "- an hybrid job is a mean to run hybrid quantum-classical algorithms requiring both classical AWS resources and quantum processing units (QPUs). See [What is a Hybrid Job](https://docs.aws.amazon.com/braket/latest/developerguide/braket-what-is-hybrid-job.html) for more details.\n",
    "\n",
    "Here, quantum tasks are analogous to Qiskit jobs, which is why tasks have a `job_id` property. If a task has been submitted to Braket managed device, `job_id` will return a Task ARN (Amazon Resource number) which identifies the task and allows to retrieve it in the Braket Console and in your notebooks. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d00d454c",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-06T06:26:49.681208Z",
     "start_time": "2024-03-06T06:26:49.656719Z"
    }
   },
   "outputs": [],
   "source": [
    "task = local_simulator.run(qc, shots=10)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0d22adcd",
   "metadata": {},
   "source": [
    "Results are returned via a `Result` object, from which you can extract counts and plot them in a histogram."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b653ca31",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-06T06:26:55.379878Z",
     "start_time": "2024-03-06T06:26:55.035869Z"
    }
   },
   "outputs": [],
   "source": [
    "results = task.result()\n",
    "plot_histogram(results.get_counts())"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "43128a91",
   "metadata": {},
   "source": [
    "Each single shot measurements are also retrievable."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "91adc995",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-06T06:26:58.125488Z",
     "start_time": "2024-03-06T06:26:58.120859Z"
    }
   },
   "outputs": [],
   "source": [
    "results.get_memory()"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "d48764e8",
   "metadata": {},
   "source": [
    "More complex circuits on devices can be submitted to the Braket devices and simulators. Behind the scenes, the qiskit transpiler will adapt the circuits such that they are executable on all devices. In the following, we will focus on running circuit in the IonQ Aria-1 device. \n",
    "\n",
    "We will start with generating random circuit and printing it out."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2d278880",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-06T06:27:10.638790Z",
     "start_time": "2024-03-06T06:27:10.629393Z"
    }
   },
   "outputs": [],
   "source": [
    "qiskit_random_circuit = random_circuit(4, 5, seed=42)\n",
    "qiskit_random_circuit.draw(fold=-1)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "d9d69137",
   "metadata": {},
   "source": [
    "Each device has a set of supported operations which are accessible via the backend."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fd631f39",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-06T06:27:20.803662Z",
     "start_time": "2024-03-06T06:27:20.800528Z"
    }
   },
   "outputs": [],
   "source": [
    "aria_supported_gates = Aria_1.get_gateset()\n",
    "print(aria_supported_gates)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "51e60f77",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-06T06:27:29.671085Z",
     "start_time": "2024-03-06T06:27:29.604054Z"
    },
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "outputs": [],
   "source": [
    "braket_random_circuit = to_braket(\n",
    "    qiskit_random_circuit, basis_gates=aria_supported_gates\n",
    ")\n",
    "print(braket_random_circuit)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "c698ffb1",
   "metadata": {},
   "source": [
    "We printed the Braket Circuit object that will be used to create the quantum task on the Aria-1 device. To submit the task, you do not have to create a Braket circuit, this is done internal when you submit the Qiskit circuit to the Backend."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dfad043a",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-06T06:27:41.090278Z",
     "start_time": "2024-03-06T06:27:38.860357Z"
    },
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "outputs": [],
   "source": [
    "aria_task = Aria_1.run(qiskit_random_circuit, shots=10)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "1c129b43",
   "metadata": {},
   "source": [
    "If you do not want to wait for the task completion and return to it later, you can use `retrieve_job` method on device to get job object. For this, you will need to know the job_id (which is the task ARN that identifies the task in the AWS cloud)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "34787aad",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-06T06:27:46.073116Z",
     "start_time": "2024-03-06T06:27:46.024758Z"
    }
   },
   "outputs": [],
   "source": [
    "task_arn = aria_task.job_id()\n",
    "retrieved_task = Aria_1.retrieve_job(task_id=task_arn)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "d28a2001",
   "metadata": {},
   "source": [
    "From the task, you can check its status: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "764d4828",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-06T06:27:49.833820Z",
     "start_time": "2024-03-06T06:27:49.416314Z"
    }
   },
   "outputs": [],
   "source": [
    "retrieved_task.status()"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "6ce860f0",
   "metadata": {},
   "source": [
    "and get the results as done previously: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4a8a8760",
   "metadata": {},
   "outputs": [],
   "source": [
    "random_circuit_results = retrieved_task.result()\n",
    "plot_histogram(random_circuit_results.get_counts())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a16ab026",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Quantum Task Summary\")\n",
    "print(t.quantum_tasks_statistics())\n",
    "print(\n",
    "    \"Note: Charges shown are estimates based on your Amazon Braket simulator and quantum processing \"\n",
    "    \"unit (QPU) task usage. Estimated charges shown may differ from your actual charges. Estimated \"\n",
    "    \"charges do not factor in any discounts or credits, and you may experience additional charges \"\n",
    "    \"based on your use of other services such as Amazon Elastic Compute Cloud (Amazon EC2).\"\n",
    ")\n",
    "print(\n",
    "    f\"Estimated cost to run this example: {t.qpu_tasks_cost() + t.simulator_tasks_cost():.3f} USD\"\n",
    ")"
   ]
  }
 ],
 "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.13.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}