diff --git a/examples/autoqasm_deutsch_jozsa.ipynb b/examples/autoqasm_deutsch_jozsa.ipynb new file mode 100644 index 0000000..fbdf957 --- /dev/null +++ b/examples/autoqasm_deutsch_jozsa.ipynb @@ -0,0 +1,323 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Deutsch-Jozsa Algorithm with Imperative Computation (AutoQASM)\n", + "\n", + "This notebook will introduce the Deutsch-Joszsa problem and solution. We then implement the quantum solution with AutoQASM, demonstrating it's success by submitting quantum jobs to qBraid's QIR simulator." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "\n", + "### The Problem\n", + "We are given a hidden decision function $f: \\mathbb \\{0, 1\\}^n \\to \\{0, 1\\}$. For this problem, we assume any such $f$ is either \"balanced\" or \"constant\". \n", + "- Balanced: $f$ returns $0$'s on exactly $2^{n-1}$ of the possible inputs, and $1$'s on the other $2^{n-1}$.\n", + "- Constant: $f(x) = 0$ or $f(x) = 1$ for all $x \\in \\{0, 1\\}^n$. \n", + "\n", + "The Deutch-Jozsa problem is to determine, given a function $f$, to determine whether it is in fact balanced or constant." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### The Classical Deterministic Solution\n", + "\n", + "By doing a worst-case analysis on the problem, it is easy to see that we would require $2^{n-1} + 1$ calls to $f$ to solve this problem. There are probabilistic classical alternatives that use less calls." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### The Classical Probabilistic Solution\n", + "\n", + "Given that a function is balanced where the inputs are uniformly chosen to have output $0$ or $1$, there is a $\\dfrac{1}{2^{n-1}}$ probability that we actually have to query $f$ $2^{n-1}+1$ times. This hints at a probabilistic solution to the problem.\n", + "\n", + "The solution is the choose $k$ random inputs, from which we can derive $$P_{\\text{constant}}(k) = 1- \\frac{1}{2^{k-1}}.$$\n", + "\n", + "Basically, we can run the classical deterministic solution and just break whenever we have, say, $p$ probability of confidence." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### The Quantum Solution\n", + "\n", + "A quantum computer can solve the Deutsch-Jozsa problem with 100% confidence with only ***one*** call to $f$. The actual circuit has $n+1$ qubits ($n \\ket{0}$ 's and $1 \\ket{1}$) and is split into four main parts:\n", + "1. Preparing the quantum registers: The first $n$-qubit register are set to $\\ket{0}^{\\otimes n}$ and the second as $\\ket 1$: $$\\ket {\\psi_0} = \\ket 0 ^{\\otimes n} \\ket 1.$$\n", + "2. Applying a Hadamard gate to all qubits: $$\\ket{\\psi_{1}} = \\frac{1}{\\sqrt{2^{n+1}}} \\sum_{x=0}^{2^n-1} \\ket x (\\ket 0 - \\ket 1).$$\n", + "3. Appling the quantum oracle $U_f: \\ket x \\ket y \\mapsto \\ket x \\ket{y \\oplus f(x)}$: $$\\ket{\\psi_2} = \\frac{1}{\\sqrt{2^n+1}} \\sum_{x=0}^{2^n-1} (-1)^{f(x)}\\ket x (\\ket 0 - \\ket 1)$$\n", + "4. Applying a Hadamard gate to the first $n$ qubits: $$\\ket{\\psi_3} = \\frac{1}{2^n} \\sum_{y=0}^{2^n-1} \\left[\\sum_{x=0}^{2^n-1} (-1)^{f(x)} (-1)^{x\\cdot y}\\right] \\ket y $$\n", + "\n", + "Now, we just measure the first register. We see that: $$P(\\ket{0}^{\\otimes n}) = \\left| \\frac{1}{2^n} \\sum_{x=0}^{2^n - 1} (-1)^{f(x)} \\right|^2,$$\n", + "which is 1 *if and only if* $f$ is constant. This suffices to show correctness of our solution." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### The Oracle\n", + "**Constant Oracle**\n", + "\n", + "When a oracle is constant, it has no effect on the input qubits. So step 4 reverses step 2, giving us the same vector we had after step 1. \n", + "\n", + "**Balanced Oracle**\n", + "\n", + "Step 2 gives us an $x$ that is an equal superposition of all states in the $n$-qubit basis. A balanced oracle has a phase kickback that gives negative phase to exactly half of the states. The state after applying the oracle is orthogonal to the prior state, meaning applying $n$ Hadamard gates yields a state orthogonal to $\\ket{00\\ldots 0}$. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## AutoQASM Implementation\n", + "\n", + "We start by importing the relevant libraries." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%pip install qbraid \\\n", + " qbraid-qir \\\n", + " autoqasm \\\n", + " matplotlib --quiet" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import autoqasm as aq\n", + "import autoqasm.instructions as ins\n", + "\n", + "from qbraid.runtime import QbraidProvider\n", + "from qbraid.visualization import plot_histogram" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Constant Example\n", + "\n", + "For this example, we're gonna use a four qubit example. We start by identifying that the oracle is going to just be the identity, as a valid $U_f: \\ket x \\ket y \\mapsto \\ket x \\ket{y \\oplus 0} = \\ket x \\ket y$ is the identity mapping." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "n = 4" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "def constant_f(x):\n", + " return 0\n", + "\n", + "\n", + "@aq.gate\n", + "def constant_oracle(q1: aq.Qubit, q2: aq.Qubit, q3: aq.Qubit, q4: aq.Qubit, q5: aq.Qubit):\n", + " pass\n", + "\n", + "\n", + "@aq.main(num_qubits=n + 1)\n", + "def deutch_jozsa_constant():\n", + " ins.x(4)\n", + " for i in range(n + 1):\n", + " ins.h(i)\n", + "\n", + " constant_oracle(*range(n + 1))\n", + "\n", + " for i in range(n + 1):\n", + " ins.h(i)\n", + "\n", + " for i in range(n):\n", + " ins.measure(i)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## qBraid Instructions\n", + "\n", + "qBraid providers a QIR Simulator for quantum jobs. To demonstrate its usage, we'll be using it to simulate the Deutsch-Jozsa algorithm." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "provider = QbraidProvider()\n", + "\n", + "device = provider.get_device(\"qbraid_qir_simulator\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "job = device.run(deutch_jozsa_constant, shots=1000)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkQAAAG9CAYAAAD0lWkWAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA/PUlEQVR4nO3deXRU9f3/8de9E7IYyEJCNghJgKCAYBAQIm5IShDUIihSU0Wk4vEHWKUVsS4Va6Xi+bqgVmqr4AIVV1RQlKWCSGQJRpAt7AmESYCQTAJkkszM74/EmUmhKjGQwH0+zuGczvt+Zj6fedfceeUuE8Pj8XgEAABgYWZTLwAAAKCpEYgAAIDlEYgAAIDlEYgAAIDlEYgAAIDlEYgAAIDlEYgAAIDlEYgAAIDlEYgAAIDlEYgAAIDlNWkgWrFiha677jolJCTIMAzNnz/fu626uloPPPCAunfvrtDQUCUkJOi2225TYWFhvdcoKSlRVlaWwsLCFBERobFjx6qioqLemA0bNujyyy9XcHCwEhMTNX369DPx9gAAwFmiSQPR0aNHddFFF+mll146YduxY8e0fv16PfLII1q/fr0++OADbdu2Tddff329cVlZWdq0aZMWL16sBQsWaMWKFRo3bpx3u8Ph0KBBg5SUlKScnBw9/fTTeuyxx/TKK6+c9vcHAADODkZz+eOuhmHoww8/1LBhw/7nmLVr1+qSSy7R3r171b59e23ZskVdu3bV2rVr1bt3b0nSokWLNGTIEO3bt08JCQl6+eWX9dBDD8lutyswMFCSNGXKFM2fP19bt249E28NAAA0cwFNvYBTUVZWJsMwFBERIUnKzs5WRESENwxJUkZGhkzT1OrVq3XDDTcoOztbV1xxhTcMSVJmZqaeeuopHTlyRJGRkSfM43Q65XQ6vY/dbrdKSkoUFRUlwzBO3xsEAACNxuPxqLy8XAkJCTLNHz8pdtYEosrKSj3wwAP6zW9+o7CwMEmS3W5XTExMvXEBAQFq3bq17Ha7d0xKSkq9MbGxsd5tJwtE06ZN09SpU0/H2wAAAGdYQUGB2rVr96NjzopAVF1drZEjR8rj8ejll18+7fM9+OCDmjRpkvdxWVmZ2rdvr4KCAm8YAwAAzZvD4VBiYqJatWr1k2ObfSD6IQzt3btXy5YtqxdI4uLiVFxcXG98TU2NSkpKFBcX5x1TVFRUb8wPj38Y89+CgoIUFBR0Qj0sLIxABADAWebnXO7SrL+H6IcwtH37di1ZskRRUVH1tqenp6u0tFQ5OTne2rJly+R2u9W3b1/vmBUrVqi6uto7ZvHixTr//PNPeroMAABYT5MGooqKCuXm5io3N1eStHv3buXm5io/P1/V1dW68cYbtW7dOs2ZM0cul0t2u112u11VVVWSpC5dumjw4MG68847tWbNGn399deaMGGCRo0apYSEBEnSLbfcosDAQI0dO1abNm3SvHnz9Pzzz9c7JQYAAKytSW+7//LLLzVgwIAT6qNHj9Zjjz12wsXQP/jPf/6jq666SlLtFzNOmDBBn3zyiUzT1IgRIzRjxgy1bNnSO37Dhg0aP3681q5dq+joaE2cOFEPPPDAz16nw+FQeHi4ysrKOGUGAMBZ4lQ+v5vN9xA1ZwQiAADOPqfy+d2sryECAAA4EwhEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAM4599xzj5KTk2UYhnJzc7317du369JLL1Xnzp3Vp08fbdq06RdvA3BuIBABOOfceOONWrlypZKSkurV77rrLo0bN055eXl64IEHdPvtt//ibQDODYbH4/E09SKaO4fDofDwcJWVlSksLKyplwPgZ0pOTtb8+fOVlpam4uJiderUSSUlJQoICJDH41F8fLxWrlypsLCwBm3r1KlTU79FAD/iVD6/OUIEwBIKCgoUHx+vgIAASZJhGGrfvr3y8/MbvA3AuYNABAAALC+gqRcAAGdCYmKiDhw4oJqaGu+pr/z8fLVv315hYWEN2gbg3MERIgCWEBMTo4svvlhvvfWWJOn9999Xu3bt1KlTpwZvA3Du4KLqn4GLqoGzy1133aWFCxfKbrcrKipKrVq10o4dO7Rt2zbdfvvtOnz4sMLCwjRr1ix1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mzRv3jw9//zzmjRpUhO9awAA0Nw06TVE69at04ABA7yPfwgpo0eP1uzZszV58mQdPXpU48aNU2lpqS677DItWrRIwcHB3ufMmTNHEyZM0MCBA2WapkaMGKEZM2Z4t4eHh+uLL77Q+PHj1atXL0VHR+vRRx+t911FAADA2prNXWbN2en+a/fcZQYAsLqmvsus2V5DBAAAcKYQiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOU160Dkcrn0yCOPKCUlRSEhIerYsaP+8pe/yOPxeMd4PB49+uijio+PV0hIiDIyMrR9+/Z6r1NSUqKsrCyFhYUpIiJCY8eOVUVFxZl+OwAAoJlq1oHoqaee0ssvv6wXX3xRW7Zs0VNPPaXp06frhRde8I6ZPn26ZsyYoZkzZ2r16tUKDQ1VZmamKisrvWOysrK0adMmLV68WAsWLNCKFSs0bty4pnhLAACgGTI8/odbmplrr71WsbGxevXVV721ESNGKCQkRG+99ZY8Ho8SEhL0hz/8QX/84x8lSWVlZYqNjdXs2bM1atQobdmyRV27dtXatWvVu3dvSdKiRYs0ZMgQ7du3TwkJCT+5DofDofDwcJWVlSksLKzR32fylIWN/poAAJxN9vxtaKO/5ql8fjfrI0SXXnqpli5dqry8PEnSd999p5UrV+qaa66RJO3evVt2u10ZGRne54SHh6tv377Kzs6WJGVnZysiIsIbhiQpIyNDpmlq9erVJ53X6XTK4XDU+wcAAM5dAU29gB8zZcoUORwOXXDBBbLZbHK5XPrrX/+qrKwsSZLdbpckxcbG1ntebGysd5vdbldMTEy97QEBAWrdurV3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Re+9956qqqqaelkAGhGBCAB+xPbt23XppZcqJCREmZmZ2rt3r6ZNm6bx48fr+PHjTb08AI2Eb6oGgB/x+OOPKzc3Vx988IGk2lNnM2bM0L///W+lpqbqtddeU3BwcBOvEsAvxREiAPgRxcXFKigo8D4OCAjQ+PHjdccdd2jXrl168skn5XK5mnCFABoDgQgATsLtdkuS0tLSZBiGNmzYoB8OqAcFBem2225Tenq6Fi5cqLKysqZcKoBGwCkzAPDj8XhkGIb3cWFhoS655BL17dtX//rXvxQZGekdU1paqujoaM2bN08jRoxowlUD+KUCmnoBANBcbNu2TXPmzFF+fr4uu+wy9evXTxdeeKHmz5+vAQMGaOLEiXr66acVHx8vqfZ6oh49eqh169ZNvHIAvxSBCABUe2v9pZdeqoyMDB04cEBbt27V1KlT9eqrr2rQoEH65JNPdP3116u4uFi/+c1v1KNHD7377rsqLCxUx44dm3r5AH4hTpkBsDyXy6Xbb79dHo9Hb731liQpNzdXL774ombPnq358+fr2muvVV5enn7/+99r165dqqmpUUhIiN5880317Nmzid8BgF+KI0QALM/tdqugoEDp6eneWlpamqZNm6bAwEDddNNN+uKLL3T55ZfrvffeU1lZmcrLy9WmTRtOlwHnCAIRAMtr0aKFLrzwQi1fvlxHjhxRZGSkJKlNmzZ68MEHVVxcrGnTpql79+6KiIhQaGhoE68YQGPjtnsAkHTFFVfo+PHjmjVrlsrLy731xMREXXfddfruu+/q1QGcWzhCBMBy9uzZo8WLF8s0TbVr106ZmZkaOXKkvvrqK/3jH/9QSEiIbr75Zu/psD59+ui8884jEAHnMAIRAEvZuHGjBgwYoNTUVB08eFBFRUW68cYbNWPGDL3wwgv63e9+p7///e/Ky8vThAkTFB4ertdff12maSo2Nraplw/gNOEuMwCWUVFRoUGDBql3796aMWOG7Ha7cnNzlZWVpZ49e2ru3LmKiYnR448/riVLlmjlypW6+OKLtX//fn366afcTQacwwhEACyjsrJS/fv31+TJk3XzzTd763l5eerfv7/69eunTz75RFLt3zBbv369WrVqpaSkJLVr166plg3gDOCiagCW4XK5VFRUpG3btnlr1dXV6ty5s5YuXar//Oc/mjp1qiQpJiZGgwcPVv/+/QlDgAUQiABYRmhoqCZNmqR//vOfWrBggaTaW+6rq6vVo0cPPfjgg/rss89UUlLi/eOuAKyBi6oBnLMOHDiggoICHTlyRBkZGbLZbBo+fLi++eYbTZ8+XYGBgRo0aJBatGghSYqOjpbD4VBwcLBMk98XASvhJx7AOWnDhg1KT0/XrbfeqptvvlndunXT22+/rbZt22ry5MkKDw/Xww8/rLfffltS7amzXbt2KSYmRi6Xq4lXD+BM46JqAOecgwcP6oorrtDw4cM1duxYBQcHa9KkSfr222+VlZWlBx54QFu3btXMmTP1r3/9S926dVNISIi2bdumZcuWKS0tranfAoAzjEAE4JyzefNmDR06VO+995569erlrU+ZMkULFizQmDFjNGnSJB07dkwbN27UkiVL1KZNGw0cOFCdOnVqwpUDaCpcQwTgnFNVVaXq6modO3ZMknT8+HGFhITob3/7m44fP64XXnhBv/rVr9SjRw/169dP/fr1a+IVA2hqHCECcE5wu93yeDyy2WySpMsvv1ymaWr58uWSJKfTqaCgIEm1f4qjU6dO+ve//91k6wXQvHBRNYCz3ubNm3XbbbcpMzNTd955p5YvX67nn39e+/fv18iRIyVJQUFBqqmpkVT7h1yPHj3alEsG0MwQiACc1bZt26ZLL71ULpdLffr00dq1a3X//ffrX//6l/7yl78oJydHN9xwg6qrq7230hcXFys0NFQ1NTXiIDkAiVNmAM5iHo9HDz/8sHbs2KF58+ZJksrLy/Xcc89pwYIF6tSpk0aOHKnJkydLkrp27arAwEAtXLhQ33zzjS688MKmXD6AZoSLqgGctQzDUGFhoex2u7fWqlUr3XvvvQoJCdEHH3ygvLw8rVu3Tn/96191+PBhBQcHa82aNeratWsTrhxAc8MRIgBnJY/HI8Mw9MILL2jevHl69dVXdf7553u3HzlyRJMnT9bGjRuVnZ0twzAk1V58zbdQA/hvBCIAZ7WdO3eqX79+uv766/X888+rZcuW3rBUUFCgpKQkLViwQEOGDJHkC1IA4I9TZgDOah07dtQ777yja665RiEhIXrssccUHR0tqfYPt/bo0UORkZHe8YQhACdDIAJw1hswYIDeffdd3XTTTTpw4IBGjhypHj166I033lBxcbESExObeokAmjlOmQE4Z6xfv16TJk3Snj17FBAQIJvNprfffls9e/Zs6qUBaOYIRADOKQ6HQyUlJSovL1d8fLz39BkA/BgCEQAAsDzuPQUAAJZHIAIAAJZHIAIAAJZHIAIAAJZHIAIAAJZHIAIAAJZHIAIAAJZHIAIAAJZHIAIAAJZHIAIAAJZHIAIAAJb3/wG54MAtD8ImNAAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "result = job.result()\n", + "\n", + "counts = result.measurement_counts()\n", + "\n", + "plot_histogram(counts)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we can see, the algorithm returns $\\ket{00000}$ 100% of the time, implying that our $f$ is constant. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we consider a balanced $f$. The f we will use is given by $$f(x) = \\begin{cases} 0 & x \\in [0, 7] \\\\ 1 & x \\in [8, 15]\\end{cases}.$$\n", + "\n", + "To construct $U_f$ that suffices $\\ket x \\ket y \\mapsto \\ket x \\ket{y \\oplus f(x)}$, we consider when $f(x) = 1$, which is exactly when the most significant bit (MSB) is also equal to 1. As such, our oracle just uses a controlled-NOT gate where the control qubit is the MSB of $x$ adn the target bit is $y$. " + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "def balanced_f(x):\n", + " if x in range(0, 2**n / 2):\n", + " return 0\n", + " return 1\n", + "\n", + "\n", + "@aq.gate\n", + "def balanced_oracle(q0: aq.Qubit, q1: aq.Qubit, q2: aq.Qubit, q3: aq.Qubit, q4: aq.Qubit):\n", + " ins.cnot(q3, q4)\n", + "\n", + "\n", + "@aq.main(num_qubits=n + 1)\n", + "def deutch_jozsa_balanced():\n", + " ins.x(4)\n", + " for i in range(n + 1):\n", + " ins.h(i)\n", + "\n", + " balanced_oracle(*range(n + 1))\n", + "\n", + " for i in range(n + 1):\n", + " ins.h(i)\n", + "\n", + " for i in range(n):\n", + " ins.measure(i)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "job_bal = device.run(deutch_jozsa_balanced, shots=1000)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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7nd3mYoLka6N983zZb6pSrd0k18vVhUaynUYusOvF1uOGFpz0NRtbVwKrXk35Vhe92P5gQMHlJ+f76116dJFTqfTe1JEktq3b6+QkBBt3LjRW0tMTFR8fLzWrl3rrUVHR6tNmza13o9/SqO+ZPbuu+/qoYce0vPPP6/OnTsrOztbkyZN0gsvvKAxY8Zo9erV6tOnjwoKChQfH+993siRI2UYhubPn69nn31Wc+fO1fbt22u9dkxMjKZNm6Z77733lHmdTqecTqf3scPhUGJiIpfMAAA4Sxr6klmjPkP00EMPacqUKRo1apSk6rS4d+9eTZ8+XWPGjFFcXJwkqbCwsFYgKiwsVNeuXSVJcXFxKioqqvW6J06cUHFxsff5/y4oKEhBQUFnYY8AAEBj1KjvITp27JhMs/YSbTab3O7q03QpKSmKi4vT0qVLvdsdDofWrFmj1NRUSVJqaqpKSkpqXbJatmyZ3G63evXqdQ72AgAANHaN+gzRDTfcoGeeeUatW7dW586d9d133+mFF17QXXfdJUkyDEOTJk3S008/rfbt2yslJUWPP/64EhISNGzYMElSx44dNXjwYN19992aNWuWqqqqNHHiRI0aNepnfcIMAABc+Bp1IHrllVf0+OOP6//9v/+noqIiJSQk6J577tHUqVO9Yx5++GEdPXpU48ePV0lJifr27atFixYpODjYO+add97RxIkTNXDgQJmmqREjRmjmzJkNsUsAAKARatQ3VTcWfA8RAABnV0PfVN2o7yECAAA4FwhEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8uoUiDZs2KBNmzZ5H3/66acaNmyYfv/736uysrLeFgcAAHAu1CkQ3XPPPcrJyZEk7dq1S6NGjdJFF12k999/Xw8//HC9LhAAAOBsq1MgysnJUdeuXSVJ77//vvr166d58+Zpzpw5+vDDD+tzfQAAAGddnQKRx+OR2+2WJC1Z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vbXTpm5RbnWL8s397i5TcSHStfG+eb7MN1Xplm7y68WqQkO5DkN3+PViyxFDaw6auq2NS6E176Ycy+t+LD9w4IDy8/O9tS5dusjpdConJ8dba9++vUJCQrRx40ZvLTExUfHx8Vq7dq23Fh0drTZt2tR6P/4pdbpk9sADD9R6XFVVpezsbG3evFljxozRyy+/fKYveVqBgYHq0aOHVq9e7a3df//9WrdunTIzM7V69Wr16dNHBQUFio+P944ZOXKkDMPQ/Pnz9eyzz2ru3Lnavn17rdeOiYnRtGnTdO+9954yr9PplNPp9D52OBxKTEzkkhkAAGdJQ18yq9MZohdffPG09SeeeKJeP70VHx+vTp061ap17NhRH374oSQpLi5OklRYWFgrEBUWFnpv+o6Li1NRUVGt1zhx4oSKi4u9z/93QUFBCgoKqq/dAAAAjVy93kP0m9/8pl7/jlmfPn1OObOTk5OjpKQkSVJKSori4uK0dOlS73aHw6E1a9YoNTVVkpSamqqSkpJal6yWLVsmt9utXr161dtaAQDA+avOf9z1dDIzM70fda8PDzzwgK666io9++yzGjlypNauXas33nhDb7zxhqTqj/hPmjRJTz/9tNq3b+/92H1CQoKGDRsmqfqM0uDBg3X33Xdr1qxZqqqq0sSJEzVq1Kif9QkzAABw4atTIBo+fHitxx6PRwcOHND69ev1+OOP18vCJKlnz576+OOP9eijj+rJJ59USkqKXnrpJWVkZHjHPPzwwzp69KjGjx+vkpIS9e3bV4sWLaoVzN555x1NnDhRAwcOlGmaGjFihGbOnFlv6wQAAOe3Ot1UPXbs2FqPTdNUixYtNGDAAA0aNKjeFtdY8D1EAACcXeflTdWzZ8+u08IAAAAao190D1FWVpa2bdsmSercubO6detWL4sCAAA4l+oUiIqKijRq1Ch98803ioyMlCSVlJSof//+evfdd9WiRYv6XCMAAMBZVaeP3d93330qKyvTli1bVFxcrOLiYm3evFkOh0P3339/fa8RAADgrKrTGaJFixZpyZIl6tixo7fWqVMnvfbaaxfkTdUAAODCVqczRG63W02aNDml3qRJE7nd7tM8AwAAoPGqUyAaMGCAfvvb36qgoMBb279/vx544AENHDiw3hYHAABwLtQpEL366qtyOBxKTk5W27Zt1bZtW6WkpMjhcOiVV16p7zUCAACcVXW6hygxMVEbNmzQkiVL9MMPP0iq/hMZaWlp9bo4AACAc+GMzhAtW7ZMnTp1ksPhkGEY+tWvfqX77rtP9913n3r27KnOnTvrX//619laKwAAwFlxRoHopZde0t13333ar7+OiIjQPffcoxdeeKHeFgcAAHAunFEg+v777zV48OD/uH3QoEHKysr6xYsCAAA4l84oEBUWFp724/YnBQQE6ODBg794UQAAAOfSGQWili1bavPmzf9x+8aNGxUfH/+LFwUAAHAunVEgGjJkiB5//HFVVFScsu348eP64x//qOuvv77eFgcAAHAunNHH7h977DF99NFH6tChgyZOnKiLL75YkvTDDz/otddek8vl0h/+8IezslAAAICz5YwCUWxsrFavXq17771Xjz76qDwejyTJMAylp6frtddeU2xs7FlZKAAAwNlyxl/MmJSUpC+++EJHjhzRzp075fF41L59ezVr1uxsrA8AAOCsq9M3VUtSs2bN1LNnz/pcCwAAQIOo098yAwAAuJAQiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOWdV4HoT3/6kwzD0KRJk7y1iooKTZgwQVFRUQoLC9OIESNUWFhY63l5eXkaOnSoLrroIsXExOihhx7SiRMnzvHqAQBAY3XeBKJ169bpL3/5iy677LJa9QceeECff/653n//fS1fvlwFBQUaPny4d7vL5dLQoUNVWVmp1atXa+7cuZozZ46mTp16rncBAAA0UudFICovL1dGRob++te/qlmzZt56aWmp/v73v+uFF17QgAED1L17d82ePVurV6/Wt99+K0n6+uuvtXXrVr399tvq2rWrrrvuOj311FN67bXXVFlZ2VC7BAAAGpHzIhBNmDBBQ4cOVVpaWq16VlaWqqqqatUvueQStW7dWpmZmZKkzMxMdenSRbGxsd4x6enpcjgc2rJly2nnczqdcjgctf4BAIALV0BDL+CnvPvuu9qwYYPWrVt3yja73a7AwEBFRkbWqsfGxsput3vH+Iehk9tPbjud6dOna9q0aafU169fr7CwMElS165dVVZWptzcXO/2Sy65RDabrVbQSk5OVlRUlLKysmrNn5SUpOzsbFVWVuquDi7tO2ro6/2mBrdyKeGi6nFlVdL7u21KjXGrY6TH+/zZOaY6RnrUO8ZX+2iPqdAAKb2V21tbWmDqcIU0so2vlnXI0PfFpu5s75JpVNd2OAz9y27q5iSXmgVV14oqpAV5Ng1IcCs5rHoep1t6Z6dN3aPdury5b+55uaZahkrXxPnmWZhvyu2Rbmjtq60sNLS7zNDodr7apiOG1h009eu2LoXYqmt55YaWFJgakuhSXEh1rbRK+nC3TX1i3bo4wjf3P3JsurSZW1e28NU+3GOqaRNpUEvfPEv2myqplG5J8dXWHTS06YipsR1cqmmFckoNrSw0NTzZpcjA6lrhcWlhvk0DE9xKqulFhUual2tTj2i3LvPrxTs7TSWGedQvzldbkFf9e8f1fr1YYTeUX24ow68XG4sNrT9k6va2LgXX9GJvuaGlBaaGJroUW9OLkkrpoz029Y11q0NNLzySZufY1KWZWz39evHBblORgVKaXy++3m+qrEoakeyrrT1oaPMRU3d1cHlr20sNrSo0NSLFpYgm1TX7cemLfJvSEtxqXdOL4y7pn7k29WzhVpdmvrnf2mkqpalHfWN9tc/zTJmGNDTRN/dyu6n9R6Xb2/pq3xcbyjpkKqOdS0E1v7btKTe0rMDU9a1digmurh1xSh/vtenqOLfah1fP4/ZIc3bYdHlzt7pH++Z+b5epqGBpYIJvnq/2mTp6Qhru14tviwxtKzE0toOvtq3EUGaRqVtTXGpa04uCY9KifTYNaulWq9DqeY6ekObvsqlXC7c6+/XizR2m2oZ71MevF5/uNRVoStf59eKbA6bsx6VRfj+z3x029N1hU6PbudSkphe7ygx9c8DUja1diq7pxWGn9Olem66Jc6ttTS9OeKQ3d9jUtblbV/j1Yv4uUy2CpQF+vVi0z1SFSxqW5KtlFhnaXmrozva+2tYSQ98WmRqZ4lJYTS/2HzP01T5T6a3canlR9TzlVdJ7u23qHeNWJ7/j15wdpi6O8CjV7/j1yV5TwTZpsN/xa1mBqYMV0m1+vdhwyFB2sak72rsUUPNDm+swtNxu6qYkl6Jqjl+HKqTP8my6Nt6tNk2r56lyS2/ttKlblFvdonxzv7vLVFyIdG28b54v801VuqWb/HqxqtBQrsPQHX692HLE0JqDpm5r41Jozbspx/K6H8sPHDig/Px8b61Lly5yOp3Kycnx1tq3b6+QkBBt3LjRW0tMTFR8fLzWrl3rrUVHR6tNmzb/8cTH6Rgej8fz08MaRn5+vnr06KHFixd77x269tpr1bVrV7300kuaN2+exo4dK6fTWet5V155pfr376/nnntO48eP1969e/XVV195tx87dkyhoaH64osvdN11150yr9PprPWaDodDiYmJKi0tVXh4eL3vZ/KUhfX+mgAAnE/2/Glovb+mw+FQRETEz3r/btSXzLKyslRUVKQrrrhCAQEBCggI0PLlyzVz5kwFBAQoNjZWlZWVKikpqfW8wsJCxcXFSZLi4uJO+dTZyccnx/y7oKAghYeH1/oHAAAuXI06EA0cOFCbNm1Sdna291+PHj2UkZHh/d9NmjTR0qVLvc/Zvn278vLylJqaKklKTU3Vpk2bVFRU5B2zePFihYeHq1OnTud8nwAAQOPTqO8hatq0qS699NJatdDQUEVFRXnr48aN0+TJk9W8eXOFh4frvvvuU2pqqnr37i1JGjRokDp16qTRo0drxowZstvteuyxxzRhwgQFBQWd830CAACNT6MORD/Hiy++KNM0NWLECDmdTqWnp+vPf/6zd7vNZtOCBQt07733KjU1VaGhoRozZoyefPLJBlw1AABoTBr1TdWNxZnclFUX3FQNALA6bqoGAABoYAQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQiAABgeQQi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7B1rs9m0YMEC3XvvvUpNTVVoaKjGjBmjJ5988lztBgAAaOTOm5uqGxLfQwQAwNnV0DdVN+p7iAAAAM4FAhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8AhEAALA8SwWi1157TcnJyQoODlavXr20du3ahl4SAABoBCwTiObPn6/Jkyfrj3/8ozZs2KDLL79c6enpKioqauilAQCABhbQ0As4V1544QXdfffdGjt2rCRp1qxZWrhwof7xj39oypQptcY6nU45nU7v49LSUkmSw+E4K2tzO4+dldcFAOB8cTbeY0++psfj+cmxlghElZWVysrK0qOPPuqtmaaptLQ0ZWZmnjJ++vTpmjZt2in1xMTEs7pOAACsKuKls/faZWVlioiI+NExlghEhw4dksvlUmxsbK16bGysfvjhh1PGP/roo5o8ebL3sdvtVnFxsaKiomQYxllfL4Bzx+FwKDExUfn5+QoPD2/o5QCoRx6PR2VlZUpISPjJsZYIRGcqKChIQUFBtWqRkZENsxgA50R4eDiBCLgA/dSZoZMscVN1dHS0bDabCgsLa9ULCwsVFxfXQKsCAACNhSUCUWBgoLp3766lS5d6a263W0uXLlVqamoDrgwAADQGlrlkNnnyZI0ZM0Y9evTQlVdeqZdeeklHjx71fuoMgDUFBQXpj3/84ymXyQFYi+H5OZ9Fu0C8+uqrev7552W329W1a1fNnDlTvXr1auhlAQCABmapQAQAAHA6lriHCAAA4McQiAAAgOURiAAAgOURiAAAgOURiAAAgOURiAAAgOURiADgZ6ioqFBZWVmtmtvtbqDVAKhvBCIA+AmbN2/WTTfd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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "result_bal = job_bal.result()\n", + "\n", + "counts_bal = result_bal.measurement_counts()\n", + "\n", + "plot_histogram(counts_bal)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We see here that the probability of measuring $\\ket{00000}$ is 0. This correctly predicts that our $f$ is balanced." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "qir", + "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.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/qbraid-autoqasm-poc.ipynb b/examples/qbraid-autoqasm-poc.ipynb new file mode 100644 index 0000000..5e36bd3 --- /dev/null +++ b/examples/qbraid-autoqasm-poc.ipynb @@ -0,0 +1,424 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f2f3000f-4271-421a-8ed5-d0fbd7af8629", + "metadata": { + "tags": [] + }, + "source": [ + "# AutoQASM x qBraid-QIR integration POC\n", + "\n", + "\n", + "## Overview\n", + "\n", + "This proof of concept demonstrates one potential approach to the integration between the AutoQASM and qBraid open-source projects, aiming to facilitate seamless conversion from AutoQASM to Quantum Intermediate Representation (QIR). This integration would enhance the interoperability of all involved quantum software frameworks and enable submission of quantum jobs to QIR-compatible devices using AutoQASM.\n", + "\n", + "## Resources\n", + "\n", + "\n", + "[OpenQASM](https://openqasm.com/): Quantum assembly language that enables complex quantum programming by supporting classical logic, timing controls, and procedural programming within quantum circuits.\n", + "\n", + "[QIR](https://www.qir-alliance.org/): Quantum intermediate representation based on LLVM, designed to facilitate optimization and cross-platform compatibility between different quantum processors and development tools.\n", + "\n", + "[AutoQASM](https://github.com/amazon-braket/autoqasm): Experimental Python-embedded module designed to enable a Pythonic, imperative programming experience for constructing OpenQASM programs.\n", + "\n", + "[qBraid-SDK](https://github.com/qBraid/qBraid): Platform-agnostic quantum runtime framework designed to streamline the full lifecycle management of quantum jobs—from defining program specifications, to job submission, and through to processing of results.\n", + "\n", + "[qBraid-QIR](): qBraid-SDK extension providing support for QIR conversions, including OpenQASM 3 $\\rightarrow$ QIR. \n", + "\n", + "\n", + "## Goals\n", + "\n", + "**Enhance Interoperability**: Ensure that AutoQASM seamlessly interacts with other quantum computing platforms to broaden its application scope.\n", + "\n", + "**Submit Quantum Jobs**: Facilitate the submission of quantum jobs written in AutoQASM to the remote qBraid QIR simulator (June 2024), leveraging the strengths of both systems to provide a great end-user experience." + ] + }, + { + "cell_type": "markdown", + "id": "b99d4134-30ae-476b-b1c5-b2f8a1d5bdee", + "metadata": {}, + "source": [ + "Run this notebook on [lab.qbraid.com](https://lab.qbraid.com) with environment access code:`592ca9a2-0881-41c4-bbf1-4b27d604afb1`" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "6d5737d1-b6a7-47ec-bae0-c0499ccdbdf1", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import warnings\n", + "\n", + "warnings.filterwarnings(\n", + " \"ignore\",\n", + " message=\"networkx backend defined more than once: nx-loopback\",\n", + " category=RuntimeWarning,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "bc44ec7c-3a25-4f0e-b7bd-2eb633743ed4", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import re\n", + "\n", + "import autoqasm as aq\n", + "from autoqasm.instructions import cnot, h, measure\n", + "\n", + "import qbraid\n", + "from qbraid.passes.qasm3.compat import add_stdgates_include, insert_gate_def\n", + "from qbraid.programs import QPROGRAM_REGISTRY, register_program_type\n", + "from qbraid.runtime.native import QbraidProvider\n", + "from qbraid.transpiler import Conversion, ConversionGraph, transpile\n", + "from qbraid.visualization.plot_counts import plot_histogram" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "9af4d088-bae1-41db-9cb6-1d26aa447eb6", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "qBraid-SDK: A platform-agnostic quantum runtime framework\n", + "======================================================================\n", + "(C) 2024 qBraid Development Team (https://github.com/qBraid/qBraid)\n", + "\n", + "qbraid:\t0.7.0.dev\n", + "\n", + "Core Dependencies\n", + "-----------------\n", + "networkx: 3.2.1\n", + "openqasm3: 0.5.0\n", + "numpy: 1.26.4\n", + "ply: 3.11\n", + "\n", + "Optional Dependencies\n", + "---------------------\n", + "qbraid_core: 0.1.6\n", + "qbraid_qir: 0.2.0\n", + "braket: 1.79.1.dev0\n", + "\n", + "Python: 3.9.18\n", + "Platform: Linux (x86_64)\n" + ] + } + ], + "source": [ + "qbraid.about()" + ] + }, + { + "cell_type": "markdown", + "id": "e86eec25-d4f2-415e-a6b3-39285acff1bb", + "metadata": {}, + "source": [ + "### OpenQASM 3 IR compatibility\n", + "\n", + "The following function is where most of the collaboration will need to take place. In this function, we need to convert the OpenQASM 3 programs generated from AutoQASM into a format compatible with the qBraid-QIR qasm3 converter.\n", + "\n", + "\n", + "There are some OpenQASM 3 operations, such as pulse-level operations, that are not supported by QIR which we can omit for the time being. A good first step would be to ensure coverage for the entire OpenQASM 3 standard gate set. Within this gate set, there are a number of discrepancies between the AutoQASM output IR format and the supported qBraid-QIR OpenQASM 3 program input for gates like the S, T, square root of X, and phase gates, as well as in the ways measurement and qubit state initialization are handled.\n", + "\n", + "\n", + "Below is a basic, proof-of-concept implementation that shows what this type of bridge might look like, designed solely for internal demonstration purposes. Again, this cross-compatibility function will be central to the success of the integration." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "3e8e6da9-6128-4195-9ef5-2d8247d3b5c9", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def _process_qasm(qasm: str) -> str:\n", + " \"\"\"\n", + " Convert OpenQASM 3 string to a format that\n", + " will be accepted by the qbraid-qir converter.\n", + "\n", + " Args:\n", + " qasm (str): The input QASM string to process.\n", + "\n", + " Returns:\n", + " The processed QASM string.\n", + "\n", + " \"\"\"\n", + " # Regular expression to remove initialization to zeros\n", + " pattern = r'(bit\\[\\d+\\] +__bit_\\d+__)\\s+=\\s+\"[0]+\"(;)'\n", + "\n", + " # Transform each line, removing zero initializations\n", + " transformed_lines = [re.sub(pattern, r\"\\1\\2\", line) for line in qasm.split(\"\\n\")]\n", + "\n", + " # Rejoin the transformed lines back into a single string\n", + " qasm = \"\\n\".join(transformed_lines)\n", + "\n", + " # Replace specific keywords with comments in a single step to avoid multiple replacements\n", + " qasm = re.sub(r\"^(output|return_value =)\", r\"// \\1\", qasm, flags=re.MULTILINE)\n", + "\n", + " # Insert and replace various gate definitions for compatibility\n", + " qasm = add_stdgates_include(qasm)\n", + " qasm = insert_gate_def(qasm, \"iswap\")\n", + " qasm = insert_gate_def(qasm, \"sxdg\")\n", + "\n", + " return qasm\n", + "\n", + "\n", + "def autoqasm_to_qasm3(program: aq.program.MainProgram) -> str:\n", + " \"\"\"\n", + " Convert AutoQASM program to OpenQASM 3 string in a format that\n", + " will be accepted by the qbraid-qir converter.\n", + "\n", + " \"\"\"\n", + " qasm = program.build().to_ir()\n", + "\n", + " return _process_qasm(qasm)" + ] + }, + { + "cell_type": "markdown", + "id": "223e0b33-79c0-44be-8363-2ec08432a86d", + "metadata": {}, + "source": [ + "### Registering AutoQASM program type with qBraid conversion graph" + ] + }, + { + "cell_type": "markdown", + "id": "2bd65768-cb0f-492e-acfe-b1fbb378ae40", + "metadata": {}, + "source": [ + "The qBraid-SDK does not assume a fixed input or output/target software framework. Instead, it allows providers to dynamically register any desired run input program type as the target, depending on their specific needs." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "76fd47aa-bbb1-477c-8226-7915c222348c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "register_program_type(aq.program.MainProgram)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "0bfecc00-c00d-406f-94e1-b77558e2772a", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'braket': braket.circuits.circuit.Circuit,\n", + " 'openqasm3': openqasm3.ast.Program,\n", + " 'pyqir': Module,\n", + " 'qasm2': str,\n", + " 'qasm3': str,\n", + " 'autoqasm': autoqasm.program.program.MainProgram}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "QPROGRAM_REGISTRY" + ] + }, + { + "cell_type": "markdown", + "id": "168396ea-fa61-48c7-8835-ebdd6ea70af7", + "metadata": {}, + "source": [ + "Registered program types are then interconnected via a graph-based transpiler, where each program type is represented as a node and supported conversions as edges. The breadth, depth, and connectivity can be customized by the provider." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "08eca693-942d-4c49-95a7-a940443a3710", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "image/png": "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s88+S+3atfn6669NZRo2bEhCQgLHjh0DICwsjE8++YTevXubynz66ads2LCB/fv359v1QAmZjlgIUbxcjU8uUokAGGsHrt1LpqqPC7aPM3lZZCTcuJHvceW35557LssdroeHh+n/tra2LF26lGrVqlG2bFm+/PLLXB+7du3aZj8nJCQwceJE1q9fT0REBBkZGSQnJ5tqBlq2bEnZsmUJCQmhTZs2tGnThi5duuDgkP1kUE5O/y07369fP2bPnp1jLF9++SUtWrQw2xYQEADA6dOn6dKli9lz9erVY9OmTaafz549yyuvvGJW5plnnmHHjh0AJCYmEh4eztChQxk+fLipTEZGBq6urjnGpSZJBoQQhepiEekr8CCDYkxkwtwd876zn1/+B1QA53V0dCQsLOyhZTLvaqOjo4mOjsbRMXevx4Plxo4dy9atW5k6dSphYWHY29vTrVs30tKMk085Oztz5MgRdu3axZYtW/jwww+ZOHEihw8fzrazX+YdOYCLi8tDY/Hz83vkdT6JhATjCJR58+ZRt25ds+cerJGwFJIMCCEKTWqGgXtpGWqHkSsGg4Fda37h4NYNXDp9koS4WAICyzKkf1/Gjh2bt+rYXFbVW7rw8HDeeOMN5s2bx/Llyxk4cCDbtm1DqzXWltja2qLX526lyX379jFo0CDTXXhCQgKXL182K2NtbU2LFi1o0aIFEyZMwM3NjR07dtC1a9csx8uvL/dKlSpx8OBBs20HDhww+7lChQocPnyYAQMGmLYdPnzY9H9fX18CAgK4ePEiffv2zZe4CpokA0KIQhObWnQ64aUmJ/PNe29QvnotWvUagKuHFxeO/82ECRPYvn07O3bsQKMpKt0gcyc1NZXIB/o2WFtb4+XlhV6vp1+/frRu3ZrBgwfTpk0bqlatyrRp03jrrbcACAoK4uDBg1y+fBknJyezJoYHlStXjtWrV9OhQwc0Gg3jx4/HYPhvqOnvv//OxYsXady4Me7u7mzYsAGDwUCFChWe+DpjY2OzXKezszOOjo689tprNGjQgKlTp9KpUyc2b95s1kQAMGrUKIYPH07t2rWpX78+y5cv559//iEkJMRU5qOPPuK1117D1dWVNm3akJqayl9//UVMTAxvvvnmE19DfpPRBEKIQhOTkl5kRhFY29gwadlapiz/jW4vv07LHn0ZMWk6748fz65d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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "graph = ConversionGraph()\n", + "\n", + "aq_conversion = Conversion(\"autoqasm\", \"qasm3\", autoqasm_to_qasm3)\n", + "\n", + "graph.add_conversion(aq_conversion)\n", + "\n", + "graph.plot(legend=True)" + ] + }, + { + "cell_type": "markdown", + "id": "1f74a345-d794-4f12-bc80-80b47f94d2d2", + "metadata": {}, + "source": [ + "### Submitting AutoQASM program to qBraid QIR simulator" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "b1462227-284e-4ad2-818a-3e32c706b45d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "provider = QbraidProvider()\n", + "\n", + "device = provider.get_device(\"qbraid_qir_simulator\")\n", + "\n", + "device.update_scheme(conversion_graph=graph)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "10c9649e-a8d4-497c-a6e7-6a355a6e5b25", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "@aq.main\n", + "def bell_state():\n", + " h(0)\n", + " cnot(0, 1)\n", + " return measure([0, 1])" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "62fa4dff-fc4f-4380-92a1-ba8f27dcc7f7", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "job = device.run(bell_state, shots=100)\n", + "\n", + "job.status()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "0df2f88e-b4bf-4885-9538-ddf5e70cf808", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "image/png": "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q/sAAAA4Y9/oHpmCggL961//ktT6d5JGjRrVJU0BAACcjk4FmQMHDmjWrFlat26d76PRR44c0cSJE7Vy5Ur17du3K3sEAAA4oU59/Pree+9VfX29duzYoUOHDunQoUPavn27nE6nfvzjH3d1jwAAACfUqTMy7777rtauXashQ4b4akOHDtXixYvP6GZfAACAb6JTZ2Q8Ho+CgoKOqwcFBcnj8ZzgGQAAAF2vU0Fm0qRJ+slPfqLKykpfbf/+/Zo7d64uu+yyLmsOAADgVDoVZP7rv/5LTqdTycnJGjhwoAYOHKiUlBQ5nU796U9/6uoeAQAATqhT98gkJCRo8+bNWrt2rb744gtJ0pAhQzR58uQubQ4AAOBUzuiMzAcffKChQ4fK6XTKMAx95zvf0b333qt7771X2dnZGjZsmD766KOz1SsAAEAHZxRkFi1apDvvvFMRERHHLYuMjNSPfvQjPfHEE13WHAAAwKmcUZDZsmWLrrjiipMuv/zyy1VQUPCNmwIAADgdZxRkampqTvix62N69Oih2trab9wUAADA6TijINO/f39t3779pMu3bt2quLi4b9wUAADA6TijIHPVVVfpoYceUlNT03HLjh49qt/85je6+uqru6w5AACAUzmjj1//+te/1quvvqrBgwfrnnvuUXp6uiTpiy++0OLFi+V2u/WrX/3qrDQKAADwf51RkLHb7fr0009111136cEHH5TX65UkGYahKVOmaPHixbLb7WelUQAAgP/rjL8QLykpSW+//bYOHz6skpISeb1epaWlKSoq6mz0BwAAcFKd+mZfSYqKilJ2dnZX9gIAAHBGOvW3lgAAALoDggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALCsgAaZBQsWKDs7W7169VK/fv10/fXXq6ioqMM6TU1NuvvuuxUdHa3w8HBNnz5dNTU1AeoYAAB0JwENMnl5ebr77ru1YcMGvf/++2ppadHll1+uxsZG3zpz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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "result = job.result()\n", + "\n", + "counts = result.measurement_counts()\n", + "\n", + "plot_histogram(counts)" + ] + }, + { + "cell_type": "markdown", + "id": "7edaff03-26c3-4370-9f91-8b028b625732", + "metadata": {}, + "source": [ + "
\n", + "Copyright Notice: \n", + " All rights reserved © [2024] qBraid. This notebook is part of the qBraid-SDK.\n", + "The qBraid-SDK is free software released under the GNU General Public License v3\n", + "or later. You can redistribute and/or modify it under the terms of the GPL v3.\n", + "See the LICENSE file in the project root or . THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3.\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0a44c223-fef0-4f58-8451-cf075a154cd7", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 [AutoQASM]", + "language": "python", + "name": "python3_autoqa_8a87m9" + }, + "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.9.18" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pyproject.toml b/pyproject.toml index 97b0255..96612d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ Discord = "https://discord.gg/TPBU2sa8Et" [project.optional-dependencies] cirq = ["cirq-core>=1.3.0,<1.5.0"] qasm3 = ["openqasm3[parser]>=0.4.0,<1.1.0"] -test = ["qbraid>=0.7.1,<0.8.0", "pytest", "pytest-cov"] +test = ["qbraid>=0.7.1,<0.8.0", "pytest", "pytest-cov", "autoqasm>=0.1.0"] lint = ["black[jupyter]", "isort", "pylint", "qbraid-cli>=0.8.3"] docs = ["sphinx>=7.3.7,<7.5.0", "sphinx-autodoc-typehints>=1.24,<2.3", "sphinx-rtd-theme~=2.0.0", "docutils<0.22", "sphinx-copybutton"] diff --git a/qbraid_qir/__init__.py b/qbraid_qir/__init__.py index 20bb103..9d38469 100644 --- a/qbraid_qir/__init__.py +++ b/qbraid_qir/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # # This file is part of the qBraid-SDK # diff --git a/qbraid_qir/_version.py b/qbraid_qir/_version.py index 2f946b1..65be486 100644 --- a/qbraid_qir/_version.py +++ b/qbraid_qir/_version.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # # This file is part of the qBraid-SDK # diff --git a/qbraid_qir/cirq/__init__.py b/qbraid_qir/cirq/__init__.py index 3ef8f1d..c3786b5 100644 --- a/qbraid_qir/cirq/__init__.py +++ b/qbraid_qir/cirq/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # # This file is part of the qBraid-SDK # diff --git a/qbraid_qir/cirq/convert.py b/qbraid_qir/cirq/convert.py index b079397..684f811 100644 --- a/qbraid_qir/cirq/convert.py +++ b/qbraid_qir/cirq/convert.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # # This file is part of the qBraid-SDK # diff --git a/qbraid_qir/cirq/elements.py b/qbraid_qir/cirq/elements.py index 5b8461d..360d6f2 100644 --- a/qbraid_qir/cirq/elements.py +++ b/qbraid_qir/cirq/elements.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # # This file is part of the qBraid-SDK # diff --git a/qbraid_qir/cirq/exceptions.py b/qbraid_qir/cirq/exceptions.py index 7e6d98d..ea2884a 100644 --- a/qbraid_qir/cirq/exceptions.py +++ b/qbraid_qir/cirq/exceptions.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # # This file is part of the qBraid-SDK # diff --git a/qbraid_qir/cirq/opsets.py b/qbraid_qir/cirq/opsets.py index 3cae011..5e38fac 100644 --- a/qbraid_qir/cirq/opsets.py +++ b/qbraid_qir/cirq/opsets.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # # This file is part of the qBraid-SDK # diff --git a/qbraid_qir/cirq/passes.py b/qbraid_qir/cirq/passes.py index 29dc135..baad1e2 100644 --- a/qbraid_qir/cirq/passes.py +++ b/qbraid_qir/cirq/passes.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # # This file is part of the qBraid-SDK # diff --git a/qbraid_qir/cirq/visitor.py b/qbraid_qir/cirq/visitor.py index 10522da..3f82024 100644 --- a/qbraid_qir/cirq/visitor.py +++ b/qbraid_qir/cirq/visitor.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # # This file is part of the qBraid-SDK # diff --git a/qbraid_qir/exceptions.py b/qbraid_qir/exceptions.py index fec6d80..b3d8ad3 100644 --- a/qbraid_qir/exceptions.py +++ b/qbraid_qir/exceptions.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # # This file is part of the qBraid-SDK # diff --git a/qbraid_qir/qasm3/__init__.py b/qbraid_qir/qasm3/__init__.py index 0351180..6bff53f 100644 --- a/qbraid_qir/qasm3/__init__.py +++ b/qbraid_qir/qasm3/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # # This file is part of the qBraid-SDK # diff --git a/qbraid_qir/qasm3/convert.py b/qbraid_qir/qasm3/convert.py index 83b7167..8531fc9 100644 --- a/qbraid_qir/qasm3/convert.py +++ b/qbraid_qir/qasm3/convert.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # # This file is part of the qBraid-SDK # diff --git a/qbraid_qir/qasm3/elements.py b/qbraid_qir/qasm3/elements.py index 7da74b8..cc4dfb7 100644 --- a/qbraid_qir/qasm3/elements.py +++ b/qbraid_qir/qasm3/elements.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # # This file is part of the qBraid-SDK # diff --git a/qbraid_qir/qasm3/exceptions.py b/qbraid_qir/qasm3/exceptions.py index b7d1339..c44ed14 100644 --- a/qbraid_qir/qasm3/exceptions.py +++ b/qbraid_qir/qasm3/exceptions.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # # This file is part of the qBraid-SDK # diff --git a/qbraid_qir/qasm3/linalg.py b/qbraid_qir/qasm3/linalg.py new file mode 100644 index 0000000..b0a66a6 --- /dev/null +++ b/qbraid_qir/qasm3/linalg.py @@ -0,0 +1,302 @@ +# Copyright (C) 2024 qBraid +# +# This file is part of the qBraid-SDK +# +# The qBraid-SDK is free software released under the GNU General Public License v3 +# or later. You can redistribute and/or modify it under the terms of the GPL v3. +# See the LICENSE file in the project root or . +# +# THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3. + +# pylint: disable=too-many-locals + +""" +Module for linear algebra functions necessary for gate decomposition. + +""" + +import cmath +import functools +import math + +import numpy as np + +MAGIC = np.array([[1, 0, 0, 1j], [0, 1j, 1, 0], [0, 1j, -1, 0], [1, 0, 0, -1j]]) * np.sqrt(0.5) + +MAGIC_CONJ_T = np.conj(MAGIC.T) + +KAK_MAGIC = np.array([[1, 0, 0, 1j], [0, 1j, 1, 0], [0, 1j, -1, 0], [1, 0, 0, -1j]]) * np.sqrt(0.5) + +KAK_MAGIC_DAG = np.conjugate(np.transpose(KAK_MAGIC)) + +KAK_GAMMA = np.array([[1, 1, 1, 1], [1, 1, -1, -1], [-1, 1, -1, 1], [1, -1, -1, 1]]) * 0.25 + + +def _helper_svd(mat): + """ + Helper function to perform SVD on a matrix. + """ + if mat.size == 0: + return np.zeros((0, 0), dtype=mat.dtype), np.array([]), np.zeros((0, 0), dtype=mat.dtype) + return np.linalg.svd(mat) + + +def _merge_dtypes(dtype1, dtype2): + return (np.zeros(0, dtype1) + np.zeros(0, dtype2)).dtype + + +def _block_diag(*blocks): + """ + Helper function to perform block diagonalization. + """ + n = sum(b.shape[0] for b in blocks) + dtype = functools.reduce(_merge_dtypes, (b.dtype for b in blocks)) + + result = np.zeros((n, n), dtype=dtype) + i = 0 + for b in blocks: + j = i + b.shape[0] + result[i:j, i:j] = b + i = j + + return result + + +def _orthogonal_diagonalize(symmetric_matrix, diagonal_matrix): + """ + Find orthogonal matrix that diagonalize mat1 and mat2. + """ + + def similar_singular(i, j): + return np.allclose(diagonal_matrix[i, i], diagonal_matrix[j, j]) + + ranges = [] + start = 0 + while start < diagonal_matrix.shape[0]: + past = start + 1 + while past < diagonal_matrix.shape[0] and similar_singular(start, past): + past += 1 + ranges.append((start, past)) + start = past + + p = np.zeros(symmetric_matrix.shape, dtype=np.float64) + for start, end in ranges: + block = symmetric_matrix[start:end, start:end] + _, res = np.linalg.eigh(block) + p[start:end, start:end] = res + + return p + + +def orthogonal_bidiagonalize(mat1, mat2): + """ + Find orthogonal matrices that diagonalize mat1 and mat2. + """ + atol = 1e-9 + base_left, base_diag, base_right = _helper_svd(mat1) + base_diag = np.diag(base_diag) + + dim = base_diag.shape[0] + rank = dim + while rank > 0 and np.all(np.less_equal(np.abs(base_diag[rank - 1, rank - 1]), atol)): + rank -= 1 + base_diag = base_diag[:rank, :rank] + + semi_corrected = np.dot(np.dot(base_left.T, np.real(mat2)), base_right.T) + + overlap = semi_corrected[:rank, :rank] + overlap_adjust = _orthogonal_diagonalize(overlap, base_diag) + + extra = semi_corrected[rank:, rank:] + extra_left_adjust, _, extra_right_adjust = _helper_svd(extra) + + left_adjust = _block_diag(overlap_adjust, extra_left_adjust) + right_adjust = _block_diag(overlap_adjust.T, extra_right_adjust) + left = np.dot(left_adjust.T, base_left.T) + right = np.dot(base_right.T, right_adjust.T) + return left, right + + +def _kronecker_fator(mat): + """ + Split U = kron(A, B) to A and B. + """ + a, b = max(((i, j) for i in range(4) for j in range(4)), key=lambda t: abs(mat[t])) + + f1 = np.zeros((2, 2), dtype=mat.dtype) + f2 = np.zeros((2, 2), dtype=mat.dtype) + for i in range(2): + for j in range(2): + f1[(a >> 1) ^ i, (b >> 1) ^ j] = mat[a ^ (i << 1), b ^ (j << 1)] + f2[(a & 1) ^ i, (b & 1) ^ j] = mat[a ^ i, b ^ j] + + with np.errstate(divide="ignore", invalid="ignore"): + f1 /= np.sqrt(np.linalg.det(f1)) or 1 + f2 /= np.sqrt(np.linalg.det(f2)) or 1 + + g = mat[a, b] / (f1[a >> 1, b >> 1] * f2[a & 1, b & 1]) + if np.real(g) < 0: + f1 *= -1 + g *= -1 + + return g, f1, f2 + + +def _so4_to_su2(mat): + """ + Decompose SO(4) matrix to SU(2) matrices. + """ + ab = np.dot(np.dot(MAGIC, mat), MAGIC_CONJ_T) + _, a, b = _kronecker_fator(ab) + + return a, b + + +def _kak_canonicalize_vector(x, y, z): + """ + Canonicalize vector for KAK decomposition. + """ + phase = [complex(1)] + left = [np.eye(2)] * 2 + right = [np.eye(2)] * 2 + v = [x, y, z] + + flippers = [ + np.array([[0, 1], [1, 0]]) * 1j, + np.array([[0, -1j], [1j, 0]]) * 1j, + np.array([[1, 0], [0, -1]]) * 1j, + ] + + swappers = [ + np.array([[1, -1j], [1j, -1]]) * 1j * np.sqrt(0.5), + np.array([[1, 1], [1, -1]]) * 1j * np.sqrt(0.5), + np.array([[0, 1 - 1j], [1 + 1j, 0]]) * 1j * np.sqrt(0.5), + ] + + def shift(k, step): + v[k] += step * np.pi / 2 + phase[0] *= 1j**step + right[0] = np.dot(flippers[k] ** (step % 4), right[0]) + right[1] = np.dot(flippers[k] ** (step % 4), right[1]) + + def negate(k1, k2): + v[k1] *= -1 + v[k2] *= -1 + phase[0] *= -1 + s = flippers[3 - k1 - k2] + left[1] = np.dot(left[1], s) + right[1] = np.dot(s, right[1]) + + def swap(k1, k2): + v[k1], v[k2] = v[k2], v[k1] + s = swappers[3 - k1 - k2] + left[0] = np.dot(left[0], s) + left[1] = np.dot(left[1], s) + right[0] = np.dot(s, right[0]) + right[1] = np.dot(s, right[1]) + + def canonical_shift(k): + while v[k] <= -np.pi / 4: + shift(k, +1) + while v[k] > np.pi / 4: + shift(k, -1) + + def sort(): + if abs(v[0]) < abs(v[1]): + swap(0, 1) + if abs(v[1]) < abs(v[2]): + swap(1, 2) + if abs(v[0]) < abs(v[1]): + swap(0, 1) + + canonical_shift(0) + canonical_shift(1) + canonical_shift(2) + sort() + + if v[0] < 0: + negate(0, 2) + if v[1] < 0: + negate(1, 2) + canonical_shift(2) + + atol = 1e-9 + if v[0] > np.pi / 4 - atol and v[2] < 0: + shift(0, -1) + negate(0, 2) + + return { + "single_qubit_operations_after": (left[1], left[0]), + "single_qubit_operations_before": (right[1], right[0]), + } + + +def _deconstruct_matrix_to_angles(mat): + """ + Decompose matrix into angles. + """ + + def _phase_matrix(angle): + return np.diag([1, np.exp(1j * angle)]) + + def _rotation_matrix(angle): + c, s = np.cos(angle), np.sin(angle) + return np.array([[c, -s], [s, c]]) + + right_phase = cmath.phase(mat[0, 1] * np.conj(mat[0, 0])) + np.pi + mat = np.dot(mat, _phase_matrix(-right_phase)) + + bottom_phase = cmath.phase(mat[1, 0] * np.conj(mat[0, 0])) + mat = np.dot(_phase_matrix(-bottom_phase), mat) + + rotation = math.atan2(abs(mat[1, 0]), abs(mat[0, 0])) + mat = np.dot(_rotation_matrix(-rotation), mat) + + diagonal_phase = cmath.phase(mat[1, 1] * np.conj(mat[0, 0])) + + return right_phase + diagonal_phase, rotation * 2, bottom_phase + + +def so_bidiagonalize(mat): + """ + Find special orthogonal L and R so that L @ mat @ R is diagonal. + """ + left, right = orthogonal_bidiagonalize(np.real(mat), np.imag(mat)) + with np.errstate(divide="ignore", invalid="ignore"): + if np.linalg.det(left) < 0: + left[0, :] *= -1 + if np.linalg.det(right) < 0: + right[:, 0] *= -1 + + diag = np.dot(np.dot(left, mat), right) + + return left, np.diag(diag), right + + +def kak_decomposition_angles(mat): + """ + Decompose matrix into KAK decomposition, return all angles. + """ + left, d, right = so_bidiagonalize(KAK_MAGIC_DAG @ mat @ KAK_MAGIC) + + a1, a0 = _so4_to_su2(left.T) + b1, b0 = _so4_to_su2(right.T) + _, x, y, z = (KAK_GAMMA @ np.angle(d).reshape(-1, 1)).flatten() + + inner_cannon = _kak_canonicalize_vector(x, y, z) + b1 = np.dot(inner_cannon["single_qubit_operations_before"][0], b1) + b0 = np.dot(inner_cannon["single_qubit_operations_before"][1], b0) + a1 = np.dot(a1, inner_cannon["single_qubit_operations_after"][0]) + a0 = np.dot(a0, inner_cannon["single_qubit_operations_after"][1]) + + pre_phase00, rotation00, post_phase00 = _deconstruct_matrix_to_angles(b1) + pre_phase01, rotation01, post_phase01 = _deconstruct_matrix_to_angles(b0) + pre_phase10, rotation10, post_phase10 = _deconstruct_matrix_to_angles(a1) + pre_phase11, rotation11, post_phase11 = _deconstruct_matrix_to_angles(a0) + + return [ + [rotation00, post_phase00, pre_phase00], + [rotation01, post_phase01, pre_phase01], + [rotation10, post_phase10, pre_phase10], + [rotation11, post_phase11, pre_phase11], + ] diff --git a/qbraid_qir/qasm3/oq3_maps.py b/qbraid_qir/qasm3/oq3_maps.py index ee845c6..c51f372 100644 --- a/qbraid_qir/qasm3/oq3_maps.py +++ b/qbraid_qir/qasm3/oq3_maps.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # # This file is part of the qBraid-SDK # @@ -15,6 +15,7 @@ from typing import Union +import numpy as np import pyqir from openqasm3.ast import ( AngleType, @@ -29,6 +30,7 @@ from .elements import InversionOp from .exceptions import Qasm3ConversionError +from .linalg import kak_decomposition_angles OPERATOR_MAP = { "+": lambda x, y: x + y, @@ -143,41 +145,375 @@ def u2_inv_gate(builder, phi, lam, qubits): u3_inv_gate(builder, CONSTANTS_MAP["pi"] / 2, phi, lam, qubits) +def sx_gate(builder, qubits): + """ + Implements the Sqrt(X) gate as a decomposition of other gates. + """ + pyqir._native.rx(builder, CONSTANTS_MAP["pi"] / 2, qubits) + + +def sxdg_gate(builder, qubits): + """ + Implements the conjugate transpose of the Sqrt(X) gate as a decomposition of other gates. + """ + pyqir._native.rx(builder, -CONSTANTS_MAP["pi"] / 2, qubits) + + +def cv_gate(builder, qubit0, qubit1): + """ + Implements the controlled V gate as a decomposition of other gates. + """ + pyqir._native.x(builder, qubit0) + pyqir._native.h(builder, qubit1) + pyqir._native.cx(builder, qubit0, qubit1) + pyqir._native.h(builder, qubit1) + pyqir._native.rx(builder, CONSTANTS_MAP["pi"] / 4, qubit1) + pyqir._native.h(builder, qubit1) + pyqir._native.cx(builder, qubit0, qubit1) + pyqir._native.t_adj(builder, qubit0) + pyqir._native.h(builder, qubit1) + pyqir._native.x(builder, qubit0) + pyqir._native.rz(builder, -CONSTANTS_MAP["pi"] / 4, qubit1) + + +def cy_gate(builder, qubit0, qubit1): + """ + Implements the CY gate as a decomposition of other gates. + """ + pyqir._native.s_adj(builder, qubit1) + pyqir._native.cx(builder, qubit0, qubit1) + pyqir._native.s(builder, qubit1) + + +def xx_gate(builder, theta, qubit0, qubit1): + """ + Implements the XX gate as a decomposition of other gates. + """ + qubits = [qubit0, qubit1] + pyqir._native.h(builder, qubits[0]) + pyqir._native.h(builder, qubits[1]) + pyqir._native.cz(builder, qubits[0], qubits[1]) + pyqir._native.h(builder, qubits[1]) + pyqir._native.rx(builder, theta, qubits[0]) + pyqir._native.h(builder, qubits[1]) + pyqir._native.cz(builder, qubits[0], qubits[1]) + pyqir._native.h(builder, qubits[0]) + pyqir._native.h(builder, qubits[1]) + + +def xy_gate(builder, theta, qubit0, qubit1): + """ + Implements the XY gate as a decomposition of other gates. + """ + qubits = [qubit0, qubit1] + pyqir._native.rx(builder, -theta / 2, qubits[0]) + pyqir._native.ry(builder, theta / 2, qubits[1]) + pyqir._native.ry(builder, theta / 2, qubits[0]) + pyqir._native.rx(builder, theta / 2, qubits[0]) + pyqir._native.cx(builder, qubits[1], qubits[0]) + pyqir._native.ry(builder, -theta / 2, qubits[0]) + pyqir._native.ry(builder, -theta / 2, qubits[1]) + pyqir._native.cx(builder, qubits[1], qubits[0]) + pyqir._native.rx(builder, theta / 2, qubits[0]) + pyqir._native.ry(builder, -theta / 2, qubits[1]) + pyqir._native.ry(builder, theta / 2, qubits[1]) + pyqir._native.rx(builder, -theta / 2, qubits[0]) + + +def yy_gate(builder, theta, qubit0, qubit1): + """ + Implements the YY gate as a decomposition of other gates. + """ + qubits = [qubit0, qubit1] + pyqir._native.rx(builder, theta / 2, qubits[0]) + pyqir._native.rx(builder, theta / 2, qubits[1]) + pyqir._native.cz(builder, qubits[0], qubits[1]) + pyqir._native.h(builder, qubits[1]) + pyqir._native.rx(builder, theta, qubits[1]) + pyqir._native.h(builder, qubits[1]) + pyqir._native.cz(builder, qubits[0], qubits[1]) + pyqir._native.rx(builder, -theta / 2, qubits[0]) + pyqir._native.rx(builder, -theta / 2, qubits[1]) + + +def zz_gate(builder, theta, qubit0, qubit1): + """ + Implements the ZZ gate as a decomposition of other gates. + """ + qubits = [qubit0, qubit1] + pyqir._native.cz(builder, qubits[0], qubits[1]) + pyqir._native.h(builder, qubits[1]) + pyqir._native.rz(builder, theta, qubits[1]) + pyqir._native.h(builder, qubits[1]) + pyqir._native.cz(builder, qubits[0], qubits[1]) + + +def phaseshift_gate(builder, theta, qubit): + """ + Implements the phase shift gate as a decomposition of other gates. + """ + pyqir._native.h(builder, qubit) + pyqir._native.rx(builder, theta, qubit) + pyqir._native.h(builder, qubit) + + +def cswap_gate(builder, qubit0, qubit1, qubit2): + """ + Implements the CSWAP gate as a decomposition of other gates. + """ + qubits = [qubit0, qubit1, qubit2] + pyqir._native.cx(builder, qubits[2], qubits[1]) + pyqir._native.h(builder, qubits[2]) + pyqir._native.cx(builder, qubits[1], qubits[2]) + pyqir._native.t_adj(builder, qubits[2]) + pyqir._native.cx(builder, qubits[0], qubits[2]) + pyqir._native.t(builder, qubits[2]) + pyqir._native.cx(builder, qubits[1], qubits[2]) + pyqir._native.t(builder, qubits[1]) + pyqir._native.t_adj(builder, qubits[2]) + pyqir._native.cx(builder, qubits[0], qubits[2]) + pyqir._native.cx(builder, qubits[0], qubits[1]) + pyqir._native.t(builder, qubits[2]) + pyqir._native.t(builder, qubits[0]) + pyqir._native.t_adj(builder, qubits[1]) + pyqir._native.h(builder, qubits[2]) + pyqir._native.cx(builder, qubits[0], qubits[1]) + pyqir._native.cx(builder, qubits[2], qubits[1]) + + +def pswap_gate(builder, theta, qubit0, qubit1): + """ + Implements the PSWAP gate as a decomposition of other gates. + + """ + qubits = [qubit0, qubit1] + pyqir._native.swap(builder, qubits[0], qubits[1]) + pyqir._native.cx(builder, qubits[0], qubits[1]) + u3_gate(builder, 0, 0, theta, qubits[1]) + pyqir._native.cx(builder, qubits[0], qubits[1]) + + +def cphaseshift_gate(builder, theta, qubit0, qubit1): + """ + Implements the controlled phase shift gate as a decomposition of other gates. + """ + qubits = [qubit0, qubit1] + pyqir._native.h(builder, qubits[0]) + pyqir._native.rx(builder, theta / 2, qubits[0]) + pyqir._native.h(builder, qubits[0]) + pyqir._native.cx(builder, qubits[0], qubits[1]) + pyqir._native.h(builder, qubits[1]) + pyqir._native.rx(builder, -theta / 2, qubits[0]) + pyqir._native.h(builder, qubits[1]) + pyqir._native.cx(builder, qubits[0], qubits[1]) + pyqir._native.h(builder, qubits[1]) + pyqir._native.rx(builder, theta / 2, qubits[1]) + pyqir._native.h(builder, qubits[1]) + + +def cphaseshift00_gate(builder, theta, qubit0, qubit1): + """ + Implements the controlled phase shift 00 gate as a decomposition of other gates. + + """ + qubits = [qubit0, qubit1] + pyqir._native.x(builder, qubits[0]) + pyqir._native.x(builder, qubits[1]) + u3_gate(builder, 0, 0, theta / 2, qubits[0]) + u3_gate(builder, 0, 0, theta / 2, qubits[1]) + pyqir._native.cx(builder, qubits[0], qubits[1]) + u3_gate(builder, 0, 0, -theta / 2, qubits[1]) + pyqir._native.cx(builder, qubits[0], qubits[1]) + pyqir._native.x(builder, qubits[0]) + pyqir._native.x(builder, qubits[1]) + + +def cphaseshift01_gate(builder, theta, qubit0, qubit1): + """ + Implements the controlled phase shift 01 gate as a decomposition of other gates. + + """ + qubits = [qubit0, qubit1] + pyqir._native.x(builder, qubits[0]) + u3_gate(builder, 0, 0, theta / 2, qubits[1]) + u3_gate(builder, 0, 0, theta / 2, qubits[0]) + pyqir._native.cx(builder, qubits[0], qubits[1]) + u3_gate(builder, 0, 0, -theta / 2, qubits[1]) + pyqir._native.cx(builder, qubits[0], qubits[1]) + pyqir._native.x(builder, qubits[0]) + + +def cphaseshift10_gate(builder, theta, qubit0, qubit1): + """ + Implements the controlled phase shift 10 gate as a decomposition of other gates. + + """ + qubits = [qubit0, qubit1] + u3_gate(builder, 0, 0, theta / 2, qubits[0]) + pyqir._native.x(builder, qubits[1]) + u3_gate(builder, 0, 0, theta / 2, qubits[1]) + pyqir._native.cx(builder, qubits[0], qubits[1]) + u3_gate(builder, 0, 0, -theta / 2, qubits[1]) + pyqir._native.cx(builder, qubits[0], qubits[1]) + pyqir._native.x(builder, qubits[1]) + + +def gpi_gate(builder, phi, qubit): + """ + Implements the gpi gate as a decomposition of other gates. + """ + theta_0 = CONSTANTS_MAP["pi"] + phi_0 = phi + lambda_0 = -phi_0 + CONSTANTS_MAP["pi"] + u3_gate(builder, theta_0, phi_0, lambda_0, qubit) + + +def gpi2_gate(builder, phi, qubit): + """ + Implements the gpi2 gate as a decomposition of other gates. + """ + theta_0 = CONSTANTS_MAP["pi"] / 2 + phi_0 = phi + 3 * CONSTANTS_MAP["pi"] / 2 + lambda_0 = -phi_0 + CONSTANTS_MAP["pi"] / 2 + u3_gate(builder, theta_0, phi_0, lambda_0, qubit) + + +def ms_gate(builder, phi0, phi1, theta, qubit0, qubit1): # pylint: disable=too-many-arguments + """ + Implements the Molmer Sorenson gate as a decomposition of other gates. + """ + mat = np.array( + [ + [ + np.cos(np.pi * theta), + 0, + 0, + -1j * np.exp(-1j * 2 * np.pi * (phi0 + phi1)) * np.sin(np.pi * theta), + ], + [ + 0, + np.cos(np.pi * theta), + -1j * np.exp(-1j * 2 * np.pi * (phi0 - phi1)) * np.sin(np.pi * theta), + 0, + ], + [ + 0, + -1j * np.exp(1j * 2 * np.pi * (phi0 - phi1)) * np.sin(np.pi * theta), + np.cos(np.pi * theta), + 0, + ], + [ + -1j * np.exp(1j * 2 * np.pi * (phi0 + phi1)) * np.sin(np.pi * theta), + 0, + 0, + np.cos(np.pi * theta), + ], + ] + ) + angles = kak_decomposition_angles(mat) + qubits = [qubit0, qubit1] + + u3_gate(builder, angles[0][0], angles[0][1], angles[0][2], qubits[0]) + u3_gate(builder, angles[1][0], angles[1][1], angles[1][2], qubits[1]) + sx_gate(builder, qubits[0]) + pyqir._native.cx(builder, qubits[0], qubits[1]) + pyqir._native.rx(builder, ((1 / 2) - 2 * theta) * CONSTANTS_MAP["pi"], qubits[0]) + pyqir._native.rx(builder, CONSTANTS_MAP["pi"] / 2, qubits[1]) + pyqir._native.cx(builder, qubits[1], qubits[0]) + sxdg_gate(builder, qubits[1]) + pyqir._native.s(builder, qubits[1]) + pyqir._native.cx(builder, qubits[0], qubits[1]) + u3_gate(builder, angles[2][0], angles[2][1], angles[2][2], qubits[0]) + u3_gate(builder, angles[3][0], angles[3][1], angles[3][2], qubits[1]) + + +def ecr_gate(builder, qubit0, qubit1): + """ + Implements the ECR gate as a decomposition of other gates. + + """ + qubits = [qubit0, qubit1] + pyqir._native.s(builder, qubits[0]) + pyqir._native.rx(builder, CONSTANTS_MAP["pi"] / 2, qubits[1]) + pyqir._native.cx(builder, qubits[0], qubits[1]) + pyqir._native.x(builder, qubits[0]) + + +def prx_gate(builder, theta, phi, qubit): + """ + Implements the PRX gate as a decomposition of other gates. + """ + theta_0 = theta + phi_0 = CONSTANTS_MAP["pi"] / 2 - phi + lambda_0 = -phi_0 + u3_gate(builder, theta_0, phi_0, lambda_0, qubit) + + PYQIR_ONE_QUBIT_OP_MAP = { - # Identity Gate + "i": id_gate, "id": id_gate, - # Single-Qubit Clifford Gates "h": pyqir._native.h, "x": pyqir._native.x, "y": pyqir._native.y, "z": pyqir._native.z, - # Single-Qubit Non-Clifford Gates "s": pyqir._native.s, "t": pyqir._native.t, "sdg": pyqir._native.s_adj, + "si": pyqir._native.s_adj, "tdg": pyqir._native.t_adj, + "ti": pyqir._native.t_adj, + "v": sx_gate, + "sx": sx_gate, + "vi": sxdg_gate, + "sxdg": sxdg_gate, } PYQIR_ONE_QUBIT_ROTATION_MAP = { "rx": pyqir._native.rx, "ry": pyqir._native.ry, "rz": pyqir._native.rz, + "u": u3_gate, "U": u3_gate, "u3": u3_gate, "U3": u3_gate, "U2": u2_gate, "u2": u2_gate, + "prx": prx_gate, + "phaseshift": phaseshift_gate, + "p": phaseshift_gate, + "gpi": gpi_gate, + "gpi2": gpi2_gate, } PYQIR_TWO_QUBIT_OP_MAP = { "cx": pyqir._native.cx, "CX": pyqir._native.cx, + "cnot": pyqir._native.cx, "cz": pyqir._native.cz, "swap": pyqir._native.swap, + "cv": cv_gate, + "cy": cy_gate, + "xx": xx_gate, + "xy": xy_gate, + "yy": yy_gate, + "zz": zz_gate, + "pswap": pswap_gate, + "cp": cphaseshift_gate, + "cphaseshift": cphaseshift_gate, + "cp00": cphaseshift00_gate, + "cphaseshift00": cphaseshift00_gate, + "cp01": cphaseshift01_gate, + "cphaseshift01": cphaseshift01_gate, + "cp10": cphaseshift10_gate, + "cphaseshift10": cphaseshift10_gate, + "ecr": ecr_gate, + "ms": ms_gate, } PYQIR_THREE_QUBIT_OP_MAP = { "ccx": pyqir._native.ccx, + "ccnot": pyqir._native.ccx, + "cswap": cswap_gate, } diff --git a/qbraid_qir/qasm3/visitor.py b/qbraid_qir/qasm3/visitor.py index 8bf2346..9bd97a8 100644 --- a/qbraid_qir/qasm3/visitor.py +++ b/qbraid_qir/qasm3/visitor.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # # This file is part of the qBraid-SDK # @@ -493,7 +493,7 @@ def _get_op_parameters(self, operation: QuantumGate) -> List[float]: param_list = [] for param in operation.arguments: param_value = self._evaluate_expression(param) - print(param_value) + # print(param_value) param_list.append(param_value) return param_list diff --git a/qbraid_qir/serialization.py b/qbraid_qir/serialization.py index c37af75..53c4621 100644 --- a/qbraid_qir/serialization.py +++ b/qbraid_qir/serialization.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 qBraid +# Copyright (C) 2024 qBraid # # This file is part of the qBraid-SDK # diff --git a/tests/autoqasm_qir/__init__.py b/tests/autoqasm_qir/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/autoqasm_qir/test_convert.py b/tests/autoqasm_qir/test_convert.py new file mode 100644 index 0000000..fd51a2a --- /dev/null +++ b/tests/autoqasm_qir/test_convert.py @@ -0,0 +1,302 @@ +# Copyright (C) 2024 qBraid +# +# This file is part of the qBraid-SDK +# +# The qBraid-SDK is free software released under the GNU General Public License v3 +# or later. You can redistribute and/or modify it under the terms of the GPL v3. +# See the LICENSE file in the project root or . +# +# THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3. + +""" +Tests the convert module of autoqasm to qir + +""" +import re +from typing import TYPE_CHECKING + +import autoqasm as aq +import autoqasm.instructions as ins +import numpy as np +import pytest +from pyqir import Module +from qbraid.passes.qasm3.compat import add_stdgates_include, insert_gate_def + +from qbraid_qir.qasm3 import qasm3_to_qir + +if TYPE_CHECKING: + from autoqasm.program import MainProgram + + +def _process_qasm(qasm: str) -> str: + """ + Convert OpenQASM 3 string to a format that + will be accepted by the qbraid-qir converter. + + Args: + qasm (str): The input QASM string to process. + + Returns: + The processed QASM string. + + """ + # Regular expression to remove initialization to zeros + pattern = r'(bit\[\d+\] +__bit_\d+__)\s+=\s+"[0]+"(;)' + + # Transform each line, removing zero initializations + transformed_lines = [re.sub(pattern, r"\1\2", line) for line in qasm.split("\n")] + + # Rejoin the transformed lines back into a single string + qasm = "\n".join(transformed_lines) + + # Replace specific keywords with comments in a single step to avoid multiple replacements + qasm = re.sub(r"^(output|return_value =)", r"// \1", qasm, flags=re.MULTILINE) + + # Insert and replace various gate definitions for compatibility + qasm = add_stdgates_include(qasm) + qasm = insert_gate_def(qasm, "iswap") + qasm = insert_gate_def(qasm, "sxdg") + + return qasm + + +def autoqasm_to_qir(program: "MainProgram", **kwargs) -> "Module": + """ + Converts an AutoQASM program to a PyQIR module. + + Args: + program (cirq.Circuit): The Cirq circuit to convert. + + Returns: + The QIR ``pyqir.Module`` representation of the input AutoQASM program. + + """ + qasm = program.build().to_ir() + + processed_qasm = _process_qasm(qasm) + + return qasm3_to_qir(processed_qasm, **kwargs) + + +@aq.main(num_qubits=1) +def one_qubit_gates(): + ins.h(0) + ins.i(0) + ins.s(0) + ins.si(0) + ins.t(0) + ins.ti(0) + ins.x(0) + ins.y(0) + ins.z(0) + ins.v(0) + ins.vi(0) + return ins.measure(0) + + +@aq.main(num_qubits=1) +def one_qubit_rotation_gates(): + ins.rx(0, np.pi / 2) + ins.ry(0, np.pi / 2) + ins.rz(0, np.pi / 2) + ins.u(0, np.pi / 2, np.pi, np.pi / 2) + + +@aq.main(num_qubits=2) +def ising_coupling_gates(): + ins.xx(0, 1, np.pi / 2) + ins.xy(0, 1, np.pi / 2) + ins.yy(0, 1, np.pi / 2) + ins.zz(0, 1, np.pi / 2) + + +@aq.main(num_qubits=2) +def controlled_two_qubit_gates(): + ins.cnot(0, 1) + ins.cv(0, 1) + ins.cy(0, 1) + ins.cz(0, 1) + + +@aq.main(num_qubits=3) +def swap_gates(): + ins.swap(0, 1) + ins.iswap(0, 1) + ins.pswap(0, 1, np.pi / 2) + + +@aq.main(num_qubits=3) +def controlled_three_qubit_gates(): + ins.ccnot(0, 1, 2) + ins.cswap(0, 1, 2) + + +@aq.main(num_qubits=1) +def phase_shift_gates(): + ins.phaseshift(0, np.pi / 2) + ins.prx(0, np.pi / 2, np.pi / 2) + + +@aq.main(num_qubits=2) +def controlled_phase_shift_gates(): + ins.cphaseshift(0, 1, np.pi / 2) + ins.cphaseshift00(0, 1, np.pi / 2) + ins.cphaseshift01(0, 1, np.pi / 2) + ins.cphaseshift10(0, 1, np.pi / 2) + + +@aq.main +def global_phase_gates(): + ins.gphase(np.pi / 2) + + +@aq.main(num_qubits=2) +def ionq_gates(): + ins.gpi(0, np.pi / 2) + ins.gpi2(0, np.pi / 2) + ins.ms(0, 1, 0, np.pi / 2, np.pi) + + +@aq.main(num_qubits=3) +def reset_example(): + ins.x(0) + ins.reset(0) + ins.h(1) + ins.cnot(1, 2) + + +@aq.main(num_qubits=3) +def miscellaneous_gates(): + ins.ecr(0, 1) + + +def test_one_qubit_gates(): + """Test converting one qubti gate autoqasm to qir.""" + qasm = autoqasm_to_qir(one_qubit_gates) + assert isinstance(qasm, Module) + + +def test_one_qubit_rotation_gates(): + """Test converting one qubit rotation gate autoqasm to qir.""" + qasm = autoqasm_to_qir(one_qubit_rotation_gates) + assert isinstance(qasm, Module) + + +def test_ising_coupling_gates(): + """Test converting ising coupling gate autoqasm to qir.""" + qasm = autoqasm_to_qir(ising_coupling_gates) + assert isinstance(qasm, Module) + + +def test_controlled_two_qubit_gates(): + """Test converting controlled two qubit gate autoqasm to qir.""" + qasm = autoqasm_to_qir(controlled_two_qubit_gates) + assert isinstance(qasm, Module) + + +def test_swap_gates(): + """Test converting swap gate autoqasm to qir.""" + qasm = autoqasm_to_qir(swap_gates) + assert isinstance(qasm, Module) + + +def test_controlled_three_qubit_gates(): + """Test converting controlled three qubit gate autoqasm to qir.""" + qasm = autoqasm_to_qir(controlled_three_qubit_gates) + assert isinstance(qasm, Module) + + +def test_phase_shift_gates(): + """Test converting phase shift gate autoqasm to qir.""" + qasm = autoqasm_to_qir(phase_shift_gates) + assert isinstance(qasm, Module) + + +def test_controlled_phase_shift_gates(): + """Test converting controlled phase shift gate autoqasm to qir.""" + qasm = autoqasm_to_qir(controlled_phase_shift_gates) + assert isinstance(qasm, Module) + + +def test_ionq_gates(): + """Test converting ionq gate autoqasm to qir.""" + qasm = autoqasm_to_qir(ionq_gates) + assert isinstance(qasm, Module) + + +def test_reset(): + """Test autoqasm reset usage to qir.""" + qasm = autoqasm_to_qir(reset_example) + assert isinstance(qasm, Module) + + +def test_miscellaneous_gates(): + """Test converting miscellaneous gate autoqasm to qir.""" + qasm = autoqasm_to_qir(miscellaneous_gates) + assert isinstance(qasm, Module) + + +@aq.subroutine +def bell_subroutine(q0: int, q1: int): + ins.h(q0) + ins.cnot(q0, q1) + + +@aq.main(num_qubits=4) +def two_bell(): + bell_subroutine(0, 1) + bell_subroutine(2, 3) + + +@pytest.mark.skipif(True, reason="Subroutines are not currently supported.") +def test_subroutine_usage(): + """Test autoqasm subroutine usage to qir.""" + qasm = autoqasm_to_qir(two_bell) + assert isinstance(qasm, Module) + + +@aq.gate +def my_gate(q0: aq.Qubit, q1: aq.Qubit): + ins.h(q0) + ins.cnot(q0, q1) + + +@aq.main(num_qubits=2) +def bell_gate(): + my_gate(0, 1) + + +def test_gate_usage(): + """Test autoqasm gate usage to qir.""" + qasm = autoqasm_to_qir(bell_gate) + assert isinstance(qasm, Module) + + +@aq.main +def bell_state(): + with aq.verbatim(): + ins.h("$0") + ins.cnot("$0", "$1") + return ins.measure(["$0", "$1"]) + + +@pytest.mark.skipif(True, reason="Pragma is not currently supported.") +def test_verbatim(): + """Test autoqasm verbatim usage to qir.""" + qasm = autoqasm_to_qir(bell_state) + assert isinstance(qasm, Module) + + +@aq.main +def my_program(): + ins.h(0) + ins.cnot(0, 1) + result = ins.measure([0, 1]) + return result + + +@pytest.mark.skipif(True, reason="Measurement variable assignment is not currently supported.") +def test_measurement_variable(): + """Test autoqasm measurement variable assignment to qir.""" + qasm = autoqasm_to_qir(my_program) + assert isinstance(qasm, Module) diff --git a/tests/qasm3_qir/converter/test_gates.py b/tests/qasm3_qir/converter/test_gates.py index 9d74917..c6cdae7 100644 --- a/tests/qasm3_qir/converter/test_gates.py +++ b/tests/qasm3_qir/converter/test_gates.py @@ -191,7 +191,6 @@ def test_inv_gate_modifier(): def test_nested_gate_modifiers(): - complex_qir = qasm3_to_qir( """ OPENQASM 3; diff --git a/tests/qasm3_qir/fixtures/gates.py b/tests/qasm3_qir/fixtures/gates.py index 1aa1a13..976d567 100644 --- a/tests/qasm3_qir/fixtures/gates.py +++ b/tests/qasm3_qir/fixtures/gates.py @@ -164,10 +164,43 @@ def test_fixture(): locals()[name] = _generate_custom_op_fixture(test_name) single_op_tests = [_fixture_name(s) for s in PYQIR_ONE_QUBIT_OP_MAP] -single_op_tests.remove("Fixture_id") # as we have already tested x gate +already_tested_single_op = ["i", "id", "si", "ti", "v", "sx", "vi", "sxdg"] +for gate in already_tested_single_op: + single_op_tests.remove(_fixture_name(gate)) + rotation_tests = [_fixture_name(s) for s in PYQIR_ONE_QUBIT_ROTATION_MAP if "u" not in s.lower()] +already_tested_rotation = ["prx", "phaseshift", "p", "gpi", "gpi2"] +for gate in already_tested_rotation: + rotation_tests.remove(_fixture_name(gate)) + double_op_tests = [_fixture_name(s) for s in PYQIR_TWO_QUBIT_OP_MAP] +already_tested_double_op = [ + "cv", + "cy", + "xx", + "xy", + "yy", + "zz", + "pswap", + "cp", + "cp00", + "cp01", + "cp10", + "cphaseshift", + "cphaseshift00", + "cphaseshift01", + "cphaseshift10", + "ecr", + "ms", +] +for gate in already_tested_double_op: + double_op_tests.remove(_fixture_name(gate)) + triple_op_tests = [_fixture_name(s) for s in PYQIR_THREE_QUBIT_OP_MAP] +already_tested_triple_op = ["ccnot", "cswap"] +for gate in already_tested_triple_op: + triple_op_tests.remove(_fixture_name(gate)) + custom_op_tests = [_fixture_name(s) for s in CUSTOM_OPS] # qasm_input, expected_error diff --git a/tests/qasm3_qir/test_linalg.py b/tests/qasm3_qir/test_linalg.py new file mode 100644 index 0000000..ecc3ae8 --- /dev/null +++ b/tests/qasm3_qir/test_linalg.py @@ -0,0 +1,28 @@ +# Copyright (C) 2023 qBraid +# +# This file is part of the qBraid-SDK +# +# The qBraid-SDK is free software released under the GNU General Public License v3 +# or later. You can redistribute and/or modify it under the terms of the GPL v3. +# See the LICENSE file in the project root or . +# +# THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3. + +""" +Module containing unit tests for linalg.py functions. + +""" +import numpy as np + +from qbraid_qir.qasm3.linalg import _kak_canonicalize_vector + + +def test_kak_canonicalize_vector(): + """Test _kak_canonicalize_vector function.""" + x, y, z = -1, -2, -1 + result = _kak_canonicalize_vector(x, y, z) + assert result["single_qubit_operations_before"][0][0][0] == -np.sqrt(2) / 2 * 1j + + x, y, z = 1, 2, 1 + result = _kak_canonicalize_vector(x, y, z) + assert result["single_qubit_operations_before"][0][0][0] == -np.sqrt(2) / 2