{ "cells": [ { "cell_type": "markdown", "id": "5f4d0289", "metadata": {}, "source": [ "# Native programming\n", "\n", "In order to execute a program on a quantum computer, each qubit in the program must be mapped to a physical qubit on the device, and each operation must be mapped to one or more \"native gates\", that is, gates that are natively implemented by the hardware. While this can be handled automatically by a compiler, a quantum software developer or researcher may want to be able to control these mappings explicitly. We refer to this low-level programming as \"native programming\".\n", "\n", "This notebook provides a demonstration of the native programming features of AutoQASM by targeting a simple two-qubit circuit to physical qubits and native gates of an IonQ quantum computer, which is available through Amazon Braket. " ] }, { "cell_type": "code", "execution_count": 1, "id": "954638fe", "metadata": {}, "outputs": [], "source": [ "# general imports\n", "import IPython\n", "\n", "# AutoQASM imports\n", "import autoqasm as aq\n", "\n", "# AWS imports: Import Braket SDK modules\n", "from braket.devices import Devices" ] }, { "cell_type": "markdown", "id": "5dd374f2", "metadata": {}, "source": [ "The circuit we will use for this demonstration is a program which creates and measures a Bell state on two qubits. Here, we write this program at the typical level of abstraction, which is hardware-agnostic. We use integers `0` and `1` to specify qubit indices, and we use the built-in `h` and `cnot` instructions from the AutoQASM `instructions` module." ] }, { "cell_type": "code", "execution_count": 2, "id": "ddf6cca5", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "OPENQASM 3.0;\n", "output bit[2] return_value;\n", "qubit[2] __qubits__;\n", "h __qubits__[0];\n", "cnot __qubits__[0], __qubits__[1];\n", "bit[2] __bit_0__ = \"00\";\n", "__bit_0__[0] = measure __qubits__[0];\n", "__bit_0__[1] = measure __qubits__[1];\n", "return_value = __bit_0__;\n" ] } ], "source": [ "from autoqasm.instructions import cnot, h, measure\n", "\n", "\n", "@aq.main\n", "def bell_state():\n", " h(0)\n", " cnot(0, 1)\n", " return measure([0, 1])\n", "\n", "\n", "print(bell_state.build().to_ir())" ] }, { "cell_type": "markdown", "id": "9ff3d255", "metadata": {}, "source": [ "As seen in the generated OpenQASM program, this produces a program that uses a two-qubit register `__qubits__` and the built-in `h` and `cnot` gates. At runtime, the compiler will automatically map this to two physical qubits, and will compile the `h` and `cnot` instructions to an equivalent sequence of gates which are native to the target device.\n", "\n", "In the native programming scenario, however, the developer wants full control over the physical qubit mappings and conversion to native gates. We can take advantage of two features of AutoQASM to enable this. First, we replace the integers `0` and `1`, which specify virtual qubit indices, with the strings `\"$0\"` and `\"$1\"`, which specify physical qubits. Second, we wrap the gates inside a `verbatim` block (using the `aq.verbatim()` context), which instructs the compiler to avoid modifying anything inside the block." ] }, { "cell_type": "code", "execution_count": 3, "id": "940d4b22", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "OPENQASM 3.0;\n", "output bit[2] return_value;\n", "pragma braket verbatim\n", "box {\n", " h $0;\n", " cnot $0, $1;\n", "}\n", "bit[2] __bit_0__ = \"00\";\n", "__bit_0__[0] = measure $0;\n", "__bit_0__[1] = measure $1;\n", "return_value = __bit_0__;\n" ] } ], "source": [ "@aq.main\n", "def bell_state():\n", " with aq.verbatim():\n", " h(\"$0\")\n", " cnot(\"$0\", \"$1\")\n", " return measure([\"$0\", \"$1\"])\n", "\n", "\n", "print(bell_state.build().to_ir())" ] }, { "cell_type": "markdown", "id": "a9182030", "metadata": {}, "source": [ "This program now targets physical qubits, and the gates will not be modified by the compiler.\n", "\n", "## Device-specific validation\n", "\n", "Bypassing the mapping and compilation is only the first step of native programming. Because native programming is intended for targeting a program to a specific device, we need to specify the target device in the AutoQASM program. We can accomplish this by adding a `device` argument to the `build()` call, passing the ARN of the Amazon Braket device (or, optionally, a `braket.devices.Device` object) that we want to target.\n", "\n", "Here we target the `Devices.IonQ.ForteEnterprise1` device. When building this program, AutoQASM will validate that (among other things) the contents of any `verbatim` blocks respect the native gate set and connectivity of the target device. " ] }, { "cell_type": "code", "execution_count": 4, "id": "72cde00f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "ERROR: The gate \"h\" is not a native gate of the target device \"Forte Enterprise 1\". Only native gates may be used inside a verbatim block. The native gates of the device are: ['gpi', 'gpi2', 'zz']\n" ] } ], "source": [ "@aq.main\n", "def bell_state():\n", " with aq.verbatim():\n", " h(\"$0\")\n", " cnot(\"$0\", \"$1\")\n", " return measure([\"$0\", \"$1\"])\n", "\n", "\n", "try:\n", " bell_state.build(device=Devices.IonQ.ForteEnterprise1)\n", "except Exception as e:\n", " print(\"ERROR:\", e)" ] }, { "cell_type": "markdown", "id": "3ef34c2d", "metadata": {}, "source": [ "The validation error indicates that we cannot use `h` and `cnot` inside a `verbatim` block for this device. Instead, we must express our program in terms of the native gates of the device: `gpi`, `gpi2`, and `zz`.\n", "\n", "## Custom gate definitions using `@aq.gate`\n", "\n", "In order to do this, we can use the `@aq.gate` decorator in AutoQASM to define custom gates, which we implement in terms of this native gate set. In the Python script `ionq_gates.py`, we define custom implementations of the `h` and `cnot` gates which are built on top of the `gpi`, `gpi2`, and `zz` gates." ] }, { "cell_type": "code", "execution_count": 5, "id": "518b9d23", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
import numpy as np\n",
"\n",
"import autoqasm as aq\n",
"from autoqasm.instructions import gpi, gpi2, zz\n",
"\n",
"\n",
"@aq.gate\n",
"def h(q: aq.Qubit):\n",
" gpi2(q, np.pi / 2)\n",
" gpi(q, 0)\n",
"\n",
"\n",
"@aq.gate\n",
"def u(q: aq.Qubit, a: float, b: float, c: float):\n",
" gpi2(q, a)\n",
" gpi(q, b)\n",
" gpi2(q, c)\n",
"\n",
"\n",
"@aq.gate\n",
"def rx(q: aq.Qubit, theta: float):\n",
" u(q, np.pi / 2, theta / 2 + np.pi / 2, np.pi / 2)\n",
"\n",
"\n",
"@aq.gate\n",
"def ry(q: aq.Qubit, theta: float):\n",
" u(q, np.pi, theta / 2 + np.pi, np.pi)\n",
"\n",
"\n",
"@aq.gate\n",
"def xx(q0: aq.Qubit, q1: aq.Qubit, theta: float):\n",
" h(q0)\n",
" h(q1)\n",
" zz(q0, q1, theta)\n",
" h(q0)\n",
" h(q1)\n",
"\n",
"\n",
"@aq.gate\n",
"def cnot(q0: aq.Qubit, q1: aq.Qubit):\n",
" ry(q0, np.pi / 2)\n",
" xx(q0, q1, np.pi / 2)\n",
" rx(q0, -np.pi / 2)\n",
" rx(q1, -np.pi / 2)\n",
" ry(q0, -np.pi / 2)\n",
"