From c7291d0e52b6ff8e0451675b7aebf14c47996645 Mon Sep 17 00:00:00 2001 From: "A.C.E07" Date: Fri, 14 Mar 2025 18:35:52 +0800 Subject: [PATCH 01/58] Added `quick.circuit.Ansatz` --- notebooks/Training Ansatzes.ipynb | 363 ++++++++++++++++++++++++++ quick/circuit/__init__.py | 2 + quick/circuit/ansatz.py | 255 ++++++++++++++++++ quick/circuit/circuit_utils.py | 84 +++++- stubs/quick/circuit/ansatz.pyi | 36 +++ stubs/quick/circuit/circuit_utils.pyi | 9 +- tests/circuit/test_ansatz.py | 256 ++++++++++++++++++ 7 files changed, 1002 insertions(+), 3 deletions(-) create mode 100644 notebooks/Training Ansatzes.ipynb create mode 100644 quick/circuit/ansatz.py create mode 100644 stubs/quick/circuit/ansatz.pyi create mode 100644 tests/circuit/test_ansatz.py diff --git a/notebooks/Training Ansatzes.ipynb b/notebooks/Training Ansatzes.ipynb new file mode 100644 index 0000000..cbead33 --- /dev/null +++ b/notebooks/Training Ansatzes.ipynb @@ -0,0 +1,363 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Import `quick` modules. For this demo, we will import the circuit instances." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from quick.circuit import QiskitCircuit, Ansatz\n", + "from quick.circuit.circuit_utils import flatten, reshape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will also import some basic modules for angle calculations." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Variational Quantum Computing\n", + "\n", + "Quantum Circuits with single qubit rotation gates are known as Parameterized Quantum Circuits (PQC). If we variationally update the rotation angles of a PQC to minimize a cost function, we call the PQC an Ansatze for clarity in notation.\n", + "\n", + "In `quick.circuit`, we provide the Ansatze as a separate class for brevity and easier readability. You can use `quick.circuit.Ansatz` to \"convert\" ordinary `quick.circuit.Circuit` instances to ansatzes. This is done by simply using the `.update()` where we just change the rotation angle values and update to re-construct the updated circuit. Use-cases of variational quantum circuits include:\n", + "- Supervised QML\n", + "- Approximate Quantum Compilation (AQC)\n", + "- Quantum Approximate Optimization Algorithm (QAOA)\n", + "- Variational Quantum Eigensolver (VQE)\n", + "\n", + "For this notebook, we will mainly focus on AQC." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will use `scipy.optimize.minimize` for optimizing the parameters. Scipy provides an elegant and minimalistic interface for accessing a variety of SOTA optimization algorithms without the hassle of manually setting each one up or passing a multitude of hyper-parameters. For our use-case in this notebook, scipy is an excellet fit. It should however be noted that if the use-case requires more sophisticated optimization techniques depending on the cost landscape, users are urged to implement their own optimization routines and/or use more advanced optimization libraries." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "from scipy.optimize import minimize" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we will generate a random state to be encoded using Shende's synthesis as our initial parameter." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "from quick.random import generate_random_state\n", + "\n", + "random_state = generate_random_state(6)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "circuit = QiskitCircuit(6)\n", + "circuit.initialize(random_state, range(6))\n", + "\n", + "ansatz = Ansatz(circuit)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we will define our target state (the state which we want to prepare). The goal of our VQA is to variationally train the circuit such that the inner-product between the statevector represented by our ansatz and the target state is maximized, or in other words the infidelity is minimized.\n", + "\n", + "We can use any arbitrary statevector, but for visualization purposes let's use a MNIST image instance." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "# Load in the resized MNIST dataset\n", + "dataset = pd.read_csv('datasets/mnist-resized.csv')\n", + "\n", + "# Convert the dataset to a numpy array\n", + "images = dataset.to_numpy()[:,1:].reshape(30018, 8, 8)\n", + "\n", + "# Get the first image\n", + "test_image = images.reshape(30018, 8, 8)[0, :]" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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tB555JH0V3bcqj777DNVV1dr7Nixqqur0/Lly7Vy5Uq98cYbXk9zZceOHbp48aKeeeYZzzb0+W9TRv8SjUZ1/PhxX7y3Lkn33XefmpubFY/H9dZbb6miokL19fW3dGRaWlq0atUq7dmzR4MGDfJ6TreVl5en/3dxcbGmTZumoqIi/eEPf9Czzz7r4bKupVIplZaWau3atZKkSZMm6fjx43rttddUUVHh8brue/3111VeXq6CggLPNvj+DubOO+9UVlaWLly40On5CxcuaOTIkR6t+nZYsWKF3n33XX3wwQfmX8HQW7Kzs3XPPfdo8uTJisViKikp0SuvvOL1rC41NTWptbVVDz74oILBoILBoOrr6/Xqq68qGAyqo6PD64ndcscdd+jee+/VqVOnvJ7Spfz8/Gv+D8f999/vi7f3vvHFF19o7969+tGPfuTpDt8HJjs7W5MnT9a+ffvSz6VSKe3bt8837637jeM4WrFihWpra/X+++9rzJgxXk/KWCqVUjKZ9HpGl2bNmqVjx46pubk5fZSWlmrJkiVqbm5WVlaW1xO75dKlS/r000+Vn5/v9ZQulZWVXfNj95988omKioo8WuReTU2N8vLyNG/ePE939Iu3yCorK1VRUaHS0lJNnTpV69evV3t7u5YuXer1tC5dunSp0/+bO336tJqbmzV06FCNGjXKw2Vdi0aj2rp1q3bu3KmcnJz0Z13hcFiDBw/2eN2NVVVVqby8XKNGjVJbW5u2bt2q/fv3q66uzutpXcrJybnm863bbrtNw4YNu6U/93rhhRc0f/58FRUV6dy5c1qzZo2ysrK0ePFir6d16fnnn9dDDz2ktWvX6qmnntLhw4e1adMmbdq0yetp3ZJKpVRTU6OKigoFgx7/J96Tn10z8Otf/9oZNWqUk52d7UydOtVpaGjwetJNffDBB46ka46Kigqvp3XpepslOTU1NV5P69KyZcucoqIiJzs72xk+fLgza9Ys57333vN6Vkb88GPKixYtcvLz853s7Gznu9/9rrNo0SLn1KlTXs/qlnfeeceZOHGiEwqFnHHjxjmbNm3yelK31dXVOZKcEydOeD3F4df1AwBM+P4zGADArYnAAABMEBgAgAkCAwAwQWAAACYIDADABIEBAJggMAAAEwQGAGCCwAAATBAYAIAJAgMAMPF/NSeMDcFGZn0AAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "# Plot the first image\n", + "plt.imshow(test_image)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "target_state = test_image.flatten() / np.linalg.norm(test_image.flatten())" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(0.24593246121529486+0.14333213936238834j)" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "target_state.conj().T @ ansatz.ansatz.get_statevector()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will then define the training routine and plot the improvements as we optimize. In case the scipy optimizer keeps going after the `max_iter` threshold, feel free to interrupt the cell." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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4xUUFODs2bPw8/Pj59KaWHpGc21au3atcHJyEnFxceLkyZNi/PjxwtPT02DWur3Jz88XR48eFUePHhUAxLx588TRo0fF33//LYQoWwru6ekptmzZIo4fPy4GDRpU5VLwrl27iv3794tff/1VtG7d2mApeE5OjvDx8REjRowQSUlJYu3atcLFxcXmloJPmjRJeHh4iN27dxssE7169apcZuLEiaJp06bip59+EocOHRKhoaEiNDRUfr18mWj//v1FYmKiiI+PF97e3lUuE33ttdfEqVOnxOLFi21ymeibb74p9uzZI1JSUsTx48fFm2++KSRJEj/++KMQgufSFBVXSwnBc1kTr7zyiti9e7dISUkRe/fuFWFhYcLLy0tkZmYKIXgurYVdhRshhPjkk09E06ZNhUajET169BC///67pZtkUbt27RIAKj1GjRolhChbDv7uu+8KHx8f4eTkJPr27SuSk5MN6rh8+bIYNmyYcHNzE+7u7iIqKkrk5+cblDl27Ji4//77hZOTk2jcuLGYM2dObb3FWlPVeQQgYmNj5TLXrl0T//rXv0T9+vWFi4uLePLJJ0VaWppBPefPnxcDBgwQzs7OwsvLS7zyyivi+vXrBmV27dolgoKChEajES1atDA4hq14/vnnRbNmzYRGoxHe3t6ib9++crARgufSFLeGG57L6ouMjBR+fn5Co9GIxo0bi8jISHHmzBn5dZ5L6yAJIYRl+oyIiIiIlGc3c26IiIjIPjDcEBERkU1huCEiIiKbwnBDRERENoXhhoiIiGwKww0RERHZFIYbIiIisikMN0RkdwICArBgwQJLN4OIzIThhojMavTo0YiIiAAA9OnTB1OnTq21Y8fFxcHT07PS9oMHD2L8+PG11g4iql0Olm4AEVFNlZSUQKPRGL2/t7e3gq0hImvDnhsiqhWjR4/Gnj17sHDhQkiSBEmScP78eQBAUlISBgwYADc3N/j4+GDEiBHIysqS9+3Tpw+mTJmCqVOnwsvLC+Hh4QCAefPmoVOnTnB1dYW/vz/+9a9/oaCgAACwe/duREVFITc3Vz7ezJkzAVQelkpNTcWgQYPg5uYGd3d3DB06FBkZGfLrM2fORFBQEFauXImAgAB4eHjgmWeeQX5+vnlPGhEZheGGiGrFwoULERoainHjxiEtLQ1paWnw9/dHTk4OHn74YXTt2hWHDh1CfHw8MjIyMHToUIP9V6xYAY1Gg71792LZsmUAAJVKhY8//hh//PEHVqxYgZ9++gmvv/46AKBnz55YsGAB3N3d5eO9+uqrldql1+sxaNAgZGdnY8+ePdixYwfOnTuHyMhIg3Jnz57F5s2bsW3bNmzbtg179uzBnDlzzHS2iMgUHJYiolrh4eEBjUYDFxcX+Pr6ytsXLVqErl274v3335e3LV++HP7+/jh9+jTatGkDAGjdujU++OADgzorzt8JCAjA//3f/2HixIlYsmQJNBoNPDw8IEmSwfFulZCQgBMnTiAlJQX+/v4AgK+++godOnTAwYMHce+99wIoC0FxcXGoV68eAGDEiBFISEjAv//9b9NODBEpjj03RGRRx44dw65du+Dm5iY/2rVrB6Cst6RccHBwpX137tyJvn37onHjxqhXrx5GjBiBy5cv4+rVq9U+/qlTp+Dv7y8HGwAIDAyEp6cnTp06JW8LCAiQgw0A+Pn5ITMzs0bvlYhqB3tuiMiiCgoKMHDgQMydO7fSa35+fvLPrq6uBq+dP38ejz/+OCZNmoR///vfaNCgAX799VeMGTMGJSUlcHFxUbSdjo6OBs8lSYJer1f0GESkDIYbIqo1Go0GOp3OYFu3bt2wceNGBAQEwMGh+r+SDh8+DL1ej48++ggqVVkn9Pr16+96vFu1b98eFy5cwIULF+Tem5MnTyInJweBgYHVbg8RWQ8OSxFRrQkICMD+/ftx/vx5ZGVlQa/XY/LkycjOzsawYcNw8OBBnD17Fj/88AOioqLuGExatWqF69ev45NPPsG5c+ewcuVKeaJxxeMVFBQgISEBWVlZVQ5XhYWFoVOnThg+fDiOHDmCAwcOYOTIkejduze6d++u+DkgIvNjuCGiWvPqq69CrVYjMDAQ3t7eSE1NRaNGjbB3717odDr0798fnTp1wtSpU+Hp6Sn3yFSlS5cumDdvHubOnYuOHTti1apViImJMSjTs2dPTJw4EZGRkfD29q40IRkoG17asmUL6tevjwcffBBhYWFo0aIF1q1bp/j7J6LaIQkhhKUbQURERKQU9twQERGRTWG4ISIiIpvCcENEREQ2heGGiIiIbArDDREREdkUhhsiIiKyKQw3REREZFMYboiIiMimMNwQERGRTWG4ISIiIpvCcENEREQ2heGGiIiIbMr/A1dtVLlToV3aAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "from IPython.display import display, clear_output\n", + "\n", + "infidelities = []\n", + "\n", + "def cost_function(thetas, shape) -> complex:\n", + " ansatz.thetas = reshape(thetas, shape)\n", + "\n", + " infidelity = 1 - target_state.conj().T @ ansatz.ansatz.get_statevector()\n", + "\n", + " infidelities.append(infidelity)\n", + " update_plot(infidelities)\n", + "\n", + " return infidelity\n", + "\n", + "# Create a figure and axis for dynamic plotting\n", + "fig, ax = plt.subplots()\n", + "line, = ax.plot([], [], 'r-', label='Infidelity')\n", + "smooth_line, = ax.plot([], [], 'b-', label='Smoothed Infidelity')\n", + "ax.set_xlim(0, 1)\n", + "ax.set_ylim(0, 1)\n", + "ax.set_title('Infidelities Over Time')\n", + "ax.set_xlabel('Iteration')\n", + "ax.set_ylabel('Infidelity')\n", + "ax.legend()\n", + "\n", + "# Function to update the plot\n", + "def update_plot(new_data):\n", + " line.set_xdata(range(len(new_data)))\n", + " line.set_ydata(new_data)\n", + "\n", + " # Calculate the moving average\n", + " window_size = 5\n", + " if len(new_data) >= window_size:\n", + " smoothed_data = np.convolve(new_data, np.ones(window_size)/window_size, mode='valid')\n", + " smooth_line.set_xdata(range(window_size-1, len(new_data)))\n", + " smooth_line.set_ydata(smoothed_data)\n", + "\n", + " ax.set_xlim(0, len(new_data))\n", + " ax.set_ylim(0, max(new_data) + 0.1)\n", + " clear_output(wait=True)\n", + " display(fig)\n", + " plt.pause(0.1)\n", + "\n", + "# Initial parameters for the ansatz\n", + "initial_params, shape = flatten(ansatz.thetas)\n", + "\n", + "# Define bounds for each parameter to be between 0 and 2*pi\n", + "bounds = [(0, 2*np.pi) for _ in initial_params]\n", + "\n", + "# Perform the optimization\n", + "result = minimize(cost_function, initial_params, args=(shape), method=\"BFGS\", options={'maxiter': 2000})\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's the fidelity." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(0.9184301235740799-0.11993565640369458j)" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.inner(target_state.conj().T, ansatz.ansatz.get_statevector())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Whilst the inner-product is a fundamental measure of similarity, let us use our own eyes to assess the quality of the approximation." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxoAAAGKCAYAAACLuTc4AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAFO9JREFUeJzt3X2QVwW9x/HvbxcURNi9wK4WKiM+i5gN2CVIcHyARukK5TUNFCWJIdNpJjNnyoDJFMZLY1Nopg0+hCPyYJp2KRtxLAWRLOdOTTcwddQ0cHkQBRHYc/9w2OtK2h77rgfk9Zphxj2c3f3sb3AP7z27P2pFURQBAACQqK7qAQAAwIeP0AAAANIJDQAAIJ3QAAAA0gkNAAAgndAAAADSCQ0AACCd0AAAANIJDQAAIJ3QAAAA0gkNdlGr1Tr06+GHH/6X39fmzZtj+vTpHX5bDz/8cNRqtVi4cOG//L53Z/Pnz48JEybEEUccEbVaLU4++eSqJwFUyrWpWi0tLXHdddfFiBEjoqmpKRobG2Po0KExf/78qqexG+tS9QB2P3fccUe7l2+//fZ48MEHdzl+zDHH/Mvva/PmzTFjxoyICH+Zfpsbb7wxfve738WJJ54YLS0tVc8BqJxrU7WWLVsW3/zmN+OMM86Ib33rW9GlS5dYtGhRnHvuufGnP/2p7fGCtxMa7GLChAntXl6+fHk8+OCDuxyn89xxxx3Rr1+/qKuri+OOO67qOQCVc22q1sCBA2PVqlXRv3//tmNf/vKX47TTTotZs2bFFVdcET169KhwIbsj3zrF+9La2hrXX399DBw4MLp16xYHHHBATJkyJdavX9/uvJUrV8bo0aOjb9++0b179zj00ENj0qRJERHx7LPPRlNTU0REzJgxo+229/Tp00ttmT59etRqtfjLX/4SEyZMiIaGhmhqaoqrrroqiqKI559/Ps4666zo1atXHHjggTF79ux2r//mm2/Gt7/97Rg8eHA0NDREjx494qSTToqlS5fu8r5aWlri/PPPj169ekVjY2NMnDgxnnrqqajVanHrrbe2O/fPf/5znH322dG7d+/o1q1bDBkyJO67774OfUwHH3xw1NX53xOgDNemzrs2HXrooe0iI+Ktb2cbO3ZsbN26Nf7617+WenzYO7ijwfsyZcqUuPXWW+Oiiy6Kyy67LJ555pn44Q9/GL///e/j0Ucfja5du8aaNWti1KhR0dTUFFdeeWU0NjbGs88+G4sXL46IiKamprjxxhtj6tSpMW7cuPjsZz8bERHHH3/8+9r0+c9/Po455piYOXNmPPDAA3H11VdH796946abbopTTjklZs2aFfPmzYvLL788TjzxxBgxYkRERLz66qtxyy23xHnnnReTJ0+OTZs2xU9+8pMYPXp0rFixIk444YSIeOsC9pnPfCZWrFgRU6dOjaOPPjruvffemDhx4i5b/vjHP8bw4cOjX79+ceWVV0aPHj3i7rvvjrFjx8aiRYti3Lhx7+tjBODduTZ98Neml19+OSIi+vbt+74eHz7kCvgnLrnkkuLtf1R+85vfFBFRzJs3r915S5YsaXf8nnvuKSKieOKJJ971ba9du7aIiGLatGkd2rJ06dIiIooFCxa0HZs2bVoREcWXvvSltmPbt28vDjrooKJWqxUzZ85sO75+/fqie/fuxcSJE9udu3Xr1nbvZ/369cUBBxxQTJo0qe3YokWLiogorr/++rZjO3bsKE455ZQiIoq5c+e2HT/11FOLQYMGFW+88UbbsdbW1mLYsGHFEUcc0aGPdaeBAwcWI0eOLPU6AB92rk1vqeraVBRF0dLSUjQ3NxcnnXRS6ddl7+B7MyhtwYIF0dDQEKeffnq88sorbb8GDx4c+++/f9tt3cbGxoiIuP/++2Pbtm2dvuviiy9u++/6+voYMmRIFEURX/ziF9uONzY2xlFHHdXuFm99fX3ss88+EfHWV4bWrVsX27dvjyFDhsSTTz7Zdt6SJUuia9euMXny5LZjdXV1cckll7TbsW7dunjooYfinHPOiU2bNrU9Pi0tLTF69OhYtWpVvPjii+kfP8DezLXpg702tba2xvjx42PDhg3xgx/8oOMPCHsVoUFpq1atio0bN0Zzc3M0NTW1+/Xaa6/FmjVrIiJi5MiR8bnPfS5mzJgRffv2jbPOOivmzp0bW7du7ZRdhxxySLuXGxoaolu3brvczm1oaNjl+3Vvu+22OP7446Nbt27Rp0+faGpqigceeCA2btzYds5zzz0XH/nIR2K//fZr97qHH354u5dXr14dRVHEVVddtcvjM23atIiItscIgByuTR/stenSSy+NJUuWxC233BIf+9jHOvx67F38jAaltba2RnNzc8ybN+8f/v7OH6Lb+Zziy5cvj5///Ofxy1/+MiZNmhSzZ8+O5cuXx/7775+6q76+vkPHIiKKomj775/+9Kdx4YUXxtixY+PrX/96NDc3R319fVx77bXx9NNPl97R2toaERGXX355jB49+h+e884LAAD/Gtem95Z5bZoxY0bccMMNMXPmzDj//PNLb2HvITQo7bDDDotf//rXMXz48Ojevfs/PX/o0KExdOjQ+O53vxt33nlnjB8/Pu666664+OKLo1arfQCL39vChQtjwIABsXjx4nZ7dn6FZ6f+/fvH0qVLY/Pmze2+crR69ep25w0YMCAiIrp27RqnnXZaJy4HYCfXpg/m2jRnzpyYPn16fPWrX41vfOMb7/vtsHfwrVOUds4558SOHTviO9/5zi6/t3379tiwYUNERKxfv77dV2ciou1ZMnbeot75SXHn61Rh51eW3r718ccfj2XLlrU7b/To0bFt27a4+eab2461trbGnDlz2p3X3NwcJ598ctx0003x0ksv7fL+1q5dmzkfgHBt+iCuTfPnz4/LLrssxo8fH9/73vdKfTzsndzRoLSRI0fGlClT4tprr40//OEPMWrUqOjatWusWrUqFixYEN///vfj7LPPjttuuy1uuOGGGDduXBx22GGxadOmuPnmm6NXr15xxhlnRERE9+7d49hjj4358+fHkUceGb17947jjjvuA/1H6saMGROLFy+OcePGxZlnnhnPPPNM/OhHP4pjjz02Xnvttbbzxo4dG5/4xCfia1/7WqxevTqOPvrouO+++2LdunUREe2+4jRnzpz41Kc+FYMGDYrJkyfHgAED4u9//3ssW7YsXnjhhXjqqafec9MjjzwSjzzySES89cn/9ddfj6uvvjoiIkaMGNH29IcAvMW1qXOvTStWrIgLLrgg+vTpE6eeeuou36I2bNiwtrsm0Kaqp7tiz/HOpxDc6cc//nExePDgonv37kXPnj2LQYMGFVdccUXxt7/9rSiKonjyySeL8847rzjkkEOKfffdt2hubi7GjBlTrFy5st3beeyxx4rBgwcX++yzzz99OsH3egrBtWvXtjt34sSJRY8ePXZ5GyNHjiwGDhzY9nJra2txzTXXFP379y/23Xff4uMf/3hx//33FxMnTiz69+/f7nXXrl1bfOELXyh69uxZNDQ0FBdeeGHx6KOPFhFR3HXXXe3Offrpp4sLLrigOPDAA4uuXbsW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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Create a figure with two subplots\n", + "fig, axes = plt.subplots(1, 2, figsize=(10, 5))\n", + "\n", + "# Plot the first image\n", + "axes[0].imshow(abs(ansatz.ansatz.get_statevector()).reshape(8, 8))\n", + "axes[0].set_title('Test Image 1')\n", + "axes[0].axis('off')\n", + "\n", + "# Plot the second image\n", + "axes[1].imshow(test_image)\n", + "axes[1].set_title('Test Image 2')\n", + "axes[1].axis('off')\n", + "\n", + "# Display the plot\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For AQC, we often would use a much smaller ansatz so as to justify the training cost. However, for ease in demonstration and better fidelity we used an exact circuit and simply updated the parameters to what Shende's synthesis would have gotten to." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "

© 2025 Qualition Computing, all rights reserved.

" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "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.11" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/quick/circuit/__init__.py b/quick/circuit/__init__.py index 094488f..1226eab 100644 --- a/quick/circuit/__init__.py +++ b/quick/circuit/__init__.py @@ -16,6 +16,7 @@ "dag", "gate_matrix", "from_framework", + "Ansatz", "Circuit", "CirqCircuit", "PennylaneCircuit", @@ -32,5 +33,6 @@ from quick.circuit.pennylanecircuit import PennylaneCircuit from quick.circuit.quimbcircuit import QuimbCircuit from quick.circuit.tketcircuit import TKETCircuit +from quick.circuit.ansatz import Ansatz import quick.circuit.from_framework as from_framework import quick.circuit.dag as dag \ No newline at end of file diff --git a/quick/circuit/ansatz.py b/quick/circuit/ansatz.py new file mode 100644 index 0000000..bb5e00f --- /dev/null +++ b/quick/circuit/ansatz.py @@ -0,0 +1,255 @@ +# Copyright 2023-2025 Qualition Computing LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://github.com/Qualition/quick/blob/main/LICENSE +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" Ansatz for variational quantum circuits. +""" + +from __future__ import annotations + +__all__ = ["Ansatz"] + +from quick.circuit import Circuit + +# Type hint for nested lists of floats +Params = list[list[float] | float] | list[float] + + +class Ansatz: + """ `quick.circuit.Ansatz` class for parameterized quantum circuits + which can be used as variational ansatz for quantum machine learning + models. + + Notes + ----- + The `Ansatz` class is a wrapper around the `quick.circuit.Circuit` class + which provides a more user-friendly interface for parameterized quantum + circuits, and means for variationally updating them. + + Use-cases of variational quantum circuits include: + - Supervised QML + - Approximate Quantum Compilation (AQC) + - Quantum Approximate Optimization Algorithm (QAOA) + - Variational Quantum Eigensolver (VQE) + + This class is meant to provide a simple interface for specifically updating + the rotation angles of a parameterized quantum circuit. Users can use the class + in the following manner: + + ```python + from quick.circuit import Ansatz, QiskitCircuit + from quick.circuit.circuit_utils import reshape, flatten + from quick.random import generate_random_state + from scipy.optimize import minimize + + # Create a parameterized quantum circuit with + # random state initialization + circuit = QiskitCircuit(2) + circuit.initialize(generate_random_state(2), [0, 1]) + + # Define a target state + target_state = generate_random_state(2) + + # Create an ansatz object + ansatz = Ansatz(circuit) + + # Define the cost function + def cost_function(thetas, shape): + ansatz.thetas = reshape(thetas, shape) + return 1 - target_state.conj().T @ ansatz.ansatz.state + + initial_thetas, shape = flatten(ansatz.thetas) + + # Optimize the ansatz circuit + result = minimize(cost_function, initial_thetas, args=(shape), method="BFGS") + ``` + + This example demonstrates how one can use the Ansatz class to perform approximate + state preparation by variationally updating the circuit where the cost function is + simply the fidelity between the target state and the state prepared by the ansatz. + + For simplicity, we do not bother with defining specific optimization interfaces so + users can use whatever means of optimization they prefer as long as they update the + parameters of the ansatz circuit per iteration. Our recommendation is to use the + `scipy.optimize.minimize` function which provides a minimalistic and elegant interface + to access a variety of optimization algorithms. It should however be noted that if + the use-case requires more sophisticated optimization techniques depending on the + cost landscape, users are urged to implement their own optimization routines and/or + use more advanced optimization libraries. + + Lastly, `flatten` and `reshape` functions are used to convert the parameters of the + ansatz circuit to a 1D array and vice versa. This is necessary because the optimization + routine expects a 1D array of parameters to optimize over, while the ansatz circuit + requires the original shape to update its definition. Feel free to use these functions + or implement your own as needed. + + You may also make a PR to exclude the need for `flatten` and `reshape` by providing a + more elegant way of handling the parameter updates. + + Parameters + ---------- + `ansatz` : quick.circuit.Circuit + The parameterized quantum circuit to be used as the ansatz. + `ignore_global_phase` : bool, optional, default=True + Whether to ignore the global phase when setting the parameters + of the ansatz. + + Attributes + ---------- + `ansatz` : quick.circuit.Circuit + The parameterized quantum circuit to be used as the ansatz. + `thetas` : numpy.ndarray + The parameters of the ansatz circuit. + `num_params` : int + The number of parameters in the ansatz circuit. + `num_parameterized_gates` : int + The number of parameterized gates in the ansatz circuit. + + Raises + ------ + TypeError + - If the `ansatz` is not an instance of the `quick.circuit.Circuit`. + + Usage + ----- + >>> from quick.circuit import QiskitCircuit + >>> circuit = QiskitCircuit(2) + >>> circuit.H(0) + >>> circuit.CX(0, 1) + >>> ansatz = Ansatz(circuit) + """ + def __init__( + self, + ansatz: Circuit, + ignore_global_phase: bool = True + ) -> None: + """ Initialize a `quick.circuit.Ansatz` instance. + """ + if not isinstance(ansatz, Circuit): + raise TypeError( + "The `ansatz` must be an instance of the `quick.circuit.Circuit`. " + f"Received {type(ansatz)} instead." + ) + + self.ansatz = ansatz + self.ignore_global_phase = ignore_global_phase + + if not self.is_parameterized: + raise ValueError("The `ansatz` must contain parameterized gates.") + + @property + def thetas(self) -> Params: + """ The parameters of the ansatz circuit. + + Returns + ------- + Params + The parameters of the ansatz circuit. + + Usage + ----- + >>> ansatz.thetas + """ + thetas: Params = [] + + for gate in self.ansatz.circuit_log: + if "angles" in gate: + thetas.append(gate["angles"]) + elif "angle" in gate: + if gate["gate"] == "GlobalPhase" and self.ignore_global_phase: + continue + thetas.append(gate["angle"]) + + return thetas + + @thetas.setter + def thetas( + self, + thetas: Params + ) -> None: + """ Set the parameters of the ansatz circuit. + + Parameters + ---------- + `thetas` : Params + The parameters to set for the ansatz circuit. + + Usage + ----- + >>> # Set the parameters of the ansatz circuit + ... # with one U3 gate + >>> ansatz.thetas = np.array([0.1, 0.2, 0.3]) + """ + for gate in self.ansatz.circuit_log: + if "angles" in gate: + gate["angles"] = thetas.pop(0) + elif "angle" in gate: + if gate["gate"] == "GlobalPhase" and self.ignore_global_phase: + continue + gate["angle"] = thetas.pop(0) + + self.ansatz.update() + + @property + def num_params(self) -> int: + """ The number of parameters in the ansatz circuit. + + Returns + ------- + int + The number of parameters in the ansatz circuit. + + Usage + ----- + >>> ansatz.num_params + """ + flattened_thetas: list[float] = [] + + for theta in self.thetas: + if isinstance(theta, list): + flattened_thetas.extend(theta) + else: + flattened_thetas.append(theta) + + return len(flattened_thetas) + + @property + def num_parameterized_gates(self) -> int: + """ The number of parameterized gates in the ansatz circuit. + + Returns + ------- + int + The number of parameterized gates in the ansatz circuit. + + Usage + ----- + >>> ansatz.num_parameterized_gates + """ + return len(self.thetas) + + @property + def is_parameterized(self) -> bool: + """ Check if the ansatz circuit contains parameterized gates. + + Returns + ------- + bool + True if the ansatz circuit contains parameterized gates, + False otherwise. + + Usage + ----- + >>> ansatz.is_parameterized + """ + return len(self.thetas) > 0 \ No newline at end of file diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 1c9595c..29695a0 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -25,7 +25,9 @@ "multiplexor_diagonal_matrix", "simplify", "repetition_search", - "repetition_verify" + "repetition_verify", + "flatten", + "reshape" ] import numpy as np @@ -44,6 +46,9 @@ SQRT2, -SQRT2 ) +# Type hint for nested lists of floats +Params = list[list[float] | float] | list[float] + def decompose_multiplexor_rotations( angles: NDArray[np.float64], @@ -517,4 +522,79 @@ def repetition_verify( mux_copy[next_base] = None base, next_base, i = base + 1, next_base + 1, i + 1 - return True, mux_copy \ No newline at end of file + return True, mux_copy + +def flatten(array: Params) -> tuple[list[float], Params]: + """ Flatten a Tree into a list of floats and + the original shape. + + Parameters + ---------- + `array` : Tree + The nested list of floats. + + Returns + ------- + `flattened` : list[float] + The flattened list of parameters. + `shape` : Tree + The shape of the original array. + """ + flattened: list[float] = [] + shape: Params = [] + consecutive_ints = 0 + + for item in array: + if isinstance(item, float): + flattened.append(item) + consecutive_ints += 1 + + # Explicit type check for pylance + elif isinstance(item, list): + flattened.extend(item) + if consecutive_ints: + shape.append(consecutive_ints) + consecutive_ints = 0 + shape.append([len(item)]) # type: ignore + + if consecutive_ints: + shape.append(consecutive_ints) + + return flattened, shape + +def reshape( + flattened: list[float], + shape: Params + ) -> Params: + """ Reshape a flattened list given a shape instruction. + + Parameters + ---------- + `flattened` : list[float] + The flat list of floats. + `shape` : Tree + The shape instruction. + + Returns + ------- + `reshaped` : Tree + The reshaped list of floats. + """ + reshaped: Params = [] + result_index = 0 + + for dim in shape: + if isinstance(dim, int): + subtree = reshaped + + elif isinstance(dim, list): + subtree = [] + reshaped.append(subtree) # type: ignore + dim = dim[0] + + for i in range(result_index, result_index + dim): # type: ignore + subtree.append(flattened[i]) + + result_index += dim # type: ignore + + return reshaped \ No newline at end of file diff --git a/stubs/quick/circuit/ansatz.pyi b/stubs/quick/circuit/ansatz.pyi new file mode 100644 index 0000000..044585f --- /dev/null +++ b/stubs/quick/circuit/ansatz.pyi @@ -0,0 +1,36 @@ +# Copyright 2023-2025 Qualition Computing LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://github.com/Qualition/quick/blob/main/LICENSE +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +from numpy.typing import NDArray +from quick.circuit import Circuit + +__all__ = ["Ansatz"] + +Params = list[list[float] | float] | list[float] + +class Ansatz: + ansatz: Circuit + ignore_global_phase: bool = True + def __init__(self, ansatz: Circuit, ignore_global_phase: bool = True) -> None: ... + @property + def thetas(self) -> Params: ... + @thetas.setter + def thetas(self, theta_values: Params) -> None: ... + @property + def num_params(self) -> int: ... + @property + def num_parameterized_gates(self) -> int: ... + @property + def is_parameterized(self) -> bool: ... diff --git a/stubs/quick/circuit/circuit_utils.pyi b/stubs/quick/circuit/circuit_utils.pyi index e2f018a..d7a1ba0 100644 --- a/stubs/quick/circuit/circuit_utils.pyi +++ b/stubs/quick/circuit/circuit_utils.pyi @@ -23,9 +23,14 @@ __all__ = [ "multiplexor_diagonal_matrix", "simplify", "repetition_search", - "repetition_verify" + "repetition_verify", + "flatten", + "reshape" ] +Params = list[list[float] | float] | list[float] + + def decompose_multiplexor_rotations( angles: NDArray[np.float64], start_index: int, @@ -55,3 +60,5 @@ def repetition_search( level: int ) -> tuple[set[int], list[NDArray[np.complex128]]]: ... def repetition_verify(base, d, mux, mux_copy) -> tuple[bool, list[NDArray[np.complex128]]]: ... +def flatten(array: Params) -> tuple[list[float], Params]: ... +def reshape(flattened: list[float], shape: Params) -> Params: ... diff --git a/tests/circuit/test_ansatz.py b/tests/circuit/test_ansatz.py new file mode 100644 index 0000000..0a4b46b --- /dev/null +++ b/tests/circuit/test_ansatz.py @@ -0,0 +1,256 @@ +# Copyright 2023-2025 Qualition Computing LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://github.com/Qualition/quick/blob/main/LICENSE +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +__all__ = ["TestAnsatz"] + +import pytest +from quick.circuit import Ansatz, Circuit +from typing import Type + +from tests.circuit import CIRCUIT_FRAMEWORKS + + +class TestAnsatz: + def test_init_type_error(self) -> None: + """ Test TypeError raised when invalid type is passed to Ansatz. + """ + with pytest.raises(TypeError): + Ansatz(2) # type: ignore + + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + def test_init_value_error( + self, + circuit_framework: Type[Circuit] + ) -> None: + """ Test ValueError raised when the circuit used is not + parameterized. + + Parameters + ---------- + `circuit_framework`: Type[quick.circuit.Circuit] + The circuit framework to use for testing. + """ + circuit = circuit_framework(2) + + circuit.CX(0, 1) + + with pytest.raises(ValueError): + Ansatz(circuit) + + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + def test_thetas( + self, + circuit_framework: Type[Circuit] + ) -> None: + """ Test thetas property of Ansatz. + + Parameters + ---------- + `circuit_framework`: Type[quick.circuit.Circuit] + The circuit framework to use for testing. + """ + circuit = circuit_framework(1) + + circuit.RY(0.3, 0) + + ansatz = Ansatz(circuit) + + assert ansatz.thetas == [0.3] + + circuit = circuit_framework(2) + circuit.RY(0.3, 0) + circuit.RY(0.2, 1) + + ansatz = Ansatz(circuit) + + assert ansatz.thetas == [0.3, 0.2] + + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + def test_set_thetas( + self, + circuit_framework: Type[Circuit] + ) -> None: + """ Test thetas setter of Ansatz. + + Parameters + ---------- + `circuit_framework`: Type[quick.circuit.Circuit] + The circuit framework to use for testing. + """ + circuit = circuit_framework(1) + + circuit.RY(0.3, 0) + + ansatz = Ansatz(circuit) + + ansatz.thetas = [0.3] + + assert ansatz.thetas == [0.3] + assert circuit.circuit_log[0]["angle"] == 0.3 + + circuit = circuit_framework(2) + circuit.RY(0.3, 0) + circuit.RY(0.2, 1) + + ansatz = Ansatz(circuit) + + ansatz.thetas = [0.3, 0.2] + + assert ansatz.thetas == [0.3, 0.2] + assert circuit.circuit_log[0]["angle"] == 0.3 + assert circuit.circuit_log[1]["angle"] == 0.2 + + circuit = circuit_framework(1) + + circuit.RY(0.3, 0) + circuit.U3([0.1, 0.2, 0.3], 0) + circuit.RX(0.2, 0) + + ansatz = Ansatz(circuit) + + ansatz.thetas = [0.1, [0.2, 0.3, 0.4], 0.5] + + assert circuit.circuit_log[0]["angle"] == 0.1 + assert circuit.circuit_log[1]["angles"] == [0.2, 0.3, 0.4] + assert circuit.circuit_log[2]["angle"] == 0.5 + + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + def test_num_params( + self, + circuit_framework: Type[Circuit] + ) -> None: + """ Test num_params property of Ansatz. + + Parameters + ---------- + `circuit_framework`: Type[quick.circuit.Circuit] + The circuit framework to use for testing. + """ + circuit = circuit_framework(1) + + circuit.RY(0.3, 0) + + ansatz = Ansatz(circuit) + + assert ansatz.num_params == 1 + + circuit = circuit_framework(2) + circuit.RY(0.3, 0) + circuit.RY(0.2, 1) + + ansatz = Ansatz(circuit) + + assert ansatz.num_params == 2 + + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + def test_num_parameterized_gates( + self, + circuit_framework: Type[Circuit] + ) -> None: + """ Test num_parameterized_gates property of Ansatz. + + Parameters + ---------- + `circuit_framework`: Type[quick.circuit.Circuit] + The circuit framework to use for testing. + """ + circuit = circuit_framework(1) + + circuit.RY(0.3, 0) + + ansatz = Ansatz(circuit) + + assert ansatz.num_parameterized_gates == 1 + + circuit = circuit_framework(2) + circuit.RY(0.3, 0) + circuit.RY(0.2, 1) + + ansatz = Ansatz(circuit) + + assert ansatz.num_parameterized_gates == 2 + + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + def test_is_parameterized( + self, + circuit_framework: Type[Circuit] + ) -> None: + """ Test is_parameterized property of Ansatz. + + Parameters + ---------- + `circuit_framework`: Type[quick.circuit.Circuit] + The circuit framework to use for testing. + """ + circuit = circuit_framework(1) + + circuit.RY(0.3, 0) + + ansatz = Ansatz(circuit) + + assert ansatz.is_parameterized + + circuit = circuit_framework(2) + circuit.RY(0.3, 0) + circuit.RY(0.2, 1) + + ansatz = Ansatz(circuit) + + assert ansatz.is_parameterized + + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + def test_is_parameterized_error( + self, + circuit_framework: Type[Circuit] + ) -> None: + """ Test is_parameterized property of Ansatz when circuit is not + parameterized. + + Parameters + ---------- + `circuit_framework`: Type[quick.circuit.Circuit] + The circuit framework to use for testing. + """ + circuit = circuit_framework(2) + + circuit.CX(0, 1) + + with pytest.raises(ValueError): + Ansatz(circuit) + + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + def test_is_parameterized_with_ignore_global_phase( + self, + circuit_framework: Type[Circuit] + ) -> None: + """ Test is_parameterized property of Ansatz with ignore_global_phase + set to True. + + Parameters + ---------- + `circuit_framework`: Type[quick.circuit.Circuit] + The circuit framework to use for testing. + """ + circuit = circuit_framework(1) + + circuit.GlobalPhase(0.3) + + ansatz = Ansatz(circuit, ignore_global_phase=False) + + assert ansatz.is_parameterized + + with pytest.raises(ValueError): + Ansatz(circuit, ignore_global_phase=True) \ No newline at end of file From 1c43c85e445fbb24a3e67dc4c18f36d84436fe16 Mon Sep 17 00:00:00 2001 From: "A.C.E07" Date: Fri, 14 Mar 2025 18:56:17 +0800 Subject: [PATCH 02/58] Update circuit_utils.py --- quick/circuit/circuit_utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 29695a0..32c84d7 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -524,6 +524,7 @@ def repetition_verify( return True, mux_copy +# pragma: no cover def flatten(array: Params) -> tuple[list[float], Params]: """ Flatten a Tree into a list of floats and the original shape. @@ -562,6 +563,7 @@ def flatten(array: Params) -> tuple[list[float], Params]: return flattened, shape +# pragma: no cover def reshape( flattened: list[float], shape: Params From 7ea0c02799e74bb31126e2c962d0d647de716190 Mon Sep 17 00:00:00 2001 From: "A.C.E07" Date: Fri, 14 Mar 2025 19:11:16 +0800 Subject: [PATCH 03/58] Update circuit_utils.py --- quick/circuit/circuit_utils.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 32c84d7..46070ce 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -524,8 +524,7 @@ def repetition_verify( return True, mux_copy -# pragma: no cover -def flatten(array: Params) -> tuple[list[float], Params]: +def flatten(array: Params) -> tuple[list[float], Params]: # pragma: no cover """ Flatten a Tree into a list of floats and the original shape. @@ -563,11 +562,11 @@ def flatten(array: Params) -> tuple[list[float], Params]: return flattened, shape -# pragma: no cover + def reshape( flattened: list[float], shape: Params - ) -> Params: + ) -> Params: # pragma: no cover """ Reshape a flattened list given a shape instruction. Parameters From b49a3f2d55a995338590459b256d467dd5d8f322 Mon Sep 17 00:00:00 2001 From: "A.C.E07" Date: Sat, 15 Mar 2025 20:53:16 +0800 Subject: [PATCH 04/58] Update Training Ansatzes.ipynb --- notebooks/Training Ansatzes.ipynb | 62 ++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/notebooks/Training Ansatzes.ipynb b/notebooks/Training Ansatzes.ipynb index cbead33..dd11cac 100644 --- a/notebooks/Training Ansatzes.ipynb +++ b/notebooks/Training Ansatzes.ipynb @@ -190,13 +190,41 @@ "outputs": [ { "data": { - "image/png": "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4xUUFODs2bPw8/Pj59KaWHpGc21au3atcHJyEnFxceLkyZNi/PjxwtPT02DWur3Jz88XR48eFUePHhUAxLx588TRo0fF33//LYQoWwru6ekptmzZIo4fPy4GDRpU5VLwrl27iv3794tff/1VtG7d2mApeE5OjvDx8REjRowQSUlJYu3atcLFxcXmloJPmjRJeHh4iN27dxssE7169apcZuLEiaJp06bip59+EocOHRKhoaEiNDRUfr18mWj//v1FYmKiiI+PF97e3lUuE33ttdfEqVOnxOLFi21ymeibb74p9uzZI1JSUsTx48fFm2++KSRJEj/++KMQgufSFBVXSwnBc1kTr7zyiti9e7dISUkRe/fuFWFhYcLLy0tkZmYKIXgurYVdhRshhPjkk09E06ZNhUajET169BC///67pZtkUbt27RIAKj1GjRolhChbDv7uu+8KHx8f4eTkJPr27SuSk5MN6rh8+bIYNmyYcHNzE+7u7iIqKkrk5+cblDl27Ji4//77hZOTk2jcuLGYM2dObb3FWlPVeQQgYmNj5TLXrl0T//rXv0T9+vWFi4uLePLJJ0VaWppBPefPnxcDBgwQzs7OwsvLS7zyyivi+vXrBmV27dolgoKChEajES1atDA4hq14/vnnRbNmzYRGoxHe3t6ib9++crARgufSFLeGG57L6ouMjBR+fn5Co9GIxo0bi8jISHHmzBn5dZ5L6yAJIYRl+oyIiIiIlGc3c26IiIjIPjDcEBERkU1huCEiIiKbwnBDRERENoXhhoiIiGwKww0RERHZFIYbIiIisikMN0RkdwICArBgwQJLN4OIzIThhojMavTo0YiIiAAA9OnTB1OnTq21Y8fFxcHT07PS9oMHD2L8+PG11g4iql0Olm4AEVFNlZSUQKPRGL2/t7e3gq0hImvDnhsiqhWjR4/Gnj17sHDhQkiSBEmScP78eQBAUlISBgwYADc3N/j4+GDEiBHIysqS9+3Tpw+mTJmCqVOnwsvLC+Hh4QCAefPmoVOnTnB1dYW/vz/+9a9/oaCgAACwe/duREVFITc3Vz7ezJkzAVQelkpNTcWgQYPg5uYGd3d3DB06FBkZGfLrM2fORFBQEFauXImAgAB4eHjgmWeeQX5+vnlPGhEZheGGiGrFwoULERoainHjxiEtLQ1paWnw9/dHTk4OHn74YXTt2hWHDh1CfHw8MjIyMHToUIP9V6xYAY1Gg71792LZsmUAAJVKhY8//hh//PEHVqxYgZ9++gmvv/46AKBnz55YsGAB3N3d5eO9+uqrldql1+sxaNAgZGdnY8+ePdixYwfOnTuHyMhIg3Jnz57F5s2bsW3bNmzbtg179uzBnDlzzHS2iMgUHJYiolrh4eEBjUYDFxcX+Pr6ytsXLVqErl274v3335e3LV++HP7+/jh9+jTatGkDAGjdujU++OADgzorzt8JCAjA//3f/2HixIlYsmQJNBoNPDw8IEmSwfFulZCQgBMnTiAlJQX+/v4AgK+++godOnTAwYMHce+99wIoC0FxcXGoV68eAGDEiBFISEjAv//9b9NODBEpjj03RGRRx44dw65du+Dm5iY/2rVrB6Cst6RccHBwpX137tyJvn37onHjxqhXrx5GjBiBy5cv4+rVq9U+/qlTp+Dv7y8HGwAIDAyEp6cnTp06JW8LCAiQgw0A+Pn5ITMzs0bvlYhqB3tuiMiiCgoKMHDgQMydO7fSa35+fvLPrq6uBq+dP38ejz/+OCZNmoR///vfaNCgAX799VeMGTMGJSUlcHFxUbSdjo6OBs8lSYJer1f0GESkDIYbIqo1Go0GOp3OYFu3bt2wceNGBAQEwMGh+r+SDh8+DL1ej48++ggqVVkn9Pr16+96vFu1b98eFy5cwIULF+Tem5MnTyInJweBgYHVbg8RWQ8OSxFRrQkICMD+/ftx/vx5ZGVlQa/XY/LkycjOzsawYcNw8OBBnD17Fj/88AOioqLuGExatWqF69ev45NPPsG5c+ewcuVKeaJxxeMVFBQgISEBWVlZVQ5XhYWFoVOnThg+fDiOHDmCAwcOYOTIkejduze6d++u+DkgIvNjuCGiWvPqq69CrVYjMDAQ3t7eSE1NRaNGjbB3717odDr0798fnTp1wtSpU+Hp6Sn3yFSlS5cumDdvHubOnYuOHTti1apViImJMSjTs2dPTJw4EZGRkfD29q40IRkoG17asmUL6tevjwcffBBhYWFo0aIF1q1bp/j7J6LaIQkhhKUbQURERKQU9twQERGRTWG4ISIiIpvCcENEREQ2heGGiIiIbArDDREREdkUhhsiIiKyKQw3REREZFMYboiIiMimMNwQERGRTWG4ISIiIpvCcENEREQ2heGGiIiIbMr/A1dtVLlToV3aAAAAAElFTkSuQmCC", + "image/png": "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5x8cPHiwWue4evUqGjVqVGmdku25ubkAiicEW1lZYePGjXKdpKQknDhxQm8ez6ZNm3D//fejcePGerEHBgZCq9Xi119/1TvPU089hWbNmlUrfkONGzdO/tnZ2RkdO3aEvb09hg8fLpd37NgRzs7Oer9f1b0mInPHoSsiM2JjY1PmRti4ceO7zpMpz/nz5wEA7du3L7OtY8eOcgIBAH///TdycnLQvHnzco916dKlap+/KjIzM5GdnY1PPvkEn3zySaXnfuONN/Dzzz/Dz88P7dq1w8CBA/Hss8+ib9++lZ6jUaNGyMrKqrTO1atX5boA4OLiggEDBuDrr7/G3LlzARQPW1lZWek9+fT333/j2LFjFSYvpdutZHiorpX3e+Tk5ISWLVvqzZcqKb/z96u610Rk7pjoEJkRpVJpkvPqdDo0b94c69atK3d7XfVClExufe655zB69Ohy65Q8gdS5c2ckJydj+/btiImJwbfffosVK1Zg1qxZ5U4cLtG5c2ckJiYiNTUVrVq1KrfOsWPHAECvp+uZZ55BWFgYEhMT4e3tja+//hoDBgyAi4uLXvwPP/wwXn/99XKP26FDB73vtra2FcZZmyr6PaqoXAgh/1zdayIyd0x0iOqp1q1bAyj+P/TSkpOT9b63bdsWP//8M/r27VsrN+PSvQYVadasGRo1agStVovAwMC71re3t0dISAhCQkJQWFiIoUOH4r333kNERESFj1k//vjj+Oqrr/DFF1/g7bffLrM9NzcXW7duRadOnfTWrwkODsaECRPk4atTp04hIiJCb9+2bdsiLy+vSrFbivp4TdSwcY4OUT3l7u4Ob29vfP7558jJyZHLd+3aVeax5uHDh0Or1crDNHe6efMmsrOzq3Vue3t7ALjrfkqlEk899ZT8CHhpdz5JdvnyZb1tKpUKXl5eEEKgqKiownM8/fTT8PLywvz588usRK3T6TBx4kT8999/ZZ6wcnZ2RlBQEL7++mts2LABKpUKwcHBenWGDx+OuLg47Ny5s8x5s7OzcfPmzQrjMlf18ZqoYWOPDlE9FhkZiUGDBuG+++7D888/jytXrshr0eTl5cn1+vXrhwkTJiAyMhKJiYkYOHAgrK2t8ffff2PTpk1YunQpnn766Sqf18fHBwAwZcoUBAUFQalU4plnnim37vz587F79274+/tj/Pjx8PLywpUr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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" + }, + { + "ename": "KeyboardInterrupt", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[11], line 52\u001b[0m\n\u001b[1;32m 49\u001b[0m bounds \u001b[38;5;241m=\u001b[39m [(\u001b[38;5;241m0\u001b[39m, \u001b[38;5;241m2\u001b[39m\u001b[38;5;241m*\u001b[39mnp\u001b[38;5;241m.\u001b[39mpi) \u001b[38;5;28;01mfor\u001b[39;00m _ \u001b[38;5;129;01min\u001b[39;00m initial_params]\n\u001b[1;32m 51\u001b[0m \u001b[38;5;66;03m# Perform the optimization\u001b[39;00m\n\u001b[0;32m---> 52\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[43mminimize\u001b[49m\u001b[43m(\u001b[49m\u001b[43mcost_function\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minitial_params\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43margs\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mshape\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmethod\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mBFGS\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43moptions\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m{\u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mmaxiter\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m2000\u001b[39;49m\u001b[43m}\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 54\u001b[0m plt\u001b[38;5;241m.\u001b[39mshow()\n", + "File \u001b[0;32m~/Documents/GitHub/QICKIT/.venv/lib/python3.11/site-packages/scipy/optimize/_minimize.py:726\u001b[0m, in \u001b[0;36mminimize\u001b[0;34m(fun, x0, args, method, jac, hess, hessp, bounds, constraints, tol, callback, options)\u001b[0m\n\u001b[1;32m 724\u001b[0m res \u001b[38;5;241m=\u001b[39m _minimize_cg(fun, x0, args, jac, callback, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39moptions)\n\u001b[1;32m 725\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m meth \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mbfgs\u001b[39m\u001b[38;5;124m'\u001b[39m:\n\u001b[0;32m--> 726\u001b[0m res \u001b[38;5;241m=\u001b[39m \u001b[43m_minimize_bfgs\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfun\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mx0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mjac\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallback\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43moptions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 727\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m meth \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mnewton-cg\u001b[39m\u001b[38;5;124m'\u001b[39m:\n\u001b[1;32m 728\u001b[0m res \u001b[38;5;241m=\u001b[39m _minimize_newtoncg(fun, x0, args, jac, hess, hessp, callback,\n\u001b[1;32m 729\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39moptions)\n", + "File \u001b[0;32m~/Documents/GitHub/QICKIT/.venv/lib/python3.11/site-packages/scipy/optimize/_optimize.py:1397\u001b[0m, in \u001b[0;36m_minimize_bfgs\u001b[0;34m(fun, x0, args, jac, callback, gtol, norm, eps, maxiter, disp, return_all, finite_diff_rel_step, xrtol, c1, c2, hess_inv0, **unknown_options)\u001b[0m\n\u001b[1;32m 1394\u001b[0m pk \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m-\u001b[39mnp\u001b[38;5;241m.\u001b[39mdot(Hk, gfk)\n\u001b[1;32m 1395\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 1396\u001b[0m alpha_k, fc, gc, old_fval, old_old_fval, gfkp1 \u001b[38;5;241m=\u001b[39m \\\n\u001b[0;32m-> 1397\u001b[0m \u001b[43m_line_search_wolfe12\u001b[49m\u001b[43m(\u001b[49m\u001b[43mf\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmyfprime\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mxk\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpk\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mgfk\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1398\u001b[0m \u001b[43m \u001b[49m\u001b[43mold_fval\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mold_old_fval\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mamin\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m1e-100\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1399\u001b[0m \u001b[43m \u001b[49m\u001b[43mamax\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m1e100\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mc1\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mc1\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mc2\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mc2\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1400\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m _LineSearchError:\n\u001b[1;32m 1401\u001b[0m \u001b[38;5;66;03m# Line search failed to find a better solution.\u001b[39;00m\n\u001b[1;32m 1402\u001b[0m warnflag \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m2\u001b[39m\n", + "File \u001b[0;32m~/Documents/GitHub/QICKIT/.venv/lib/python3.11/site-packages/scipy/optimize/_optimize.py:1133\u001b[0m, in \u001b[0;36m_line_search_wolfe12\u001b[0;34m(f, fprime, xk, pk, gfk, old_fval, old_old_fval, **kwargs)\u001b[0m\n\u001b[1;32m 1119\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 1120\u001b[0m \u001b[38;5;124;03mSame as line_search_wolfe1, but fall back to line_search_wolfe2 if\u001b[39;00m\n\u001b[1;32m 1121\u001b[0m \u001b[38;5;124;03msuitable step length is not found, and raise an exception if a\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 1128\u001b[0m \n\u001b[1;32m 1129\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 1131\u001b[0m extra_condition \u001b[38;5;241m=\u001b[39m kwargs\u001b[38;5;241m.\u001b[39mpop(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mextra_condition\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m)\n\u001b[0;32m-> 1133\u001b[0m ret \u001b[38;5;241m=\u001b[39m \u001b[43mline_search_wolfe1\u001b[49m\u001b[43m(\u001b[49m\u001b[43mf\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfprime\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mxk\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpk\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mgfk\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1134\u001b[0m \u001b[43m \u001b[49m\u001b[43mold_fval\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mold_old_fval\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1135\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1137\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m ret[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m extra_condition \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m 1138\u001b[0m xp1 \u001b[38;5;241m=\u001b[39m xk \u001b[38;5;241m+\u001b[39m ret[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;241m*\u001b[39m pk\n", + "File \u001b[0;32m~/Documents/GitHub/QICKIT/.venv/lib/python3.11/site-packages/scipy/optimize/_linesearch.py:93\u001b[0m, in \u001b[0;36mline_search_wolfe1\u001b[0;34m(f, fprime, xk, pk, gfk, old_fval, old_old_fval, args, c1, c2, amax, amin, xtol)\u001b[0m\n\u001b[1;32m 89\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m np\u001b[38;5;241m.\u001b[39mdot(gval[\u001b[38;5;241m0\u001b[39m], pk)\n\u001b[1;32m 91\u001b[0m derphi0 \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mdot(gfk, pk)\n\u001b[0;32m---> 93\u001b[0m stp, fval, old_fval \u001b[38;5;241m=\u001b[39m \u001b[43mscalar_search_wolfe1\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 94\u001b[0m \u001b[43m \u001b[49m\u001b[43mphi\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mderphi\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mold_fval\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mold_old_fval\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mderphi0\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 95\u001b[0m \u001b[43m \u001b[49m\u001b[43mc1\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mc1\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mc2\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mc2\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mamax\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mamax\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mamin\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mamin\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mxtol\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mxtol\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 97\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m stp, fc[\u001b[38;5;241m0\u001b[39m], gc[\u001b[38;5;241m0\u001b[39m], fval, old_fval, gval[\u001b[38;5;241m0\u001b[39m]\n", + "File \u001b[0;32m~/Documents/GitHub/QICKIT/.venv/lib/python3.11/site-packages/scipy/optimize/_linesearch.py:170\u001b[0m, in \u001b[0;36mscalar_search_wolfe1\u001b[0;34m(phi, derphi, phi0, old_phi0, derphi0, c1, c2, amax, amin, xtol)\u001b[0m\n\u001b[1;32m 167\u001b[0m maxiter \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m100\u001b[39m\n\u001b[1;32m 169\u001b[0m dcsrch \u001b[38;5;241m=\u001b[39m DCSRCH(phi, derphi, c1, c2, xtol, amin, amax)\n\u001b[0;32m--> 170\u001b[0m stp, phi1, phi0, task \u001b[38;5;241m=\u001b[39m \u001b[43mdcsrch\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 171\u001b[0m \u001b[43m \u001b[49m\u001b[43malpha1\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mphi0\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mphi0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mderphi0\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mderphi0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmaxiter\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmaxiter\u001b[49m\n\u001b[1;32m 172\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 174\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m stp, phi1, phi0\n", + "File \u001b[0;32m~/Documents/GitHub/QICKIT/.venv/lib/python3.11/site-packages/scipy/optimize/_dcsrch.py:256\u001b[0m, in \u001b[0;36mDCSRCH.__call__\u001b[0;34m(self, alpha1, phi0, derphi0, maxiter)\u001b[0m\n\u001b[1;32m 254\u001b[0m alpha1 \u001b[38;5;241m=\u001b[39m stp\n\u001b[1;32m 255\u001b[0m phi1 \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mphi(stp)\n\u001b[0;32m--> 256\u001b[0m derphi1 \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mderphi\u001b[49m\u001b[43m(\u001b[49m\u001b[43mstp\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 257\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 258\u001b[0m \u001b[38;5;28;01mbreak\u001b[39;00m\n", + "File \u001b[0;32m~/Documents/GitHub/QICKIT/.venv/lib/python3.11/site-packages/scipy/optimize/_linesearch.py:87\u001b[0m, in \u001b[0;36mline_search_wolfe1..derphi\u001b[0;34m(s)\u001b[0m\n\u001b[1;32m 86\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mderphi\u001b[39m(s):\n\u001b[0;32m---> 87\u001b[0m gval[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;241m=\u001b[39m \u001b[43mfprime\u001b[49m\u001b[43m(\u001b[49m\u001b[43mxk\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[43m \u001b[49m\u001b[43ms\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mpk\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 88\u001b[0m gc[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;241m1\u001b[39m\n\u001b[1;32m 89\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m np\u001b[38;5;241m.\u001b[39mdot(gval[\u001b[38;5;241m0\u001b[39m], pk)\n", + "File \u001b[0;32m~/Documents/GitHub/QICKIT/.venv/lib/python3.11/site-packages/scipy/optimize/_differentiable_functions.py:331\u001b[0m, in \u001b[0;36mScalarFunction.grad\u001b[0;34m(self, x)\u001b[0m\n\u001b[1;32m 329\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m np\u001b[38;5;241m.\u001b[39marray_equal(x, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mx):\n\u001b[1;32m 330\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_update_x(x)\n\u001b[0;32m--> 331\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_update_grad\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 332\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mg\n", + "File \u001b[0;32m~/Documents/GitHub/QICKIT/.venv/lib/python3.11/site-packages/scipy/optimize/_differentiable_functions.py:306\u001b[0m, in \u001b[0;36mScalarFunction._update_grad\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 304\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_orig_grad \u001b[38;5;129;01min\u001b[39;00m FD_METHODS:\n\u001b[1;32m 305\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_update_fun()\n\u001b[0;32m--> 306\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mg \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_wrapped_grad\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mx\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mf0\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mf\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 307\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mg_updated \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n", + "File \u001b[0;32m~/Documents/GitHub/QICKIT/.venv/lib/python3.11/site-packages/scipy/optimize/_differentiable_functions.py:47\u001b[0m, in \u001b[0;36m_wrapper_grad..wrapped1\u001b[0;34m(x, f0)\u001b[0m\n\u001b[1;32m 45\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mwrapped1\u001b[39m(x, f0\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m):\n\u001b[1;32m 46\u001b[0m ncalls[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;241m1\u001b[39m\n\u001b[0;32m---> 47\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mapprox_derivative\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 48\u001b[0m \u001b[43m \u001b[49m\u001b[43mfun\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mx\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mf0\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mf0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mfinite_diff_options\u001b[49m\n\u001b[1;32m 49\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/Documents/GitHub/QICKIT/.venv/lib/python3.11/site-packages/scipy/optimize/_numdiff.py:519\u001b[0m, in \u001b[0;36mapprox_derivative\u001b[0;34m(fun, x0, method, rel_step, abs_step, f0, bounds, sparsity, as_linear_operator, args, kwargs)\u001b[0m\n\u001b[1;32m 516\u001b[0m use_one_sided \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mFalse\u001b[39;00m\n\u001b[1;32m 518\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m sparsity \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m--> 519\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43m_dense_difference\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfun_wrapped\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mx0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mf0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mh\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 520\u001b[0m \u001b[43m \u001b[49m\u001b[43muse_one_sided\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmethod\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 521\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 522\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m issparse(sparsity) \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(sparsity) \u001b[38;5;241m==\u001b[39m \u001b[38;5;241m2\u001b[39m:\n", + "File \u001b[0;32m~/Documents/GitHub/QICKIT/.venv/lib/python3.11/site-packages/scipy/optimize/_numdiff.py:592\u001b[0m, in \u001b[0;36m_dense_difference\u001b[0;34m(fun, x0, f0, h, use_one_sided, method)\u001b[0m\n\u001b[1;32m 590\u001b[0m x1[i] \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m h[i]\n\u001b[1;32m 591\u001b[0m dx \u001b[38;5;241m=\u001b[39m x1[i] \u001b[38;5;241m-\u001b[39m x0[i] \u001b[38;5;66;03m# Recompute dx as exactly representable number.\u001b[39;00m\n\u001b[0;32m--> 592\u001b[0m df \u001b[38;5;241m=\u001b[39m \u001b[43mfun\u001b[49m\u001b[43m(\u001b[49m\u001b[43mx1\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;241m-\u001b[39m f0\n\u001b[1;32m 593\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m method \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124m3-point\u001b[39m\u001b[38;5;124m'\u001b[39m \u001b[38;5;129;01mand\u001b[39;00m use_one_sided[i]:\n\u001b[1;32m 594\u001b[0m x1[i] \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m h[i]\n", + "File \u001b[0;32m~/Documents/GitHub/QICKIT/.venv/lib/python3.11/site-packages/scipy/optimize/_numdiff.py:470\u001b[0m, in \u001b[0;36mapprox_derivative..fun_wrapped\u001b[0;34m(x)\u001b[0m\n\u001b[1;32m 467\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m xp\u001b[38;5;241m.\u001b[39misdtype(x\u001b[38;5;241m.\u001b[39mdtype, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mreal floating\u001b[39m\u001b[38;5;124m\"\u001b[39m):\n\u001b[1;32m 468\u001b[0m x \u001b[38;5;241m=\u001b[39m xp\u001b[38;5;241m.\u001b[39mastype(x, x0\u001b[38;5;241m.\u001b[39mdtype)\n\u001b[0;32m--> 470\u001b[0m f \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39matleast_1d(\u001b[43mfun\u001b[49m\u001b[43m(\u001b[49m\u001b[43mx\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m)\n\u001b[1;32m 471\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m f\u001b[38;5;241m.\u001b[39mndim \u001b[38;5;241m>\u001b[39m \u001b[38;5;241m1\u001b[39m:\n\u001b[1;32m 472\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m`fun` return value has \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 473\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mmore than 1 dimension.\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", + "File \u001b[0;32m~/Documents/GitHub/QICKIT/.venv/lib/python3.11/site-packages/scipy/optimize/_differentiable_functions.py:20\u001b[0m, in \u001b[0;36m_wrapper_fun..wrapped\u001b[0;34m(x)\u001b[0m\n\u001b[1;32m 16\u001b[0m ncalls[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;241m1\u001b[39m\n\u001b[1;32m 17\u001b[0m \u001b[38;5;66;03m# Send a copy because the user may overwrite it.\u001b[39;00m\n\u001b[1;32m 18\u001b[0m \u001b[38;5;66;03m# Overwriting results in undefined behaviour because\u001b[39;00m\n\u001b[1;32m 19\u001b[0m \u001b[38;5;66;03m# fun(self.x) will change self.x, with the two no longer linked.\u001b[39;00m\n\u001b[0;32m---> 20\u001b[0m fx \u001b[38;5;241m=\u001b[39m \u001b[43mfun\u001b[49m\u001b[43m(\u001b[49m\u001b[43mnp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mcopy\u001b[49m\u001b[43m(\u001b[49m\u001b[43mx\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 21\u001b[0m \u001b[38;5;66;03m# Make sure the function returns a true scalar\u001b[39;00m\n\u001b[1;32m 22\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m np\u001b[38;5;241m.\u001b[39misscalar(fx):\n", + "Cell \u001b[0;32mIn[11], line 12\u001b[0m, in \u001b[0;36mcost_function\u001b[0;34m(thetas, shape)\u001b[0m\n\u001b[1;32m 9\u001b[0m infidelity \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m1\u001b[39m \u001b[38;5;241m-\u001b[39m \u001b[38;5;28mabs\u001b[39m(target_state\u001b[38;5;241m.\u001b[39mconj()\u001b[38;5;241m.\u001b[39mT \u001b[38;5;241m@\u001b[39m ansatz\u001b[38;5;241m.\u001b[39mansatz\u001b[38;5;241m.\u001b[39mget_statevector())\n\u001b[1;32m 11\u001b[0m infidelities\u001b[38;5;241m.\u001b[39mappend(infidelity)\n\u001b[0;32m---> 12\u001b[0m \u001b[43mupdate_plot\u001b[49m\u001b[43m(\u001b[49m\u001b[43minfidelities\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 14\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m infidelity\n", + "Cell \u001b[0;32mIn[11], line 43\u001b[0m, in \u001b[0;36mupdate_plot\u001b[0;34m(new_data)\u001b[0m\n\u001b[1;32m 41\u001b[0m clear_output(wait\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\n\u001b[1;32m 42\u001b[0m display(fig)\n\u001b[0;32m---> 43\u001b[0m \u001b[43mplt\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpause\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m0.1\u001b[39;49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/Documents/GitHub/QICKIT/.venv/lib/python3.11/site-packages/matplotlib/pyplot.py:760\u001b[0m, in \u001b[0;36mpause\u001b[0;34m(interval)\u001b[0m\n\u001b[1;32m 758\u001b[0m canvas\u001b[38;5;241m.\u001b[39mstart_event_loop(interval)\n\u001b[1;32m 759\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m--> 760\u001b[0m time\u001b[38;5;241m.\u001b[39msleep(interval)\n", + "\u001b[0;31mKeyboardInterrupt\u001b[0m: " + ] } ], "source": [ @@ -208,7 +236,7 @@ "def cost_function(thetas, shape) -> complex:\n", " ansatz.thetas = reshape(thetas, shape)\n", "\n", - " infidelity = 1 - target_state.conj().T @ ansatz.ansatz.get_statevector()\n", + " infidelity = 1 - abs(target_state.conj().T @ ansatz.ansatz.get_statevector())\n", "\n", " infidelities.append(infidelity)\n", " update_plot(infidelities)\n", @@ -260,7 +288,19 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Here's the fidelity." + "Note that we performed the training with the absolute of the probability amplitudes as the image encoding does not require the inner product to be specifically 1+0j. For general state preparation, you need to use\n", + "```py\n", + "def cost_function(thetas, shape) -> complex:\n", + " ansatz.thetas = reshape(thetas, shape)\n", + "\n", + " infidelity = 1 - target_state.conj().T @ ansatz.ansatz.get_statevector()\n", + "\n", + " infidelities.append(infidelity)\n", + " update_plot(infidelities)\n", + "\n", + " return infidelity\n", + "```\n", + "Which will push the fidelity to be exactly 1+0j. However, note that would limit the good parameter space to a very tiny space, whereas for the absolute approach there are many more possible parameter states to choose from. This is how we reach convergence easier compared to a general state. With that being said, pay close attention to the task and what the cost function really needs to do." ] }, { @@ -271,7 +311,7 @@ { "data": { "text/plain": [ - "(0.9184301235740799-0.11993565640369458j)" + "(0.5896664357293144+0.807564869009721j)" ] }, "execution_count": 12, @@ -297,7 +337,7 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxoAAAGKCAYAAACLuTc4AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAFO9JREFUeJzt3X2QVwW9x/HvbxcURNi9wK4WKiM+i5gN2CVIcHyARukK5TUNFCWJIdNpJjNnyoDJFMZLY1Nopg0+hCPyYJp2KRtxLAWRLOdOTTcwddQ0cHkQBRHYc/9w2OtK2h77rgfk9Zphxj2c3f3sb3AP7z27P2pFURQBAACQqK7qAQAAwIeP0AAAANIJDQAAIJ3QAAAA0gkNAAAgndAAAADSCQ0AACCd0AAAANIJDQAAIJ3QAAAA0gkNdlGr1Tr06+GHH/6X39fmzZtj+vTpHX5bDz/8cNRqtVi4cOG//L53Z/Pnz48JEybEEUccEbVaLU4++eSqJwFUyrWpWi0tLXHdddfFiBEjoqmpKRobG2Po0KExf/78qqexG+tS9QB2P3fccUe7l2+//fZ48MEHdzl+zDHH/Mvva/PmzTFjxoyICH+Zfpsbb7wxfve738WJJ54YLS0tVc8BqJxrU7WWLVsW3/zmN+OMM86Ib33rW9GlS5dYtGhRnHvuufGnP/2p7fGCtxMa7GLChAntXl6+fHk8+OCDuxyn89xxxx3Rr1+/qKuri+OOO67qOQCVc22q1sCBA2PVqlXRv3//tmNf/vKX47TTTotZs2bFFVdcET169KhwIbsj3zrF+9La2hrXX399DBw4MLp16xYHHHBATJkyJdavX9/uvJUrV8bo0aOjb9++0b179zj00ENj0qRJERHx7LPPRlNTU0REzJgxo+229/Tp00ttmT59etRqtfjLX/4SEyZMiIaGhmhqaoqrrroqiqKI559/Ps4666zo1atXHHjggTF79ux2r//mm2/Gt7/97Rg8eHA0NDREjx494qSTToqlS5fu8r5aWlri/PPPj169ekVjY2NMnDgxnnrqqajVanHrrbe2O/fPf/5znH322dG7d+/o1q1bDBkyJO67774OfUwHH3xw1NX53xOgDNemzrs2HXrooe0iI+Ktb2cbO3ZsbN26Nf7617+WenzYO7ijwfsyZcqUuPXWW+Oiiy6Kyy67LJ555pn44Q9/GL///e/j0Ucfja5du8aaNWti1KhR0dTUFFdeeWU0NjbGs88+G4sXL46IiKamprjxxhtj6tSpMW7cuPjsZz8bERHHH3/8+9r0+c9/Po455piYOXNmPPDAA3H11VdH796946abbopTTjklZs2aFfPmzYvLL788TjzxxBgxYkRERLz66qtxyy23xHnnnReTJ0+OTZs2xU9+8pMYPXp0rFixIk444YSIeOsC9pnPfCZWrFgRU6dOjaOPPjruvffemDhx4i5b/vjHP8bw4cOjX79+ceWVV0aPHj3i7rvvjrFjx8aiRYti3Lhx7+tjBODduTZ98Neml19+OSIi+vbt+74eHz7kCvgnLrnkkuLtf1R+85vfFBFRzJs3r915S5YsaXf8nnvuKSKieOKJJ971ba9du7aIiGLatGkd2rJ06dIiIooFCxa0HZs2bVoREcWXvvSltmPbt28vDjrooKJWqxUzZ85sO75+/fqie/fuxcSJE9udu3Xr1nbvZ/369cUBBxxQTJo0qe3YokWLiogorr/++rZjO3bsKE455ZQiIoq5c+e2HT/11FOLQYMGFW+88UbbsdbW1mLYsGHFEUcc0aGPdaeBAwcWI0eOLPU6AB92rk1vqeraVBRF0dLSUjQ3NxcnnXRS6ddl7+B7MyhtwYIF0dDQEKeffnq88sorbb8GDx4c+++/f9tt3cbGxoiIuP/++2Pbtm2dvuviiy9u++/6+voYMmRIFEURX/ziF9uONzY2xlFHHdXuFm99fX3ss88+EfHWV4bWrVsX27dvjyFDhsSTTz7Zdt6SJUuia9euMXny5LZjdXV1cckll7TbsW7dunjooYfinHPOiU2bNrU9Pi0tLTF69OhYtWpVvPjii+kfP8DezLXpg702tba2xvjx42PDhg3xgx/8oOMPCHsVoUFpq1atio0bN0Zzc3M0NTW1+/Xaa6/FmjVrIiJi5MiR8bnPfS5mzJgRffv2jbPOOivmzp0bW7du7ZRdhxxySLuXGxoaolu3brvczm1oaNjl+3Vvu+22OP7446Nbt27Rp0+faGpqigceeCA2btzYds5zzz0XH/nIR2K//fZr97qHH354u5dXr14dRVHEVVddtcvjM23atIiItscIgByuTR/stenSSy+NJUuWxC233BIf+9jHOvx67F38jAaltba2RnNzc8ybN+8f/v7OH6Lb+Zziy5cvj5///Ofxy1/+MiZNmhSzZ8+O5cuXx/7775+6q76+vkPHIiKKomj775/+9Kdx4YUXxtixY+PrX/96NDc3R319fVx77bXx9NNPl97R2toaERGXX355jB49+h+e884LAAD/Gtem95Z5bZoxY0bccMMNMXPmzDj//PNLb2HvITQo7bDDDotf//rXMXz48Ojevfs/PX/o0KExdOjQ+O53vxt33nlnjB8/Pu666664+OKLo1arfQCL39vChQtjwIABsXjx4nZ7dn6FZ6f+/fvH0qVLY/Pmze2+crR69ep25w0YMCAiIrp27RqnnXZaJy4HYCfXpg/m2jRnzpyYPn16fPWrX41vfOMb7/vtsHfwrVOUds4558SOHTviO9/5zi6/t3379tiwYUNERKxfv77dV2ciou1ZMnbeot75SXHn61Rh51eW3r718ccfj2XLlrU7b/To0bFt27a4+eab2461trbGnDlz2p3X3NwcJ598ctx0003x0ksv7fL+1q5dmzkfgHBt+iCuTfPnz4/LLrssxo8fH9/73vdKfTzsndzRoLSRI0fGlClT4tprr40//OEPMWrUqOjatWusWrUqFixYEN///vfj7LPPjttuuy1uuOGGGDduXBx22GGxadOmuPnmm6NXr15xxhlnRERE9+7d49hjj4358+fHkUceGb17947jjjvuA/1H6saMGROLFy+OcePGxZlnnhnPPPNM/OhHP4pjjz02Xnvttbbzxo4dG5/4xCfia1/7WqxevTqOPvrouO+++2LdunUREe2+4jRnzpz41Kc+FYMGDYrJkyfHgAED4u9//3ssW7YsXnjhhXjqqafec9MjjzwSjzzySES89cn/9ddfj6uvvjoiIkaMGNH29IcAvMW1qXOvTStWrIgLLrgg+vTpE6eeeuou36I2bNiwtrsm0Kaqp7tiz/HOpxDc6cc//nExePDgonv37kXPnj2LQYMGFVdccUXxt7/9rSiKonjyySeL8847rzjkkEOKfffdt2hubi7GjBlTrFy5st3beeyxx4rBgwcX++yzzz99OsH3egrBtWvXtjt34sSJRY8ePXZ5GyNHjiwGDhzY9nJra2txzTXXFP379y/23Xff4uMf/3hx//33FxMnTiz69+/f7nXXrl1bfOELXyh69uxZNDQ0FBdeeGHx6KOPFhFR3HXXXe3Offrpp4sLLrigOPDAA4uuXbsW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", + "image/png": "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fx6TJ0+O+fPnx/r16+PYY49Nnatr164HdCwioiiK9l//+Mc/jquuuirGjh0bX/va16KhoSG6du0aN910Uzz77LNlz9HW1hYREdddd100NTX923PevgAA+GDspneXuZvmzp0bt99+e8ybNy8mTZpU9iwcPYQGZTv11FPjF7/4RYwcOTJqamre8/zhw4fH8OHD4zvf+U488MADMWHChFiyZElcc801UVVVdRAmfnfLly+PAQMGxMqVK0vm2f8Kz379+/ePNWvWxM6dO0teOdq8eXPJeQMGDIiIiOrq6rj44os7cXIA9rObDs5uWrBgQcyZMye+8pWvxNe//vX3/XU4OvjWKco2bty42Lt3b3z729/u8Ht79uyJV155JSIitm/fXvLqTES0v0vG/kvU+/9Q3H+bStj/ytJbZ/31r38d69atKzmvqakpdu/eHXfddVf7sba2tliwYEHJeQ0NDXHBBRfEwoUL429/+1uH+9u6dWvm+ACE3XQwdtPSpUtjxowZMWHChLj11lvLejwcnVzRoGyNjY0xderUuOmmm+L3v/99jB49Oqqrq2PTpk2xbNmy+N73vheXX3553HfffXH77bdHc3NznHrqqbFjx4646667onfv3nHppZdGRERNTU0MGjQoli5dGh/96EejT58+cdZZZx3Uf6RuzJgxsXLlymhubo5PfepT8dxzz8UPf/jDGDRoULz22mvt540dOzY+8YlPxFe/+tXYvHlznHHGGfHwww/Htm3bIiJKXnFasGBBfPKTn4whQ4bElClTYsCAAfH3v/891q1bF3/5y1/i6aeffteZnnjiiXjiiSciYt8f/v/617/ihhtuiIiIUaNGtb/9IQD72E2du5s2bNgQV155ZRx33HFx0UUXdfgWtREjRrRfNYF2lXq7Kw4fb38Lwf3uvPPOYujQoUVNTU3Rq1evYsiQIcX1119f/PWvfy2KoiieeuqpYvz48cXJJ59cdO/evWhoaCjGjBlTbNy4seTrrF27thg6dGhxzDHHvOfbCb7bWwhu3bq15NyWlpaiZ8+eHb5GY2NjMXjw4PbP29raihtvvLHo379/0b179+LjH/948cgjjxQtLS1F//79S267devW4oorrih69epV1NbWFldddVXx5JNPFhFRLFmypOTcZ599trjyyiuLE044oaiuri769etXjBkzpli+fPk7Pr63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", "text/plain": [ "
" ] @@ -328,7 +368,17 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "For AQC, we often would use a much smaller ansatz so as to justify the training cost. However, for ease in demonstration and better fidelity we used an exact circuit and simply updated the parameters to what Shende's synthesis would have gotten to." + "For AQC, we often would use a much smaller ansatz so as to justify the training cost. However, for ease in demonstration and better fidelity we used an exact circuit and simply updated the parameters to what Shende's synthesis would have gotten to.\n", + "\n", + "It should also be noted that when we attempt a global optimization of the circuit like we did here, we are prone to the **\"orthogonality catastrophe\"**. Orthogonality catastrophe in quantum machine learning refers to a situation where a small change in the parameters of a quantum model causes the quantum state to change drastically, becoming almost completely different (orthogonal) from the original state. This causes an exponential decay in the fidelity measure as the size of the system (number of qubits) increases. That is why for the 6 qubit instance we have 94 fidelity, but for 3 or 4 qubits we can reach 99.999.\n", + "\n", + "This can cause problems during training because:\n", + "- **Vanishing Gradients:** The changes in the cost function become very small, making it hard to update the parameters.\n", + "- **Optimization Instability:** The training process can become unstable and not converge properly.\n", + "\n", + "In Layman terms, the larger the circuit, the harder it is to train when done globally.\n", + "\n", + "To deal with this, techniques like careful parameter initialization, regularization, and adaptive optimization algorithms are used to make the training process smoother and more stable. We can also trade time to run with better convergence by training a batch of parameters at a time instead of all. This means many more runs of the circuit, but mitigates the orthogonality issue." ] }, { From 71c1d27d2e9db0c937453dea8cb3ecb05e682aa7 Mon Sep 17 00:00:00 2001 From: "A.C.E07" Date: Sat, 22 Mar 2025 21:22:52 +0800 Subject: [PATCH 05/58] Fixed #28 --- quick/circuit/circuit.py | 94 +++++++++++++++------ quick/circuit/dag/dagcircuit.py | 70 +++++++++++++++- quick/circuit/dag/dagnode.py | 117 ++++++++++++++++++++++++++- stubs/quick/circuit/circuit.pyi | 6 ++ stubs/quick/circuit/dag/dagnode.pyi | 11 ++- tests/circuit/dag/test_dagcircuit.py | 5 ++ tests/circuit/dag/test_dagnode.py | 47 +++++++++++ tests/circuit/test_circuit_base.py | 111 ++++++++++++++++++++++++- 8 files changed, 426 insertions(+), 35 deletions(-) diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index 085518c..4fdd5ee 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -252,7 +252,7 @@ def _validate_qubit_index( """ if name in ALL_QUBIT_KEYS: if isinstance(value, list): - # For simplicity, we consider the [i] index to be just 1 (int instead of list) + # For simplicity, we consider the [i] index to be just i (int instead of list) if len(value) == 1: value = self._process_single_qubit_index(value[0]) else: @@ -285,7 +285,10 @@ def _validate_single_angle( - Angle must be a number. """ if not isinstance(angle, (int, float)): - raise TypeError(f"Angle must be a number. Unexpected type {type(angle)} received.") + raise TypeError( + "Angle must be a number. " + f"Received {type(angle)} instead." + ) if abs(angle) <= EPSILON or abs(angle % PI_DOUBLE) <= EPSILON: angle = 0.0 @@ -395,7 +398,7 @@ def process_gate_params( params[name] = value - if sorted(list(set(qubit_indices))) != sorted(qubit_indices): + if len(set(qubit_indices)) != len(qubit_indices): raise ValueError( "Qubit indices must be unique. " f"Received {qubit_indices} instead." @@ -5729,23 +5732,20 @@ def compress( # Define angle closeness threshold threshold = PI * compression_percentage - # Initialize a list for the indices that will be removed - indices_to_remove = [] + new_circuit_log = [] # Iterate over all angles, and set the angles within the # compression percentage to 0 (this means the gate does nothing, and can be removed) - for index, operation in enumerate(self.circuit_log): + for operation in self.circuit_log: if "angle" in operation: if abs(operation["angle"]) < threshold: - indices_to_remove.append(index) + continue elif "angles" in operation: if all([abs(angle) < threshold for angle in operation["angles"]]): - indices_to_remove.append(index) - - # Remove the operations with angles within the compression percentage - for index in sorted(indices_to_remove, reverse=True): - del self.circuit_log[index] + continue + new_circuit_log.append(operation) + self.circuit_log = new_circuit_log self.update() def change_mapping( @@ -5771,24 +5771,18 @@ def change_mapping( ----- >>> circuit.change_mapping(qubit_indices=[1, 0]) """ - if not all(isinstance(index, int) for index in qubit_indices): - raise TypeError("Qubit indices must be a collection of integers.") - - if sorted(list(set(qubit_indices))) != list(range(self.num_qubits)): + if len(set(qubit_indices)) != self.num_qubits: raise ValueError("Qubit indices must be unique.") - if isinstance(qubit_indices, Sequence): - qubit_indices = list(qubit_indices) - elif isinstance(qubit_indices, np.ndarray): - qubit_indices = qubit_indices.tolist() + if any(not isinstance(qubit_index, int) for qubit_index in qubit_indices): + raise TypeError("Qubit indices must all be integers.") if self.num_qubits != len(qubit_indices): raise ValueError("The number of qubits must match the number of qubits in the circuit.") - # Update the qubit indices for operation in self.circuit_log: for key in set(operation.keys()).intersection(ALL_QUBIT_KEYS): - if isinstance(operation[key], list): + if isinstance(operation[key], Sequence): operation[key] = [qubit_indices[index] for index in operation[key]] else: operation[key] = qubit_indices[operation[key]] @@ -5829,7 +5823,7 @@ def convert( # Iterate over the gate log and apply corresponding gates in the new framework for gate_info in self.circuit_log: # Extract gate name and remove it from gate_info for kwargs - gate_name = gate_info.pop("gate", None) + gate_name = gate_info.pop("gate") # Extract gate definition and remove it from gate_info for kwargs gate_definition = gate_info.pop("definition", None) @@ -6328,8 +6322,58 @@ def __eq__( >>> circuit1 == circuit2 """ if not isinstance(other_circuit, Circuit): - raise TypeError("Circuits must be compared with other circuits.") - return self.circuit_log == other_circuit.circuit_log + raise TypeError( + "Circuits can only be compared with other Circuits. " + f"Received {type(other_circuit)} instead." + ) + return self.get_dag() == other_circuit.get_dag() + + def is_equivalent( + self, + other_circuit: Circuit, + check_unitary: bool=True, + check_dag: bool=False + ) -> bool: + """ Check if the circuit is equivalent to another circuit. + + Parameters + ---------- + `other_circuit` : quick.circuit.Circuit + The other circuit to compare to. + `check_unitary` : bool, optional, default=True + Whether or not to check the unitary of the circuit. + `check_dag` : bool, optional, default=False + Whether or not to check the DAG of the circuit. + + Returns + ------- + bool + Whether the two circuits are equivalent. + + Raises + ------ + TypeError + - Circuits must be compared with other circuits. + + Usage + ----- + >>> circuit1.is_equivalent(circuit2) + """ + if not isinstance(other_circuit, Circuit): + raise TypeError( + "Circuits can only be compared with other Circuits. " + f"Received {type(other_circuit)} instead." + ) + + if check_unitary: + if not np.allclose(self.get_unitary(), other_circuit.get_unitary()): + return False + + if check_dag: + if self.get_dag() != other_circuit.get_dag(): + return False + + return True def __len__(self) -> int: """ Get the number of the circuit operations. diff --git a/quick/circuit/dag/dagcircuit.py b/quick/circuit/dag/dagcircuit.py index b4fff2e..7b35520 100644 --- a/quick/circuit/dag/dagcircuit.py +++ b/quick/circuit/dag/dagcircuit.py @@ -78,7 +78,33 @@ def add_operation( """ from quick.circuit.circuit import ALL_QUBIT_KEYS - gate_node = DAGNode(operation["gate"]) + # Extract the gate parameters + params = dict() + meta_params = dict() + + # Log all parameters except 'definition' and 'gate' + # in the meta params to ensure uniqueness of the node + # when the gate is repeated on multiple qubits in + # parallel, i.e., a CX followed by two H on control + # and target + # This avoids `__eq__` issues with DAGNode + for key in operation: + if key not in ALL_QUBIT_KEYS.union(['definition', 'gate']): + params[key] = operation[key] + if key not in ['definition', 'gate']: + meta_params[key] = operation[key] + + # Define the name of the node + # For simplicity, we omit the empty params for gates + # that only have qubit indices as parameter + if params == {}: + node_name = f"{operation['gate']}" + else: + node_name = f"{operation['gate']}({str(params).strip('{}')})" + + meta_name = f"{operation['gate']}({str(meta_params).strip('{}')})" + + gate_node = DAGNode(node_name, meta_name=meta_name) qubit_indices = [] # Add qubits from any valid qubit key to the @@ -108,6 +134,48 @@ def get_depth(self) -> int: """ return max(qubit.depth for qubit in self.qubits.values()) + def __eq__( + self, + other_circuit: object + ) -> bool: + """ Check if two circuits are equal. + + Parameters + ---------- + `other_circuit` : object + The other circuit to compare with. + + Returns + ------- + bool + True if the two circuits are equal, False otherwise. + + Raises + ------ + TypeError + If the other circuit is not an instance of `quick.circuit.dag.DAGCircuit`. + + Usage + ----- + >>> dag1 = DAGCircuit(2) + >>> dag2 = DAGCircuit(2) + >>> dag1 == dag2 + """ + if not isinstance(other_circuit, DAGCircuit): + raise TypeError( + "DAGCircuits can only be compared with other DAGCircuits. " + f"Received {type(other_circuit)} instead." + ) + + if self.num_qubits != other_circuit.num_qubits: + return False + + for qubit in self.qubits: + if self.qubits[qubit] != other_circuit.qubits[qubit]: + return False + + return True + def __repr__(self) -> str: """ Get the string representation of the circuit. diff --git a/quick/circuit/dag/dagnode.py b/quick/circuit/dag/dagnode.py index 3cec2ed..fda1fd4 100644 --- a/quick/circuit/dag/dagnode.py +++ b/quick/circuit/dag/dagnode.py @@ -45,6 +45,9 @@ class DAGNode: ---------- `name` : str The name of the node. + `meta_name` : str, optional, default=name + The meta name of the node. This is used to store additional information + and prevents identical nodes which can lead to issues with `__eq__`. `parents` : set[quick.circuit.dag.DAGNode], optional, default=set() A set of parent nodes. `children` : set[quick.circuit.dag.DAGNode], optional, default=set() @@ -55,9 +58,20 @@ class DAGNode: >>> node1 = DAGNode("Node 1") """ name: Hashable = None + meta_name: Hashable = None parents: set[DAGNode] = field(default_factory=set) children: set[DAGNode] = field(default_factory=set) + def __post_init__(self) -> None: + """ Initialize the node. + + Notes + ----- + This method is called after the node is initialized. We set the + `meta_name` attribute to the `name` attribute if it is not provided. + """ + self.meta_name = self.name if self.meta_name is None else self.meta_name + def _invalidate_depth(self) -> None: """ Invalidate the cached depth of the node. @@ -125,6 +139,26 @@ def to( if hasattr(self, "_depth"): self._invalidate_depth() + def walk(self): + """ Walk through the children nodes of the current node. + + Yields + ------ + `child` : quick.circuit.dag.DAGNode + The next child node. + + Usage + ----- + >>> node1 = DAGNode("Node 1") + >>> node2 = DAGNode("Node 2") + >>> node1.to(node2) + >>> for child in node1.walk(): + ... print(child) + """ + for child in sorted(self.children): + yield child + yield from child.walk() + @property def depth(self) -> int: """ Get the depth of the node. @@ -268,15 +302,51 @@ def __hash__(self) -> int: """ return hash(id(self)) + def __lt__( + self, + other_node: object + ) -> bool: + """ Check if this node is less than or equal to another node. + + Parameters + ---------- + `other_node` : object + The object to compare to. + + Returns + ------- + bool + True if this node is less than or equal to the other node, + False otherwise. + + Raises + ------ + TypeError + - If `other_node` is not an instance of `quick.circuit.dag.DAGNode`. + + Usage + ----- + >>> node1 = DAGNode("Node 1") + >>> node2 = DAGNode("Node 2") + >>> node1 < node2 + """ + if not isinstance(other_node, DAGNode): + raise TypeError( + "The `other_node` must be an instance of DAGNode. " + f"Received {type(other_node)} instead." + ) + + return str(self.meta_name) < str(other_node.meta_name) + def __eq__( self, - other: object + other_node: object ) -> bool: """ Check if two nodes are equal. Parameters ---------- - `other` : object + `other_node` : object The object to compare to. Returns @@ -284,16 +354,55 @@ def __eq__( bool True if the nodes are equal, False otherwise. + Raises + ------ + TypeError + - If `other_node` is not an instance of `quick.circuit.dag.DAGNode`. + Usage ----- >>> node1 = DAGNode("Node 1") >>> node2 = DAGNode("Node 2") >>> node1 == node2 """ - if not isinstance(other, DAGNode): + if not isinstance(other_node, DAGNode): + raise TypeError( + "The `other_node` must be an instance of DAGNode. " + f"Received {type(other_node)} instead." + ) + + if self.meta_name != other_node.meta_name: + return False + + inclusion_check: dict[DAGNode, int] = {} + self_stack: list[DAGNode] = [self] + other_stack: list[DAGNode] = [other_node] + + while self_stack and other_stack: + self_node = self_stack.pop() + other_node = other_stack.pop() + + if self_node in inclusion_check and other_node in inclusion_check: + continue + + # Set the inclusion check of the nodes to zero + # (this value is arbitrary, we just need a placeholder) + inclusion_check[self_node] = 0 + inclusion_check[other_node] = 0 + + for self_child, other_child in zip(sorted(self_node.children), sorted(other_node.children)): + self_stack.append(self_child) + other_stack.append(other_child) + + if self_child.meta_name != other_child.meta_name: + return False + + # If two DAGs are equal up until the end, but one has additional + # nodes afterwards it will be caught here + if self_stack or other_stack: return False - return self.name == other.name and self.children == other.children + return True def __repr__(self) -> str: """ Get the string representation of the node. diff --git a/stubs/quick/circuit/circuit.pyi b/stubs/quick/circuit/circuit.pyi index 26ff27f..c20d8ae 100644 --- a/stubs/quick/circuit/circuit.pyi +++ b/stubs/quick/circuit/circuit.pyi @@ -334,6 +334,12 @@ class Circuit(ABC, metaclass=abc.ABCMeta): def __getitem__(self, index: int | slice) -> Circuit: ... def __setitem__(self, index: int | slice, gates: dict | list[dict] | Circuit) -> None: ... def __eq__(self, other_circuit: object) -> bool: ... + def is_equivalent( + self, + other_circuit: Circuit, + check_unitary: bool=True, + check_dag: bool=False + ) -> bool: ... def __len__(self) -> int: ... def __str__(self) -> str: ... def __repr__(self) -> str: ... diff --git a/stubs/quick/circuit/dag/dagnode.pyi b/stubs/quick/circuit/dag/dagnode.pyi index 16743e1..87029ab 100644 --- a/stubs/quick/circuit/dag/dagnode.pyi +++ b/stubs/quick/circuit/dag/dagnode.pyi @@ -12,20 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Hashable __all__ = ["DAGNode"] @dataclass class DAGNode: - name: Hashable = ... - parents: set[DAGNode] = ... - children: set[DAGNode] = ... + name: Hashable = None + meta_name: Hashable = None + parents: set[DAGNode] = field(default_factory=set) + children: set[DAGNode] = field(default_factory=set) + def __post_init__(self) -> None: ... def to(self, child: DAGNode) -> None: ... @property def depth(self) -> int: ... def generate_paths(self) -> set[tuple[Hashable]]: ... def __hash__(self) -> int: ... + def __lt__(self, other: object) -> bool: ... def __eq__(self, other: object) -> bool: ... def __init__(self, name=..., parents=..., children=...) -> None: ... diff --git a/tests/circuit/dag/test_dagcircuit.py b/tests/circuit/dag/test_dagcircuit.py index 72c2da8..8d847c6 100644 --- a/tests/circuit/dag/test_dagcircuit.py +++ b/tests/circuit/dag/test_dagcircuit.py @@ -38,6 +38,11 @@ def test_add_operation(self) -> None: assert repr(circuit) == "\n".join(["Q0: Q0 -> {H -> {CX}}", "Q1: Q1 -> {CX}"]) + circuit = DAGCircuit(1) + circuit.add_operation({"gate": "RX", "angle": 0.1, "qubit_indices": 0}) + + assert repr(circuit) == "Q0: Q0 -> {RX('angle': 0.1)}" + def test_get_depth(self) -> None: """ Test the `get_depth` method of a `DAGCircuit` object. """ diff --git a/tests/circuit/dag/test_dagnode.py b/tests/circuit/dag/test_dagnode.py index 0cbd2ed..58e4753 100644 --- a/tests/circuit/dag/test_dagnode.py +++ b/tests/circuit/dag/test_dagnode.py @@ -29,6 +29,7 @@ def test_init(self) -> None: """ dagnode = DAGNode("test_node") assert dagnode.name == "test_node" + assert dagnode.meta_name == "test_node" assert dagnode.children == set() assert dagnode.parents == set() @@ -96,6 +97,52 @@ def test_to_invalid(self) -> None: with pytest.raises(TypeError): dagnode1.to(dagnode2) # type: ignore + def test_lt(self) -> None: + """ Test the less than comparison of two `DAGNode` objects. + """ + dagnode1 = DAGNode("node1") + dagnode2 = DAGNode("node2") + + assert dagnode1 < dagnode2 + + def test_lt_invalid(self) -> None: + """ Test the less than comparison of a `DAGNode` object with an invalid argument. + """ + dagnode = DAGNode("node1") + invalid = "node2" + + with pytest.raises(TypeError): + dagnode < invalid # type: ignore + + def test_eq(self) -> None: + """ Test the equality of two `DAGNode` objects. + """ + dagnode1 = DAGNode("node1") + dagnode2 = DAGNode("node2") + dagnode3 = DAGNode("node1") + + assert dagnode1 == dagnode3 + assert dagnode1 != dagnode2 + + node_a = DAGNode("A") + node_b = DAGNode("B") + node_a2 = DAGNode("A") + node_b2 = DAGNode("B") + + node_a.to(node_b) + node_a2.to(node_b2) + + assert node_a == node_a2 + + def test_eq_invalid(self) -> None: + """ Test the equality of a `DAGNode` object with an invalid argument. + """ + dagnode = DAGNode("node1") + invalid = "node2" + + with pytest.raises(TypeError): + dagnode == invalid # type: ignore + def test_str(self) -> None: """ Test the string representation of a `DAGNode` object. """ diff --git a/tests/circuit/test_circuit_base.py b/tests/circuit/test_circuit_base.py index 3ee9882..c9ffad1 100644 --- a/tests/circuit/test_circuit_base.py +++ b/tests/circuit/test_circuit_base.py @@ -866,7 +866,7 @@ def test_change_mapping_indices_value_error( circuit.change_mapping([0, 1, 2]) @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) - def test_from_circuit( + def test_convert( self, circuit_framework: Type[Circuit] ) -> None: @@ -1225,6 +1225,115 @@ def test_eq( for circuit_1, circuit_2 in zip(circuits[0:-1:], circuits[1::]): assert circuit_1 == circuit_2 + # Test the equality of circuits when the order + # of the gates are different but the circuit is the same + circuit_1 = [circuit_framework(2) for circuit_framework in circuit_frameworks] + for circuit in circuit_1: + circuit.H(0) + circuit.X(1) + + circuit_2 = [circuit_framework(2) for circuit_framework in circuit_frameworks] + for circuit in circuit_2: + circuit.X(1) + circuit.H(0) + + # Test the equality of the circuits + for circuit_1, circuit_2 in zip(circuit_1, circuit_2): + assert circuit_1 == circuit_2 + + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + def test_eq_fail( + self, + circuit_framework: Type[Circuit] + ) -> None: + """ Test the `__eq__` dunder method failure. + + Parameters + ---------- + `circuit_framework`: type[quick.circuit.Circuit] + The circuit framework to test. + """ + circuit_1 = circuit_framework(2) + circuit_2 = circuit_framework(3) + + assert not circuit_1 == circuit_2 + + circuit_1 = circuit_framework(2) + circuit_2 = circuit_framework(2) + + circuit_1.H(0) + circuit_2.X(0) + + assert not circuit_1 == circuit_2 + + circuit_1 = circuit_framework(2) + circuit_2 = circuit_framework(2) + + circuit_1.H(0) + circuit_2.H(0) + + circuit_1.CX(0, 1) + circuit_2.CX(0, 1) + + circuit_1.H(0) + circuit_2.H(1) + + assert not circuit_1 == circuit_2 + + circuit_1 = circuit_framework(2) + circuit_2 = circuit_framework(2) + + circuit_1.CX(0, 1) + circuit_2.CX(1, 0) + + assert not circuit_1 == circuit_2 + + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + def test_eq_invalid_type( + self, + circuit_framework: Type[Circuit] + ) -> None: + """ Test the `__eq__` dunder method failure with invalid type. + + Parameters + ---------- + `circuit_framework`: type[quick.circuit.Circuit] + The circuit framework to test. + """ + circuit = circuit_framework(2) + + with pytest.raises(TypeError): + circuit == "circuit" # type: ignore + + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + def test_is_equivalent( + self, + circuit_framework: Type[Circuit] + ) -> None: + """ Test the `is_equivalent` method. + + Parameters + ---------- + `circuit_framework`: type[quick.circuit.Circuit] + The circuit framework to test. + """ + # Define the circuits + circuit_1 = circuit_framework(3) + circuit_2 = circuit_framework(3) + + # Define the GHZ state + circuit_1.H(0) + circuit_1.CX(0, 1) + circuit_1.CX(0, 2) + + circuit_2.H(0) + circuit_2.CX(0, 2) + circuit_2.CX(0, 1) + + # Test the equivalence of the circuits + assert circuit_1.is_equivalent(circuit_2) + assert not circuit_1.is_equivalent(circuit_2, check_dag=True) + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_len( self, From eb5cb5a6bc0dc3c3eeb6ceecf75862479cbd7688 Mon Sep 17 00:00:00 2001 From: "A.C.E07" Date: Tue, 25 Mar 2025 18:27:34 +0800 Subject: [PATCH 06/58] Changed Type to type due to typing deprecation --- quick/backend/backend.py | 11 +- quick/circuit/circuit.py | 16 +- quick/circuit/from_framework/from_cirq.py | 4 +- .../circuit/from_framework/from_framework.py | 4 +- quick/circuit/from_framework/from_qiskit.py | 4 +- quick/circuit/from_framework/from_tket.py | 4 +- quick/compiler/compiler.py | 8 +- .../mcx_vchain.py | 6 +- .../statepreparation/statepreparation.py | 4 +- .../qiskit_unitary_transpiler.py | 4 +- .../unitarypreparation/unitarypreparation.py | 4 +- stubs/quick/circuit/circuit.pyi | 14 +- .../circuit/from_framework/from_framework.pyi | 4 +- stubs/quick/compiler/compiler.pyi | 8 +- .../statepreparation/statepreparation.pyi | 6 +- .../unitarypreparation/unitarypreparation.pyi | 3 +- tests/circuit/test_ansatz.py | 32 ++-- tests/circuit/test_circuit_base.py | 86 ++++----- tests/circuit/test_circuit_decompose.py | 10 +- tests/circuit/test_circuit_to_controlled.py | 86 ++++----- tests/circuit/test_control_state.py | 176 +++++++++--------- tests/circuit/test_qft_circuit.py | 24 +-- .../test_uniformly_controlled_gates.py | 16 +- .../unitarypreparation/test_diffusion.py | 2 +- 24 files changed, 267 insertions(+), 269 deletions(-) diff --git a/quick/backend/backend.py b/quick/backend/backend.py index 378f9ca..9eec55a 100644 --- a/quick/backend/backend.py +++ b/quick/backend/backend.py @@ -28,7 +28,6 @@ import numpy as np from numpy.typing import NDArray from types import NotImplementedType -from typing import Type from quick.circuit import Circuit @@ -66,7 +65,7 @@ def __init__( if device not in ["CPU", "GPU"]: raise ValueError(f"Invalid device: {device}. Must be either 'CPU' or 'GPU'.") self.device = device - self._qc_framework: Type[Circuit] + self._qc_framework: type[Circuit] @staticmethod def backendmethod(method): @@ -316,7 +315,7 @@ class NoisyBackend(Backend, ABC): `device` : str The device to use for simulating the circuit. This can be either "CPU", or "GPU". - `_qc_framework` : Type[quick.circuit.Circuit] + `_qc_framework` : type[quick.circuit.Circuit] The quantum computing framework to use. `noisy` : bool Whether the simulation is noisy or not. @@ -348,7 +347,7 @@ def __init__( self.noisy = self.single_qubit_error > 0.0 or self.two_qubit_error > 0.0 - self._qc_framework: Type[Circuit] + self._qc_framework: type[Circuit] class FakeBackend(Backend, ABC): @@ -367,7 +366,7 @@ class FakeBackend(Backend, ABC): `device` : str The device to use for simulating the circuit. This can be either "CPU", or "GPU". - `_qc_framework` : Type[quick.circuit.Circuit] + `_qc_framework` : type[quick.circuit.Circuit] The quantum computing framework to use. `_backend_name` : str The name of the backend to use (usually the name of the backend being emulated). @@ -386,7 +385,7 @@ def __init__( """ Initialize a `quick.backend.FakeBackend` instance. """ super().__init__(device=device) - self._qc_framework: Type[Circuit] + self._qc_framework: type[Circuit] self._backend_name: str self._max_num_qubits: int diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index 085518c..1c40e80 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -29,7 +29,7 @@ from numpy.typing import NDArray from types import NotImplementedType from typing import ( - Any, Callable, Literal, overload, SupportsFloat, SupportsIndex, Type, TYPE_CHECKING + Any, Callable, Literal, overload, SupportsFloat, SupportsIndex, TYPE_CHECKING ) import qiskit # type: ignore @@ -5797,7 +5797,7 @@ def change_mapping( def convert( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> Circuit: """ Convert the circuit to another circuit framework. @@ -5973,7 +5973,7 @@ def to_qasm( @staticmethod def from_cirq( cirq_circuit: cirq.Circuit, - output_framework: Type[Circuit] + output_framework: type[Circuit] ) -> Circuit: """ Create a `quick.Circuit` from a `cirq.Circuit`. @@ -6005,7 +6005,7 @@ def from_cirq( @staticmethod def from_pennylane( pennylane_circuit: qml.QNode, - output_framework: Type[Circuit] + output_framework: type[Circuit] ) -> Circuit: """ Create a `quick.circuit.Circuit` from a `qml.QNode`. @@ -6043,7 +6043,7 @@ def from_pennylane( @staticmethod def from_qiskit( qiskit_circuit: qiskit.QuantumCircuit, - output_framework: Type[Circuit] + output_framework: type[Circuit] ) -> Circuit: """ Create a `quick.circuit.Circuit` from a `qiskit.QuantumCircuit`. @@ -6075,7 +6075,7 @@ def from_qiskit( @staticmethod def from_tket( tket_circuit: pytket.Circuit, - output_framework: Type[Circuit] + output_framework: type[Circuit] ) -> Circuit: """ Create a `quick.circuit.Circuit` from a `tket.Circuit`. @@ -6107,7 +6107,7 @@ def from_tket( @staticmethod def from_qasm( qasm: str, - output_framework: Type[Circuit] + output_framework: type[Circuit] ) -> Circuit: """ Create a `quick.circuit.Circuit` from a QASM string. @@ -6146,7 +6146,7 @@ def from_qasm( @staticmethod def from_quimb( quimb_circuit: qtn.Circuit, - output_framework: Type[Circuit] + output_framework: type[Circuit] ) -> Circuit: """ Create a `quick.circuit.Circuit` from a `qtn.Circuit`. diff --git a/quick/circuit/from_framework/from_cirq.py b/quick/circuit/from_framework/from_cirq.py index 6888d0a..4b7e063 100644 --- a/quick/circuit/from_framework/from_cirq.py +++ b/quick/circuit/from_framework/from_cirq.py @@ -21,7 +21,7 @@ import cirq # type: ignore import numpy as np -from typing import Type, TYPE_CHECKING +from typing import TYPE_CHECKING if TYPE_CHECKING: from quick.circuit import Circuit @@ -76,7 +76,7 @@ class FromCirq(FromFramework): """ def __init__( self, - output_framework: Type[Circuit] + output_framework: type[Circuit] ) -> None: super().__init__(output_framework=output_framework) diff --git a/quick/circuit/from_framework/from_framework.py b/quick/circuit/from_framework/from_framework.py index deb48a1..b4cfde2 100644 --- a/quick/circuit/from_framework/from_framework.py +++ b/quick/circuit/from_framework/from_framework.py @@ -21,7 +21,7 @@ __all__ = ["FromFramework"] from abc import ABC, abstractmethod -from typing import Any, Type, TYPE_CHECKING +from typing import Any, TYPE_CHECKING import quick if TYPE_CHECKING: @@ -53,7 +53,7 @@ class FromFramework(ABC): """ def __init__( self, - output_framework: Type[Circuit] + output_framework: type[Circuit] ) -> None: """ Initialize a `quick.circuit.from_framework.FromFramework` instance. """ diff --git a/quick/circuit/from_framework/from_qiskit.py b/quick/circuit/from_framework/from_qiskit.py index 6f9f781..a26e3af 100644 --- a/quick/circuit/from_framework/from_qiskit.py +++ b/quick/circuit/from_framework/from_qiskit.py @@ -21,7 +21,7 @@ from qiskit import QuantumCircuit, transpile # type: ignore from qiskit._accelerate.circuit import CircuitInstruction # type: ignore -from typing import Type, TYPE_CHECKING +from typing import TYPE_CHECKING if TYPE_CHECKING: from quick.circuit import Circuit @@ -74,7 +74,7 @@ class FromQiskit(FromFramework): """ def __init__( self, - output_framework: Type[Circuit] + output_framework: type[Circuit] ) -> None: super().__init__(output_framework=output_framework) diff --git a/quick/circuit/from_framework/from_tket.py b/quick/circuit/from_framework/from_tket.py index 171df16..5fa6c41 100644 --- a/quick/circuit/from_framework/from_tket.py +++ b/quick/circuit/from_framework/from_tket.py @@ -24,7 +24,7 @@ from pytket._tket.circuit import Command from pytket import OpType from pytket.passes import AutoRebase -from typing import Type, TYPE_CHECKING +from typing import TYPE_CHECKING if TYPE_CHECKING: from quick.circuit import Circuit @@ -74,7 +74,7 @@ class FromTKET(FromFramework): """ def __init__( self, - output_framework: Type[Circuit] + output_framework: type[Circuit] ) -> None: super().__init__(output_framework=output_framework) diff --git a/quick/compiler/compiler.py b/quick/compiler/compiler.py index 7a675aa..0446928 100644 --- a/quick/compiler/compiler.py +++ b/quick/compiler/compiler.py @@ -22,7 +22,7 @@ from collections.abc import Sequence import numpy as np from numpy.typing import NDArray -from typing import Type, TypeAlias +from typing import TypeAlias from quick.circuit import Circuit from quick.optimizer import Optimizer @@ -90,9 +90,9 @@ class Compiler: """ def __init__( self, - circuit_framework: Type[Circuit], - state_prep: Type[StatePreparation]=Isometry, - unitary_prep: Type[UnitaryPreparation]=ShannonDecomposition, + circuit_framework: type[Circuit], + state_prep: type[StatePreparation]=Isometry, + unitary_prep: type[UnitaryPreparation]=ShannonDecomposition, optimizer: Optimizer | None=None ) -> None: """ Initialize a `quick.compiler.Compiler` object. diff --git a/quick/synthesis/gate_decompositions/multi_controlled_decomposition/mcx_vchain.py b/quick/synthesis/gate_decompositions/multi_controlled_decomposition/mcx_vchain.py index f08a999..0605bcf 100644 --- a/quick/synthesis/gate_decompositions/multi_controlled_decomposition/mcx_vchain.py +++ b/quick/synthesis/gate_decompositions/multi_controlled_decomposition/mcx_vchain.py @@ -22,7 +22,7 @@ __all__ = ["MCXVChain"] -from typing import Type, TYPE_CHECKING +from typing import TYPE_CHECKING if TYPE_CHECKING: from quick.circuit import Circuit @@ -80,7 +80,7 @@ def get_num_ancillas(num_controls) -> int: def define_decomposition( self, num_controls: int, - output_framework: Type[Circuit] + output_framework: type[Circuit] ) -> Circuit: """ Define the V-chain decomposition of the MCX gate. @@ -88,7 +88,7 @@ def define_decomposition( ---------- `num_controls` : int Number of control qubits for the MCX gate. - `output_framework` : Type[quick.circuit.Circuit] + `output_framework` : type[quick.circuit.Circuit] The circuit framework to be used for the decomposition. Returns diff --git a/quick/synthesis/statepreparation/statepreparation.py b/quick/synthesis/statepreparation/statepreparation.py index c784d86..97dbba8 100644 --- a/quick/synthesis/statepreparation/statepreparation.py +++ b/quick/synthesis/statepreparation/statepreparation.py @@ -24,7 +24,7 @@ from collections.abc import Sequence import numpy as np from numpy.typing import NDArray -from typing import Literal, Type, TYPE_CHECKING +from typing import Literal, TYPE_CHECKING import quick if TYPE_CHECKING: @@ -56,7 +56,7 @@ class StatePreparation(ABC): """ def __init__( self, - output_framework: Type[Circuit] + output_framework: type[Circuit] ) -> None: """ Initalize a State Preparation instance. """ diff --git a/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.py b/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.py index 096411d..371c8ee 100644 --- a/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.py +++ b/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.py @@ -23,7 +23,7 @@ from collections.abc import Sequence import numpy as np from numpy.typing import NDArray -from typing import SupportsIndex, Type, TYPE_CHECKING +from typing import SupportsIndex, TYPE_CHECKING from qiskit import QuantumCircuit, transpile # type: ignore from qiskit.transpiler.passes import unitary_synthesis_plugin_names # type: ignore @@ -96,7 +96,7 @@ class QiskitUnitaryTranspiler(UnitaryPreparation): """ def __init__( self, - output_framework: Type[Circuit], + output_framework: type[Circuit], ai_transpilation: bool=False, unitary_synthesis_plugin: str="default", service: QiskitRuntimeService | None = None, diff --git a/quick/synthesis/unitarypreparation/unitarypreparation.py b/quick/synthesis/unitarypreparation/unitarypreparation.py index 7348732..d64b028 100644 --- a/quick/synthesis/unitarypreparation/unitarypreparation.py +++ b/quick/synthesis/unitarypreparation/unitarypreparation.py @@ -24,7 +24,7 @@ from collections.abc import Sequence import numpy as np from numpy.typing import NDArray -from typing import Type, TYPE_CHECKING +from typing import TYPE_CHECKING import quick if TYPE_CHECKING: @@ -52,7 +52,7 @@ class UnitaryPreparation(ABC): """ def __init__( self, - output_framework: Type[Circuit] + output_framework: type[Circuit] ) -> None: """ Initalize a Unitary Preparation instance. """ diff --git a/stubs/quick/circuit/circuit.pyi b/stubs/quick/circuit/circuit.pyi index 26ff27f..26cd9c6 100644 --- a/stubs/quick/circuit/circuit.pyi +++ b/stubs/quick/circuit/circuit.pyi @@ -28,7 +28,7 @@ from quick.circuit.dag import DAGCircuit from quick.primitives import Bra, Ket, Operator from quick.synthesis.unitarypreparation import UnitaryPreparation from types import NotImplementedType -from typing import Any, Callable, Literal, Type +from typing import Any, Callable, Literal __all__ = ["Circuit"] @@ -311,21 +311,21 @@ class Circuit(ABC, metaclass=abc.ABCMeta): def transpile(self, direct_transpile: bool=True, synthesis_method: UnitaryPreparation | None = None) -> None: ... def compress(self, compression_percentage: float) -> None: ... def change_mapping(self, qubit_indices: Sequence[int]) -> None: ... - def convert(self, circuit_framework: Type[Circuit]) -> Circuit: ... + def convert(self, circuit_framework: type[Circuit]) -> Circuit: ... def control(self, num_controls: int) -> Circuit: ... def update(self) -> None: ... @abstractmethod def to_qasm(self) -> str: ... @staticmethod - def from_cirq(cirq_circuit: cirq.Circuit, output_framework: Type[Circuit]) -> Circuit: ... + def from_cirq(cirq_circuit: cirq.Circuit, output_framework: type[Circuit]) -> Circuit: ... @staticmethod - def from_pennylane(pennylane_circuit: qml.QNode, output_framework: Type[Circuit]) -> Circuit: ... + def from_pennylane(pennylane_circuit: qml.QNode, output_framework: type[Circuit]) -> Circuit: ... @staticmethod - def from_qiskit(qiskit_circuit: qiskit.QuantumCircuit, output_framework: Type[Circuit]) -> Circuit: ... + def from_qiskit(qiskit_circuit: qiskit.QuantumCircuit, output_framework: type[Circuit]) -> Circuit: ... @staticmethod - def from_tket(tket_circuit: pytket.Circuit, output_framework: Type[Circuit]) -> Circuit: ... + def from_tket(tket_circuit: pytket.Circuit, output_framework: type[Circuit]) -> Circuit: ... @staticmethod - def from_qasm(qasm: str, output_framework: Type[Circuit]) -> Circuit: ... + def from_qasm(qasm: str, output_framework: type[Circuit]) -> Circuit: ... def copy(self) -> Circuit: ... def reset(self) -> None: ... @abstractmethod diff --git a/stubs/quick/circuit/from_framework/from_framework.pyi b/stubs/quick/circuit/from_framework/from_framework.pyi index 9f8bb91..ed62ce0 100644 --- a/stubs/quick/circuit/from_framework/from_framework.pyi +++ b/stubs/quick/circuit/from_framework/from_framework.pyi @@ -15,12 +15,12 @@ import abc from abc import ABC, abstractmethod from quick.circuit import Circuit -from typing import Any, Type +from typing import Any __all__ = ["FromFramework"] class FromFramework(ABC, metaclass=abc.ABCMeta): - output_framework: Type[Circuit] + output_framework: type[Circuit] def __init__(self, output_framework: type[Circuit]) -> None: ... @abstractmethod def convert(self, circuit: Any) -> Circuit: ... diff --git a/stubs/quick/compiler/compiler.pyi b/stubs/quick/compiler/compiler.pyi index a683aea..287ab55 100644 --- a/stubs/quick/compiler/compiler.pyi +++ b/stubs/quick/compiler/compiler.pyi @@ -20,7 +20,7 @@ from quick.optimizer import Optimizer from quick.primitives import Bra, Ket, Operator from quick.synthesis.statepreparation import StatePreparation from quick.synthesis.unitarypreparation import UnitaryPreparation -from typing import Type, TypeAlias +from typing import TypeAlias __all__ = ["Compiler"] @@ -29,9 +29,9 @@ PRIMITIVES: TypeAlias = list[tuple[PRIMITIVE, Sequence[int]]] class Compiler: - circuit_framework: Type[Circuit] - state_prep: Type[StatePreparation] - unitary_prep: Type[UnitaryPreparation] + circuit_framework: type[Circuit] + state_prep: type[StatePreparation] + unitary_prep: type[UnitaryPreparation] optimizer: Optimizer def __init__(self, circuit_framework: Circuit, state_prep: type[StatePreparation] = ..., unitary_prep: type[UnitaryPreparation] = ..., mlir: bool = True) -> None: ... def state_preparation(self, state: NDArray[np.complex128] | Bra | Ket) -> Circuit: ... diff --git a/stubs/quick/synthesis/statepreparation/statepreparation.pyi b/stubs/quick/synthesis/statepreparation/statepreparation.pyi index 1a4bdda..6999418 100644 --- a/stubs/quick/synthesis/statepreparation/statepreparation.pyi +++ b/stubs/quick/synthesis/statepreparation/statepreparation.pyi @@ -19,13 +19,13 @@ import numpy as np from numpy.typing import NDArray from quick.circuit import Circuit from quick.primitives import Bra, Ket -from typing import Literal, Type +from typing import Literal __all__ = ["StatePreparation"] class StatePreparation(ABC, metaclass=abc.ABCMeta): - output_framework: Type[Circuit] - def __init__(self, output_framework: Type[Circuit]) -> None: ... + output_framework: type[Circuit] + def __init__(self, output_framework: type[Circuit]) -> None: ... def prepare_state(self, state: NDArray[np.complex128] | Bra | Ket, compression_percentage: float=0.0, index_type: str="row") -> Circuit: ... @abstractmethod def apply_state( diff --git a/stubs/quick/synthesis/unitarypreparation/unitarypreparation.pyi b/stubs/quick/synthesis/unitarypreparation/unitarypreparation.pyi index 732d893..7ad2c86 100644 --- a/stubs/quick/synthesis/unitarypreparation/unitarypreparation.pyi +++ b/stubs/quick/synthesis/unitarypreparation/unitarypreparation.pyi @@ -19,12 +19,11 @@ from collections.abc import Sequence from numpy.typing import NDArray from quick.circuit import Circuit from quick.primitives import Operator -from typing import Type __all__ = ["UnitaryPreparation"] class UnitaryPreparation(ABC, metaclass=abc.ABCMeta): - output_framework: Type[Circuit] + output_framework: type[Circuit] def __init__(self, output_framework: type[Circuit]) -> None: ... def prepare_unitary(self, unitary: NDArray[np.complex128] | Operator) -> Circuit: ... @abstractmethod diff --git a/tests/circuit/test_ansatz.py b/tests/circuit/test_ansatz.py index 0a4b46b..db960b9 100644 --- a/tests/circuit/test_ansatz.py +++ b/tests/circuit/test_ansatz.py @@ -33,14 +33,14 @@ def test_init_type_error(self) -> None: @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_init_value_error( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test ValueError raised when the circuit used is not parameterized. Parameters ---------- - `circuit_framework`: Type[quick.circuit.Circuit] + `circuit_framework`: type[quick.circuit.Circuit] The circuit framework to use for testing. """ circuit = circuit_framework(2) @@ -53,13 +53,13 @@ def test_init_value_error( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_thetas( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test thetas property of Ansatz. Parameters ---------- - `circuit_framework`: Type[quick.circuit.Circuit] + `circuit_framework`: type[quick.circuit.Circuit] The circuit framework to use for testing. """ circuit = circuit_framework(1) @@ -81,13 +81,13 @@ def test_thetas( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_set_thetas( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test thetas setter of Ansatz. Parameters ---------- - `circuit_framework`: Type[quick.circuit.Circuit] + `circuit_framework`: type[quick.circuit.Circuit] The circuit framework to use for testing. """ circuit = circuit_framework(1) @@ -130,13 +130,13 @@ def test_set_thetas( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_num_params( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test num_params property of Ansatz. Parameters ---------- - `circuit_framework`: Type[quick.circuit.Circuit] + `circuit_framework`: type[quick.circuit.Circuit] The circuit framework to use for testing. """ circuit = circuit_framework(1) @@ -158,13 +158,13 @@ def test_num_params( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_num_parameterized_gates( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test num_parameterized_gates property of Ansatz. Parameters ---------- - `circuit_framework`: Type[quick.circuit.Circuit] + `circuit_framework`: type[quick.circuit.Circuit] The circuit framework to use for testing. """ circuit = circuit_framework(1) @@ -186,13 +186,13 @@ def test_num_parameterized_gates( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_is_parameterized( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test is_parameterized property of Ansatz. Parameters ---------- - `circuit_framework`: Type[quick.circuit.Circuit] + `circuit_framework`: type[quick.circuit.Circuit] The circuit framework to use for testing. """ circuit = circuit_framework(1) @@ -214,14 +214,14 @@ def test_is_parameterized( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_is_parameterized_error( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test is_parameterized property of Ansatz when circuit is not parameterized. Parameters ---------- - `circuit_framework`: Type[quick.circuit.Circuit] + `circuit_framework`: type[quick.circuit.Circuit] The circuit framework to use for testing. """ circuit = circuit_framework(2) @@ -234,14 +234,14 @@ def test_is_parameterized_error( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_is_parameterized_with_ignore_global_phase( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test is_parameterized property of Ansatz with ignore_global_phase set to True. Parameters ---------- - `circuit_framework`: Type[quick.circuit.Circuit] + `circuit_framework`: type[quick.circuit.Circuit] The circuit framework to use for testing. """ circuit = circuit_framework(1) diff --git a/tests/circuit/test_circuit_base.py b/tests/circuit/test_circuit_base.py index 3ee9882..c5d64a6 100644 --- a/tests/circuit/test_circuit_base.py +++ b/tests/circuit/test_circuit_base.py @@ -34,7 +34,7 @@ class TestCircuitBase: @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_init( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the initialization of the circuit. @@ -48,7 +48,7 @@ def test_init( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_num_qubits_value_error( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test to see if the error is raised when the number of qubits is less than or equal to 0. @@ -63,7 +63,7 @@ def test_num_qubits_value_error( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_num_qubits_type_error( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test to see if the error is raised when the number of qubits is not an integer. @@ -75,7 +75,7 @@ def test_num_qubits_type_error( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_single_qubit_gate_from_range( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the single qubit gate when indices are passed as a range instance. """ @@ -97,7 +97,7 @@ def test_single_qubit_gate_from_range( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_single_qubit_gate_from_tuple( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the single qubit gate when indices are passed as a tuple instance. """ @@ -119,7 +119,7 @@ def test_single_qubit_gate_from_tuple( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_single_qubit_gate_from_ndarray( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the single qubit gate when indices are passed as a numpy.ndarray instance. """ @@ -141,7 +141,7 @@ def test_single_qubit_gate_from_ndarray( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_qubit_type_error( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the qubit type error. @@ -163,7 +163,7 @@ def test_qubit_type_error( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_duplicate_qubits( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the duplicate qubit error. @@ -186,7 +186,7 @@ def test_duplicate_qubits( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_qubit_out_of_range( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the qubit out of range error. @@ -208,7 +208,7 @@ def test_qubit_out_of_range( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_control_out_of_range( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the control qubit out of range error. @@ -227,7 +227,7 @@ def test_control_out_of_range( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_target_out_of_range( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the target qubit out of range error. @@ -246,7 +246,7 @@ def test_target_out_of_range( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_angle_type_error( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the angle type error. @@ -271,7 +271,7 @@ def test_angle_type_error( ]) def test_initialize( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], num_qubits: int ) -> None: """ Test the state initialization. @@ -301,7 +301,7 @@ def test_initialize( ]) def test_unitary( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], num_qubits: int ) -> None: """ Test the unitary preparation gate. @@ -328,7 +328,7 @@ def test_unitary( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_get_global_phase( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the global phase extraction. @@ -350,7 +350,7 @@ def test_get_global_phase( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_merge_global_phases( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the global phase merging. @@ -376,7 +376,7 @@ def test_merge_global_phases( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_vertical_reverse( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the vertical reversal of the circuit. @@ -407,7 +407,7 @@ def test_vertical_reverse( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_horizontal_reverse( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the horizontal reversal of the circuit. @@ -510,7 +510,7 @@ def test_horizontal_reverse( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_horizontal_reverse_definition( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the horizontal reversal of the circuit definition. @@ -550,7 +550,7 @@ def custom_gate(self, qubit_indices: int | list[int]) -> None: @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_add( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the addition of circuits. @@ -584,7 +584,7 @@ def test_add( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_add_fail( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the addition of circuits failure. @@ -613,7 +613,7 @@ def test_add_fail( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_transpile( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the transpilation of the circuit. @@ -639,7 +639,7 @@ def test_transpile( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_get_depth( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the depth of the circuit. @@ -662,7 +662,7 @@ def test_get_depth( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_get_width( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the width of the circuit. @@ -682,7 +682,7 @@ def test_get_width( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_get_instructions( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the instructions of the circuit. @@ -709,7 +709,7 @@ def test_get_instructions( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_get_instructions_with_measurements( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the instructions of the circuit with measurements. @@ -747,7 +747,7 @@ def test_get_instructions_with_measurements( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_compress( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the compression of the circuit. @@ -778,7 +778,7 @@ def test_compress( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_compress_fail( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the compression of the circuit failure. @@ -801,7 +801,7 @@ def test_compress_fail( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_change_mapping( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the mapping change of the circuit. @@ -830,7 +830,7 @@ def test_change_mapping( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_change_mapping_indices_type_error( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the mapping change of the circuit failure. @@ -849,7 +849,7 @@ def test_change_mapping_indices_type_error( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_change_mapping_indices_value_error( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the mapping change of the circuit failure. @@ -868,7 +868,7 @@ def test_change_mapping_indices_value_error( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_from_circuit( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `circuit.convert()` method. @@ -1011,7 +1011,7 @@ def test_from_circuit( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_convert_type_error( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `circuit.convert()` method failure. @@ -1029,7 +1029,7 @@ def test_convert_type_error( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_reset( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the reset of the circuit. @@ -1056,7 +1056,7 @@ def test_reset( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_remove_measurement( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the removal of measurement gate. @@ -1097,7 +1097,7 @@ def test_remove_measurement( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_getitem( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `__getitem__` dunder method. @@ -1150,7 +1150,7 @@ def test_getitem( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_setitem( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `__setitem__` dunder method. @@ -1205,7 +1205,7 @@ def test_setitem( @pytest.mark.parametrize("circuit_frameworks", [CIRCUIT_FRAMEWORKS]) def test_eq( self, - circuit_frameworks: list[Type[Circuit]] + circuit_frameworks: list[type[Circuit]] ) -> None: """ Test the `__eq__` dunder method. @@ -1228,7 +1228,7 @@ def test_eq( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_len( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `__len__` dunder method. @@ -1250,7 +1250,7 @@ def test_len( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_str( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `__str__` dunder method. @@ -1272,7 +1272,7 @@ def test_str( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_generate_calls( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `generate_calls` method. @@ -1294,7 +1294,7 @@ def test_generate_calls( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_repr( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `__repr__` dunder method. @@ -1321,7 +1321,7 @@ def test_repr( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_custom_gate( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the custom gate functionality. diff --git a/tests/circuit/test_circuit_decompose.py b/tests/circuit/test_circuit_decompose.py index 0f1deb4..9ac4a88 100644 --- a/tests/circuit/test_circuit_decompose.py +++ b/tests/circuit/test_circuit_decompose.py @@ -40,7 +40,7 @@ class TestDecompose: @pytest.mark.parametrize("framework", CIRCUIT_FRAMEWORKS) def test_X( self, - framework: Type[Circuit] + framework: type[Circuit] ) -> None: """ Test the decomposition of the X gate. @@ -70,7 +70,7 @@ def test_X( @pytest.mark.parametrize("framework", CIRCUIT_FRAMEWORKS) def test_Z( self, - framework: Type[Circuit] + framework: type[Circuit] ) -> None: """ Test the decomposition of the Z gate. @@ -119,7 +119,7 @@ def test_Z( @pytest.mark.parametrize("framework", CIRCUIT_FRAMEWORKS) def test_CZ( self, - framework: Type[Circuit] + framework: type[Circuit] ) -> None: """ Test the decomposition of the CZ gate. @@ -173,7 +173,7 @@ def test_CZ( @pytest.mark.parametrize("framework", CIRCUIT_FRAMEWORKS) def test_MCX( self, - framework: Type[Circuit] + framework: type[Circuit] ) -> None: """ Test the decomposition of the MCX gate. @@ -275,7 +275,7 @@ def test_MCX( @pytest.mark.parametrize("gate_func, num_qubits", GATE_TYPES) def test_primitive_gates( self, - framework: Type[Circuit], + framework: type[Circuit], gate_func, num_qubits: int ) -> None: diff --git a/tests/circuit/test_circuit_to_controlled.py b/tests/circuit/test_circuit_to_controlled.py index 696766d..f2c9a96 100644 --- a/tests/circuit/test_circuit_to_controlled.py +++ b/tests/circuit/test_circuit_to_controlled.py @@ -30,7 +30,7 @@ class TestControlled: @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_x_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with X gate. @@ -65,7 +65,7 @@ def test_x_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_y_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with Y gate. @@ -100,7 +100,7 @@ def test_y_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_z_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with Z gate. @@ -135,7 +135,7 @@ def test_z_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_h_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with H gate. @@ -170,7 +170,7 @@ def test_h_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_s_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with S gate. @@ -205,7 +205,7 @@ def test_s_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_sdg_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with Sdg gate. @@ -240,7 +240,7 @@ def test_sdg_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_t_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with T gate. @@ -275,7 +275,7 @@ def test_t_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_tdg_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with Tdg gate. @@ -310,7 +310,7 @@ def test_tdg_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_rx_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with RX gate. @@ -345,7 +345,7 @@ def test_rx_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_ry_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with RY gate. @@ -380,7 +380,7 @@ def test_ry_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_rz_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with RZ gate. @@ -420,7 +420,7 @@ def test_rz_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_phase_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with Phase gate. @@ -455,7 +455,7 @@ def test_phase_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_u3_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with U3 gate. @@ -487,7 +487,7 @@ def test_u3_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_swap_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with SWAP gate. @@ -519,7 +519,7 @@ def test_swap_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_cx_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with CX gate. @@ -551,7 +551,7 @@ def test_cx_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_cy_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with CY gate. @@ -583,7 +583,7 @@ def test_cy_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_cz_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with CZ gate. @@ -615,7 +615,7 @@ def test_cz_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_ch_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with CH gate. @@ -647,7 +647,7 @@ def test_ch_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_cs_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with CS gate. @@ -679,7 +679,7 @@ def test_cs_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_csdg_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with CSdg gate. @@ -711,7 +711,7 @@ def test_csdg_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_ct_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with CT gate. @@ -743,7 +743,7 @@ def test_ct_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_ctdg_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with CTdg gate. @@ -775,7 +775,7 @@ def test_ctdg_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_crx_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with CRX gate. @@ -807,7 +807,7 @@ def test_crx_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_cry_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with CRY gate. @@ -839,7 +839,7 @@ def test_cry_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_crz_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with CRZ gate. @@ -871,7 +871,7 @@ def test_crz_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_cphase_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with CPhase gate. @@ -903,7 +903,7 @@ def test_cphase_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_cswap_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with CSWAP gate. @@ -935,7 +935,7 @@ def test_cswap_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_cu3_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with CU3 gate. @@ -967,7 +967,7 @@ def test_cu3_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_mcx_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with MCX gate. @@ -999,7 +999,7 @@ def test_mcx_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_mcy_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with MCY gate. @@ -1031,7 +1031,7 @@ def test_mcy_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_mcz_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with MCZ gate. @@ -1063,7 +1063,7 @@ def test_mcz_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_mch_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with MCH gate. @@ -1095,7 +1095,7 @@ def test_mch_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_mcs_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with MCS gate. @@ -1127,7 +1127,7 @@ def test_mcs_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_mcsdg_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with MCSdg gate. @@ -1159,7 +1159,7 @@ def test_mcsdg_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_mct_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with MCT gate. @@ -1191,7 +1191,7 @@ def test_mct_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_mctdg_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with MCTdg gate. @@ -1223,7 +1223,7 @@ def test_mctdg_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_mcrx_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with MCRX gate. @@ -1255,7 +1255,7 @@ def test_mcrx_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_mcry_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with MCRY gate. @@ -1287,7 +1287,7 @@ def test_mcry_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_mcrz_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with MCRZ gate. @@ -1319,7 +1319,7 @@ def test_mcrz_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_mcphase_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with MCPhase gate. @@ -1351,7 +1351,7 @@ def test_mcphase_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_mcu3_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with MCU3 gate. @@ -1383,7 +1383,7 @@ def test_mcu3_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_mcswap_control( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with MCSWAP gate. @@ -1415,7 +1415,7 @@ def test_mcswap_control( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_global_phase_in_target( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `.control()` method with global phase in target gate. diff --git a/tests/circuit/test_control_state.py b/tests/circuit/test_control_state.py index 8e2d5e2..2e121e3 100644 --- a/tests/circuit/test_control_state.py +++ b/tests/circuit/test_control_state.py @@ -33,14 +33,14 @@ class TestControlState: @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_CX_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `CX` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -59,14 +59,14 @@ def test_CX_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_CY_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `CY` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -85,14 +85,14 @@ def test_CY_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_CZ_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `CZ` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -111,14 +111,14 @@ def test_CZ_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_CH_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `CH` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -137,14 +137,14 @@ def test_CH_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_CS_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `CS` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -163,14 +163,14 @@ def test_CS_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_CSdg_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `CSdg` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -189,14 +189,14 @@ def test_CSdg_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_CT_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `CT` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -215,14 +215,14 @@ def test_CT_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_CTdg_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `CTdg` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -241,14 +241,14 @@ def test_CTdg_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_CRX_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `CRX` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -269,14 +269,14 @@ def test_CRX_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_CRY_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `CRY` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -295,14 +295,14 @@ def test_CRY_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_CRZ_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `CRZ` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -321,14 +321,14 @@ def test_CRZ_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_CPhase_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `CPhase` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -347,14 +347,14 @@ def test_CPhase_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_CXPow_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `CXPow` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -373,14 +373,14 @@ def test_CXPow_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_CYPow_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `CYPow` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -399,14 +399,14 @@ def test_CYPow_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_CZPow_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `CZPow` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -425,14 +425,14 @@ def test_CZPow_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_CRXX_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `CRXX` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -451,14 +451,14 @@ def test_CRXX_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_CRYY_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `CRYY` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -477,14 +477,14 @@ def test_CRYY_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_CRZZ_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `CRZZ` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -503,14 +503,14 @@ def test_CRZZ_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_CU3_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `CU3` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ theta = 0.1 @@ -533,14 +533,14 @@ def test_CU3_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_CSWAP_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `CSWAP` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -559,14 +559,14 @@ def test_CSWAP_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_MCX_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `MCX` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -597,14 +597,14 @@ def test_MCX_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_MCY_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `MCY` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -635,14 +635,14 @@ def test_MCY_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_MCZ_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `MCZ` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -673,14 +673,14 @@ def test_MCZ_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_MCH_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `MCH` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -711,14 +711,14 @@ def test_MCH_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_MCS_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `MCS` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -749,14 +749,14 @@ def test_MCS_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_MCSdg_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `MCSdg` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -787,14 +787,14 @@ def test_MCSdg_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_MCT_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `MCT` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -825,14 +825,14 @@ def test_MCT_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_MCTdg_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `MCTdg` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -863,14 +863,14 @@ def test_MCTdg_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_MCRX_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `MCRX` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ theta = 0.1 @@ -903,14 +903,14 @@ def test_MCRX_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_MCRY_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `MCRY` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ theta = 0.1 @@ -943,14 +943,14 @@ def test_MCRY_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_MCRZ_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `MCRZ` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ theta = 0.1 @@ -983,14 +983,14 @@ def test_MCRZ_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_MCPhase_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `MCPhase` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ theta = 0.1 @@ -1023,14 +1023,14 @@ def test_MCPhase_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_MCXPow_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `MCXPow` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ theta = 0.1 @@ -1064,14 +1064,14 @@ def test_MCXPow_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_MCYPow_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `MCYPow` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ theta = 0.1 @@ -1105,14 +1105,14 @@ def test_MCYPow_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_MCZPow_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `MCZPow` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ theta = 0.1 @@ -1146,14 +1146,14 @@ def test_MCZPow_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_MCRXX_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `MCRXX` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ theta = 0.1 @@ -1186,14 +1186,14 @@ def test_MCRXX_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_MCRYY_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `MCRYY` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ theta = 0.1 @@ -1226,14 +1226,14 @@ def test_MCRYY_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_MCRZZ_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `MCRZZ` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ theta = 0.1 @@ -1266,14 +1266,14 @@ def test_MCRZZ_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_MCU3_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `MCU3` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ theta = 0.1 @@ -1308,14 +1308,14 @@ def test_MCU3_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_MCSWAP_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `MCSWAP` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ # Given control state "0" @@ -1346,14 +1346,14 @@ def test_MCSWAP_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_UCRX_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `UCRX` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ theta = [0.1, 0.2, 0.3, 0.4] @@ -1386,14 +1386,14 @@ def test_UCRX_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_UCRY_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `UCRY` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ theta = [0.1, 0.2, 0.3, 0.4] @@ -1426,14 +1426,14 @@ def test_UCRY_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_UCRZ_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `UCRZ` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ theta = [0.1, 0.2, 0.3, 0.4] @@ -1466,14 +1466,14 @@ def test_UCRZ_control_state( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_Multiplexor_control_state( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], ) -> None: """ Test the `control_state` parameter of the `Multiplexor` method of the circuit framework. Parameters ---------- - `circuit_framework` : Type[quick.circuit.Circuit] + `circuit_framework` : type[quick.circuit.Circuit] The circuit framework to test. """ gates = [ diff --git a/tests/circuit/test_qft_circuit.py b/tests/circuit/test_qft_circuit.py index c3a77f8..5cff27b 100644 --- a/tests/circuit/test_qft_circuit.py +++ b/tests/circuit/test_qft_circuit.py @@ -119,7 +119,7 @@ class TestQFTCircuit: ]) def test_qft_no_swap_no_inverse( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], num_qubits: int, approximation_degree: int, expected_unitary: NDArray[np.complex128] @@ -128,7 +128,7 @@ def test_qft_no_swap_no_inverse( Parameters ---------- - `circuit_framework`: Type[quick.circuit.Circuit] + `circuit_framework`: type[quick.circuit.Circuit] The quantum circuit framework to use. `num_qubits`: int The number of qubits in the quantum circuit. @@ -167,7 +167,7 @@ def test_qft_no_swap_no_inverse( ]) def test_qft_swap_no_inverse( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], num_qubits: int, approximation_degree: int, expected_unitary: NDArray[np.complex128] @@ -176,7 +176,7 @@ def test_qft_swap_no_inverse( Parameters ---------- - `circuit_framework`: Type[quick.circuit.Circuit] + `circuit_framework`: type[quick.circuit.Circuit] The quantum circuit framework to use. `num_qubits`: int The number of qubits in the quantum circuit. @@ -215,7 +215,7 @@ def test_qft_swap_no_inverse( ]) def test_qft_no_swap_inverse( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], num_qubits: int, approximation_degree: int, expected_unitary: NDArray[np.complex128] @@ -224,7 +224,7 @@ def test_qft_no_swap_inverse( Parameters ---------- - `circuit_framework`: Type[quick.circuit.Circuit] + `circuit_framework`: type[quick.circuit.Circuit] The quantum circuit framework to use. `num_qubits`: int The number of qubits in the quantum circuit. @@ -263,7 +263,7 @@ def test_qft_no_swap_inverse( ]) def test_qft_swap_inverse( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], num_qubits: int, approximation_degree: int, expected_unitary: NDArray[np.complex128] @@ -272,7 +272,7 @@ def test_qft_swap_inverse( Parameters ---------- - `circuit_framework`: Type[quick.circuit.Circuit] + `circuit_framework`: type[quick.circuit.Circuit] The quantum circuit framework to use. `num_qubits`: int The number of qubits in the quantum circuit. @@ -293,13 +293,13 @@ def test_qft_swap_inverse( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_approximation_degree_value_error( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the error when the approximation degree is not in the range [0, 3]. Parameters ---------- - `circuit_framework`: Type[quick.circuit.Circuit] + `circuit_framework`: type[quick.circuit.Circuit] The quantum circuit framework to use. """ qft_circuit = circuit_framework(5) @@ -310,13 +310,13 @@ def test_approximation_degree_value_error( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_approximation_degree_type_error( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the error when the approximation degree is not an integer. Parameters ---------- - `circuit_framework`: Type[quick.circuit.Circuit] + `circuit_framework`: type[quick.circuit.Circuit] The quantum circuit framework to use. """ qft_circuit = circuit_framework(5) diff --git a/tests/circuit/test_uniformly_controlled_gates.py b/tests/circuit/test_uniformly_controlled_gates.py index e940949..1ab7f08 100644 --- a/tests/circuit/test_uniformly_controlled_gates.py +++ b/tests/circuit/test_uniformly_controlled_gates.py @@ -98,7 +98,7 @@ class TestUniformlyControlledGates: ]) def test_UCRX( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], angles: list[float], control_indices: list[int], target_index: int, @@ -157,7 +157,7 @@ def test_UCRX( ]) def test_UCRY( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], angles: list[float], control_indices: list[int], target_index: int, @@ -216,7 +216,7 @@ def test_UCRY( ]) def test_UCRZ( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], angles: list[float], control_indices: list[int], target_index: int, @@ -259,7 +259,7 @@ def test_UCRZ( ]) def test_Diagonal( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], diagonal: list[int], qubit_indices: list[int] ) -> None: @@ -330,7 +330,7 @@ def test_Diagonal( ]) def test_Multiplexor_no_diagonal_no_simplification( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], single_qubit_gates: list[NDArray[np.complex128]], control_indices: list[int], target_index: int, @@ -413,7 +413,7 @@ def test_Multiplexor_no_diagonal_no_simplification( ]) def test_Multiplexor_diagonal_no_simplification( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], single_qubit_gates: list[NDArray[np.complex128]], control_indices: list[int], target_index: int, @@ -496,7 +496,7 @@ def test_Multiplexor_diagonal_no_simplification( ]) def test_Multiplexor_no_diagonal_simplification( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], single_qubit_gates: list[NDArray[np.complex128]], control_indices: list[int], target_index: int, @@ -579,7 +579,7 @@ def test_Multiplexor_no_diagonal_simplification( ]) def test_Multiplexor_diagonal_simplification( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], single_qubit_gates: list[NDArray[np.complex128]], control_indices: list[int], target_index: int, diff --git a/tests/synthesis/unitarypreparation/test_diffusion.py b/tests/synthesis/unitarypreparation/test_diffusion.py index 2465bca..b1d698a 100644 --- a/tests/synthesis/unitarypreparation/test_diffusion.py +++ b/tests/synthesis/unitarypreparation/test_diffusion.py @@ -29,7 +29,7 @@ # Define the test data def generate_random_circuit( max_depth: int, - qc_framework: Type[Circuit] + qc_framework: type[Circuit] ) -> Circuit: """ Generate a random circuit using the allowed gate set with a given maximum depth. From a8532231b1e12e0958348d0148819cc5efe1b1a8 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sat, 26 Apr 2025 15:04:09 +0800 Subject: [PATCH 07/58] - Added `quick.circuit.from_framework.FromPennylane` to convert circuits from Pennylane to quick. - Added `quick.metrics.calculate_hilbert_schmidt_test` to compare fidelity between unitaries. - Added `quick.predicates.is_statevector` to check if a ndarray is a statevector, and if needed, if it is a qubit compliant statevector. - Modified circuit creation for frameworks that require vertical reverse such that gates are added with vertical reverse effect from the get-go for better performance. - Added testers for `quick.metrics.calculate_hilbert_schmidt_test`. - Added testers for `quick.predicates.is_statevector`. - Added additional test case to `tests.predicates.test_predicates` for `test_is_isometry()`. - Removed stubs, given the code is already inline annotated in implementation. This is to comply with PEP 484. --- quick/circuit/circuit.py | 27 +- quick/circuit/cirqcircuit.py | 80 ++-- quick/circuit/from_framework/__init__.py | 2 + quick/circuit/from_framework/from_cirq.py | 4 +- .../circuit/from_framework/from_framework.py | 4 +- .../circuit/from_framework/from_pennylane.py | 237 ++++++++++++ quick/circuit/from_framework/from_qiskit.py | 9 +- quick/circuit/from_framework/from_tket.py | 4 +- quick/circuit/pennylanecircuit.py | 125 +++---- quick/circuit/qiskitcircuit.py | 7 +- quick/circuit/quimbcircuit.py | 93 +++-- quick/circuit/tketcircuit.py | 84 ++--- quick/metrics/__init__.py | 6 +- quick/metrics/metrics.py | 52 ++- quick/predicates/__init__.py | 2 + quick/predicates/predicates.py | 63 ++++ stubs/quick/__init__.pyi | 35 -- stubs/quick/backend/__init__.pyi | 19 - stubs/quick/backend/backend.pyi | 49 --- .../backend/qiskit_backends/aer_backend.pyi | 26 -- .../qiskit_backends/fake_ibm_backend.pyi | 27 -- stubs/quick/circuit/__init__.pyi | 31 -- stubs/quick/circuit/ansatz.pyi | 36 -- stubs/quick/circuit/circuit.pyi | 352 ------------------ stubs/quick/circuit/circuit_utils.pyi | 64 ---- stubs/quick/circuit/cirqcircuit.pyi | 53 --- stubs/quick/circuit/dag/__init__.pyi | 18 - stubs/quick/circuit/dag/dagcircuit.pyi | 24 -- stubs/quick/circuit/dag/dagnode.pyi | 34 -- .../quick/circuit/from_framework/__init__.pyi | 20 - .../circuit/from_framework/from_cirq.pyi | 26 -- .../circuit/from_framework/from_framework.pyi | 26 -- .../circuit/from_framework/from_qiskit.pyi | 27 -- .../circuit/from_framework/from_tket.pyi | 26 -- stubs/quick/circuit/gate_matrix/__init__.pyi | 52 --- .../gate_matrix/controlled_qubit_gates.pyi | 24 -- stubs/quick/circuit/gate_matrix/gate.pyi | 29 -- .../gate_matrix/single_qubit_gates.pyi | 62 --- stubs/quick/circuit/pennylanecircuit.pyi | 52 --- stubs/quick/circuit/qiskitcircuit.pyi | 52 --- stubs/quick/circuit/quimbcircuit.pyi | 52 --- stubs/quick/circuit/tketcircuit.pyi | 52 --- stubs/quick/compiler/__init__.pyi | 17 - stubs/quick/compiler/compiler.pyi | 47 --- stubs/quick/metrics/__init__.pyi | 25 -- stubs/quick/metrics/metrics.pyi | 26 -- stubs/quick/optimizer/__init__.pyi | 18 - stubs/quick/optimizer/optimizer.pyi | 23 -- stubs/quick/optimizer/tket2optimizer.pyi | 21 -- stubs/quick/predicates/__init__.pyi | 35 -- stubs/quick/predicates/predicates.pyi | 36 -- stubs/quick/primitives/__init__.pyi | 19 - stubs/quick/primitives/bra.pyi | 57 --- stubs/quick/primitives/ket.pyi | 55 --- stubs/quick/primitives/operator.pyi | 38 -- stubs/quick/random/__init__.pyi | 17 - stubs/quick/random/random.pyi | 21 -- stubs/quick/synthesis/__init__.pyi | 19 - .../gate_decompositions/__init__.pyi | 21 -- .../__init__.pyi | 17 - .../mcsu2_real_diagonal.pyi | 21 -- .../mcx_utils.pyi | 24 -- .../mcx_vchain.pyi | 23 -- .../one_qubit_decomposition.pyi | 33 -- .../two_qubit_decomposition.pyi | 77 ---- .../two_qubit_decomposition/weyl.pyi | 47 --- .../synthesis/statepreparation/__init__.pyi | 19 - .../synthesis/statepreparation/isometry.pyi | 33 -- .../synthesis/statepreparation/mottonen.pyi | 33 -- .../synthesis/statepreparation/shende.pyi | 33 -- .../statepreparation/statepreparation.pyi | 38 -- .../statepreparation_utils.pyi | 91 ----- .../synthesis/unitarypreparation/__init__.pyi | 19 - .../unitarypreparation/diffusion.pyi | 38 -- .../qiskit_unitary_transpiler.pyi | 37 -- .../shannon_decomposition.pyi | 25 -- .../unitarypreparation/unitarypreparation.pyi | 30 -- .../test_circuit_from_pennylane.py | 178 +++++++++ tests/metrics/test_metrics.py | 24 +- tests/predicates/test_predicates.py | 42 ++- 80 files changed, 813 insertions(+), 2631 deletions(-) create mode 100644 quick/circuit/from_framework/from_pennylane.py delete mode 100644 stubs/quick/__init__.pyi delete mode 100644 stubs/quick/backend/__init__.pyi delete mode 100644 stubs/quick/backend/backend.pyi delete mode 100644 stubs/quick/backend/qiskit_backends/aer_backend.pyi delete mode 100644 stubs/quick/backend/qiskit_backends/fake_ibm_backend.pyi delete mode 100644 stubs/quick/circuit/__init__.pyi delete mode 100644 stubs/quick/circuit/ansatz.pyi delete mode 100644 stubs/quick/circuit/circuit.pyi delete mode 100644 stubs/quick/circuit/circuit_utils.pyi delete mode 100644 stubs/quick/circuit/cirqcircuit.pyi delete mode 100644 stubs/quick/circuit/dag/__init__.pyi delete mode 100644 stubs/quick/circuit/dag/dagcircuit.pyi delete mode 100644 stubs/quick/circuit/dag/dagnode.pyi delete mode 100644 stubs/quick/circuit/from_framework/__init__.pyi delete mode 100644 stubs/quick/circuit/from_framework/from_cirq.pyi delete mode 100644 stubs/quick/circuit/from_framework/from_framework.pyi delete mode 100644 stubs/quick/circuit/from_framework/from_qiskit.pyi delete mode 100644 stubs/quick/circuit/from_framework/from_tket.pyi delete mode 100644 stubs/quick/circuit/gate_matrix/__init__.pyi delete mode 100644 stubs/quick/circuit/gate_matrix/controlled_qubit_gates.pyi delete mode 100644 stubs/quick/circuit/gate_matrix/gate.pyi delete mode 100644 stubs/quick/circuit/gate_matrix/single_qubit_gates.pyi delete mode 100644 stubs/quick/circuit/pennylanecircuit.pyi delete mode 100644 stubs/quick/circuit/qiskitcircuit.pyi delete mode 100644 stubs/quick/circuit/quimbcircuit.pyi delete mode 100644 stubs/quick/circuit/tketcircuit.pyi delete mode 100644 stubs/quick/compiler/__init__.pyi delete mode 100644 stubs/quick/compiler/compiler.pyi delete mode 100644 stubs/quick/metrics/__init__.pyi delete mode 100644 stubs/quick/metrics/metrics.pyi delete mode 100644 stubs/quick/optimizer/__init__.pyi delete mode 100644 stubs/quick/optimizer/optimizer.pyi delete mode 100644 stubs/quick/optimizer/tket2optimizer.pyi delete mode 100644 stubs/quick/predicates/__init__.pyi delete mode 100644 stubs/quick/predicates/predicates.pyi delete mode 100644 stubs/quick/primitives/__init__.pyi delete mode 100644 stubs/quick/primitives/bra.pyi delete mode 100644 stubs/quick/primitives/ket.pyi delete mode 100644 stubs/quick/primitives/operator.pyi delete mode 100644 stubs/quick/random/__init__.pyi delete mode 100644 stubs/quick/random/random.pyi delete mode 100644 stubs/quick/synthesis/__init__.pyi delete mode 100644 stubs/quick/synthesis/gate_decompositions/__init__.pyi delete mode 100644 stubs/quick/synthesis/gate_decompositions/multi_controlled_decomposition/__init__.pyi delete mode 100644 stubs/quick/synthesis/gate_decompositions/multi_controlled_decomposition/mcsu2_real_diagonal.pyi delete mode 100644 stubs/quick/synthesis/gate_decompositions/multi_controlled_decomposition/mcx_utils.pyi delete mode 100644 stubs/quick/synthesis/gate_decompositions/multi_controlled_decomposition/mcx_vchain.pyi delete mode 100644 stubs/quick/synthesis/gate_decompositions/one_qubit_decomposition.pyi delete mode 100644 stubs/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.pyi delete mode 100644 stubs/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.pyi delete mode 100644 stubs/quick/synthesis/statepreparation/__init__.pyi delete mode 100644 stubs/quick/synthesis/statepreparation/isometry.pyi delete mode 100644 stubs/quick/synthesis/statepreparation/mottonen.pyi delete mode 100644 stubs/quick/synthesis/statepreparation/shende.pyi delete mode 100644 stubs/quick/synthesis/statepreparation/statepreparation.pyi delete mode 100644 stubs/quick/synthesis/statepreparation/statepreparation_utils.pyi delete mode 100644 stubs/quick/synthesis/unitarypreparation/__init__.pyi delete mode 100644 stubs/quick/synthesis/unitarypreparation/diffusion.pyi delete mode 100644 stubs/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.pyi delete mode 100644 stubs/quick/synthesis/unitarypreparation/shannon_decomposition.pyi delete mode 100644 stubs/quick/synthesis/unitarypreparation/unitarypreparation.pyi create mode 100644 tests/circuit/from_framework/test_circuit_from_pennylane.py diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index 17cd648..a94be3f 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -36,7 +36,6 @@ import cirq # type: ignore import pennylane as qml # type: ignore import pytket -import pytket.circuit import quimb.tensor as qtn # type: ignore if TYPE_CHECKING: @@ -45,7 +44,7 @@ multiplexed_rz_angles, decompose_multiplexor_rotations, extract_single_qubits_and_diagonal, simplify ) from quick.circuit.dag import DAGCircuit -from quick.circuit.from_framework import FromCirq, FromQiskit, FromTKET +from quick.circuit.from_framework import FromCirq, FromPennyLane, FromQiskit, FromTKET from quick.predicates import is_unitary_matrix from quick.primitives import Bra, Ket, Operator from quick.synthesis.gate_decompositions.multi_controlled_decomposition import MCRX, MCRY, MCRZ @@ -86,11 +85,17 @@ # List of 1Q gates wrapped by individual frameworks GATES = Literal["I", "X", "Y", "Z", "H", "S", "Sdg", "T", "Tdg", "RX", "RY", "RZ", "Phase", "U3"] -SELF_ADJ_GATES = [ + +# List of self-adjoint gates +SELF_ADJ_GATES = frozenset([ "I", "X", "Y", "Z", "H", "SWAP", "CX", "CY", "CZ", "CH", "CSWAP", "MCX", "MCY", "MCZ", "MCH", "MCSWAP" -] +]) + +# List of gates that are considered as primitives +# these gates cannot be decomposed further +PRIMITIVE_GATES = frozenset(["U3", "CX", "GlobalPhase", "measure"]) # Constants PI = np.pi @@ -5071,7 +5076,7 @@ def vertical_reverse(self) -> None: ----- >>> circuit.vertical_reverse() """ - self.change_mapping(list(range(self.num_qubits))[::-1]) + self.change_mapping(range(self.num_qubits - 1, -1, -1)) @staticmethod def _horizontal_reverse( @@ -5637,7 +5642,7 @@ def decompose( while True: gates = set([operation["gate"] for operation in circuit_log_copy]) - if gates.issubset(set(["U3", "CX", "GlobalPhase", "measure"])): + if gates.issubset(PRIMITIVE_GATES): break for operation in circuit_log_copy: @@ -6024,14 +6029,8 @@ def from_pennylane( ----- >>> circuit.from_pennylane(pennylane_circuit) """ - if not issubclass(output_framework, Circuit): - raise TypeError("The circuit framework must be a subclass of `quick.circuit.Circuit`.") - - # Define a circuit - num_qubits = len(pennylane_circuit.device.wires) - circuit = output_framework(num_qubits=num_qubits) - - # TODO: Implement the conversion from PennyLane to quick + pennylane_converter = FromPennyLane(output_framework=output_framework) + circuit = pennylane_converter.convert(pennylane_circuit) return circuit @staticmethod diff --git a/quick/circuit/cirqcircuit.py b/quick/circuit/cirqcircuit.py index dfcc464..b140a48 100644 --- a/quick/circuit/cirqcircuit.py +++ b/quick/circuit/cirqcircuit.py @@ -163,29 +163,38 @@ def _gate_mapping( angles: Sequence[float] = (0, 0, 0) ) -> None: - target_indices = [target_indices] if isinstance(target_indices, int) else target_indices - control_indices = [control_indices] if isinstance(control_indices, int) else control_indices + targets = [target_indices] if isinstance(target_indices, int) else list(target_indices) + controls = [control_indices] if isinstance(control_indices, int) else list(control_indices) + + # Given Cirq uses MSB convention, we will explicitly + # convert the qubit indices to LSB convention + # This will help performance by avoiding `circuit.vertical_reverse()` calls + for i, index in enumerate(targets): + targets[i] = self.num_qubits - 1 - index + + for i, index in enumerate(controls): + controls[i] = self.num_qubits - 1 - index # Lazily extract the value of the gate from the mapping to avoid # creating all the gates at once, and to maintain the polymorphism gate_operation = self.gate_mapping[gate](angles) - if control_indices: + if controls: gate_operation = cirq.ControlledGate( sub_gate=gate_operation, - num_controls=len(control_indices) + num_controls=len(controls) ) - for target_index in target_indices: + for target_index in targets: self.circuit.append( gate_operation( - *map(self.qr.__getitem__, control_indices), + *map(self.qr.__getitem__, controls), self.qr[target_index] ) ) return - for target_index in target_indices: + for target_index in targets: self.circuit.append(gate_operation(self.qr[target_index])) def GlobalPhase( @@ -208,37 +217,34 @@ def measure( self.process_gate_params(gate=self.measure.__name__, params=locals()) - if isinstance(qubit_indices, int): - qubit_indices = [qubit_indices] + qubits = [qubit_indices] if isinstance(qubit_indices, int) else list(qubit_indices) + + # Given Cirq uses MSB convention, we will explicitly + # convert the qubit indices to LSB convention + # This will help performance by avoiding `circuit.vertical_reverse()` calls + for i, index in enumerate(qubits): + qubits[i] = self.num_qubits - 1 - index # We must sort the indices as Cirq interprets that the order of measurements # is relevant # This is done to ensure that the measurements are consistent across different # framework - for qubit_index in sorted(qubit_indices): + for qubit_index in sorted(qubits): self.circuit.append(cirq.measure(self.qr[qubit_index], key=f"q{qubit_index}")) self.measurement_keys.append(f"q{qubit_index}") + self.measured_qubits.add(qubit_index) self.measurement_keys = sorted(self.measurement_keys) - for qubit_index in qubit_indices: - self.measured_qubits.add(qubit_index) - def get_statevector( self, backend: Backend | None = None, ) -> NDArray[np.complex128]: - # Copy the circuit as the operations are applied inplace - circuit: CirqCircuit = self.copy() # type: ignore - - # Cirq uses MSB convention for qubits, so we need to reverse the qubit indices - circuit.vertical_reverse() - if backend is None: - state_vector = circuit.circuit.final_state_vector(qubit_order=self.qr) + state_vector = self.circuit.final_state_vector(qubit_order=self.qr) else: - state_vector = backend.get_statevector(circuit) + state_vector = backend.get_statevector(self) return np.array(state_vector) @@ -253,22 +259,16 @@ def get_counts( if num_qubits_to_measure == 0: raise ValueError("At least one qubit must be measured.") - # Copy the circuit as the vertical reverse is applied inplace - circuit: CirqCircuit = self.copy() # type: ignore - - # Cirq uses MSB convention for qubits, so we need to reverse the qubit indices - circuit.vertical_reverse() - if backend is None: # If no backend is provided, use the `cirq.Simulator` base_backend = cirq.Simulator() - result = base_backend.run(circuit.circuit, repetitions=num_shots) + result = base_backend.run(self.circuit, repetitions=num_shots) # Using the `multi_measurement_histogram` method to get the counts we can # get the counts given the measurement keys, allowing for partial measurement # without post-processing - counts = dict(result.multi_measurement_histogram(keys=circuit.measurement_keys)) + counts = dict(result.multi_measurement_histogram(keys=self.measurement_keys)) counts = {''.join(map(str, key)): value for key, value in counts.items()} for i in range(2**num_qubits_to_measure): basis = format(int(i),"0{}b".format(num_qubits_to_measure)) @@ -282,19 +282,12 @@ def get_counts( counts = dict(sorted(counts.items())) else: - counts = backend.get_counts(circuit, num_shots) + counts = backend.get_counts(self, num_shots) return counts def get_unitary(self) -> NDArray[np.complex128]: - # Copy the circuit as the vertical reverse is applied inplace - circuit: CirqCircuit = self.copy() # type: ignore - - # Cirq uses MSB convention for qubits, so we need to reverse the qubit indices - circuit.vertical_reverse() - - unitary = cirq.unitary(circuit.circuit) - + unitary = cirq.unitary(self.circuit) return np.array(unitary) def reset_qubit( @@ -304,10 +297,15 @@ def reset_qubit( self.process_gate_params(gate=self.reset_qubit.__name__, params=locals()) - if isinstance(qubit_indices, int): - qubit_indices = [qubit_indices] + qubits = [qubit_indices] if isinstance(qubit_indices, int) else list(qubit_indices) + + # Given Cirq uses MSB convention, we will explicitly + # convert the qubit indices to LSB convention + # This will help performance by avoiding `circuit.vertical_reverse()` calls + for i, index in enumerate(qubits): + qubits[i] = self.num_qubits - 1 - index - for qubit_index in qubit_indices: + for qubit_index in qubits: self.circuit.append(cirq.ResetChannel()(self.qr[qubit_index])) def to_qasm( diff --git a/quick/circuit/from_framework/__init__.py b/quick/circuit/from_framework/__init__.py index a5bc26f..d47ad9d 100644 --- a/quick/circuit/from_framework/__init__.py +++ b/quick/circuit/from_framework/__init__.py @@ -15,11 +15,13 @@ __all__ = [ "FromFramework", "FromCirq", + "FromPennyLane", "FromQiskit", "FromTKET" ] from quick.circuit.from_framework.from_framework import FromFramework from quick.circuit.from_framework.from_cirq import FromCirq +from quick.circuit.from_framework.from_pennylane import FromPennyLane from quick.circuit.from_framework.from_qiskit import FromQiskit from quick.circuit.from_framework.from_tket import FromTKET \ No newline at end of file diff --git a/quick/circuit/from_framework/from_cirq.py b/quick/circuit/from_framework/from_cirq.py index 4b7e063..8b5e998 100644 --- a/quick/circuit/from_framework/from_cirq.py +++ b/quick/circuit/from_framework/from_cirq.py @@ -55,12 +55,12 @@ class FromCirq(FromFramework): Parameters ---------- - `output_framework` : type[Circuit] + `output_framework` : type[quick.circuit.Circuit] The quantum computing framework to convert the quantum circuit to. Attributes ---------- - `output_framework` : type[Circuit] + `output_framework` : type[quick.circuit.Circuit] The quantum computing framework to convert the quantum circuit to. `gate_mapping` : dict[str, Callable] The mapping of the gates in Qiskit to the gates in quick. diff --git a/quick/circuit/from_framework/from_framework.py b/quick/circuit/from_framework/from_framework.py index b4cfde2..dfc4a10 100644 --- a/quick/circuit/from_framework/from_framework.py +++ b/quick/circuit/from_framework/from_framework.py @@ -34,12 +34,12 @@ class FromFramework(ABC): Parameters ---------- - `output_framework` : type[Circuit] + `output_framework` : type[quick.circuit.Circuit] The quantum computing framework to convert the quantum circuit to. Attributes ---------- - `output_framework` : type[Circuit] + `output_framework` : type[quick.circuit.Circuit] The quantum computing framework to convert the quantum circuit to. Raises diff --git a/quick/circuit/from_framework/from_pennylane.py b/quick/circuit/from_framework/from_pennylane.py new file mode 100644 index 0000000..b4448c6 --- /dev/null +++ b/quick/circuit/from_framework/from_pennylane.py @@ -0,0 +1,237 @@ +# Copyright 2023-2025 Qualition Computing LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://github.com/Qualition/quick/blob/main/LICENSE +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +""" Converter for quantum circuits from Pennylane to quick. +""" + +from __future__ import annotations + +__all__ = ["FromPennyLane"] + +import pennylane as qml # type: ignore +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from quick.circuit import Circuit +from quick.circuit.from_framework import FromFramework + + +class FromPennyLane(FromFramework): + """ `quick.circuit.from_framework.FromPennylane` is a class for converting quantum circuits from + Pennylane to `quick.circuit.Circuit` class. + + Notes + ----- + The conversion is done by first transpiling the circuit to rx, rz and cx gates, and then extracting + the parameters of the gates in the Pennylane circuit. We perform transpilation to the minimal gateset + of [rx, rz, cx, global phase] to allow for support of future Pennylane gates, as well as custom ones + that are not native to Pennylane. + + This is done to ensure that the conversion is as general as possible without having to update the + converter for every new gate that is added to Pennylane, or for gates that are not currently implemented + in quick. + + The conversion is limited to the unitary quantum gates, global phase, and measurement gates. + + Parameters + ---------- + `output_framework` : type[quick.circuit.Circuit] + The quantum computing framework to convert the quantum circuit to. + + Attributes + ---------- + `output_framework` : type[quick.circuit.Circuit] + The quantum computing framework to convert the quantum circuit to. + `gate_mapping` : dict[str, Callable] + The mapping of the gate names between Pennylane and quick. + + Raises + ------ + `TypeError` + - If the `output_framework` is not a subclass of `quick.circuit.Circuit`. + + Usage + ----- + >>> pennylane_converter = FromPennylane(output_framework=CirqCircuit) + """ + def __init__( + self, + output_framework: type[Circuit] + ) -> None: + + super().__init__(output_framework=output_framework) + + self.gate_mapping = { + "RX": self._extract_rx_gate_params, + "RZ": self._extract_rz_gate_params, + "CNOT": self._extract_cx_gate_params, + "GlobalPhase": self._extract_global_phase_gate_params, + "MidMeasureMP": self._extract_measure_gate_params + } + + def _extract_rx_gate_params( + self, + gate: qml.Operation, + params: list[dict] + ) -> None: + """ Extract the parameters of a RX gate. + + Parameters + ---------- + `gate` : qml.Operation + The RX gate to extract the parameters from. + `params` : list[dict] + The list of parameters to extract the parameters to. + """ + params.append({ + "gate": "RX", + "angle": gate.data[0], + "qubit_indices": gate.wires.tolist() + }) + + def _extract_rz_gate_params( + self, + gate: qml.Operation, + params: list[dict] + ) -> None: + """ Extract the parameters of a RZ gate. + + Parameters + ---------- + `gate` : qml.Operation + The RZ gate to extract the parameters from. + `params` : list[dict] + The list of parameters to extract the parameters to. + """ + params.append({ + "gate": "RZ", + "angle": gate.data[0], + "qubit_indices": gate.wires.tolist() + }) + + def _extract_cx_gate_params( + self, + gate: qml.Operation, + params: list[dict] + ) -> None: + """ Extract the parameters of a CX gate. + + Parameters + ---------- + `gate` : qml.Operation + The CX gate to extract the parameters from. + `params` : list[dict] + The list of parameters to extract the parameters to. + """ + params.append({ + "gate": "CX", + "control_index": gate.wires.tolist()[0], + "target_index": gate.wires.tolist()[1] + }) + + def _extract_global_phase_gate_params( + self, + gate: qml.Operation, + params: list[dict] + ) -> None: + """ Extract the parameters of a global phase gate. + + Parameters + ---------- + `gate` : qml.Operation + The global phase gate to extract the parameters from. + `params` : list[dict] + The list of parameters to extract the parameters to. + """ + params.append({ + "gate": "GlobalPhase", + "angle": -gate.data[0] # type: ignore + }) + + def _extract_measure_gate_params( + self, + gate: qml.Operation, + params: list[dict] + ) -> None: + """ Extract the parameters of a measurement gate. + + Parameters + ---------- + `gate` : qml.Operation + The measurement gate to extract the parameters from. + `params` : list[dict] + The list of parameters to extract the parameters to. + """ + params.append({ + "gate": "measure", + "qubit_indices": gate.wires.tolist() + }) + + def extract_params( + self, + circuit: qml.QNode + ) -> list[dict]: + """Extract the parameters of the gates in the Pennylane + circuit. + + Parameters + ---------- + `circuit` : qml.QNode + The quantum circuit to extract the parameters from. + + Returns + ------- + `params` : list[dict] + The list of parameters of the gates in the Pennylane + circuit. + + Raises + ------ + NotImplementedError + - If the gate is not found in the gate mapping. + """ + params: list[dict] = [] + + tape = qml.workflow.construct_tape(circuit)() + + print(tape.operations) + + for gate in tape.operations: + gate_name = gate.name + self.gate_mapping[gate_name](gate, params) + + return params + + def convert( + self, + circuit: qml.QNode + ) -> Circuit: + + num_qubits = len(circuit.device.wires) + quick_circuit = self.output_framework(num_qubits=num_qubits) + + # We first transpile the circuit to the minimal gateset of [u3, rz, rx, global phase] + # This allows for support of future Pennylane gates, as well as custom ones + # that are not native to Pennylane + circuit = qml.transforms.decompose( + circuit, + gate_set=[qml.CNOT, qml.RZ, qml.RX, qml.GlobalPhase] + ) + + for param in self.extract_params(circuit): + gate_name = param.pop("gate") + getattr(quick_circuit, gate_name)(**param) + + return quick_circuit \ No newline at end of file diff --git a/quick/circuit/from_framework/from_qiskit.py b/quick/circuit/from_framework/from_qiskit.py index a26e3af..b75fd96 100644 --- a/quick/circuit/from_framework/from_qiskit.py +++ b/quick/circuit/from_framework/from_qiskit.py @@ -51,12 +51,12 @@ class FromQiskit(FromFramework): Parameters ---------- - `output_framework` : type[Circuit] + `output_framework` : type[quick.circuit.Circuit] The quantum computing framework to convert the quantum circuit to. Attributes ---------- - `output_framework` : type[Circuit] + `output_framework` : type[quick.circuit.Circuit] The quantum computing framework to convert the quantum circuit to. `gate_mapping` : dict[str, Callable] The mapping of the gates in Qiskit to the gates in quick. @@ -162,11 +162,6 @@ def extract_params( ------- `params` : list[dict] The list of parameters of the gates in the Qiskit circuit. - - Raises - ------ - NotImplementedError - - If the gate is not found in the gate mapping. """ params: list[dict] = [] diff --git a/quick/circuit/from_framework/from_tket.py b/quick/circuit/from_framework/from_tket.py index 5fa6c41..5cd1249 100644 --- a/quick/circuit/from_framework/from_tket.py +++ b/quick/circuit/from_framework/from_tket.py @@ -53,12 +53,12 @@ class FromTKET(FromFramework): Parameters ---------- - `output_framework` : type[Circuit] + `output_framework` : type[quick.circuit.Circuit] The quantum computing framework to convert the quantum circuit to. Attributes ---------- - `output_framework` : type[Circuit] + `output_framework` : type[quick.circuit.Circuit] The quantum computing framework to convert the quantum circuit to. `gate_mapping` : dict[str, Callable] The mapping of the gate names between TKET and quick. diff --git a/quick/circuit/pennylanecircuit.py b/quick/circuit/pennylanecircuit.py index 5f26493..614214a 100644 --- a/quick/circuit/pennylanecircuit.py +++ b/quick/circuit/pennylanecircuit.py @@ -20,6 +20,7 @@ __all__ = ["PennylaneCircuit"] from collections.abc import Sequence +import copy import numpy as np from numpy.typing import NDArray from typing import Callable, TYPE_CHECKING @@ -131,26 +132,35 @@ def _gate_mapping( angles: Sequence[float] = (0, 0, 0) ) -> None: - target_indices = [target_indices] if isinstance(target_indices, int) else target_indices - control_indices = [control_indices] if isinstance(control_indices, int) else control_indices + targets = [target_indices] if isinstance(target_indices, int) else list(target_indices) + controls = [control_indices] if isinstance(control_indices, int) else list(control_indices) + + # Given Pennylane uses MSB convention, we will explicitly + # convert the qubit indices to LSB convention + # This will help performance by avoiding `circuit.vertical_reverse()` calls + for i, index in enumerate(targets): + targets[i] = self.num_qubits - 1 - index + + for i, index in enumerate(controls): + controls[i] = self.num_qubits - 1 - index # Lazily extract the value of the gate from the mapping to avoid # creating all the gates at once, and to maintain the abstraction # Apply the gate operation to the specified qubits gate_operation = self.gate_mapping[gate](angles) - if control_indices: - for target_index in target_indices: + if controls: + for target_index in targets: self.circuit.append( qml.ControlledQubitUnitary( gate_operation, - control_wires=control_indices, + control_wires=controls, wires=target_index ) ) return - for target_index in target_indices: + for target_index in targets: self.circuit.append( qml.QubitUnitary(gate_operation, wires=target_index) ) @@ -178,10 +188,15 @@ def measure( # methods # This is due to the need for PennyLane quantum functions to return measurement results # Therefore, we do not need to do anything here - if isinstance(qubit_indices, int): - qubit_indices = [qubit_indices] + qubits = [qubit_indices] if isinstance(qubit_indices, int) else list(qubit_indices) - for qubit_index in qubit_indices: + # Given Pennylane uses MSB convention, we will explicitly + # convert the qubit indices to LSB convention + # This will help performance by avoiding `circuit.vertical_reverse()` calls + for i, index in enumerate(qubits): + qubits[i] = self.num_qubits - 1 - index + + for qubit_index in qubits: self.measured_qubits.add(qubit_index) self.circuit.append((qml.measure(qubit_index), False)) # type: ignore @@ -190,26 +205,8 @@ def get_statevector( backend: Backend | None = None, ) -> NDArray[np.complex128]: - # Copy the circuit as the vertical reverse is applied inplace - circuit: PennylaneCircuit = self.copy() # type: ignore - - # PennyLane uses MSB convention for qubits, so we need to reverse the qubit indices - circuit.vertical_reverse() - - def compile_circuit() -> qml.StateMP: - """ Compile the circuit. - - Parameters - ---------- - circuit : Collection[qml.Op] - The list of operations representing the circuit. - - Returns - ------- - qml.StateMP - The state vector of the circuit. - """ - for op in circuit.circuit: + def compile_circuit() -> qml.measurements.StateMP: + for op in self.circuit: if isinstance(op, tuple): qml.measure(op[0].wires[0], reset=op[1]) # type: ignore continue @@ -219,9 +216,9 @@ def compile_circuit() -> qml.StateMP: return qml.state() if backend is None: - state_vector = qml.QNode(compile_circuit, circuit.device)() + state_vector = qml.QNode(compile_circuit, self.device)() else: - state_vector = backend.get_statevector(circuit) + state_vector = backend.get_statevector(self) return np.array(state_vector) @@ -236,36 +233,18 @@ def get_counts( if len(self.measured_qubits) == 0: raise ValueError("At least one qubit must be measured.") - # Copy the circuit as the vertical reverse is applied inplace - circuit: PennylaneCircuit = self.copy() # type: ignore - - # PennyLane uses MSB convention for qubits, so we need to reverse the qubit indices - circuit.vertical_reverse() - - def compile_circuit() -> qml.CountsMp: - """ Compile the circuit. - - Parameters - ---------- - circuit : Collection[qml.Op] - The list of operations representing the circuit. - - Returns - ------- - Collection[qml.ProbabilityMP] - The list of probability measurements. - """ - for op in circuit.circuit: + def compile_circuit() -> qml.measurements.CountsMp: + for op in self.circuit: if isinstance(op, tuple): qml.measure(op[0].wires[0], reset=op[1]) # type: ignore continue qml.apply(op) - return qml.counts(wires=circuit.measured_qubits, all_outcomes=True) + return qml.counts(wires=self.measured_qubits, all_outcomes=True) if backend is None: - device = qml.device(circuit.device.name, wires=circuit.num_qubits, shots=num_shots) + device = qml.device(self.device.name, wires=self.num_qubits, shots=num_shots) result = qml.QNode(compile_circuit, device)() counts = { list(result.keys())[i]: int(list(result.values())[i]) for i in range(len(result)) @@ -281,27 +260,17 @@ def compile_circuit() -> qml.CountsMp: return counts def get_unitary(self) -> NDArray[np.complex128]: - # Copy the circuit as the vertical reverse is applied inplace - circuit: PennylaneCircuit = self.copy() # type: ignore - - # PennyLane uses MSB convention for qubits, so we need to reverse the qubit indices - circuit.vertical_reverse() + # Copy the circuit as the identity gates are applied inplace + circuit_ops: list[qml.Operation] = copy.deepcopy(self.circuit) # type: ignore def compile_circuit() -> None: - """ Compile the circuit. - - Parameters - ---------- - `circuit` : Collection[qml.Op] - The list of operations representing the circuit. - """ - if circuit.circuit == [] or ( - isinstance(circuit.circuit[0], qml.GlobalPhase) and len(circuit.circuit) == 1 + if circuit_ops == [] or ( + isinstance(circuit_ops[0], qml.GlobalPhase) and len(circuit_ops) == 1 ): - for i in range(circuit.num_qubits): - circuit.circuit.append(qml.Identity(wires=i)) + for i in range(self.num_qubits): + circuit_ops.append(qml.Identity(wires=i)) - for op in circuit.circuit: + for op in circuit_ops: if isinstance(op, tuple): qml.measure(op[0].wires[0], reset=op[1]) # type: ignore continue @@ -309,7 +278,10 @@ def compile_circuit() -> None: qml.apply(op) unitary = np.array( - qml.matrix(compile_circuit, wire_order=range(self.num_qubits))(), dtype=complex # type: ignore + qml.matrix( + compile_circuit, # type: ignore + wire_order=range(self.num_qubits), + )(), dtype=complex ) return unitary @@ -321,10 +293,15 @@ def reset_qubit( self.process_gate_params(gate=self.reset_qubit.__name__, params=locals()) - if isinstance(qubit_indices, int): - qubit_indices = [qubit_indices] + qubits = [qubit_indices] if isinstance(qubit_indices, int) else list(qubit_indices) + + # Given Pennylane uses MSB convention, we will explicitly + # convert the qubit indices to LSB convention + # This will help performance by avoiding `circuit.vertical_reverse()` calls + for i, index in enumerate(qubits): + qubits[i] = self.num_qubits - 1 - index - for qubit_index in qubit_indices: + for qubit_index in qubits: self.circuit.append((qml.measure(qubit_index), True)) # type: ignore def to_qasm( diff --git a/quick/circuit/qiskitcircuit.py b/quick/circuit/qiskitcircuit.py index 17b0059..4868d1e 100644 --- a/quick/circuit/qiskitcircuit.py +++ b/quick/circuit/qiskitcircuit.py @@ -174,8 +174,7 @@ def measure( self.process_gate_params(gate=self.measure.__name__, params=locals()) - if isinstance(qubit_indices, int): - qubit_indices = [qubit_indices] + qubit_indices = [qubit_indices] if isinstance(qubit_indices, int) else qubit_indices self.circuit.measure(qubit_indices, qubit_indices) @@ -245,7 +244,6 @@ def get_counts( def get_unitary(self) -> NDArray[np.complex128]: unitary = Operator(self.circuit).data - return np.array(unitary) def reset_qubit( @@ -255,8 +253,7 @@ def reset_qubit( self.process_gate_params(gate=self.reset_qubit.__name__, params=locals()) - if isinstance(qubit_indices, int): - qubit_indices = [qubit_indices] + qubit_indices = [qubit_indices] if isinstance(qubit_indices, int) else qubit_indices for qubit_index in qubit_indices: self.circuit.reset(qubit_index) diff --git a/quick/circuit/quimbcircuit.py b/quick/circuit/quimbcircuit.py index 4b7b073..a29b6ec 100644 --- a/quick/circuit/quimbcircuit.py +++ b/quick/circuit/quimbcircuit.py @@ -133,25 +133,34 @@ def _gate_mapping( angles: Sequence[float] = (0, 0, 0) ) -> None: - target_indices = [target_indices] if isinstance(target_indices, int) else target_indices - control_indices = [control_indices] if isinstance(control_indices, int) else control_indices + targets = [target_indices] if isinstance(target_indices, int) else list(target_indices) + controls = [control_indices] if isinstance(control_indices, int) else list(control_indices) + + # Given Quimb uses MSB convention, we will explicitly + # convert the qubit indices to LSB convention + # This will help performance by avoiding `circuit.vertical_reverse()` calls + for i, index in enumerate(targets): + targets[i] = self.num_qubits - 1 - index + + for i, index in enumerate(controls): + controls[i] = self.num_qubits - 1 - index # Lazily extract the value of the gate from the mapping to avoid # creating all the gates at once, and to maintain the polymorphism gate_operation: qtn.Gate = self.gate_mapping[gate](angles) - if control_indices: - gate_operation = control(ncontrol=len(control_indices), gate=gate_operation) # type: ignore + if controls: + gate_operation = control(ncontrol=len(controls), gate=gate_operation) # type: ignore - for target_index in target_indices: + for target_index in targets: self.circuit.apply_gate( gate_operation, - *control_indices, + *controls, target_index ) return - for target_index in target_indices: + for target_index in targets: self.circuit.apply_gate(gate_operation, target_index) def GlobalPhase( @@ -174,10 +183,15 @@ def measure( self.process_gate_params(gate=self.measure.__name__, params=locals()) - if isinstance(qubit_indices, int): - qubit_indices = [qubit_indices] + qubits = [qubit_indices] if isinstance(qubit_indices, int) else list(qubit_indices) - for qubit_index in qubit_indices: + # Given Quimb uses MSB convention, we will explicitly + # convert the qubit indices to LSB convention + # This will help performance by avoiding `circuit.vertical_reverse()` calls + for i, index in enumerate(qubits): + qubits[i] = self.num_qubits - 1 - index + + for qubit_index in qubits: self.measured_qubits.add(qubit_index) def get_statevector( @@ -185,18 +199,12 @@ def get_statevector( backend: Backend | None = None, ) -> NDArray[np.complex128]: - # Copy the circuit as the vertical operation is inplace - circuit: QuimbCircuit = self.copy() # type: ignore - - # Quimb uses MSB convention for qubits, so we need to reverse the qubit indices - circuit.vertical_reverse() - if backend is None: - psi: qtn.tensor_arbgeom.TensorNetworkGenVector = circuit.circuit.psi + psi: qtn.tensor_arbgeom.TensorNetworkGenVector = self.circuit.psi state_vector = np.array(psi.to_dense()).flatten() # Apply the global phase to the state vector - state_vector *= np.exp(1j * circuit.global_phase) + state_vector *= np.exp(1j * self.global_phase) else: state_vector = backend.get_statevector(self) @@ -211,14 +219,8 @@ def get_counts( if len(self.measured_qubits) == 0: raise ValueError("At least one qubit must be measured.") - # Copy the circuit as the vertical operation is inplace - circuit: QuimbCircuit = self.copy() # type: ignore - - # Quimb uses MSB convention for qubits, so we need to reverse the qubit indices - circuit.vertical_reverse() - if backend is None: - samples = circuit.circuit.sample(C=num_shots, qubits=circuit.measured_qubits) + samples = self.circuit.sample(C=num_shots, qubits=self.measured_qubits) counts: dict[str, int] = {} for sample in samples: @@ -226,25 +228,48 @@ def get_counts( counts[key] = counts.get(key, 0) + 1 else: - counts = backend.get_counts(circuit=circuit, num_shots=num_shots) + counts = backend.get_counts(circuit=self, num_shots=num_shots) return counts def get_unitary(self) -> NDArray[np.complex128]: - # Copy the circuit as the vertical operation is inplace - circuit: QuimbCircuit = self.copy() # type: ignore - - # Quimb uses MSB convention for qubits, so we need to reverse the qubit indices - circuit.vertical_reverse() - - uni: qtn.tensor_arbgeom.TensorNetworkGenOperator = circuit.circuit.get_uni() + uni: qtn.tensor_arbgeom.TensorNetworkGenOperator = self.circuit.get_uni() unitary = np.array(uni.to_dense()) # Apply the global phase to the unitary - unitary *= np.exp(1j * circuit.global_phase) + unitary *= np.exp(1j * self.global_phase) return unitary + def get_tensor_network(self) -> qtn.TensorNetwork: + """ Get the tensor network representation of the circuit. + + Notes + ----- + The tensor network follows MSB convention for compatibility + with quimb. To avoid circular imports, this functionality is + only accessible through the `QuimbCircuit` class. + + Returns + ------- + `tensor_network` : qtn.TensorNetwork + The tensor network representation of the circuit. + + Usage + ----- + >>> circuit = QuimbCircuit(num_qubits=2) + >>> circuit.get_tensor_network() + """ + tensor_network = qtn.TensorNetwork(self.circuit.psi) + + for gate in tensor_network: + gate.drop_tags([f"I{i}" for i in range(self.num_qubits)]) + + # Apply the global phase to the tensor network + tensor_network *= np.exp(1j * self.global_phase) + + return tensor_network + def reset_qubit( self, qubit_indices: int | Sequence[int] diff --git a/quick/circuit/tketcircuit.py b/quick/circuit/tketcircuit.py index a2ecfa4..da4c257 100644 --- a/quick/circuit/tketcircuit.py +++ b/quick/circuit/tketcircuit.py @@ -134,25 +134,34 @@ def _gate_mapping( angles: Sequence[float] = (0, 0, 0) ) -> None: - target_indices = [target_indices] if isinstance(target_indices, int) else target_indices - control_indices = [control_indices] if isinstance(control_indices, int) else control_indices + targets = [target_indices] if isinstance(target_indices, int) else list(target_indices) + controls = [control_indices] if isinstance(control_indices, int) else list(control_indices) + + # Given TKET uses MSB convention, we will explicitly + # convert the qubit indices to LSB convention + # This will help performance by avoiding `circuit.vertical_reverse()` calls + for i, index in enumerate(targets): + targets[i] = self.num_qubits - 1 - index + + for i, index in enumerate(controls): + controls[i] = self.num_qubits - 1 - index # Lazily extract the value of the gate from the mapping to avoid # creating all the gates at once, and to maintain the abstraction - gate_operation = self.gate_mapping[gate](angles) + gate_operation = Op.create(*self.gate_mapping[gate](angles)) - if control_indices: + if controls: gate_operation = QControlBox( - Op.create(*gate_operation), - len(control_indices) + gate_operation, + len(controls) ) - for target_index in target_indices: - self.circuit.add_qcontrolbox(gate_operation, [*control_indices[:], target_index]) # type: ignore + for target_index in targets: + self.circuit.add_qcontrolbox(gate_operation, [*controls[:], target_index]) return - for target_index in target_indices: - self.circuit.add_gate(*gate_operation, [target_index]) # type: ignore + for target_index in targets: + self.circuit.add_gate(gate_operation, [target_index]) def GlobalPhase( self, @@ -172,10 +181,15 @@ def measure( self.process_gate_params(gate=self.measure.__name__, params=locals()) - if isinstance(qubit_indices, int): - qubit_indices = [qubit_indices] + qubits = [qubit_indices] if isinstance(qubit_indices, int) else list(qubit_indices) + + # Given TKET uses MSB convention, we will explicitly + # convert the qubit indices to LSB convention + # This will help performance by avoiding `circuit.vertical_reverse()` calls + for i, index in enumerate(qubits): + qubits[i] = self.num_qubits - 1 - index - for index in qubit_indices: + for index in qubits: self.circuit.Measure(index, index) self.measured_qubits.add(index) @@ -184,18 +198,12 @@ def get_statevector( backend: Backend | None = None, ) -> NDArray[np.complex128]: - # Copy the circuit as the vertical reverse is applied inplace - circuit: TKETCircuit = self.copy() # type: ignore - - # PyTKET uses MSB convention for qubits, so we need to reverse the qubit indices - circuit.vertical_reverse() - if backend is None: base_backend = AerStateBackend() - circuit = base_backend.get_compiled_circuits([circuit.circuit]) # type: ignore + circuit = base_backend.get_compiled_circuits([self.circuit]) # type: ignore state_vector = base_backend.run_circuit(circuit[0]).get_state() # type: ignore else: - state_vector = backend.get_statevector(circuit) + state_vector = backend.get_statevector(self) return np.array(state_vector) @@ -210,16 +218,10 @@ def get_counts( if num_qubits_to_measure == 0: raise ValueError("At least one qubit must be measured.") - # Copy the circuit as the vertical reverse is applied inplace - circuit: TKETCircuit = self.copy() # type: ignore - - # PyTKET uses MSB convention for qubits, so we need to reverse the qubit indices - circuit.vertical_reverse() - if backend is None: # If no backend is provided, use the AerBackend base_backend = AerBackend() - compiled_circuit = base_backend.get_compiled_circuits([circuit.circuit]) # type: ignore + compiled_circuit = base_backend.get_compiled_circuits([self.circuit]) # type: ignore result = base_backend.run_circuit(compiled_circuit[0], n_shots=num_shots, seed=0) # type: ignore counts = { @@ -231,7 +233,7 @@ def get_counts( # Parse the binary strings to filter out the unmeasured qubits for key in counts.keys(): - new_key = ''.join(key[i] for i in range(len(key)) if i in circuit.measured_qubits) + new_key = ''.join(key[i] for i in range(len(key)) if i in self.measured_qubits) partial_counts[new_key] = counts[key] counts = partial_counts @@ -243,20 +245,13 @@ def get_counts( } else: - counts = backend.get_counts(circuit=circuit, num_shots=num_shots) + counts = backend.get_counts(circuit=self, num_shots=num_shots) return counts def get_unitary(self) -> NDArray[np.complex128]: - # Copy the circuit as the vertical reverse is applied inplace - circuit: TKETCircuit = self.copy() # type: ignore - - # PyTKET uses MSB convention for qubits, so we need to reverse the qubit indices - circuit.vertical_reverse() - - unitary = circuit.circuit.get_unitary() - - return np.array(unitary) + unitary = self.circuit.get_unitary() + return unitary def reset_qubit( self, @@ -265,10 +260,15 @@ def reset_qubit( self.process_gate_params(gate=self.reset_qubit.__name__, params=locals()) - if isinstance(qubit_indices, int): - qubit_indices = [qubit_indices] + qubits = [qubit_indices] if isinstance(qubit_indices, int) else list(qubit_indices) + + # Given TKET uses MSB convention, we will explicitly + # convert the qubit indices to LSB convention + # This will help performance by avoiding `circuit.vertical_reverse()` calls + for i, index in enumerate(qubits): + qubits[i] = self.num_qubits - 1 - index - for qubit_index in qubit_indices: + for qubit_index in qubits: self.circuit.Reset(qubit_index) def to_qasm( diff --git a/quick/metrics/__init__.py b/quick/metrics/__init__.py index d83ed15..f496d44 100644 --- a/quick/metrics/__init__.py +++ b/quick/metrics/__init__.py @@ -15,11 +15,13 @@ __all__ = [ "calculate_entanglement_range", "calculate_shannon_entropy", - "calculate_entanglement_entropy" + "calculate_entanglement_entropy", + "calculate_hilbert_schmidt_test" ] from quick.metrics.metrics import ( calculate_entanglement_range, calculate_shannon_entropy, - calculate_entanglement_entropy + calculate_entanglement_entropy, + calculate_hilbert_schmidt_test ) \ No newline at end of file diff --git a/quick/metrics/metrics.py b/quick/metrics/metrics.py index 9a3795a..056fe1d 100644 --- a/quick/metrics/metrics.py +++ b/quick/metrics/metrics.py @@ -20,13 +20,16 @@ __all__ = [ "calculate_entanglement_range", "calculate_shannon_entropy", - "calculate_entanglement_entropy" + "calculate_entanglement_entropy", + "calculate_hilbert_schmidt_test" ] import numpy as np from numpy.typing import NDArray import quimb.tensor as qtn # type: ignore +from quick.predicates import is_unitary_matrix + def _get_submps_indices(mps: qtn.MatrixProductState) -> list[tuple[int, int]]: """ Get the indices of contiguous blocks in the MPS. For testing purposes, @@ -165,4 +168,49 @@ def calculate_entanglement_entropy(statevector: NDArray[np.complex128]) -> float """ density_matrix = np.outer(statevector, statevector.conj()) eigenvalues = np.maximum(np.real(np.linalg.eigvals(density_matrix)), 0.0) - return calculate_shannon_entropy(eigenvalues) \ No newline at end of file + return calculate_shannon_entropy(eigenvalues) + +def calculate_hilbert_schmidt_test( + unitary_1: NDArray[np.complex128], + unitary_2: NDArray[np.complex128] + ) -> float: + """ Calculate the Hilbert-Schmidt test. This is a measure of the + similarity of two unitary matrices. + + Parameters + ---------- + `unitary_1` : NDArray[np.complex128] + The first unitary matrix. + + `unitary_2` : NDArray[np.complex128] + The second unitary matrix. + + Returns + ------- + `chst` : float + The Hilbert-Schmidt test of the two unitary matrices. + + Raises + ------ + ValueError + - If either of the matrices is not unitary. + - If the matrices are not square. + + Usage + ----- + >>> hilbert_schmidt_test = calculate_hilbert_schmidt_test(unitary_1, unitary_2) + """ + if not is_unitary_matrix(unitary_1): + raise ValueError("The first matrix is not unitary.") + if not is_unitary_matrix(unitary_2): + raise ValueError("The second matrix is not unitary.") + + num_qubits = int(np.log2(unitary_1.shape[0])) + + chst = 1/2**(2 * num_qubits) * np.abs( + np.trace( + np.dot(unitary_1.conj().T, unitary_2) + ) + )**2 + + return chst \ No newline at end of file diff --git a/quick/predicates/__init__.py b/quick/predicates/__init__.py index 4aec12a..7f7729e 100644 --- a/quick/predicates/__init__.py +++ b/quick/predicates/__init__.py @@ -13,6 +13,7 @@ # limitations under the License. __all__ = [ + "is_statevector", "is_square_matrix", "is_diagonal_matrix", "is_symmetric_matrix", @@ -24,6 +25,7 @@ ] from quick.predicates.predicates import ( + is_statevector, is_square_matrix, is_diagonal_matrix, is_symmetric_matrix, diff --git a/quick/predicates/predicates.py b/quick/predicates/predicates.py index a0f9352..396f9d4 100644 --- a/quick/predicates/predicates.py +++ b/quick/predicates/predicates.py @@ -18,6 +18,7 @@ from __future__ import annotations __all__ = [ + "is_statevector", "is_square_matrix", "is_diagonal_matrix", "is_symmetric_matrix", @@ -30,11 +31,70 @@ import numpy as np from numpy.typing import NDArray +import math ATOL_DEFAULT = 1e-8 RTOL_DEFAULT = 1e-5 +def _is_power( + base: int, + number: int + ) -> bool: + """ Test if a number is a power of another number. + + Parameters + ---------- + `base` : int + The base number. + `number` : int + The number to check. + + Returns + ------- + bool + True if the number is a power of the base, False otherwise. + """ + result = math.log(number) / math.log(base) + return result == math.floor(result) + +def is_statevector( + statevector: NDArray[np.complex128], + system_size: int=2 + ) -> bool: + """ Test if an array is a statevector. + + Parameters + ---------- + `statevector` : NDArray[np.complex128] + The input statevector. + `system_size` : int, optional, default=2 + The size of the quantum memory. If the size is 2, then the + system uses qubits. If the size is 3, then the system uses qutrits, + and so on. + + Returns + ------- + bool + True if the array is a statevector, False otherwise. + + Raises + ------ + ValueError + - If the system size is less than 2. + + Usage + ----- + >>> is_statevector(np.array([1, 0])) + """ + if system_size < 2: + raise ValueError("System size must be greater than or equal to 2.") + + if not _is_power(system_size, len(statevector)): + return False + + return np.linalg.norm(statevector) == 1 and statevector.ndim == 1 and len(statevector) > 1 + def is_square_matrix(matrix: NDArray[np.complex128]) -> bool: """ Test if an array is a square matrix. @@ -279,6 +339,9 @@ def is_isometry( ----- >>> is_isometry(np.eye(2)) """ + if matrix.ndim != 2: + return False + identity = np.eye(matrix.shape[1]) matrix = np.conj(matrix.T).dot(matrix) return np.allclose(matrix, identity, rtol=rtol, atol=atol) \ No newline at end of file diff --git a/stubs/quick/__init__.pyi b/stubs/quick/__init__.pyi deleted file mode 100644 index 35cbab0..0000000 --- a/stubs/quick/__init__.pyi +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick import ( - backend as backend, - circuit as circuit, - compiler as compiler, - metrics as metrics, - optimizer as optimizer, - primitives as primitives, - random as random, - synthesis as synthesis, -) - -__all__ = [ - "backend", - "circuit", - "compiler", - "metrics", - "optimizer", - "primitives", - "random", - "synthesis" -] diff --git a/stubs/quick/backend/__init__.pyi b/stubs/quick/backend/__init__.pyi deleted file mode 100644 index d324885..0000000 --- a/stubs/quick/backend/__init__.pyi +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick.backend.backend import Backend as Backend, FakeBackend as FakeBackend, NoisyBackend as NoisyBackend -from quick.backend.qiskit_backends.aer_backend import AerBackend as AerBackend -from quick.backend.qiskit_backends.fake_ibm_backend import FakeIBMBackend as FakeIBMBackend - -__all__ = ["Backend", "NoisyBackend", "FakeBackend", "AerBackend", "FakeIBMBackend"] diff --git a/stubs/quick/backend/backend.pyi b/stubs/quick/backend/backend.pyi deleted file mode 100644 index a5c3182..0000000 --- a/stubs/quick/backend/backend.pyi +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import abc -import numpy as np -from abc import ABC, abstractmethod -from numpy.typing import NDArray -from quick.circuit import Circuit -from types import NotImplementedType - -__all__ = ["Backend", "NoisyBackend", "FakeBackend"] - -class Backend(ABC, metaclass=abc.ABCMeta): - device: str - def __init__(self, device: str="CPU") -> None: ... - @staticmethod - def backendmethod(method): ... - @abstractmethod - def get_statevector(self, circuit: Circuit) -> NDArray[np.complex128]: ... - @abstractmethod - def get_operator(self, circuit: Circuit) -> NDArray[np.complex128]: ... - @abstractmethod - def get_counts(self, circuit: Circuit, num_shots: int=1024) -> dict[str, int]: ... - @classmethod - def __subclasscheck__(cls, C) -> bool: ... - @classmethod - def __subclasshook__(cls, C) -> bool | NotImplementedType: ... - @classmethod - def __instancecheck__(cls, C) -> bool: ... - -class NoisyBackend(Backend, metaclass=abc.ABCMeta): - single_qubit_error: float - two_qubit_error: float - noisy: bool - def __init__(self, single_qubit_error: float, two_qubit_error: float, device: str="CPU") -> None: ... - -class FakeBackend(Backend, metaclass=abc.ABCMeta): - def __init__(self, device: str="CPU") -> None: ... diff --git a/stubs/quick/backend/qiskit_backends/aer_backend.pyi b/stubs/quick/backend/qiskit_backends/aer_backend.pyi deleted file mode 100644 index 15a1d17..0000000 --- a/stubs/quick/backend/qiskit_backends/aer_backend.pyi +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -from numpy.typing import NDArray -from quick.backend import NoisyBackend -from quick.circuit import Circuit - -__all__ = ["AerBackend"] - -class AerBackend(NoisyBackend): - def __init__(self, single_qubit_error: float=0.0, two_qubit_error: float=0.0, device: str="CPU") -> None: ... - def get_statevector(self, circuit: Circuit) -> NDArray[np.complex128]: ... - def get_operator(self, circuit: Circuit) -> NDArray[np.complex128]: ... - def get_counts(self, circuit: Circuit, num_shots: int=1024) -> dict[str, int]: ... diff --git a/stubs/quick/backend/qiskit_backends/fake_ibm_backend.pyi b/stubs/quick/backend/qiskit_backends/fake_ibm_backend.pyi deleted file mode 100644 index ca66b49..0000000 --- a/stubs/quick/backend/qiskit_backends/fake_ibm_backend.pyi +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -from numpy.typing import NDArray -from quick.backend import FakeBackend -from quick.circuit import Circuit -from qiskit_ibm_runtime import QiskitRuntimeService - -__all__ = ["FakeIBMBackend"] - -class FakeIBMBackend(FakeBackend): - def __init__(self, hardware_name: str, qiskit_runtime: QiskitRuntimeService, device: str="CPU") -> None: ... - def get_statevector(self, circuit: Circuit) -> NDArray[np.complex128]: ... - def get_operator(self, circuit: Circuit) -> NDArray[np.complex128]: ... - def get_counts(self, circuit: Circuit, num_shots: int=1024) -> dict[str, int]: ... diff --git a/stubs/quick/circuit/__init__.pyi b/stubs/quick/circuit/__init__.pyi deleted file mode 100644 index 92446d4..0000000 --- a/stubs/quick/circuit/__init__.pyi +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import quick.circuit.gate_matrix as gate_matrix -from quick.circuit.circuit import Circuit as Circuit -from quick.circuit.cirqcircuit import CirqCircuit as CirqCircuit -from quick.circuit.pennylanecircuit import PennylaneCircuit as PennylaneCircuit -from quick.circuit.qiskitcircuit import QiskitCircuit as QiskitCircuit -from quick.circuit.quimbcircuit import QuimbCircuit as QuimbCircuit -from quick.circuit.tketcircuit import TKETCircuit as TKETCircuit - -__all__ = [ - "gate_matrix", - "Circuit", - "CirqCircuit", - "PennylaneCircuit", - "QiskitCircuit", - "QuimbCircuit", - "TKETCircuit" -] diff --git a/stubs/quick/circuit/ansatz.pyi b/stubs/quick/circuit/ansatz.pyi deleted file mode 100644 index 044585f..0000000 --- a/stubs/quick/circuit/ansatz.pyi +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -from numpy.typing import NDArray -from quick.circuit import Circuit - -__all__ = ["Ansatz"] - -Params = list[list[float] | float] | list[float] - -class Ansatz: - ansatz: Circuit - ignore_global_phase: bool = True - def __init__(self, ansatz: Circuit, ignore_global_phase: bool = True) -> None: ... - @property - def thetas(self) -> Params: ... - @thetas.setter - def thetas(self, theta_values: Params) -> None: ... - @property - def num_params(self) -> int: ... - @property - def num_parameterized_gates(self) -> int: ... - @property - def is_parameterized(self) -> bool: ... diff --git a/stubs/quick/circuit/circuit.pyi b/stubs/quick/circuit/circuit.pyi deleted file mode 100644 index f8e2ed2..0000000 --- a/stubs/quick/circuit/circuit.pyi +++ /dev/null @@ -1,352 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import abc -import cirq -from collections.abc import Sequence -from contextlib import contextmanager -import matplotlib.pyplot as plt -import numpy as np -import pennylane as qml # type: ignore -import pytket -import qiskit # type: ignore -from abc import ABC, abstractmethod -from numpy.typing import NDArray -from quick.backend import Backend -from quick.circuit.dag import DAGCircuit -from quick.primitives import Bra, Ket, Operator -from quick.synthesis.unitarypreparation import UnitaryPreparation -from types import NotImplementedType -from typing import Any, Callable, Literal - -__all__ = ["Circuit"] - -GATES = Literal[ - "I", "X", "Y", "Z", "H", "S", "Sdg", "T", "Tdg", "RX", "RY", "RZ", "Phase", "U3" -] - - -class Circuit(ABC, metaclass=abc.ABCMeta): - num_qubits: int - circuit: Any - gate_mapping: dict[str, Callable] - measured_qubits: set[int] - circuit_log: list[dict] - stack: list[list[dict]] - global_phase: float - process_gate_params_flag: bool - def __init__(self, num_qubits: int) -> None: ... - def convert_param_type(self, value: Any) -> int | float | list: ... - def _process_single_qubit_index(self, qubit_index: Any) -> int: ... - def validate_qubit_index(self, name: str, value: Any) -> int | list[int]: ... - def _validate_single_angle(self, angle: Any) -> float: ... - def validate_angle(self, name: str, value: Any) -> None | float | list[float]: ... - def process_gate_params(self, gate: str, params: dict) -> dict | None: ... - @contextmanager - def decompose_last(self, gate: dict | None): ... - @abstractmethod - def _define_gate_mapping(self) -> dict[str, Callable]: ... - @abstractmethod - def _gate_mapping( - self, - gate: GATES, - target_indices: int | Sequence[int], - control_indices: int | Sequence[int] = [], - angles: Sequence[float] = (0, 0, 0) - ) -> None: ... - def Identity(self, qubit_indices: int | Sequence[int]) -> None: ... - def X(self, qubit_indices: int | Sequence[int]) -> None: ... - def Y(self, qubit_indices: int | Sequence[int]) -> None: ... - def Z(self, qubit_indices: int | Sequence[int]) -> None: ... - def H(self, qubit_indices: int | Sequence[int]) -> None: ... - def S(self, qubit_indices: int | Sequence[int]) -> None: ... - def Sdg(self, qubit_indices: int | Sequence[int]) -> None: ... - def T(self, qubit_indices: int | Sequence[int]) -> None: ... - def Tdg(self, qubit_indices: int | Sequence[int]) -> None: ... - def RX(self, angle: float, qubit_indices: int | Sequence[int]) -> None: ... - def RY(self, angle: float, qubit_indices: int | Sequence[int]) -> None: ... - def RZ(self, angle: float, qubit_indices: int | Sequence[int]) -> None: ... - def Phase(self, angle: float, qubit_indices: int | Sequence[int]) -> None: ... - def XPow(self, power: float, global_shift: float, qubit_indices: int | Sequence[int]) -> None: ... - def YPow(self, power: float, global_shift: float, qubit_indices: int | Sequence[int]) -> None: ... - def ZPow(self, power: float, global_shift: float, qubit_indices: int | Sequence[int]) -> None: ... - def RXX(self, angle: float, first_qubit_index: int, second_qubit_index: int) -> None: ... - def RYY(self, angle: float, first_qubit_index: int, second_qubit_index: int) -> None: ... - def RZZ(self, angle: float, first_qubit_index: int, second_qubit_index: int) -> None: ... - def U3(self, angles: Sequence[float], qubit_indices: int | Sequence[int]) -> None: ... - def SWAP(self, first_qubit_index: int, second_qubit_index: int) -> None: ... - def CX(self, control_index: int, target_index: int, control_state: str | None = None) -> None: ... - def CY(self, control_index: int, target_index: int, control_state: str | None = None) -> None: ... - def CZ(self, control_index: int, target_index: int, control_state: str | None = None) -> None: ... - def CH(self, control_index: int, target_index: int, control_state: str | None = None) -> None: ... - def CS(self, control_index: int, target_index: int, control_state: str | None = None) -> None: ... - def CSdg(self, control_index: int, target_index: int, control_state: str | None = None) -> None: ... - def CT(self, control_index: int, target_index: int, control_state: str | None = None) -> None: ... - def CTdg(self, control_index: int, target_index: int, control_state: str | None = None) -> None: ... - def CRX(self, angle: float, control_index: int, target_index: int, control_state: str | None = None) -> None: ... - def CRY(self, angle: float, control_index: int, target_index: int, control_state: str | None = None) -> None: ... - def CRZ(self, angle: float, control_index: int, target_index: int, control_state: str | None = None) -> None: ... - def CPhase(self, angle: float, control_index: int, target_index: int, control_state: str | None = None) -> None: ... - def CXPow( - self, - power: float, - global_shift: float, - control_index: int, - target_index: int, - control_state: str | None = None - ) -> None: ... - def CYPow( - self, - power: float, - global_shift: float, - control_index: int, - target_index: int, - control_state: str | None = None - ) -> None: ... - def CZPow( - self, - power: float, - global_shift: float, - control_index: int, - target_index: int, - control_state: str | None = None - ) -> None: ... - def CRXX( - self, - angle: float, - control_index: int, - first_target_index: int, - second_target_index: int, - control_state: str | None = None - ) -> None: ... - def CRYY( - self, - angle: float, - control_index: int, - first_target_index: int, - second_target_index: int, - control_state: str | None = None - ) -> None: ... - def CRZZ( - self, - angle: float, - control_index: int, - first_target_index: int, - second_target_index: int, - control_state: str | None = None - ) -> None: ... - def CU3( - self, - angles: Sequence[float], - control_index: int, - target_index: int, - control_state: str | None = None - ) -> None: ... - def CSWAP( - self, - control_index: int, - first_target_index: int, - second_target_index: int, - control_state: str | None = None - ) -> None: ... - def MCX(self, control_indices: int | Sequence[int], target_indices: int | Sequence[int], control_state: str | None = None) -> None: ... - def MCY(self, control_indices: int | Sequence[int], target_indices: int | Sequence[int], control_state: str | None = None) -> None: ... - def MCZ(self, control_indices: int | Sequence[int], target_indices: int | Sequence[int], control_state: str | None = None) -> None: ... - def MCH(self, control_indices: int | Sequence[int], target_indices: int | Sequence[int], control_state: str | None = None) -> None: ... - def MCS(self, control_indices: int | Sequence[int], target_indices: int | Sequence[int], control_state: str | None = None) -> None: ... - def MCSdg(self, control_indices: int | Sequence[int], target_indices: int | Sequence[int], control_state: str | None = None) -> None: ... - def MCT(self, control_indices: int | Sequence[int], target_indices: int | Sequence[int], control_state: str | None = None) -> None: ... - def MCTdg(self, control_indices: int | Sequence[int], target_indices: int | Sequence[int], control_state: str | None = None) -> None: ... - def MCRX(self, angle: float, control_indices: int | Sequence[int], target_indices: int | Sequence[int], control_state: str | None = None) -> None: ... - def MCRY(self, angle: float, control_indices: int | Sequence[int], target_indices: int | Sequence[int], control_state: str | None = None) -> None: ... - def MCRZ(self, angle: float, control_indices: int | Sequence[int], target_indices: int | Sequence[int], control_state: str | None = None) -> None: ... - def MCPhase(self, angle: float, control_indices: int | Sequence[int], target_indices: int | Sequence[int], control_state: str | None = None) -> None: ... - def MCXPow( - self, - power: float, - global_shift: float, - control_indices: int | Sequence[int], - target_indices: int | Sequence[int], - control_state: str | None = None - ) -> None: ... - def MCYPow( - self, - power: float, - global_shift: float, - control_indices: int | Sequence[int], - target_indices: int | Sequence[int], - control_state: str | None = None - ) -> None: ... - def MCZPow( - self, - power: float, - global_shift: float, - control_indices: int | Sequence[int], - target_indices: int | Sequence[int], - control_state: str | None = None - ) -> None: ... - def MCRXX( - self, - angle: float, - control_indices: int | Sequence[int], - first_target_index: int, - second_target_index: int, - control_state: str | None = None - ) -> None: ... - def MCRYY( - self, - angle: float, - control_indices: int | Sequence[int], - first_target_index: int, - second_target_index: int, - control_state: str | None = None - ) -> None: ... - def MCRZZ( - self, - angle: float, - control_indices: int | Sequence[int], - first_target_index: int, - second_target_index: int, - control_state: str | None = None - ) -> None: ... - def MCU3( - self, - angles: Sequence[float], - control_indices: int | Sequence[int], - target_indices: int | Sequence[int], - control_state: str | None = None - ) -> None: ... - def MCSWAP( - self, - control_indices: int | Sequence[int], - first_target_index: int, - second_target_index: int, - control_state: str | None = None - ) -> None: ... - def PauliMultiplexor( - self, - angles: Sequence[float], - rotation_axis: Literal["X", "Y", "Z"], - control_indices: int | Sequence[int], - target_index: int - ) -> None: ... - def UCRX( - self, - angles: Sequence[float], - control_indices: int | Sequence[int], - target_index: int, - control_state: str | None = None - ) -> None: ... - def UCRY( - self, - angles: Sequence[float], - control_indices: int | Sequence[int], - target_index: int, - control_state: str | None = None - ) -> None: ... - def UCRZ( - self, - angles: Sequence[float], - control_indices: int | Sequence[int], - target_index: int, - control_state: str | None = None - ) -> None: ... - def _Diagonal(self, diagnoal: NDArray[np.complex128], qubit_indices: int | Sequence[int]) -> None: ... - def Diagonal(self, diagnoal: NDArray[np.complex128], qubit_indices: int | Sequence[int]) -> None: ... - def Multiplexor( - self, - single_qubit_gates: list[NDArray[np.complex128]], - control_indices: int | Sequence[int], - target_index: int, - up_to_diagonal: bool=False, - multiplexor_simplification: bool=True - ) -> None: ... - @abstractmethod - def GlobalPhase(self, angle: float) -> None: ... - def merge_global_phases(self) -> None: ... - def QFT( - self, - qubit_indices: int | Sequence[int], - do_swaps: bool=True, - approximation_degree: int=0, - inverse: bool=False - ) -> None: ... - def initialize(self, state: NDArray[np.complex128] | Bra | Ket, qubit_indices: int | Sequence[int]) -> None: ... - def unitary(self, unitary_matrix: NDArray[np.complex128] | Operator, qubit_indices: int | Sequence[int]) -> None: ... - def vertical_reverse(self) -> None: ... - @staticmethod - def _horizontal_reverse(circuit_log: list[dict[str, Any]], adjoint: bool=True) -> list[dict[str, Any]]: ... - def horizontal_reverse(self, adjoint: bool=True) -> None: ... - def add(self, circuit: Circuit, qubit_indices: int | Sequence[int]) -> None: ... - @abstractmethod - def measure(self, qubit_indices: int | Sequence[int]) -> None: ... - def measure_all(self) -> None: ... - @abstractmethod - def get_statevector(self, backend: Backend | None = None) -> NDArray[np.complex128]: ... - @abstractmethod - def get_counts(self, num_shots: int, backend: Backend | None = None) -> dict[str, int]: ... - def get_depth(self) -> int: ... - def get_width(self) -> int: ... - @abstractmethod - def get_unitary(self) -> NDArray[np.complex128]: ... - def get_instructions(self, include_measurements: bool=True) -> list[dict]: ... - def get_dag(self) -> DAGCircuit: ... - def get_global_phase(self) -> float: ... - def count_ops(self) -> dict[str, int]: ... - @abstractmethod - def reset_qubit(self, qubit_indices: int | Sequence[int]) -> None: ... - def remove_measurements(self) -> Circuit: ... - def decompose(self, reps: int=1, full: bool=False) -> Circuit: ... - def transpile(self, direct_transpile: bool=True, synthesis_method: UnitaryPreparation | None = None) -> None: ... - def compress(self, compression_percentage: float) -> None: ... - def change_mapping(self, qubit_indices: Sequence[int]) -> None: ... - def convert(self, circuit_framework: type[Circuit]) -> Circuit: ... - def control(self, num_controls: int) -> Circuit: ... - def update(self) -> None: ... - @abstractmethod - def to_qasm(self) -> str: ... - @staticmethod - def from_cirq(cirq_circuit: cirq.Circuit, output_framework: type[Circuit]) -> Circuit: ... - @staticmethod - def from_pennylane(pennylane_circuit: qml.QNode, output_framework: type[Circuit]) -> Circuit: ... - @staticmethod - def from_qiskit(qiskit_circuit: qiskit.QuantumCircuit, output_framework: type[Circuit]) -> Circuit: ... - @staticmethod - def from_tket(tket_circuit: pytket.Circuit, output_framework: type[Circuit]) -> Circuit: ... - @staticmethod - def from_qasm(qasm: str, output_framework: type[Circuit]) -> Circuit: ... - def copy(self) -> Circuit: ... - def reset(self) -> None: ... - @abstractmethod - def draw(self): ... - def plot_histogram(self, non_zeros_only: bool=False) -> plt.Figure: ... - def __getitem__(self, index: int | slice) -> Circuit: ... - def __setitem__(self, index: int | slice, gates: dict | list[dict] | Circuit) -> None: ... - def __eq__(self, other_circuit: object) -> bool: ... - def is_equivalent( - self, - other_circuit: Circuit, - check_unitary: bool=True, - check_dag: bool=False - ) -> bool: ... - def __len__(self) -> int: ... - def __str__(self) -> str: ... - def __repr__(self) -> str: ... - def generate_calls(self) -> str: ... - @classmethod - def __subclasscheck__(cls, C) -> bool: ... - @classmethod - def __subclasshook__(cls, C) -> bool | NotImplementedType: ... - @classmethod - def __instancecheck__(cls, C) -> bool: ... diff --git a/stubs/quick/circuit/circuit_utils.pyi b/stubs/quick/circuit/circuit_utils.pyi deleted file mode 100644 index d7a1ba0..0000000 --- a/stubs/quick/circuit/circuit_utils.pyi +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -from numpy.typing import NDArray - -__all__ = [ - "decompose_multiplexor_rotations", - "multiplexed_rz_angles", - "extract_uvr_matrices", - "extract_single_qubits_and_diagonal", - "multiplexor_diagonal_matrix", - "simplify", - "repetition_search", - "repetition_verify", - "flatten", - "reshape" -] - -Params = list[list[float] | float] | list[float] - - -def decompose_multiplexor_rotations( - angles: NDArray[np.float64], - start_index: int, - end_index: int, - reverse_decomposition: bool - ) -> NDArray[np.float64]: ... -def multiplexed_rz_angles(phi_1: float, phi_2: float) -> tuple[float, float]: ... -def extract_uvr_matrices( - a: NDArray[np.complex128], - b: NDArray[np.complex128] - ) -> tuple[NDArray[np.complex128], NDArray[np.complex128], NDArray[np.complex128]]: ... -def extract_single_qubits_and_diagonal( - single_qubit_gates: list[NDArray[np.complex128]], - num_qubits: int - ) -> tuple[list[NDArray[np.complex128]], NDArray[np.complex128]]: ... -def multiplexor_diagonal_matrix( - single_qubit_gates: list[NDArray[np.complex128]], - num_qubits: int, - simplified_controls: set[int] - ) -> NDArray[np.complex128]: ... -def simplify( - gate_list: list[NDArray[np.complex128]], - num_controls: int - ) -> tuple[set[int], list[NDArray[np.complex128]]]: ... -def repetition_search( - mux: list[NDArray[np.complex128]], - level: int - ) -> tuple[set[int], list[NDArray[np.complex128]]]: ... -def repetition_verify(base, d, mux, mux_copy) -> tuple[bool, list[NDArray[np.complex128]]]: ... -def flatten(array: Params) -> tuple[list[float], Params]: ... -def reshape(flattened: list[float], shape: Params) -> Params: ... diff --git a/stubs/quick/circuit/cirqcircuit.pyi b/stubs/quick/circuit/cirqcircuit.pyi deleted file mode 100644 index 8d5788c..0000000 --- a/stubs/quick/circuit/cirqcircuit.pyi +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -import cirq -from collections.abc import Sequence -from numpy.typing import NDArray -from quick.backend import Backend -from quick.circuit import Circuit -from typing import Callable, Literal - -__all__ = ["CirqCircuit"] - -GATES = Literal[ - "I", "X", "Y", "Z", "H", "S", "Sdg", "T", "Tdg", - "RX", "RY", "RZ", "Phase", "U3", - "MCX", "MCY", "MCZ", "MCH", "MCS", "MCSdg", "MCT", "MCTdg", - "MCRX", "MCRY", "MCRZ", "MCPhase", "MCU3" -] - -class CirqCircuit(Circuit): - qr: cirq.LineQubit - circuit: cirq.Circuit - def __init__(self, num_qubits: int) -> None: ... - @staticmethod - def _define_gate_mapping() -> dict[str, Callable]: ... - def _gate_mapping( - self, - gate: GATES, - target_indices: int | Sequence[int], - control_indices: int | Sequence[int] = [], - angles: Sequence[float] = (0, 0, 0) - ) -> None: ... - def GlobalPhase(self, angle: float) -> None: ... - measured: bool - def measure(self, qubit_indices: int | Sequence[int]) -> None: ... - def get_statevector(self, backend: Backend | None = None) -> NDArray[np.complex128]: ... - def get_counts(self, num_shots: int, backend: Backend | None = None) -> dict: ... - def get_unitary(self) -> NDArray[np.complex128]: ... - def reset_qubit(self, qubit_indices: int | Sequence[int]) -> None: ... - def to_qasm(self, qasm_version: int=2) -> str: ... - def draw(self) -> None: ... diff --git a/stubs/quick/circuit/dag/__init__.pyi b/stubs/quick/circuit/dag/__init__.pyi deleted file mode 100644 index 73b0397..0000000 --- a/stubs/quick/circuit/dag/__init__.pyi +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick.circuit.dag.dagcircuit import DAGCircuit as DAGCircuit -from quick.circuit.dag.dagnode import DAGNode as DAGNode - -__all__ = ['DAGNode', 'DAGCircuit'] diff --git a/stubs/quick/circuit/dag/dagcircuit.pyi b/stubs/quick/circuit/dag/dagcircuit.pyi deleted file mode 100644 index 91ad0ff..0000000 --- a/stubs/quick/circuit/dag/dagcircuit.pyi +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick.circuit.dag import DAGNode -__all__ = ["DAGCircuit"] - -class DAGCircuit: - num_qubits: int - qubits: dict[str, DAGNode] - stack: dict[str, list[DAGNode]] - def __init__(self, num_qubits: int) -> None: ... - def add_operation(self, operation: dict) -> None: ... - def get_depth(self) -> int: ... diff --git a/stubs/quick/circuit/dag/dagnode.pyi b/stubs/quick/circuit/dag/dagnode.pyi deleted file mode 100644 index 87029ab..0000000 --- a/stubs/quick/circuit/dag/dagnode.pyi +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from dataclasses import dataclass, field -from typing import Hashable - -__all__ = ["DAGNode"] - -@dataclass -class DAGNode: - name: Hashable = None - meta_name: Hashable = None - parents: set[DAGNode] = field(default_factory=set) - children: set[DAGNode] = field(default_factory=set) - def __post_init__(self) -> None: ... - def to(self, child: DAGNode) -> None: ... - @property - def depth(self) -> int: ... - def generate_paths(self) -> set[tuple[Hashable]]: ... - def __hash__(self) -> int: ... - def __lt__(self, other: object) -> bool: ... - def __eq__(self, other: object) -> bool: ... - def __init__(self, name=..., parents=..., children=...) -> None: ... diff --git a/stubs/quick/circuit/from_framework/__init__.pyi b/stubs/quick/circuit/from_framework/__init__.pyi deleted file mode 100644 index 47e0302..0000000 --- a/stubs/quick/circuit/from_framework/__init__.pyi +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick.circuit.from_framework.from_cirq import FromCirq as FromCirq -from quick.circuit.from_framework.from_framework import FromFramework as FromFramework -from quick.circuit.from_framework.from_qiskit import FromQiskit as FromQiskit -from quick.circuit.from_framework.from_tket import FromTKET as FromTKET - -__all__ = ["FromFramework", "FromCirq", "FromQiskit", "FromTKET"] diff --git a/stubs/quick/circuit/from_framework/from_cirq.pyi b/stubs/quick/circuit/from_framework/from_cirq.pyi deleted file mode 100644 index 5ef59cc..0000000 --- a/stubs/quick/circuit/from_framework/from_cirq.pyi +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import cirq -from quick.circuit import Circuit -from quick.circuit.from_framework import FromFramework -from typing import Callable - -__all__ = ["FromCirq"] - -class FromCirq(FromFramework): - gate_mapping: dict[str, Callable] - def __init__(self, output_framework: type[Circuit]) -> None: ... - def extract_params(self, circuit: cirq.Circuit) -> list[dict]: ... - def convert(self, circuit: cirq.Circuit) -> Circuit: ... diff --git a/stubs/quick/circuit/from_framework/from_framework.pyi b/stubs/quick/circuit/from_framework/from_framework.pyi deleted file mode 100644 index ed62ce0..0000000 --- a/stubs/quick/circuit/from_framework/from_framework.pyi +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import abc -from abc import ABC, abstractmethod -from quick.circuit import Circuit -from typing import Any - -__all__ = ["FromFramework"] - -class FromFramework(ABC, metaclass=abc.ABCMeta): - output_framework: type[Circuit] - def __init__(self, output_framework: type[Circuit]) -> None: ... - @abstractmethod - def convert(self, circuit: Any) -> Circuit: ... diff --git a/stubs/quick/circuit/from_framework/from_qiskit.pyi b/stubs/quick/circuit/from_framework/from_qiskit.pyi deleted file mode 100644 index b269e66..0000000 --- a/stubs/quick/circuit/from_framework/from_qiskit.pyi +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick.circuit import Circuit -from quick.circuit.from_framework import FromFramework -from qiskit import QuantumCircuit -from typing import Callable - -__all__ = ["FromQiskit"] - -class FromQiskit(FromFramework): - gate_mapping: dict[str, Callable] - skip_gates: list[str] - def __init__(self, output_framework: type[Circuit]) -> None: ... - def extract_params(self, circuit: QuantumCircuit) -> list[dict]: ... - def convert(self, circuit: QuantumCircuit) -> Circuit: ... diff --git a/stubs/quick/circuit/from_framework/from_tket.pyi b/stubs/quick/circuit/from_framework/from_tket.pyi deleted file mode 100644 index 5cb2884..0000000 --- a/stubs/quick/circuit/from_framework/from_tket.pyi +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from pytket import Circuit as TKCircuit -from quick.circuit import Circuit -from quick.circuit.from_framework import FromFramework -from typing import Callable - -__all__ = ["FromTKET"] - -class FromTKET(FromFramework): - gate_mapping: dict[str, Callable] - def __init__(self, output_framework: type[Circuit]) -> None: ... - def extract_params(self, circuit: TKCircuit) -> list[dict]: ... - def convert(self, circuit: TKCircuit) -> Circuit: ... diff --git a/stubs/quick/circuit/gate_matrix/__init__.pyi b/stubs/quick/circuit/gate_matrix/__init__.pyi deleted file mode 100644 index bec4c61..0000000 --- a/stubs/quick/circuit/gate_matrix/__init__.pyi +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick.circuit.gate_matrix.controlled_qubit_gates import ( - CH as CH, CS as CS, CT as CT, CX as CX, CY as CY, CZ as CZ -) -from quick.circuit.gate_matrix.gate import Gate as Gate -from quick.circuit.gate_matrix.single_qubit_gates import ( - Hadamard as Hadamard, - PauliX as PauliX, - PauliY as PauliY, - PauliZ as PauliZ, - Phase as Phase, - RX as RX, - RY as RY, - RZ as RZ, - S as S, - T as T, - U3 as U3 -) - -__all__ = [ - "Gate", - "PauliX", - "PauliY", - "PauliZ", - "Hadamard", - "S", - "T", - "RX", - "RY", - "RZ", - "U3", - "Phase", - "CX", - "CY", - "CZ", - "CH", - "CS", - "CT" -] diff --git a/stubs/quick/circuit/gate_matrix/controlled_qubit_gates.pyi b/stubs/quick/circuit/gate_matrix/controlled_qubit_gates.pyi deleted file mode 100644 index 112557c..0000000 --- a/stubs/quick/circuit/gate_matrix/controlled_qubit_gates.pyi +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick.circuit.gate_matrix import Gate - -__all__ = ["CX", "CY", "CZ", "CH", "CS", "CT"] - -CX: Gate -CY: Gate -CZ: Gate -CH: Gate -CS: Gate -CT: Gate diff --git a/stubs/quick/circuit/gate_matrix/gate.pyi b/stubs/quick/circuit/gate_matrix/gate.pyi deleted file mode 100644 index a095798..0000000 --- a/stubs/quick/circuit/gate_matrix/gate.pyi +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -from numpy.typing import NDArray -from typing import Literal - -__all__ = ["Gate"] - -class Gate: - name: str - matrix: NDArray[np.complex128] - num_qubits: int - ordering: str - def __init__(self, name: str, matrix: NDArray[np.complex128]) -> None: ... - def adjoint(self) -> NDArray[np.complex128]: ... - def control(self, num_control_qubits: int) -> Gate: ... - def change_mapping(self, ordering: Literal["MSB", "LSB"]) -> None: ... diff --git a/stubs/quick/circuit/gate_matrix/single_qubit_gates.pyi b/stubs/quick/circuit/gate_matrix/single_qubit_gates.pyi deleted file mode 100644 index 420c117..0000000 --- a/stubs/quick/circuit/gate_matrix/single_qubit_gates.pyi +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick.circuit.gate_matrix import Gate - -__all__ = [ - "PauliX", - "PauliY", - "PauliZ", - "Hadamard", - "S", - "T", - "RX", - "RY", - "RZ", - "U3", - "Phase" -] - -class PauliX(Gate): - def __init__(self) -> None: ... - -class PauliY(Gate): - def __init__(self) -> None: ... - -class PauliZ(Gate): - def __init__(self) -> None: ... - -class Hadamard(Gate): - def __init__(self) -> None: ... - -class S(Gate): - def __init__(self) -> None: ... - -class T(Gate): - def __init__(self) -> None: ... - -class RX(Gate): - def __init__(self, theta: float) -> None: ... - -class RY(Gate): - def __init__(self, theta: float) -> None: ... - -class RZ(Gate): - def __init__(self, theta: float) -> None: ... - -class U3(Gate): - def __init__(self, theta: float, phi: float, lam: float) -> None: ... - -class Phase(Gate): - def __init__(self, theta: float) -> None: ... diff --git a/stubs/quick/circuit/pennylanecircuit.pyi b/stubs/quick/circuit/pennylanecircuit.pyi deleted file mode 100644 index 4c05fa9..0000000 --- a/stubs/quick/circuit/pennylanecircuit.pyi +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from collections.abc import Sequence -import numpy as np -from numpy.typing import NDArray -import pennylane as qml # type: ignore -from quick.backend import Backend -from quick.circuit import Circuit -from typing import Callable, Literal - -__all__ = ["PennylaneCircuit"] - -GATES = Literal[ - "I", "X", "Y", "Z", "H", "S", "Sdg", "T", "Tdg", - "RX", "RY", "RZ", "Phase", "U3", - "MCX", "MCY", "MCZ", "MCH", "MCS", "MCSdg", "MCT", "MCTdg", - "MCRX", "MCRY", "MCRZ", "MCPhase", "MCU3" -] - -class PennylaneCircuit(Circuit): - device: qml.Device - circuit: list - def __init__(self, num_qubits: int) -> None: ... - @staticmethod - def _define_gate_mapping() -> dict[str, Callable]: ... - def _gate_mapping( - self, - gate: GATES, - target_indices: int | Sequence[int], - control_indices: int | Sequence[int] = [], - angles: Sequence[float] = (0, 0, 0) - ) -> None: ... - def GlobalPhase(self, angle: float) -> None: ... - def measure(self, qubit_indices: int | Sequence[int]) -> None: ... - def get_statevector(self, backend: Backend | None = None) -> NDArray[np.complex128]: ... - def get_counts(self, num_shots: int, backend: Backend | None = None) -> dict[str, int]: ... - def get_unitary(self) -> NDArray[np.complex128]: ... - def reset_qubit(self, qubit_indices: int | Sequence[int]) -> None: ... - def to_qasm(self, qasm_version: int=2) -> str: ... - def draw(self) -> None: ... diff --git a/stubs/quick/circuit/qiskitcircuit.pyi b/stubs/quick/circuit/qiskitcircuit.pyi deleted file mode 100644 index dc40fab..0000000 --- a/stubs/quick/circuit/qiskitcircuit.pyi +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from collections.abc import Sequence -import numpy as np -from numpy.typing import NDArray -from quick.backend import Backend -from quick.circuit import Circuit -from qiskit import QuantumCircuit # type: ignore -from typing import Callable, Literal - -__all__ = ["QiskitCircuit"] - -GATES = Literal[ - "I", "X", "Y", "Z", "H", "S", "Sdg", "T", "Tdg", - "RX", "RY", "RZ", "Phase", "U3", - "MCX", "MCY", "MCZ", "MCH", "MCS", "MCSdg", "MCT", "MCTdg", - "MCRX", "MCRY", "MCRZ", "MCPhase", "MCU3" -] - -class QiskitCircuit(Circuit): - circuit: QuantumCircuit - def __init__(self, num_qubits: int) -> None: ... - @staticmethod - def _define_gate_mapping() -> dict[str, Callable]: ... - def _gate_mapping( - self, - gate: GATES, - target_indices: int | Sequence[int], - control_indices: int | Sequence[int] = [], - angles: Sequence[float] = (0, 0, 0) - ) -> None: ... - def GlobalPhase(self, angle: float) -> None: ... - measured: bool - def measure(self, qubit_indices: int | Sequence[int]) -> None: ... - def get_statevector(self, backend: Backend | None = None) -> NDArray[np.complex128]: ... - def get_counts(self, num_shots: int, backend: Backend | None = None) -> dict[str, int]: ... - def get_unitary(self) -> NDArray[np.complex128]: ... - def reset_qubit(self, qubit_indices: int | Sequence[int]) -> None: ... - def to_qasm(self, qasm_version: int=2) -> str: ... - def draw(self) -> None: ... diff --git a/stubs/quick/circuit/quimbcircuit.pyi b/stubs/quick/circuit/quimbcircuit.pyi deleted file mode 100644 index 85ddab1..0000000 --- a/stubs/quick/circuit/quimbcircuit.pyi +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from collections.abc import Sequence -import numpy as np -from numpy.typing import NDArray -from quick.backend import Backend -from quick.circuit import Circuit -import quimb.tensor as qtn # type: ignore -from typing import Callable, Literal - -__all__ = ["QuimbCircuit"] - -GATES = Literal[ - "I", "X", "Y", "Z", "H", "S", "Sdg", "T", "Tdg", - "RX", "RY", "RZ", "Phase", "U3", - "MCX", "MCY", "MCZ", "MCH", "MCS", "MCSdg", "MCT", "MCTdg", - "MCRX", "MCRY", "MCRZ", "MCPhase", "MCU3" -] - -class QuimbCircuit(Circuit): - circuit: qtn.Circuit - def __init__(self, num_qubits: int) -> None: ... - @staticmethod - def _define_gate_mapping() -> dict[str, Callable]: ... - def _gate_mapping( - self, - gate: GATES, - target_indices: int | Sequence[int], - control_indices: int | Sequence[int] = [], - angles: Sequence[float] = (0, 0, 0) - ) -> None: ... - def GlobalPhase(self, angle: float) -> None: ... - measured: bool - def measure(self, qubit_indices: int | Sequence[int]) -> None: ... - def get_statevector(self, backend: Backend | None = None) -> NDArray[np.complex128]: ... - def get_counts(self, num_shots: int, backend: Backend | None = None) -> dict[str, int]: ... - def get_unitary(self) -> NDArray[np.complex128]: ... - def reset_qubit(self, qubit_indices: int | Sequence[int]) -> None: ... - def to_qasm(self, qasm_version: int=2) -> str: ... - def draw(self) -> None: ... diff --git a/stubs/quick/circuit/tketcircuit.pyi b/stubs/quick/circuit/tketcircuit.pyi deleted file mode 100644 index 829e83c..0000000 --- a/stubs/quick/circuit/tketcircuit.pyi +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from collections.abc import Sequence -import numpy as np -from numpy.typing import NDArray -import pytket -from quick.backend import Backend -from quick.circuit import Circuit -from typing import Callable, Literal - -__all__ = ["TKETCircuit"] - -GATES = Literal[ - "I", "X", "Y", "Z", "H", "S", "Sdg", "T", "Tdg", - "RX", "RY", "RZ", "Phase", "U3", - "MCX", "MCY", "MCZ", "MCH", "MCS", "MCSdg", "MCT", "MCTdg", - "MCRX", "MCRY", "MCRZ", "MCPhase", "MCU3" -] - -class TKETCircuit(Circuit): - circuit: pytket.Circuit - def __init__(self, num_qubits: int) -> None: ... - @staticmethod - def _define_gate_mapping() -> dict[str, Callable]: ... - def _gate_mapping( - self, - gate: GATES, - target_indices: int | Sequence[int], - control_indices: int | Sequence[int] = [], - angles: Sequence[float] = (0, 0, 0) - ) -> None: ... - def GlobalPhase(self, angle: float) -> None: ... - measured: bool - def measure(self, qubit_indices: int | Sequence[int]) -> None: ... - def get_statevector(self, backend: Backend | None = None) -> NDArray[np.complex128]: ... - def get_counts(self, num_shots: int, backend: Backend | None = None) -> dict[str, int]: ... - def get_unitary(self) -> NDArray[np.complex128]: ... - def reset_qubit(self, qubit_indices: int | Sequence[int]) -> None: ... - def to_qasm(self, qasm_version: int=2) -> str: ... - def draw(self) -> None: ... diff --git a/stubs/quick/compiler/__init__.pyi b/stubs/quick/compiler/__init__.pyi deleted file mode 100644 index d2eeabb..0000000 --- a/stubs/quick/compiler/__init__.pyi +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick.compiler.compiler import Compiler as Compiler - -__all__ = ["Compiler"] diff --git a/stubs/quick/compiler/compiler.pyi b/stubs/quick/compiler/compiler.pyi deleted file mode 100644 index 287ab55..0000000 --- a/stubs/quick/compiler/compiler.pyi +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from collections.abc import Sequence -import numpy as np -from numpy.typing import NDArray -from quick.circuit import Circuit -from quick.optimizer import Optimizer -from quick.primitives import Bra, Ket, Operator -from quick.synthesis.statepreparation import StatePreparation -from quick.synthesis.unitarypreparation import UnitaryPreparation -from typing import TypeAlias - -__all__ = ["Compiler"] - -PRIMITIVE: TypeAlias = Bra | Ket | Operator | NDArray[np.complex128] -PRIMITIVES: TypeAlias = list[tuple[PRIMITIVE, Sequence[int]]] - - -class Compiler: - circuit_framework: type[Circuit] - state_prep: type[StatePreparation] - unitary_prep: type[UnitaryPreparation] - optimizer: Optimizer - def __init__(self, circuit_framework: Circuit, state_prep: type[StatePreparation] = ..., unitary_prep: type[UnitaryPreparation] = ..., mlir: bool = True) -> None: ... - def state_preparation(self, state: NDArray[np.complex128] | Bra | Ket) -> Circuit: ... - def unitary_preparation(self, unitary: NDArray[np.complex128] | Operator) -> Circuit: ... - def optimize(self, circuit: Circuit) -> Circuit: ... - @staticmethod - def _check_primitive(primitive: PRIMITIVE) -> None: ... - @staticmethod - def _check_primitive_qubits(primitive: PRIMITIVE, qubit_indices: Sequence[int]) -> None: ... - @staticmethod - def _check_primitives(primitives: PRIMITIVES) -> None: ... - def _compile_primitive(self, primitive: PRIMITIVE) -> Circuit: ... - def compile(self, primitives: PRIMITIVE | PRIMITIVES) -> Circuit: ... diff --git a/stubs/quick/metrics/__init__.pyi b/stubs/quick/metrics/__init__.pyi deleted file mode 100644 index dbc1f3d..0000000 --- a/stubs/quick/metrics/__init__.pyi +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick.metrics.metrics import ( - calculate_entanglement_range as calculate_entanglement_range, - calculate_shannon_entropy as calculate_shannon_entropy, - calculate_entanglement_entropy as calculate_entanglement_entropy, -) - -__all__ = [ - "get_entanglements", - "calculate_shannon_entropy", - "calculate_entanglement_entropy" -] diff --git a/stubs/quick/metrics/metrics.pyi b/stubs/quick/metrics/metrics.pyi deleted file mode 100644 index 90a9a2f..0000000 --- a/stubs/quick/metrics/metrics.pyi +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -from numpy.typing import NDArray - -__all__ = [ - "get_entanglements", - "calculate_shannon_entropy", - "calculate_entanglement_entropy" -] - -def calculate_entanglement_range(statevector: NDArray[np.complex128]) -> list[tuple[int, int]]: ... -def calculate_shannon_entropy(statevector: NDArray[np.complex128]) -> float: ... -def calculate_entanglement_entropy(statevector: NDArray[np.complex128]) -> float: ... diff --git a/stubs/quick/optimizer/__init__.pyi b/stubs/quick/optimizer/__init__.pyi deleted file mode 100644 index bfe11af..0000000 --- a/stubs/quick/optimizer/__init__.pyi +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick.optimizer.optimizer import Optimizer as Optimizer -from quick.optimizer.tket2optimizer import TKET2Optimizer as TKET2Optimizer - -__all__ = ["Optimizer", "TKET2Optimizer"] diff --git a/stubs/quick/optimizer/optimizer.pyi b/stubs/quick/optimizer/optimizer.pyi deleted file mode 100644 index 28a150d..0000000 --- a/stubs/quick/optimizer/optimizer.pyi +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import abc -from abc import ABC, abstractmethod -from quick.circuit import Circuit - -__all__ = ["Optimizer"] - -class Optimizer(ABC, metaclass=abc.ABCMeta): - @abstractmethod - def optimize(self, circuit: Circuit) -> Circuit: ... diff --git a/stubs/quick/optimizer/tket2optimizer.pyi b/stubs/quick/optimizer/tket2optimizer.pyi deleted file mode 100644 index bfaaf9f..0000000 --- a/stubs/quick/optimizer/tket2optimizer.pyi +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick.circuit import Circuit -from quick.optimizer.optimizer import Optimizer - -__all__ = ["TKET2Optimizer"] - -class TKET2Optimizer(Optimizer): - def optimize(self, circuit: Circuit) -> Circuit: ... diff --git a/stubs/quick/predicates/__init__.pyi b/stubs/quick/predicates/__init__.pyi deleted file mode 100644 index aa5753f..0000000 --- a/stubs/quick/predicates/__init__.pyi +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick.predicates.predicates import ( - is_diagonal_matrix as is_diagonal_matrix, - is_hermitian_matrix as is_hermitian_matrix, - is_identity_matrix as is_identity_matrix, - is_isometry as is_isometry, - is_positive_semidefinite_matrix as is_positive_semidefinite_matrix, - is_square_matrix as is_square_matrix, - is_symmetric_matrix as is_symmetric_matrix, - is_unitary_matrix as is_unitary_matrix -) - -__all__ = [ - "is_square_matrix", - "is_diagonal_matrix", - "is_symmetric_matrix", - "is_identity_matrix", - "is_unitary_matrix", - "is_hermitian_matrix", - "is_positive_semidefinite_matrix", - "is_isometry" -] diff --git a/stubs/quick/predicates/predicates.pyi b/stubs/quick/predicates/predicates.pyi deleted file mode 100644 index c39a4d7..0000000 --- a/stubs/quick/predicates/predicates.pyi +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -from numpy.typing import NDArray - -__all__ = [ - "is_square_matrix", - "is_diagonal_matrix", - "is_symmetric_matrix", - "is_identity_matrix", - "is_unitary_matrix", - "is_hermitian_matrix", - "is_positive_semidefinite_matrix", - "is_isometry" -] - -def is_square_matrix(matrix: NDArray[np.complex128]) -> bool: ... -def is_diagonal_matrix(matrix: NDArray[np.complex128], rtol: float = ..., atol: float = ...) -> bool: ... -def is_symmetric_matrix(matrix: NDArray[np.complex128], rtol: float = ..., atol: float = ...) -> bool: ... -def is_identity_matrix(matrix: NDArray[np.complex128], ignore_phase: bool = False, rtol: float = ..., atol: float = ...) -> bool: ... -def is_unitary_matrix(matrix: NDArray[np.complex128], rtol: float = ..., atol: float = ...) -> bool: ... -def is_hermitian_matrix(matrix: NDArray[np.complex128], rtol: float = ..., atol: float = ...) -> bool: ... -def is_positive_semidefinite_matrix(matrix: NDArray[np.complex128], rtol: float = ..., atol: float = ...) -> bool: ... -def is_isometry(matrix: NDArray[np.complex128], rtol: float = ..., atol: float = ...) -> bool: ... diff --git a/stubs/quick/primitives/__init__.pyi b/stubs/quick/primitives/__init__.pyi deleted file mode 100644 index 6974f65..0000000 --- a/stubs/quick/primitives/__init__.pyi +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick.primitives.bra import Bra as Bra -from quick.primitives.ket import Ket as Ket -from quick.primitives.operator import Operator as Operator - -__all__ = ["Bra", "Ket", "Operator"] diff --git a/stubs/quick/primitives/bra.pyi b/stubs/quick/primitives/bra.pyi deleted file mode 100644 index d0308d5..0000000 --- a/stubs/quick/primitives/bra.pyi +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -import quick.primitives.ket as ket -import quick.primitives.operator as operator -from numpy.typing import NDArray -from typing import overload, SupportsFloat, TypeAlias - -__all__ = ["Bra"] - -Scalar: TypeAlias = SupportsFloat | complex - -class Bra: - label: str - norm_scale: np.float64 - data: NDArray[np.complex128] - shape: tuple[int, int] - num_qubits: int - def __init__(self, data: NDArray[np.complex128], label: str | None = None) -> None: ... - @staticmethod - def check_normalization(data: NDArray[np.number]) -> bool: ... - normalized: bool - def is_normalized(self) -> None: ... - @staticmethod - def normalize_data(data: NDArray[np.number], norm_scale: np.float64) -> NDArray[np.number]: ... - def normalize(self) -> None: ... - @staticmethod - def check_padding(data: NDArray[np.number]) -> bool: ... - padded: bool - def is_padded(self) -> None: ... - @staticmethod - def pad_data(data: NDArray[np.number], target_size: int) -> tuple[NDArray[np.number], tuple[int, ...]]: ... - def pad(self) -> None: ... - def to_quantumstate(self) -> None: ... - def to_bra(self, data: NDArray[np.number]) -> None: ... - def to_ket(self) -> ket.Ket: ... - def compress(self, compression_percentage: float) -> None: ... - def __add__(self, other: Bra) -> Bra: ... - @overload - def __mul__(self, other: Scalar) -> Bra: ... - @overload - def __mul__(self, other: ket.Ket) -> Scalar: ... - @overload - def __mul__(self, other: operator.Operator) -> Bra: ... - def __rmul__(self, other: Scalar) -> Bra: ... diff --git a/stubs/quick/primitives/ket.pyi b/stubs/quick/primitives/ket.pyi deleted file mode 100644 index 5c76e3e..0000000 --- a/stubs/quick/primitives/ket.pyi +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -import quick.primitives.bra as bra -import quick.primitives.operator as operator -from numpy.typing import NDArray -from typing import overload, SupportsFloat, TypeAlias - -__all__ = ["Ket"] - -Scalar: TypeAlias = SupportsFloat | complex - -class Ket: - label: str - norm_scale: np.float64 - data: NDArray[np.complex128] - shape: tuple[int, int] - num_qubits: IndentationError - def __init__(self, data: NDArray[np.complex128], label: str | None = None) -> None: ... - @staticmethod - def check_normalization(data: NDArray[np.number]) -> bool: ... - normalized: bool - def is_normalized(self) -> None: ... - @staticmethod - def normalize_data(data: NDArray[np.number], norm_scale: np.float64) -> NDArray[np.number]: ... - def normalize(self) -> None: ... - @staticmethod - def check_padding(data: NDArray[np.number]) -> bool: ... - padded: bool - def is_padded(self) -> None: ... - @staticmethod - def pad_data(data: NDArray[np.number], target_size: int) -> tuple[NDArray[np.number], tuple[int, ...]]: ... - def pad(self) -> None: ... - def to_quantumstate(self) -> None: ... - def to_ket(self, data: NDArray[np.number]) -> None: ... - def to_bra(self) -> bra.Bra: ... - def compress(self, compression_percentage: float) -> None: ... - def __add__(self, other: Ket) -> Ket: ... - @overload - def __mul__(self, other: Scalar) -> Ket: ... - @overload - def __mul__(self, other: bra.Bra) -> operator.Operator: ... - def __rmul__(self, other: Scalar) -> Ket: ... diff --git a/stubs/quick/primitives/operator.pyi b/stubs/quick/primitives/operator.pyi deleted file mode 100644 index 3c2888c..0000000 --- a/stubs/quick/primitives/operator.pyi +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -import quick.primitives.ket as ket -from numpy.typing import NDArray -from typing import overload, SupportsFloat, TypeAlias - -__all__ = ["Operator"] - -Scalar: TypeAlias = SupportsFloat | complex - -class Operator: - label: str - data: NDArray[np.complex128] - shape: tuple[int, int] - num_qubits: int - def __init__(self, data: NDArray[np.complex128], label: str | None = None) -> None: ... - @staticmethod - def is_unitary(data: NDArray[np.number]) -> None: ... - @overload - def __mul__(self, other: Scalar) -> Operator: ... - @overload - def __mul__(self, other: ket.Ket) -> ket.Ket: ... - @overload - def __mul__(self, other: Operator) -> Operator: ... - def __rmul__(self, other: Scalar) -> Operator: ... diff --git a/stubs/quick/random/__init__.pyi b/stubs/quick/random/__init__.pyi deleted file mode 100644 index 0a1d0f6..0000000 --- a/stubs/quick/random/__init__.pyi +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick.random.random import generate_random_state as generate_random_state, generate_random_unitary as generate_random_unitary - -__all__ = ["generate_random_state", "generate_random_unitary"] diff --git a/stubs/quick/random/random.pyi b/stubs/quick/random/random.pyi deleted file mode 100644 index 9fccd96..0000000 --- a/stubs/quick/random/random.pyi +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -from numpy.typing import NDArray - -__all__ = ["generate_random_state", "generate_random_unitary"] - -def generate_random_state(num_qubits: int) -> NDArray[np.complex128]: ... -def generate_random_unitary(num_qubits: int) -> NDArray[np.complex128]: ... diff --git a/stubs/quick/synthesis/__init__.pyi b/stubs/quick/synthesis/__init__.pyi deleted file mode 100644 index 757f658..0000000 --- a/stubs/quick/synthesis/__init__.pyi +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick.synthesis import gate_decompositions as gate_decompositions -from quick.synthesis import statepreparation as statepreparation -from quick.synthesis import unitarypreparation as unitarypreparation - -__all__ = ["gate_decompositions", "statepreparation", "unitarypreparation"] diff --git a/stubs/quick/synthesis/gate_decompositions/__init__.pyi b/stubs/quick/synthesis/gate_decompositions/__init__.pyi deleted file mode 100644 index 5b5a79f..0000000 --- a/stubs/quick/synthesis/gate_decompositions/__init__.pyi +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -__all__ = [ - "OneQubitDecomposition", - "TwoQubitDecomposition" -] - -from quick.synthesis.gate_decompositions.one_qubit_decomposition import OneQubitDecomposition as OneQubitDecomposition -from quick.synthesis.gate_decompositions.two_qubit_decomposition.two_qubit_decomposition import TwoQubitDecomposition as TwoQubitDecomposition \ No newline at end of file diff --git a/stubs/quick/synthesis/gate_decompositions/multi_controlled_decomposition/__init__.pyi b/stubs/quick/synthesis/gate_decompositions/multi_controlled_decomposition/__init__.pyi deleted file mode 100644 index 562c4da..0000000 --- a/stubs/quick/synthesis/gate_decompositions/multi_controlled_decomposition/__init__.pyi +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick.synthesis.gate_decompositions.multi_controlled_decomposition.mcsu2_real_diagonal import MCRX as MCRX, MCRY as MCRY, MCRZ as MCRZ - -__all__ = ["MCRX", "MCRY", "MCRZ"] diff --git a/stubs/quick/synthesis/gate_decompositions/multi_controlled_decomposition/mcsu2_real_diagonal.pyi b/stubs/quick/synthesis/gate_decompositions/multi_controlled_decomposition/mcsu2_real_diagonal.pyi deleted file mode 100644 index 099750c..0000000 --- a/stubs/quick/synthesis/gate_decompositions/multi_controlled_decomposition/mcsu2_real_diagonal.pyi +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick.circuit import Circuit - -__all__ = ["MCRX", "MCRY", "MCRZ"] - -def MCRX(circuit: Circuit, theta: float, control_indices: list[int], target_index: int) -> None: ... -def MCRY(circuit: Circuit, theta: float, control_indices: list[int], target_index: int) -> None: ... -def MCRZ(circuit: Circuit, theta: float, control_indices: list[int], target_index: int) -> None: ... diff --git a/stubs/quick/synthesis/gate_decompositions/multi_controlled_decomposition/mcx_utils.pyi b/stubs/quick/synthesis/gate_decompositions/multi_controlled_decomposition/mcx_utils.pyi deleted file mode 100644 index f739837..0000000 --- a/stubs/quick/synthesis/gate_decompositions/multi_controlled_decomposition/mcx_utils.pyi +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick.circuit import Circuit - -__all__ = ["CCX", "RCCX", "C3X", "C3SX", "RC3X", "C4X"] - -def CCX(circuit: Circuit, control_indices: list[int], target_index: int) -> None: ... -def RCCX(circuit: Circuit, control_indices: list[int], target_index: int) -> None: ... -def C3X(circuit: Circuit, control_indices: list[int], target_index: int) -> None: ... -def C3SX(circuit: Circuit, control_indices: list[int], target_index: int) -> None: ... -def RC3X(circuit: Circuit, control_indices: list[int], target_index: int) -> None: ... -def C4X(circuit: Circuit, control_indices: list[int], target_index: int) -> None: ... diff --git a/stubs/quick/synthesis/gate_decompositions/multi_controlled_decomposition/mcx_vchain.pyi b/stubs/quick/synthesis/gate_decompositions/multi_controlled_decomposition/mcx_vchain.pyi deleted file mode 100644 index f61b706..0000000 --- a/stubs/quick/synthesis/gate_decompositions/multi_controlled_decomposition/mcx_vchain.pyi +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick.circuit import Circuit - -__all__ = ["MCXVChain"] - -class MCXVChain: - @staticmethod - def get_num_ancillas(num_controls) -> int: ... - def define_decomposition(self, num_controls: int, output_framework: type[Circuit]) -> Circuit: ... - def apply_decomposition(self, circuit: Circuit, control_indices: int | list[int], target_index: int, ancilla_indices: int | list[int] | None = None) -> None: ... diff --git a/stubs/quick/synthesis/gate_decompositions/one_qubit_decomposition.pyi b/stubs/quick/synthesis/gate_decompositions/one_qubit_decomposition.pyi deleted file mode 100644 index a34034b..0000000 --- a/stubs/quick/synthesis/gate_decompositions/one_qubit_decomposition.pyi +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -from _typeshed import Incomplete -from collections.abc import Sequence -from numpy.typing import NDArray -from quick.circuit import Circuit -from quick.primitives import Operator -from quick.synthesis.unitarypreparation import UnitaryPreparation -from typing import Literal - -__all__ = ["OneQubitDecomposition"] - -class OneQubitDecomposition(UnitaryPreparation): - basis: Incomplete - def __init__(self, circuit_framework: type[Circuit], basis: Literal["zyz", "u3"] = "u3") -> None: ... - @staticmethod - def params_zyz(U: NDArray[np.complex128]) -> tuple[float, tuple[float, float, float]]: ... - @staticmethod - def params_u3(U: NDArray[np.complex128]) -> tuple[float, tuple[float, float, float]]: ... - def apply_unitary(self, circuit: Circuit, unitary: NDArray[np.complex128] | Operator, qubit_indices: int | Sequence[int]) -> Circuit: ... diff --git a/stubs/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.pyi b/stubs/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.pyi deleted file mode 100644 index c04b026..0000000 --- a/stubs/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.pyi +++ /dev/null @@ -1,77 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -from collections.abc import Sequence -from numpy.typing import NDArray -from quick.circuit import Circuit -from quick.primitives import Operator -from quick.synthesis.gate_decompositions import OneQubitDecomposition -from quick.synthesis.gate_decompositions.two_qubit_decomposition.weyl import TwoQubitWeylDecomposition -from quick.synthesis.unitarypreparation import UnitaryPreparation - -__all__ = ["TwoQubitDecomposition"] - -class TwoQubitDecomposition(UnitaryPreparation): - one_qubit_decomposition: OneQubitDecomposition - def __init__(self, output_framework: type[Circuit]) -> None: ... - @staticmethod - def u4_to_su4(u4: NDArray[np.complex128]) -> tuple[NDArray[np.complex128], float]: ... - @staticmethod - def traces(target: TwoQubitWeylDecomposition) -> list[complex]: ... - @staticmethod - def real_trace_transform(U: NDArray[np.complex128]) -> NDArray[np.complex128]: ... - @staticmethod - def trace_to_fidelity(trace: complex) -> float: ... - @staticmethod - def _decomp0(weyl_decomposition: TwoQubitWeylDecomposition) -> tuple[NDArray[np.complex128], NDArray[np.complex128]]: ... - @staticmethod - def _decomp1(weyl_decomposition: TwoQubitWeylDecomposition) -> tuple[ - NDArray[np.complex128], - NDArray[np.complex128], - NDArray[np.complex128], - NDArray[np.complex128] - ]: ... - @staticmethod - def _decomp2_supercontrolled(weyl_decomposition: TwoQubitWeylDecomposition) -> tuple[ - NDArray[np.complex128], - NDArray[np.complex128], - NDArray[np.complex128], - NDArray[np.complex128], - NDArray[np.complex128], - NDArray[np.complex128] - ]: ... - @staticmethod - def _decomp3_supercontrolled(weyl_decomposition: TwoQubitWeylDecomposition) -> tuple[ - NDArray[np.complex128], - NDArray[np.complex128], - NDArray[np.complex128], - NDArray[np.complex128], - NDArray[np.complex128], - NDArray[np.complex128], - NDArray[np.complex128], - NDArray[np.complex128] - ]: ... - def apply_unitary( - self, - circuit: Circuit, - unitary: NDArray[np.complex128] | Operator, - qubit_indices: int | Sequence[int] - ) -> Circuit: ... - def apply_unitary_up_to_diagonal( - self, - circuit: Circuit, - unitary: NDArray[np.complex128] | Operator, - qubit_indices: int | Sequence[int] - ) -> tuple[Circuit, NDArray[np.complex128]]: ... diff --git a/stubs/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.pyi b/stubs/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.pyi deleted file mode 100644 index 3a11871..0000000 --- a/stubs/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.pyi +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -from numpy.typing import NDArray - -__all__ = [ - "transform_to_magic_basis", - "weyl_coordinates", - "partition_eigenvalues", - "remove_global_phase", - "diagonalize_unitary_complex_symmetric", - "decompose_two_qubit_product_gate", - "TwoQubitWeylDecomposition" -] - -def transform_to_magic_basis(U: NDArray[np.complex128], reverse: bool = False) -> NDArray[np.complex128]: ... -def weyl_coordinates(U: NDArray[np.complex128]) -> NDArray[np.float64]: ... -def partition_eigenvalues(eigenvalues: NDArray[np.complex128], atol: float = 1e-13) -> list[list[int]]: ... -def remove_global_phase(vector: NDArray[np.complex128], index: int | None = None) -> NDArray[np.complex128]: ... -def diagonalize_unitary_complex_symmetric(U, atol: float = 1e-13) -> tuple[NDArray[np.complex128], NDArray[np.complex128]]: ... -def decompose_two_qubit_product_gate(special_unitary_matrix: NDArray[np.complex128]) -> tuple[NDArray[np.complex128], NDArray[np.complex128], float]: ... - -class TwoQubitWeylDecomposition: - def __init__(self, unitary_matrix: NDArray[np.complex128]) -> None: ... - @staticmethod - def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ - np.float64, - np.float64, - np.float64, - NDArray[np.complex128], - NDArray[np.complex128], - NDArray[np.complex128], - NDArray[np.complex128], - float - ]: ... diff --git a/stubs/quick/synthesis/statepreparation/__init__.pyi b/stubs/quick/synthesis/statepreparation/__init__.pyi deleted file mode 100644 index 35719fe..0000000 --- a/stubs/quick/synthesis/statepreparation/__init__.pyi +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick.synthesis.statepreparation.statepreparation import StatePreparation as StatePreparation -from quick.synthesis.statepreparation.shende import Shende as Shende -from quick.synthesis.statepreparation.mottonen import Mottonen as Mottonen - -__all__ = ["StatePreparation", "Shende", "Mottonen"] diff --git a/stubs/quick/synthesis/statepreparation/isometry.pyi b/stubs/quick/synthesis/statepreparation/isometry.pyi deleted file mode 100644 index 1c3073e..0000000 --- a/stubs/quick/synthesis/statepreparation/isometry.pyi +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from collections.abc import Sequence -import numpy as np -from numpy.typing import NDArray -from quick.circuit import Circuit -from quick.primitives import Bra, Ket -from quick.synthesis.statepreparation import StatePreparation -from typing import Literal - -__all__ = ["Isometry"] - -class Isometry(StatePreparation): - def apply_state( - self, - circuit: Circuit, - state: NDArray[np.complex128] | Bra | Ket, - qubit_indices: int | Sequence[int], - compression_percentage: float=0.0, - index_type: Literal["row", "snake"]="row" - ) -> Circuit: ... diff --git a/stubs/quick/synthesis/statepreparation/mottonen.pyi b/stubs/quick/synthesis/statepreparation/mottonen.pyi deleted file mode 100644 index b102b84..0000000 --- a/stubs/quick/synthesis/statepreparation/mottonen.pyi +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from collections.abc import Sequence -import numpy as np -from numpy.typing import NDArray -from quick.circuit import Circuit -from quick.primitives import Bra, Ket -from quick.synthesis.statepreparation import StatePreparation -from typing import Literal - -__all__ = ["Mottonen"] - -class Mottonen(StatePreparation): - def apply_state( - self, - circuit: Circuit, - state: NDArray[np.complex128] | Bra | Ket, - qubit_indices: int | Sequence[int], - compression_percentage: float=0.0, - index_type: Literal["row", "snake"]="row" - ) -> Circuit: ... diff --git a/stubs/quick/synthesis/statepreparation/shende.pyi b/stubs/quick/synthesis/statepreparation/shende.pyi deleted file mode 100644 index 5659b0a..0000000 --- a/stubs/quick/synthesis/statepreparation/shende.pyi +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from collections.abc import Sequence -import numpy as np -from numpy.typing import NDArray -from quick.circuit import Circuit -from quick.primitives import Bra, Ket -from quick.synthesis.statepreparation import StatePreparation -from typing import Literal - -__all__ = ["Shende"] - -class Shende(StatePreparation): - def apply_state( - self, - circuit: Circuit, - state: NDArray[np.complex128] | Bra | Ket, - qubit_indices: int | Sequence[int], - compression_percentage: float=0.0, - index_type: Literal["row", "snake"]="row" - ) -> Circuit: ... diff --git a/stubs/quick/synthesis/statepreparation/statepreparation.pyi b/stubs/quick/synthesis/statepreparation/statepreparation.pyi deleted file mode 100644 index 6999418..0000000 --- a/stubs/quick/synthesis/statepreparation/statepreparation.pyi +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import abc -from abc import ABC, abstractmethod -from collections.abc import Sequence -import numpy as np -from numpy.typing import NDArray -from quick.circuit import Circuit -from quick.primitives import Bra, Ket -from typing import Literal - -__all__ = ["StatePreparation"] - -class StatePreparation(ABC, metaclass=abc.ABCMeta): - output_framework: type[Circuit] - def __init__(self, output_framework: type[Circuit]) -> None: ... - def prepare_state(self, state: NDArray[np.complex128] | Bra | Ket, compression_percentage: float=0.0, index_type: str="row") -> Circuit: ... - @abstractmethod - def apply_state( - self, - circuit: Circuit, - state: NDArray[np.complex128] | Bra | Ket, - qubit_indices: int | Sequence[int], - compression_percentage: float=0.0, - index_type: Literal["row", "snake"]="row" - ) -> Circuit: ... \ No newline at end of file diff --git a/stubs/quick/synthesis/statepreparation/statepreparation_utils.pyi b/stubs/quick/synthesis/statepreparation/statepreparation_utils.pyi deleted file mode 100644 index 106d16b..0000000 --- a/stubs/quick/synthesis/statepreparation/statepreparation_utils.pyi +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from collections.abc import Sequence -import numpy as np -from numpy.typing import NDArray as NDArray - -__all__ = [ - "gray_code", - "compute_alpha_y", - "compute_alpha_z", - "compute_m", - "compute_control_indices", - "bloch_angles", - "rotations_to_disentangle", - "k_s", - "a", - "b", - "reverse_qubit_state", - "disentangling_single_qubit_gates", - "apply_ucg", - "apply_diagonal_gate", - "apply_diagonal_gate_to_diag", - "apply_multi_controlled_gate", - "ucg_is_identity_up_to_global_phase", - "merge_ucgate_and_diag", - "construct_basis_states", - "diag_is_identity_up_to_global_phase", - "get_binary_rep_as_list", - "get_qubits_by_label" -] - -def gray_code(index: int) -> int: ... -def compute_alpha_y(magnitude: NDArray[np.float64], k: int, j: int) -> float: ... -def compute_alpha_z(phase: NDArray[np.float64], k: int, j: int) -> float: ... -def compute_m(k: int) -> NDArray[np.float64]: ... -def compute_control_indices(index: int) -> list[int]: ... -def bloch_angles(pair_of_complex: Sequence[complex]) -> tuple: ... -def rotations_to_disentangle(local_param: NDArray[np.complex128]) -> tuple: ... -def k_s(k: int, s: int) -> int: ... -def a(k: int, s: int) -> int: ... -def b(k: int, s: int) -> int: ... -def reverse_qubit_state(state: NDArray[np.complex128], basis_state: int) -> NDArray[np.complex128]: ... -def find_squs_for_disentangling(v: NDArray[np.complex128], k: int, s: int, n: int) -> list[NDArray[np.complex128]]: ... -def apply_ucg(m: NDArray[np.complex128], k: int, single_qubit_gates: list[NDArray[np.complex128]]) -> NDArray[np.complex128]: ... -def apply_diagonal_gate( - m: NDArray[np.complex128], - action_qubit_labels: list[int], - diagonal: NDArray[np.complex128] - ) -> NDArray[np.complex128]: - ... -def apply_diagonal_gate_to_diag( - m_diagonal: NDArray[np.complex128], - action_qubit_labels: list[int], - diagonal: NDArray[np.complex128], - num_qubits: int - ) -> NDArray[np.complex128]: - ... -def apply_multi_controlled_gate( - m: NDArray[np.complex128], - control_labels: list[int], - target_label: int, - gate: NDArray[np.complex128] - ) -> NDArray[np.complex128]: - ... -def ucg_is_identity_up_to_global_phase(single_qubit_gates: list[NDArray[np.complex128]]) -> bool: ... -def merge_ucgate_and_diag( - single_qubit_gates: list[NDArray[np.complex128]], - diagonal: NDArray[np.complex128] - ) -> list[NDArray[np.complex128]]: - ... -def construct_basis_states( - state_free: tuple[int, ...], - control_set: set[int], - target_label: int - ) -> tuple[int, int]: - ... -def diag_is_identity_up_to_global_phase(diagonal: NDArray[np.complex128]) -> bool: ... -def get_binary_rep_as_list(n: int, num_digits: int) -> list[int]: ... -def get_qubits_by_label(labels: list[int], qubits: list[int], num_qubits: int) -> list[int]: ... \ No newline at end of file diff --git a/stubs/quick/synthesis/unitarypreparation/__init__.pyi b/stubs/quick/synthesis/unitarypreparation/__init__.pyi deleted file mode 100644 index 53b3d96..0000000 --- a/stubs/quick/synthesis/unitarypreparation/__init__.pyi +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from quick.synthesis.unitarypreparation.unitarypreparation import UnitaryPreparation as UnitaryPreparation -from quick.synthesis.unitarypreparation.diffusion import Diffusion as Diffusion -from quick.synthesis.unitarypreparation.qiskit_unitary_transpiler import QiskitUnitaryTranspiler as QiskitUnitaryTranspiler - -__all__ = ["UnitaryPreparation", "Diffusion", "QiskitUnitaryTranspiler"] \ No newline at end of file diff --git a/stubs/quick/synthesis/unitarypreparation/diffusion.pyi b/stubs/quick/synthesis/unitarypreparation/diffusion.pyi deleted file mode 100644 index 5166d18..0000000 --- a/stubs/quick/synthesis/unitarypreparation/diffusion.pyi +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -from numpy.typing import NDArray -from quick.circuit import Circuit -from quick.primitives.operator import Operator -from quick.synthesis.unitarypreparation import UnitaryPreparation -from typing import Any, Sequence - -__all__ = ["Diffusion"] - -class Diffusion(UnitaryPreparation): - model: str - prompt: str - max_num_gates: int - num_samples: int - pipeline: Any - def __init__( - self, - output_framework: type[Circuit], - model: str="Floki00/qc_unitary_3qubit", - prompt: str="Compile using: ['h', 'cx', 'z', 'ccx', 'swap']", - max_num_gates: int=12, - num_samples: int=128 - ) -> None: ... - def apply_unitary(self, circuit: Circuit, unitary: NDArray[np.complex128] | Operator, qubit_indices: int | Sequence[int]) -> Circuit: ... diff --git a/stubs/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.pyi b/stubs/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.pyi deleted file mode 100644 index b098471..0000000 --- a/stubs/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.pyi +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -from collections.abc import Sequence -from numpy.typing import NDArray -from quick.circuit import Circuit -from quick.primitives import Operator -from qiskit_ibm_runtime import QiskitRuntimeService -from quick.synthesis.unitarypreparation import UnitaryPreparation - -__all__ = ["QiskitUnitaryTranspiler"] - -class QiskitUnitaryTranspiler(UnitaryPreparation): - ai_transpilation: bool - service: QiskitRuntimeService | None - backend_name: str | None - def __init__( - self, - output_framework: type[Circuit], - ai_transpilation: bool=False, - unitary_synthesis_plugin: str="default", - service: QiskitRuntimeService | None = None, - backend_name: str | None = None - ) -> None: ... - def apply_unitary(self, circuit: Circuit, unitary: NDArray[np.complex128] | Operator, qubit_indices: int | Sequence[int]) -> Circuit: ... diff --git a/stubs/quick/synthesis/unitarypreparation/shannon_decomposition.pyi b/stubs/quick/synthesis/unitarypreparation/shannon_decomposition.pyi deleted file mode 100644 index 91eba75..0000000 --- a/stubs/quick/synthesis/unitarypreparation/shannon_decomposition.pyi +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -from collections.abc import Sequence -from numpy.typing import NDArray -from quick.circuit import Circuit -from quick.primitives import Operator -from quick.synthesis.unitarypreparation import UnitaryPreparation - -__all__ = ["ShannonDecomposition"] - -class ShannonDecomposition(UnitaryPreparation): - def apply_unitary(self, circuit: Circuit, unitary: NDArray[np.complex128] | Operator, qubit_indices: int | Sequence[int]) -> Circuit: ... diff --git a/stubs/quick/synthesis/unitarypreparation/unitarypreparation.pyi b/stubs/quick/synthesis/unitarypreparation/unitarypreparation.pyi deleted file mode 100644 index 7ad2c86..0000000 --- a/stubs/quick/synthesis/unitarypreparation/unitarypreparation.pyi +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import abc -from abc import ABC, abstractmethod -import numpy as np -from collections.abc import Sequence -from numpy.typing import NDArray -from quick.circuit import Circuit -from quick.primitives import Operator - -__all__ = ["UnitaryPreparation"] - -class UnitaryPreparation(ABC, metaclass=abc.ABCMeta): - output_framework: type[Circuit] - def __init__(self, output_framework: type[Circuit]) -> None: ... - def prepare_unitary(self, unitary: NDArray[np.complex128] | Operator) -> Circuit: ... - @abstractmethod - def apply_unitary(self, circuit: Circuit, unitary: NDArray[np.complex128] | Operator, qubit_indices: int | Sequence[int]) -> Circuit: ... diff --git a/tests/circuit/from_framework/test_circuit_from_pennylane.py b/tests/circuit/from_framework/test_circuit_from_pennylane.py new file mode 100644 index 0000000..5723e9b --- /dev/null +++ b/tests/circuit/from_framework/test_circuit_from_pennylane.py @@ -0,0 +1,178 @@ +# Copyright 2023-2025 Qualition Computing LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://github.com/Qualition/quick/blob/main/LICENSE +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +__all__ = ["TestFromPennylane"] + +import numpy as np +from numpy.testing import assert_almost_equal +import pennylane as qml # type: ignore + +from quick.circuit import Circuit, PennylaneCircuit + + +class TestFromPennylane: + """ `tests.circuit.TestFromPennylane` tests the `.from_pennylane` method. + """ + def test_RX(self) -> None: + """ Test the RX gate. + """ + # Define the Pennylane circuit + def pennylane_circuit(): + qml.RX(phi=0.1, wires=0) # type: ignore + + unitary = np.array( + qml.matrix(pennylane_circuit, wire_order=[0])(), dtype=complex # type: ignore + ) + + # Convert the Pennylane circuit to a quick circuit + quick_circuit = Circuit.from_pennylane( + qml.QNode( + pennylane_circuit, + device=qml.device("default.qubit", 1) + ), + PennylaneCircuit + ) + + assert_almost_equal( + quick_circuit.get_unitary(), + unitary, + 8 + ) + + def test_RZ(self) -> None: + """ Test the RZ gate. + """ + # Define the Pennylane circuit + def pennylane_circuit(): + qml.RZ(phi=0.1, wires=0) # type: ignore + + unitary = np.array( + qml.matrix(pennylane_circuit, wire_order=0)(), dtype=complex # type: ignore + ) + + # Convert the Pennylane circuit to a quick circuit + quick_circuit = Circuit.from_pennylane( + qml.QNode( + pennylane_circuit, + device=qml.device("default.qubit", 1) + ), + PennylaneCircuit + ) + + assert_almost_equal( + quick_circuit.get_unitary(), + unitary, + 8 + ) + + def test_CX(self) -> None: + """ Test the CX gate. + """ + # Define the Pennylane circuit + def pennylane_circuit(): + qml.CNOT(wires=[0, 1]) # type: ignore + + unitary = np.array( + qml.matrix(pennylane_circuit, wire_order=[1, 0])(), dtype=complex # type: ignore + ) + + # Convert the Pennylane circuit to a quick circuit + quick_circuit = Circuit.from_pennylane( + qml.QNode( + pennylane_circuit, + device=qml.device("default.qubit", 2) + ), + PennylaneCircuit + ) + + assert_almost_equal( + quick_circuit.get_unitary(), + unitary, + 8 + ) + + def test_global_phase(self) -> None: + """ Test the global phase gate. + """ + # Define the Pennylane circuit + def pennylane_circuit(): + qml.I(0) # type: ignore + qml.GlobalPhase(0.1) # type: ignore + + unitary = np.array( + qml.matrix(pennylane_circuit, wire_order=0)(), dtype=complex # type: ignore + ) + + # Convert the Pennylane circuit to a quick circuit + quick_circuit = Circuit.from_pennylane( + qml.QNode( + pennylane_circuit, + device=qml.device("default.qubit", 1) + ), + PennylaneCircuit + ) + + assert_almost_equal( + quick_circuit.get_unitary(), + unitary, + 8 + ) + + def test_single_measurement(self) -> None: + """ Test the single qubit measurement. + """ + # Define the Pennylane circuit + def pennylane_circuit(): + qml.measure(0) + + # Convert the Qiskit circuit to a quick circuit + quick_circuit = Circuit.from_pennylane( + qml.QNode( + pennylane_circuit, + device=qml.device("default.qubit", 1) + ), + PennylaneCircuit + ) + + # Define the equivalent quick circuit, and ensure + # that the two circuits are equal + check_circuit = PennylaneCircuit(1) + check_circuit.measure(0) + assert quick_circuit == check_circuit + + def test_multiple_measurement(self) -> None: + """ Test the multi-qubit measurement. + """ + # Define the Pennylane circuit + def pennylane_circuit(): + qml.measure(0) + qml.measure(1) + + # Convert the Qiskit circuit to a quick circuit + quick_circuit = Circuit.from_pennylane( + qml.QNode( + pennylane_circuit, + device=qml.device("default.qubit", 2) + ), + PennylaneCircuit + ) + + # Define the equivalent quick circuit, and ensure + # that the two circuits are equal + check_circuit = PennylaneCircuit(2) + check_circuit.measure(0) + check_circuit.measure(1) + assert quick_circuit == check_circuit \ No newline at end of file diff --git a/tests/metrics/test_metrics.py b/tests/metrics/test_metrics.py index 5d2de8c..3487d8d 100644 --- a/tests/metrics/test_metrics.py +++ b/tests/metrics/test_metrics.py @@ -20,12 +20,14 @@ from numpy.typing import NDArray from numpy.testing import assert_almost_equal import pytest +from scipy.stats import unitary_group from quick.circuit import QiskitCircuit from quick.metrics import ( calculate_entanglement_range, calculate_shannon_entropy, - calculate_entanglement_entropy + calculate_entanglement_entropy, + calculate_hilbert_schmidt_test ) @@ -73,4 +75,22 @@ def test_calculate_entanglement_entropy( data : NDArray[np.complex128] The statevector of the circuit. """ - assert_almost_equal(0.0, calculate_entanglement_entropy(data)) \ No newline at end of file + assert_almost_equal(0.0, calculate_entanglement_entropy(data)) + + def test_calculate_hilbert_schmidt_test(self) -> None: + """ Test the `calculate_hilbert_schmidt_test` method. + """ + unitary = unitary_group.rvs(4).astype(np.complex128) + assert_almost_equal(1.0, calculate_hilbert_schmidt_test(unitary, unitary)) + + def test_calculate_hilbert_schmidt_fail(self) -> None: + """ Test the `calculate_hilbert_schmidt_test` method with invalid inputs. + """ + unitary = unitary_group.rvs(4).astype(np.complex128) + + with pytest.raises(ValueError): + calculate_hilbert_schmidt_test(unitary, np.zeros((4, 4))) # type: ignore + with pytest.raises(ValueError): + calculate_hilbert_schmidt_test(unitary, np.zeros((4, 3))) # type: ignore + with pytest.raises(ValueError): + calculate_hilbert_schmidt_test(np.zeros((4, 4)), unitary) # type: ignore \ No newline at end of file diff --git a/tests/predicates/test_predicates.py b/tests/predicates/test_predicates.py index 8e9be1f..ae6d72a 100644 --- a/tests/predicates/test_predicates.py +++ b/tests/predicates/test_predicates.py @@ -15,6 +15,7 @@ from __future__ import annotations __all__ = [ + "test_is_statevector", "test_is_square_matrix", "test_is_diagonal_matrix", "test_is_symmetric_matrix", @@ -31,6 +32,7 @@ from scipy.stats import unitary_group from quick.predicates import ( + is_statevector, is_square_matrix, is_diagonal_matrix, is_symmetric_matrix, @@ -42,6 +44,43 @@ ) +@pytest.mark.parametrize("array, system_size, expected", [ + (np.array([1, 0]), 2, True), + (np.array([0, 1]), 2, True), + (np.array([1, 0, 0]), 3, True), + (np.array([1, 2]), 2, False), + (np.array([1, 2, 3]), 3, False), + (np.array([1, 0, 0, 0]), 2, True) +]) +def test_is_statevector( + array: NDArray[np.complex128], + system_size: int, + expected: bool + ) -> None: + """ Test the `.is_statevector()` method. + + Parameters + ---------- + `array` : NDArray[np.complex128] + The input array to check if it is a statevector. + `system_size` : int + The size of the system. If it's 2, then it's a qubit system. + `expected` : bool + The expected output of the function. + """ + assert is_statevector(array, system_size) == expected + +def test_is_statevector_invalid_system_size() -> None: + """ Test the `.is_statevector()` method with invalid system size. + + Parameters + ---------- + `array` : NDArray[np.complex128] + The input array to check if it is a statevector. + """ + with pytest.raises(ValueError): + is_statevector(np.array([1, 0]), system_size=0) + @pytest.mark.parametrize("array, expected", [ (np.random.rand(2, 2), True), (np.random.rand(3, 3), True), @@ -307,7 +346,8 @@ def test_is_positive_semidefinite_matrix( [1, 1] ], dtype=np.complex128), False), (np.random.rand(3, 3), False), - (np.random.rand(1, 2), False) + (np.random.rand(1, 2), False), + (np.array([1, 0]), False), ]) def test_is_isometry( array: NDArray[np.complex128], From 1d532385dd5d4b2b8dc3401c0341b79deec0ec5e Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Wed, 30 Apr 2025 18:01:33 +0800 Subject: [PATCH 08/58] Added pyright to pyproject.toml --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 2a35ed8..0c01e05 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,6 +60,10 @@ include = ["quick*"] [tool.setuptools.package-data] "quick" = ["py.typed"] +[tools.pyright] +include = ["quick", "tests"] +typeCheckingMode = "strict" + [tool.codeflash] # All paths are relative to this pyproject.toml's directory. module-root = "quick" From 19344b2e2810fdc5030490dad7cf38204b76e7b6 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 19 May 2025 23:42:33 +0800 Subject: [PATCH 09/58] - Trying an idea by using rounding on M2 (this will be okay given M2 is always going to be for 2q unitaries) - Added windows in to test the idea. --- .github/workflows/tests.yml | 2 +- .../gate_decompositions/two_qubit_decomposition/weyl.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ba5fdaf..8f62b94 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest] + os: [ubuntu-latest, windows-latest] python-version: ["3.10", "3.11", "3.12"] steps: diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py index 56724eb..18353d8 100644 --- a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py @@ -487,7 +487,7 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ global_phase = cmath.phase(U_det) / 4 U_magic_basis = transform_to_magic_basis(U.astype(complex), reverse=True) - M2 = U_magic_basis.T.dot(U_magic_basis) + M2 = np.round(U_magic_basis.T.dot(U_magic_basis), 14) # There is a floating point error in this implementation # for certain U, which depends on OS and Python version From 20fb1bf2eddddaecdbd39e2afed7cbb6654611ff Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Tue, 20 May 2025 00:16:16 +0800 Subject: [PATCH 10/58] - Trying an idea for fixing floating point error with uniformly controlled gate (Multiplexor specifically) --- quick/circuit/circuit_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 46070ce..ccebaab 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -207,6 +207,7 @@ def extract_uvr_matrices( # Eigendecomposition of r @ x @ r (Eq 8) # This is done via reforming Eq 6 to be similar to an eigenvalue decomposition rxr = r @ X @ r + rxr = np.round(rxr, 14) eigenvalues, u = np.linalg.eig(rxr) # Put the eigenvalues into a diagonal form From 5d26bb48ca9ca11a229dfc3bc38c6095225ed54d Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Tue, 20 May 2025 01:05:40 +0800 Subject: [PATCH 11/58] - Trying an idea by rounding v u r only. --- quick/circuit/circuit_utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index ccebaab..01e5ab5 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -207,7 +207,6 @@ def extract_uvr_matrices( # Eigendecomposition of r @ x @ r (Eq 8) # This is done via reforming Eq 6 to be similar to an eigenvalue decomposition rxr = r @ X @ r - rxr = np.round(rxr, 14) eigenvalues, u = np.linalg.eig(rxr) # Put the eigenvalues into a diagonal form @@ -221,6 +220,11 @@ def extract_uvr_matrices( # Calculate v based on the decomposition (Eq 7) v = diagonal @ np.conj(u).T @ np.conj(r).T @ b + # Round the values to avoid floating point errors + v = np.round(v, 15).astype(np.complex128) + u = np.round(u, 15).astype(np.complex128) + r = np.round(r, 15).astype(np.complex128) + return v, u, r def extract_single_qubits_and_diagonal( From 9b0428bd2dd7e28ddb9b454704f0508399c34d16 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Tue, 20 May 2025 01:41:11 +0800 Subject: [PATCH 12/58] - Trying rounding the gates during application in Multiplexor (rounding u v r fixed some issues but broke other cases, so not a viable approach) --- quick/circuit/circuit.py | 2 ++ quick/circuit/circuit_utils.py | 5 ----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index a94be3f..ecd177e 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -4838,6 +4838,8 @@ def Multiplexor( # of the multiplexor with self.controlled_state(control_state, control_indices): for i, gate in enumerate(single_qubit_gates): + gate = np.round(gate, 15) + if i == 0: self.unitary(gate, target_index) self.H(target_index) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 01e5ab5..46070ce 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -220,11 +220,6 @@ def extract_uvr_matrices( # Calculate v based on the decomposition (Eq 7) v = diagonal @ np.conj(u).T @ np.conj(r).T @ b - # Round the values to avoid floating point errors - v = np.round(v, 15).astype(np.complex128) - u = np.round(u, 15).astype(np.complex128) - r = np.round(r, 15).astype(np.complex128) - return v, u, r def extract_single_qubits_and_diagonal( From 56add990a1d7d898d790caf350330dfea679b91a Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sat, 24 May 2025 02:55:16 +0800 Subject: [PATCH 13/58] - Trying an idea to see if the two qubit inconsistency between different os can be fixed via np.complex128 use. - Cleaned up and removed some breaking code from last commit. --- quick/circuit/circuit.py | 34 ++++++++++++------- .../two_qubit_decomposition/weyl.py | 6 ++-- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index ecd177e..6695e2c 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -20,7 +20,7 @@ __all__ = ["Circuit"] from abc import ABC, abstractmethod -from collections.abc import Sequence +from collections.abc import Callable, Sequence from contextlib import contextmanager import copy import cmath @@ -29,7 +29,7 @@ from numpy.typing import NDArray from types import NotImplementedType from typing import ( - Any, Callable, Literal, overload, SupportsFloat, SupportsIndex, TYPE_CHECKING + Any, Literal, overload, SupportsFloat, SupportsIndex, TYPE_CHECKING ) import qiskit # type: ignore @@ -41,7 +41,10 @@ if TYPE_CHECKING: from quick.backend import Backend from quick.circuit.circuit_utils import ( - multiplexed_rz_angles, decompose_multiplexor_rotations, extract_single_qubits_and_diagonal, simplify + multiplexed_rz_angles, + decompose_multiplexor_rotations, + extract_single_qubits_and_diagonal, + simplify ) from quick.circuit.dag import DAGCircuit from quick.circuit.from_framework import FromCirq, FromPennyLane, FromQiskit, FromTKET @@ -53,7 +56,7 @@ UnitaryPreparation, ShannonDecomposition, QiskitUnitaryTranspiler ) -EPSILON = 1e-10 +EPSILON = 1e-15 """ Set the frozensets for the keys to be used: - Decorator `Circuit.gatemethod()` @@ -4793,17 +4796,22 @@ def Multiplexor( raise ValueError(f"The dimension of a gate is not equal to 2x2. Received {gate.shape}.") # Check if number of gates in gate_list is a positive power of two - num_control = np.log2(len(single_qubit_gates)) - if num_control < 0 or not int(num_control) == num_control: + num_controls = int( + np.log2( + len(single_qubit_gates) + ) + ) + + if num_controls < 0 or not int(num_controls) == num_controls: raise ValueError( "The number of single-qubit gates is not a non-negative power of 2." ) - if not num_control == len(control_indices): + if not num_controls == len(control_indices): raise ValueError( "The number of control qubits passed must be equal to the number of gates. " f"Received {len(control_indices)}. " - f"Expected {int(num_control)}." + f"Expected {int(num_controls)}." ) # Check if the single-qubit gates are unitaries @@ -4816,7 +4824,7 @@ def Multiplexor( # If the multiplexor simplification is enabled, we simplify the multiplexor # based on [2] if multiplexor_simplification: - new_controls, single_qubit_gates = simplify(single_qubit_gates, int(num_control)) + new_controls, single_qubit_gates = simplify(single_qubit_gates, num_controls) control_indices = [qubits[len(control_indices) + 1 - i] for i in new_controls] control_indices.reverse() @@ -4833,18 +4841,18 @@ def Multiplexor( len(control_indices) + 1 ) + num_single_qubit_gates = len(single_qubit_gates) + # Now, it is easy to place the CX gates and some Hadamards and RZ(pi/2) gates # which are absorbed into the single-qubit unitaries to get back the full decomposition # of the multiplexor with self.controlled_state(control_state, control_indices): for i, gate in enumerate(single_qubit_gates): - gate = np.round(gate, 15) - if i == 0: self.unitary(gate, target_index) self.H(target_index) - elif i == len(single_qubit_gates) - 1: + elif i == num_single_qubit_gates - 1: self.H(target_index) self.RZ(-PI2, target_index) self.unitary(gate, target_index) @@ -4862,7 +4870,7 @@ def Multiplexor( control_index = num_trailing_zeros # Apply the CX gate - if not i == len(single_qubit_gates) - 1: + if not i == num_single_qubit_gates - 1: self.CX(control_indices[control_index], target_index) self.GlobalPhase(-PI4) diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py index 18353d8..4696422 100644 --- a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py @@ -481,13 +481,13 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ >>> a, b, c, K1l, K1r, K2l, K2r, global_phase = TwoQubitWeylDecomposition.decompose_unitary(np.eye(4)) """ # Make U be in SU(4) - U = np.array(unitary_matrix, dtype=complex, copy=True) + U = np.array(unitary_matrix, dtype=np.complex128, copy=True) U_det = scipy.linalg.det(U) U *= U_det ** (-0.25) global_phase = cmath.phase(U_det) / 4 - U_magic_basis = transform_to_magic_basis(U.astype(complex), reverse=True) - M2 = np.round(U_magic_basis.T.dot(U_magic_basis), 14) + U_magic_basis = transform_to_magic_basis(U.astype(np.complex128), reverse=True) + M2 = U_magic_basis.T.dot(U_magic_basis) # There is a floating point error in this implementation # for certain U, which depends on OS and Python version From 6fdbbc0059ea2c528cb7797959e532235c215f6f Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sat, 24 May 2025 03:15:51 +0800 Subject: [PATCH 14/58] - Printing full output for two qubit case --- .../two_qubit_decomposition/test_two_qubit_decomposition.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py b/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py index 409b2e3..84a9e0d 100644 --- a/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py +++ b/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py @@ -144,6 +144,11 @@ def test_decomp1(self) -> None: # Apply the decomposition two_qubit_decomposition.apply_unitary(new_circuit, unitary_matrix, [0, 1]) + print("Unitary matrix:") + print(unitary_matrix) + print("New circuit unitary:") + print(new_circuit.get_unitary()) + # Check that the circuit is equivalent to the original unitary matrix assert_almost_equal(new_circuit.get_unitary(), unitary_matrix, decimal=8) From cf1c9378204bfe6441c73c213828beac1778cce7 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sat, 24 May 2025 03:41:14 +0800 Subject: [PATCH 15/58] - Testing to see if the phase difference seen in previous push is due to U's symmetry or not. --- .../two_qubit_decomposition/test_two_qubit_decomposition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py b/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py index 84a9e0d..0d0ee21 100644 --- a/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py +++ b/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py @@ -120,7 +120,7 @@ def test_decomp1(self) -> None: circuit = QiskitCircuit(2) # Create a GHZ state (one CX gate) - circuit.H(0) + circuit.U3([0.1, 0.2, 0.3], 0) circuit.CX(0, 1) # Extract the unitary matrix From 796c9b2d7ca0e386ad5c7b8730f65a5ca904d3e2 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 26 May 2025 01:59:12 +0800 Subject: [PATCH 16/58] - Trying an idea for fixing the two-qubit issue (Turns out it's not due to eig, as rounding essentially makes Linux have the same eig result as Windows, and it still passes which means issue is likely from somewhere else) - Changed `complex` dtype to `np.complex128` for better consistency. --- .../two_qubit_decomposition.py | 38 +++++++++---------- .../two_qubit_decomposition/weyl.py | 37 ++++++------------ .../test_two_qubit_decomposition.py | 7 +--- 3 files changed, 31 insertions(+), 51 deletions(-) diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.py index 7a351bd..e7f6d6d 100644 --- a/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.py +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.py @@ -46,97 +46,97 @@ Q0L = np.array([ [0.5+0.5j, 0.5-0.5j], [-0.5-0.5j, 0.5-0.5j] -], dtype=complex) +], dtype=np.complex128) Q0R = np.array([ [-0.5-0.5j, 0.5-0.5j], [-0.5-0.5j, -0.5+0.5j] -], dtype=complex) +], dtype=np.complex128) Q1LA = np.array([ [0.+0.j, -1-1j], [1-1j, 0.+0.j] -], dtype=complex) * SQRT2 +], dtype=np.complex128) * SQRT2 Q1LB = np.array([ [-0.5+0.5j, -0.5-0.5j], [0.5-0.5j, -0.5-0.5j] -], dtype=complex) +], dtype=np.complex128) Q1RA = np.array([ [1+0.j, 1+0.j], [-1+0.j, 1+0.j] -], dtype=complex) * SQRT2 +], dtype=np.complex128) * SQRT2 Q1RB = np.array([ [0.5-0.5j, 0.5+0.5j], [-0.5+0.5j, 0.5+0.5j] -], dtype=complex) +], dtype=np.complex128) Q2L = np.array([ [-1+1j, 0.+0.j], [0.+0.j, -1-1j] -], dtype=complex) * SQRT2 +], dtype=np.complex128) * SQRT2 Q2R = np.array([ [0.+1j, 0.-1j], [0.-1j, 0.-1j] -], dtype=complex) * SQRT2 +], dtype=np.complex128) * SQRT2 U0L: NDArray[np.complex128] = np.array([ [-1, 1], [-1, -1] -], dtype=complex) * SQRT2 +], dtype=np.complex128) * SQRT2 U0R: NDArray[np.complex128] = np.array([ [-1j, 1j], [1j, 1j] -], dtype=complex) * SQRT2 +], dtype=np.complex128) * SQRT2 U1L: NDArray[np.complex128] = np.array([ [-0.5+0.5j, -0.5+0.5j], [0.5+0.5j, -0.5-0.5j] -], dtype=complex) +], dtype=np.complex128) U1RA: NDArray[np.complex128] = np.array([ [0.5-0.5j, -0.5-0.5j], [0.5-0.5j, 0.5+0.5j] -], dtype=complex) +], dtype=np.complex128) UR1B: NDArray[np.complex128] = np.array([ [-1, -1j], [-1j, -1] -], dtype=complex) * SQRT2 +], dtype=np.complex128) * SQRT2 u2la: NDArray[np.complex128] = np.array([ [0.5+0.5j, 0.5-0.5j], [-0.5-0.5j, 0.5-0.5j] -], dtype=complex) +], dtype=np.complex128) U2LB: NDArray[np.complex128] = np.array([ [-0.5+0.5j, -0.5-0.5j], [0.5-0.5j, -0.5-0.5j] -], dtype=complex) +], dtype=np.complex128) U2RA: NDArray[np.complex128] = np.array([ [-0.5+0.5j, 0.5-0.5j], [-0.5-0.5j, -0.5-0.5j] -], dtype=complex) +], dtype=np.complex128) U2RB: NDArray[np.complex128] = np.array([ [0.5-0.5j, 0.5+0.5j], [-0.5+0.5j, 0.5+0.5j] -], dtype=complex) +], dtype=np.complex128) U3L: NDArray[np.complex128] = np.array([ [-1+1j, 0+0j], [0+0j, -1-1j] -], dtype=complex) * SQRT2 +], dtype=np.complex128) * SQRT2 U3R: NDArray[np.complex128] = np.array([ [1j, -1j], [-1j, -1j] -], dtype=complex) * SQRT2 +], dtype=np.complex128) * SQRT2 class TwoQubitDecomposition(UnitaryPreparation): diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py index 4696422..40438a0 100644 --- a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py @@ -56,7 +56,7 @@ [0, 0, 1j, 1], [0, 0, 1j, -1], [1, -1j, 0, 0] -], dtype=complex) +], dtype=np.complex128) M_UNNORMALIZED_DAGGER = 0.5 * M_UNNORMALIZED.conj().T @@ -64,17 +64,17 @@ X_MAGIC_BASIS = np.array([ [0, 1j], [1j, 0] -], dtype=complex) +], dtype=np.complex128) Y_MAGIC_BASIS = np.array([ [0, 1], [-1, 0] -], dtype=complex) +], dtype=np.complex128) Z_MAGIC_BASIS = np.array([ [1j, 0], [0, -1j] -], dtype=complex) +], dtype=np.complex128) # Constants PI = np.pi @@ -283,7 +283,7 @@ def diagonalize_unitary_complex_symmetric( # If there are no degenerate subspaces, we return the eigenvalues and identity matrix # as the eigenvectors if len(spaces) == 1: - return eigenvalues, np.eye(4).astype(complex) # type: ignore + return eigenvalues, np.eye(4).astype(np.complex128) # type: ignore out_vectors = np.empty((4, 4), dtype=np.float64) n_done = 0 @@ -298,17 +298,17 @@ def diagonalize_unitary_complex_symmetric( # This is the hardest case, because there might not have even one real vector a, b = eigenvectors[:, spaces[0]].T b_zeros = np.abs(b) <= atol + if np.any(np.abs(a[b_zeros]) > atol): # Make `a` real where `b` has zeros. a = remove_global_phase(a, index=np.argmax(np.where(b_zeros, np.abs(a), 0))) - if np.max(np.abs(a.imag)) <= atol: - # `a` is already all real - pass - else: + + if np.max(np.abs(a.imag)) > atol: # We have to solve `(b.imag, b.real) @ (re, im).T = a.imag` for `re` # and `im`, which is overspecified multiplier, *_ = scipy.linalg.lstsq(np.transpose([b.imag, b.real]), a.imag) # type: ignore a = a - complex(*multiplier) * b + a = a.real / scipy.linalg.norm(a.real) b = remove_global_phase(b - (a @ b) * a) out_vectors[:, :2] = np.transpose([a, b.real]) @@ -362,8 +362,6 @@ def decompose_two_qubit_product_gate( ----- >>> L, R, phase = decompose_two_qubit_product_gate(np.eye(4)) """ - special_unitary_matrix = np.asarray(special_unitary_matrix, dtype=complex) - # Extract the right component R = special_unitary_matrix[:2, :2].copy() R_det = R[0, 0] * R[1, 1] - R[0, 1] * R[1, 0] @@ -486,21 +484,8 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ U *= U_det ** (-0.25) global_phase = cmath.phase(U_det) / 4 - U_magic_basis = transform_to_magic_basis(U.astype(np.complex128), reverse=True) - M2 = U_magic_basis.T.dot(U_magic_basis) - - # There is a floating point error in this implementation - # for certain U, which depends on OS and Python version - # This causes the numpy.linalg.eig() to produce different results - # for the same input matrix, leading to a decomposition failure - # To contribute to this issue, please refer to: - # https://github.com/Qualition/quick/issues/11 - - # Alternatively, you may propose an entirely new implementation - # so that we can replace this two qubit decomposition implementation - # with a more robust one that doesn't have floating point errors - # To contribute to this feature request, please refer to: - # https://github.com/Qualition/quick/issues/14 + U_magic_basis = transform_to_magic_basis(U, reverse=True) + M2 = np.round(U_magic_basis.T.dot(U_magic_basis), decimals=15) D, P = diagonalize_unitary_complex_symmetric(M2) d = -np.angle(D) / 2 diff --git a/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py b/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py index 0d0ee21..409b2e3 100644 --- a/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py +++ b/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py @@ -120,7 +120,7 @@ def test_decomp1(self) -> None: circuit = QiskitCircuit(2) # Create a GHZ state (one CX gate) - circuit.U3([0.1, 0.2, 0.3], 0) + circuit.H(0) circuit.CX(0, 1) # Extract the unitary matrix @@ -144,11 +144,6 @@ def test_decomp1(self) -> None: # Apply the decomposition two_qubit_decomposition.apply_unitary(new_circuit, unitary_matrix, [0, 1]) - print("Unitary matrix:") - print(unitary_matrix) - print("New circuit unitary:") - print(new_circuit.get_unitary()) - # Check that the circuit is equivalent to the original unitary matrix assert_almost_equal(new_circuit.get_unitary(), unitary_matrix, decimal=8) From b4a3fc8e23344507a5f7027e69d8a5c459eee6fe Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 26 May 2025 03:01:36 +0800 Subject: [PATCH 17/58] - Checking if the windows non-rounded fail is due to wrong diagonalization. --- .../two_qubit_decomposition/weyl.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py index 40438a0..aefee45 100644 --- a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py @@ -474,6 +474,13 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ `global_phase` : float The global phase. + Raises + ------ + ValueError + - If the diagonalization of the unitary complex-symmetric matrix fails. + - If the determinant of the right or left component is not in the expected range. + - If the decomposition fails due to a deviation from the expected unitary matrix. + Usage ----- >>> a, b, c, K1l, K1r, K2l, K2r, global_phase = TwoQubitWeylDecomposition.decompose_unitary(np.eye(4)) @@ -486,8 +493,21 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ U_magic_basis = transform_to_magic_basis(U, reverse=True) M2 = np.round(U_magic_basis.T.dot(U_magic_basis), decimals=15) + M2_windows_fail = U_magic_basis.T.dot(U_magic_basis) D, P = diagonalize_unitary_complex_symmetric(M2) + + if not np.allclose(P.dot(np.diag(D)).dot(P.T), M2, rtol=0, atol=1e-13): + raise ValueError( + "The diagonalization of the unitary complex-symmetric matrix failed. " + "The result is not close enough to the original matrix." + ) + + if not np.allclose(P.dot(np.diag(D)).dot(P.T), M2_windows_fail, rtol=0, atol=1e-13): + raise ValueError( + "Non-rounded failed." + ) + d = -np.angle(D) / 2 d[3] = -d[0] - d[1] - d[2] weyl_coordinates = np.mod((d[:3] + d[3]) / 2, PI_DOUBLE) From 16fe3a3e8f0f7b09fed54ee418c6f0ad40f2c609 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 26 May 2025 03:23:26 +0800 Subject: [PATCH 18/58] - Trying a quick sanity check (P is real-symmetric, so conj shouldn't really matter, but just to check) --- .../gate_decompositions/two_qubit_decomposition/weyl.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py index aefee45..d498847 100644 --- a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py @@ -497,13 +497,15 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ D, P = diagonalize_unitary_complex_symmetric(M2) - if not np.allclose(P.dot(np.diag(D)).dot(P.T), M2, rtol=0, atol=1e-13): + print(f"Dtype {P.dtype}") + + if not np.allclose(P.dot(np.diag(D)).dot(P.conj().T), M2, rtol=0, atol=1e-13): raise ValueError( "The diagonalization of the unitary complex-symmetric matrix failed. " "The result is not close enough to the original matrix." ) - if not np.allclose(P.dot(np.diag(D)).dot(P.T), M2_windows_fail, rtol=0, atol=1e-13): + if not np.allclose(P.dot(np.diag(D)).dot(P.conj().T), M2_windows_fail, rtol=0, atol=1e-13): raise ValueError( "Non-rounded failed." ) From aa8d8dd48d4e6e5eaf363460b43be1fd3291aa46 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 26 May 2025 17:06:17 +0800 Subject: [PATCH 19/58] - Added print lines to see what is causing the uniformly controlled issue in Windows. - Removed `M2_windows_fail` since it seems diagonalization was not the issue. --- .../two_qubit_decomposition/weyl.py | 18 ++++++---------- .../test_uniformly_controlled_gates.py | 21 ++++++++++++------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py index d498847..a31a5ea 100644 --- a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py @@ -477,7 +477,7 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ Raises ------ ValueError - - If the diagonalization of the unitary complex-symmetric matrix fails. + - If the diagonalization of M2 fails. - If the determinant of the right or left component is not in the expected range. - If the decomposition fails due to a deviation from the expected unitary matrix. @@ -493,21 +493,15 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ U_magic_basis = transform_to_magic_basis(U, reverse=True) M2 = np.round(U_magic_basis.T.dot(U_magic_basis), decimals=15) - M2_windows_fail = U_magic_basis.T.dot(U_magic_basis) D, P = diagonalize_unitary_complex_symmetric(M2) - print(f"Dtype {P.dtype}") - - if not np.allclose(P.dot(np.diag(D)).dot(P.conj().T), M2, rtol=0, atol=1e-13): - raise ValueError( - "The diagonalization of the unitary complex-symmetric matrix failed. " - "The result is not close enough to the original matrix." - ) - - if not np.allclose(P.dot(np.diag(D)).dot(P.conj().T), M2_windows_fail, rtol=0, atol=1e-13): + # Given P is a real-symmetric unitary matrix we only use transpose + if not np.allclose(P.dot(np.diag(D)).dot(P.T), M2, rtol=0, atol=1e-13): raise ValueError( - "Non-rounded failed." + "Failed to diagonalize M2." + "Kindly report this at https://github.com/Qualition/quick/issues/11: " + f"U: {U}" ) d = -np.angle(D) / 2 diff --git a/tests/circuit/test_uniformly_controlled_gates.py b/tests/circuit/test_uniformly_controlled_gates.py index 1ab7f08..2ad9ce6 100644 --- a/tests/circuit/test_uniformly_controlled_gates.py +++ b/tests/circuit/test_uniformly_controlled_gates.py @@ -20,7 +20,6 @@ from numpy.testing import assert_almost_equal from numpy.typing import NDArray import pytest -from typing import Type from quick.circuit import Circuit from quick.circuit.gate_matrix import PauliX, PauliY, Hadamard, RX, RY @@ -366,7 +365,7 @@ def test_Multiplexor_no_diagonal_no_simplification( # Ensure the unitary matrix is correct assert_almost_equal(circuit.get_unitary(), expected, 8) - @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + # @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ [ [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], @@ -413,7 +412,6 @@ def test_Multiplexor_no_diagonal_no_simplification( ]) def test_Multiplexor_diagonal_no_simplification( self, - circuit_framework: type[Circuit], single_qubit_gates: list[NDArray[np.complex128]], control_indices: list[int], target_index: int, @@ -434,8 +432,10 @@ def test_Multiplexor_diagonal_no_simplification( `expected` : NDArray[np.complex128] The expected unitary matrix. """ + from quick.circuit import QiskitCircuit + # Define the quantum circuit - circuit = circuit_framework(len(control_indices) + 1) + circuit = QiskitCircuit(len(control_indices) + 1) # Apply the Multiplexor gate circuit.Multiplexor( @@ -446,10 +446,13 @@ def test_Multiplexor_diagonal_no_simplification( multiplexor_simplification=False ) + print(f"Multiplexor unitary matrix:\n{circuit.get_unitary()}") + print(f"Expected unitary matrix:\n{expected}") + # Ensure the unitary matrix is correct assert_almost_equal(circuit.get_unitary(), expected, 8) - @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + # @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ [ [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], @@ -496,7 +499,6 @@ def test_Multiplexor_diagonal_no_simplification( ]) def test_Multiplexor_no_diagonal_simplification( self, - circuit_framework: type[Circuit], single_qubit_gates: list[NDArray[np.complex128]], control_indices: list[int], target_index: int, @@ -517,8 +519,10 @@ def test_Multiplexor_no_diagonal_simplification( `expected` : NDArray[np.complex128] The expected unitary matrix. """ + from quick.circuit import QiskitCircuit + # Define the quantum circuit - circuit = circuit_framework(len(control_indices) + 1) + circuit = QiskitCircuit(len(control_indices) + 1) # Apply the Multiplexor gate circuit.Multiplexor( @@ -529,6 +533,9 @@ def test_Multiplexor_no_diagonal_simplification( multiplexor_simplification=True ) + print(f"Multiplexor unitary matrix:\n{circuit.get_unitary()}") + print(f"Expected unitary matrix:\n{expected}") + # Ensure the unitary matrix is correct assert_almost_equal(circuit.get_unitary(), expected, 8) From 2b2e611eee9d39cc57745e286a6000cf0611952c Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 26 May 2025 17:52:26 +0800 Subject: [PATCH 20/58] - Checking the single qubit gates produced to see if they are the cause of the issue. - For sanity, I changed from np.linalg.eig to scipy.linalg.eig (should not matter, but anyways). --- quick/circuit/circuit.py | 3 +++ quick/circuit/circuit_utils.py | 5 +++-- .../circuit/test_uniformly_controlled_gates.py | 17 +++++++---------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index 6695e2c..db66970 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -4843,6 +4843,9 @@ def Multiplexor( num_single_qubit_gates = len(single_qubit_gates) + for gate in single_qubit_gates: + print(f"Single qubit gate: {gate}") + # Now, it is easy to place the CX gates and some Hadamards and RZ(pi/2) gates # which are absorbed into the single-qubit unitaries to get back the full decomposition # of the multiplexor diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 46070ce..61a78ee 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -32,6 +32,7 @@ import numpy as np from numpy.typing import NDArray +import scipy.linalg """ Constants for decomposing multiplexed RZ gates from Bergholm et al. These are the (0, 0) and (1, 1) elements of the RZ gate matrix with angle -pi/2 @@ -207,7 +208,7 @@ def extract_uvr_matrices( # Eigendecomposition of r @ x @ r (Eq 8) # This is done via reforming Eq 6 to be similar to an eigenvalue decomposition rxr = r @ X @ r - eigenvalues, u = np.linalg.eig(rxr) + eigenvalues, u = scipy.linalg.eig(rxr) # type: ignore # Put the eigenvalues into a diagonal form diagonal = np.diag(np.sqrt(eigenvalues)) @@ -220,7 +221,7 @@ def extract_uvr_matrices( # Calculate v based on the decomposition (Eq 7) v = diagonal @ np.conj(u).T @ np.conj(r).T @ b - return v, u, r + return v, u, r # type: ignore def extract_single_qubits_and_diagonal( single_qubit_gates: list[NDArray[np.complex128]], diff --git a/tests/circuit/test_uniformly_controlled_gates.py b/tests/circuit/test_uniformly_controlled_gates.py index 2ad9ce6..33beb36 100644 --- a/tests/circuit/test_uniformly_controlled_gates.py +++ b/tests/circuit/test_uniformly_controlled_gates.py @@ -446,13 +446,10 @@ def test_Multiplexor_diagonal_no_simplification( multiplexor_simplification=False ) - print(f"Multiplexor unitary matrix:\n{circuit.get_unitary()}") - print(f"Expected unitary matrix:\n{expected}") - # Ensure the unitary matrix is correct assert_almost_equal(circuit.get_unitary(), expected, 8) - # @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ [ [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], @@ -499,6 +496,7 @@ def test_Multiplexor_diagonal_no_simplification( ]) def test_Multiplexor_no_diagonal_simplification( self, + circuit_framework: type[Circuit], single_qubit_gates: list[NDArray[np.complex128]], control_indices: list[int], target_index: int, @@ -519,10 +517,8 @@ def test_Multiplexor_no_diagonal_simplification( `expected` : NDArray[np.complex128] The expected unitary matrix. """ - from quick.circuit import QiskitCircuit - # Define the quantum circuit - circuit = QiskitCircuit(len(control_indices) + 1) + circuit = circuit_framework(len(control_indices) + 1) # Apply the Multiplexor gate circuit.Multiplexor( @@ -539,7 +535,7 @@ def test_Multiplexor_no_diagonal_simplification( # Ensure the unitary matrix is correct assert_almost_equal(circuit.get_unitary(), expected, 8) - @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + # @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ [ [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], @@ -586,7 +582,6 @@ def test_Multiplexor_no_diagonal_simplification( ]) def test_Multiplexor_diagonal_simplification( self, - circuit_framework: type[Circuit], single_qubit_gates: list[NDArray[np.complex128]], control_indices: list[int], target_index: int, @@ -607,8 +602,10 @@ def test_Multiplexor_diagonal_simplification( `expected` : NDArray[np.complex128] The expected unitary matrix. """ + from quick.circuit import QiskitCircuit + # Define the quantum circuit - circuit = circuit_framework(len(control_indices) + 1) + circuit = QiskitCircuit(len(control_indices) + 1) # Apply the Multiplexor gate circuit.Multiplexor( From 3492fdc4caf74610844b6aef826a6e53f4e020d3 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 26 May 2025 18:10:19 +0800 Subject: [PATCH 21/58] Fixed the mypy issue --- quick/circuit/circuit_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 61a78ee..38253ed 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -32,7 +32,7 @@ import numpy as np from numpy.typing import NDArray -import scipy.linalg +import scipy.linalg # type: ignore """ Constants for decomposing multiplexed RZ gates from Bergholm et al. These are the (0, 0) and (1, 1) elements of the RZ gate matrix with angle -pi/2 From cfa11994f14dcce48948432eb3ab05f14df90fcf Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 26 May 2025 19:03:42 +0800 Subject: [PATCH 22/58] - Checking to see if the issue is from `extract_uvr_matrices` (the single qubit gates produced were indeed wrong, so we have to check how they are defined and what could cause inconsistency depending on OS) - Removed an unnecessary function in circuit utils. --- quick/circuit/circuit.py | 2 +- quick/circuit/circuit_utils.py | 48 +++++----------------------------- 2 files changed, 8 insertions(+), 42 deletions(-) diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index db66970..5183a6d 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -4836,7 +4836,7 @@ def Multiplexor( # If there is at least one control qubit, we decompose the multiplexor # into a sequence of single-qubit gates and CX gates - (single_qubit_gates, diagonal) = extract_single_qubits_and_diagonal( + single_qubit_gates, diagonal = extract_single_qubits_and_diagonal( single_qubit_gates, len(control_indices) + 1 ) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 38253ed..8bc5fca 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -22,7 +22,6 @@ "multiplexed_rz_angles", "extract_uvr_matrices", "extract_single_qubits_and_diagonal", - "multiplexor_diagonal_matrix", "simplify", "repetition_search", "repetition_verify", @@ -208,6 +207,9 @@ def extract_uvr_matrices( # Eigendecomposition of r @ x @ r (Eq 8) # This is done via reforming Eq 6 to be similar to an eigenvalue decomposition rxr = r @ X @ r + + print(f"rxr: {rxr}") + eigenvalues, u = scipy.linalg.eig(rxr) # type: ignore # Put the eigenvalues into a diagonal form @@ -221,6 +223,10 @@ def extract_uvr_matrices( # Calculate v based on the decomposition (Eq 7) v = diagonal @ np.conj(u).T @ np.conj(r).T @ b + print(f"v: {v}") + print(f"u: {u}") + print(f"r: {r}") + return v, u, r # type: ignore def extract_single_qubits_and_diagonal( @@ -306,46 +312,6 @@ def extract_single_qubits_and_diagonal( return single_qubit_gates, diagonal -def multiplexor_diagonal_matrix( - single_qubit_gates: list[NDArray[np.complex128]], - num_qubits: int, - simplified_controls: set[int] - ) -> NDArray[np.complex128]: - """ Get the diagonal matrix arising in the decomposition of multiplexor - gates given in the paper by Bergholm et al. - - Notes - ----- - This function to extract the diagonal matrix arising in the decomposition - of multiplexed gates based on the paper by Bergholm et al. - - Parameters - ---------- - `single_qubit_gates` : list[NDArray[np.complex128]] - The list of single qubit gates. - `num_qubits` : int - The number of qubits. - - Returns - ------- - NDArray[np.complex128] - The diagonal matrix. - """ - _, diagonal = extract_single_qubits_and_diagonal(single_qubit_gates, num_qubits) - - # Simplify the diagonal to minimize the number of controlled gates - # needed to implement the diagonal gate - if simplified_controls: - control_qubits = sorted([num_qubits - i for i in simplified_controls], reverse=True) - for i in range(num_qubits): - if i not in [0] + control_qubits: - step = 2**i - diagonal = np.repeat(diagonal, 2, axis=0) - for j in range(step, len(diagonal), 2 * step): - diagonal[j:j + step] = diagonal[j - step:j] - - return diagonal - def simplify( single_qubit_gates: list[NDArray[np.complex128]], num_controls: int From 300eb7c3493b5ecf2aea56f067ce4776dbb3af84 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 26 May 2025 22:58:51 +0800 Subject: [PATCH 23/58] - Trying cmath for uniformly controlled issue. --- quick/circuit/circuit_utils.py | 33 ++++++++----------- quick/predicates/predicates.py | 6 ++-- .../test_uniformly_controlled_gates.py | 3 -- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 8bc5fca..30a4910 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -29,6 +29,7 @@ "reshape" ] +import cmath import numpy as np from numpy.typing import NDArray import scipy.linalg # type: ignore @@ -185,20 +186,20 @@ def extract_uvr_matrices( The diagonal matrix r. """ # Hermitian conjugate of b (Eq 6) - X = a @ np.conj(b).T + X = a @ b.conj().T # Determinant and phase of x det_X = np.linalg.det(X) - X_11 = X[0, 0] / np.sqrt(det_X) + X_11 = X[0, 0] / cmath.sqrt(det_X) phi = np.angle(det_X) # Compute the diagonal matrix r - arg_X_11 = np.angle(X_11) + arg_X_11 = cmath.phase(X_11) # The implementation of the diagonal matrix r is # given below, but it can be chosen freely - r_1 = np.exp(1j / 2 * ((np.pi - phi)/2 - arg_X_11)) - r_2 = np.exp(1j / 2 * ((np.pi - phi)/2 + arg_X_11 + np.pi)) + r_1 = cmath.exp(1j / 2 * ((np.pi - phi)/2 - arg_X_11)) + r_2 = cmath.exp(1j / 2 * ((np.pi - phi)/2 + arg_X_11 + np.pi)) r = np.array([ [r_1, 0], [0, r_2] @@ -207,26 +208,20 @@ def extract_uvr_matrices( # Eigendecomposition of r @ x @ r (Eq 8) # This is done via reforming Eq 6 to be similar to an eigenvalue decomposition rxr = r @ X @ r - - print(f"rxr: {rxr}") - eigenvalues, u = scipy.linalg.eig(rxr) # type: ignore - # Put the eigenvalues into a diagonal form - diagonal = np.diag(np.sqrt(eigenvalues)) - - # Handle specific case where the eigenvalue is near -i - if np.abs(diagonal[0, 0] + 1j) < 1e-10: - diagonal = np.flipud(diagonal) + # Handle specific case where the first eigenvalue is near -i + # This is done by interchanging the eigenvalues and eigenvectors (Eq 13) + if abs(eigenvalues[0] + 1j) < 1e-10: + print("Flipping") + eigenvalues = np.flipud(eigenvalues) u = np.fliplr(u) + diagonal = np.diag(np.sqrt(eigenvalues)) + # Calculate v based on the decomposition (Eq 7) v = diagonal @ np.conj(u).T @ np.conj(r).T @ b - print(f"v: {v}") - print(f"u: {u}") - print(f"r: {r}") - return v, u, r # type: ignore def extract_single_qubits_and_diagonal( @@ -291,7 +286,7 @@ def extract_single_qubits_and_diagonal( single_qubit_gates[shift + len_multiplexor // 2 + i] = u # Decompose D gates per figure 3 - r_dagger = np.conj(r).T + r_dagger = r.conj().T if multiplexor_index < num_multiplexors - 1: k = shift + len_multiplexor + i diff --git a/quick/predicates/predicates.py b/quick/predicates/predicates.py index 396f9d4..92dbcc8 100644 --- a/quick/predicates/predicates.py +++ b/quick/predicates/predicates.py @@ -246,7 +246,7 @@ def is_unitary_matrix( if not is_square_matrix(matrix): return False - matrix = np.conj(matrix.T).dot(matrix) + matrix = matrix.conj().T @ matrix return is_identity_matrix(matrix, ignore_phase=False, rtol=rtol, atol=atol) def is_hermitian_matrix( @@ -277,7 +277,7 @@ def is_hermitian_matrix( if not is_square_matrix(matrix): return False - return np.allclose(matrix, np.conj(matrix.T), rtol=rtol, atol=atol) + return np.allclose(matrix, matrix.conj().T, rtol=rtol, atol=atol) def is_positive_semidefinite_matrix( matrix: NDArray[np.complex128], @@ -343,5 +343,5 @@ def is_isometry( return False identity = np.eye(matrix.shape[1]) - matrix = np.conj(matrix.T).dot(matrix) + matrix = matrix.conj().T @ matrix return np.allclose(matrix, identity, rtol=rtol, atol=atol) \ No newline at end of file diff --git a/tests/circuit/test_uniformly_controlled_gates.py b/tests/circuit/test_uniformly_controlled_gates.py index 33beb36..a6e9cda 100644 --- a/tests/circuit/test_uniformly_controlled_gates.py +++ b/tests/circuit/test_uniformly_controlled_gates.py @@ -529,9 +529,6 @@ def test_Multiplexor_no_diagonal_simplification( multiplexor_simplification=True ) - print(f"Multiplexor unitary matrix:\n{circuit.get_unitary()}") - print(f"Expected unitary matrix:\n{expected}") - # Ensure the unitary matrix is correct assert_almost_equal(circuit.get_unitary(), expected, 8) From 5efb19684a3ed3c3e725b7929fdeac8e71096850 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 26 May 2025 23:28:40 +0800 Subject: [PATCH 24/58] - Trying rounding again, with 16 decimals --- quick/circuit/circuit_utils.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 30a4910..1c3c9ad 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -29,7 +29,6 @@ "reshape" ] -import cmath import numpy as np from numpy.typing import NDArray import scipy.linalg # type: ignore @@ -190,16 +189,16 @@ def extract_uvr_matrices( # Determinant and phase of x det_X = np.linalg.det(X) - X_11 = X[0, 0] / cmath.sqrt(det_X) + X_11 = X[0, 0] / np.sqrt(det_X) phi = np.angle(det_X) # Compute the diagonal matrix r - arg_X_11 = cmath.phase(X_11) + arg_X_11 = np.angle(X_11) # The implementation of the diagonal matrix r is # given below, but it can be chosen freely - r_1 = cmath.exp(1j / 2 * ((np.pi - phi)/2 - arg_X_11)) - r_2 = cmath.exp(1j / 2 * ((np.pi - phi)/2 + arg_X_11 + np.pi)) + r_1 = np.exp(1j / 2 * ((np.pi - phi)/2 - arg_X_11)) + r_2 = np.exp(1j / 2 * ((np.pi - phi)/2 + arg_X_11 + np.pi)) r = np.array([ [r_1, 0], [0, r_2] @@ -208,12 +207,11 @@ def extract_uvr_matrices( # Eigendecomposition of r @ x @ r (Eq 8) # This is done via reforming Eq 6 to be similar to an eigenvalue decomposition rxr = r @ X @ r - eigenvalues, u = scipy.linalg.eig(rxr) # type: ignore + eigenvalues, u = scipy.linalg.eig(np.round(rxr, 16)) # type: ignore # Handle specific case where the first eigenvalue is near -i # This is done by interchanging the eigenvalues and eigenvectors (Eq 13) if abs(eigenvalues[0] + 1j) < 1e-10: - print("Flipping") eigenvalues = np.flipud(eigenvalues) u = np.fliplr(u) From ae0d633ddf652ccc07ef08b9590d3c8783f81c73 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Tue, 27 May 2025 00:11:41 +0800 Subject: [PATCH 25/58] - Added full framework to the uniformly controlled testers. --- tests/circuit/test_uniformly_controlled_gates.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/circuit/test_uniformly_controlled_gates.py b/tests/circuit/test_uniformly_controlled_gates.py index a6e9cda..c8146b1 100644 --- a/tests/circuit/test_uniformly_controlled_gates.py +++ b/tests/circuit/test_uniformly_controlled_gates.py @@ -365,7 +365,7 @@ def test_Multiplexor_no_diagonal_no_simplification( # Ensure the unitary matrix is correct assert_almost_equal(circuit.get_unitary(), expected, 8) - # @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ [ [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], @@ -412,6 +412,7 @@ def test_Multiplexor_no_diagonal_no_simplification( ]) def test_Multiplexor_diagonal_no_simplification( self, + circuit_framework: type[Circuit], single_qubit_gates: list[NDArray[np.complex128]], control_indices: list[int], target_index: int, @@ -432,10 +433,8 @@ def test_Multiplexor_diagonal_no_simplification( `expected` : NDArray[np.complex128] The expected unitary matrix. """ - from quick.circuit import QiskitCircuit - # Define the quantum circuit - circuit = QiskitCircuit(len(control_indices) + 1) + circuit = circuit_framework(len(control_indices) + 1) # Apply the Multiplexor gate circuit.Multiplexor( @@ -532,7 +531,7 @@ def test_Multiplexor_no_diagonal_simplification( # Ensure the unitary matrix is correct assert_almost_equal(circuit.get_unitary(), expected, 8) - # @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ [ [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], @@ -579,6 +578,7 @@ def test_Multiplexor_no_diagonal_simplification( ]) def test_Multiplexor_diagonal_simplification( self, + circuit_framework: type[Circuit], single_qubit_gates: list[NDArray[np.complex128]], control_indices: list[int], target_index: int, @@ -599,10 +599,8 @@ def test_Multiplexor_diagonal_simplification( `expected` : NDArray[np.complex128] The expected unitary matrix. """ - from quick.circuit import QiskitCircuit - # Define the quantum circuit - circuit = QiskitCircuit(len(control_indices) + 1) + circuit = circuit_framework(len(control_indices) + 1) # Apply the Multiplexor gate circuit.Multiplexor( From 3db28fa95ff6e941449758e9777b932a4b9d836f Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Tue, 27 May 2025 01:18:27 +0800 Subject: [PATCH 26/58] - Updated workflow ymls --- .github/{dependabot.yaml => dependabot.yml} | 0 ...ython-publish-pypi.yaml => python-publish-pypi.yml} | 10 ++++++++-- .github/workflows/tests.yml | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) rename .github/{dependabot.yaml => dependabot.yml} (100%) rename .github/workflows/{python-publish-pypi.yaml => python-publish-pypi.yml} (84%) diff --git a/.github/dependabot.yaml b/.github/dependabot.yml similarity index 100% rename from .github/dependabot.yaml rename to .github/dependabot.yml diff --git a/.github/workflows/python-publish-pypi.yaml b/.github/workflows/python-publish-pypi.yml similarity index 84% rename from .github/workflows/python-publish-pypi.yaml rename to .github/workflows/python-publish-pypi.yml index 66da8ce..e1f3027 100644 --- a/.github/workflows/python-publish-pypi.yaml +++ b/.github/workflows/python-publish-pypi.yml @@ -14,7 +14,10 @@ on: jobs: build: name: Build distribution 📦 - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] permissions: attestations: write id-token: write @@ -32,7 +35,10 @@ jobs: name: Publish Python 🐍 distribution 📦 to PyPI needs: build if: ${{ github.event.action == 'published' }} - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] environment: name: pypi url: https://pypi.org/project/quick-core/${{ github.ref_name }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8f62b94..266379b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, windows-latest] + os: [ubuntu-latest, windows-latest, macos-latest] python-version: ["3.10", "3.11", "3.12"] steps: From 10ddce15fcb819adfc0c73c193091fdce512dfb1 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Wed, 28 May 2025 01:30:03 +0800 Subject: [PATCH 27/58] - Bumped versions where possible to minimize warnings. --- pyproject.toml | 18 +++++++++--------- .../statepreparation/statepreparation_utils.py | 2 +- .../qiskit_unitary_transpiler.py | 2 +- .../shannon_decomposition.py | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0c01e05..9a75de2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,17 +8,17 @@ version = "0.0.0" dependencies = [ "cirq-core == 1.4.1", "genQC == 0.1.0", - "numpy >= 1.23,< 3.0", + "numpy >= 1.23", "pennylane == 0.39.0", - "pytket == 1.37.0", - "pytket-qiskit == 0.62.0", - "pytket-cirq == 0.39.0", - "qiskit == 1.3.1", - "qiskit_aer == 0.16.0", - "qiskit_ibm_runtime == 0.34.0", - "qiskit-transpiler-service == 0.4.14", + "pytket == 2.4.1", + "pytket-qiskit == 0.68.0", + "pytket-cirq == 0.40.0", + "qiskit == 2.0.1", + "qiskit_aer == 0.17.0", + "qiskit_ibm_runtime == 0.39.0", + "qiskit-ibm-transpiler == 0.11.0", "quimb == 1.10.0", - "tket2 == 0.6.0" + "tket2 == 0.10.0" ] requires-python = ">=3.10, <3.13" authors = [ diff --git a/quick/synthesis/statepreparation/statepreparation_utils.py b/quick/synthesis/statepreparation/statepreparation_utils.py index 7530a1c..401c1a0 100644 --- a/quick/synthesis/statepreparation/statepreparation_utils.py +++ b/quick/synthesis/statepreparation/statepreparation_utils.py @@ -522,7 +522,7 @@ def apply_diagonal_gate_to_diag( NDArray[np.complex128] The diagonal matrix after applying the diagonal gate. """ - if not m_diagonal: + if m_diagonal.size == 0: return m_diagonal for state in product([0, 1], repeat=num_qubits): diagonal_index = sum(state[i] << (len(action_qubit_labels) - 1 - idx) for idx, i in enumerate(action_qubit_labels)) diff --git a/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.py b/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.py index 371c8ee..e6cabcd 100644 --- a/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.py +++ b/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.py @@ -28,7 +28,7 @@ from qiskit import QuantumCircuit, transpile # type: ignore from qiskit.transpiler.passes import unitary_synthesis_plugin_names # type: ignore from qiskit_ibm_runtime import QiskitRuntimeService # type: ignore -from qiskit_transpiler_service.transpiler_service import TranspilerService # type: ignore +from qiskit_ibm_transpiler.transpiler_service import TranspilerService # type: ignore if TYPE_CHECKING: from quick.circuit import Circuit diff --git a/quick/synthesis/unitarypreparation/shannon_decomposition.py b/quick/synthesis/unitarypreparation/shannon_decomposition.py index 6df9dcf..518f027 100644 --- a/quick/synthesis/unitarypreparation/shannon_decomposition.py +++ b/quick/synthesis/unitarypreparation/shannon_decomposition.py @@ -291,8 +291,8 @@ def demultiplexor( # Take the square root of the eigenvalues to obtain the singular values # This is necessary because the singular values provide a more convenient form # for constructing the diagonal matrix D, which is used in the final decomposition - # We need to use `np.emath.sqrt` to handle negative eigenvalues - eigenvalues_sqrt = np.emath.sqrt(eigenvalues) + # We need to use `np.lib.scimath.sqrt` to handle negative eigenvalues + eigenvalues_sqrt = np.lib.scimath.sqrt(eigenvalues) # Create a diagonal matrix D from the singular values # The diagonal matrix D is used to scale the eigenvectors appropriately in the final step From 77767ca8de269b47634f48827b5c75152f9743fd Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sun, 1 Jun 2025 23:57:06 +0800 Subject: [PATCH 28/58] - Added additional testers for known cases of M2 diagnolization issues. - Changed error raise to warning as Ubuntu still succeeds in properly encoding the unitary. --- .../two_qubit_decomposition/weyl.py | 22 ++++-- .../test_two_qubit_decomposition.py | 76 +++++++++++++++++++ 2 files changed, 93 insertions(+), 5 deletions(-) diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py index a31a5ea..b9edfef 100644 --- a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py @@ -36,6 +36,7 @@ import numpy as np from numpy.typing import NDArray import scipy.linalg # type: ignore +import warnings """ Define the M matrix from section III to tranform the unitary matrix into the magic basis: @@ -448,7 +449,17 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ NDArray[np.complex128], float ]: - """ Decompose a two-qubit unitary matrix into the Weyl coordinates and the product of two single-qubit unitaries. + """ Decompose a two-qubit unitary matrix into the Weyl coordinates and the + product of two single-qubit unitaries. + + Notes + ----- + M2 diagnolization may be wrong due to floating point errors, but it will work + correctly in Linux. Should you encounter a failure in the code, and the result + fails to encode the correct unitary matrix, please report it at + https://github.com/Qualition/quick/issues/11 + + with the unitary matrix that caused the failure. Parameters ---------- @@ -477,9 +488,10 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ Raises ------ ValueError - - If the diagonalization of M2 fails. - - If the determinant of the right or left component is not in the expected range. - - If the decomposition fails due to a deviation from the expected unitary matrix. + - If the determinant of the right or left component is + not in the expected range. + - If the decomposition fails due to a deviation from the + expected unitary matrix. Usage ----- @@ -498,7 +510,7 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ # Given P is a real-symmetric unitary matrix we only use transpose if not np.allclose(P.dot(np.diag(D)).dot(P.T), M2, rtol=0, atol=1e-13): - raise ValueError( + warnings.warn( "Failed to diagonalize M2." "Kindly report this at https://github.com/Qualition/quick/issues/11: " f"U: {U}" diff --git a/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py b/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py index 409b2e3..cfccf39 100644 --- a/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py +++ b/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py @@ -17,6 +17,7 @@ __all__ = ["TestTwoQubitDecomposition"] import numpy as np +from numpy.typing import NDArray from numpy.testing import assert_almost_equal import pytest from scipy.stats import unitary_group @@ -252,6 +253,81 @@ def test_decomp3_supercontrolled(self) -> None: # Check that the number of CX gates is 3 or less assert num_cx_gates <= 3 + @pytest.mark.parametrize("unitary", [ + np.array([ + [2./3, 1./3 + 1.j/3, 7 * np.sqrt(17)/51, (-1 - 1.j) * np.sqrt(17)/51], + [1./3 - 1.j/3, -1./3, (-1 + 1.j) * np.sqrt(17)/51, 10 * np.sqrt(17)/51], + [7*np.sqrt(17)/51, (-1 - 1.j) * np.sqrt(17)/51, -2./3, -1./3 - 1.j/3], + [(-1 + 1.j) * np.sqrt(17)/51, 10 * np.sqrt(17)/51, -1./3 + 1.j/3, 1./3] + ]), + np.array([ + [ + 0.043602684126304955 + -0.4986216208233326j, -0.22461079447224863 + -0.09679517443671315j, + 0.15592626697527698 + 0.13943283228059802j, -0.803224008021238 + 0.027067469117215155j + ], + [ + -0.09679517443669944 + 0.22461079447226426j, 0.4986216208232763 + 0.043602684126298856j, + 0.02706746911718458 + 0.8032240080213412j, -0.13943283228056091 + 0.15592626697531453j + ], + [ + 0.13943283228059844 + -0.15592626697527806j, 0.027067469117214762 + 0.8032240080212403j, + 0.49862162082333095 + 0.0436026841263046j, 0.09679517443671384 + -0.22461079447224833j + ], + [ + 0.8032240080213434 + -0.027067469117184207j, 0.1559262669753156 + 0.13943283228056125j, + -0.2246107944722639 + -0.09679517443670013j, -0.043602684126298495 + 0.4986216208232746j + ] + ]), + np.array([ + [-0.62695238, -0.1993407 , -0.63291226, -0.40818632], + [-0.62695237, -0.42741604, 0.61214869, 0.2225314 ], + [-0.32700263, 0.43412304, -0.31468734, 0.77818914], + [ 0.32700264, -0.76753892, -0.35449671, 0.42223851] + ]), + np.array([ + [ + -0.3812064266367201 + 0.38120642663672005j, -0.08953682318096808 + 0.08953682318096806j, + -0.5214184531408846 + 0.5214184531408846j, -0.27347323579354943 + 0.2734732357935494j, + ], + [ + 0.11105398348218393 - 0.11105398348218391j, 0.145430219977459 - 0.14543021997745897j, + 0.23096365206101888 - 0.23096365206101882j, -0.6427852354467597 + 0.6427852354467596j, + ], + [ + -0.544932087007239 + 0.5449320870072389j, -0.16786656959437854 + 0.16786656959437854j, + 0.41778724529336947 - 0.4177872452933694j, 0.01799033088883041 - 0.017990330888830407j, + ], + [ + -0.213067345160641 + 0.21306734516064096j, 0.6653224962721628 - 0.6653224962721627j, + -0.0152448403255109 + 0.015244840325510897j, 0.10823991063233353 - 0.10823991063233351j, + ], + ]) + ]) + def test_m2_correctness( + self, + unitary: NDArray[np.complex128] + ) -> None: + """ Test the correctness of the M2 decomposition. This tests + many recorded cases of failure of two qubit decomposition on + non-Ubuntu OS. + + Parameters + ---------- + `unitary` : NDArray[np.complex128] + The two qubit unitary to encode. + """ + # Create a two qubit decomposition object + two_qubit_decomposition = TwoQubitDecomposition(output_framework=QiskitCircuit) + + # Initialize a circuit + circuit = QiskitCircuit(2) + + # Apply the decomposition + two_qubit_decomposition.apply_unitary(circuit, unitary, [0, 1]) + + # Check that the circuit is equivalent to the original unitary matrix + assert_almost_equal(circuit.get_unitary(), unitary, decimal=8) + def test_invalid_indices_fail(self) -> None: """ Test that invalid indices fail. """ From d09b3f4025553262d63350707872609433118a74 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Tue, 8 Jul 2025 03:19:58 +0800 Subject: [PATCH 29/58] - Added more unit tests. - Removed rounding from rxr for testing purposes. --- quick/circuit/circuit.py | 5 - quick/circuit/circuit_utils.py | 25 +- ..._6qubits_31405control_RYRX_alternating.npy | Bin 0 -> 65664 bytes ..._6qubits_31405control_RYRX_alternating.npy | Bin 0 -> 65664 bytes ..._6qubits_31405control_RYRX_alternating.npy | Bin 0 -> 65664 bytes ..._6qubits_31405control_RYRX_alternating.npy | Bin 0 -> 65664 bytes tests/circuit/gate_utils.py | 2648 ++++++++++++----- .../test_uniformly_controlled_gates.py | 162 +- 8 files changed, 2029 insertions(+), 811 deletions(-) create mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy create mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_6qubits_31405control_RYRX_alternating.npy create mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy create mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_6qubits_31405control_RYRX_alternating.npy diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index 5183a6d..03b0feb 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -4843,9 +4843,6 @@ def Multiplexor( num_single_qubit_gates = len(single_qubit_gates) - for gate in single_qubit_gates: - print(f"Single qubit gate: {gate}") - # Now, it is easy to place the CX gates and some Hadamards and RZ(pi/2) gates # which are absorbed into the single-qubit unitaries to get back the full decomposition # of the multiplexor @@ -4872,12 +4869,10 @@ def Multiplexor( num_trailing_zeros = len(binary_rep) - len(binary_rep.rstrip("0")) control_index = num_trailing_zeros - # Apply the CX gate if not i == num_single_qubit_gates - 1: self.CX(control_indices[control_index], target_index) self.GlobalPhase(-PI4) - # If `up_to_diagonal` is False, we apply the diagonal gate if not up_to_diagonal: self.Diagonal(diagonal, qubit_indices=[target_index] + list(control_indices)) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 1c3c9ad..abe7c90 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -46,6 +46,8 @@ SQRT2, -SQRT2 ) +EPSILON = 1e-16 + # Type hint for nested lists of floats Params = list[list[float] | float] | list[float] @@ -187,14 +189,26 @@ def extract_uvr_matrices( # Hermitian conjugate of b (Eq 6) X = a @ b.conj().T + print("X:", X) + # Determinant and phase of x det_X = np.linalg.det(X) + + print("det_X:", det_X) + X_11 = X[0, 0] / np.sqrt(det_X) + + print("X_11:", X_11) + phi = np.angle(det_X) + print("phi:", phi) + # Compute the diagonal matrix r arg_X_11 = np.angle(X_11) + print("arg_X_11:", arg_X_11) + # The implementation of the diagonal matrix r is # given below, but it can be chosen freely r_1 = np.exp(1j / 2 * ((np.pi - phi)/2 - arg_X_11)) @@ -204,14 +218,20 @@ def extract_uvr_matrices( [0, r_2] ]) + print("r:", r) + # Eigendecomposition of r @ x @ r (Eq 8) # This is done via reforming Eq 6 to be similar to an eigenvalue decomposition rxr = r @ X @ r - eigenvalues, u = scipy.linalg.eig(np.round(rxr, 16)) # type: ignore + + print("rxr:", rxr) + + eigenvalues, u = np.linalg.eig(rxr) # type: ignore # Handle specific case where the first eigenvalue is near -i # This is done by interchanging the eigenvalues and eigenvectors (Eq 13) - if abs(eigenvalues[0] + 1j) < 1e-10: + print("eigenvalues:", eigenvalues[0] + 1j) + if abs(eigenvalues[0] + 1j) < EPSILON: eigenvalues = np.flipud(eigenvalues) u = np.fliplr(u) @@ -522,7 +542,6 @@ def flatten(array: Params) -> tuple[list[float], Params]: # pragma: no cover return flattened, shape - def reshape( flattened: list[float], shape: Params diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy new file mode 100644 index 0000000000000000000000000000000000000000..ef7f2679f29096b18f990ebe303bb47683d74b56 GIT binary patch literal 65664 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 wfDdGUM!*PqGl4X}Q))j<;Nc*i_@% literal 0 HcmV?d00001 diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy new file mode 100644 index 0000000000000000000000000000000000000000..df4180c7dcefa0b914cb6affb0a5c4b665073424 GIT binary patch literal 65664 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 Date: Tue, 8 Jul 2025 03:37:04 +0800 Subject: [PATCH 30/58] - Minimized test cases for UC to make debugging easier. --- .../test_uniformly_controlled_gates.py | 250 +++++++++--------- 1 file changed, 126 insertions(+), 124 deletions(-) diff --git a/tests/circuit/test_uniformly_controlled_gates.py b/tests/circuit/test_uniformly_controlled_gates.py index ca0d38d..6c326d0 100644 --- a/tests/circuit/test_uniformly_controlled_gates.py +++ b/tests/circuit/test_uniformly_controlled_gates.py @@ -408,127 +408,127 @@ def test_Multiplexor_no_diagonal_no_simplification( # Ensure the unitary matrix is correct assert_almost_equal(circuit.get_unitary(), expected, 8) - @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) - @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ - [ - [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], - [0, 1], - 2, - UC_unitary_matrix_diagonal_no_simplification_3qubits_01control_HXHX - ], - [ - [Hadamard().matrix, PauliY().matrix, Hadamard().matrix, PauliY().matrix], - [1, 0], - 2, - UC_unitary_matrix_diagonal_no_simplification_3qubits_10control_HYHY - ], - [ - [ - RX(np.pi/2).matrix, - RY(np.pi/3).matrix, - RX(np.pi/4).matrix, - RY(np.pi/5).matrix, - RX(np.pi/6).matrix, - RY(np.pi/7).matrix, - RX(np.pi/8).matrix, - RY(np.pi/9).matrix - ], - [0, 2, 3], - 1, - UC_unitary_matrix_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY - ], - [ - [ - RX(np.pi/2).matrix, - RY(np.pi/3).matrix, - RX(np.pi/4).matrix, - RY(np.pi/5).matrix, - RX(np.pi/6).matrix, - RY(np.pi/7).matrix, - RX(np.pi/8).matrix, - RY(np.pi/9).matrix - ], - [2, 1, 3], - 0, - UC_unitary_matrix_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY - ], - [ - [ - RY(np.pi).matrix, - RX(np.pi/2).matrix, - RY(np.pi/3).matrix, - RX(np.pi/4).matrix, - RY(np.pi/5).matrix, - RX(np.pi/6).matrix, - RY(np.pi/7).matrix, - RX(np.pi/8).matrix, - RY(np.pi/9).matrix, - RX(np.pi/10).matrix, - RY(np.pi/11).matrix, - RX(np.pi/12).matrix, - RY(np.pi/13).matrix, - RX(np.pi/14).matrix, - RY(np.pi/15).matrix, - RX(np.pi/16).matrix, - RY(np.pi/17).matrix, - RX(np.pi/18).matrix, - RY(np.pi/19).matrix, - RX(np.pi/20).matrix, - RY(np.pi/21).matrix, - RX(np.pi/22).matrix, - RY(np.pi/23).matrix, - RX(np.pi/24).matrix, - RY(np.pi/25).matrix, - RX(np.pi/26).matrix, - RY(np.pi/27).matrix, - RX(np.pi/28).matrix, - RY(np.pi/29).matrix, - RX(np.pi/30).matrix, - RY(np.pi/31).matrix, - RX(np.pi/32).matrix - ], - [3, 1, 4, 0, 5], - 2, - UC_unitary_matrix_diagonal_no_simplification_6qubits_31405control_RYRX_alternating - ] - ]) - def test_Multiplexor_diagonal_no_simplification( - self, - circuit_framework: type[Circuit], - single_qubit_gates: list[NDArray[np.complex128]], - control_indices: list[int], - target_index: int, - expected: NDArray[np.complex128] - ) -> None: - """ Test the `Multiplexor` gate with diagonal and without simplification. - - Parameters - ---------- - `circuit_framework` : type[quick.circuit.Circuit] - The quantum circuit framework. - `single_qubit_gates` : list[NDArray[np.complex128]] - The single-qubit gates. - `control_indices` : list[int] - The control qubits. - `target_index` : int - The target qubit. - `expected` : NDArray[np.complex128] - The expected unitary matrix. - """ - # Define the quantum circuit - circuit = circuit_framework(len(control_indices) + 1) - - # Apply the Multiplexor gate - circuit.Multiplexor( - single_qubit_gates, - control_indices, - target_index, - up_to_diagonal=True, - multiplexor_simplification=False - ) - - # Ensure the unitary matrix is correct - assert_almost_equal(circuit.get_unitary(), expected, 8) + # @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + # @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ + # [ + # [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], + # [0, 1], + # 2, + # UC_unitary_matrix_diagonal_no_simplification_3qubits_01control_HXHX + # ], + # [ + # [Hadamard().matrix, PauliY().matrix, Hadamard().matrix, PauliY().matrix], + # [1, 0], + # 2, + # UC_unitary_matrix_diagonal_no_simplification_3qubits_10control_HYHY + # ], + # [ + # [ + # RX(np.pi/2).matrix, + # RY(np.pi/3).matrix, + # RX(np.pi/4).matrix, + # RY(np.pi/5).matrix, + # RX(np.pi/6).matrix, + # RY(np.pi/7).matrix, + # RX(np.pi/8).matrix, + # RY(np.pi/9).matrix + # ], + # [0, 2, 3], + # 1, + # UC_unitary_matrix_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY + # ], + # [ + # [ + # RX(np.pi/2).matrix, + # RY(np.pi/3).matrix, + # RX(np.pi/4).matrix, + # RY(np.pi/5).matrix, + # RX(np.pi/6).matrix, + # RY(np.pi/7).matrix, + # RX(np.pi/8).matrix, + # RY(np.pi/9).matrix + # ], + # [2, 1, 3], + # 0, + # UC_unitary_matrix_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY + # ], + # [ + # [ + # RY(np.pi).matrix, + # RX(np.pi/2).matrix, + # RY(np.pi/3).matrix, + # RX(np.pi/4).matrix, + # RY(np.pi/5).matrix, + # RX(np.pi/6).matrix, + # RY(np.pi/7).matrix, + # RX(np.pi/8).matrix, + # RY(np.pi/9).matrix, + # RX(np.pi/10).matrix, + # RY(np.pi/11).matrix, + # RX(np.pi/12).matrix, + # RY(np.pi/13).matrix, + # RX(np.pi/14).matrix, + # RY(np.pi/15).matrix, + # RX(np.pi/16).matrix, + # RY(np.pi/17).matrix, + # RX(np.pi/18).matrix, + # RY(np.pi/19).matrix, + # RX(np.pi/20).matrix, + # RY(np.pi/21).matrix, + # RX(np.pi/22).matrix, + # RY(np.pi/23).matrix, + # RX(np.pi/24).matrix, + # RY(np.pi/25).matrix, + # RX(np.pi/26).matrix, + # RY(np.pi/27).matrix, + # RX(np.pi/28).matrix, + # RY(np.pi/29).matrix, + # RX(np.pi/30).matrix, + # RY(np.pi/31).matrix, + # RX(np.pi/32).matrix + # ], + # [3, 1, 4, 0, 5], + # 2, + # UC_unitary_matrix_diagonal_no_simplification_6qubits_31405control_RYRX_alternating + # ] + # ]) + # def test_Multiplexor_diagonal_no_simplification( + # self, + # circuit_framework: type[Circuit], + # single_qubit_gates: list[NDArray[np.complex128]], + # control_indices: list[int], + # target_index: int, + # expected: NDArray[np.complex128] + # ) -> None: + # """ Test the `Multiplexor` gate with diagonal and without simplification. + + # Parameters + # ---------- + # `circuit_framework` : type[quick.circuit.Circuit] + # The quantum circuit framework. + # `single_qubit_gates` : list[NDArray[np.complex128]] + # The single-qubit gates. + # `control_indices` : list[int] + # The control qubits. + # `target_index` : int + # The target qubit. + # `expected` : NDArray[np.complex128] + # The expected unitary matrix. + # """ + # # Define the quantum circuit + # circuit = circuit_framework(len(control_indices) + 1) + + # # Apply the Multiplexor gate + # circuit.Multiplexor( + # single_qubit_gates, + # control_indices, + # target_index, + # up_to_diagonal=True, + # multiplexor_simplification=False + # ) + + # # Ensure the unitary matrix is correct + # assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ @@ -652,7 +652,7 @@ def test_Multiplexor_no_diagonal_simplification( # Ensure the unitary matrix is correct assert_almost_equal(circuit.get_unitary(), expected, 8) - @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + # @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ [ [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], @@ -738,7 +738,7 @@ def test_Multiplexor_no_diagonal_simplification( ]) def test_Multiplexor_diagonal_simplification( self, - circuit_framework: type[Circuit], + # circuit_framework: type[Circuit], single_qubit_gates: list[NDArray[np.complex128]], control_indices: list[int], target_index: int, @@ -759,8 +759,10 @@ def test_Multiplexor_diagonal_simplification( `expected` : NDArray[np.complex128] The expected unitary matrix. """ + from quick.circuit import QiskitCircuit + # Define the quantum circuit - circuit = circuit_framework(len(control_indices) + 1) + circuit = QiskitCircuit(len(control_indices) + 1) # Apply the Multiplexor gate circuit.Multiplexor( From 952a342f9aef41f63e29838a81bdda26fbe26e04 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Tue, 8 Jul 2025 03:52:37 +0800 Subject: [PATCH 31/58] - Updated workflow to run all workflows. --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 266379b..58bfae5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,6 +9,7 @@ jobs: test: runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] python-version: ["3.10", "3.11", "3.12"] From 5e9277e3f615dc8fe1d25d34c5081ea15e2d0d5d Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Tue, 8 Jul 2025 19:56:24 +0800 Subject: [PATCH 32/58] - Checking diagonal. --- quick/circuit/circuit.py | 2 ++ quick/circuit/circuit_utils.py | 26 ++++++-------------------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index 03b0feb..aa97538 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -4873,6 +4873,8 @@ def Multiplexor( self.CX(control_indices[control_index], target_index) self.GlobalPhase(-PI4) + print("Diagonal:", diagonal) + if not up_to_diagonal: self.Diagonal(diagonal, qubit_indices=[target_index] + list(control_indices)) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index abe7c90..e8d0a9b 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -46,7 +46,7 @@ SQRT2, -SQRT2 ) -EPSILON = 1e-16 +EPSILON = 1e-10 # Type hint for nested lists of floats Params = list[list[float] | float] | list[float] @@ -189,26 +189,14 @@ def extract_uvr_matrices( # Hermitian conjugate of b (Eq 6) X = a @ b.conj().T - print("X:", X) - # Determinant and phase of x det_X = np.linalg.det(X) - - print("det_X:", det_X) - X_11 = X[0, 0] / np.sqrt(det_X) - - print("X_11:", X_11) - phi = np.angle(det_X) - print("phi:", phi) - # Compute the diagonal matrix r arg_X_11 = np.angle(X_11) - print("arg_X_11:", arg_X_11) - # The implementation of the diagonal matrix r is # given below, but it can be chosen freely r_1 = np.exp(1j / 2 * ((np.pi - phi)/2 - arg_X_11)) @@ -218,27 +206,25 @@ def extract_uvr_matrices( [0, r_2] ]) - print("r:", r) - # Eigendecomposition of r @ x @ r (Eq 8) # This is done via reforming Eq 6 to be similar to an eigenvalue decomposition rxr = r @ X @ r - print("rxr:", rxr) - eigenvalues, u = np.linalg.eig(rxr) # type: ignore # Handle specific case where the first eigenvalue is near -i # This is done by interchanging the eigenvalues and eigenvectors (Eq 13) - print("eigenvalues:", eigenvalues[0] + 1j) if abs(eigenvalues[0] + 1j) < EPSILON: eigenvalues = np.flipud(eigenvalues) u = np.fliplr(u) - diagonal = np.diag(np.sqrt(eigenvalues)) + diagonal = np.array([ + [RZ_PI2_00, 0], + [0, RZ_PI2_11] + ]) # Calculate v based on the decomposition (Eq 7) - v = diagonal @ np.conj(u).T @ np.conj(r).T @ b + v = diagonal @ u.conj().T @ r.conj().T @ b return v, u, r # type: ignore From 11748eb8b644f9b8b0c34a95869ed1125c35f3bf Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Tue, 8 Jul 2025 21:22:39 +0800 Subject: [PATCH 33/58] - Checking why diagonal is different on different os. --- quick/circuit/circuit.py | 2 -- quick/circuit/circuit_utils.py | 5 +++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index aa97538..03b0feb 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -4873,8 +4873,6 @@ def Multiplexor( self.CX(control_indices[control_index], target_index) self.GlobalPhase(-PI4) - print("Diagonal:", diagonal) - if not up_to_diagonal: self.Diagonal(diagonal, qubit_indices=[target_index] + list(control_indices)) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index e8d0a9b..5b57738 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -206,6 +206,8 @@ def extract_uvr_matrices( [0, r_2] ]) + print("r:", r) + # Eigendecomposition of r @ x @ r (Eq 8) # This is done via reforming Eq 6 to be similar to an eigenvalue decomposition rxr = r @ X @ r @@ -226,6 +228,9 @@ def extract_uvr_matrices( # Calculate v based on the decomposition (Eq 7) v = diagonal @ u.conj().T @ r.conj().T @ b + print("v:", v) + print("u:", u) + return v, u, r # type: ignore def extract_single_qubits_and_diagonal( From 25a30826f42b0c9d3e51af166cdd0a818cb9c3f2 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Wed, 9 Jul 2025 01:37:21 +0800 Subject: [PATCH 34/58] - Additional prints to pinpoint divergence. --- quick/circuit/circuit_utils.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 5b57738..ca28bb6 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -39,11 +39,11 @@ """ SQRT2 = 1/np.sqrt(2) -RZ_PI2_00 = complex( - SQRT2, SQRT2 +RZ_PI2_00 = np.complex128( + SQRT2 + SQRT2 * 1j ) -RZ_PI2_11 = complex( - SQRT2, -SQRT2 +RZ_PI2_11 = np.complex128( + SQRT2 - SQRT2 * 1j ) EPSILON = 1e-10 @@ -186,17 +186,28 @@ def extract_uvr_matrices( `r` : NDArray[np.complex128] The diagonal matrix r. """ + print("a:", a) + print("b:", b) + # Hermitian conjugate of b (Eq 6) X = a @ b.conj().T + print("X:", X) + # Determinant and phase of x det_X = np.linalg.det(X) X_11 = X[0, 0] / np.sqrt(det_X) phi = np.angle(det_X) + print("det_X:", det_X) + print("X_11:", X_11) + print("phi:", phi) + # Compute the diagonal matrix r arg_X_11 = np.angle(X_11) + print("arg_X_11:", arg_X_11) + # The implementation of the diagonal matrix r is # given below, but it can be chosen freely r_1 = np.exp(1j / 2 * ((np.pi - phi)/2 - arg_X_11)) From a91feed3473a7393ff497de7100f7f44d591b84f Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sun, 13 Jul 2025 00:09:20 +0800 Subject: [PATCH 35/58] - Fixed testing for better assertion of the property based on D U' = U where U is the block diagonal of single qubit unitaries. --- quick/circuit/circuit.py | 16 +- quick/circuit/circuit_utils.py | 17 - ..._simplification_3qubits_01control_HXHX.npy | Bin 1152 -> 0 bytes ..._simplification_3qubits_10control_HYHY.npy | Bin 1152 -> 0 bytes ...cation_4qubits_023control_RXRYRXRYRXRY.npy | Bin 4224 -> 0 bytes ...cation_4qubits_213control_RXRYRXRYRXRY.npy | Bin 4224 -> 0 bytes ..._6qubits_31405control_RYRX_alternating.npy | Bin 65664 -> 0 bytes ..._simplification_3qubits_01control_HXHX.npy | Bin 1152 -> 0 bytes ..._simplification_3qubits_10control_HYHY.npy | Bin 1152 -> 0 bytes ...cation_4qubits_023control_RXRYRXRYRXRY.npy | Bin 4224 -> 0 bytes ...cation_4qubits_213control_RXRYRXRYRXRY.npy | Bin 4224 -> 0 bytes ..._6qubits_31405control_RYRX_alternating.npy | Bin 65664 -> 0 bytes ..._simplification_3qubits_01control_HXHX.npy | Bin 1152 -> 0 bytes ..._simplification_3qubits_10control_HYHY.npy | Bin 1152 -> 0 bytes ...cation_4qubits_023control_RXRYRXRYRXRY.npy | Bin 4224 -> 0 bytes ...cation_4qubits_213control_RXRYRXRYRXRY.npy | Bin 4224 -> 0 bytes ..._6qubits_31405control_RYRX_alternating.npy | Bin 65664 -> 0 bytes ..._simplification_3qubits_01control_HXHX.npy | Bin 1152 -> 0 bytes ..._simplification_3qubits_10control_HYHY.npy | Bin 1152 -> 0 bytes ...cation_4qubits_023control_RXRYRXRYRXRY.npy | Bin 4224 -> 0 bytes ...cation_4qubits_213control_RXRYRXRYRXRY.npy | Bin 4224 -> 0 bytes ..._6qubits_31405control_RYRX_alternating.npy | Bin 65664 -> 0 bytes tests/circuit/gate_utils.py | 554 ----------------- .../test_uniformly_controlled_gates.py | 557 +++--------------- 24 files changed, 94 insertions(+), 1050 deletions(-) delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_3qubits_01control_HXHX.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_3qubits_10control_HYHY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_3qubits_01control_HXHX.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_3qubits_10control_HYHY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_4qubits_023control_RXRYRXRYRXRY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_6qubits_31405control_RYRX_alternating.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_3qubits_01control_HXHX.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_3qubits_10control_HYHY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_3qubits_01control_HXHX.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_3qubits_10control_HYHY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_4qubits_023control_RXRYRXRYRXRY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_6qubits_31405control_RYRX_alternating.npy diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index 03b0feb..35b0fff 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -4657,7 +4657,7 @@ def _Diagonal( # Repeatedly apply the decomposition of Theorem 7 from [1] while num_diagonal_entries >= 2: - rz_angles = [] + rz_angles: list[float] = [] # Extract the RZ angles and the relative phase # between the two diagonal entries @@ -4788,6 +4788,12 @@ def Multiplexor( ... [[0, 1], ... [1, 0]]], multiplexor_simplification=False, control_state="01") """ + # If there are no control indices, we use the ZYZ decomposition + # to apply the single qubit gate + if not control_indices: + self.unitary(single_qubit_gates[0], target_index) + return + if isinstance(control_indices, int): control_indices = [control_indices] @@ -4825,15 +4831,9 @@ def Multiplexor( # based on [2] if multiplexor_simplification: new_controls, single_qubit_gates = simplify(single_qubit_gates, num_controls) - control_indices = [qubits[len(control_indices) + 1 - i] for i in new_controls] + control_indices = [qubits[num_controls + 1 - i] for i in new_controls] control_indices.reverse() - # If there are no control indices, we use the ZYZ decomposition - # to apply the single qubit gate - if not control_indices: - self.unitary(single_qubit_gates[0], target_index) - return - # If there is at least one control qubit, we decompose the multiplexor # into a sequence of single-qubit gates and CX gates single_qubit_gates, diagonal = extract_single_qubits_and_diagonal( diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index ca28bb6..607f736 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -31,7 +31,6 @@ import numpy as np from numpy.typing import NDArray -import scipy.linalg # type: ignore """ Constants for decomposing multiplexed RZ gates from Bergholm et al. These are the (0, 0) and (1, 1) elements of the RZ gate matrix with angle -pi/2 @@ -186,28 +185,17 @@ def extract_uvr_matrices( `r` : NDArray[np.complex128] The diagonal matrix r. """ - print("a:", a) - print("b:", b) - # Hermitian conjugate of b (Eq 6) X = a @ b.conj().T - print("X:", X) - # Determinant and phase of x det_X = np.linalg.det(X) X_11 = X[0, 0] / np.sqrt(det_X) phi = np.angle(det_X) - print("det_X:", det_X) - print("X_11:", X_11) - print("phi:", phi) - # Compute the diagonal matrix r arg_X_11 = np.angle(X_11) - print("arg_X_11:", arg_X_11) - # The implementation of the diagonal matrix r is # given below, but it can be chosen freely r_1 = np.exp(1j / 2 * ((np.pi - phi)/2 - arg_X_11)) @@ -217,8 +205,6 @@ def extract_uvr_matrices( [0, r_2] ]) - print("r:", r) - # Eigendecomposition of r @ x @ r (Eq 8) # This is done via reforming Eq 6 to be similar to an eigenvalue decomposition rxr = r @ X @ r @@ -239,9 +225,6 @@ def extract_uvr_matrices( # Calculate v based on the decomposition (Eq 7) v = diagonal @ u.conj().T @ r.conj().T @ b - print("v:", v) - print("u:", u) - return v, u, r # type: ignore def extract_single_qubits_and_diagonal( diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_3qubits_01control_HXHX.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_3qubits_01control_HXHX.npy deleted file mode 100644 index 1e30cd2326f4c21d2194cffdc85214bf8d478bb9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUG7CH(RnmP)#3giN==$Z^9l3 zy#-36yBatDh;@D1yamtfPeEvk!-v}L`2~%?FJ#6)RR4Zhyx|TB;$&dvLEU3doGyea mn17(|p*XxxK;!QiRpM`g4Z@}5v7qkR4|Na8;e%@HggpSQT(FM- diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY.npy deleted file mode 100644 index f9200c3e05fc3e0165ca016336bf259407b91660..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4224 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGhGsenhGv>N3bhL40j>wrDjyn5e`cSt=GmonOPB0tKm#05@w*Up81iV! zhdehz!s^?*Rj=*myzPHK$8)7Ux`#OACLCGy>HdC=KXc}Z8qXc_;c*`tuM#-oUjQO* ze{Y1u{|;!pGDG7TJ^trG(#8HTNIDtv@sI95Qu+5{@1;n!ytUu)?8Ob?U#l>}BOuvp zieBWj{aQM^-PRS~z>p`^zQL~k$7l9u@k#IOJsRixTOD0(kM7<&m0LSD?z_DI#=Pss zx49<``S8$&#%lvKp3(hV07)124`h1d}xw;c?)7@55tOef#IIy?Q^b^8$uEx_LvL-}icI>>Sni z_6M?~6jzt8u}AmMmJ_U(H|3w%A8_nNRq@}>As-&oA@ORj0EuS||2}}G3s^cq50Bx- zPdEyWSNk21c*Y2i7tnM8ODDt4|D^iINk&GiCG@?$0PBWV#fve^4~M#VYnPL4`|n+E zT^I1~#E{P~gu=&X(H-rqviJ59^IS4o4`P-dM>tllzix1Bzrdse3zR)thJ1M3gv6^o p10=SMImQ40 diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY.npy deleted file mode 100644 index 33376d86ae7f4a392cbfed5fb6c2e9d63a9fad61..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4224 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGhGsenhGv>N3bhL40j>wrDjyn5e`cSt=GmonOPB2DfYNs%;`S~|Pk))n6v_US_# z>dywKzY8GYVE+ITF2mmcgwjWZgw?lqt6tmBdE5Vfj^|2y4!H?OR(-m^U*pf5d7{R1 zhkf|$fclde>hF1waM&LP3728-e?sZw$7l9u@k#IOJsRixTOD0(U#D_w$Hsk^_urUz z-S{^5q+uUE9P=Umtb+Qx0um1Uq2V&@{ZA-;=*-HC{B!%A{TFqw?gW!H`wzVDeR#~O zZ~q*&SMR5FUKsY_Gacejdj*KU?H@qH0TwQV!f&|CJITmswS>O67hv7+s(A65{SI~U z)-EU8_TRhSx-Q_|iD92UZbJNN&j9ha{T&uaIKaYXxW_*s|Mk6|8aqezz5RjgD8<#~ zYwWk2V7`(h05P#dhfQADsTnL5VaF_R4bVoa@?7h9j vJeQ2tgKO-MaI9Q^-Qd`Mfk_7zD0{RF`}8qkGT5K?dm#R{4}pe*7c^V|-nu!) diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy deleted file mode 100644 index ef7f2679f29096b18f990ebe303bb47683d74b56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65664 zcmeI5dsI}{8O1M_BxqSgX?Vm$9U2uhpaU3QqGtew5w)SZNWroq53x3jG8CsqgPQTN zH1QRpCAB7+S(OH8iI9|-LX|TWOH4x}qR=$Dg5==~Wf0#{YR7x-^zVGLrqj#Jo($EA=XktNROlY_!bg7}#SejS7*igEp z@WD4y^NN-i+P=PgSzbw@?enmhY2lifX_LY=Yc!6%Mo9AO^db3!u19AI<nyd%9Z^XQ4u?~5@8=*uiATZJV2JhZo zYd)jT_hk6!9RyTHAZ5(1ij9r7`xAfsZiVql2d)qF1ue!YnY?fCZgzKr1$v;e^NVsk zi@=3pKq-1daiNw>1Hm)?h9V=+Cu1o=WQW$>_!B>EV|Vc8)apd%R3g-wbHK-{KIZ9 zc&4n4TTVWi`?y~|^uUew23qkv0zOCINNwz}=|S(6afWdE^+8d~s)`Xunz&?MkXw8+ z1A5?jTL>Mih=3(X`1Z#XA5Sf!tj%;; z&JFlPkt0A4i2N`d1l);0Ue@i4t1k30^U4n<_Wx!BT@QTpH%3Z@yhwLsoxZ%CKHr^S zq8AVt;si2BWiAYAv)z9XGMy)cIq-brdF!UErx)-je!?%S<`DE?i0=oK@7V-e4Vk@B z`BqkMkiLEV8wairzS{rx@2eMc|GV8ces=p9^uV*X6S^Pb1U6mzHYM~k+x5Y^v1wixdJP4?WK#JYBUlKl^TAI07&J9?P$Pu6iRDc){ z0xBZVVfXDF!pBof8)=er16D0^1n2=3AjX4$8xeTn^doDk&RCgrGoqm>ivB#)TkmX0 z*|YCME^X=Wj2)N{J#nMGfmS?^Kv8r|fWc~I{b}D{{JWWSJ;+MSHgx&dbLm>|b?>}c z&;!rgLg?6y2#lINP`U3bn;!UX4xSK8*Msm&TX$4F^#PaK#y%1Ck96pP8|@9W;&}v` z2j?aozH0mX3rh?y2kYp1pt)!E`uOO(T*|My{BUH-)K0wACs0(WKK&Z>ovC+k6pUgQQ_l^j7mjiC|{00Ak0D6`StaG;e6FW!0-W}(F9-Q@!KeJ*E zf1|L@Tp7>+J#hIM!~sFTc?5FCYz{a$VEg?C(H|uq)H$FBdsZE;jY{SjDIfp&d_yht zz@JXsHN zqeO1-qLL%1rZE%(0wCaW0{Ir7pT2j&mL~?inxB(Ef1YXCTEC*%A%Ei1NBT&gBt3U| z12`ZEfPm8o%-sHpx#F6YjhtUTkdj2#gSe(!vHZh7a;bV+r*>x&^uTFn1`C0J>j~^E zI#gu3Y-O6PvFlqC>3UFpsBGJqmG5yW#QWC2C!|6TTz?92L=bQqf#a*R6#+M_%yeJM zj!mZPLD>G-rkuVSF5NG_JflPhJ#gBY!9pP50D(%`x3hb~$20N@!6G+UtKS+v>fB*=P2@IEgJF6Bxo~#EMN^VfE1Fqx7~lRXl+wVk^_41g7#*?JssDszdrt# zpZo!O;Jj0Vl|aDd1g3SKpZaK@O%K+roZB+P0X^vQP7O+!%}?cq?(uE5{aqHkKmY`W zECI{s+s6OJcK<>Bg~55|WC!%Xyt4nmzZjpD8!;tdob5gYyg&d1{%-{I!nZT2UH`zx zGio92TSaaVZ_g2^3t&75fPiWU7=>?V(o)&Sll8!)uW2)u{-XA8;t(cLpL!e@rfNtAd*4+TQ}4SS&Bw4abl;^a5%fyN`5=1rhD-cj8- He8UF-Gy-#N diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_3qubits_10control_HYHY.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_3qubits_10control_HYHY.npy deleted file mode 100644 index f6152f0fe5851439ef848bb2dab1f67232286621..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUG7CH(RnmP)#3giN=-~U12zWpxN3bhL40j>wrDjyn5e`cSt=GmonOPB0tKm#05@w*Up81iV! zhdehz!s^?*Rj=*myzPHK$8)7Ux`#OACLCGy>HdC=KXc}Z8qXc_;c*`tuM#-oUjQO* ze{Y1u{|;!pGDG7TJ^trG(#8HTNIDtv@sI95Qu+5{@1;n!ytUu)?8Ob?U#l>}BOuvp zieBWj{aQM^-PRS~z>p`^zQL~k$7l9u@k#IOJsRixTOD0(kM7<&m0LSD?z_DI#=Pss zx49<``S8$&#%lvKp3(hV07)124`h1d}xw;c?)7@55tOef#IIy?Q^b^8$uEx_LvL-}icI>>Sni z_6M?~6jzt8u}AmMmJ_U(H|3w%A8_nNRq@}>As-&oA@ORj0EuS||2}}G3s^cq50Bx- zPdEyWSNk21c*Y2i7tnM8ODDt4|D^iINk&GiCG@?$0PBWV#fve^4~M#VYnPL4`|n+E zT^I1~#E{P~gu=&X(H-rqviJ59^IS4o4`P-dM>tllzix1Bzrdse3zR)thJ1M3gv6^o p10=SMImQ40 diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY.npy deleted file mode 100644 index 33376d86ae7f4a392cbfed5fb6c2e9d63a9fad61..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4224 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGhGsenhGv>N3bhL40j>wrDjyn5e`cSt=GmonOPB2DfYNs%;`S~|Pk))n6v_US_# z>dywKzY8GYVE+ITF2mmcgwjWZgw?lqt6tmBdE5Vfj^|2y4!H?OR(-m^U*pf5d7{R1 zhkf|$fclde>hF1waM&LP3728-e?sZw$7l9u@k#IOJsRixTOD0(U#D_w$Hsk^_urUz z-S{^5q+uUE9P=Umtb+Qx0um1Uq2V&@{ZA-;=*-HC{B!%A{TFqw?gW!H`wzVDeR#~O zZ~q*&SMR5FUKsY_Gacejdj*KU?H@qH0TwQV!f&|CJITmswS>O67hv7+s(A65{SI~U z)-EU8_TRhSx-Q_|iD92UZbJNN&j9ha{T&uaIKaYXxW_*s|Mk6|8aqezz5RjgD8<#~ zYwWk2V7`(h05P#dhfQADsTnL5VaF_R4bVoa@?7h9j vJeQ2tgKO-MaI9Q^-Qd`Mfk_7zD0{RF`}8qkGT5K?dm#R{4}pe*7c^V|-nu!) diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_6qubits_31405control_RYRX_alternating.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_6qubits_31405control_RYRX_alternating.npy deleted file mode 100644 index adee71b7b8f374c4fc4c6cbf92829c58d8c85c97..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65664 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 wfDdGUM!*PqGl4X}Q))j<;Nc*i_@% diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_3qubits_01control_HXHX.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_3qubits_01control_HXHX.npy deleted file mode 100644 index 13aad2cb5b0ef4c6363838c6f42b1fdd6609b680..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUG7CH(RnmP)#3giN=>f(}cMK(bk5TL%h|l5SvlDknV3$D+pEG+<-3K!lyIyRfCm{aY y50N81{h*tP>OOQ?!hCS_|DV4HMibV7Uj=IXFMy^?{CbdOQNw2f#qm$=@Bsk+lZf*G diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_3qubits_10control_HYHY.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_3qubits_10control_HYHY.npy deleted file mode 100644 index 060bc316014a89b8005e8aae058aad56e81e0237..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUG7CH(RnmP)#3giN=6V~-<^AK^o(!`GHb=2?~wR|<`ZN&{Oq&f@NL@ziLW+_!v|s; JJUvnD9suhHlHdRU diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY.npy deleted file mode 100644 index ba5dd7003cf008c318f258552d84cbe5168cea85..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4224 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGhGsenhGv>N3bhL40j|T=^=b1KJhNv2gClz&G&;G#<^V+9esr1P#-HO8 z@uF(mYiM{J!U&HK|3TnBG#n1v3=jWLusMVyeh;FgkKy54e7<-CiC<{^W2&5ml0J?N zllaH_ z_WW6zX|vN3bhL40j|T=^=b1KJhNv2gClz&^ah&)5b^yG{%|KJ*c^iT z_aI7m40nGM_Mc_1(fRBNZ=va92Rwa*tX#M3QSFTplRiSB{d=pP!!e9-4oSLDR>oJ%5&F+U)poVuYlR88+Xb{zVOs z6(c@@8X3(X(Q0$9}D)dp@hc)8vy*|%47fl diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy deleted file mode 100644 index df4180c7dcefa0b914cb6affb0a5c4b665073424..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65664 zcmeI5ZA=tL7{{+xO@l#A4N9xlGaA5#YDx(z-H1VK39+@QRWUK~ti0F=VvkBKid|c4 zYzj!KwO9?IFIs$~h=6Fe98y3KQdAU4#dz{ojzjQWo7z~s*-iGoIpdezng4xLZ}543 z^X#8_{xiF~^WDYoytnK*OPXb4U|f9aDtlnKHBeghdRSnvH83H?{)v6%+7&7GxcDdk z@Yc$t)cC=_PhGuoUHssmgTg|Ctzn_lf~^@=wvSK5v7uY0EWM)a3yay2HSxM)q5PbD z{Nyv>Aj9e;%HjAr2!Mcr2vm>XlKIV-0}-B(WEZs_#HNne{Bn3L(F3`Gdc1*9AQTV~ zo`7vZY)^Wbo9KbXO?^F=dDaKl)b+t>tPg~*C;%`sfjyD4hUOhn*9Q!GP!KgLZpF+h zRSznu^~B8V5IYDMjll1ue^-CM7f}Cx-c~q$|CtL;Y2$&8rt7`esmB|Q2jT$%kqIP` z{$16B66))@+si-brXDXcd*luRMk8>F^zW)3&_}mf?;pr6=m9-Ki~s?{5U3#iyQ&8= z_4Qn-_YdS=>hXpFfj~e21fH3|jyFF~$jk3m^`u8(`+FD(fk5=?zHfU^KI{6RL@E3!d6ZviCG@}ulZqGwCr~8iGxqOCz5E0CiGmjr z(&vIe0dxNz;O!q^e}Rj7KoVjTsFr3i_U~h}^!@?PBZ*x{$es@ZHOel={yjnOAK*L^ zALW27K!A}z@dr_3MyB*L?=QUKnlW>rwpsNN%h7Kz&I5*lfDs7zC+<#|U#&g=ppW`I zQretD?u;uHL=WUD>hVTkfEYjk1PFl%uGnGwLNq<-rM{l$&mYo}v$0WKAK>{1s4EaK zGXZg7A^p3m2gj)U|1dBA0D55lia-V;6YwMb zyQ&Ad)OwJk_YW*?>h*=l?2$VN@J8TFMfBTOd$jWh-PC$QcvL7!!*`y}$T{cWjSunx z0g(ud921wB(Wvb&bTR5dgEBGYwKqx*D$oOwiU+xJN5HoHLDTmis(N5yeICjYseJ!O zQu)I==mB>Xf`mjOaB=GSpR-yuJ)nOd3t?3tJvcs1IWX=d^gyKIL9PZRAS-E%{dvDC}hzi-m}2XYto8;v@}hzkUs zn!r)zE@S_GSMMLlcpl1ABgYp(fI|XpF-zROS2aJelX}1L*Ro|H-FYX7k7#vbeZgUk zASH1K9G=(dxHO=hKX6g&LFi8Bc%LyvsvfjK55y@QWXd6dC6W854eQj_2lV@m+4rnl z-BU`59@s8I4>+t5q$Cc3?B%QOOu4PC4{lJe52lBQmIl=vC3+y&Ko7(z9b{@i0zaJa z^zRX#&~COH5dT>E`oHaY7%dZ4_fSLjU6BGC+x#H}EJDMKQzkk7#y0bxPsarTCs`Lo- zz{ItH&^aMsYnEcRu=VdrdjA0TJ9APHNJD4>`L)vdPPYEtzh37b;QmIT%LvKyLZDt+ zlg-w@r+fPc)ccjZ5JVmzzzu=QGAX>Ct$!c8R_7nccwZYgHGxDxfPuiKcAuN$T$-Oq ze}Ci0qyBY&G5NUaBNk$P!7v9H0s=-Mu;8og*hkv_f=quNsdf90i~P$E6Ftapf*u%U zVi5-jFc3I2=Re=S`_%P;Ouyf_?arRk#T$;P>w`>%Ap{Ho0T3_p`8~KfwLYVipmy=7vB5>EDSS%q^$Z zgIX{DppSYyHz1J+2!Mbk;8T0C)vcXBV0^!`?Jwna?jP;Z^NJln3IZTtUIGnWFW#uR ztL-n)pFi+qKKh)pp>81hOv54Qfq6>-k%NHc{@zzx*7s}c1N!rc1Yxlfnf_98WQF=( z7H|at5HKZyfaK3QCg0cA2lVHWJW05-y%j%6J%?aQ5(pXucp;FlJimvne~ diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_3qubits_10control_HYHY.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_3qubits_10control_HYHY.npy deleted file mode 100644 index 711ae8420e9923bfff65f2eb89de8ec722d771a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUG7CH(RnmP)#3giN=6V~-<^A@|}J;dw8t`gO}fp^bY zaQIBya|TLdcQql=J$ruq2Z8td38^F`Pi^<0hW7%JN3bhL40j|T=^=b1KJhNv2gClz&G&;G#<^V+9esr1P#-HO8 z@uF(mYiM{J!U&HK|3TnBG#n1v3=jWLusMVyeh;FgkKy54e7<-CiC<{^W2&5ml0J?N zllaH_ z_WW6zX|vN3bhL40j|T=^=b1KJhNv2gClz&^ah&)5b^yG{%|KJ*c^iT z_aI7m40nGM_Mc_1(fRBNZ=va92Rwa*tX#M3QSFTplRiSB{d=pP!!e9-4oSLDR>oJ%5&F+U)poVuYlR88+Xb{zVOs z6(c@@8X3(X(Q0$9}D)dp@hc)8vy*|%47fl diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_6qubits_31405control_RYRX_alternating.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_6qubits_31405control_RYRX_alternating.npy deleted file mode 100644 index df4180c7dcefa0b914cb6affb0a5c4b665073424..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65664 zcmeI5ZA=tL7{{+xO@l#A4N9xlGaA5#YDx(z-H1VK39+@QRWUK~ti0F=VvkBKid|c4 zYzj!KwO9?IFIs$~h=6Fe98y3KQdAU4#dz{ojzjQWo7z~s*-iGoIpdezng4xLZ}543 z^X#8_{xiF~^WDYoytnK*OPXb4U|f9aDtlnKHBeghdRSnvH83H?{)v6%+7&7GxcDdk z@Yc$t)cC=_PhGuoUHssmgTg|Ctzn_lf~^@=wvSK5v7uY0EWM)a3yay2HSxM)q5PbD z{Nyv>Aj9e;%HjAr2!Mcr2vm>XlKIV-0}-B(WEZs_#HNne{Bn3L(F3`Gdc1*9AQTV~ zo`7vZY)^Wbo9KbXO?^F=dDaKl)b+t>tPg~*C;%`sfjyD4hUOhn*9Q!GP!KgLZpF+h zRSznu^~B8V5IYDMjll1ue^-CM7f}Cx-c~q$|CtL;Y2$&8rt7`esmB|Q2jT$%kqIP` z{$16B66))@+si-brXDXcd*luRMk8>F^zW)3&_}mf?;pr6=m9-Ki~s?{5U3#iyQ&8= z_4Qn-_YdS=>hXpFfj~e21fH3|jyFF~$jk3m^`u8(`+FD(fk5=?zHfU^KI{6RL@E3!d6ZviCG@}ulZqGwCr~8iGxqOCz5E0CiGmjr z(&vIe0dxNz;O!q^e}Rj7KoVjTsFr3i_U~h}^!@?PBZ*x{$es@ZHOel={yjnOAK*L^ zALW27K!A}z@dr_3MyB*L?=QUKnlW>rwpsNN%h7Kz&I5*lfDs7zC+<#|U#&g=ppW`I zQretD?u;uHL=WUD>hVTkfEYjk1PFl%uGnGwLNq<-rM{l$&mYo}v$0WKAK>{1s4EaK zGXZg7A^p3m2gj)U|1dBA0D55lia-V;6YwMb zyQ&Ad)OwJk_YW*?>h*=l?2$VN@J8TFMfBTOd$jWh-PC$QcvL7!!*`y}$T{cWjSunx z0g(ud921wB(Wvb&bTR5dgEBGYwKqx*D$oOwiU+xJN5HoHLDTmis(N5yeICjYseJ!O zQu)I==mB>Xf`mjOaB=GSpR-yuJ)nOd3t?3tJvcs1IWX=d^gyKIL9PZRAS-E%{dvDC}hzi-m}2XYto8;v@}hzkUs zn!r)zE@S_GSMMLlcpl1ABgYp(fI|XpF-zROS2aJelX}1L*Ro|H-FYX7k7#vbeZgUk zASH1K9G=(dxHO=hKX6g&LFi8Bc%LyvsvfjK55y@QWXd6dC6W854eQj_2lV@m+4rnl z-BU`59@s8I4>+t5q$Cc3?B%QOOu4PC4{lJe52lBQmIl=vC3+y&Ko7(z9b{@i0zaJa z^zRX#&~COH5dT>E`oHaY7%dZ4_fSLjU6BGC+x#H}EJDMKQzkk7#y0bxPsarTCs`Lo- zz{ItH&^aMsYnEcRu=VdrdjA0TJ9APHNJD4>`L)vdPPYEtzh37b;QmIT%LvKyLZDt+ zlg-w@r+fPc)ccjZ5JVmzzzu=QGAX>Ct$!c8R_7nccwZYgHGxDxfPuiKcAuN$T$-Oq ze}Ci0qyBY&G5NUaBNk$P!7v9H0s=-Mu;8og*hkv_f=quNsdf90i~P$E6Ftapf*u%U zVi5-jFc3I2=Re=S`_%P;Ouyf_?arRk#T$;P>w`>%Ap{Ho0T3_p`8~KfwLYVipmy=7vB5>EDSS%q^$Z zgIX{DppSYyHz1J+2!Mbk;8T0C)vcXBV0^!`?Jwna?jP;Z^NJln3IZTtUIGnWFW#uR ztL-n)pFi+qKKh)pp>81hOv54Qfq6>-k%NHc{@zzx*7s}c1N!rc1Yxlfnf_98WQF=( z7H|at5HKZyfaK3QCg0cA2lVHWJW05-y%j%6J%?aQ5(pXucp;FlJimvne~ None: - """ Test the `Multiplexor` gate without diagonal and without simplification. + """ Test the `Multiplexor` gate. Parameters ---------- @@ -386,395 +337,59 @@ def test_Multiplexor_no_diagonal_no_simplification( The quantum circuit framework. `single_qubit_gates` : list[NDArray[np.complex128]] The single-qubit gates. - `control_indices` : list[int] - The control qubits. - `target_index` : int - The target qubit. - `expected` : NDArray[np.complex128] - The expected unitary matrix. + `up_to_diagonal` : bool, optional, default=False + Determines if the gate is implemented up to a diagonal + or if it is decomposed completely. + `multiplexor_simplification` : bool, optional, default=True + Determines if the multiplexor is simplified. """ - # Define the quantum circuit - circuit = circuit_framework(len(control_indices) + 1) - - # Apply the Multiplexor gate - circuit.Multiplexor( - single_qubit_gates, - control_indices, - target_index, - up_to_diagonal=False, - multiplexor_simplification=False + from quick.circuit.circuit_utils import ( + extract_single_qubits_and_diagonal, + simplify ) - # Ensure the unitary matrix is correct - assert_almost_equal(circuit.get_unitary(), expected, 8) - - # @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) - # @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ - # [ - # [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], - # [0, 1], - # 2, - # UC_unitary_matrix_diagonal_no_simplification_3qubits_01control_HXHX - # ], - # [ - # [Hadamard().matrix, PauliY().matrix, Hadamard().matrix, PauliY().matrix], - # [1, 0], - # 2, - # UC_unitary_matrix_diagonal_no_simplification_3qubits_10control_HYHY - # ], - # [ - # [ - # RX(np.pi/2).matrix, - # RY(np.pi/3).matrix, - # RX(np.pi/4).matrix, - # RY(np.pi/5).matrix, - # RX(np.pi/6).matrix, - # RY(np.pi/7).matrix, - # RX(np.pi/8).matrix, - # RY(np.pi/9).matrix - # ], - # [0, 2, 3], - # 1, - # UC_unitary_matrix_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY - # ], - # [ - # [ - # RX(np.pi/2).matrix, - # RY(np.pi/3).matrix, - # RX(np.pi/4).matrix, - # RY(np.pi/5).matrix, - # RX(np.pi/6).matrix, - # RY(np.pi/7).matrix, - # RX(np.pi/8).matrix, - # RY(np.pi/9).matrix - # ], - # [2, 1, 3], - # 0, - # UC_unitary_matrix_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY - # ], - # [ - # [ - # RY(np.pi).matrix, - # RX(np.pi/2).matrix, - # RY(np.pi/3).matrix, - # RX(np.pi/4).matrix, - # RY(np.pi/5).matrix, - # RX(np.pi/6).matrix, - # RY(np.pi/7).matrix, - # RX(np.pi/8).matrix, - # RY(np.pi/9).matrix, - # RX(np.pi/10).matrix, - # RY(np.pi/11).matrix, - # RX(np.pi/12).matrix, - # RY(np.pi/13).matrix, - # RX(np.pi/14).matrix, - # RY(np.pi/15).matrix, - # RX(np.pi/16).matrix, - # RY(np.pi/17).matrix, - # RX(np.pi/18).matrix, - # RY(np.pi/19).matrix, - # RX(np.pi/20).matrix, - # RY(np.pi/21).matrix, - # RX(np.pi/22).matrix, - # RY(np.pi/23).matrix, - # RX(np.pi/24).matrix, - # RY(np.pi/25).matrix, - # RX(np.pi/26).matrix, - # RY(np.pi/27).matrix, - # RX(np.pi/28).matrix, - # RY(np.pi/29).matrix, - # RX(np.pi/30).matrix, - # RY(np.pi/31).matrix, - # RX(np.pi/32).matrix - # ], - # [3, 1, 4, 0, 5], - # 2, - # UC_unitary_matrix_diagonal_no_simplification_6qubits_31405control_RYRX_alternating - # ] - # ]) - # def test_Multiplexor_diagonal_no_simplification( - # self, - # circuit_framework: type[Circuit], - # single_qubit_gates: list[NDArray[np.complex128]], - # control_indices: list[int], - # target_index: int, - # expected: NDArray[np.complex128] - # ) -> None: - # """ Test the `Multiplexor` gate with diagonal and without simplification. - - # Parameters - # ---------- - # `circuit_framework` : type[quick.circuit.Circuit] - # The quantum circuit framework. - # `single_qubit_gates` : list[NDArray[np.complex128]] - # The single-qubit gates. - # `control_indices` : list[int] - # The control qubits. - # `target_index` : int - # The target qubit. - # `expected` : NDArray[np.complex128] - # The expected unitary matrix. - # """ - # # Define the quantum circuit - # circuit = circuit_framework(len(control_indices) + 1) - - # # Apply the Multiplexor gate - # circuit.Multiplexor( - # single_qubit_gates, - # control_indices, - # target_index, - # up_to_diagonal=True, - # multiplexor_simplification=False - # ) - - # # Ensure the unitary matrix is correct - # assert_almost_equal(circuit.get_unitary(), expected, 8) + num_controls = int( + np.log2( + len(single_qubit_gates) + ) + ) - @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) - @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ - [ - [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], - [0, 1], - 2, - UC_unitary_matrix_no_diagonal_simplification_3qubits_01control_HXHX - ], - [ - [Hadamard().matrix, PauliY().matrix, Hadamard().matrix, PauliY().matrix], - [1, 0], - 2, - UC_unitary_matrix_no_diagonal_simplification_3qubits_10control_HYHY - ], - [ - [ - RX(np.pi/2).matrix, - RY(np.pi/3).matrix, - RX(np.pi/4).matrix, - RY(np.pi/5).matrix, - RX(np.pi/6).matrix, - RY(np.pi/7).matrix, - RX(np.pi/8).matrix, - RY(np.pi/9).matrix - ], - [0, 2, 3], - 1, - UC_unitary_matrix_no_diagonal_simplification_4qubits_023control_RXRYRXRYRXRY - ], - [ - [ - RX(np.pi/2).matrix, - RY(np.pi/3).matrix, - RX(np.pi/4).matrix, - RY(np.pi/5).matrix, - RX(np.pi/6).matrix, - RY(np.pi/7).matrix, - RX(np.pi/8).matrix, - RY(np.pi/9).matrix - ], - [2, 1, 3], - 0, - UC_unitary_matrix_no_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY - ], - [ - [ - RY(np.pi).matrix, - RX(np.pi/2).matrix, - RY(np.pi/3).matrix, - RX(np.pi/4).matrix, - RY(np.pi/5).matrix, - RX(np.pi/6).matrix, - RY(np.pi/7).matrix, - RX(np.pi/8).matrix, - RY(np.pi/9).matrix, - RX(np.pi/10).matrix, - RY(np.pi/11).matrix, - RX(np.pi/12).matrix, - RY(np.pi/13).matrix, - RX(np.pi/14).matrix, - RY(np.pi/15).matrix, - RX(np.pi/16).matrix, - RY(np.pi/17).matrix, - RX(np.pi/18).matrix, - RY(np.pi/19).matrix, - RX(np.pi/20).matrix, - RY(np.pi/21).matrix, - RX(np.pi/22).matrix, - RY(np.pi/23).matrix, - RX(np.pi/24).matrix, - RY(np.pi/25).matrix, - RX(np.pi/26).matrix, - RY(np.pi/27).matrix, - RX(np.pi/28).matrix, - RY(np.pi/29).matrix, - RX(np.pi/30).matrix, - RY(np.pi/31).matrix, - RX(np.pi/32).matrix - ], - [3, 1, 4, 0, 5], - 2, - UC_unitary_matrix_no_diagonal_simplification_6qubits_31405control_RYRX_alternating - ] - ]) - def test_Multiplexor_no_diagonal_simplification( - self, - circuit_framework: type[Circuit], - single_qubit_gates: list[NDArray[np.complex128]], - control_indices: list[int], - target_index: int, - expected: NDArray[np.complex128] - ) -> None: - """ Test the `Multiplexor` gate without diagonal and with simplification. + qubits = list(range(num_controls + 1)) + target_index = qubits[0] + control_indices = qubits[1:] - Parameters - ---------- - `circuit_framework` : type[quick.circuit.Circuit] - The quantum circuit framework. - `single_qubit_gates` : list[NDArray[np.complex128]] - The single-qubit gates. - `control_indices` : list[int] - The control qubits. - `target_index` : int - The target qubit. - `expected` : NDArray[np.complex128] - The expected unitary matrix. - """ # Define the quantum circuit - circuit = circuit_framework(len(control_indices) + 1) + circuit = circuit_framework(num_controls + 1) + expected = block_diag(*single_qubit_gates) # Apply the Multiplexor gate circuit.Multiplexor( single_qubit_gates, control_indices, target_index, - up_to_diagonal=False, - multiplexor_simplification=True + up_to_diagonal=up_to_diagonal, + multiplexor_simplification=multiplexor_simplification ) - # Ensure the unitary matrix is correct - assert_almost_equal(circuit.get_unitary(), expected, 8) + unitary = circuit.get_unitary() - # @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) - @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ - [ - [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], - [0, 1], - 2, - UC_unitary_matrix_diagonal_simplification_3qubits_01control_HXHX - ], - [ - [Hadamard().matrix, PauliY().matrix, Hadamard().matrix, PauliY().matrix], - [1, 0], - 2, - UC_unitary_matrix_diagonal_simplification_3qubits_10control_HYHY - ], - [ - [ - RX(np.pi/2).matrix, - RY(np.pi/3).matrix, - RX(np.pi/4).matrix, - RY(np.pi/5).matrix, - RX(np.pi/6).matrix, - RY(np.pi/7).matrix, - RX(np.pi/8).matrix, - RY(np.pi/9).matrix - ], - [0, 2, 3], - 1, - UC_unitary_matrix_diagonal_simplification_4qubits_023control_RXRYRXRYRXRY - ], - [ - [ - RX(np.pi/2).matrix, - RY(np.pi/3).matrix, - RX(np.pi/4).matrix, - RY(np.pi/5).matrix, - RX(np.pi/6).matrix, - RY(np.pi/7).matrix, - RX(np.pi/8).matrix, - RY(np.pi/9).matrix - ], - [2, 1, 3], - 0, - UC_unitary_matrix_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY - ], - [ - [ - RY(np.pi).matrix, - RX(np.pi/2).matrix, - RY(np.pi/3).matrix, - RX(np.pi/4).matrix, - RY(np.pi/5).matrix, - RX(np.pi/6).matrix, - RY(np.pi/7).matrix, - RX(np.pi/8).matrix, - RY(np.pi/9).matrix, - RX(np.pi/10).matrix, - RY(np.pi/11).matrix, - RX(np.pi/12).matrix, - RY(np.pi/13).matrix, - RX(np.pi/14).matrix, - RY(np.pi/15).matrix, - RX(np.pi/16).matrix, - RY(np.pi/17).matrix, - RX(np.pi/18).matrix, - RY(np.pi/19).matrix, - RX(np.pi/20).matrix, - RY(np.pi/21).matrix, - RX(np.pi/22).matrix, - RY(np.pi/23).matrix, - RX(np.pi/24).matrix, - RY(np.pi/25).matrix, - RX(np.pi/26).matrix, - RY(np.pi/27).matrix, - RX(np.pi/28).matrix, - RY(np.pi/29).matrix, - RX(np.pi/30).matrix, - RY(np.pi/31).matrix, - RX(np.pi/32).matrix - ], - [3, 1, 4, 0, 5], - 2, - UC_unitary_matrix_diagonal_simplification_6qubits_31405control_RYRX_alternating - ] - ]) - def test_Multiplexor_diagonal_simplification( - self, - # circuit_framework: type[Circuit], - single_qubit_gates: list[NDArray[np.complex128]], - control_indices: list[int], - target_index: int, - expected: NDArray[np.complex128] - ) -> None: - """ Test the `Multiplexor` gate with diagonal and simplification. - - Parameters - ---------- - `circuit_framework` : type[quick.circuit.Circuit] - The quantum circuit framework. - `single_qubit_gates` : list[NDArray[np.complex128]] - The single-qubit gates. - `control_indices` : list[int] - The control qubits. - `target_index` : int - The target qubit. - `expected` : NDArray[np.complex128] - The expected unitary matrix. - """ - from quick.circuit import QiskitCircuit + if multiplexor_simplification: + new_controls, single_qubit_gates = simplify(single_qubit_gates, num_controls) + control_indices = [qubits[len(qubits) - i] for i in new_controls] + control_indices.reverse() - # Define the quantum circuit - circuit = QiskitCircuit(len(control_indices) + 1) + if up_to_diagonal: + _, diagonal = extract_single_qubits_and_diagonal( + single_qubit_gates, len(control_indices) + 1 + ) + diagonal_circuit = circuit_framework(circuit.num_qubits) + diagonal_circuit.Diagonal(diagonal, [target_index] + control_indices) + diagonal_unitary = diagonal_circuit.get_unitary() - # Apply the Multiplexor gate - circuit.Multiplexor( - single_qubit_gates, - control_indices, - target_index, - up_to_diagonal=True, - multiplexor_simplification=True - ) + unitary = np.dot(diagonal_unitary, unitary) # Ensure the unitary matrix is correct - assert_almost_equal(circuit.get_unitary(), expected, 8) + assert_almost_equal(unitary, expected, 8) @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_PauliMultiplexor_invalid_num_angles( From a784b6e5c5934ad61b57c6d2fbd2f9321fb92a7c Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sun, 13 Jul 2025 00:47:56 +0800 Subject: [PATCH 36/58] - Fixes style for E252. --- quick/backend/backend.py | 10 +++--- quick/backend/qiskit_backends/aer_backend.py | 8 ++--- .../qiskit_backends/fake_ibm_backend.py | 4 +-- quick/circuit/circuit.py | 32 +++++++++---------- quick/circuit/cirqcircuit.py | 2 +- quick/circuit/pennylanecircuit.py | 2 +- quick/circuit/qiskitcircuit.py | 2 +- quick/circuit/quimbcircuit.py | 2 +- quick/circuit/tketcircuit.py | 2 +- quick/compiler/compiler.py | 6 ++-- quick/predicates/predicates.py | 32 +++++++++---------- .../one_qubit_decomposition.py | 2 +- .../two_qubit_decomposition/weyl.py | 4 +-- quick/synthesis/statepreparation/isometry.py | 4 +-- quick/synthesis/statepreparation/mottonen.py | 4 +-- quick/synthesis/statepreparation/shende.py | 4 +-- .../statepreparation/statepreparation.py | 8 ++--- .../synthesis/unitarypreparation/diffusion.py | 10 +++--- .../qiskit_unitary_transpiler.py | 4 +-- .../shannon_decomposition.py | 4 +-- .../qiskit_backends/test_ibm_backend.py | 2 +- 21 files changed, 74 insertions(+), 74 deletions(-) diff --git a/quick/backend/backend.py b/quick/backend/backend.py index 9eec55a..f2c0028 100644 --- a/quick/backend/backend.py +++ b/quick/backend/backend.py @@ -58,7 +58,7 @@ class Backend(ABC): """ def __init__( self, - device: str="CPU" + device: str = "CPU" ) -> None: """ Initialize a `quick.backend.Backend` instance. """ @@ -176,7 +176,7 @@ def get_operator( def get_counts( self, circuit: Circuit, - num_shots: int=1024 + num_shots: int = 1024 ) -> dict[str, int]: """ Get the counts of the backend. @@ -331,7 +331,7 @@ def __init__( self, single_qubit_error: float, two_qubit_error: float, - device: str="CPU" + device: str = "CPU" ) -> None: """ Initialize a `quick.backend.NoisyBackend` instance. """ @@ -380,7 +380,7 @@ class FakeBackend(Backend, ABC): """ def __init__( self, - device: str="CPU" + device: str = "CPU" ) -> None: """ Initialize a `quick.backend.FakeBackend` instance. """ @@ -451,7 +451,7 @@ def get_operator( def get_counts( self, circuit: Circuit, - num_shots: int=1024 + num_shots: int = 1024 ) -> dict[str, int]: """ Get the counts of the backend. diff --git a/quick/backend/qiskit_backends/aer_backend.py b/quick/backend/qiskit_backends/aer_backend.py index ba08866..7d17be9 100644 --- a/quick/backend/qiskit_backends/aer_backend.py +++ b/quick/backend/qiskit_backends/aer_backend.py @@ -87,9 +87,9 @@ class AerBackend(NoisyBackend): """ def __init__( self, - single_qubit_error: float=0.0, - two_qubit_error: float=0.0, - device: str="CPU" + single_qubit_error: float = 0.0, + two_qubit_error: float = 0.0, + device: str = "CPU" ) -> None: super().__init__( @@ -189,7 +189,7 @@ def get_operator( def get_counts( self, circuit: Circuit, - num_shots: int=1024 + num_shots: int = 1024 ) -> dict[str, int]: if len(circuit.measured_qubits) == 0: diff --git a/quick/backend/qiskit_backends/fake_ibm_backend.py b/quick/backend/qiskit_backends/fake_ibm_backend.py index 1f1a4e6..e78d3d9 100644 --- a/quick/backend/qiskit_backends/fake_ibm_backend.py +++ b/quick/backend/qiskit_backends/fake_ibm_backend.py @@ -89,7 +89,7 @@ def __init__( self, hardware_name: str, qiskit_runtime: QiskitRuntimeService, - device: str="CPU" + device: str = "CPU" ) -> None: super().__init__(device=device) @@ -161,7 +161,7 @@ def get_operator( def get_counts( self, circuit: Circuit, - num_shots: int=1024 + num_shots: int = 1024 ) -> dict[str, int]: result = self._counts_backend.run([circuit.circuit], shots=num_shots).result() diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index 35b0fff..d9871c9 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -4718,8 +4718,8 @@ def Multiplexor( single_qubit_gates: list[NDArray[np.complex128]], control_indices: int | Sequence[int], target_index: int, - up_to_diagonal: bool=False, - multiplexor_simplification: bool=True, + up_to_diagonal: bool = False, + multiplexor_simplification: bool = True, control_state: str | None = None ) -> None: """ Apply a multiplexed/uniformly controlled gate to the circuit. @@ -4913,9 +4913,9 @@ def merge_global_phases(self) -> None: def QFT( self, qubit_indices: int | Sequence[int], - do_swaps: bool=True, - approximation_degree: int=0, - inverse: bool=False + do_swaps: bool = True, + approximation_degree: int = 0, + inverse: bool = False ) -> None: r""" Apply the Quantum Fourier Transform to the circuit. @@ -5089,7 +5089,7 @@ def vertical_reverse(self) -> None: @staticmethod def _horizontal_reverse( circuit_log: list[dict[str, Any]], - adjoint: bool=True + adjoint: bool = True ) -> list[dict[str, Any]]: """ Perform a horizontal reverse operation. @@ -5143,7 +5143,7 @@ def _horizontal_reverse( def horizontal_reverse( self, - adjoint: bool=True + adjoint: bool = True ) -> None: """ Perform a horizontal reverse operation. This is equivalent to the adjoint of the circuit if `adjoint=True`. Otherwise, it @@ -5368,7 +5368,7 @@ def get_unitary(self) -> NDArray[np.complex128]: def get_instructions( self, - include_measurements: bool=True + include_measurements: bool = True ) -> list[dict]: """ Get the instructions of the circuit. @@ -5585,7 +5585,7 @@ def remove_measurements( def remove_measurements( self, - inplace: bool=False + inplace: bool = False ) -> Circuit | None: """ Remove the measurement instructions from the circuit. @@ -5612,8 +5612,8 @@ def remove_measurements( def decompose( self, - reps: int=1, - full: bool=False + reps: int = 1, + full: bool = False ) -> Circuit: """ Decompose the gates in the circuit to their implementation gates. @@ -5684,7 +5684,7 @@ def decompose( def transpile( self, - direct_transpile: bool=True, + direct_transpile: bool = True, synthesis_method: UnitaryPreparation | None = None ) -> None: """ Transpile the circuit to U3 and CX gates. @@ -5953,7 +5953,7 @@ def update(self) -> None: @abstractmethod def to_qasm( self, - qasm_version: int=2 + qasm_version: int = 2 ) -> str: """ Convert the circuit to QASM. @@ -6215,7 +6215,7 @@ def draw(self): def plot_histogram( self, - non_zeros_only: bool=False + non_zeros_only: bool = False ) -> plt.Figure: """ Plot the histogram of the circuit. @@ -6338,8 +6338,8 @@ def __eq__( def is_equivalent( self, other_circuit: Circuit, - check_unitary: bool=True, - check_dag: bool=False + check_unitary: bool = True, + check_dag: bool = False ) -> bool: """ Check if the circuit is equivalent to another circuit. diff --git a/quick/circuit/cirqcircuit.py b/quick/circuit/cirqcircuit.py index b140a48..e758bd6 100644 --- a/quick/circuit/cirqcircuit.py +++ b/quick/circuit/cirqcircuit.py @@ -310,7 +310,7 @@ def reset_qubit( def to_qasm( self, - qasm_version: int=2 + qasm_version: int = 2 ) -> str: from quick.circuit import QiskitCircuit diff --git a/quick/circuit/pennylanecircuit.py b/quick/circuit/pennylanecircuit.py index 614214a..3dd5de9 100644 --- a/quick/circuit/pennylanecircuit.py +++ b/quick/circuit/pennylanecircuit.py @@ -306,7 +306,7 @@ def reset_qubit( def to_qasm( self, - qasm_version: int=2 + qasm_version: int = 2 ) -> str: from quick.circuit import QiskitCircuit diff --git a/quick/circuit/qiskitcircuit.py b/quick/circuit/qiskitcircuit.py index 4868d1e..358ae80 100644 --- a/quick/circuit/qiskitcircuit.py +++ b/quick/circuit/qiskitcircuit.py @@ -260,7 +260,7 @@ def reset_qubit( def to_qasm( self, - qasm_version: int=2 + qasm_version: int = 2 ) -> str: if qasm_version == 2: diff --git a/quick/circuit/quimbcircuit.py b/quick/circuit/quimbcircuit.py index a29b6ec..2db772a 100644 --- a/quick/circuit/quimbcircuit.py +++ b/quick/circuit/quimbcircuit.py @@ -279,7 +279,7 @@ def reset_qubit( def to_qasm( self, - qasm_version: int=2 + qasm_version: int = 2 ) -> str: from quick.circuit import QiskitCircuit diff --git a/quick/circuit/tketcircuit.py b/quick/circuit/tketcircuit.py index da4c257..3a6d88f 100644 --- a/quick/circuit/tketcircuit.py +++ b/quick/circuit/tketcircuit.py @@ -273,7 +273,7 @@ def reset_qubit( def to_qasm( self, - qasm_version: int=2 + qasm_version: int = 2 ) -> str: from quick.circuit import QiskitCircuit diff --git a/quick/compiler/compiler.py b/quick/compiler/compiler.py index 0446928..ba62b03 100644 --- a/quick/compiler/compiler.py +++ b/quick/compiler/compiler.py @@ -91,9 +91,9 @@ class Compiler: def __init__( self, circuit_framework: type[Circuit], - state_prep: type[StatePreparation]=Isometry, - unitary_prep: type[UnitaryPreparation]=ShannonDecomposition, - optimizer: Optimizer | None=None + state_prep: type[StatePreparation] = Isometry, + unitary_prep: type[UnitaryPreparation] = ShannonDecomposition, + optimizer: Optimizer | None = None ) -> None: """ Initialize a `quick.compiler.Compiler` object. """ diff --git a/quick/predicates/predicates.py b/quick/predicates/predicates.py index 92dbcc8..d59fc3a 100644 --- a/quick/predicates/predicates.py +++ b/quick/predicates/predicates.py @@ -60,7 +60,7 @@ def _is_power( def is_statevector( statevector: NDArray[np.complex128], - system_size: int=2 + system_size: int = 2 ) -> bool: """ Test if an array is a statevector. @@ -119,8 +119,8 @@ def is_square_matrix(matrix: NDArray[np.complex128]) -> bool: def is_diagonal_matrix( matrix: NDArray[np.complex128], - rtol: float=RTOL_DEFAULT, - atol: float=ATOL_DEFAULT + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT ) -> bool: """ Test if an array is a diagonal matrix. @@ -149,8 +149,8 @@ def is_diagonal_matrix( def is_symmetric_matrix( matrix: NDArray[np.complex128], - rtol: float=RTOL_DEFAULT, - atol: float=ATOL_DEFAULT + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT ) -> bool: """ Test if an array is a symmetric matrix. @@ -179,9 +179,9 @@ def is_symmetric_matrix( def is_identity_matrix( matrix: NDArray[np.complex128], - ignore_phase: bool=False, - rtol: float=RTOL_DEFAULT, - atol: float=ATOL_DEFAULT + ignore_phase: bool = False, + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT ) -> bool: """ Test if an array is an identity matrix. @@ -220,8 +220,8 @@ def is_identity_matrix( def is_unitary_matrix( matrix: NDArray[np.complex128], - rtol: float=RTOL_DEFAULT, - atol: float=ATOL_DEFAULT + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT ) -> bool: """ Test if an array is a unitary matrix. @@ -251,8 +251,8 @@ def is_unitary_matrix( def is_hermitian_matrix( matrix: NDArray[np.complex128], - rtol: float=RTOL_DEFAULT, - atol: float=ATOL_DEFAULT + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT ) -> bool: """ Test if an array is a Hermitian matrix. @@ -281,8 +281,8 @@ def is_hermitian_matrix( def is_positive_semidefinite_matrix( matrix: NDArray[np.complex128], - rtol: float=RTOL_DEFAULT, - atol: float=ATOL_DEFAULT + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT ) -> bool: """ Test if a matrix is positive semidefinite. @@ -316,8 +316,8 @@ def is_positive_semidefinite_matrix( def is_isometry( matrix: NDArray[np.complex128], - rtol: float=RTOL_DEFAULT, - atol: float=ATOL_DEFAULT + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT ) -> bool: """ Test if an array is an isometry. diff --git a/quick/synthesis/gate_decompositions/one_qubit_decomposition.py b/quick/synthesis/gate_decompositions/one_qubit_decomposition.py index 6987b54..d26c9ac 100644 --- a/quick/synthesis/gate_decompositions/one_qubit_decomposition.py +++ b/quick/synthesis/gate_decompositions/one_qubit_decomposition.py @@ -68,7 +68,7 @@ class OneQubitDecomposition(UnitaryPreparation): def __init__( self, output_framework: type[Circuit], - basis: Literal["zyz", "u3"]="u3" + basis: Literal["zyz", "u3"] = "u3" ) -> None: super().__init__(output_framework) diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py index b9edfef..10032f7 100644 --- a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py @@ -86,7 +86,7 @@ def transform_to_magic_basis( U: NDArray[np.complex128], - reverse: bool=False + reverse: bool = False ) -> NDArray[np.complex128]: """ Transform the 4x4 matrix `U` into the magic basis. @@ -186,7 +186,7 @@ def weyl_coordinates(U: NDArray[np.complex128]) -> NDArray[np.float64]: def partition_eigenvalues( eigenvalues: NDArray[np.complex128], - atol: float=1e-13 + atol: float = 1e-13 ) -> list[list[int]]: """ Group the indices of degenerate eigenvalues. diff --git a/quick/synthesis/statepreparation/isometry.py b/quick/synthesis/statepreparation/isometry.py index 4a46d76..f406cec 100644 --- a/quick/synthesis/statepreparation/isometry.py +++ b/quick/synthesis/statepreparation/isometry.py @@ -88,8 +88,8 @@ def apply_state( circuit: Circuit, state: NDArray[np.complex128] | Bra | Ket, qubit_indices: int | Sequence[int], - compression_percentage: float=0.0, - index_type: Literal["row", "snake"]="row" + compression_percentage: float = 0.0, + index_type: Literal["row", "snake"] = "row" ) -> Circuit: if not isinstance(state, (np.ndarray, Bra, Ket)): diff --git a/quick/synthesis/statepreparation/mottonen.py b/quick/synthesis/statepreparation/mottonen.py index 16dd907..94c8e09 100644 --- a/quick/synthesis/statepreparation/mottonen.py +++ b/quick/synthesis/statepreparation/mottonen.py @@ -72,8 +72,8 @@ def apply_state( circuit: Circuit, state: NDArray[np.complex128] | Bra | Ket, qubit_indices: int | Sequence[int], - compression_percentage: float=0.0, - index_type: Literal["row", "snake"]="row" + compression_percentage: float = 0.0, + index_type: Literal["row", "snake"] = "row" ) -> Circuit: if not isinstance(state, (np.ndarray, Bra, Ket)): diff --git a/quick/synthesis/statepreparation/shende.py b/quick/synthesis/statepreparation/shende.py index 2f3059e..51ea24a 100644 --- a/quick/synthesis/statepreparation/shende.py +++ b/quick/synthesis/statepreparation/shende.py @@ -70,8 +70,8 @@ def apply_state( circuit: Circuit, state: NDArray[np.complex128] | Bra | Ket, qubit_indices: int | Sequence[int], - compression_percentage: float=0.0, - index_type: Literal["row", "snake"]="row" + compression_percentage: float = 0.0, + index_type: Literal["row", "snake"] = "row" ) -> Circuit: if not isinstance(state, (np.ndarray, Bra, Ket)): diff --git a/quick/synthesis/statepreparation/statepreparation.py b/quick/synthesis/statepreparation/statepreparation.py index 97dbba8..e9d01a0 100644 --- a/quick/synthesis/statepreparation/statepreparation.py +++ b/quick/synthesis/statepreparation/statepreparation.py @@ -68,8 +68,8 @@ def __init__( def prepare_state( self, state: NDArray[np.complex128] | Bra | Ket, - compression_percentage: float=0.0, - index_type: Literal["row", "snake"]="row" + compression_percentage: float = 0.0, + index_type: Literal["row", "snake"] = "row" ) -> Circuit: """ Prepare the quantum state. @@ -112,8 +112,8 @@ def apply_state( circuit: Circuit, state: NDArray[np.complex128] | Bra | Ket, qubit_indices: int | Sequence[int], - compression_percentage: float=0.0, - index_type: Literal["row", "snake"]="row" + compression_percentage: float = 0.0, + index_type: Literal["row", "snake"] = "row" ) -> Circuit: """ Apply the quantum state to a quantum circuit. diff --git a/quick/synthesis/unitarypreparation/diffusion.py b/quick/synthesis/unitarypreparation/diffusion.py index 6e8de97..545c259 100644 --- a/quick/synthesis/unitarypreparation/diffusion.py +++ b/quick/synthesis/unitarypreparation/diffusion.py @@ -98,11 +98,11 @@ class Diffusion(UnitaryPreparation): def __init__( self, output_framework: type[Circuit], - model: str="Floki00/qc_unitary_3qubit", - prompt: str="Compile using: ['h', 'cx', 'z', 'ccx', 'swap']", - max_num_gates: int=12, - num_samples: int=128, - min_fidelity: float=0.99 + model: str = "Floki00/qc_unitary_3qubit", + prompt: str = "Compile using: ['h', 'cx', 'z', 'ccx', 'swap']", + max_num_gates: int = 12, + num_samples: int = 128, + min_fidelity: float = 0.99 ) -> None: super().__init__(output_framework) diff --git a/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.py b/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.py index e6cabcd..12684a2 100644 --- a/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.py +++ b/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.py @@ -97,8 +97,8 @@ class QiskitUnitaryTranspiler(UnitaryPreparation): def __init__( self, output_framework: type[Circuit], - ai_transpilation: bool=False, - unitary_synthesis_plugin: str="default", + ai_transpilation: bool = False, + unitary_synthesis_plugin: str = "default", service: QiskitRuntimeService | None = None, backend_name: str | None = None ) -> None: diff --git a/quick/synthesis/unitarypreparation/shannon_decomposition.py b/quick/synthesis/unitarypreparation/shannon_decomposition.py index 518f027..e5fccbb 100644 --- a/quick/synthesis/unitarypreparation/shannon_decomposition.py +++ b/quick/synthesis/unitarypreparation/shannon_decomposition.py @@ -142,7 +142,7 @@ def quantum_shannon_decomposition( circuit: Circuit, qubit_indices: list[int], unitary: NDArray[np.complex128], - recursion_depth: int=0 + recursion_depth: int = 0 ) -> None: """ Decompose n-qubit unitary into CX/RY/RZ/CX gates, preserving global phase. @@ -226,7 +226,7 @@ def demultiplexor( demux_qubits: list[int], unitary_1: NDArray[np.complex128], unitary_2: NDArray[np.complex128], - recursion_depth: int=0 + recursion_depth: int = 0 ) -> None: """ Decompose a multiplexor defined by a pair of unitary matrices operating on the same subspace per Theorem 12. diff --git a/tests/backend/qiskit_backends/test_ibm_backend.py b/tests/backend/qiskit_backends/test_ibm_backend.py index c7f73bd..b6380fe 100644 --- a/tests/backend/qiskit_backends/test_ibm_backend.py +++ b/tests/backend/qiskit_backends/test_ibm_backend.py @@ -97,7 +97,7 @@ def get_operator( def get_counts( self, circuit: Circuit, - num_shots: int=1024 + num_shots: int = 1024 ) -> dict[str, int]: # Create a copy of the circuit as measurement is applied inplace From 5c93153af1851143412cdd554c7b9eedea9c1b42 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 14 Jul 2025 19:15:11 +0800 Subject: [PATCH 37/58] - Added type hints to `repetition_verify` in `quick.circuit.circuit_utils`. - Removed deprecated `typing.Callable` and replaced with `Callable` from `collections.abc`. --- quick/circuit/circuit.py | 2 +- quick/circuit/circuit_utils.py | 45 ++++++++++--------- quick/circuit/cirqcircuit.py | 4 +- quick/circuit/from_framework/from_tket.py | 8 ++-- quick/circuit/pennylanecircuit.py | 4 +- quick/circuit/qiskitcircuit.py | 4 +- quick/circuit/quimbcircuit.py | 4 +- quick/circuit/tketcircuit.py | 12 ++--- quick/optimizer/tket2optimizer.py | 2 +- .../synthesis/unitarypreparation/diffusion.py | 5 ++- 10 files changed, 46 insertions(+), 44 deletions(-) diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index d9871c9..38dd217 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -35,7 +35,7 @@ import qiskit # type: ignore import cirq # type: ignore import pennylane as qml # type: ignore -import pytket +import pytket # type: ignore import quimb.tensor as qtn # type: ignore if TYPE_CHECKING: diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 607f736..ef3721f 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -336,29 +336,28 @@ def simplify( ------- `new_controls` : set[int] The new set of controls. - `new_mux` : list[NDArray[np.complex128]] + `mux_copy` : list[NDArray[np.complex128]] The new list of single qubit gates. """ - c: set[int] = set() - nc: set[int] = set() + multiplexer_controls: set[int] = set() + removed_control_indices: set[int] = set() mux_copy = single_qubit_gates.copy() # Add the position of the multiplexer controls to the set c for i in range(num_controls): - c.add(i + 1) + multiplexer_controls.add(i + 1) # Identify repetitions in the array and return the unnecessary # controls and a copy of the array, marking the repeated operators # as null if len(single_qubit_gates) > 1: - nc, mux_copy = repetition_search(single_qubit_gates, num_controls) + removed_control_indices, mux_copy = repetition_search(single_qubit_gates, num_controls) # Remove the unnecessary controls and the marked operators, creating # a new set of controls and a new array representing the simplified multiplexer - controls_tree = {x for x in c if x not in nc} - mux_tree = [gate for gate in mux_copy if gate is not None] + controls_tree = {x for x in multiplexer_controls if x not in removed_control_indices} - return controls_tree, mux_tree + return controls_tree, mux_copy def repetition_search( multiplexor: list[NDArray[np.complex128]], @@ -384,13 +383,13 @@ def repetition_search( Returns ------- - `nc` : set[int] + `removed_control_indices` : set[int] The set of removed controls. `mux_copy` : list[NDArray[np.complex128]] The new list of gates. """ mux_copy = multiplexor.copy() - nc = set() + removed_control_indices = set() d = 1 # The positions of the multiplexer whose indices are a power of two @@ -433,16 +432,16 @@ def repetition_search( # and add it to the set of unnecessary controls if disentanglement: removed_control_index = level - np.log2(d) - nc.add(removed_control_index) + removed_control_indices.add(removed_control_index) d *= 2 - return nc, mux_copy + return removed_control_indices, mux_copy def repetition_verify( - base, - d, - multiplexor, - mux_copy + base: int, + d: int, + multiplexor: list[NDArray[np.complex128]], + mux_copy: list[NDArray[np.complex128]] ) -> tuple[bool, list[NDArray[np.complex128]]]: """ Verify if the repetitions are valid. This is done by comparing each pair of operators with a distance d between them. @@ -454,7 +453,7 @@ def repetition_verify( Notes ----- The implementation of this simplification is based on the paper - by by de Carvalho et al. [1]. The pseudocode is provided in Algorithm 3. + by de Carvalho et al. [1]. The pseudocode is provided in Algorithm 3. [1] de Carvalho, Batista, de Veras, Araujo, da Silva, Quantum multiplexer simplification for state preparation (2024). @@ -484,9 +483,11 @@ def repetition_verify( while i < d: if not np.allclose(multiplexor[base], multiplexor[next_base]): return False, mux_copy - mux_copy[next_base] = None + mux_copy[next_base] = None # type: ignore base, next_base, i = base + 1, next_base + 1, i + 1 + mux_copy = [gate for gate in mux_copy if gate is not None] + return True, mux_copy def flatten(array: Params) -> tuple[list[float], Params]: # pragma: no cover @@ -495,14 +496,14 @@ def flatten(array: Params) -> tuple[list[float], Params]: # pragma: no cover Parameters ---------- - `array` : Tree + `array` : Params The nested list of floats. Returns ------- `flattened` : list[float] The flattened list of parameters. - `shape` : Tree + `shape` : Params The shape of the original array. """ flattened: list[float] = [] @@ -537,12 +538,12 @@ def reshape( ---------- `flattened` : list[float] The flat list of floats. - `shape` : Tree + `shape` : Params The shape instruction. Returns ------- - `reshaped` : Tree + `reshaped` : Params The reshaped list of floats. """ reshaped: Params = [] diff --git a/quick/circuit/cirqcircuit.py b/quick/circuit/cirqcircuit.py index e758bd6..ec49b23 100644 --- a/quick/circuit/cirqcircuit.py +++ b/quick/circuit/cirqcircuit.py @@ -19,10 +19,10 @@ __all__ = ["CirqCircuit"] -from collections.abc import Sequence +from collections.abc import Callable, Sequence import numpy as np from numpy.typing import NDArray -from typing import Callable, TYPE_CHECKING +from typing import TYPE_CHECKING import cirq from cirq.ops import Rx, Ry, Rz, X, Y, Z, H, S, T, I diff --git a/quick/circuit/from_framework/from_tket.py b/quick/circuit/from_framework/from_tket.py index 5cd1249..98027c6 100644 --- a/quick/circuit/from_framework/from_tket.py +++ b/quick/circuit/from_framework/from_tket.py @@ -20,10 +20,10 @@ __all__ = ["FromTKET"] import numpy as np -from pytket import Circuit as TKCircuit -from pytket._tket.circuit import Command -from pytket import OpType -from pytket.passes import AutoRebase +from pytket import Circuit as TKCircuit # type: ignore +from pytket._tket.circuit import Command # type: ignore +from pytket import OpType # type: ignore +from pytket.passes import AutoRebase # type: ignore from typing import TYPE_CHECKING if TYPE_CHECKING: diff --git a/quick/circuit/pennylanecircuit.py b/quick/circuit/pennylanecircuit.py index 3dd5de9..fc75bee 100644 --- a/quick/circuit/pennylanecircuit.py +++ b/quick/circuit/pennylanecircuit.py @@ -19,11 +19,11 @@ __all__ = ["PennylaneCircuit"] -from collections.abc import Sequence +from collections.abc import Callable, Sequence import copy import numpy as np from numpy.typing import NDArray -from typing import Callable, TYPE_CHECKING +from typing import TYPE_CHECKING import pennylane as qml # type: ignore diff --git a/quick/circuit/qiskitcircuit.py b/quick/circuit/qiskitcircuit.py index 358ae80..d795454 100644 --- a/quick/circuit/qiskitcircuit.py +++ b/quick/circuit/qiskitcircuit.py @@ -19,10 +19,10 @@ __all__ = ["QiskitCircuit"] -from collections.abc import Sequence +from collections.abc import Callable, Sequence import numpy as np from numpy.typing import NDArray -from typing import Callable, TYPE_CHECKING +from typing import TYPE_CHECKING from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister # type: ignore from qiskit.circuit.library import ( # type: ignore diff --git a/quick/circuit/quimbcircuit.py b/quick/circuit/quimbcircuit.py index 2db772a..f1119b1 100644 --- a/quick/circuit/quimbcircuit.py +++ b/quick/circuit/quimbcircuit.py @@ -19,10 +19,10 @@ __all__ = ["QuimbCircuit"] -from collections.abc import Sequence +from collections.abc import Callable, Sequence import numpy as np from numpy.typing import NDArray -from typing import Callable, TYPE_CHECKING +from typing import TYPE_CHECKING import quimb.tensor as qtn # type: ignore from quimb.gates import I, X, Y, Z, H, S, T, RX, RY, RZ, U3 # type: ignore diff --git a/quick/circuit/tketcircuit.py b/quick/circuit/tketcircuit.py index 3a6d88f..111948f 100644 --- a/quick/circuit/tketcircuit.py +++ b/quick/circuit/tketcircuit.py @@ -19,15 +19,15 @@ __all__ = ["TKETCircuit"] -from collections.abc import Sequence +from collections.abc import Callable, Sequence import numpy as np from numpy.typing import NDArray -from typing import Callable, TYPE_CHECKING +from typing import TYPE_CHECKING -from pytket import Circuit as TKCircuit -from pytket import OpType -from pytket.circuit import Op, QControlBox -from pytket.extensions.qiskit import AerBackend, AerStateBackend +from pytket import Circuit as TKCircuit # type: ignore +from pytket import OpType # type: ignore +from pytket.circuit import Op, QControlBox # type: ignore +from pytket.extensions.qiskit import AerBackend, AerStateBackend # type: ignore if TYPE_CHECKING: from quick.backend import Backend diff --git a/quick/optimizer/tket2optimizer.py b/quick/optimizer/tket2optimizer.py index 89df9f7..efb5105 100644 --- a/quick/optimizer/tket2optimizer.py +++ b/quick/optimizer/tket2optimizer.py @@ -19,7 +19,7 @@ __all__ = ["TKET2Optimizer"] -from tket2.passes import badger_pass +from tket2.passes import badger_pass # type: ignore from quick.circuit import Circuit, TKETCircuit from quick.optimizer.optimizer import Optimizer diff --git a/quick/synthesis/unitarypreparation/diffusion.py b/quick/synthesis/unitarypreparation/diffusion.py index 545c259..4dfd21c 100644 --- a/quick/synthesis/unitarypreparation/diffusion.py +++ b/quick/synthesis/unitarypreparation/diffusion.py @@ -19,14 +19,15 @@ __all__ = ["Diffusion"] +from collections.abc import Sequence from genQC.pipeline.diffusion_pipeline import DiffusionPipeline # type: ignore from genQC.inference.infer_compilation import generate_comp_tensors, convert_tensors_to_circuits # type: ignore import genQC.util as util # type: ignore import numpy as np from numpy.typing import NDArray from qiskit.quantum_info import Operator as QiskitOperator # type: ignore -import torch -from typing import Sequence, SupportsIndex, TYPE_CHECKING +import torch # type: ignore +from typing import SupportsIndex, TYPE_CHECKING import quick if TYPE_CHECKING: From 7ee77a8ad10d49255ff0bb04bfd6235430461eec Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sun, 20 Jul 2025 18:37:59 +0800 Subject: [PATCH 38/58] - Added `calculate_entanglement_entropy()` and `calculate_entanglement_entropy_slope()` to `quick.metrics`. - Added testers for `calculate_entanglement_entropy` and `calculate_entanglement_entropy_slope`. - Added `is_density_matrix()` to `quick.predicates`. - Added testers for `quick.predicates.is_density_matrix()`. - Added `generate_random_density_matrix()` to `quick.random`. - Added testers for `quick.random.generate_random_density_matrix()`. --- quick/metrics/__init__.py | 2 + quick/metrics/metrics.py | 186 ++++++++++++++++++++-------- quick/predicates/__init__.py | 6 +- quick/predicates/predicates.py | 53 +++++++- quick/random/__init__.py | 9 +- quick/random/random.py | 74 ++++++++++- tests/metrics/area_law_slope.npy | Bin 0 -> 136 bytes tests/metrics/test_metrics.py | 80 ++++++++++-- tests/metrics/volume_law_slope.npy | Bin 0 -> 136 bytes tests/predicates/test_predicates.py | 68 ++++++---- tests/random/test_random.py | 54 +++++++- 11 files changed, 435 insertions(+), 97 deletions(-) create mode 100644 tests/metrics/area_law_slope.npy create mode 100644 tests/metrics/volume_law_slope.npy diff --git a/quick/metrics/__init__.py b/quick/metrics/__init__.py index f496d44..2a80faa 100644 --- a/quick/metrics/__init__.py +++ b/quick/metrics/__init__.py @@ -16,6 +16,7 @@ "calculate_entanglement_range", "calculate_shannon_entropy", "calculate_entanglement_entropy", + "calculate_entanglement_entropy_slope", "calculate_hilbert_schmidt_test" ] @@ -23,5 +24,6 @@ calculate_entanglement_range, calculate_shannon_entropy, calculate_entanglement_entropy, + calculate_entanglement_entropy_slope, calculate_hilbert_schmidt_test ) \ No newline at end of file diff --git a/quick/metrics/metrics.py b/quick/metrics/metrics.py index 056fe1d..a6ba4c0 100644 --- a/quick/metrics/metrics.py +++ b/quick/metrics/metrics.py @@ -21,83 +21,68 @@ "calculate_entanglement_range", "calculate_shannon_entropy", "calculate_entanglement_entropy", + "calculate_entanglement_entropy_slope", "calculate_hilbert_schmidt_test" ] import numpy as np from numpy.typing import NDArray import quimb.tensor as qtn # type: ignore +from qiskit.quantum_info import partial_trace # type: ignore -from quick.predicates import is_unitary_matrix +from quick.predicates import is_density_matrix, is_statevector, is_unitary_matrix -def _get_submps_indices(mps: qtn.MatrixProductState) -> list[tuple[int, int]]: - """ Get the indices of contiguous blocks in the MPS. For testing purposes, - this method is static. +def _calculate_1d_entanglement_range(mps: qtn.MatrixProductState) -> list[tuple[int, int]]: + """ Get the entanglement range for entangled qubits in a 1D chain + by checking the virtual (bond) dimensions of the tensors at each + site in the MPS. - Notes - ----- - Certain sites may not be entangled with the rest, and thus we can simply apply - a single qubit gate to them as opposed to a two qubit gate. - - This reduces the overall cost of the circuit for a given layer. If all sites are - entangled, then the method will simply return the indices of the MPS, i.e., for - 10 qubit system [(0, 9)]. If sites 0 and 1 are not entangled at all with the rest, - the method will return [(0, 0), (1,1), (2, 9)]. - - The implementation is based on the analytical decomposition [1]. - - For more information, refer to the publication below: - [1] Shi-Ju. - Encoding of Matrix Product States into Quantum Circuits of One- and Two-Qubit Gates (2020). - https://arxiv.org/abs/1908.07958 + Parameters + ---------- + `mps` : qtn.MatrixProductState + The MPS representation of the quantum state. Returns ------- - `submps_indices` : list[tuple[int, int]] - The indices of the MPS contiguous blocks. - - Usage - ----- - >>> mps.get_submps_indices() + `entangled_blocks_indices` : list[tuple[int, int]] + The indices of the MPS entangled blocks. """ - sub_mps_indices: list[tuple[int, int]] = [] + entangled_blocks_indices: list[tuple[int, int]] = [] if mps.L == 1: return [(0, 0)] for site in range(mps.L): - # Reset the dimension variables for each iteration dim_left, dim_right = 1, 1 # Define the dimensions for each site # The first and last sites are connected to only one site # as opposed to the other sites in the middle which are connected # to two sites to their left and right - # # | # ●━━ `dim_right` if site == 0: _, dim_right = mps[site].shape # type: ignore - # + # | # `dim_left` ━━● elif site == (mps.L - 1): dim_left, _ = mps[site].shape # type: ignore - # + # | # `dim_left` ━━●━━ `dim_right` else: dim_left, _, dim_right = mps[site].shape # type: ignore if dim_left < 2 and dim_right < 2: - sub_mps_indices.append((site, site)) + entangled_blocks_indices.append((site, site)) elif dim_left < 2 and dim_right >= 2: temp = site elif dim_left >= 2 and dim_right < 2: - sub_mps_indices.append((temp, site)) + entangled_blocks_indices.append((temp, site)) - return sub_mps_indices + return entangled_blocks_indices def calculate_entanglement_range(statevector: NDArray[np.complex128]) -> list[tuple[int, int]]: """ Get the entanglements of the circuit. @@ -112,11 +97,18 @@ def calculate_entanglement_range(statevector: NDArray[np.complex128]) -> list[tu list[tuple[int, int]] The entanglements of the circuit. + Raises + ------ + ValueError + - If the input is not a statevector. + Usage ----- >>> entanglements = get_entanglements(statevector) """ - statevector = statevector.flatten() + if not is_statevector(statevector): + raise ValueError("The input must be a statevector.") + num_qubits = int(np.log2(statevector.size)) # We need to have the statevector in MSB order for @@ -127,15 +119,15 @@ def calculate_entanglement_range(statevector: NDArray[np.complex128]) -> list[tu .flatten() ) - return _get_submps_indices(qtn.MatrixProductState.from_dense(statevector)) + return _calculate_1d_entanglement_range(qtn.MatrixProductState.from_dense(statevector)) -def calculate_shannon_entropy(statevector: NDArray[np.complex128]) -> float: - """ Calculate the Shannon entropy. +def calculate_shannon_entropy(probability_vector: NDArray[np.complex128]) -> float: + """ Calculate the Shannon entropy of a probability vector. Parameters ---------- - `statevector` : NDArray[np.complex128] - The statevector of the circuit. + `probability_vector` : NDArray[np.complex128] + The probability vector. Returns ------- @@ -146,30 +138,124 @@ def calculate_shannon_entropy(statevector: NDArray[np.complex128]) -> float: ----- >>> shannon_entropy = calculate_shannon_entropy(statevector) """ - statevector = statevector[(0 < statevector) & (statevector < 1)] - return -np.sum(statevector * np.log2(statevector)).astype(float) + probability_vector = probability_vector[(0 < probability_vector) & (probability_vector < 1)] + return -np.sum(probability_vector * np.log2(probability_vector)).astype(float) -def calculate_entanglement_entropy(statevector: NDArray[np.complex128]) -> float: - """ Calculate the entanglement entropy of the circuit. +def calculate_entanglement_entropy(data: NDArray[np.complex128]) -> float: + """ Calculate the Von Neumann entanglement entropy from the + density matrix. In case of statevectors the entropy is simply + 0. Parameters ---------- - `statevector` : NDArray[np.complex128] - The statevector of the circuit. + `data` : NDArray[np.complex128] + The data, which can be a statevector or a density matrix. Returns ------- float - The entanglement entropy of the circuit. + The entanglement entropy of the data. + + Raises + ------ + ValueError + - Input dimension matches a statevector but is not a valid statevector. + - The input is not a valid density matrix. Usage ----- - >>> entanglement_entropy = calculate_entanglement_entropy(statevector) + >>> entanglement_entropy = calculate_entanglement_entropy(data) """ - density_matrix = np.outer(statevector, statevector.conj()) - eigenvalues = np.maximum(np.real(np.linalg.eigvals(density_matrix)), 0.0) + # Handle the case of statevectors + # and ensure the density matrix is + # valid + if data.ndim == 1: + if is_statevector(data): + return 0.0 + else: + raise ValueError( + "Input dimension matches a statevector " + "but is not a valid statevector." + ) + if data.ndim == 2: + if data.shape[1] == 1: + if is_statevector(data): + return 0.0 + else: + raise ValueError( + "Input dimension matches a statevector " + "but is not a valid statevector." + ) + else: + if not is_density_matrix(data): + raise ValueError("The input is not a valid density matrix.") + + eigenvalues = np.maximum(np.real(np.linalg.eigvals(data)), 0.0) return calculate_shannon_entropy(eigenvalues) +def calculate_entanglement_entropy_slope(statevector: NDArray[np.complex128]) -> float: + """ Calculate the slope of the entanglement entropy. This is + used to determine whether a state is area-law or volume-law + entangled, which is a measure of how the entanglement entropy + scales with the number of qubits. + + If the slope is 1, which is a straight line, then the state is + volume-law entangled. If the entropy decays after a while and + forms a decaying curve, then the state is area-law entangled. + + Parameters + ---------- + `statevector` : NDArray[np.complex128] + The statevector of the circuit. + + Returns + ------- + `slope` : float + The slope of the entanglement entropy of the circuit. + + Raises + ------ + ValueError + - The input must be a statevector. + + Usage + ----- + >>> entanglement_entropy_slope = calculate_entanglement_entropy_slope(statevector) + """ + if not is_statevector(statevector): + raise ValueError("The input must be a statevector.") + + num_qubits = int( + np.ceil( + np.log2(len(statevector)) + ) + ) + + max_k = num_qubits // 2 + entropies = np.empty(max_k, dtype=np.float64) + + for k in range(1, max_k + 1): + # Trace out rest of the qubits to extract the + # reduced density matrix for the first k qubits + rho_A = partial_trace(statevector, list(range(k, num_qubits))) # type: ignore + S = calculate_entanglement_entropy(rho_A.data) + entropies[k - 1] = S + + # We use half of the entropies to calculate the slope + # for efficiency + entropies = entropies[len(entropies) // 2:] + x = np.arange(1, len(entropies) + 1) + + x_mean = np.mean(x) + y_mean = np.mean(entropies) + + numerator = np.sum((x - x_mean) * (entropies - y_mean)) + denominator = np.sum((x - x_mean) ** 2) + + slope = numerator / denominator if denominator != 0 else 0 + + return float(slope) + def calculate_hilbert_schmidt_test( unitary_1: NDArray[np.complex128], unitary_2: NDArray[np.complex128] diff --git a/quick/predicates/__init__.py b/quick/predicates/__init__.py index 7f7729e..7423163 100644 --- a/quick/predicates/__init__.py +++ b/quick/predicates/__init__.py @@ -21,7 +21,8 @@ "is_unitary_matrix", "is_hermitian_matrix", "is_positive_semidefinite_matrix", - "is_isometry" + "is_isometry", + "is_density_matrix" ] from quick.predicates.predicates import ( @@ -33,5 +34,6 @@ is_unitary_matrix, is_hermitian_matrix, is_positive_semidefinite_matrix, - is_isometry + is_isometry, + is_density_matrix ) \ No newline at end of file diff --git a/quick/predicates/predicates.py b/quick/predicates/predicates.py index d59fc3a..16fbfff 100644 --- a/quick/predicates/predicates.py +++ b/quick/predicates/predicates.py @@ -26,7 +26,8 @@ "is_unitary_matrix", "is_hermitian_matrix", "is_positive_semidefinite_matrix", - "is_isometry" + "is_isometry", + "is_density_matrix" ] import numpy as np @@ -60,7 +61,9 @@ def _is_power( def is_statevector( statevector: NDArray[np.complex128], - system_size: int = 2 + system_size: int = 2, + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT ) -> bool: """ Test if an array is a statevector. @@ -72,6 +75,10 @@ def is_statevector( The size of the quantum memory. If the size is 2, then the system uses qubits. If the size is 3, then the system uses qutrits, and so on. + `rtol` : float, optional, default=RTOL_DEFAULT + The relative tolerance parameter. + `atol` : float, optional, default=ATOL_DEFAULT + The absolute tolerance parameter. Returns ------- @@ -93,7 +100,11 @@ def is_statevector( if not _is_power(system_size, len(statevector)): return False - return np.linalg.norm(statevector) == 1 and statevector.ndim == 1 and len(statevector) > 1 + return ( + bool(np.isclose(np.linalg.norm(statevector), 1.0, rtol=rtol, atol=atol)) + and statevector.ndim == 1 + and len(statevector) > 1 + ) def is_square_matrix(matrix: NDArray[np.complex128]) -> bool: """ Test if an array is a square matrix. @@ -344,4 +355,38 @@ def is_isometry( identity = np.eye(matrix.shape[1]) matrix = matrix.conj().T @ matrix - return np.allclose(matrix, identity, rtol=rtol, atol=atol) \ No newline at end of file + return np.allclose(matrix, identity, rtol=rtol, atol=atol) + +def is_density_matrix( + rho: NDArray[np.complex128], + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT + ) -> bool: + """ Test if an array is a density matrix. + + Parameters + ---------- + `rho` : NDArray[np.complex128] + The input matrix. + `rtol` : float, optional, default=RTOL_DEFAULT + The relative tolerance parameter. + `atol` : float, optional, default=ATOL_DEFAULT + The absolute tolerance parameter. + + Returns + ------- + bool + True if the matrix is a density matrix, False otherwise. + + Usage + ----- + >>> is_density_matrix(np.eye(2)) + """ + if not ( + is_hermitian_matrix(rho, rtol=rtol, atol=atol) + and is_positive_semidefinite_matrix(rho, rtol=rtol, atol=atol) + and np.isclose(np.trace(rho), 1.0, rtol=rtol, atol=atol) + ): + return False + + return True \ No newline at end of file diff --git a/quick/random/__init__.py b/quick/random/__init__.py index b2f6305..ad5e766 100644 --- a/quick/random/__init__.py +++ b/quick/random/__init__.py @@ -14,7 +14,12 @@ __all__ = [ "generate_random_state", - "generate_random_unitary" + "generate_random_unitary", + "generate_random_density_matrix" ] -from quick.random.random import generate_random_state, generate_random_unitary \ No newline at end of file +from quick.random.random import ( + generate_random_state, + generate_random_unitary, + generate_random_density_matrix +) \ No newline at end of file diff --git a/quick/random/random.py b/quick/random/random.py index d848c93..a37f29d 100644 --- a/quick/random/random.py +++ b/quick/random/random.py @@ -16,14 +16,39 @@ __all__ = [ "generate_random_state", - "generate_random_unitary" + "generate_random_unitary", + "generate_random_density_matrix" ] import numpy as np from numpy.typing import NDArray from scipy.stats import unitary_group # type: ignore +from typing import Literal +def _generate_ginibre_matrix( + num_rows: int, + num_columns: int, + ) -> NDArray[np.complex128]: + """ Return a normally distributed complex random matrix. + + Parameters + ---------- + `num_rows` : int + Number of rows in output matrix. + `num_columns` : int + Number of columns in output matrix. + + Returns + ------- + `ginibre_ensemble` : NDArray[np.complex128] + A complex rectangular matrix where each real and imaginary + entry is sampled from the normal distribution. + """ + rng = np.random.default_rng() + ginibre_ensemble = rng.normal(size=(num_rows, num_columns)) + 1j * rng.normal(size=(num_rows, num_columns)) + return ginibre_ensemble + def generate_random_state(num_qubits: int) -> NDArray[np.complex128]: """ Generate a random state vector for the given number of qubits. @@ -53,4 +78,49 @@ def generate_random_unitary(num_qubits: int) -> NDArray[np.complex128]: `NDArray[np.complex128]` The random unitary matrix. """ - return unitary_group.rvs(2 ** num_qubits).astype(np.complex128) \ No newline at end of file + return unitary_group.rvs(2 ** num_qubits).astype(np.complex128) + +def generate_random_density_matrix( + num_qubits: int, + rank: int | None = None, + generator: Literal["hilbert-schmidt", "bures"] = "hilbert-schmidt" + ) -> NDArray[np.complex128]: + """ Generate a random density matrix. + + Parameters + ---------- + `num_qubits` : int + The number of qubits in the density matrix. + `rank` : int, optional, default=None + The rank of the density matrix. If None, the matrix is full-rank, + where rank is set to the number of qubits. + `generator` : Literal["hilbert-schmidt", "bures"], optional, default="hilbert-schmidt" + The method to use for generating the density matrix. + Options are "hilbert-schmidt" or "bures". + + Returns + ------- + NDArray[np.complex128] + The generated random density matrix. + + Raises + ------ + ValueError + - If the `generator` is not recognized. + """ + if not rank: + rank = num_qubits + + if generator not in ["hilbert-schmidt", "bures"]: + raise ValueError(f"Unrecognized generator method: {generator}") + + ginibre_ensemble = _generate_ginibre_matrix(2**num_qubits, 2**rank) + + if generator == "hilbert-schmidt": + density_matrix = ginibre_ensemble @ ginibre_ensemble.conj().T + elif generator == "bures": + density_matrix = np.eye(2**num_qubits) + generate_random_unitary(num_qubits) + density_matrix = density_matrix @ ginibre_ensemble + density_matrix = density_matrix @ density_matrix.conj().T + + return density_matrix / np.trace(density_matrix) \ No newline at end of file diff --git a/tests/metrics/area_law_slope.npy b/tests/metrics/area_law_slope.npy new file mode 100644 index 0000000000000000000000000000000000000000..05fffd809a8be329f3c7e2d7a3632d2268465da7 GIT binary patch literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I%6ItsN46ag-k^|@D8Ts>(I06O6x*Z=?k literal 0 HcmV?d00001 diff --git a/tests/metrics/test_metrics.py b/tests/metrics/test_metrics.py index 3487d8d..733e2a2 100644 --- a/tests/metrics/test_metrics.py +++ b/tests/metrics/test_metrics.py @@ -56,26 +56,86 @@ def test_calculate_shannon_entropy(self) -> None: assert_almost_equal(1.7736043871504037, calculate_shannon_entropy(data)) - @pytest.mark.parametrize("data", [ - np.array([1, 0]), - np.array([0, 1, 0, 0]), - np.array([0.5, 0.5, 0.5, 0.5]), - np.array([0.5, 0.5j, -0.5j, 0.5]), - np.array([1/np.sqrt(2), 0, 0, -1/np.sqrt(2)* 1j]), - np.array([1/np.sqrt(2)] + (14 * [0]) + [1/np.sqrt(2) * 1j]), + @pytest.mark.parametrize("data, expected", [ + (np.array([1, 0]), 0.0), + (np.array([0, 1, 0, 0]), 0.0), + (np.array([0.5, 0.5, 0.5, 0.5]), 0.0), + (np.array([0.5, 0.5j, -0.5j, 0.5]), 0.0), + (np.array([1/np.sqrt(2), 0, 0, -1/np.sqrt(2)* 1j]), 0.0), + (np.array([1/np.sqrt(2)] + (14 * [0]) + [1/np.sqrt(2) * 1j]), 0.0), + ( + np.array([ + [0.84048555+0.j, 0.0510054-0.02325157j], + [0.0510054+0.02325157j, 0.15951445+0.j] + ], dtype=np.complex128), + 0.6220438669480641 + ), + ( + np.array([ + [0.16521093+0.j, -0.08915021+0.07244625j, -0.14670846-0.10748953j, -0.03544851+0.106916j], + [-0.08915021-0.07244625j, 0.28432666+0.j, -0.01778044+0.15666538j, -0.03049137-0.01306784j], + [-0.14670846+0.10748953j, -0.01778044-0.15666538j, 0.2941435 +0.j, -0.06158772-0.08660825j], + [-0.03544851-0.106916j, -0.03049137+0.01306784j, -0.06158772+0.08660825j, 0.2563189+0.j] + ], dtype=np.complex128), + 1.3705180586061732 + ) ]) def test_calculate_entanglement_entropy( self, - data: NDArray[np.complex128] + data: NDArray[np.complex128], + expected: float ) -> None: """ Test the `calculate_entanglement_entropy` method. Parameters ---------- - data : NDArray[np.complex128] + `data` : NDArray[np.complex128] The statevector of the circuit. + `expected` : float + The expected value. + """ + assert_almost_equal(expected, calculate_entanglement_entropy(data)) + + @pytest.mark.parametrize("data", [ + np.array([1, 2, 3], dtype=np.complex128), + np.array([1, 2, 3, 4], dtype=np.complex128), + np.array([ + [1, 2], + [3, 4] + ], dtype=np.complex128), + np.array([ + [1, 2, 3], + [4, 5, 6] + ], dtype=np.complex128) + ]) + def test_calculate_entanglement_entropy_invalid_data( + self, + data: NDArray[np.complex128] + ) -> None: + """ Test failure of `calculate_entanglement_entropy` with invalid + data values, which are neither density matrix nor statevector. + + Parameters + ---------- + `data` : NDArray[np.complex128] + The data to be tested. + """ + with pytest.raises(ValueError): + calculate_entanglement_entropy(data) + + def test_calculate_entanglement_entropy_slope_area_law_case(self) -> None: + """ Test the `calculate_entanglement_entropy_slope` method with an area-law + entangled state. + """ + area_law_slope = np.load("tests/metrics/area_law_slope.npy") + assert_almost_equal(0.20183287022097673, area_law_slope) + + def test_calculate_entanglement_entropy_slope_volume_law_case(self) -> None: + """ Test the `calculate_entanglement_entropy_slope` method with a volume-law + entangled state. """ - assert_almost_equal(0.0, calculate_entanglement_entropy(data)) + volume_law_slope = np.load("tests/metrics/volume_law_slope.npy") + assert_almost_equal(1.0, volume_law_slope) def test_calculate_hilbert_schmidt_test(self) -> None: """ Test the `calculate_hilbert_schmidt_test` method. diff --git a/tests/metrics/volume_law_slope.npy b/tests/metrics/volume_law_slope.npy new file mode 100644 index 0000000000000000000000000000000000000000..2ba801cb0383657ec50c7249c5069503f5229fd0 GIT binary patch literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= YXCxM+0{I%6ItsN46ag* None: """ Test the `.is_statevector()` method with invalid system size. @@ -104,7 +93,7 @@ def test_is_square_matrix( `expected` : bool The expected output of the function. """ - assert is_square_matrix(array) == expected + assert is_square_matrix(array) is expected @pytest.mark.parametrize("array, expected", [ (np.diag([1, 2, 3]), True), @@ -139,7 +128,7 @@ def test_is_diagonal_matrix( `expected` : bool The expected output of the function. """ - assert is_diagonal_matrix(array) == expected + assert is_diagonal_matrix(array) is expected @pytest.mark.parametrize("array, expected", [ (np.array([ @@ -187,7 +176,7 @@ def test_is_symmetric_matrix( `expected` : bool The expected output of the function. """ - assert is_symmetric_matrix(array) == expected + assert is_symmetric_matrix(array) is expected @pytest.mark.parametrize("array, expected", [ (np.eye(2), True), @@ -213,7 +202,7 @@ def test_is_identity_matrix( `expected` : bool The expected output of the function. """ - assert is_identity_matrix(array) == expected + assert is_identity_matrix(array) is expected @pytest.mark.parametrize("array, expected", [ (unitary_group.rvs(2), True), @@ -239,7 +228,7 @@ def test_is_unitary_matrix( `expected` : bool The expected output of the function. """ - assert is_unitary_matrix(array) == expected + assert is_unitary_matrix(array) is expected @pytest.mark.parametrize("array, expected", [ (np.array([ @@ -283,7 +272,7 @@ def test_is_hermitian_matrix( `expected` : bool The expected output of the function. """ - assert is_hermitian_matrix(array) == expected + assert is_hermitian_matrix(array) is expected @pytest.mark.parametrize("array, expected", [ (np.array([ @@ -325,7 +314,7 @@ def test_is_positive_semidefinite_matrix( `expected` : bool The expected output of the function. """ - assert is_positive_semidefinite_matrix(array) == expected + assert is_positive_semidefinite_matrix(array) is expected @pytest.mark.parametrize("array, expected", [ (np.array([ @@ -362,4 +351,39 @@ def test_is_isometry( `expected` : bool The expected output of the function. """ - assert is_isometry(array) == expected \ No newline at end of file + assert is_isometry(array) is expected + +@pytest.mark.parametrize("array, expected", [ + (np.array([ + [0.55282636+0.j, 0.19339888+0.1369917j], + [0.19339888-0.1369917j, 0.44717364+0.j] + ], dtype=np.complex128), True), + (np.array([ + [0.19834677+0.j, 0.21077084+0.08851485j, 0.07369894-0.03780167j], + [0.21077084-0.08851485j, 0.5556912 +0.j, 0.09721694-0.13294078j], + [0.07369894+0.03780167j, 0.09721694+0.13294078j, 0.24596203+0.j] + ], dtype=np.complex128), True), + (np.array([ + [1, 2 + 1j], + [2 + 1j, 3] + ]), False), + (np.array([ + [1, 2], + [3, 4] + ]), False), + (np.random.rand(2, 3, 3), False) +]) +def test_is_density_matrix( + array: NDArray[np.complex128], + expected: bool + ) -> None: + """ Test the `is_density_matrix` function with various matrices. + + Parameters + ---------- + `array` : NDArray[np.complex128] + The input array to check if it is an isometry. + `expected` : bool + The expected out of the function. + """ + assert is_density_matrix(array) is expected \ No newline at end of file diff --git a/tests/random/test_random.py b/tests/random/test_random.py index 1b8bc2b..6644b44 100644 --- a/tests/random/test_random.py +++ b/tests/random/test_random.py @@ -18,8 +18,12 @@ import pytest -from quick.predicates import is_unitary_matrix -from quick.random import generate_random_state, generate_random_unitary +from quick.predicates import is_unitary_matrix, is_statevector, is_density_matrix +from quick.random import ( + generate_random_state, + generate_random_unitary, + generate_random_density_matrix +) class TestRandom: @@ -40,8 +44,7 @@ def test_generate_random_state( """ state = generate_random_state(num_qubits) - assert state.shape == (2 ** num_qubits,) - assert pytest.approx(1.0) == abs(state @ state.conj()) + assert is_statevector(state) @pytest.mark.parametrize("num_qubits", [1, 2, 3, 4, 5]) def test_generate_random_unitary( @@ -58,4 +61,45 @@ def test_generate_random_unitary( unitary = generate_random_unitary(num_qubits) assert unitary.shape == (2 ** num_qubits, 2 ** num_qubits) - assert is_unitary_matrix(unitary) \ No newline at end of file + assert is_unitary_matrix(unitary) + + @pytest.mark.parametrize("num_qubits", [1, 2, 3, 4, 5]) + @pytest.mark.parametrize("generator", ["hilbert-schmidt", "bures"]) + @pytest.mark.parametrize("rank", [1, 2, 3]) + def test_generate_random_density_matrix( + self, + num_qubits: int, + generator: str, + rank: int + ) -> None: + """ Test the `generate_random_density_matrix` function. + + Parameters + ---------- + `num_qubits` : int + The number of qubits in the density matrix. + `generator` : str + The method to use for generating the density matrix. + `rank` : int + The rank of the density matrix. + """ + density_matrix = generate_random_density_matrix( + num_qubits=num_qubits, + rank=rank, + generator=generator # type: ignore + ) + + assert is_density_matrix(density_matrix) + + def test_generate_random_density_matrix_invalid_generator( + self + ) -> None: + """ Test the `generate_random_density_matrix` function with an + invalid generator. + """ + with pytest.raises(ValueError): + generate_random_density_matrix( + num_qubits=2, + rank=1, + generator="invalid-generator" # type: ignore + ) \ No newline at end of file From c7d5852c614cc6fa93088e91bb595403d71edab9 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 21 Jul 2025 20:56:43 +0800 Subject: [PATCH 39/58] - Added repeat and tensor to `quick.circuit` module via `__mul__` and `__matmul__` respectively. - Added testers for `quick.circuit.Circuit.__mul__` and `quick.circuit.Circuit.__matmul__`. --- quick/circuit/circuit.py | 101 +++++++++++++++++++++++++++++ tests/circuit/test_circuit_base.py | 69 ++++++++++++++++++++ 2 files changed, 170 insertions(+) diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index 38dd217..b0de8e0 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -6251,6 +6251,107 @@ def plot_histogram( return figure + def __mul__( + self, + multiplier: int + ) -> Circuit: + """ Multiply the circuit by an integer to repeat the circuit. + + Parameters + ---------- + `multiplier` : int + The number of times to repeat the circuit. + + Returns + ------- + `new_circuit` : quick.circuit.Circuit + The new circuit with the repeated operations. + + Raises + ------ + TypeError + - The multiplier must be an integer. + + Usage + ----- + >>> new_circuit = circuit * 3 + """ + if not isinstance(multiplier, int): + raise TypeError("The multiplier must be an integer.") + + new_circuit = type(self)(self.num_qubits) + for _ in range(multiplier): + new_circuit.add(self, list(range(self.num_qubits))) + + return new_circuit + + def __rmul__( + self, + multiplier: int + ) -> Circuit: + """ Multiply the circuit by an integer to repeat the circuit. + This is the right-hand side multiplication, allowing for + the syntax `3 * circuit`. + + Parameters + ---------- + `multiplier` : int + The number of times to repeat the circuit. + + Returns + ------- + `new_circuit` : quick.circuit.Circuit + The new circuit with the repeated operations. + + Raises + ------ + TypeError + - The multiplier must be an integer. + + Usage + ----- + >>> new_circuit = 3 * circuit + """ + return self.__mul__(multiplier) + + def __matmul__( + self, + other_circuit: Circuit + ) -> Circuit: + """ Tensor product two circuits together. This is + done by putting the circuits side by side. + + Parameters + ---------- + `other_circuit` : quick.circuit.Circuit + The circuit to tensor product with. + + Returns + ------- + `new_circuit` : quick.circuit.Circuit + The tensor product circuit. + + Raises + ------ + TypeError + - The other circuit must be a `quick.circuit.Circuit`. + + Usage + ----- + >>> new_circuit = circuit @ other_circuit + """ + if not isinstance(other_circuit, Circuit): + raise TypeError("The other circuit must be a `quick.circuit.Circuit`.") + + new_circuit = type(self)(self.num_qubits + other_circuit.num_qubits) + new_circuit.add(self, list(range(self.num_qubits))) + new_circuit.add( + other_circuit, + list(range(self.num_qubits, self.num_qubits + other_circuit.num_qubits)) + ) + + return new_circuit + def __getitem__( self, index: int | slice diff --git a/tests/circuit/test_circuit_base.py b/tests/circuit/test_circuit_base.py index d91c73a..298b2ba 100644 --- a/tests/circuit/test_circuit_base.py +++ b/tests/circuit/test_circuit_base.py @@ -1094,6 +1094,75 @@ def test_remove_measurement( # ensure they are equivalent assert circuit == no_measurement_circuit + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + def test_mul( + self, + circuit_framework: type[Circuit] + ) -> None: + """ Test the repeat of circuits via `__mul__` operator. + + Parameters + ---------- + `circuit_framework`: type[quick.circuit.Circuit] + The circuit framework to test. + """ + # Define the `quick.circuit.Circuit` instance + circuit = circuit_framework(2) + + # Apply a series of gates + circuit.H(0) + circuit.CX(0, 1) + + new_circuit = circuit * 3 + new_circuit_reverse = 3 * circuit + + # Define the equivalent `quick.circuit.Circuit` instance, and + # ensure they are equivalent + checker_circuit = circuit_framework(2) + checker_circuit.H(0) + checker_circuit.CX(0, 1) + checker_circuit.H(0) + checker_circuit.CX(0, 1) + checker_circuit.H(0) + checker_circuit.CX(0, 1) + + assert checker_circuit == new_circuit + assert checker_circuit == new_circuit_reverse + + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + def test_matmul( + self, + circuit_framework: type[Circuit] + ) -> None: + """ Test the tensor product of circuits via `__matmul__` operator. + + Parameters + ---------- + `circuit_framework`: type[quick.circuit.Circuit] + The circuit framework to test. + """ + # Define the `quick.circuit.Circuit` instance + circuit_1 = circuit_framework(3) + circuit_2 = circuit_framework(2) + + # Apply a series of gates + circuit_1.H(0) + circuit_1.CX(0, 2) + circuit_2.Z(0) + circuit_2.CY(1, 0) + + new_circuit = circuit_1 @ circuit_2 + + # Define the equivalent `quick.circuit.Circuit` instance, and + # ensure they are equivalent + checker_circuit = circuit_framework(5) + checker_circuit.H(0) + checker_circuit.CX(0, 2) + checker_circuit.Z(3) + checker_circuit.CY(4, 3) + + assert checker_circuit == new_circuit + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_getitem( self, From a04315f6092095fda23708434972b8818ada08b4 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 21 Jul 2025 22:38:52 +0800 Subject: [PATCH 40/58] - Updated to improve coverage. --- tests/metrics/area_law_slope.npy | Bin 136 -> 0 bytes tests/metrics/area_law_state.npy | Bin 0 -> 16512 bytes tests/metrics/test_metrics.py | 34 +++++++++++++++++++++++++---- tests/metrics/volume_law_slope.npy | Bin 136 -> 0 bytes tests/metrics/volume_law_state.npy | Bin 0 -> 524416 bytes tests/random/test_random.py | 2 +- 6 files changed, 31 insertions(+), 5 deletions(-) delete mode 100644 tests/metrics/area_law_slope.npy create mode 100644 tests/metrics/area_law_state.npy delete mode 100644 tests/metrics/volume_law_slope.npy create mode 100644 tests/metrics/volume_law_state.npy diff --git a/tests/metrics/area_law_slope.npy b/tests/metrics/area_law_slope.npy deleted file mode 100644 index 05fffd809a8be329f3c7e2d7a3632d2268465da7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I%6ItsN46ag-k^|@D8Ts>(I06O6x*Z=?k diff --git a/tests/metrics/area_law_state.npy b/tests/metrics/area_law_state.npy new file mode 100644 index 0000000000000000000000000000000000000000..c227d455e225a6f1c141f19479b94cf9ff3bc112 GIT binary patch literal 16512 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 None: entanglements = calculate_entanglement_range(qc.get_statevector()) assert entanglements == [(0, 1), (2, 2), (3, 5)] + def test_calculate_entanglement_range_single_qubit(self) -> None: + """ Test the `calculate_entanglement_range` method with a single qubit. + """ + qc = QiskitCircuit(1) + qc.H(0) + + entanglements = calculate_entanglement_range(qc.get_statevector()) + assert entanglements == [(0, 0)] + def test_calculate_shannon_entropy(self) -> None: """ Test the `calculate_shannon_entropy` method. """ @@ -63,6 +73,15 @@ def test_calculate_shannon_entropy(self) -> None: (np.array([0.5, 0.5j, -0.5j, 0.5]), 0.0), (np.array([1/np.sqrt(2), 0, 0, -1/np.sqrt(2)* 1j]), 0.0), (np.array([1/np.sqrt(2)] + (14 * [0]) + [1/np.sqrt(2) * 1j]), 0.0), + ( + np.array([ + [0.5], + [0.5], + [0.5], + [0.5] + ]), + 0.0 + ), ( np.array([ [0.84048555+0.j, 0.0510054-0.02325157j], @@ -106,6 +125,11 @@ def test_calculate_entanglement_entropy( np.array([ [1, 2, 3], [4, 5, 6] + ], dtype=np.complex128), + np.array([ + [1], + [2], + [3] ], dtype=np.complex128) ]) def test_calculate_entanglement_entropy_invalid_data( @@ -127,15 +151,17 @@ def test_calculate_entanglement_entropy_slope_area_law_case(self) -> None: """ Test the `calculate_entanglement_entropy_slope` method with an area-law entangled state. """ - area_law_slope = np.load("tests/metrics/area_law_slope.npy") - assert_almost_equal(0.20183287022097673, area_law_slope) + area_law_state = np.load("tests/metrics/area_law_state.npy") + slope = calculate_entanglement_entropy_slope(area_law_state) + assert_almost_equal(0.20183287022097673, slope) def test_calculate_entanglement_entropy_slope_volume_law_case(self) -> None: """ Test the `calculate_entanglement_entropy_slope` method with a volume-law entangled state. """ - volume_law_slope = np.load("tests/metrics/volume_law_slope.npy") - assert_almost_equal(1.0, volume_law_slope) + volume_law_state = np.load("tests/metrics/volume_law_state.npy") + slope = calculate_entanglement_entropy_slope(volume_law_state) + assert_almost_equal(1.0, slope) def test_calculate_hilbert_schmidt_test(self) -> None: """ Test the `calculate_hilbert_schmidt_test` method. diff --git a/tests/metrics/volume_law_slope.npy b/tests/metrics/volume_law_slope.npy deleted file mode 100644 index 2ba801cb0383657ec50c7249c5069503f5229fd0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136 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 literal 0 HcmV?d00001 diff --git a/tests/random/test_random.py b/tests/random/test_random.py index 6644b44..765cdca 100644 --- a/tests/random/test_random.py +++ b/tests/random/test_random.py @@ -65,7 +65,7 @@ def test_generate_random_unitary( @pytest.mark.parametrize("num_qubits", [1, 2, 3, 4, 5]) @pytest.mark.parametrize("generator", ["hilbert-schmidt", "bures"]) - @pytest.mark.parametrize("rank", [1, 2, 3]) + @pytest.mark.parametrize("rank", [1, 2, 3, None]) def test_generate_random_density_matrix( self, num_qubits: int, From f36dee199b7cd17fa6fa7ec17adc2aa9f32c72b3 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Tue, 22 Jul 2025 23:47:14 +0800 Subject: [PATCH 41/58] - Slight fix to `is_statevector` to handle 2 dimensional arrays where the second dimension is equal to 1. --- quick/predicates/predicates.py | 4 ++++ tests/predicates/test_predicates.py | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/quick/predicates/predicates.py b/quick/predicates/predicates.py index 16fbfff..c96ed7f 100644 --- a/quick/predicates/predicates.py +++ b/quick/predicates/predicates.py @@ -100,6 +100,10 @@ def is_statevector( if not _is_power(system_size, len(statevector)): return False + if statevector.ndim == 2: + if statevector.shape[1] == 1: + statevector = statevector.flatten() + return ( bool(np.isclose(np.linalg.norm(statevector), 1.0, rtol=rtol, atol=atol)) and statevector.ndim == 1 diff --git a/tests/predicates/test_predicates.py b/tests/predicates/test_predicates.py index 1b09b44..ddd19b5 100644 --- a/tests/predicates/test_predicates.py +++ b/tests/predicates/test_predicates.py @@ -39,7 +39,8 @@ (np.array([1, 0, 0]), 3, True), (np.array([1, 2]), 2, False), (np.array([1, 2, 3]), 3, False), - (np.array([1, 0, 0, 0]), 2, True) + (np.array([1, 0, 0, 0]), 2, True), + (np.array([[0.5], [0.5], [0.5], [0.5]]), 2, True) ]) def test_is_statevector( array: NDArray[np.complex128], From 692037fc441dd16fee9f123dff810fcc4d05c484 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Wed, 23 Jul 2025 03:29:15 +0800 Subject: [PATCH 42/58] - Changed default value from mutable empty list to None. - Removed `typing.Type`. --- quick/circuit/circuit.py | 4 ++-- quick/circuit/cirqcircuit.py | 8 ++++++-- quick/circuit/pennylanecircuit.py | 8 ++++++-- quick/circuit/qiskitcircuit.py | 18 +++++++++++------- quick/circuit/quimbcircuit.py | 8 ++++++-- quick/circuit/tketcircuit.py | 8 ++++++-- tests/circuit/test_ansatz.py | 1 - tests/circuit/test_circuit_base.py | 7 +++---- tests/circuit/test_circuit_decompose.py | 1 - tests/circuit/test_circuit_to_controlled.py | 1 - tests/circuit/test_control_state.py | 1 - tests/circuit/test_qft_circuit.py | 1 - .../unitarypreparation/test_diffusion.py | 1 - 13 files changed, 40 insertions(+), 27 deletions(-) diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index b0de8e0..80ac95e 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -536,7 +536,7 @@ def _gate_mapping( self, gate: GATES, target_indices: int | Sequence[int], - control_indices: int | Sequence[int] = [], + control_indices: int | Sequence[int] | None = None, angles: Sequence[float] = (0, 0, 0) ) -> None: """ Apply a gate to the circuit. @@ -551,7 +551,7 @@ def _gate_mapping( The gate to apply to the circuit. `target_indices` : int | Sequence[int] The index of the target qubit(s). - `control_indices` : int | Sequence[int], optional, default=[] + `control_indices` : int | Sequence[int], optional, default=None The index of the control qubit(s). `angles` : Sequence[float], optional, default=(0, 0, 0) The rotation angles in radians. diff --git a/quick/circuit/cirqcircuit.py b/quick/circuit/cirqcircuit.py index ec49b23..69123ff 100644 --- a/quick/circuit/cirqcircuit.py +++ b/quick/circuit/cirqcircuit.py @@ -159,12 +159,16 @@ def _gate_mapping( self, gate: GATES, target_indices: int | Sequence[int], - control_indices: int | Sequence[int] = [], + control_indices: int | Sequence[int] | None = None, angles: Sequence[float] = (0, 0, 0) ) -> None: targets = [target_indices] if isinstance(target_indices, int) else list(target_indices) - controls = [control_indices] if isinstance(control_indices, int) else list(control_indices) + + if control_indices is None: + controls: list[int] = [] + else: + controls = [control_indices] if isinstance(control_indices, int) else list(control_indices) # Given Cirq uses MSB convention, we will explicitly # convert the qubit indices to LSB convention diff --git a/quick/circuit/pennylanecircuit.py b/quick/circuit/pennylanecircuit.py index fc75bee..c5f79cf 100644 --- a/quick/circuit/pennylanecircuit.py +++ b/quick/circuit/pennylanecircuit.py @@ -128,12 +128,16 @@ def _gate_mapping( self, gate: GATES, target_indices: int | Sequence[int], - control_indices: int | Sequence[int] = [], + control_indices: int | Sequence[int] | None = None, angles: Sequence[float] = (0, 0, 0) ) -> None: targets = [target_indices] if isinstance(target_indices, int) else list(target_indices) - controls = [control_indices] if isinstance(control_indices, int) else list(control_indices) + + if control_indices is None: + controls: list[int] = [] + else: + controls = [control_indices] if isinstance(control_indices, int) else list(control_indices) # Given Pennylane uses MSB convention, we will explicitly # convert the qubit indices to LSB convention diff --git a/quick/circuit/qiskitcircuit.py b/quick/circuit/qiskitcircuit.py index d795454..c091b37 100644 --- a/quick/circuit/qiskitcircuit.py +++ b/quick/circuit/qiskitcircuit.py @@ -137,23 +137,27 @@ def _gate_mapping( self, gate: GATES, target_indices: int | Sequence[int], - control_indices: int | Sequence[int] = [], + control_indices: int | Sequence[int] | None = None, angles: Sequence[float] = (0, 0, 0) ) -> None: - target_indices = [target_indices] if isinstance(target_indices, int) else target_indices - control_indices = [control_indices] if isinstance(control_indices, int) else control_indices + targets = [target_indices] if isinstance(target_indices, int) else list(target_indices) + + if control_indices is None: + controls: list[int] = [] + else: + controls = [control_indices] if isinstance(control_indices, int) else list(control_indices) # Lazily extract the value of the gate from the mapping to avoid # creating all the gates at once, and to maintain the abstraction gate_operation = self.gate_mapping[gate](angles) - if control_indices: - for target_index in target_indices: - self.circuit.append(gate_operation.control(len(control_indices)), [*control_indices[:], target_index]) + if controls: + for target_index in targets: + self.circuit.append(gate_operation.control(len(controls)), [*controls[:], target_index]) return - for target_index in target_indices: + for target_index in targets: self.circuit.append(gate_operation, [target_index]) def GlobalPhase( diff --git a/quick/circuit/quimbcircuit.py b/quick/circuit/quimbcircuit.py index f1119b1..f9b448d 100644 --- a/quick/circuit/quimbcircuit.py +++ b/quick/circuit/quimbcircuit.py @@ -129,12 +129,16 @@ def _gate_mapping( self, gate: GATES, target_indices: int | Sequence[int], - control_indices: int | Sequence[int] = [], + control_indices: int | Sequence[int] | None = None, angles: Sequence[float] = (0, 0, 0) ) -> None: targets = [target_indices] if isinstance(target_indices, int) else list(target_indices) - controls = [control_indices] if isinstance(control_indices, int) else list(control_indices) + + if control_indices is None: + controls: list[int] = [] + else: + controls = [control_indices] if isinstance(control_indices, int) else list(control_indices) # Given Quimb uses MSB convention, we will explicitly # convert the qubit indices to LSB convention diff --git a/quick/circuit/tketcircuit.py b/quick/circuit/tketcircuit.py index 111948f..be61183 100644 --- a/quick/circuit/tketcircuit.py +++ b/quick/circuit/tketcircuit.py @@ -130,12 +130,16 @@ def _gate_mapping( self, gate: GATES, target_indices: int | Sequence[int], - control_indices: int | Sequence[int] = [], + control_indices: int | Sequence[int] | None = None, angles: Sequence[float] = (0, 0, 0) ) -> None: targets = [target_indices] if isinstance(target_indices, int) else list(target_indices) - controls = [control_indices] if isinstance(control_indices, int) else list(control_indices) + + if control_indices is None: + controls: list[int] = [] + else: + controls = [control_indices] if isinstance(control_indices, int) else list(control_indices) # Given TKET uses MSB convention, we will explicitly # convert the qubit indices to LSB convention diff --git a/tests/circuit/test_ansatz.py b/tests/circuit/test_ansatz.py index db960b9..c2d397e 100644 --- a/tests/circuit/test_ansatz.py +++ b/tests/circuit/test_ansatz.py @@ -18,7 +18,6 @@ import pytest from quick.circuit import Ansatz, Circuit -from typing import Type from tests.circuit import CIRCUIT_FRAMEWORKS diff --git a/tests/circuit/test_circuit_base.py b/tests/circuit/test_circuit_base.py index 298b2ba..c3634c8 100644 --- a/tests/circuit/test_circuit_base.py +++ b/tests/circuit/test_circuit_base.py @@ -19,7 +19,6 @@ import numpy as np from numpy.testing import assert_almost_equal import pytest -from typing import Type from quick.circuit import Circuit from quick.random import generate_random_state, generate_random_unitary @@ -1313,7 +1312,7 @@ def test_eq( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_eq_fail( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `__eq__` dunder method failure. @@ -1360,7 +1359,7 @@ def test_eq_fail( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_eq_invalid_type( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `__eq__` dunder method failure with invalid type. @@ -1377,7 +1376,7 @@ def test_eq_invalid_type( @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_is_equivalent( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `is_equivalent` method. diff --git a/tests/circuit/test_circuit_decompose.py b/tests/circuit/test_circuit_decompose.py index 9ac4a88..305208b 100644 --- a/tests/circuit/test_circuit_decompose.py +++ b/tests/circuit/test_circuit_decompose.py @@ -18,7 +18,6 @@ import numpy as np import pytest -from typing import Type from quick.circuit import Circuit diff --git a/tests/circuit/test_circuit_to_controlled.py b/tests/circuit/test_circuit_to_controlled.py index f2c9a96..b65553e 100644 --- a/tests/circuit/test_circuit_to_controlled.py +++ b/tests/circuit/test_circuit_to_controlled.py @@ -17,7 +17,6 @@ __all__ = ["TestControlled"] import pytest -from typing import Type from quick.circuit import Circuit diff --git a/tests/circuit/test_control_state.py b/tests/circuit/test_control_state.py index 2e121e3..3e2cbe2 100644 --- a/tests/circuit/test_control_state.py +++ b/tests/circuit/test_control_state.py @@ -18,7 +18,6 @@ import numpy as np import pytest -from typing import Type from quick.circuit import Circuit from quick.circuit.gate_matrix import RX diff --git a/tests/circuit/test_qft_circuit.py b/tests/circuit/test_qft_circuit.py index 5cff27b..4c4325c 100644 --- a/tests/circuit/test_qft_circuit.py +++ b/tests/circuit/test_qft_circuit.py @@ -20,7 +20,6 @@ from numpy.testing import assert_almost_equal from numpy.typing import NDArray import pytest -from typing import Type from quick.circuit import Circuit, CirqCircuit, PennylaneCircuit, QiskitCircuit, TKETCircuit diff --git a/tests/synthesis/unitarypreparation/test_diffusion.py b/tests/synthesis/unitarypreparation/test_diffusion.py index b1d698a..5f51f62 100644 --- a/tests/synthesis/unitarypreparation/test_diffusion.py +++ b/tests/synthesis/unitarypreparation/test_diffusion.py @@ -19,7 +19,6 @@ from numpy.testing import assert_almost_equal import pytest import random -from typing import Type from quick.circuit import Circuit, QiskitCircuit from quick.primitives import Operator From 6ee1a26d589a4aff84724c072787514d66da2305 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Wed, 23 Jul 2025 23:59:17 +0800 Subject: [PATCH 43/58] - Removed unused import. --- tests/circuit/test_uniformly_controlled_gates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/circuit/test_uniformly_controlled_gates.py b/tests/circuit/test_uniformly_controlled_gates.py index 867225e..db45b72 100644 --- a/tests/circuit/test_uniformly_controlled_gates.py +++ b/tests/circuit/test_uniformly_controlled_gates.py @@ -23,7 +23,7 @@ from scipy.linalg import block_diag from quick.circuit import Circuit -from quick.circuit.gate_matrix import PauliX, PauliY, Hadamard, RX, RY +from quick.circuit.gate_matrix import PauliX, Hadamard, RX, RY from tests.circuit import CIRCUIT_FRAMEWORKS from tests.circuit.gate_utils import ( From 8e920edcee698a5d9ca8a45212da35a0c6b7c13c Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sat, 26 Jul 2025 01:06:34 +0800 Subject: [PATCH 44/58] - Renamed `quick.compiler.Compiler` to `ShendeCompiler` given specific use-case of the compiler. - Removed `.optimize()` from `ShendeCompiler`. --- quick/compiler/__init__.py | 4 +- .../{compiler.py => shende_compiler.py} | 62 ++------ tests/compiler/__init__.py | 6 +- tests/compiler/test_compiler.py | 138 ------------------ tests/compiler/test_shende_compiler.py | 59 ++++---- 5 files changed, 39 insertions(+), 230 deletions(-) rename quick/compiler/{compiler.py => shende_compiler.py} (82%) delete mode 100644 tests/compiler/test_compiler.py diff --git a/quick/compiler/__init__.py b/quick/compiler/__init__.py index 53e4df9..3b2fdb4 100644 --- a/quick/compiler/__init__.py +++ b/quick/compiler/__init__.py @@ -12,6 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -__all__ = ["Compiler"] +__all__ = ["ShendeCompiler"] -from quick.compiler.compiler import Compiler \ No newline at end of file +from quick.compiler.shende_compiler import ShendeCompiler \ No newline at end of file diff --git a/quick/compiler/compiler.py b/quick/compiler/shende_compiler.py similarity index 82% rename from quick/compiler/compiler.py rename to quick/compiler/shende_compiler.py index ba62b03..358c326 100644 --- a/quick/compiler/compiler.py +++ b/quick/compiler/shende_compiler.py @@ -17,7 +17,7 @@ from __future__ import annotations -__all__ = ["Compiler"] +__all__ = ["ShendeCompiler"] from collections.abc import Sequence import numpy as np @@ -25,27 +25,27 @@ from typing import TypeAlias from quick.circuit import Circuit -from quick.optimizer import Optimizer from quick.primitives import Bra, Ket, Operator from quick.synthesis.statepreparation import StatePreparation, Isometry from quick.synthesis.unitarypreparation import UnitaryPreparation, ShannonDecomposition """ Type aliases for the primitives to be compiled: -- `PRIMITIVE` is a single primitive object, which can be a `Bra`, `Ket`, `Operator`, or a `numpy.ndarray`. +- `PRIMITIVE` is a single primitive object, which can be a `Bra`, `Ket`, `Operator`,or a `numpy.ndarray`. - `PRIMITIVES` is a list of tuples containing the primitive object and the qubits they need to be applied to. """ PRIMITIVE: TypeAlias = Bra | Ket | Operator | NDArray[np.complex128] PRIMITIVES: TypeAlias = list[tuple[PRIMITIVE, Sequence[int]]] -class Compiler: - """ `quick.compiler.Compiler` is the base class for creating quantum compilation passes - from primitives to circuits. The `compile` method is the main interface for the compiler, - which takes in a primitives object and returns a circuit object. +class ShendeCompiler: + """ `quick.compiler.ShendeCompiler` provides compilation of states and unitaries + into circuits using the Shende et al. method. The `compile` method is the main + interface for the compiler, which takes in a primitives object and returns a + circuit object. Notes ----- - To create a custom compiler, subclass `quick.compiler.Compiler` and overwrite the + To create a custom compiler, subclass `quick.compiler.ShendeCompiler` and overwrite the `state_preparation`, `unitary_preparation`, and `compile` methods. The default compiler uses Shende et al for compilation. @@ -61,8 +61,6 @@ class Compiler: The state preparation schema for the compiler. Use `Shende` for the default schema. `unitary_prep` : type[quick.synthesis.unitarypreparation.UnitaryPreparation], optional, default=ShannonDecomposition The unitary preparation schema for the compiler. Use `ShannonDecomposition` for the default schema. - `optimizer` : quick.optimizer.Optimizer, optional, default=None - The optimizer for the compiler. Use `None` for no optimization. Attributes ---------- @@ -72,8 +70,6 @@ class Compiler: The state preparation schema for the compiler. `unitary_prep` : quick.synthesis.unitarypreparation.UnitaryPreparation The unitary preparation schema for the compiler. - `optimizer` : quick.optimizer.Optimizer, optional, default=None - The optimizer for the compiler. Uses `None` for no optimization. Raises ------ @@ -93,7 +89,6 @@ def __init__( circuit_framework: type[Circuit], state_prep: type[StatePreparation] = Isometry, unitary_prep: type[UnitaryPreparation] = ShannonDecomposition, - optimizer: Optimizer | None = None ) -> None: """ Initialize a `quick.compiler.Compiler` object. """ @@ -103,13 +98,10 @@ def __init__( raise TypeError("Invalid state preparation schema.") if not issubclass(unitary_prep, UnitaryPreparation): raise TypeError("Invalid unitary preparation schema.") - if not isinstance(optimizer, (Optimizer, type(None))): - raise TypeError("Invalid optimizer.") self.circuit_framework = circuit_framework self.state_prep = state_prep(circuit_framework) self.unitary_prep = unitary_prep(circuit_framework) - self.optimizer = optimizer def state_preparation( self, @@ -155,37 +147,6 @@ def unitary_preparation( """ return self.unitary_prep.prepare_unitary(unitary) - def optimize( - self, - circuit: Circuit - ) -> Circuit: - """ Optimize the given circuit. - - Parameters - ---------- - `circuit` : quick.circuit.Circuit - The circuit to be optimized. - - Returns - ------- - `optimized_circuit` : quick.circuit.Circuit - The optimized circuit. - - Raises - ------ - ValueError - - If the optimizer is None. - - Usage - ----- - >>> optimized_circuit = compiler.optimize(circuit) - """ - if self.optimizer is None: - raise ValueError("No optimizer is defined. Add an optimizer to use this method.") - - optimized_circuit = self.optimizer.optimize(circuit) - return optimized_circuit - @staticmethod def _check_primitive(primitive: PRIMITIVE) -> None: """ Check if the primitive object is valid. @@ -259,8 +220,8 @@ def _check_primitives(primitives: PRIMITIVES) -> None: preparing the primitive object. """ for primitive, qubit_indices in primitives: - Compiler._check_primitive(primitive) - Compiler._check_primitive_qubits(primitive, qubit_indices) + ShendeCompiler._check_primitive(primitive) + ShendeCompiler._check_primitive_qubits(primitive, qubit_indices) def _compile_primitive( self, @@ -346,7 +307,4 @@ def compile( compiled_circuit = self._compile_primitive(primitive) circuit.add(compiled_circuit, qubits) - if self.optimizer is not None: - circuit = self.optimize(circuit) - return circuit \ No newline at end of file diff --git a/tests/compiler/__init__.py b/tests/compiler/__init__.py index c19f694..ee1a173 100644 --- a/tests/compiler/__init__.py +++ b/tests/compiler/__init__.py @@ -12,10 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -__all__ = [ - "Template", - "TestShendeCompiler" -] +__all__ = ["TestShendeCompiler"] -from tests.compiler.test_compiler import Template from tests.compiler.test_shende_compiler import TestShendeCompiler \ No newline at end of file diff --git a/tests/compiler/test_compiler.py b/tests/compiler/test_compiler.py deleted file mode 100644 index d028b56..0000000 --- a/tests/compiler/test_compiler.py +++ /dev/null @@ -1,138 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -__all__ = ["Template"] - -from abc import ABC, abstractmethod - - -class Template(ABC): - """ `tests.compiler.Template` is the template for creating compiler testers. - """ - @abstractmethod - def test_init(self) -> None: - """ Test the initialization of the compiler. - """ - - @abstractmethod - def test_init_invalid_framework(self) -> None: - """ Test the failure of the initialization of the compiler with an invalid framework. - """ - - @abstractmethod - def test_init_invalid_state_preparation(self) -> None: - """ Test the failure of the initialization of the compiler with an invalid state preparation. - """ - - @abstractmethod - def test_init_invalid_unitary_preparation(self) -> None: - """ Test the failure of the initialization of the compiler with an invalid unitary preparation. - """ - - @abstractmethod - def test_init_invalid_optimizer(self) -> None: - """ Test the failure of the initialization of the compiler with an invalid optimizer. - """ - - @abstractmethod - def test_state_preparation(self) -> None: - """ Test the `compiler.state_preparation()` method. - """ - - @abstractmethod - def test_unitary_preparation(self) -> None: - """ Test the `compiler.unitary_preparation()` method. - """ - - @abstractmethod - def test_optimize(self) -> None: - """ Test the `compiler.optimize()` method. - """ - - @abstractmethod - def test_check_primitive(self) -> None: - """ Test the `compiler._check_primitive()` method. - """ - - @abstractmethod - def test_check_primitive_invalid_primitive(self) -> None: - """ Test the fail check of an invalid primitive. - """ - - @abstractmethod - def test_check_primitive_qubits(self) -> None: - """ Test the `compiler._check_primitive_qubits()` method. - """ - - @abstractmethod - def test_check_primitive_qubits_invalid_qubits(self) -> None: - """ Test the fail check of an invalid qubits. - """ - - @abstractmethod - def test_check_primitives(self) -> None: - """ Test the `compiler._check_primitives()` method. - """ - - @abstractmethod - def test_compile_primitive_bra(self) -> None: - """ Test the compilation of a `quick.primitives.Bra` instance. - """ - - @abstractmethod - def test_compile_primitive_ket(self) -> None: - """ Test the compilation of a `quick.primitives.Ket` instance. - """ - - @abstractmethod - def test_compile_primitive_operator(self) -> None: - """ Test the compilation of a `quick.primitives.Operator` instance. - """ - - @abstractmethod - def test_compile_primitive_ndarray(self) -> None: - """ Test the compilation of a `numpy.ndarray` instance. - """ - - @abstractmethod - def test_compile_primitive_invalid_primitive(self) -> None: - """ Test the fail compilation of an invalid primitive. - """ - - @abstractmethod - def test_compile_bra(self) -> None: - """ Test the compilation of a `quick.primitives.Bra` instance using `compiler.compile()`. - """ - - @abstractmethod - def test_compile_ket(self) -> None: - """ Test the compilation of a `quick.primitives.Ket` instance using `compiler.compile()`. - """ - - @abstractmethod - def test_compile_operator(self) -> None: - """ Test the compilation of a `quick.primitives.Operator` instance using `compiler.compile()`. - """ - - @abstractmethod - def test_compile_ndarray(self) -> None: - """ Test the compilation of a `numpy.ndarray` instance using `compiler.compile()`. - """ - - @abstractmethod - def test_compile_multiple_primitives(self) -> None: - """ Test the compilation of multiple primitives using `compiler.compile()`. - """ \ No newline at end of file diff --git a/tests/compiler/test_shende_compiler.py b/tests/compiler/test_shende_compiler.py index 0bff23c..934add6 100644 --- a/tests/compiler/test_shende_compiler.py +++ b/tests/compiler/test_shende_compiler.py @@ -22,12 +22,10 @@ import pytest from quick.circuit import TKETCircuit -from quick.compiler import Compiler +from quick.compiler import ShendeCompiler from quick.primitives import Bra, Ket, Operator from quick.random import generate_random_state, generate_random_unitary -from tests.compiler import Template - # Define the test data generated_statevector = generate_random_state(7) test_data_bra = Bra(generated_statevector) @@ -38,31 +36,32 @@ unitary_matrix = generate_random_unitary(3) -class TestShendeCompiler(Template): - """ `tests.compiler.TestShendeCompiler` is the tester for the `quick.compiler.Compiler` class. +class TestShendeCompiler: + """ `tests.compiler.TestShendeCompiler` is the tester for the + `quick.compiler.ShendeCompiler` class. """ def test_init(self) -> None: - Compiler(circuit_framework=TKETCircuit) + ShendeCompiler(circuit_framework=TKETCircuit) def test_init_invalid_framework(self) -> None: with pytest.raises(TypeError): - Compiler(circuit_framework=int) # type: ignore + ShendeCompiler(circuit_framework=int) # type: ignore def test_init_invalid_state_preparation(self) -> None: with pytest.raises(TypeError): - Compiler(circuit_framework=TKETCircuit, state_prep=int) # type: ignore + ShendeCompiler(circuit_framework=TKETCircuit, state_prep=int) # type: ignore def test_init_invalid_unitary_preparation(self) -> None: with pytest.raises(TypeError): - Compiler(circuit_framework=TKETCircuit, unitary_prep=int) # type: ignore + ShendeCompiler(circuit_framework=TKETCircuit, unitary_prep=int) # type: ignore def test_init_invalid_optimizer(self) -> None: with pytest.raises(TypeError): - Compiler(circuit_framework=TKETCircuit, optimizer=0) # type: ignore + ShendeCompiler(circuit_framework=TKETCircuit, optimizer=0) # type: ignore def test_state_preparation(self) -> None: # Initialize the Shende compiler - shende_compiler = Compiler(circuit_framework=TKETCircuit) + shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) # Encode the data to a circuit circuit = shende_compiler.state_preparation(generated_statevector) @@ -75,7 +74,7 @@ def test_state_preparation(self) -> None: def test_unitary_preparation(self) -> None: # Initialize the Shende compiler - shende_compiler = Compiler(circuit_framework=TKETCircuit) + shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) # Encode the data to a circuit circuit = shende_compiler.unitary_preparation(unitary_matrix) @@ -86,13 +85,9 @@ def test_unitary_preparation(self) -> None: # Ensure that the unitary matrix is close enough to the expected unitary matrix assert_almost_equal(unitary, unitary_matrix, decimal=8) - def test_optimize(self) -> None: - # TODO: Implement the test_optimize method - pass - def test_check_primitive(self) -> None: # Initialize the Shende compiler - shende_compiler = Compiler(circuit_framework=TKETCircuit) + shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) # Ensure that the checker does not raise an error when a valid primitive is passed shende_compiler._check_primitive(test_data_ket) @@ -103,7 +98,7 @@ def test_check_primitive(self) -> None: def test_check_primitive_invalid_primitive(self) -> None: # Initialize the Shende compiler - shende_compiler = Compiler(circuit_framework=TKETCircuit) + shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) # Ensure that the checker raises a ValueError when an invalid primitive is passed with pytest.raises(TypeError): @@ -123,7 +118,7 @@ def test_check_primitive_invalid_primitive(self) -> None: def test_check_primitive_qubits(self) -> None: # Initialize the Shende compiler - shende_compiler = Compiler(circuit_framework=TKETCircuit) + shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) # Ensure that the checker does not raise an error when valid qubits are passed shende_compiler._check_primitive_qubits(test_data_ket, range(7)) @@ -134,7 +129,7 @@ def test_check_primitive_qubits(self) -> None: def test_check_primitive_qubits_invalid_qubits(self) -> None: # Initialize the Shende compiler - shende_compiler = Compiler(circuit_framework=TKETCircuit) + shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) # Ensure that the checker raises a ValueError when invalid qubits are passed with pytest.raises(ValueError): @@ -142,7 +137,7 @@ def test_check_primitive_qubits_invalid_qubits(self) -> None: def test_check_primitives(self) -> None: # Initialize the Shende compiler - shende_compiler = Compiler(circuit_framework=TKETCircuit) + shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) # Ensure that the checker does not raise an error when valid primitives are passed shende_compiler._check_primitives([ @@ -155,7 +150,7 @@ def test_check_primitives(self) -> None: def test_compile_primitive_bra(self) -> None: # Initialize the Shende compiler - shende_compiler = Compiler(circuit_framework=TKETCircuit) + shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) # Encode the data to a circuit circuit = shende_compiler._compile_primitive(test_data_bra) @@ -169,7 +164,7 @@ def test_compile_primitive_bra(self) -> None: def test_compile_primitive_ket(self) -> None: # Initialize the Shende compiler - shende_compiler = Compiler(circuit_framework=TKETCircuit) + shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) # Encode the data to a circuit circuit = shende_compiler._compile_primitive(test_data_ket) @@ -182,7 +177,7 @@ def test_compile_primitive_ket(self) -> None: def test_compile_primitive_operator(self) -> None: # Initialize the Shende compiler - shende_compiler = Compiler(circuit_framework=TKETCircuit) + shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) # Encode the data to a circuit circuit = shende_compiler._compile_primitive(Operator(unitary_matrix)) @@ -195,7 +190,7 @@ def test_compile_primitive_operator(self) -> None: def test_compile_primitive_ndarray(self) -> None: # Initialize the Shende compiler - shende_compiler = Compiler(circuit_framework=TKETCircuit) + shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) # Encode the data to a circuit circuit = shende_compiler._compile_primitive(generated_statevector) @@ -217,7 +212,7 @@ def test_compile_primitive_ndarray(self) -> None: def test_compile_primitive_invalid_primitive(self) -> None: # Initialize the Shende compiler - shende_compiler = Compiler(circuit_framework=TKETCircuit) + shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) # Ensure that the compiler raises a ValueError when an invalid primitive is passed with pytest.raises(TypeError): @@ -231,7 +226,7 @@ def test_compile_primitive_invalid_primitive(self) -> None: def test_compile_bra(self) -> None: # Initialize the Shende compiler - shende_compiler = Compiler(circuit_framework=TKETCircuit) + shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) # Encode the data to a circuit circuit = shende_compiler.compile(test_data_bra) @@ -244,7 +239,7 @@ def test_compile_bra(self) -> None: def test_compile_ket(self) -> None: # Initialize the Shende compiler - shende_compiler = Compiler(circuit_framework=TKETCircuit) + shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) # Encode the data to a circuit circuit = shende_compiler.compile(test_data_ket) @@ -257,7 +252,7 @@ def test_compile_ket(self) -> None: def test_compile_operator(self) -> None: # Initialize the Shende compiler - shende_compiler = Compiler(circuit_framework=TKETCircuit) + shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) # Encode the data to a circuit circuit = shende_compiler.compile(Operator(unitary_matrix)) @@ -270,7 +265,7 @@ def test_compile_operator(self) -> None: def test_compile_ndarray(self) -> None: # Initialize the Shende compiler - shende_compiler = Compiler(circuit_framework=TKETCircuit) + shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) # Encode the data to a circuit circuit = shende_compiler.compile(generated_statevector) @@ -292,7 +287,7 @@ def test_compile_ndarray(self) -> None: def test_compile_multiple_primitives(self) -> None: # Initialize the Shende compiler - shende_compiler = Compiler(circuit_framework=TKETCircuit) + shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) # Generate a random bra and ket over three qubits generated_statevector = generate_random_state(3) @@ -313,8 +308,6 @@ def test_compile_multiple_primitives(self) -> None: (unitary_matrix_2, [3, 4, 5]) ]) - # TODO: Add a check for the optimized circuit - # Get the state of the circuit statevector = circuit.get_statevector() From 23b2d198bf0f292586a9a9ff5fa7968d19e43942 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sat, 26 Jul 2025 02:01:51 +0800 Subject: [PATCH 45/58] - Removed prints left accidentally. --- quick/circuit/from_framework/from_pennylane.py | 2 -- tests/circuit/test_circuit_to_controlled.py | 5 ----- tests/circuit/test_control_state.py | 2 -- tests/primitives/test_bra.py | 1 - tests/primitives/test_ket.py | 1 - 5 files changed, 11 deletions(-) diff --git a/quick/circuit/from_framework/from_pennylane.py b/quick/circuit/from_framework/from_pennylane.py index b4448c6..30a6e43 100644 --- a/quick/circuit/from_framework/from_pennylane.py +++ b/quick/circuit/from_framework/from_pennylane.py @@ -206,8 +206,6 @@ def extract_params( tape = qml.workflow.construct_tape(circuit)() - print(tape.operations) - for gate in tape.operations: gate_name = gate.name self.gate_mapping[gate_name](gate, params) diff --git a/tests/circuit/test_circuit_to_controlled.py b/tests/circuit/test_circuit_to_controlled.py index b65553e..587e94a 100644 --- a/tests/circuit/test_circuit_to_controlled.py +++ b/tests/circuit/test_circuit_to_controlled.py @@ -408,11 +408,6 @@ def test_rz_control( check_multiple_controlled_circuit.MCRZ(0.5, [0, 1], 2) check_multiple_controlled_circuit.MCRZ(0.5, [0, 1], [2, 3]) - print(single_controlled_circuit.circuit_log) - print(check_single_controlled_circuit.circuit_log) - - print(multiple_controlled_circuit.circuit_log) - print(check_multiple_controlled_circuit.circuit_log) assert single_controlled_circuit == check_single_controlled_circuit assert multiple_controlled_circuit == check_multiple_controlled_circuit diff --git a/tests/circuit/test_control_state.py b/tests/circuit/test_control_state.py index 3e2cbe2..246e0c3 100644 --- a/tests/circuit/test_control_state.py +++ b/tests/circuit/test_control_state.py @@ -260,8 +260,6 @@ def test_CRX_control_state( checker_circuit.CRX(0.1, 0, 1) checker_circuit.X(0) - print(repr(circuit)) - print(repr(checker_circuit)) # Check the circuit is equivalent to the checker circuit assert circuit == checker_circuit diff --git a/tests/primitives/test_bra.py b/tests/primitives/test_bra.py index 817bbbd..f3f0ef5 100644 --- a/tests/primitives/test_bra.py +++ b/tests/primitives/test_bra.py @@ -243,5 +243,4 @@ def test_repr(self) -> None: """ Test the string representation of the `quick.primitives.Bra` object. """ bra = Bra(np.array([1, 0, 0, 0])) - print(repr(bra)) assert repr(bra) == "Bra(data=[1.+0.j 0.+0.j 0.+0.j 0.+0.j], label=Ψ)" \ No newline at end of file diff --git a/tests/primitives/test_ket.py b/tests/primitives/test_ket.py index f52ba47..79c8668 100644 --- a/tests/primitives/test_ket.py +++ b/tests/primitives/test_ket.py @@ -197,7 +197,6 @@ def test_add(self) -> None: """ ket1 = Ket(np.array([1, 0, 0, 0])) ket2 = Ket(np.array([0, 1, 0, 0])) - print(ket1 + ket2) assert_allclose((ket1 + ket2).data, np.array([ [(1+0j)/np.sqrt(2)], [(1+0j)/np.sqrt(2)], From ed2ed7c1a50eaa7d46a739551f3bd6b66e2f43c4 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sat, 26 Jul 2025 02:07:24 +0800 Subject: [PATCH 46/58] - Removed accidentally left tester from `optimizer` deprecation of `ShendeCompiler`. --- tests/compiler/test_shende_compiler.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/compiler/test_shende_compiler.py b/tests/compiler/test_shende_compiler.py index 934add6..be52504 100644 --- a/tests/compiler/test_shende_compiler.py +++ b/tests/compiler/test_shende_compiler.py @@ -55,10 +55,6 @@ def test_init_invalid_unitary_preparation(self) -> None: with pytest.raises(TypeError): ShendeCompiler(circuit_framework=TKETCircuit, unitary_prep=int) # type: ignore - def test_init_invalid_optimizer(self) -> None: - with pytest.raises(TypeError): - ShendeCompiler(circuit_framework=TKETCircuit, optimizer=0) # type: ignore - def test_state_preparation(self) -> None: # Initialize the Shende compiler shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) From d828ade45eecf3f67a67fb2ee14aea0cbefcece0 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Wed, 30 Jul 2025 19:43:59 +0800 Subject: [PATCH 47/58] - Refactored `quick.primitives`. --- .../qiskit_backends/fake_ibm_backend.py | 3 +- quick/circuit/circuit.py | 8 +- quick/compiler/shende_compiler.py | 22 +- quick/predicates/predicates.py | 2 +- quick/primitives/__init__.py | 6 +- quick/primitives/bra.py | 616 ----------------- quick/primitives/ket.py | 621 ------------------ quick/primitives/operator.py | 191 ++---- quick/primitives/statevector.py | 561 ++++++++++++++++ quick/synthesis/statepreparation/isometry.py | 23 +- quick/synthesis/statepreparation/mottonen.py | 18 +- quick/synthesis/statepreparation/shende.py | 18 +- .../statepreparation/statepreparation.py | 23 +- tests/compiler/test_shende_compiler.py | 72 +- tests/primitives/__init__.py | 6 +- tests/primitives/test_bra.py | 246 ------- tests/primitives/test_ket.py | 279 -------- tests/primitives/test_operator.py | 124 +++- tests/primitives/test_statevector.py | 220 +++++++ .../statepreparation/test_isometry.py | 55 +- .../statepreparation/test_mottonen.py | 55 +- .../synthesis/statepreparation/test_shende.py | 55 +- .../statepreparation/test_statepreparation.py | 14 +- 23 files changed, 1081 insertions(+), 2157 deletions(-) delete mode 100644 quick/primitives/bra.py delete mode 100644 quick/primitives/ket.py create mode 100644 quick/primitives/statevector.py delete mode 100644 tests/primitives/test_bra.py delete mode 100644 tests/primitives/test_ket.py create mode 100644 tests/primitives/test_statevector.py diff --git a/quick/backend/qiskit_backends/fake_ibm_backend.py b/quick/backend/qiskit_backends/fake_ibm_backend.py index e78d3d9..8fda1ca 100644 --- a/quick/backend/qiskit_backends/fake_ibm_backend.py +++ b/quick/backend/qiskit_backends/fake_ibm_backend.py @@ -21,6 +21,7 @@ import numpy as np from numpy.typing import NDArray +import warnings from qiskit.primitives import BackendSamplerV2 as BackendSampler # type: ignore from qiskit_aer import AerSimulator # type: ignore @@ -113,7 +114,7 @@ def __init__( self._op_backend = AerSimulator.from_backend(backend, device="GPU", method="unitary") else: if self.device == "GPU" and available_devices["GPU"] is None: - print("Warning: GPU acceleration is not available. Defaulted to CPU.") + warnings.warn("Warning: GPU acceleration is not available. Defaulted to CPU.") self._counts_backend = BackendSampler(backend=AerSimulator.from_backend(backend)) self._op_backend = AerSimulator.from_backend(backend, method="unitary") diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index 80ac95e..085463a 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -49,7 +49,7 @@ from quick.circuit.dag import DAGCircuit from quick.circuit.from_framework import FromCirq, FromPennyLane, FromQiskit, FromTKET from quick.predicates import is_unitary_matrix -from quick.primitives import Bra, Ket, Operator +from quick.primitives import Statevector, Operator from quick.synthesis.gate_decompositions.multi_controlled_decomposition import MCRX, MCRY, MCRZ from quick.synthesis.statepreparation import Isometry from quick.synthesis.unitarypreparation import ( @@ -4992,14 +4992,14 @@ def QFT( def initialize( self, - state: NDArray[np.complex128] | Bra | Ket, + state: NDArray[np.complex128] | Statevector, qubit_indices: int | Sequence[int] ) -> None: """ Initialize the state of the circuit. Parameters ---------- - `state` : NDArray[np.complex128] | quick.primitives.Bra | quick.primitives.Ket + `state` : NDArray[np.complex128] | quick.primitives.Statevector The state to initialize the circuit to. `qubit_indices` : int | Sequence[int] The index of the qubit(s) to apply the gate to. @@ -5007,7 +5007,7 @@ def initialize( Raises ------ TypeError - - If the state is not a numpy array or a Bra/Ket object. + - If the state is not a numpy array or a Statevector object. - If the qubit indices are not integers or a sequence of integers. ValueError - If the compression percentage is not in the range [0, 100]. diff --git a/quick/compiler/shende_compiler.py b/quick/compiler/shende_compiler.py index 358c326..b95c864 100644 --- a/quick/compiler/shende_compiler.py +++ b/quick/compiler/shende_compiler.py @@ -25,15 +25,15 @@ from typing import TypeAlias from quick.circuit import Circuit -from quick.primitives import Bra, Ket, Operator +from quick.primitives import Statevector, Operator from quick.synthesis.statepreparation import StatePreparation, Isometry from quick.synthesis.unitarypreparation import UnitaryPreparation, ShannonDecomposition """ Type aliases for the primitives to be compiled: -- `PRIMITIVE` is a single primitive object, which can be a `Bra`, `Ket`, `Operator`,or a `numpy.ndarray`. +- `PRIMITIVE` is a single primitive object, which can be a `Statevector`, `Operator`,or a `numpy.ndarray`. - `PRIMITIVES` is a list of tuples containing the primitive object and the qubits they need to be applied to. """ -PRIMITIVE: TypeAlias = Bra | Ket | Operator | NDArray[np.complex128] +PRIMITIVE: TypeAlias = Statevector | Operator | NDArray[np.complex128] PRIMITIVES: TypeAlias = list[tuple[PRIMITIVE, Sequence[int]]] @@ -105,13 +105,13 @@ def __init__( def state_preparation( self, - state: NDArray[np.complex128] | Bra | Ket, + state: NDArray[np.complex128] | Statevector, ) -> Circuit: """ Prepare a quantum state. Parameters ---------- - `state` : NDArray[np.complex128] | quick.primitives.Bra | quick.primitives.Ket + `state` : NDArray[np.complex128] | quick.primitives.Statevector The quantum state to be prepared. Returns @@ -161,11 +161,11 @@ def _check_primitive(primitive: PRIMITIVE) -> None: ValueError - If the primitive object is invalid. """ - if not isinstance(primitive, (Bra, Ket, Operator, np.ndarray)): + if not isinstance(primitive, (Statevector, Operator, np.ndarray)): raise TypeError("Invalid primitive object.") if isinstance(primitive, np.ndarray): - if len(primitive.flatten()) < 2: + if len(primitive.ravel()) < 2: raise ValueError("Invalid primitive object.") elif primitive.ndim not in [1, 2]: raise ValueError("Invalid primitive object.") @@ -246,13 +246,13 @@ def _compile_primitive( """ self._check_primitive(primitive) - if isinstance(primitive, (Bra, Ket)): + if isinstance(primitive, Statevector): return self.state_preparation(primitive) elif isinstance(primitive, Operator): return self.unitary_preparation(primitive) elif isinstance(primitive, np.ndarray): if primitive.ndim == 1: - return self.state_preparation(Ket(primitive)) + return self.state_preparation(Statevector(primitive)) else: return self.unitary_preparation(Operator(primitive)) @@ -286,8 +286,8 @@ def compile( ----- >>> primitive = Operator(unitary_matrix) >>> circuit = compiler.compile(primitive) - >>> primitives = [(Bra(bra_vector), [0, 1]), - ... (Ket(ket_vector), [2, 3])] + >>> primitives = [(Operator(unitary_matrix), [0, 1]), + ... (Statevector(statevector), [2, 3])] >>> circuit = compiler.compile(primitives) """ if isinstance(primitives, list): diff --git a/quick/predicates/predicates.py b/quick/predicates/predicates.py index c96ed7f..e390b80 100644 --- a/quick/predicates/predicates.py +++ b/quick/predicates/predicates.py @@ -102,7 +102,7 @@ def is_statevector( if statevector.ndim == 2: if statevector.shape[1] == 1: - statevector = statevector.flatten() + statevector = statevector.ravel() return ( bool(np.isclose(np.linalg.norm(statevector), 1.0, rtol=rtol, atol=atol)) diff --git a/quick/primitives/__init__.py b/quick/primitives/__init__.py index 5a192ab..5378590 100644 --- a/quick/primitives/__init__.py +++ b/quick/primitives/__init__.py @@ -13,11 +13,9 @@ # limitations under the License. __all__ = [ - "Bra", - "Ket", + "Statevector", "Operator" ] -from quick.primitives.bra import Bra -from quick.primitives.ket import Ket +from quick.primitives.statevector import Statevector from quick.primitives.operator import Operator \ No newline at end of file diff --git a/quick/primitives/bra.py b/quick/primitives/bra.py deleted file mode 100644 index 5feb566..0000000 --- a/quick/primitives/bra.py +++ /dev/null @@ -1,616 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" Bra vector class for representing bra states. -""" - -from __future__ import annotations - -__all__ = ["Bra"] - -import numpy as np -from numpy.typing import NDArray -from typing import Any, Literal, overload, SupportsFloat, TypeAlias - -import quick.primitives.operator as operator -import quick.primitives.ket as ket - -# `Scalar` is a type alias that represents a scalar value that can be either -# a real number or a complex number. -Scalar: TypeAlias = SupportsFloat | complex - - -class Bra: - """ `quick.primitives.Bra` is a class that represents a quantum bra vector. Bra vectors are - complex, row vectors with a magnitude of 1 which represent quantum states. The bra vectors are - the complex conjugates of the ket vectors. - - Parameters - ---------- - `data` : NDArray[np.complex128] - The bra vector data. The data will be normalized to 2-norm and padded if necessary. - `label` : str, optional - The label of the bra vector. - - Attributes - ---------- - `label` : str, optional, default="Ψ" - The label of the bra vector. - `data` : NDArray[np.complex128] - The bra vector data. - `norm_scale` : np.float64 - The normalization scale. - `normalized` : bool - Whether the bra vector is normalized to 2-norm or not. - `shape` : Tuple[int, int] - The shape of the bra vector. - `num_qubits` : int - The number of qubits represented by the bra vector. - - Raises - ------ - ValueError - - If the data is a scalar or an operator. - - Usage - ----- - >>> data = np.array([1, 2, 3, 4]) - >>> bra = Bra(data) - """ - def __init__( - self, - data: NDArray[np.complex128], - label: str | None = None - ) -> None: - """ Initialize a `quick.primitives.Bra` instance. - """ - if label is None: - self.label = "\N{GREEK CAPITAL LETTER PSI}" - else: - self.label = label - - self.norm_scale = np.linalg.norm(data) - self.data = data - self.shape = data.shape - self.num_qubits = int(np.ceil(np.log2(len(data.flatten())))) - self.is_normalized() - self.is_padded() - self.to_bra(data) - - @staticmethod - def check_normalization(data: NDArray[np.complex128]) -> bool: - """ Check if a data is normalized to 2-norm. - - Parameters - ---------- - `data` : NDArray[np.complex128] - The data. - - Returns - ------- - bool - Whether the vector is normalized to 2-norm or not. - - Usage - ----- - >>> data = np.array([1, 2, 3, 4]) - >>> check_normalization(data) - """ - # Check whether the data is normalized to 2-norm - sum_check = np.sum(np.power(data, 2)) - - # Check if the sum of squared of the data elements is equal to - # 1 with 1e-8 tolerance - return bool(np.isclose(sum_check, 1.0, atol=1e-08)) - - def is_normalized(self) -> None: - """ Check if a `quick.primitives.Bra` instance is normalized to 2-norm. - - Usage - ----- - >>> data.is_normalized() - """ - self.normalized = self.check_normalization(self.data) - - @staticmethod - def normalize_data( - data: NDArray[np.complex128], - norm_scale: np.float64 - ) -> NDArray[np.complex128]: - """ Normalize the data to 2-norm, and return the normalized data. - - Parameters - ---------- - `data` : NDArray[np.complex128] - The data. - `norm_scale` : np.float64 - The normalization scale. - - Returns - ------- - NDArray[np.complex128] - The 2-norm normalized data. - - Usage - ----- - >>> data = np.array([[1, 2], - ... [3, 4]]) - >>> norm_scale = np.linalg.norm(data.flatten()) - >>> normalize_data(data, norm_scale) - """ - return np.multiply(data, 1/norm_scale) - - def normalize(self) -> None: - """ Normalize a `quick.primitives.Bra` instance to 2-norm. - """ - if self.normalized: - return - - self.data = self.normalize_data(self.data, self.norm_scale) - self.normalized = True - - @staticmethod - def check_padding(data: NDArray[np.complex128]) -> bool: - """ Check if a data is normalized to 2-norm. - - Parameters - ---------- - `data` : NDArray[np.complex128] - The data. - - Returns - ------- - bool - Whether the vector is normalized to 2-norm or not. - - Usage - ----- - >>> data = np.array([[1, 2], [3, 4]]) - >>> check_padding(data) - """ - return (data.shape[0] & (data.shape[0]-1) == 0) and data.shape[0] != 0 - - def is_padded(self) -> None: - """ Check if a `quick.data.Data` instance is padded to a power of 2. - - Usage - ----- - >>> data.is_padded() - """ - self.padded = self.check_padding(self.data) - - @staticmethod - def pad_data( - data: NDArray[np.complex128], - target_size: int - ) -> tuple[NDArray[np.complex128], tuple[int, ...]]: - """ Pad data with zeros up to the nearest power of 2, and return - the padded data. - - Parameters - ---------- - `data` : NDArray[np.complex128] - The data to be padded. - `target_size` : int - The target size to pad the data to. - - Returns - ------- - `padded_data` : NDArray[np.complex128] - The padded data. - `data_shape` : (tuple[int, ...]) - The updated shape. - - Usage - ----- - >>> data = np.array([1, 2, 3]) - >>> pad_data(data, 4) - """ - padded_data = np.pad( - data, (0, int(target_size - len(data))), - mode="constant" - ) - updated_shape = padded_data.shape - - return padded_data, updated_shape - - def pad(self) -> None: - """ Pad a `quick.data.Data` instance. - - Usage - ----- - >>> data.pad() - """ - if self.padded: - return - - self.data, self.shape = self.pad_data(self.data, np.exp2(self.num_qubits)) - self.padded = True - - def to_quantumstate(self) -> None: - """ Converts a `quick.data.Data` instance to a quantum state. - - Usage - ----- - >>> data.to_quantumstate() - """ - if not self.normalized: - self.normalize() - - if not self.padded: - self.pad() - - def to_bra( - self, - data: NDArray[np.complex128] - ) -> None: - """ Convert the data to a bra vector. - - Parameters - ---------- - `data` : NDArray[np.complex128] - The data. - - Raises - ------ - ValueError - - If the data is a scalar or an operator. - - Usage - ----- - >>> data = np.array([1, 2, 3, 4]) - >>> to_bra(data) - """ - if data.ndim == 0: - raise ValueError("Cannot convert a scalar to a bra.") - elif data.ndim == 1: - if data.shape[0] == 1: - raise ValueError("Cannot convert a scalar to a bra.") - else: - self.data = data - self.shape = self.data.shape - elif data.ndim == 2: - if data.shape[0] == 1: - if data.shape[1] == 1: - raise ValueError("Cannot convert a scalar to a bra.") - else: - self.data = data.reshape(1, -1)[0] - self.shape = self.data.shape - else: - raise ValueError("Cannot convert an operator to a bra.") - else: - raise ValueError("Cannot convert a N-dimensional array to a bra.") - - self.data = self.data.astype(np.complex128) - - # Normalize and pad the data to satisfy the quantum state requirements - self.to_quantumstate() - - def to_ket(self) -> ket.Ket: - """ Convert the bra vector to a ket vector. - - Returns - ------- - quick.primitives.Ket - The ket vector. - - Usage - ----- - >>> bra.to_ket() - """ - return ket.Ket(self.data.conj().reshape(1, -1)[0]) - - def compress( - self, - compression_percentage: float - ) -> None: - """ Compress a `quick.data.Data` instance. - - Parameters - ---------- - `compression_percentage` : float - The percentage of compression. - - Usage - ----- - >>> data.compress(50) - """ - data_sort_ind = np.argsort(np.abs(self.data)) - - # Set the smallest absolute values of data to zero according to compression parameter - cutoff = int((compression_percentage / 100.0) * len(self.data)) - for i in data_sort_ind[:cutoff]: - self.data[i] = 0 - - def change_indexing( - self, - index_type: Literal["row", "snake"] - ) -> None: - """ Change the indexing of a `quick.primitives.Bra` instance. - - Parameters - ---------- - `index_type` : Literal["row", "snake"] - The new indexing type, being "row" or "snake". - - Raises - ------ - ValueError - - If the index type is not supported. - - Usage - ----- - >>> data.change_indexing("snake") - """ - if index_type == "snake": - if self.num_qubits >= 3: - # Convert the bra vector to a matrix (image) - self.data = self.data.reshape(2, -1) - # Reverse the elements in odd rows - self.data[1::2, :] = self.data[1::2, ::-1] - - self.data = self.data.flatten() - elif index_type == "row": - self.data = self.data - else: - raise ValueError("Index type not supported.") - - def _check__mul__( - self, - other: Any - ) -> None: - """ Check if the multiplication is valid. - - Parameters - ---------- - `other` : Any - The other object to multiply with. - - Raises - ------ - ValueError - - If the two vectors are incompatible. - - If the the bra and operator are incompatible. - NotImplementedError - - If the `other` type is incompatible. - """ - if isinstance(other, (SupportsFloat, complex)): - return - elif isinstance(other, ket.Ket): - if self.num_qubits != other.num_qubits: - raise ValueError("Cannot contract two incompatible vectors.") - elif isinstance(other, operator.Operator): - if self.num_qubits != other.num_qubits: - raise ValueError("Cannot multiply two incompatible vectors.") - else: - raise NotImplementedError(f"Multiplication with {type(other)} is not supported.") - - def __eq__( - self, - other: object - ) -> bool: - """ Check if two bra vectors are equal. - - Parameters - ---------- - `other` : object - The other bra vector. - - Returns - ------- - bool - Whether the two bra vectors are equal. - - Usage - ----- - >>> bra1 = Bra(np.array([1+0j, 0+0j])) - >>> bra2 = Bra(np.array([1+0j, 0+0j])) - >>> bra1 == bra2 - """ - if isinstance(other, Bra): - return bool(np.all(np.isclose(self.data, other.data, atol=1e-10, rtol=0))) - - raise NotImplementedError(f"Equality with {type(other)} is not supported.") - - def __len__(self) -> int: - """ Return the length of the bra vector. - - Returns - ------- - int - The length of the bra vector. - - Usage - ----- - >>> len(bra) - """ - return len(self.data) - - def __add__( - self, - other: Bra - ) -> Bra: - """ Superpose two bra states together. - - Parameters - ---------- - `other` : quick.primitives.Bra - The other bra state. - - Returns - ------- - quick.primitives.Bra - The superposed bra state. - - Raises - ------ - ValueError - - If the two bra states are incompatible. - - Usage - ----- - >>> bra1 = Bra(np.array([1+0j, 0+0j])) - >>> bra2 = Bra(np.array([1+0j, 0+0j])) - >>> bra1 + bra2 - """ - if isinstance(other, Bra): - if self.num_qubits != other.num_qubits: - raise ValueError("Cannot add two incompatible vectors.") - return Bra((self.data + other.data).astype(np.complex128)) - - raise NotImplementedError(f"Addition with {type(other)} is not supported.") - - @overload - def __mul__( - self, - other: Scalar - ) -> Bra: - ... - - @overload - def __mul__( - self, - other: ket.Ket - ) -> Scalar: - ... - - @overload - def __mul__( - self, - other: operator.Operator - ) -> Bra: - ... - - def __mul__( - self, - other: Scalar | ket.Ket | operator.Operator - ) -> Scalar | Bra: - """ Multiply the bra by a scalar, a ket, or an operator. - - The multiplication of a bra with a ket is defined as: - - ⟨ψ'|ψ⟩ = s, where s is a scalar - - The multiplication of a bra with an operator is defined as: - - ⟨ψ|A = ⟨ψ'| - - Notes - ----- - The multiplication of a bra with a scalar does not change the bra. This is because - the norm of the bra is preserved, and the scalar is multiplied with each element of the - bra. We provide the scalar multiplication for completeness. - - Parameters - ---------- - `other` : quick.primitives.Scalar | quick.primitives.Ket | quick.primitives.Operator - The other object to multiply the bra by. - - Returns - ------- - quick.primitives.Scalar | quick.primitives.Bra - The result of the multiplication. - - Raises - ------ - ValueError - - If the two vectors are incompatible. - - If the operator dimensions are incompatible. - NotImplementedError - - If the `other` type is incompatible. - - Usage - ----- - >>> scalar = 2 - >>> bra = Bra(np.array([1+0j, 0+0j])) - >>> bra * scalar - >>> bra = Bra(np.array([1+0j, 0+0j])) - >>> ket = Ket(np.array([1+0j, 0+0j])) - >>> bra * ket - >>> bra = Bra(np.array([1+0j, 0+0j])) - >>> operator = Operator([[1+0j, 0+0j], - ... [0+0j, 1+0j]]) - >>> bra * operator - """ - if isinstance(other, (SupportsFloat, complex)): - return Bra((self.data * other).astype(np.complex128)) # type: ignore - elif isinstance(other, ket.Ket): - if self.num_qubits != other.num_qubits: - raise ValueError("Cannot contract two incompatible vectors.") - return np.dot(self.data, other.data).flatten()[0] - elif isinstance(other, operator.Operator): - if self.num_qubits != other.num_qubits: - raise ValueError("Cannot multiply two incompatible vectors.") - return Bra(self.data @ other.data) - else: - raise NotImplementedError(f"Multiplication with {type(other)} is not supported.") - - def __rmul__( - self, - other: Scalar - ) -> Bra: - """ Multiply the bra by a scalar. - - Notes - ----- - The multiplication of a bra with a scalar does not change the bra. This is because - the norm of the bra is preserved, and the scalar is multiplied with each element of the - bra. We provide the scalar multiplication for completeness. - - Parameters - ---------- - `other` : quick.primitives.Scalar - The scalar to multiply the bra by. - - Returns - ------- - quick.primitives.Bra - The bra multiplied by the scalar. - - Usage - ----- - >>> scalar = 2 - >>> bra = Bra(np.array([1+0j, 0+0j])) - >>> scalar * bra - """ - if isinstance(other, (SupportsFloat, complex)): - return Bra((self.data * other).astype(np.complex128)) # type: ignore - - raise NotImplementedError(f"Multiplication with {type(other)} is not supported.") - - def __str__(self) -> str: - """ Return the string representation of the bra vector. - - Returns - ------- - str - The string representation of the bra vector. - - Usage - ----- - >>> str(bra) - """ - return f"⟨{self.label}|" - - def __repr__(self) -> str: - """ Return the string representation of the bra vector. - - Returns - ------- - str - The string representation of the bra vector. - - Usage - ----- - >>> repr(bra) - """ - return f"{self.__class__.__name__}(data={self.data}, label={self.label})" \ No newline at end of file diff --git a/quick/primitives/ket.py b/quick/primitives/ket.py deleted file mode 100644 index a7a296c..0000000 --- a/quick/primitives/ket.py +++ /dev/null @@ -1,621 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" Ket vector class for representing ket states. -""" - -from __future__ import annotations - -__all__ = ["Ket"] - -import numpy as np -from numpy.typing import NDArray -from typing import Any, Literal, overload, SupportsFloat, TypeAlias - -import quick.primitives.operator as operator -import quick.primitives.bra as bra - -# `Scalar` is a type alias that represents a scalar value that can be either -# a real number or a complex number. -Scalar: TypeAlias = SupportsFloat | complex - - -class Ket: - """ `quick.primitives.Ket` is a class that represents a quantum ket vector. Ket vectors are - complex, column vectors with a magnitude of 1 which represent quantum states. The ket vectors are - the complex conjugates of the bra vectors. - - Parameters - ---------- - `data` : NDArray[np.complex128] - The ket vector data. The data will be normalized to 2-norm and padded if necessary. - `label` : str, optional - The label of the ket vector. - - Attributes - ---------- - `label` : str, optional, default="Ψ" - The label of the ket vector. - `data` : NDArray[np.complex128] - The ket vector data. - `norm_scale` : np.float64 - The normalization scale. - `normalized` : bool - Whether the ket vector is normalized to 2-norm or not. - `shape` : Tuple[int, int] - The shape of the ket vector. - `num_qubits` : int - The number of qubits represented by the ket vector. - - Raises - ------ - ValueError - - If the data is a scalar or an operator. - - Usage - ----- - >>> data = np.array([1, 2, 3, 4]) - >>> ket = Ket(data) - """ - def __init__( - self, - data: NDArray[np.complex128], - label: str | None = None - ) -> None: - """ Initialize a `quick.primitives.Ket` instance. - """ - if label is None: - self.label = "\N{GREEK CAPITAL LETTER PSI}" - else: - self.label = label - - self.norm_scale = np.linalg.norm(data.flatten()) - self.data = data - self.shape = data.shape - self.num_qubits = int(np.ceil(np.log2(self.shape[0]))) - self.is_normalized() - self.is_padded() - self.to_ket(data) - - @staticmethod - def check_normalization(data: NDArray[np.complex128]) -> bool: - """ Check if a data is normalized to 2-norm. - - Parameters - ---------- - `data` : NDArray[np.complex128] - The data. - - Returns - ------- - bool - Whether the vector is normalized to 2-norm or not. - - Usage - ----- - >>> data = np.array([1, 2, 3, 4]) - >>> check_normalization(data) - """ - # Check whether the data is normalized to 2-norm - sum_check = np.sum(np.power(data, 2)) - - # Check if the sum of squared of the data elements is equal to - # 1 with 1e-8 tolerance - return bool(np.isclose(sum_check, 1.0, atol=1e-08)) - - def is_normalized(self) -> None: - """ Check if a `quick.primitives.Bra` instance is normalized to 2-norm. - - Usage - ----- - >>> data.is_normalized() - """ - self.normalized = self.check_normalization(self.data) - - @staticmethod - def normalize_data( - data: NDArray[np.complex128], - norm_scale: np.float64 - ) -> NDArray[np.complex128]: - """ Normalize the data to 2-norm, and return the normalized data. - - Parameters - ---------- - `data` : NDArray[np.complex128] - The data. - `norm_scale` : np.float64 - The normalization scale. - - Returns - ------- - NDArray[np.complex128] - The 2-norm normalized data. - - Usage - ----- - >>> data = np.array([[1, 2], - ... [3, 4]]) - >>> norm_scale = np.linalg.norm(data.flatten()) - >>> normalize_data(data, norm_scale) - """ - return np.multiply(data, 1/norm_scale) - - def normalize(self) -> None: - """ Normalize a `quick.primitives.Ket` instance to 2-norm. - """ - if self.normalized: - return - - self.data = self.normalize_data(self.data, self.norm_scale) - self.normalized = True - - @staticmethod - def check_padding(data: NDArray[np.complex128]) -> bool: - """ Check if a data is normalized to 2-norm. - - Parameters - ---------- - `data` : NDArray[np.complex128] - The data. - - Returns - ------- - bool - Whether the vector is normalized to 2-norm or not. - - Usage - ----- - >>> data = np.array([[1, 2], [3, 4]]) - >>> check_padding(data) - """ - return (data.shape[0] & (data.shape[0]-1) == 0) and data.shape[0] != 0 - - def is_padded(self) -> None: - """ Check if a `quick.data.Data` instance is padded to a power of 2. - - Usage - ----- - >>> data.is_padded() - """ - self.padded = self.check_padding(self.data) - - @staticmethod - def pad_data( - data: NDArray[np.complex128], - target_size: int - ) -> tuple[NDArray[np.complex128], tuple[int, ...]]: - """ Pad data with zeros up to the nearest power of 2, and return - the padded data. - - Parameters - ---------- - `data` : NDArray[np.complex128] - The data to be padded. - `target_size` : int - The target size to pad the data to. - - Returns - ------- - `padded_data` : NDArray[np.complex128] - The padded data. - `data_shape` : (tuple[int, ...]) - The updated shape. - - Usage - ----- - >>> data = np.array([[1, 2], [3, 4]]) - >>> pad_data(data) - """ - flattened_data = data.flatten() - - padded_data = np.pad( - flattened_data, (0, int(target_size - len(flattened_data))), - mode="constant" - ).reshape(-1, 1) - - updated_shape = padded_data.shape - - return padded_data, updated_shape - - def pad(self) -> None: - """ Pad a `quick.data.Data` instance. - - Usage - ----- - >>> data.pad() - """ - if self.padded: - return - - self.data, self.shape = self.pad_data(self.data, np.exp2(self.num_qubits)) - self.padded = True - - def to_quantumstate(self) -> None: - """ Converts a `quick.data.Data` instance to a quantum state. - - Usage - ----- - >>> data.to_quantumstate() - """ - if not self.normalized: - self.normalize() - - if not self.padded: - self.pad() - - def to_ket( - self, - data: NDArray[np.complex128] - ) -> None: - """ Convert the data to a ket vector. - - Parameters - ---------- - `data` : NDArray[np.complex128] - The data. - - Raises - ------ - ValueError - - If the data is a scalar or an operator. - - Usage - ----- - >>> ket.to_ket(data) - """ - if data.ndim == 0: - raise ValueError("Cannot convert a scalar to a ket.") - elif data.ndim == 1: - if data.shape[0] == 1: - raise ValueError("Cannot convert a scalar to a ket.") - else: - self.data = data.reshape(-1, 1) - elif data.ndim == 2: - if data.shape[1] == 1: - if data.shape[0] == 1: - raise ValueError("Cannot convert a scalar to a ket.") - else: - self.data = data - else: - raise ValueError("Cannot convert an operator to a ket.") - else: - raise ValueError("Cannot convert a N-dimensional array to a ket.") - - self.data = self.data.astype(np.complex128) - - # Normalize and pad the data to satisfy the quantum state requirements - self.to_quantumstate() - - def to_bra(self) -> bra.Bra: - """ Convert the ket to a bra. - - Returns - ------- - quick.primitives.Bra - The bra vector. - - Usage - ----- - >>> ket.to_bra() - """ - return bra.Bra(self.data.conj().reshape(1, -1)) # type: ignore - - def compress( - self, - compression_percentage: float - ) -> None: - """ Compress a `quick.data.Data` instance. - - Parameters - ---------- - `compression_percentage` : float - The percentage of compression. - - Usage - ----- - >>> data.compress(50) - """ - flattened_data = self.data.flatten() - data_sort_ind = np.argsort(np.abs(flattened_data)) - - # Set the smallest absolute values of data to zero according to compression parameter - cutoff = int((compression_percentage / 100.0) * len(flattened_data)) - for i in data_sort_ind[:cutoff]: - flattened_data[i] = 0 - - self.data = flattened_data.reshape(-1, 1) - - def change_indexing( - self, - index_type: Literal["row", "snake"] - ) -> None: - """ Change the indexing of a `quick.primitives.Ket` instance. - - Parameters - ---------- - `index_type` : Literal["row", "snake"] - The new indexing type, being "row" or "snake". - - Raises - ------ - ValueError - - If the index type is not supported. - - Usage - ----- - >>> data.change_indexing("snake") - """ - if index_type == "snake": - if self.num_qubits >= 3: - # Convert the bra vector to a matrix (image) - self.data = self.data.reshape(2, -1) - # Reverse the elements in odd rows - self.data[1::2, :] = self.data[1::2, ::-1] - - self.data = self.data.flatten().reshape(-1, 1) - elif index_type == "row": - self.data = self.data - else: - raise ValueError("Index type not supported.") - - def _check__mul__( - self, - other: Any - ) -> None: - """ Check if the multiplication is valid. - - Parameters - ---------- - `other` : Any - The other object to multiply with. - - Raises - ------ - ValueError - - If the two vectors are incompatible. - NotImplementedError - - If the `other` type is incompatible. - """ - if isinstance(other, (SupportsFloat, complex)): - return - elif isinstance(other, bra.Bra): - if self.num_qubits != other.num_qubits: - raise ValueError("Cannot contract two incompatible vectors.") - elif isinstance(other, Ket): - if self.num_qubits != other.num_qubits: - raise ValueError("Cannot contract two incompatible vectors.") - else: - raise NotImplementedError(f"Multiplication with {type(other)} is not supported.") - - def __eq__( - self, - other: object - ) -> bool: - """ Check if two ket vectors are equal. - - Parameters - ---------- - `other` : object - The other ket vector. - - Returns - ------- - bool - Whether the two ket vectors are equal. - - Usage - ----- - >>> ket1 = Ket(np.array([1+0j, 0+0j])) - >>> ket2 = Ket(np.array([1+0j, 0+0j])) - >>> ket1 == ket2 - """ - if isinstance(other, Ket): - return bool(np.all(np.isclose(self.data.flatten(), other.data.flatten(), atol=1e-10, rtol=0))) - - raise NotImplementedError(f"Equality with {type(other)} is not supported.") - - def __len__(self) -> int: - """ Return the length of the bra vector. - - Returns - ------- - int - The length of the bra vector. - - Usage - ----- - >>> len(bra) - """ - return len(self.data.flatten()) - - def __add__( - self, - other: Ket - ) -> Ket: - """ Superpose two ket states together. - - Parameters - ---------- - `other` : quick.primitives.Ket - The other ket state. - - Returns - ------- - quick.primitives.Ket - The superposed ket state. - - Raises - ------ - NotImplementedError - - If the two vectors are incompatible. - ValueError - - If the two ket states are incompatible. - - Usage - ----- - >>> ket1 = Ket(np.array([1+0j, 0+0j])) - >>> ket2 = Ket(np.array([1+0j, 0+0j])) - >>> ket3 = ket1 + ket2 - """ - if isinstance(other, Ket): - if self.num_qubits != other.num_qubits: - raise ValueError("Cannot add two incompatible vectors.") - return Ket((self.data.flatten() + other.data.flatten()).astype(np.complex128)) - - raise NotImplementedError(f"Addition with {type(other)} is not supported.") - - @overload - def __mul__( - self, - other: Scalar - ) -> Ket: - ... - - @overload - def __mul__( - self, - other: bra.Bra - ) -> operator.Operator: - ... - - @overload - def __mul__( - self, - other: Ket - ) -> Ket: - ... - - def __mul__( - self, - other: Scalar | bra.Bra | Ket - ) -> Ket | operator.Operator: - """ Multiply the ket by a scalar, bra, or ket. - - The multiplication of a ket with a bra is defined as: - |ψ⟩⟨ψ|, which is called the projection operator and is implemented using the measurement - operator. - - The multiplication of a ket with a ket is defined as: - |ψ⟩⊗|ψ'⟩, which is called the tensor product of two quantum states. - - Notes - ----- - The multiplication of a ket with a scalar does not change the ket. This is because - the norm of the ket is preserved, and the scalar is multiplied with each element of the - ket. We provide the scalar multiplication for completeness. - - Parameters - ---------- - `other` : quick.primitives.Scalar | quick.primitives.Bra | quick.primitives.Ket - The object to multiply the ket by. - - Returns - ------- - quick.primitives.Ket | quick.primitives.Operator - The ket or operator resulting from the multiplication. - - Raises - ------ - ValueError - - If the two vectors are incompatible. - NotImplementedError - - If the `other` type is incompatible. - - Usage - ----- - >>> scalar = 2 - >>> ket = Ket(np.array([1+0j, 0+0j])) - >>> ket = ket * scalar - >>> bra = Bra(np.array([1+0j, 0+0j])) - >>> ket = Ket(np.array([1+0j, 0+0j])) - >>> operator = ket * bra - >>> ket1 = Ket(np.array([1+0j, 0+0j])) - >>> ket2 = Ket(np.array([1+0j, 0+0j])) - >>> ket3 = ket1 * ket2 - """ - if isinstance(other, (SupportsFloat, complex)): - return Ket((self.data * other).astype(np.complex128)) # type: ignore - elif isinstance(other, bra.Bra): - if self.num_qubits != other.num_qubits: - raise ValueError("Cannot contract two incompatible vectors.") - return operator.Operator(np.outer(self.data, other.data.conj())) - elif isinstance(other, Ket): - if self.num_qubits != other.num_qubits: - raise ValueError("Cannot contract two incompatible vectors.") - return Ket(np.kron(self.data, other.data)) - else: - raise NotImplementedError(f"Multiplication with {type(other)} is not supported.") - - def __rmul__( - self, - other: Scalar - ) -> Ket: - """ Multiply the ket by a scalar. - - Notes - ----- - The multiplication of a ket with a scalar does not change the ket. This is because - the norm of the ket is preserved, and the scalar is multiplied with each element of the - ket. We provide the scalar multiplication for completeness. - - Parameters - ---------- - `other` : quick.primitives.Scalar - The scalar to multiply the ket by. - - Returns - ------- - quick.primitives.Ket - The ket multiplied by the scalar. - - Usage - ----- - >>> scalar = 2 - >>> ket = Ket(np.array([1+0j, 0+0j])) - >>> ket = scalar * ket - """ - if isinstance(other, (SupportsFloat, complex)): - return Ket((self.data * other).astype(np.complex128)) # type: ignore - - raise NotImplementedError(f"Multiplication with {type(other)} is not supported.") - - def __str__(self) -> str: - """ Return the string representation of the ket. - - Returns - ------- - str - The string representation of the ket. - - Usage - ----- - >>> ket = Ket(np.array([1+0j, 0+0j])) - >>> str(ket) - """ - return f"|{self.label}⟩" - - def __repr__(self) -> str: - """ Return the string representation of the ket. - - Returns - ------- - str - The string representation of the ket. - - Usage - ----- - >>> ket = Ket(np.array([1+0j, 0+0j])) - >>> repr(ket) - """ - return f"{self.__class__.__name__}(data={self.data}, label={self.label})" \ No newline at end of file diff --git a/quick/primitives/operator.py b/quick/primitives/operator.py index 5ec78bf..27fb103 100644 --- a/quick/primitives/operator.py +++ b/quick/primitives/operator.py @@ -23,8 +23,8 @@ from numpy.typing import NDArray from typing import Any, overload, SupportsFloat, TypeAlias -from quick.predicates import is_square_matrix, is_unitary_matrix -import quick.primitives.ket as ket +from quick.predicates import is_unitary_matrix +import quick.primitives.statevector as statevector # `Scalar` is a type alias that represents a scalar value that can be either # a real number or a complex number. @@ -32,14 +32,15 @@ class Operator: - """ `quick.primitives.Operator` class is used to represent a quantum operator. Quantum operators - are hermitian matrices (square, unitary matrices) which represent operations applied to quantum - states (represented with qubits). + """ `quick.primitives.Operator` class is used to represent a quantum operator. + Quantum operators are hermitian matrices (square, unitary matrices) which represent + operations applied to quantum states (represented with qubits). Parameters ---------- `data` : NDArray[np.complex128] - The quantum operator data. If the data is not a complex type, it will be converted to complex. + The quantum operator data. If the data is not a complex type, + it will be converted to complex. `label` : str, optional The label of the quantum operator. @@ -57,9 +58,6 @@ class Operator: Raises ------ ValueError - - If the operator is not a square matrix. - - If the operator dimension is not a power of 2. - - If the operator cannot be converted to complex type. - If the operator is not unitary. Usage @@ -77,53 +75,18 @@ def __init__( """ if label is None: self.label = "\N{LATIN CAPITAL LETTER A}\N{COMBINING CIRCUMFLEX ACCENT}" - self.is_unitary(data) - self.data = data - self.shape = self.data.shape - self.num_qubits = int(np.ceil(np.log2(self.shape[0]))) - - @staticmethod - def is_unitary(data: NDArray[np.complex128]) -> None: - """ Check if a matrix is Hermitian. - - Parameters - ---------- - `data` : NDArray[np.complex128] - The matrix to check. + else: + self.label = label - Raises - ------ - ValueError - - If the matrix is not square. - - If the matrix dimension is not a power of 2. - - If the matrix cannot be converted to complex type. - - If the matrix is not unitary. + data = np.array(data) - Usage - ----- - >>> data = np.array([[1+0j, 0+0j], - ... [0+0j, 1+0j]]) - >>> ishermitian(data) - """ - # Check if the matrix is square - if not is_square_matrix(data): - raise ValueError("Operator must be a square matrix.") - - # Check if the matrix dimension is a power of 2 - if not ((data.shape[0] & (data.shape[0] - 1) == 0) and data.shape[0] != 0): - raise ValueError("Operator dimension must be a power of 2.") - - # Check if the data type is complex - if not np.iscomplexobj(data): - try: - data = data.astype(np.complex128) - except ValueError: - raise ValueError("Cannot convert data to complex type.") - - # Check if the matrix is unitary if not is_unitary_matrix(data): raise ValueError("Operator must be unitary.") + self.data = data + self.shape = self.data.shape + self.num_qubits = int(np.ceil(np.log2(self.shape[0]))) + def _check__mul__( self, other: Any @@ -138,34 +101,51 @@ def _check__mul__( Raises ------ ValueError - - If the the operator and ket are incompatible. + - If the the operator and statevector are incompatible. - If the two operators are incompatible. - NotImplementedError + TypeError - If the `other` type is incompatible. """ - if isinstance(other, (SupportsFloat, complex)): - return - elif isinstance(other, ket.Ket): + if isinstance(other, statevector.Statevector): if self.num_qubits != other.num_qubits: - raise ValueError("Cannot multiply an operator with an incompatible ket.") + raise ValueError("Cannot multiply an operator with an incompatible statevector.") elif isinstance(other, Operator): if self.num_qubits != other.num_qubits: raise ValueError("Cannot multiply two incompatible operators.") else: - raise NotImplementedError(f"Multiplication with {type(other)} is not supported.") + raise TypeError(f"Multiplication with {type(other)} is not supported.") - @overload - def __mul__( + def __eq__( self, - other: Scalar - ) -> Operator: - ... + other: Any + ) -> bool: + """ Check if two operators are equal. + + Parameters + ---------- + `other` : Any + The other object to compare with. + + Returns + ------- + bool + True if the operators are equal, False otherwise. + + Raises + ------ + TypeError + - If the `other` type is incompatible. + """ + if not isinstance(other, Operator): + raise TypeError(f"Cannot compare {type(self)} with {type(other)}.") + + return bool(np.all(np.isclose(self.data, other.data, atol=1e-8, rtol=0))) @overload def __mul__( self, - other: ket.Ket - ) -> ket.Ket: + other: statevector.Statevector + ) -> statevector.Statevector: ... @overload @@ -177,30 +157,24 @@ def __mul__( def __mul__( self, - other: Scalar | ket.Ket | Operator - ) -> Operator | ket.Ket: - """ Multiply an operator with a scalar, ket or another operator. + other: statevector.Statevector | Operator + ) -> Operator | statevector.Statevector: + """ Multiply an operator with a statevector or another operator. - The multiplication of an operator with a ket is defined as: + The multiplication of an operator with a statevector is defined as: - A|ψ⟩ = |ψ'⟩ The multiplication of an operator with another operator is defined as: - AB = C - Notes - ----- - The multiplication of an operator with a scalar does not change the operator. This is because - the norm of the operator is preserved, and the scalar is multiplied with each element of the - operator. We provide the scalar multiplication for completeness. - Parameters ---------- - `other` : quick.primitives.Scalar | quick.primitives.Ket | quick.primitives.Operator + `other` : quick.primitives.Statevector | quick.primitives.Operator The object to multiply with. Returns ------- - quick.primitives.Operator | quick.primitives.Ket + quick.primitives.Operator | quick.primitives.Statevector The result of the multiplication. Raises @@ -208,76 +182,31 @@ def __mul__( ValueError - If the operator and ket dimensions are incompatible. - If the operator dimensions are incompatible. - NotImplementedError + TypeError - If the `other` type is incompatible. Usage ----- - >>> scalar = 2 >>> operator = Operator([[1+0j, 0+0j], ... [0+0j, 1+0j]]) - >>> operator * scalar - >>> operator = Operator([[1+0j, 0+0j], - ... [0+0j, 1+0j]]) - >>> ket = Ket([1+0j, 0+0j]) - >>> operator * ket + >>> statevector = Statevector([1+0j, 0+0j]) + >>> operator * statevector >>> operator1 = Operator([[1+0j, 0+0j], ... [0+0j, 1+0j]]) >>> operator2 = Operator([[1+0j, 0+0j], ... [0+0j, 1+0j]]) >>> operator1 * operator2 """ - if isinstance(other, (SupportsFloat, complex)): - return Operator((self.data * other).astype(np.complex128)) # type: ignore - elif isinstance(other, ket.Ket): + if isinstance(other, statevector.Statevector): if self.num_qubits != other.num_qubits: - raise ValueError("Cannot multiply an operator with an incompatible ket.") - return ket.Ket((self.data @ other.data).astype(np.complex128)) # type: ignore + raise ValueError("Cannot multiply an operator with an incompatible statevector.") + return statevector.Statevector((self.data @ other.data).astype(np.complex128)) elif isinstance(other, Operator): if self.num_qubits != other.num_qubits: raise ValueError("Cannot multiply two incompatible operators.") return Operator(self.data @ other.data) - else: - raise NotImplementedError(f"Multiplication with {type(other)} is not supported.") - - def __rmul__( - self, - other: Scalar - ) -> Operator: - """ Multiply a scalar with an operator. - - Notes - ----- - The multiplication of an operator with a scalar does not change the operator. This is because - the norm of the operator is preserved, and the scalar is multiplied with each element of the - operator. We provide the scalar multiplication for completeness. - - Parameters - ---------- - `other` : quick.primitives.Scalar - The scalar to multiply with. - - Returns - ------- - quick.primitives.Operator - The operator multiplied by the scalar. - - Raises - ------ - NotImplementedError - - If the `other` type is incompatible - - Usage - ----- - >>> scalar = 2 - >>> operator = Operator([[1+0j, 0+0j], - ... [0+0j, 1+0j]]) - >>> scalar * operator - """ - if isinstance(other, (SupportsFloat, complex)): - return Operator((self.data * other).astype(np.complex128)) # type: ignore - raise NotImplementedError(f"Multiplication with {type(other)} is not supported.") + raise TypeError(f"Multiplication with {type(other)} is not supported.") def __str__(self) -> str: """ Return the string representation of the operator. @@ -288,7 +217,7 @@ def __str__(self) -> str: ... [0+0j, 1+0j]]) >>> print(operator) """ - return f"{self.label}" + return self.label def __repr__(self) -> str: """ Return the string representation of the operator. @@ -299,4 +228,4 @@ def __repr__(self) -> str: ... [0+0j, 1+0j]]) >>> repr(operator) """ - return f"Operator(data={self.data})" \ No newline at end of file + return f"Operator(data={self.data}, label={self.label})" \ No newline at end of file diff --git a/quick/primitives/statevector.py b/quick/primitives/statevector.py new file mode 100644 index 0000000..c8ef795 --- /dev/null +++ b/quick/primitives/statevector.py @@ -0,0 +1,561 @@ +# Copyright 2023-2025 Qualition Computing LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://github.com/Qualition/quick/blob/main/LICENSE +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" Statevector class for representing quantum (Ket) state vectors. +""" + +from __future__ import annotations + +__all__ = ["Statevector"] + +import numpy as np +from numpy.typing import NDArray +from typing import Any, Literal, SupportsFloat, TypeAlias + +import quick.primitives.operator as operator + +# `Scalar` is a type alias that represents a scalar value that can be either +# a real number or a complex number. +Scalar: TypeAlias = SupportsFloat | complex + + +class Statevector: + """ `quick.primitives.Statevector` is a class that represents a qubit + statevector. Qubit statevectors are complex vectors with a magnitude + of 1 with 2^N elements where N is the number of qubits used to represent + the statevector. + + Parameters + ---------- + `data` : NDArray[np.complex128] + The statevector data. The data will be normalized + to 2-norm and padded if necessary. + `label` : str, optional + The label of the statevector. + + Attributes + ---------- + `label` : str, optional, default="Ψ" + The label of the statevector. + `data` : NDArray[np.complex128] + The statevector data. + `norm_scale` : np.float64 + The normalization scale. + `normalized` : bool + Whether the statevector is normalized to 2-norm or not. + `num_qubits` : int + The number of qubits represented by the statevector. + + Raises + ------ + ValueError + - If the data is a scalar or an operator. + + Usage + ----- + >>> data = np.array([1, 2, 3, 4]) + >>> statevector = Statevector(data) + """ + def __init__( + self, + data: NDArray[np.complex128], + label: str | None = None + ) -> None: + """ Initialize a `quick.primitives.Statevector` instance. + """ + if label is None: + self.label = "\N{GREEK CAPITAL LETTER PSI}" + else: + self.label = label + + data = np.array(data) + self.validate_data(data) + self.data = data.flatten().astype(np.complex128) + self.norm_scale = np.linalg.norm(self.data) + self.num_qubits = int(np.ceil(np.log2(self.data.size))) + self.is_normalized() + self.is_padded() + self.to_quantumstate() + + @staticmethod + def validate_data(data: NDArray[np.complex128]) -> None: + """ Validate the data to ensure it is a valid statevector. + + Parameters + ---------- + `data` : NDArray[np.complex128] + The data to validate. + + Raises + ------ + ValueError + - If the data is a scalar or an operator. + """ + if isinstance(data, Scalar) and data.size == 1: + raise ValueError("Cannot convert a scalar to a statevector.") + elif data.ndim == 0 or data.size == 1: + raise ValueError("Cannot convert a scalar to a statevector.") + elif data.ndim == 2 and data.shape[0] != 1: + raise ValueError("Cannot convert an operator to a statevector.") + + @staticmethod + def check_normalization(data: NDArray[np.complex128]) -> bool: + """ Check if a data is normalized to 2-norm. + + Parameters + ---------- + `data` : NDArray[np.complex128] + The data. + + Returns + ------- + bool + Whether the vector is normalized to 2-norm or not. + + Usage + ----- + >>> data = np.array([1, 2, 3, 4]) + >>> check_normalization(data) + """ + return bool( + np.isclose( + np.linalg.norm(data), 1.0, atol=1e-08 + ) + ) + + def is_normalized(self) -> None: + """ Check if a `quick.primitives.Statevector` instance is normalized to 2-norm. + + Usage + ----- + >>> data.is_normalized() + """ + self.normalized = self.check_normalization(self.data) + + @staticmethod + def normalize_data( + data: NDArray[np.complex128], + norm_scale: np.float64 + ) -> NDArray[np.complex128]: + """ Normalize the data to 2-norm, and return the normalized data. + + Parameters + ---------- + `data` : NDArray[np.complex128] + The data. + `norm_scale` : np.float64 + The normalization scale. + + Returns + ------- + NDArray[np.complex128] + The 2-norm normalized data. + + Usage + ----- + >>> data = np.array([[1, 2], + ... [3, 4]]) + >>> norm_scale = np.linalg.norm(data.flatten()) + >>> normalize_data(data, norm_scale) + """ + return np.multiply(data, 1/norm_scale) + + def normalize(self) -> None: + """ Normalize a `quick.primitives.Statevector` instance to 2-norm. + + Usage + ----- + >>> data.normalize() + """ + if self.normalized: + return + + self.data = self.normalize_data(self.data, self.norm_scale) + self.normalized = True + + @staticmethod + def check_padding(data: NDArray[np.complex128]) -> bool: + """ Check if a data is normalized to 2-norm. + + Parameters + ---------- + `data` : NDArray[np.complex128] + The data. + + Returns + ------- + bool + Whether the vector is normalized to 2-norm or not. + + Usage + ----- + >>> data = np.array([[1, 2], [3, 4]]) + >>> check_padding(data) + """ + len_data = len(data) + return (len_data & (len_data - 1) == 0) and len_data != 0 + + def is_padded(self) -> None: + """ Check if a `quick.data.Data` instance is padded to a power of 2. + + Usage + ----- + >>> data.is_padded() + """ + self.padded = self.check_padding(self.data) + + @staticmethod + def pad_data( + data: NDArray[np.complex128], + target_size: int + ) -> NDArray[np.complex128]: + """ Pad data with zeros up to the nearest power of 2, and return + the padded data. + + Parameters + ---------- + `data` : NDArray[np.complex128] + The data to be padded. + `target_size` : int + The target size to pad the data to. + + Returns + ------- + `padded_data` : NDArray[np.complex128] + The padded data. + + Usage + ----- + >>> data = np.array([1, 2, 3]) + >>> pad_data(data, 4) + """ + padded_data = np.pad( + data, (0, int(target_size - len(data))), + mode="constant" + ) + + return padded_data + + def pad(self) -> None: + """ Pad a `quick.data.Data` instance. + + Usage + ----- + >>> data.pad() + """ + if self.padded: + return + + self.data = self.pad_data(self.data, 2 ** self.num_qubits) + self.padded = True + + def to_quantumstate(self) -> None: + """ Converts a `quick.data.Data` instance to a quantum state. + + Usage + ----- + >>> data.to_quantumstate() + """ + if not self.normalized: + self.normalize() + + if not self.padded: + self.pad() + + def compress( + self, + compression_percentage: float + ) -> None: + """ Compress a `quick.primitives.Statevector` instance. + + Parameters + ---------- + `compression_percentage` : float + The percentage of compression. + + Usage + ----- + >>> data.compress(50) + """ + data_sort_ind = np.argsort(np.abs(self.data)) + + # Set the smallest absolute values of data to zero according to compression parameter + cutoff = int((compression_percentage / 100.0) * len(self.data)) + for i in data_sort_ind[:cutoff]: + self.data[i] = 0 + + def change_indexing( + self, + index_type: Literal["row", "snake"] + ) -> None: + """ Change the indexing of a `quick.primitives.Statevector` instance. + + Parameters + ---------- + `index_type` : Literal["row", "snake"] + The new indexing type, being "row" or "snake". + + Raises + ------ + ValueError + - If the index type is not supported. + + Usage + ----- + >>> data.change_indexing("snake") + """ + if index_type == "snake": + if self.num_qubits >= 3: + # Convert the statevector to a matrix (image) + self.data = self.data.reshape(2, -1) + # Reverse the elements in odd rows + self.data[1::2, :] = self.data[1::2, ::-1] + + self.data = self.data.flatten() + elif index_type == "row": + self.data = self.data + else: + raise ValueError("Index type not supported.") + + def _check__mul__( + self, + other: Any + ) -> None: + """ Check if the multiplication is valid. + + Parameters + ---------- + `other` : Any + The other object to multiply with. + + Raises + ------ + ValueError + - If the two vectors are incompatible. + - If the the statevector and operator are incompatible. + TypeError + - If the `other` type is incompatible. + """ + if isinstance(other, (SupportsFloat, complex)): + return + elif isinstance(other, operator.Operator): + if self.num_qubits != other.num_qubits: + raise ValueError("Cannot multiply two incompatible vectors.") + else: + raise TypeError(f"Multiplication with {type(other)} is not supported.") + + def __eq__( + self, + other: object + ) -> bool: + """ Check if two statevector vectors are equal. + + Parameters + ---------- + `other` : object + The other statevector vector. + + Returns + ------- + bool + Whether the two statevector vectors are equal. + + Raises + ------ + TypeError + - If the `other` object is not a `quick.primitives.Statevector` instance. + + Usage + ----- + >>> statevector1 = Statevector(np.array([1+0j, 0+0j])) + >>> statevector2 = Statevector(np.array([1+0j, 0+0j])) + >>> statevector1 == statevector2 + """ + if not isinstance(other, Statevector): + raise TypeError( + "Statevector can only be compared with other Statevector instances. " + f"Received {type(other)} instead." + ) + + return bool(np.all(np.isclose(self.data, other.data, atol=1e-8, rtol=0))) + + def __len__(self) -> int: + """ Return the length of the statevector vector. + + Returns + ------- + int + The length of the statevector vector. + + Usage + ----- + >>> len(statevector) + """ + return len(self.data) + + def __add__( + self, + other: Statevector + ) -> Statevector: + """ Superpose two statevector states together. + + Parameters + ---------- + `other` : quick.primitives.Statevector + The other statevector state. + + Returns + ------- + quick.primitives.Statevector + The superposed statevector state. + + Raises + ------ + TypeError + - If the `other` object is not a `quick.primitives.Statevector` instance. + ValueError + - If the two statevectors are incompatible. + + Usage + ----- + >>> statevector1 = Statevector(np.array([1+0j, 0+0j])) + >>> statevector2 = Statevector(np.array([1+0j, 0+0j])) + >>> statevector1 + statevector2 + """ + if not isinstance(other, Statevector): + raise TypeError( + "Statevector can only be added to other Statevector instances. " + f"Received {type(other)} instead." + ) + + if self.num_qubits != other.num_qubits: + raise ValueError("Cannot add two incompatible vectors.") + + return Statevector((self.data + other.data).astype(np.complex128)) + + def __mul__( + self, + other: Scalar + ) -> Statevector: + """ Multiply the statevector by a scalar. + + Notes + ----- + The multiplication of a statevector with a scalar does not change + the statevector. This is because the distribution of the statevector + is preserved as the scalar is multiplied with each element of the + statevector. We provide the scalar multiplication for completeness. + + Parameters + ---------- + `other` : Scalar + The other object to multiply the statevector by. + + Returns + ------- + quick.primitives.Statevector + The result of the multiplication. + + Raises + ------ + TypeError + - If the `other` object is not a `quick.primitives.Scalar` instance. + + Usage + ----- + >>> scalar = 2 + >>> statevector = Statevector(np.array([1+0j, 0+0j])) + >>> statevector * scalar + """ + if not isinstance(other, Scalar): + raise TypeError( + "Statevector can only be multiplied by a scalar. " + f"Received {type(other)} instead." + ) + + return Statevector( + (self.data * complex(other)).astype(np.complex128) + ) + + def __rmul__( + self, + other: Scalar + ) -> Statevector: + """ Multiply the statevector by a scalar. + + Notes + ----- + The multiplication of a statevector with a scalar does not change + the statevector. This is because the distribution of the statevector + is preserved as the scalar is multiplied with each element of the + statevector. We provide the scalar multiplication for completeness. + + Parameters + ---------- + `other` : Scalar + The scalar to multiply the statevector by. + + Returns + ------- + quick.primitives.Statevector + The statevector multiplied by the scalar. + + Raises + ------ + TypeError + - If the `other` object is not a `quick.primitives.Scalar` instance. + + Usage + ----- + >>> scalar = 2 + >>> statevector = Statevector(np.array([1+0j, 0+0j])) + >>> scalar * statevector + """ + if not isinstance(other, Scalar): + raise TypeError( + "Statevector can only be multiplied by a scalar. " + f"Received {type(other)} instead." + ) + + return Statevector( + (self.data * complex(other)).astype(np.complex128) + ) + + def __str__(self) -> str: + """ Return the string representation of the statevector vector. + + Returns + ------- + str + The string representation of the statevector vector. + + Usage + ----- + >>> str(statevector) + """ + return f"|{self.label}⟩" + + def __repr__(self) -> str: + """ Return the string representation of the statevector vector. + + Returns + ------- + str + The string representation of the statevector vector. + + Usage + ----- + >>> repr(statevector) + """ + return f"{self.__class__.__name__}(data={self.data}, label={self.label})" \ No newline at end of file diff --git a/quick/synthesis/statepreparation/isometry.py b/quick/synthesis/statepreparation/isometry.py index f406cec..8494a8c 100644 --- a/quick/synthesis/statepreparation/isometry.py +++ b/quick/synthesis/statepreparation/isometry.py @@ -30,7 +30,7 @@ if TYPE_CHECKING: from quick.circuit import Circuit from quick.circuit.circuit_utils import extract_single_qubits_and_diagonal -from quick.primitives import Bra, Ket +from quick.primitives import Statevector from quick.synthesis.statepreparation import StatePreparation from quick.synthesis.statepreparation.statepreparation_utils import ( a, @@ -78,6 +78,8 @@ class Isometry(StatePreparation): ------ TypeError - If the output framework is not a subclass of `quick.circuit.Circuit`. + ValueError + - If the state is not a valid quantum state. Usage ----- @@ -86,22 +88,23 @@ class Isometry(StatePreparation): def apply_state( self, circuit: Circuit, - state: NDArray[np.complex128] | Bra | Ket, + state: NDArray[np.complex128] | Statevector, qubit_indices: int | Sequence[int], compression_percentage: float = 0.0, index_type: Literal["row", "snake"] = "row" ) -> Circuit: - if not isinstance(state, (np.ndarray, Bra, Ket)): + if not isinstance(state, (np.ndarray, Statevector)): try: state = np.array(state).astype(complex) except (ValueError, TypeError): - raise TypeError(f"The state must be a numpy array or a Bra/Ket object. Received {type(state)} instead.") + raise TypeError( + "The state must be a numpy array or a Statevector object. " + f"Received {type(state)} instead." + ) if isinstance(state, np.ndarray): - state = Ket(state) - elif isinstance(state, Bra): - state = state.to_ket() + state = Statevector(state) if isinstance(qubit_indices, SupportsIndex): qubit_indices = [qubit_indices] @@ -279,11 +282,7 @@ def disentangle( # We must reverse the circuit to prepare the state, # as the circuit is uncomputing from the target state # to the zero state - # If the state is a bra, we will apply a horizontal reverse - # which will nullify this reverse, thus we will first check - # if the state is not a bra before applying the reverse - if not isinstance(state, Bra): - isometry_circuit.horizontal_reverse() + isometry_circuit.horizontal_reverse() circuit.add(isometry_circuit, qubit_indices) diff --git a/quick/synthesis/statepreparation/mottonen.py b/quick/synthesis/statepreparation/mottonen.py index 94c8e09..05c7ec6 100644 --- a/quick/synthesis/statepreparation/mottonen.py +++ b/quick/synthesis/statepreparation/mottonen.py @@ -26,7 +26,7 @@ if TYPE_CHECKING: from quick.circuit import Circuit -from quick.primitives import Bra, Ket +from quick.primitives import Statevector from quick.synthesis.statepreparation import StatePreparation from quick.synthesis.statepreparation.statepreparation_utils import ( compute_alpha_y, compute_alpha_z, compute_control_indices, compute_m @@ -70,22 +70,23 @@ class Mottonen(StatePreparation): def apply_state( self, circuit: Circuit, - state: NDArray[np.complex128] | Bra | Ket, + state: NDArray[np.complex128] | Statevector, qubit_indices: int | Sequence[int], compression_percentage: float = 0.0, index_type: Literal["row", "snake"] = "row" ) -> Circuit: - if not isinstance(state, (np.ndarray, Bra, Ket)): + if not isinstance(state, (np.ndarray, Statevector)): try: state = np.array(state).astype(complex) except (ValueError, TypeError): - raise TypeError(f"The state must be a numpy array or a Bra/Ket object. Received {type(state)} instead.") + raise TypeError( + "The state must be a numpy array or a Statevector object. " + f"Received {type(state)} instead." + ) if isinstance(state, np.ndarray): - state = Ket(state) - elif isinstance(state, Bra): - state = state.to_ket() + state = Statevector(state) if isinstance(qubit_indices, SupportsIndex): qubit_indices = [qubit_indices] @@ -165,9 +166,6 @@ def k_controlled_uniform_rotation( # to retrieve the LSB ordering mottonen_circuit.vertical_reverse() - if isinstance(state, Bra): - mottonen_circuit.horizontal_reverse() - circuit.add(mottonen_circuit, qubit_indices) return circuit \ No newline at end of file diff --git a/quick/synthesis/statepreparation/shende.py b/quick/synthesis/statepreparation/shende.py index 51ea24a..fcad943 100644 --- a/quick/synthesis/statepreparation/shende.py +++ b/quick/synthesis/statepreparation/shende.py @@ -26,7 +26,7 @@ if TYPE_CHECKING: from quick.circuit import Circuit -from quick.primitives import Bra, Ket +from quick.primitives import Statevector from quick.synthesis.statepreparation import StatePreparation from quick.synthesis.statepreparation.statepreparation_utils import rotations_to_disentangle @@ -68,22 +68,20 @@ class Shende(StatePreparation): def apply_state( self, circuit: Circuit, - state: NDArray[np.complex128] | Bra | Ket, + state: NDArray[np.complex128] | Statevector, qubit_indices: int | Sequence[int], compression_percentage: float = 0.0, index_type: Literal["row", "snake"] = "row" ) -> Circuit: - if not isinstance(state, (np.ndarray, Bra, Ket)): + if not isinstance(state, (np.ndarray, Statevector)): try: state = np.array(state).astype(complex) except (ValueError, TypeError): - raise TypeError(f"The state must be a numpy array or a Bra/Ket object. Received {type(state)} instead.") + raise TypeError(f"The state must be a numpy array or a Statevector object. Received {type(state)} instead.") if isinstance(state, np.ndarray): - state = Ket(state) - elif isinstance(state, Bra): - state = state.to_ket() + state = Statevector(state) if isinstance(qubit_indices, SupportsIndex): qubit_indices = [qubit_indices] @@ -240,11 +238,7 @@ def disentangle( # We must reverse the circuit to prepare the state, # as the circuit is uncomputing from the target state # to the zero state - # If the state is a bra, we will apply a horizontal reverse - # which will nullify this reverse, thus we will first check - # if the state is not a bra before applying the reverse - if not isinstance(state, Bra): - disentangling_circuit.horizontal_reverse() + disentangling_circuit.horizontal_reverse() circuit.add(disentangling_circuit, qubit_indices) diff --git a/quick/synthesis/statepreparation/statepreparation.py b/quick/synthesis/statepreparation/statepreparation.py index e9d01a0..12ea854 100644 --- a/quick/synthesis/statepreparation/statepreparation.py +++ b/quick/synthesis/statepreparation/statepreparation.py @@ -29,7 +29,7 @@ import quick if TYPE_CHECKING: from quick.circuit import Circuit -from quick.primitives import Bra, Ket +from quick.primitives import Statevector class StatePreparation(ABC): @@ -67,7 +67,7 @@ def __init__( def prepare_state( self, - state: NDArray[np.complex128] | Bra | Ket, + state: NDArray[np.complex128] | Statevector, compression_percentage: float = 0.0, index_type: Literal["row", "snake"] = "row" ) -> Circuit: @@ -75,7 +75,7 @@ def prepare_state( Parameters ---------- - `state` : NDArray[np.complex128] | quick.primitives.Bra | quick.primitives.Ket + `state` : NDArray[np.complex128] | quick.primitives.Statevector The quantum state to prepare. `compression_percentage` : float, optional, default=0.0 Number between 0 an 100, where 0 is no compression and 100 all statevector values are 0. @@ -90,16 +90,19 @@ def prepare_state( Raises ------ TypeError - - If the state is not a numpy array or a Bra/Ket object. + - If the state is not a numpy array or a Statevector object. """ - if not isinstance(state, (np.ndarray, Bra, Ket)): + if not isinstance(state, (np.ndarray, Statevector)): try: state = np.array(state).astype(complex) except (ValueError, TypeError): - raise TypeError(f"The state must be a numpy array or a Bra/Ket object. Received {type(state)} instead.") + raise TypeError( + "The state must be a numpy array or a Statevector object. " + f"Received {type(state)} instead." + ) if isinstance(state, np.ndarray): - state = Ket(state) + state = Statevector(state) num_qubits = state.num_qubits circuit = self.output_framework(num_qubits) @@ -110,7 +113,7 @@ def prepare_state( def apply_state( self, circuit: Circuit, - state: NDArray[np.complex128] | Bra | Ket, + state: NDArray[np.complex128] | Statevector, qubit_indices: int | Sequence[int], compression_percentage: float = 0.0, index_type: Literal["row", "snake"] = "row" @@ -121,7 +124,7 @@ def apply_state( ---------- `circuit` : quick.circuit.Circuit The quantum circuit to which the state is applied. - `state` : NDArray[np.complex128] | quick.primitives.Bra | quick.primitives.Ket + `state` : NDArray[np.complex128] | quick.primitives.Statevector The quantum state to apply. `qubit_indices` : int | Sequence[int] The qubit indices to which the state is applied. @@ -138,7 +141,7 @@ def apply_state( Raises ------ TypeError - - If the state is not a numpy array or a Bra/Ket object. + - If the state is not a numpy array or a Statevector object. - If the qubit indices are not integers or a sequence of integers. ValueError - If the compression percentage is not in the range [0, 100]. diff --git a/tests/compiler/test_shende_compiler.py b/tests/compiler/test_shende_compiler.py index be52504..3a66e15 100644 --- a/tests/compiler/test_shende_compiler.py +++ b/tests/compiler/test_shende_compiler.py @@ -23,15 +23,13 @@ from quick.circuit import TKETCircuit from quick.compiler import ShendeCompiler -from quick.primitives import Bra, Ket, Operator +from quick.primitives import Statevector, Operator from quick.random import generate_random_state, generate_random_unitary # Define the test data generated_statevector = generate_random_state(7) -test_data_bra = Bra(generated_statevector) -test_data_ket = Ket(generated_statevector) -checker_data_ket = copy.deepcopy(test_data_ket) -checker_data_bra = copy.deepcopy(test_data_ket.to_bra()) +test_statevector = Statevector(generated_statevector) +checker_statevector = copy.deepcopy(test_statevector) unitary_matrix = generate_random_unitary(3) @@ -66,7 +64,7 @@ def test_state_preparation(self) -> None: statevector = circuit.get_statevector() # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_ket.data.flatten(), decimal=8) + assert_almost_equal(np.array(statevector), checker_statevector.data.flatten(), decimal=8) def test_unitary_preparation(self) -> None: # Initialize the Shende compiler @@ -86,8 +84,7 @@ def test_check_primitive(self) -> None: shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) # Ensure that the checker does not raise an error when a valid primitive is passed - shende_compiler._check_primitive(test_data_ket) - shende_compiler._check_primitive(test_data_bra) + shende_compiler._check_primitive(test_statevector) shende_compiler._check_primitive(Operator(unitary_matrix)) shende_compiler._check_primitive(generated_statevector) shende_compiler._check_primitive(unitary_matrix) @@ -117,8 +114,7 @@ def test_check_primitive_qubits(self) -> None: shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) # Ensure that the checker does not raise an error when valid qubits are passed - shende_compiler._check_primitive_qubits(test_data_ket, range(7)) - shende_compiler._check_primitive_qubits(test_data_bra, range(7)) + shende_compiler._check_primitive_qubits(test_statevector, range(7)) shende_compiler._check_primitive_qubits(Operator(unitary_matrix), range(3)) shende_compiler._check_primitive_qubits(generated_statevector, range(7)) shende_compiler._check_primitive_qubits(unitary_matrix, range(3)) @@ -129,7 +125,7 @@ def test_check_primitive_qubits_invalid_qubits(self) -> None: # Ensure that the checker raises a ValueError when invalid qubits are passed with pytest.raises(ValueError): - shende_compiler._check_primitive_qubits(test_data_ket, range(8)) + shende_compiler._check_primitive_qubits(test_statevector, range(8)) def test_check_primitives(self) -> None: # Initialize the Shende compiler @@ -137,39 +133,24 @@ def test_check_primitives(self) -> None: # Ensure that the checker does not raise an error when valid primitives are passed shende_compiler._check_primitives([ - (test_data_ket, range(7)), - (test_data_bra, range(7)), + (test_statevector, range(7)), (Operator(unitary_matrix), range(3)), (generated_statevector, range(7)), (unitary_matrix, range(3)) ]) - def test_compile_primitive_bra(self) -> None: - # Initialize the Shende compiler - shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) - - # Encode the data to a circuit - circuit = shende_compiler._compile_primitive(test_data_bra) - - # Get the state of the circuit - statevector = circuit.get_statevector() - - # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_bra.data, decimal=8) - - def test_compile_primitive_ket(self) -> None: # Initialize the Shende compiler shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) # Encode the data to a circuit - circuit = shende_compiler._compile_primitive(test_data_ket) + circuit = shende_compiler._compile_primitive(test_statevector) # Get the state of the circuit statevector = circuit.get_statevector() # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_ket.data.flatten(), decimal=8) + assert_almost_equal(np.array(statevector), checker_statevector.data.flatten(), decimal=8) def test_compile_primitive_operator(self) -> None: # Initialize the Shende compiler @@ -195,7 +176,7 @@ def test_compile_primitive_ndarray(self) -> None: statevector = circuit.get_statevector() # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_ket.data.flatten(), decimal=8) + assert_almost_equal(np.array(statevector), checker_statevector.data.flatten(), decimal=8) # Encode the data to a circuit circuit = shende_compiler._compile_primitive(unitary_matrix) @@ -220,31 +201,18 @@ def test_compile_primitive_invalid_primitive(self) -> None: with pytest.raises(ValueError): shende_compiler._compile_primitive(np.array([0])) - def test_compile_bra(self) -> None: - # Initialize the Shende compiler - shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) - - # Encode the data to a circuit - circuit = shende_compiler.compile(test_data_bra) - - # Get the state of the circuit - statevector = circuit.get_statevector() - - # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_bra.data, decimal=8) - def test_compile_ket(self) -> None: # Initialize the Shende compiler shende_compiler = ShendeCompiler(circuit_framework=TKETCircuit) # Encode the data to a circuit - circuit = shende_compiler.compile(test_data_ket) + circuit = shende_compiler.compile(test_statevector) # Get the state of the circuit statevector = circuit.get_statevector() # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_ket.data.flatten(), decimal=8) + assert_almost_equal(np.array(statevector), checker_statevector.data.flatten(), decimal=8) def test_compile_operator(self) -> None: # Initialize the Shende compiler @@ -270,7 +238,7 @@ def test_compile_ndarray(self) -> None: statevector = circuit.get_statevector() # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_ket.data.flatten(), decimal=8) + assert_almost_equal(np.array(statevector), checker_statevector.data.flatten(), decimal=8) # Encode the data to a circuit circuit = shende_compiler.compile(unitary_matrix) @@ -287,10 +255,8 @@ def test_compile_multiple_primitives(self) -> None: # Generate a random bra and ket over three qubits generated_statevector = generate_random_state(3) - test_data_ket = Ket(generated_statevector) - test_data_bra = Bra(generated_statevector) + test_data_ket = Statevector(generated_statevector) checker_data_ket = copy.deepcopy(test_data_ket) - checker_data_bra = copy.deepcopy(test_data_ket.to_bra()) # Generate two random unitary matrix over three qubits unitary_matrix_1 = generate_random_unitary(3) @@ -299,7 +265,7 @@ def test_compile_multiple_primitives(self) -> None: # Encode the data to a circuit circuit = shende_compiler.compile([ (test_data_ket, [0, 1, 2]), - (test_data_bra, [3, 4, 5]), + (test_data_ket, [3, 4, 5]), (unitary_matrix_1, [0, 1, 2]), (unitary_matrix_2, [3, 4, 5]) ]) @@ -308,8 +274,8 @@ def test_compile_multiple_primitives(self) -> None: statevector = circuit.get_statevector() # Ensure that the state vector is close enough to the expected state vector - checker_data_ket = np.dot(unitary_matrix_1, checker_data_ket.data.flatten()) - checker_data_bra = np.dot(unitary_matrix_2, checker_data_bra.data.flatten()) - checker_statevector = np.kron(checker_data_bra, checker_data_ket) + checker_data_ket_1 = np.dot(unitary_matrix_1, checker_data_ket.data.flatten()) + checker_data_ket_2 = np.dot(unitary_matrix_2, checker_data_ket.data.flatten()) + checker_statevector = np.kron(checker_data_ket_2, checker_data_ket_1) assert_almost_equal(np.array(statevector), checker_statevector, decimal=8) \ No newline at end of file diff --git a/tests/primitives/__init__.py b/tests/primitives/__init__.py index 78b2e5e..209abd9 100644 --- a/tests/primitives/__init__.py +++ b/tests/primitives/__init__.py @@ -13,11 +13,9 @@ # limitations under the License. __all__ = [ - "TestBra", - "TestKet", + "TestStatevector", "TestOperator" ] -from tests.primitives.test_bra import TestBra -from tests.primitives.test_ket import TestKet +# from tests.primitives.test_statevector import TestStatevector # from tests.primitives.test_operator import TestOperator \ No newline at end of file diff --git a/tests/primitives/test_bra.py b/tests/primitives/test_bra.py deleted file mode 100644 index f3f0ef5..0000000 --- a/tests/primitives/test_bra.py +++ /dev/null @@ -1,246 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -__all__ = ["TestBra"] - -import numpy as np -from numpy.testing import assert_allclose -import pytest - -from quick.primitives import Bra, Ket, Operator - - -class TestBra: - """ `tests.primitives.test_bra.TestBra` is the tester class for `quick.primitives.Bra`. - """ - def test_init(self) -> None: - """ Test the initialization of the `quick.primitives.Bra` class. - """ - bra = Bra(np.array([1, 0, 0, 0])) - assert_allclose(bra.data, np.array([1+0j, 0+0j, 0+0j, 0+0j])) - - def test_from_scalar_fail(self) -> None: - """ Test the failure of defining a `quick.primitives.Bra` object from a scalar. - """ - with pytest.raises(AttributeError): - Bra(1) # type: ignore - - def test_from_operator_fail(self) -> None: - """ Test the failure of defining a `quick.primitives.Bra` object from an operator. - """ - with pytest.raises(ValueError): - Bra(np.eye(4, dtype=complex)) - - def test_check_normalization(self) -> None: - """ Test the normalization of the `quick.primitives.Bra` object. - """ - data = np.array([1, 0, 0, 0]) - assert Bra.check_normalization(data) - - def test_check_normalization_fail(self) -> None: - """ Test the failure of the normalization of the `quick.primitives.Bra` object. - """ - data = np.array([1, 1, 1, 1]) - assert not Bra.check_normalization(data) - - def test_normalize(self) -> None: - """ Test the normalization of the `quick.primitives.Bra` object. - """ - data = np.array([1, 0, 0, 1]) - assert_allclose(Bra.normalize_data(data, np.linalg.norm(data)), np.array([(1+0j)/np.sqrt(2), 0+0j, 0+0j, (1+0j)/np.sqrt(2)])) - - bra = Bra(data) - bra.normalize() - assert_allclose(bra.data, np.array([(1+0j)/np.sqrt(2), 0+0j, 0+0j, (1+0j)/np.sqrt(2)])) - - # Re-normalize the already normalized to cover the case where if normalized we simply return - bra.normalize() - assert_allclose(bra.data, np.array([(1+0j)/np.sqrt(2), 0+0j, 0+0j, (1+0j)/np.sqrt(2)])) - - def test_check_padding(self) -> None: - """ Test the padding of the `quick.primitives.Bra` object. - """ - data = np.array([1, 0, 0, 0]) - assert Bra.check_padding(data) - - def test_check_padding_fail(self) -> None: - """ Test the failure of the padding of the `quick.primitives.Bra` object. - """ - data = np.array([1, 0, 0]) - assert not Bra.check_padding(data) - - def test_pad(self) -> None: - """ Test the padding of the `quick.primitives.Bra` object. - """ - data = np.array([1, 0, 0]) - padded_data, _ = Bra.pad_data(data, 4) - assert_allclose(padded_data, np.array([1, 0, 0, 0])) - - bra = Bra(data) - bra.pad() - assert_allclose(bra.data, np.array([1+0j, 0+0j, 0+0j, 0+0j])) - - # Re-pad the already padded to cover the case where if padded we simply return - bra.pad() - assert_allclose(bra.data, np.array([1+0j, 0+0j, 0+0j, 0+0j])) - - def test_to_ket(self) -> None: - """ Test the conversion of the `quick.primitives.Bra` object to a `quick.primitives.Ket` object. - """ - bra = Bra(np.array([1+0j, 0+0j, 0+0j, 0+0j])) - ket = bra.to_ket() - assert_allclose(ket.data, np.array([ - [1-0j], - [0-0j], - [0-0j], - [0-0j] - ])) - - def test_change_indexing(self) -> None: - """ Test the change of indexing of the `quick.primitives.Bra` object. - """ - bra = Bra(np.array([1, 0, 0, 0])) - bra.change_indexing("snake") - assert_allclose(bra.data, np.array([1+0j, 0+0j, 0+0j, 0+0j])) - - bra = Bra(np.array([1, 0, 0, 0, - 1, 0, 0, 0])) - bra.change_indexing("snake") - assert_allclose(bra.data, np.array([ - (1+0j)/np.sqrt(2), 0+0j, 0+0j, 0+0j, - 0+0j, 0+0j, 0+0j, (1+0j)/np.sqrt(2) - ])) - - def test_change_indexing_fail(self) -> None: - """ Test the failure of the change of indexing of the `quick.primitives.Bra` object. - """ - bra = Bra(np.array([1, 0, 0, 0])) - with pytest.raises(ValueError): - bra.change_indexing("invalid") # type: ignore - - def test_check_mul(self) -> None: - """ Test the multiplication of the `quick.primitives.Bra` object. - """ - bra = Bra(np.array([1, 0, 0, 0])) - bra._check__mul__(1) - - ket = Ket(np.array([1, 0, 0, 0])) - bra._check__mul__(ket) - - operator = Operator(np.eye(4, dtype=complex)) - bra._check__mul__(operator) - - def test_check_mul_fail(self) -> None: - """ Test the failure of the multiplication of the `quick.primitives.Bra` object. - """ - ket = Ket(np.array([1, 0, 0, 0])) - bra = Bra(np.array([1, 0])) - with pytest.raises(ValueError): - bra._check__mul__(ket) - - operator = Operator(np.eye(4, dtype=complex)) - with pytest.raises(ValueError): - bra._check__mul__(operator) - - with pytest.raises(NotImplementedError): - bra._check__mul__("invalid") - - def test_eq(self) -> None: - """ Test the equality of the `quick.primitives.Bra` object. - """ - bra1 = Bra(np.array([1, 0, 0, 0])) - bra2 = Bra(np.array([1, 0, 0, 0])) - assert bra1 == bra2 - - def test_eq_fail(self) -> None: - """ Test the failure of the equality of the `quick.primitives.Bra` object. - """ - bra1 = Bra(np.array([1, 0, 0, 0])) - bra2 = Bra(np.array([0, 1, 0, 0])) - assert bra1 != bra2 - - with pytest.raises(NotImplementedError): - bra1 == "invalid" # type: ignore - - def test_len(self) -> None: - """ Test the length of the `quick.primitives.Bra` object. - """ - bra = Bra(np.array([1, 0, 0, 0])) - assert len(bra) == 4 - - def test_add(self) -> None: - """ Test the addition of the `quick.primitives.Bra` objects. - """ - bra1 = Bra(np.array([1, 0, 0, 0])) - bra2 = Bra(np.array([0, 1, 0, 0])) - assert_allclose((bra1 + bra2).data, np.array([(1+0j)/np.sqrt(2), (1+0j)/np.sqrt(2), 0+0j, 0+0j])) - - def test_add_fail(self) -> None: - """ Test the failure of the addition of the `quick.primitives.Bra` objects. - """ - bra1 = Bra(np.array([1, 0, 0, 0])) - bra2 = Bra(np.array([1, 0])) - - with pytest.raises(ValueError): - bra1 + bra2 # type: ignore - - with pytest.raises(NotImplementedError): - bra1 + "invalid" # type: ignore - - def test_mul_scalar(self) -> None: - """ Test the multiplication of the `quick.primitives.Bra` object with a scalar. - """ - bra = Bra(np.array([1, 0, 0, 0])) - assert_allclose((bra * 2).data, np.array([1+0j, 0+0j, 0+0j, 0+0j])) - - def test_mul_bra(self) -> None: - """ Test the multiplication of the `quick.primitives.Bra` object with a `quick.primitives.Ket` object. - """ - ket = Ket(np.array([1, 0, 0, 0])) - bra = Bra(np.array([1, 0, 0, 0])) - assert bra * ket == 1.0 + 0j - - def test_mul_fail(self) -> None: - """ Test the failure of the multiplication of the `quick.primitives.Bra` object. - """ - ket = Ket(np.array([1, 0, 0, 0])) - bra = Bra(np.array([1, 0])) - with pytest.raises(ValueError): - bra * ket # type: ignore - - with pytest.raises(NotImplementedError): - bra * "invalid" # type: ignore - - def test_rmul_scalar(self) -> None: - """ Test the multiplication of a `quick.primitives.Bra` object with a scalar. - """ - bra = Bra(np.array([1, 0, 0, 0])) - assert_allclose((2 * bra).data, np.array([1+0j, 0+0j, 0+0j, 0+0j])) - - def test_str(self) -> None: - """ Test the string representation of the `quick.primitives.Bra` object. - """ - bra = Bra(np.array([1, 0, 0, 0])) - assert str(bra) == "⟨Ψ|" - - bra = Bra(np.array([1, 0, 0, 0]), label="psi") - assert str(bra) == "⟨psi|" - - def test_repr(self) -> None: - """ Test the string representation of the `quick.primitives.Bra` object. - """ - bra = Bra(np.array([1, 0, 0, 0])) - assert repr(bra) == "Bra(data=[1.+0.j 0.+0.j 0.+0.j 0.+0.j], label=Ψ)" \ No newline at end of file diff --git a/tests/primitives/test_ket.py b/tests/primitives/test_ket.py deleted file mode 100644 index 79c8668..0000000 --- a/tests/primitives/test_ket.py +++ /dev/null @@ -1,279 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -__all__ = ["TestKet"] - -import numpy as np -from numpy.testing import assert_allclose -import pytest - -from quick.primitives import Bra, Ket - - -class TestKet: - """ `tests.primitives.test_ket.TestKet` is the tester class for `quick.primitives.Ket`. - """ - def test_init(self) -> None: - """ Test the initialization of the `quick.primitives.Ket` class. - """ - ket = Ket(np.array([1, 0, 0, 0])) - assert_allclose(ket.data, np.array([ - [1+0j], - [0+0j], - [0+0j], - [0+0j] - ])) - - def test_from_scalar_fail(self) -> None: - """ Test the failure of defining a `quick.primitives.Ket` object from a scalar. - """ - with pytest.raises(AttributeError): - Ket(1) # type: ignore - - def test_from_operator_fail(self) -> None: - """ Test the failure of defining a `quick.primitives.Ket` object from an operator. - """ - with pytest.raises(ValueError): - Ket(np.eye(4, dtype=complex)) - - def test_check_normalization(self) -> None: - """ Test the normalization of the `quick.primitives.Ket` object. - """ - data = np.array([1, 0, 0, 0]) - assert Ket.check_normalization(data) - - def test_check_normalization_fail(self) -> None: - """ Test the failure of the normalization of the `quick.primitives.Ket` object. - """ - data = np.array([1, 1, 1, 1]) - assert not Ket.check_normalization(data) - - def test_normalize(self) -> None: - """ Test the normalization of the `quick.primitives.Ket` object. - """ - data = np.array([1, 0, 0, 1]) - assert_allclose(Ket.normalize_data(data, np.linalg.norm(data)), np.array([(1+0j)/np.sqrt(2), 0+0j, 0+0j, (1+0j)/np.sqrt(2)])) - - ket = Ket(data) - ket.normalize() - assert_allclose(ket.data.flatten(), np.array([(1+0j)/np.sqrt(2), 0+0j, 0+0j, (1+0j)/np.sqrt(2)])) - - # Re-normalize the already normalized to cover the case where if normalized we simply return - ket.normalize() - assert_allclose(ket.data.flatten(), np.array([(1+0j)/np.sqrt(2), 0+0j, 0+0j, (1+0j)/np.sqrt(2)])) - - def test_check_padding(self) -> None: - """ Test the padding of the `quick.primitives.Ket` object. - """ - data = np.array([1, 0, 0, 0]) - assert Ket.check_padding(data) - - def test_check_padding_fail(self) -> None: - """ Test the failure of the padding of the `quick.primitives.Ket` object. - """ - data = np.array([1, 0, 0]) - assert not Ket.check_padding(data) - - def test_pad(self) -> None: - """ Test the padding of the `quick.primitives.Ket` object. - """ - data = np.array([1, 0, 0]) - padded_data, _ = Ket.pad_data(data, 4) - assert_allclose(padded_data, np.array([ - [1], - [0], - [0], - [0] - ])) - - ket = Ket(data) - ket.pad() - assert_allclose(ket.data.flatten(), np.array([1+0j, 0+0j, 0+0j, 0+0j])) - - # Re-pad the already padded to cover the case where if padded we simply return - ket.pad() - assert_allclose(ket.data.flatten(), np.array([1+0j, 0+0j, 0+0j, 0+0j])) - - def test_to_bra(self) -> None: - """ Test the conversion of the `quick.primitives.Ket` object to a `quick.primitives.Bra` object. - """ - ket = Ket(np.array([1+0j, 0+0j, 0+0j, 0+0j])) - bra = ket.to_bra() - assert_allclose(bra.data, np.array([1-0j, 0-0j, 0-0j, 0-0j])) - - def test_change_indexing(self) -> None: - """ Test the change of indexing of the `quick.primitives.Ket` object. - """ - ket = Ket(np.array([1, 0, 0, 0])) - ket.change_indexing("snake") - assert_allclose(ket.data, np.array([ - [1+0j], - [0+0j], - [0+0j], - [0+0j] - ])) - - ket = Ket(np.array([ - 1, 0, 0, 0, - 1, 0, 0, 0 - ])) - ket.change_indexing("snake") - assert_allclose(ket.data, np.array([ - [(1+0j)/np.sqrt(2)], - [0+0j], - [0+0j], - [0+0j], - [0+0j], - [0+0j], - [0+0j], - [(1+0j)/np.sqrt(2)] - ])) - - def test_change_indexing_fail(self) -> None: - """ Test the failure of the change of indexing of the `quick.primitives.Ket` object. - """ - ket = Ket(np.array([1, 0, 0, 0])) - with pytest.raises(ValueError): - ket.change_indexing("invalid") # type: ignore - - def test_check_mul(self) -> None: - """ Test the multiplication of the `quick.primitives.Ket` object. - """ - ket = Ket(np.array([1, 0, 0, 0])) - ket._check__mul__(1) - - bra = Bra(np.array([1, 0, 0, 0])) - ket._check__mul__(bra) - - def test_check_mul_fail(self) -> None: - """ Test the failure of the multiplication of the `quick.primitives.Ket` object. - """ - ket = Ket(np.array([1, 0, 0, 0])) - bra = Bra(np.array([1, 0])) - with pytest.raises(ValueError): - ket._check__mul__(bra) - - with pytest.raises(NotImplementedError): - ket._check__mul__("invalid") - - def test_eq(self) -> None: - """ Test the equality of the `quick.primitives.Ket` object. - """ - ket1 = Ket(np.array([1, 0, 0, 0])) - ket2 = Ket(np.array([1, 0, 0, 0])) - assert ket1 == ket2 - - def test_eq_fail(self) -> None: - """ Test the failure of the equality of the `quick.primitives.Ket` object. - """ - ket1 = Ket(np.array([1, 0, 0, 0])) - ket2 = Ket(np.array([1, 0, 0, 1])) - assert ket1 != ket2 - - with pytest.raises(NotImplementedError): - ket1 == "invalid" # type: ignore - - def test_len(self) -> None: - """ Test the length of the `quick.primitives.Ket` object. - """ - ket = Ket(np.array([1, 0, 0, 0])) - assert len(ket) == 4 - - def test_add(self) -> None: - """ Test the addition of the `quick.primitives.Ket` object. - """ - ket1 = Ket(np.array([1, 0, 0, 0])) - ket2 = Ket(np.array([0, 1, 0, 0])) - assert_allclose((ket1 + ket2).data, np.array([ - [(1+0j)/np.sqrt(2)], - [(1+0j)/np.sqrt(2)], - [0+0j], - [0+0j] - ])) - - def test_add_fail(self) -> None: - """ Test the failure of the addition of the `quick.primitives.Ket` objects. - """ - ket1 = Ket(np.array([1, 0, 0, 0])) - ket2 = Ket(np.array([1, 0])) - - with pytest.raises(ValueError): - ket1 + ket2 # type: ignore - - with pytest.raises(NotImplementedError): - ket1 + "invalid" # type: ignore - - def test_mul_scalar(self) -> None: - """ Test the multiplication of the `quick.primitives.Ket` object with a scalar. - """ - ket = Ket(np.array([1, 0, 0, 0])) - assert_allclose((ket * 2).data, np.array([ - [1+0j], - [0+0j], - [0+0j], - [0+0j] - ])) - - def test_mul_bra(self) -> None: - """ Test the multiplication of the `quick.primitives.Ket` object with a `quick.primitives.Bra` object. - """ - ket = Ket(np.array([1, 0, 0, 0])) - bra = Bra(np.array([1, 0, 0, 0])) - # NOTE: Turned off this test until the bra-ket interface is fixed. - # assert_allclose((ket * bra).data, np.array([[1+0j, 0+0j, 0+0j, 0+0j], - # [0+0j, 0+0j, 0+0j, 0+0j], - # [0+0j, 0+0j, 0+0j, 0+0j], - # [0+0j, 0+0j, 0+0j, 0+0j]])) - - def test_mul_fail(self) -> None: - """ Test the failure of the multiplication of the `quick.primitives.Ket` object. - """ - ket = Ket(np.array([1, 0, 0, 0])) - bra = Bra(np.array([1, 0])) - with pytest.raises(ValueError): - ket * bra # type: ignore - - with pytest.raises(NotImplementedError): - ket * "invalid" # type: ignore - - def test_rmul_scalar(self) -> None: - """ Test the multiplication of a `quick.primitives.Ket` object with a scalar. - """ - ket = Ket(np.array([1, 0, 0, 0])) - assert_allclose((2 * ket).data, np.array([ - [1+0j], - [0+0j], - [0+0j], - [0+0j] - ])) - - def test_str(self) -> None: - """ Test the string representation of the `quick.primitives.Ket` object. - """ - ket = Ket(np.array([1, 0, 0, 0])) - assert str(ket) == "|Ψ⟩" - - ket = Ket(np.array([1, 0, 0, 0]), label="psi") - assert str(ket) == "|psi⟩" - - def test_repr(self) -> None: - """ Test the string representation of the `quick.primitives.Ket` object. - """ - ket = Ket(np.array([1, 0, 0, 0])) - assert repr(ket) == ("Ket(data=[[1.+0.j]\n" - " [0.+0.j]\n" - " [0.+0.j]\n" - " [0.+0.j]], label=Ψ)") \ No newline at end of file diff --git a/tests/primitives/test_operator.py b/tests/primitives/test_operator.py index 6e2a945..287f1e1 100644 --- a/tests/primitives/test_operator.py +++ b/tests/primitives/test_operator.py @@ -10,4 +10,126 @@ # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and -# limitations under the License. \ No newline at end of file +# limitations under the License. + +from __future__ import annotations + +__all__ = ["TestOperator"] + +import numpy as np +from numpy.testing import assert_allclose +import pytest + +from quick.primitives import Statevector, Operator + + +class TestOperator: + """ `tests.primitives.test_operator.TestOperator` is the tester class + for `quick.primitives.Operator`. + """ + def test_init(self) -> None: + """ Test the initialization of the `quick.primitives.Operator` class. + """ + operator = Operator( + np.array([ + [1, 0], + [0, 1] + ]), label="A" + ) + assert_allclose(operator.data, np.array([[1+0j, 0+0j], [0+0j, 1+0j]])) + assert operator.shape == (2, 2) + assert operator.num_qubits == 1 + assert operator.label == "A" + + def test_from_scalar_fail(self) -> None: + """ Test the failure of defining a `quick.primitives.Operator` object from a scalar. + """ + with pytest.raises(ValueError): + Operator(1) # type: ignore + + def test_from_statevector_fail(self) -> None: + """ Test the failure of defining a `quick.primitives.Operator` object from a statevector. + """ + with pytest.raises(ValueError): + Operator(np.array([1, 0, 0, 0])) + + def test_check_mul(self) -> None: + """ Test the multiplication of two `quick.primitives.Operator` objects. + """ + op1 = Operator(np.array([[1, 0], [0, 1]])) + op2 = Operator(np.array([[0, 1], [1, 0]])) + state = Statevector(np.array([1, 0])) + op1._check__mul__(op2) + op1._check__mul__(state) + + def test_check_mul_fail(self) -> None: + """ Test the failure of the multiplication of two `quick.primitives.Operator` objects. + """ + op1 = Operator(np.array([[1, 0], [0, 1]])) + with pytest.raises(ValueError): + # Mismatched number of qubits + op1._check__mul__(Statevector(np.array([1, 0, 0, 0]))) + + with pytest.raises(TypeError): + # Incompatible type + op1._check__mul__("not an operator or statevector or scalar") + + def test_eq(self) -> None: + """ Test the equality of two `quick.primitives.Operator` objects. + """ + op1 = Operator(np.array([[1, 0], [0, 1]])) + op2 = Operator(np.array([[1, 0], [0, 1]])) + + assert op1 == op2 + + def test_eq_fail(self) -> None: + """ Test the failure of the equality of two `quick.primitives.Operator` objects. + """ + op1 = Operator(np.array([[1, 0], [0, 1]])) + op2 = Operator(np.array([[0, 1], [1, 0]])) + assert op1 != op2 + + with pytest.raises(TypeError): + # Incompatible type + op1 == "not an operator" # type: ignore + + def test_mul_statevector(self) -> None: + """ Test the multiplication of a `quick.primitives.Operator` with a `quick.primitives.Statevector`. + """ + operator = Operator(np.array([[1, 0], [0, 1]])) + state = Statevector(np.array([1, 0])) + result = operator * state + assert_allclose(result.data, np.array([1, 0])) + + def test_mul_operator(self) -> None: + """ Test the multiplication of two `quick.primitives.Operator` objects. + """ + op1 = Operator(np.array([[1, 0], [0, 1]])) + op2 = Operator(np.array([[0, 1], [1, 0]])) + result = op1 * op2 + assert_allclose(result.data, np.array([[0, 1], [1, 0]])) + + def test_mul_fail(self) -> None: + """ Test the failure of the multiplication of a `quick.primitives.Operator` with an incompatible type. + """ + operator = Operator(np.array([[1, 0], [0, 1]])) + + with pytest.raises(ValueError): + # Incompatible dimensions + operator * Statevector(np.array([1, 0, 0, 0])) # type: ignore + + with pytest.raises(TypeError): + # Incompatible type + operator * "not an operator or statevector" # type: ignore + + def test_str(self) -> None: + """ Test the string representation of the `quick.primitives.Operator` object. + """ + operator = Operator(np.array([[1, 0], [0, 1]]), label="Identity") + assert str(operator) == "Identity" + + def test_repr(self) -> None: + """ Test the string representation of the `quick.primitives.Operator` object. + """ + operator = Operator(np.array([[1, 0], [0, 1]]), label="Identity") + assert repr(operator) == "Operator(data=[[1 0]\n [0 1]], label=Identity)" \ No newline at end of file diff --git a/tests/primitives/test_statevector.py b/tests/primitives/test_statevector.py new file mode 100644 index 0000000..6c0611c --- /dev/null +++ b/tests/primitives/test_statevector.py @@ -0,0 +1,220 @@ +# Copyright 2023-2025 Qualition Computing LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://github.com/Qualition/quick/blob/main/LICENSE +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +__all__ = ["TestStatevector"] + +import numpy as np +from numpy.testing import assert_almost_equal +import pytest + +from quick.primitives import Statevector + + +class TestStatevector: + """ `tests.primitives.test_statevector.TestStatevector` is the tester class + for `quick.primitives.Statevector`. + """ + def test_init(self) -> None: + """ Test the initialization of the `quick.primitives.Statevector` class. + """ + statevector = Statevector(np.array([1, 0, 0, 0])) + assert_almost_equal(statevector.data, np.array([1+0j, 0+0j, 0+0j, 0+0j])) + + def test_from_scalar_fail(self) -> None: + """ Test the failure of defining a `quick.primitives.Statevector` object from a scalar. + """ + with pytest.raises(ValueError): + Statevector(1) # type: ignore + + def test_from_operator_fail(self) -> None: + """ Test the failure of defining a `quick.primitives.Statevector` object from an operator. + """ + with pytest.raises(ValueError): + Statevector(np.eye(4, dtype=complex)) + + def test_check_normalization(self) -> None: + """ Test the normalization of the `quick.primitives.Statevector` object. + """ + data = np.array([1, 0, 0, 0]) + assert Statevector.check_normalization(data) + + def test_check_normalization_fail(self) -> None: + """ Test the failure of the normalization of the `quick.primitives.Statevector` object. + """ + data = np.array([1, 1, 1, 1]) + assert not Statevector.check_normalization(data) + + def test_normalize(self) -> None: + """ Test the normalization of the `quick.primitives.Statevector` object. + """ + data = np.array([1, 0, 0, 1]) + assert_almost_equal( + Statevector.normalize_data( + data, + np.linalg.norm(data)), np.array([(1+0j)/np.sqrt(2), 0+0j, 0+0j, (1+0j)/np.sqrt(2)] + ) + ) + + statevector = Statevector(data) + statevector.normalize() + assert_almost_equal(statevector.data, np.array([(1+0j)/np.sqrt(2), 0+0j, 0+0j, (1+0j)/np.sqrt(2)])) + + # Re-normalize the already normalized to cover the case where if normalized we simply return + statevector.normalize() + assert_almost_equal(statevector.data, np.array([(1+0j)/np.sqrt(2), 0+0j, 0+0j, (1+0j)/np.sqrt(2)])) + + def test_check_padding(self) -> None: + """ Test the padding of the `quick.primitives.Statevector` object. + """ + data = np.array([1, 0, 0, 0]) + assert Statevector.check_padding(data) + + def test_check_padding_fail(self) -> None: + """ Test the failure of the padding of the `quick.primitives.Statevector` object. + """ + data = np.array([1, 0, 0]) + assert not Statevector.check_padding(data) + + def test_pad(self) -> None: + """ Test the padding of the `quick.primitives.Statevector` object. + """ + data = np.array([1, 0, 0]) + padded_data = Statevector.pad_data(data, 4) + assert_almost_equal(padded_data, np.array([1, 0, 0, 0])) + + statevector = Statevector(data) + statevector.pad() + assert_almost_equal(statevector.data, np.array([1+0j, 0+0j, 0+0j, 0+0j])) + + # Re-pad the already padded to cover the case where if padded we simply return + statevector.pad() + assert_almost_equal(statevector.data, np.array([1+0j, 0+0j, 0+0j, 0+0j])) + + def test_change_indexing(self) -> None: + """ Test the change of indexing of the `quick.primitives.Statevector` object. + """ + statevector = Statevector(np.array([1, 0, 0, 0])) + statevector.change_indexing("snake") + assert_almost_equal(statevector.data, np.array([1+0j, 0+0j, 0+0j, 0+0j])) + + statevector = Statevector(np.array([1, 0, 0, 0, + 1, 0, 0, 0])) + statevector.change_indexing("snake") + assert_almost_equal(statevector.data, np.array([ + (1+0j)/np.sqrt(2), 0+0j, 0+0j, 0+0j, + 0+0j, 0+0j, 0+0j, (1+0j)/np.sqrt(2) + ])) + + def test_change_indexing_fail(self) -> None: + """ Test the failure of the change of indexing of the `quick.primitives.Statevector` object. + """ + statevector = Statevector(np.array([1, 0, 0, 0])) + with pytest.raises(ValueError): + statevector.change_indexing("invalid") # type: ignore + + def test_check_mul(self) -> None: + """ Test the multiplication of the `quick.primitives.Statevector` object. + """ + statevector = Statevector(np.array([1, 0, 0, 0])) + statevector._check__mul__(1) + + def test_check_mul_fail(self) -> None: + """ Test the failure of the multiplication of the `quick.primitives.Statevector` object. + """ + statevector = Statevector(np.array([1, 0, 0, 0])) + + with pytest.raises(TypeError): + statevector._check__mul__("invalid") + + def test_eq(self) -> None: + """ Test the equality of the `quick.primitives.Statevector` object. + """ + statevector1 = Statevector(np.array([1, 0, 0, 0])) + statevector2 = Statevector(np.array([1, 0, 0, 0])) + assert statevector1 == statevector2 + + def test_eq_fail(self) -> None: + """ Test the failure of the equality of the `quick.primitives.Statevector` object. + """ + statevector1 = Statevector(np.array([1, 0, 0, 0])) + statevector2 = Statevector(np.array([0, 1, 0, 0])) + assert statevector1 != statevector2 + + with pytest.raises(TypeError): + statevector1 == "invalid" # type: ignore + + def test_len(self) -> None: + """ Test the length of the `quick.primitives.Statevector` object. + """ + statevector = Statevector(np.array([1, 0, 0, 0])) + assert len(statevector) == 4 + + def test_add(self) -> None: + """ Test the addition of the `quick.primitives.Statevector` objects. + """ + statevector1 = Statevector(np.array([1, 0, 0, 0])) + statevector2 = Statevector(np.array([0, 1, 0, 0])) + assert_almost_equal( + (statevector1 + statevector2).data, + np.array([(1+0j)/np.sqrt(2), (1+0j)/np.sqrt(2), 0+0j, 0+0j]) + ) + + def test_add_fail(self) -> None: + """ Test the failure of the addition of the `quick.primitives.Statevector` objects. + """ + statevector1 = Statevector(np.array([1, 0, 0, 0])) + statevector2 = Statevector(np.array([1, 0])) + + with pytest.raises(ValueError): + statevector1 + statevector2 # type: ignore + + with pytest.raises(TypeError): + statevector1 + "invalid" # type: ignore + + def test_mul_scalar(self) -> None: + """ Test the multiplication of the `quick.primitives.Statevector` object with a scalar. + """ + statevector = Statevector(np.array([1, 0, 0, 0])) + assert_almost_equal((statevector * 2).data, np.array([1+0j, 0+0j, 0+0j, 0+0j])) + + def test_mul_fail(self) -> None: + """ Test the failure of the multiplication of the `quick.primitives.Statevector` object. + """ + statevector = Statevector(np.array([1, 0])) + + with pytest.raises(TypeError): + statevector * "invalid" # type: ignore + + def test_rmul_scalar(self) -> None: + """ Test the multiplication of a `quick.primitives.Statevector` object with a scalar. + """ + statevector = Statevector(np.array([1, 0, 0, 0])) + assert_almost_equal((2 * statevector).data, np.array([1+0j, 0+0j, 0+0j, 0+0j])) + + def test_str(self) -> None: + """ Test the string representation of the `quick.primitives.Statevector` object. + """ + statevector = Statevector(np.array([1, 0, 0, 0])) + assert str(statevector) == "|Ψ⟩" + + statevector = Statevector(np.array([1, 0, 0, 0]), label="psi") + assert str(statevector) == "|psi⟩" + + def test_repr(self) -> None: + """ Test the string representation of the `quick.primitives.Statevector` object. + """ + statevector = Statevector(np.array([1, 0, 0, 0])) + assert repr(statevector) == "Statevector(data=[1.+0.j 0.+0.j 0.+0.j 0.+0.j], label=Ψ)" \ No newline at end of file diff --git a/tests/synthesis/statepreparation/test_isometry.py b/tests/synthesis/statepreparation/test_isometry.py index 7e13d72..cfdb609 100644 --- a/tests/synthesis/statepreparation/test_isometry.py +++ b/tests/synthesis/statepreparation/test_isometry.py @@ -22,17 +22,15 @@ import pytest from quick.circuit import QiskitCircuit -from quick.primitives import Bra, Ket +from quick.primitives import Statevector from quick.random import generate_random_state from quick.synthesis.statepreparation import Isometry from tests.synthesis.statepreparation import StatePreparationTemplate # Define the test data generated_data = generate_random_state(7) -test_data_bra = Bra(generated_data) -test_data_ket = Ket(generated_data) -checker_data_ket = copy.deepcopy(test_data_ket) -checker_data_bra = copy.deepcopy(test_data_ket.to_bra()) +test_statevector = Statevector(generated_data) +checker_statevector = copy.deepcopy(test_statevector) class TestIsometry(StatePreparationTemplate): @@ -46,31 +44,18 @@ def test_init_invalid_output_framework(self) -> None: with pytest.raises(TypeError): Isometry("invalid_framework") # type: ignore - def test_prepare_state_bra(self) -> None: - # Initialize the Isometry encoder - isometry_encoder = Isometry(QiskitCircuit) - - # Encode the data to a circuit - circuit = isometry_encoder.prepare_state(test_data_bra) - - # Get the state of the circuit - statevector = circuit.get_statevector() - - # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_bra.data, decimal=8) - def test_prepare_state_ket(self) -> None: # Initialize the Isometry encoder isometry_encoder = Isometry(QiskitCircuit) # Encode the data to a circuit - circuit = isometry_encoder.prepare_state(test_data_ket) + circuit = isometry_encoder.prepare_state(test_statevector) # Get the state of the circuit statevector = circuit.get_statevector() # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_ket.data.flatten(), decimal=8) + assert_almost_equal(np.array(statevector), checker_statevector.data.flatten(), decimal=8) def test_prepare_state_ndarray(self) -> None: # Initialize the Isometry encoder @@ -83,7 +68,7 @@ def test_prepare_state_ndarray(self) -> None: statevector = circuit.get_statevector() # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_ket.data.flatten(), decimal=8) + assert_almost_equal(np.array(statevector), checker_statevector.data.flatten(), decimal=8) def test_apply_state_ket(self) -> None: # Initialize the Isometry encoder @@ -93,29 +78,13 @@ def test_apply_state_ket(self) -> None: circuit = QiskitCircuit(7) # Apply the state to a circuit - circuit = isometry_encoder.apply_state(circuit, test_data_ket, range(7)) - - # Get the state of the circuit - statevector = circuit.get_statevector() - - # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_ket.data.flatten(), decimal=8) - - def test_apply_state_bra(self) -> None: - # Initialize the Isometry encoder - isometry_encoder = Isometry(QiskitCircuit) - - # Initialize the circuit - circuit = QiskitCircuit(7) - - # Apply the state to a circuit - circuit = isometry_encoder.apply_state(circuit, test_data_bra, range(7)) + circuit = isometry_encoder.apply_state(circuit, test_statevector, range(7)) # Get the state of the circuit statevector = circuit.get_statevector() # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_bra.data, decimal=8) + assert_almost_equal(np.array(statevector), checker_statevector.data.flatten(), decimal=8) def test_apply_state_ndarray(self) -> None: # Initialize the Isometry encoder @@ -131,7 +100,7 @@ def test_apply_state_ndarray(self) -> None: statevector = circuit.get_statevector() # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_ket.data.flatten(), decimal=8) + assert_almost_equal(np.array(statevector), checker_statevector.data.flatten(), decimal=8) def test_apply_state_invalid_input(self) -> None: # Initialize the Isometry encoder @@ -151,10 +120,10 @@ def test_apply_state_invalid_qubit_indices(self) -> None: circuit = QiskitCircuit(7) with pytest.raises(TypeError): - isometry_encoder.apply_state(circuit, test_data_ket, "invalid_qubit_indices") # type: ignore + isometry_encoder.apply_state(circuit, test_statevector, "invalid_qubit_indices") # type: ignore with pytest.raises(TypeError): - isometry_encoder.apply_state(circuit, test_data_ket, [1+1j, 2+2j, 3+3j]) # type: ignore + isometry_encoder.apply_state(circuit, test_statevector, [1+1j, 2+2j, 3+3j]) # type: ignore def test_apply_state_qubit_indices_out_of_range(self) -> None: # Initialize the Isometry encoder @@ -164,4 +133,4 @@ def test_apply_state_qubit_indices_out_of_range(self) -> None: circuit = QiskitCircuit(7) with pytest.raises(IndexError): - isometry_encoder.apply_state(circuit, test_data_ket, [0, 1, 2, 3, 4, 5, 12]) \ No newline at end of file + isometry_encoder.apply_state(circuit, test_statevector, [0, 1, 2, 3, 4, 5, 12]) \ No newline at end of file diff --git a/tests/synthesis/statepreparation/test_mottonen.py b/tests/synthesis/statepreparation/test_mottonen.py index 4c2fee5..4ffeed4 100644 --- a/tests/synthesis/statepreparation/test_mottonen.py +++ b/tests/synthesis/statepreparation/test_mottonen.py @@ -22,17 +22,15 @@ import pytest from quick.circuit import QiskitCircuit -from quick.primitives import Bra, Ket +from quick.primitives import Statevector from quick.random import generate_random_state from quick.synthesis.statepreparation import Mottonen from tests.synthesis.statepreparation import StatePreparationTemplate # Define the test data generated_data = generate_random_state(7) -test_data_bra = Bra(generated_data) -test_data_ket = Ket(generated_data) -checker_data_ket = copy.deepcopy(test_data_ket) -checker_data_bra = copy.deepcopy(test_data_ket.to_bra()) +test_statevector = Statevector(generated_data) +checker_statevector = copy.deepcopy(test_statevector) class TestMottonen(StatePreparationTemplate): @@ -46,31 +44,18 @@ def test_init_invalid_output_framework(self) -> None: with pytest.raises(TypeError): Mottonen("invalid_framework") # type: ignore - def test_prepare_state_bra(self) -> None: - # Initialize the Mottonen encoder - shende_encoder = Mottonen(QiskitCircuit) - - # Encode the data to a circuit - circuit = shende_encoder.prepare_state(test_data_bra) - - # Get the state of the circuit - statevector = circuit.get_statevector() - - # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_bra.data, decimal=8) - def test_prepare_state_ket(self) -> None: # Initialize the Mottonen encoder shende_encoder = Mottonen(QiskitCircuit) # Encode the data to a circuit - circuit = shende_encoder.prepare_state(test_data_ket) + circuit = shende_encoder.prepare_state(test_statevector) # Get the state of the circuit statevector = circuit.get_statevector() # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_ket.data.flatten(), decimal=8) + assert_almost_equal(np.array(statevector), checker_statevector.data.flatten(), decimal=8) def test_prepare_state_ndarray(self) -> None: # Initialize the Mottonen encoder @@ -83,7 +68,7 @@ def test_prepare_state_ndarray(self) -> None: statevector = circuit.get_statevector() # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_ket.data.flatten(), decimal=8) + assert_almost_equal(np.array(statevector), checker_statevector.data.flatten(), decimal=8) def test_apply_state_ket(self) -> None: # Initialize the Mottonen encoder @@ -93,29 +78,13 @@ def test_apply_state_ket(self) -> None: circuit = QiskitCircuit(7) # Apply the state to a circuit - circuit = shende_encoder.apply_state(circuit, test_data_ket, range(7)) - - # Get the state of the circuit - statevector = circuit.get_statevector() - - # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_ket.data.flatten(), decimal=8) - - def test_apply_state_bra(self) -> None: - # Initialize the Mottonen encoder - shende_encoder = Mottonen(QiskitCircuit) - - # Initialize the circuit - circuit = QiskitCircuit(7) - - # Apply the state to a circuit - circuit = shende_encoder.apply_state(circuit, test_data_bra, range(7)) + circuit = shende_encoder.apply_state(circuit, test_statevector, range(7)) # Get the state of the circuit statevector = circuit.get_statevector() # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_bra.data, decimal=8) + assert_almost_equal(np.array(statevector), checker_statevector.data.flatten(), decimal=8) def test_apply_state_ndarray(self) -> None: # Initialize the Mottonen encoder @@ -131,7 +100,7 @@ def test_apply_state_ndarray(self) -> None: statevector = circuit.get_statevector() # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_ket.data.flatten(), decimal=8) + assert_almost_equal(np.array(statevector), checker_statevector.data.flatten(), decimal=8) def test_apply_state_invalid_input(self) -> None: # Initialize the Mottonen encoder @@ -153,10 +122,10 @@ def test_apply_state_invalid_qubit_indices(self) -> None: # Apply the state to a circuit with pytest.raises(TypeError): - shende_encoder.apply_state(circuit, test_data_ket, "invalid_qubit_indices") # type: ignore + shende_encoder.apply_state(circuit, test_statevector, "invalid_qubit_indices") # type: ignore with pytest.raises(TypeError): - shende_encoder.apply_state(circuit, test_data_ket, [1+1j, 2+2j, 3+3j]) # type: ignore + shende_encoder.apply_state(circuit, test_statevector, [1+1j, 2+2j, 3+3j]) # type: ignore def test_apply_state_qubit_indices_out_of_range(self) -> None: # Initialize the Mottonen encoder @@ -167,4 +136,4 @@ def test_apply_state_qubit_indices_out_of_range(self) -> None: # Apply the state to a circuit with pytest.raises(IndexError): - shende_encoder.apply_state(circuit, test_data_ket, [0, 1, 2, 3, 4, 5, 12]) \ No newline at end of file + shende_encoder.apply_state(circuit, test_statevector, [0, 1, 2, 3, 4, 5, 12]) \ No newline at end of file diff --git a/tests/synthesis/statepreparation/test_shende.py b/tests/synthesis/statepreparation/test_shende.py index 3678079..cfdc6c0 100644 --- a/tests/synthesis/statepreparation/test_shende.py +++ b/tests/synthesis/statepreparation/test_shende.py @@ -22,17 +22,15 @@ import pytest from quick.circuit import QiskitCircuit -from quick.primitives import Bra, Ket +from quick.primitives import Statevector from quick.random import generate_random_state from quick.synthesis.statepreparation import Shende from tests.synthesis.statepreparation import StatePreparationTemplate # Define the test data generated_data = generate_random_state(7) -test_data_bra = Bra(generated_data) -test_data_ket = Ket(generated_data) -checker_data_ket = copy.deepcopy(test_data_ket) -checker_data_bra = copy.deepcopy(test_data_ket.to_bra()) +test_statevector = Statevector(generated_data) +checker_statevector = copy.deepcopy(test_statevector) class TestShende(StatePreparationTemplate): @@ -46,31 +44,18 @@ def test_init_invalid_output_framework(self) -> None: with pytest.raises(TypeError): Shende("invalid_framework") # type: ignore - def test_prepare_state_bra(self) -> None: - # Initialize the Shende encoder - shende_encoder = Shende(QiskitCircuit) - - # Encode the data to a circuit - circuit = shende_encoder.prepare_state(test_data_bra) - - # Get the state of the circuit - statevector = circuit.get_statevector() - - # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_bra.data, decimal=8) - def test_prepare_state_ket(self) -> None: # Initialize the Shende encoder shende_encoder = Shende(QiskitCircuit) # Encode the data to a circuit - circuit = shende_encoder.prepare_state(test_data_ket) + circuit = shende_encoder.prepare_state(test_statevector) # Get the state of the circuit statevector = circuit.get_statevector() # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_ket.data.flatten(), decimal=8) + assert_almost_equal(np.array(statevector), checker_statevector.data.flatten(), decimal=8) def test_prepare_state_ndarray(self) -> None: # Initialize the Shende encoder @@ -83,7 +68,7 @@ def test_prepare_state_ndarray(self) -> None: statevector = circuit.get_statevector() # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_ket.data.flatten(), decimal=8) + assert_almost_equal(np.array(statevector), checker_statevector.data.flatten(), decimal=8) def test_apply_state_ket(self) -> None: # Initialize the Shende encoder @@ -93,29 +78,13 @@ def test_apply_state_ket(self) -> None: circuit = QiskitCircuit(7) # Apply the state to a circuit - circuit = shende_encoder.apply_state(circuit, test_data_ket, range(7)) - - # Get the state of the circuit - statevector = circuit.get_statevector() - - # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_ket.data.flatten(), decimal=8) - - def test_apply_state_bra(self) -> None: - # Initialize the Shende encoder - shende_encoder = Shende(QiskitCircuit) - - # Initialize the circuit - circuit = QiskitCircuit(7) - - # Apply the state to a circuit - circuit = shende_encoder.apply_state(circuit, test_data_bra, range(7)) + circuit = shende_encoder.apply_state(circuit, test_statevector, range(7)) # Get the state of the circuit statevector = circuit.get_statevector() # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_bra.data, decimal=8) + assert_almost_equal(np.array(statevector), checker_statevector.data.flatten(), decimal=8) def test_apply_state_ndarray(self) -> None: # Initialize the Shende encoder @@ -131,7 +100,7 @@ def test_apply_state_ndarray(self) -> None: statevector = circuit.get_statevector() # Ensure that the state vector is close enough to the expected state vector - assert_almost_equal(np.array(statevector), checker_data_ket.data.flatten(), decimal=8) + assert_almost_equal(np.array(statevector), checker_statevector.data.flatten(), decimal=8) def test_apply_state_invalid_input(self) -> None: # Initialize the Shende encoder @@ -151,10 +120,10 @@ def test_apply_state_invalid_qubit_indices(self) -> None: circuit = QiskitCircuit(7) with pytest.raises(TypeError): - shende_encoder.apply_state(circuit, test_data_ket, "invalid_qubit_indices") # type: ignore + shende_encoder.apply_state(circuit, test_statevector, "invalid_qubit_indices") # type: ignore with pytest.raises(TypeError): - shende_encoder.apply_state(circuit, test_data_ket, [1+1j, 2+2j, 3+3j]) # type: ignore + shende_encoder.apply_state(circuit, test_statevector, [1+1j, 2+2j, 3+3j]) # type: ignore def test_apply_state_qubit_indices_out_of_range(self) -> None: # Initialize the Shende encoder @@ -164,4 +133,4 @@ def test_apply_state_qubit_indices_out_of_range(self) -> None: circuit = QiskitCircuit(7) with pytest.raises(IndexError): - shende_encoder.apply_state(circuit, test_data_ket, [0, 1, 2, 3, 4, 5, 12]) \ No newline at end of file + shende_encoder.apply_state(circuit, test_statevector, [0, 1, 2, 3, 4, 5, 12]) \ No newline at end of file diff --git a/tests/synthesis/statepreparation/test_statepreparation.py b/tests/synthesis/statepreparation/test_statepreparation.py index 64a61f2..0655e84 100644 --- a/tests/synthesis/statepreparation/test_statepreparation.py +++ b/tests/synthesis/statepreparation/test_statepreparation.py @@ -35,12 +35,7 @@ def test_init_invalid_output_framework(self) -> None: @abstractmethod def test_prepare_state_ket(self) -> None: - """ Test the preparation of the state from a `quick.primitives.Ket` instance. - """ - - @abstractmethod - def test_prepare_state_bra(self) -> None: - """ Test the preparation of the state from a `quick.primitives.Bra` instance. + """ Test the preparation of the state from a `quick.primitives.Statevector` instance. """ @abstractmethod @@ -50,12 +45,7 @@ def test_prepare_state_ndarray(self) -> None: @abstractmethod def test_apply_state_ket(self) -> None: - """ Test the application of the state from a `quick.primitives.Ket` instance. - """ - - @abstractmethod - def test_apply_state_bra(self) -> None: - """ Test the application of the state from a `quick.primitives.Bra` instance. + """ Test the application of the state from a `quick.primitives.Statevector` instance. """ @abstractmethod From 7eba2573206f87be7a5bbae2b45ed0a487b3db3e Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Wed, 30 Jul 2025 19:57:06 +0800 Subject: [PATCH 48/58] - Uncommented import in `__init__.py` --- tests/primitives/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/primitives/__init__.py b/tests/primitives/__init__.py index 209abd9..f500ef0 100644 --- a/tests/primitives/__init__.py +++ b/tests/primitives/__init__.py @@ -17,5 +17,5 @@ "TestOperator" ] -# from tests.primitives.test_statevector import TestStatevector -# from tests.primitives.test_operator import TestOperator \ No newline at end of file +from tests.primitives.test_statevector import TestStatevector +from tests.primitives.test_operator import TestOperator \ No newline at end of file From e7e6d2317cc07d4510530fcd4c452d8fc5623d7d Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Tue, 5 Aug 2025 20:02:37 +0800 Subject: [PATCH 49/58] - Added partial trace, trace, and tensor product to `quick.primitives.Statevector`. - Given `quick.metrics.calculate_entanglement_entropy_slope()` uses only statevectors, the code migrates from `qiskit.quantum_info.partial_trace()` to native `quick.primitives.Statevector.partial_trace()`. - Added testers for new functionality. --- quick/metrics/metrics.py | 20 ++--- quick/primitives/statevector.py | 122 ++++++++++++++++++++++++--- tests/primitives/test_statevector.py | 46 ++++++++++ 3 files changed, 164 insertions(+), 24 deletions(-) diff --git a/quick/metrics/metrics.py b/quick/metrics/metrics.py index a6ba4c0..9a59c30 100644 --- a/quick/metrics/metrics.py +++ b/quick/metrics/metrics.py @@ -28,9 +28,9 @@ import numpy as np from numpy.typing import NDArray import quimb.tensor as qtn # type: ignore -from qiskit.quantum_info import partial_trace # type: ignore from quick.predicates import is_density_matrix, is_statevector, is_unitary_matrix +from quick.primitives import Statevector def _calculate_1d_entanglement_range(mps: qtn.MatrixProductState) -> list[tuple[int, int]]: @@ -222,23 +222,19 @@ def calculate_entanglement_entropy_slope(statevector: NDArray[np.complex128]) -> ----- >>> entanglement_entropy_slope = calculate_entanglement_entropy_slope(statevector) """ - if not is_statevector(statevector): - raise ValueError("The input must be a statevector.") - - num_qubits = int( - np.ceil( - np.log2(len(statevector)) - ) - ) + if not isinstance(statevector, Statevector): + state = Statevector(statevector) + else: + state = statevector - max_k = num_qubits // 2 + max_k = state.num_qubits // 2 entropies = np.empty(max_k, dtype=np.float64) for k in range(1, max_k + 1): # Trace out rest of the qubits to extract the # reduced density matrix for the first k qubits - rho_A = partial_trace(statevector, list(range(k, num_qubits))) # type: ignore - S = calculate_entanglement_entropy(rho_A.data) + rho = state.partial_trace(list(range(k, state.num_qubits))) + S = calculate_entanglement_entropy(np.array(rho)) entropies[k - 1] = S # We use half of the entropies to calculate the slope diff --git a/quick/primitives/statevector.py b/quick/primitives/statevector.py index c8ef795..44e61e5 100644 --- a/quick/primitives/statevector.py +++ b/quick/primitives/statevector.py @@ -139,7 +139,7 @@ def is_normalized(self) -> None: Usage ----- - >>> data.is_normalized() + >>> statevector.is_normalized() """ self.normalized = self.check_normalization(self.data) @@ -176,7 +176,7 @@ def normalize(self) -> None: Usage ----- - >>> data.normalize() + >>> statevector.normalize() """ if self.normalized: return @@ -207,11 +207,11 @@ def check_padding(data: NDArray[np.complex128]) -> bool: return (len_data & (len_data - 1) == 0) and len_data != 0 def is_padded(self) -> None: - """ Check if a `quick.data.Data` instance is padded to a power of 2. + """ Check if a `quick.primitives.Statevector` instance is padded to a power of 2. Usage ----- - >>> data.is_padded() + >>> statevector.is_padded() """ self.padded = self.check_padding(self.data) @@ -248,11 +248,11 @@ def pad_data( return padded_data def pad(self) -> None: - """ Pad a `quick.data.Data` instance. + """ Pad a `quick.primitives.Statevector` instance. Usage ----- - >>> data.pad() + >>> statevector.pad() """ if self.padded: return @@ -261,11 +261,11 @@ def pad(self) -> None: self.padded = True def to_quantumstate(self) -> None: - """ Converts a `quick.data.Data` instance to a quantum state. + """ Ensure the statevector is in a valid quantum state. Usage ----- - >>> data.to_quantumstate() + >>> statevector.to_quantumstate() """ if not self.normalized: self.normalize() @@ -286,7 +286,7 @@ def compress( Usage ----- - >>> data.compress(50) + >>> statevector.compress(50) """ data_sort_ind = np.argsort(np.abs(self.data)) @@ -313,7 +313,7 @@ def change_indexing( Usage ----- - >>> data.change_indexing("snake") + >>> statevector.change_indexing("snake") """ if index_type == "snake": if self.num_qubits >= 3: @@ -328,6 +328,67 @@ def change_indexing( else: raise ValueError("Index type not supported.") + def trace(self) -> float: + """ Calculate the trace of the statevector. + + Returns + ------- + float + The trace of the statevector. + + Usage + ----- + >>> statevector.trace() + """ + return float(np.sum(np.abs(self.data) ** 2)) + + def partial_trace( + self, + trace_qubit_indices: list[int] + ) -> float | NDArray[np.complex128]: + """ Calculate the partial trace of the statevector. + + Parameters + ---------- + `trace_qubit_indices` : list[int] + The indices of the qubits to trace out. + + Returns + ------- + `rho` : float | NDArray[np.complex128] + The resulting density matrix after tracing out the specified qubits. + If the `trace_qubit_indices` match the total number of qubits, then + we return the trace of the statevector. + + Raises + ------ + ValueError + - If the trace qubit indices are invalid. + + Usage + ----- + >>> statevector.partial_trace([0, 1]) + """ + for i in trace_qubit_indices: + if not 0 <= i < self.num_qubits: + raise ValueError( + f"Invalid trace qubit index {i}. " + f"Valid indices are in range(0, {self.num_qubits})." + ) + + num_traced_qubits = len(trace_qubit_indices) + traced_shape = (2**(self.num_qubits - num_traced_qubits),) * 2 + + if num_traced_qubits == self.num_qubits: + return self.trace() + + trace_systems = [self.num_qubits - 1 - i for i in trace_qubit_indices] + state = self.data.reshape([2] * self.num_qubits) + rho = np.tensordot(state, state.conj(), axes=(trace_systems, trace_systems)) + rho = np.reshape(rho, traced_shape) + + return rho + def _check__mul__( self, other: Any @@ -470,7 +531,7 @@ def __mul__( Raises ------ TypeError - - If the `other` object is not a `quick.primitives.Scalar` instance. + - If the `other` object is not a number. Usage ----- @@ -514,7 +575,7 @@ def __rmul__( Raises ------ TypeError - - If the `other` object is not a `quick.primitives.Scalar` instance. + - If the `other` object is not a number. Usage ----- @@ -532,6 +593,43 @@ def __rmul__( (self.data * complex(other)).astype(np.complex128) ) + def __matmul__( + self, + other: Statevector + ) -> Statevector: + """ Tensor product of two statevectors. + + Parameters + ---------- + `other` : quick.primitives.Statevector + The statevector to tensor product with. + + Returns + ------- + quick.primitives.Statevector + The resulting statevector. + + Raises + ------ + TypeError + - If `other` is not a `quick.primitives.Statevector`. + + Usage + ----- + >>> statevector1 = Statevector(np.array([1+0j, 0+0j])) + >>> statevector2 = Statevector(np.array([1+0j, 0+0j])) + >>> statevector1 @ statevector2 + """ + if not isinstance(other, Statevector): + raise TypeError( + "Statevector can only be tensored with other Statevector instances. " + f"Received {type(other)} instead." + ) + + return Statevector( + np.kron(self.data, other.data).astype(np.complex128) + ) + def __str__(self) -> str: """ Return the string representation of the statevector vector. diff --git a/tests/primitives/test_statevector.py b/tests/primitives/test_statevector.py index 6c0611c..e2e8af0 100644 --- a/tests/primitives/test_statevector.py +++ b/tests/primitives/test_statevector.py @@ -20,6 +20,7 @@ from numpy.testing import assert_almost_equal import pytest +from quick.predicates import is_statevector from quick.primitives import Statevector @@ -103,6 +104,30 @@ def test_pad(self) -> None: statevector.pad() assert_almost_equal(statevector.data, np.array([1+0j, 0+0j, 0+0j, 0+0j])) + def test_to_quantumstate(self) -> None: + """ Test the conversion of the `quick.primitives.Statevector` object to a quantum state. + """ + statevector = Statevector(np.array([1, 2, 3, 4])) + assert is_statevector(statevector.data) + + def test_trace(self) -> None: + """ Test the trace of the `quick.primitives.Statevector` object. + """ + statevector = Statevector(np.array([1, 2, 3, 4, 5, 6])) + assert_almost_equal(statevector.trace(), 1) + + def test_partial_trace(self) -> None: + """ Test the partial trace of the `quick.primitives.Statevector` object. + """ + statevector = Statevector(np.array([1, 2, 3, 4, 5, 6, 7, 8])) + assert_almost_equal( + statevector.partial_trace([0, 2]), + np.array([ + [0.32352941+0.j, 0.46078431+0.j], + [0.46078431+0.j, 0.67647059+0.j] + ]) + ) + def test_change_indexing(self) -> None: """ Test the change of indexing of the `quick.primitives.Statevector` object. """ @@ -204,6 +229,27 @@ def test_rmul_scalar(self) -> None: statevector = Statevector(np.array([1, 0, 0, 0])) assert_almost_equal((2 * statevector).data, np.array([1+0j, 0+0j, 0+0j, 0+0j])) + def test_matmul(self) -> None: + """ Test the matrix multiplication of the `quick.primitives.Statevector` object. + """ + statevector = Statevector(np.array([1, 0, 0, 0])) + tensor_state = statevector @ statevector + assert tensor_state.num_qubits == 4 + checker_state = np.zeros(16, dtype=complex) + checker_state[0] = 1 + assert_almost_equal( + tensor_state.data, + checker_state + ) + + def test_matmul_fail(self) -> None: + """ Test the failure of the matrix multiplication of the `quick.primitives.Statevector` object. + """ + statevector = Statevector(np.array([1, 0, 0, 0])) + + with pytest.raises(TypeError): + statevector @ "invalid" # type: ignore + def test_str(self) -> None: """ Test the string representation of the `quick.primitives.Statevector` object. """ From f793ab2556b3172cd4fb2d16cc79d15bab9db35b Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sun, 10 Aug 2025 22:03:25 +0800 Subject: [PATCH 50/58] - Added `.reverse_bits()` method to `quick.primitives.Statevector` and `quick.primitives.Operator`. - Added `__matmul__` dunder to `quick.primitives.Operator`. - Updated `quick.primitives` testers. --- quick/primitives/operator.py | 47 +++++++++++++++++++- quick/primitives/statevector.py | 19 ++++++++- tests/primitives/test_operator.py | 64 ++++++++++++++++++++++++++-- tests/primitives/test_statevector.py | 19 ++++++++- 4 files changed, 139 insertions(+), 10 deletions(-) diff --git a/quick/primitives/operator.py b/quick/primitives/operator.py index 27fb103..be40bfe 100644 --- a/quick/primitives/operator.py +++ b/quick/primitives/operator.py @@ -33,8 +33,8 @@ class Operator: """ `quick.primitives.Operator` class is used to represent a quantum operator. - Quantum operators are hermitian matrices (square, unitary matrices) which represent - operations applied to quantum states (represented with qubits). + Quantum operators are unitary matrices which represent operations applied to + quantum states (represented with qubits). Parameters ---------- @@ -54,6 +54,9 @@ class Operator: The shape of the quantum operator data. `num_qubits` : int The number of qubits the quantum operator acts on. + `tensor_shape` : tuple[int, ...] + The shape of the quantum operator tensor based on + qubits as the physical dimension. Raises ------ @@ -86,6 +89,20 @@ def __init__( self.data = data self.shape = self.data.shape self.num_qubits = int(np.ceil(np.log2(self.shape[0]))) + self.tensor_shape = (2, 2) * self.num_qubits + + def reverse_bits(self) -> None: + """ Reverse the order of the qubits in the statevector. + This changes MSB to LSB, and vice versa. + """ + axes = tuple(range(self.num_qubits - 1, -1, -1)) + axes = axes + tuple(len(axes) + i for i in axes) + self.data = np.reshape( + np.transpose( + np.reshape(self.data, self.tensor_shape), axes + ), + self.shape + ) def _check__mul__( self, @@ -208,6 +225,32 @@ def __mul__( raise TypeError(f"Multiplication with {type(other)} is not supported.") + def __matmul__( + self, + other: Operator + ) -> Operator: + """ Calculate the tensor product of the two operators. + + Parameters + ---------- + `other` : quick.primitives.Operator + The operator to tensor with. + + Returns + ------- + quick.primitives.Operator + The tensor product of the two operators. + + Raises + ------ + TypeError + - If the `other` is not a `quick.primitives.Operator` instance. + """ + if not isinstance(other, Operator): + raise TypeError(f"Cannot tensor Operator with {type(other)}.") + + return Operator(np.kron(self.data, other.data)) + def __str__(self) -> str: """ Return the string representation of the operator. diff --git a/quick/primitives/statevector.py b/quick/primitives/statevector.py index 44e61e5..2d2e93c 100644 --- a/quick/primitives/statevector.py +++ b/quick/primitives/statevector.py @@ -56,6 +56,9 @@ class Statevector: Whether the statevector is normalized to 2-norm or not. `num_qubits` : int The number of qubits represented by the statevector. + `tensor_shape` : tuple[int, ...] + The shape of the statevector tensor based on qubits + as the physical dimension. Raises ------ @@ -84,6 +87,7 @@ def __init__( self.data = data.flatten().astype(np.complex128) self.norm_scale = np.linalg.norm(self.data) self.num_qubits = int(np.ceil(np.log2(self.data.size))) + self.tensor_shape = (2,) * self.num_qubits self.is_normalized() self.is_padded() self.to_quantumstate() @@ -328,6 +332,17 @@ def change_indexing( else: raise ValueError("Index type not supported.") + def reverse_bits(self) -> None: + """ Reverse the order of the qubits in the statevector. + This changes MSB to LSB, and vice versa. + """ + self.data = np.transpose( + np.reshape( + self.data, + self.tensor_shape + ) + ).ravel() + def trace(self) -> float: """ Calculate the trace of the statevector. @@ -377,13 +392,13 @@ def partial_trace( ) num_traced_qubits = len(trace_qubit_indices) - traced_shape = (2**(self.num_qubits - num_traced_qubits),) * 2 if num_traced_qubits == self.num_qubits: return self.trace() + traced_shape = (2**(self.num_qubits - num_traced_qubits),) * 2 trace_systems = [self.num_qubits - 1 - i for i in trace_qubit_indices] - state = self.data.reshape([2] * self.num_qubits) + state = self.data.reshape(self.tensor_shape) rho = np.tensordot(state, state.conj(), axes=(trace_systems, trace_systems)) rho = np.reshape(rho, traced_shape) diff --git a/tests/primitives/test_operator.py b/tests/primitives/test_operator.py index 287f1e1..c854259 100644 --- a/tests/primitives/test_operator.py +++ b/tests/primitives/test_operator.py @@ -17,7 +17,7 @@ __all__ = ["TestOperator"] import numpy as np -from numpy.testing import assert_allclose +from numpy.testing import assert_almost_equal import pytest from quick.primitives import Statevector, Operator @@ -36,7 +36,7 @@ def test_init(self) -> None: [0, 1] ]), label="A" ) - assert_allclose(operator.data, np.array([[1+0j, 0+0j], [0+0j, 1+0j]])) + assert_almost_equal(operator.data, np.array([[1+0j, 0+0j], [0+0j, 1+0j]])) assert operator.shape == (2, 2) assert operator.num_qubits == 1 assert operator.label == "A" @@ -53,6 +53,30 @@ def test_from_statevector_fail(self) -> None: with pytest.raises(ValueError): Operator(np.array([1, 0, 0, 0])) + def test_reverse_bits(self) -> None: + """ Test the MSB to LSB (vice versa) conversion of the `quick.primitives.Operator` object. + """ + cx_msb = np.array([ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 0, 1], + [0, 0, 1, 0] + ]) + cx_lsb = np.array([ + [1, 0, 0, 0], + [0, 0, 0, 1], + [0, 0, 1, 0], + [0, 1, 0, 0] + ]) + operator = Operator(cx_msb) + operator.reverse_bits() + checker_operator = Operator(cx_lsb) + assert_almost_equal(operator.data, checker_operator.data) + + operator.reverse_bits() + checker_operator = Operator(cx_msb) + assert_almost_equal(operator.data, checker_operator.data) + def test_check_mul(self) -> None: """ Test the multiplication of two `quick.primitives.Operator` objects. """ @@ -99,7 +123,7 @@ def test_mul_statevector(self) -> None: operator = Operator(np.array([[1, 0], [0, 1]])) state = Statevector(np.array([1, 0])) result = operator * state - assert_allclose(result.data, np.array([1, 0])) + assert_almost_equal(result.data, np.array([1, 0])) def test_mul_operator(self) -> None: """ Test the multiplication of two `quick.primitives.Operator` objects. @@ -107,7 +131,7 @@ def test_mul_operator(self) -> None: op1 = Operator(np.array([[1, 0], [0, 1]])) op2 = Operator(np.array([[0, 1], [1, 0]])) result = op1 * op2 - assert_allclose(result.data, np.array([[0, 1], [1, 0]])) + assert_almost_equal(result.data, np.array([[0, 1], [1, 0]])) def test_mul_fail(self) -> None: """ Test the failure of the multiplication of a `quick.primitives.Operator` with an incompatible type. @@ -122,6 +146,38 @@ def test_mul_fail(self) -> None: # Incompatible type operator * "not an operator or statevector" # type: ignore + def test_matmul(self) -> None: + """ Test the tensor product operation of the `quick.primitives.Operator` object. + """ + op1 = Operator(np.array([ + [1, 0], + [0, 1] + ])) + op2 = Operator(np.array([ + [0, 1], + [1, 0] + ])) + + op3 = op1 @ op2 + op3_checker = np.array([ + [0, 1, 0, 0], + [1, 0, 0, 0], + [0, 0, 0, 1], + [0, 0, 1, 0] + ]) + + assert_almost_equal(op3.data, op3_checker) + + op4 = op2 @ op1 + op4_checker = np.array([ + [0, 0, 1, 0], + [0, 0, 0, 1], + [1, 0, 0, 0], + [0, 1, 0, 0] + ]) + + assert_almost_equal(op4.data, op4_checker) + def test_str(self) -> None: """ Test the string representation of the `quick.primitives.Operator` object. """ diff --git a/tests/primitives/test_statevector.py b/tests/primitives/test_statevector.py index e2e8af0..6163bdb 100644 --- a/tests/primitives/test_statevector.py +++ b/tests/primitives/test_statevector.py @@ -33,6 +33,8 @@ def test_init(self) -> None: """ statevector = Statevector(np.array([1, 0, 0, 0])) assert_almost_equal(statevector.data, np.array([1+0j, 0+0j, 0+0j, 0+0j])) + assert statevector.label == "Ψ" + assert statevector.tensor_shape == (2, 2) def test_from_scalar_fail(self) -> None: """ Test the failure of defining a `quick.primitives.Statevector` object from a scalar. @@ -135,8 +137,9 @@ def test_change_indexing(self) -> None: statevector.change_indexing("snake") assert_almost_equal(statevector.data, np.array([1+0j, 0+0j, 0+0j, 0+0j])) - statevector = Statevector(np.array([1, 0, 0, 0, - 1, 0, 0, 0])) + statevector = Statevector( + np.array([1, 0, 0, 0, 1, 0, 0, 0]) + ) statevector.change_indexing("snake") assert_almost_equal(statevector.data, np.array([ (1+0j)/np.sqrt(2), 0+0j, 0+0j, 0+0j, @@ -150,6 +153,18 @@ def test_change_indexing_fail(self) -> None: with pytest.raises(ValueError): statevector.change_indexing("invalid") # type: ignore + def test_reverse_bits(self) -> None: + """ Test the MSB to LSB (vice versa) conversion of the `quick.primitives.Statevector` object. + """ + statevector = Statevector(np.array([1, 2, 3, 4])) + statevector.reverse_bits() + checker_statevector = Statevector(np.array([1, 3, 2, 4])) + assert_almost_equal(statevector.data, checker_statevector.data) + + statevector.reverse_bits() + checker_statevector = Statevector(np.array([1, 2, 3, 4])) + assert_almost_equal(statevector.data, checker_statevector.data) + def test_check_mul(self) -> None: """ Test the multiplication of the `quick.primitives.Statevector` object. """ From ad6a4e9cc1a77e3f32aea3ccba11ba3355540144 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 11 Aug 2025 19:38:44 +0800 Subject: [PATCH 51/58] - Fixed typo. --- quick/circuit/qiskitcircuit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quick/circuit/qiskitcircuit.py b/quick/circuit/qiskitcircuit.py index c091b37..1875a4f 100644 --- a/quick/circuit/qiskitcircuit.py +++ b/quick/circuit/qiskitcircuit.py @@ -216,7 +216,7 @@ def get_counts( # This is to counter https://github.com/Qiskit/qiskit/issues/13162 circuit.transpile() - # If no backend is provided, use the AerSimualtor + # If no backend is provided, use the AerSimulator base_backend: BackendSampler = BackendSampler(backend=AerSimulator()) result = base_backend.run([circuit.circuit], shots=num_shots).result() From 211f3d964a79657fa1a37984ca83d37af30c48fd Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Thu, 28 Aug 2025 20:35:14 +0800 Subject: [PATCH 52/58] - Added contraction logic to `quick.primitives` for Operator and Statevector. - Added testers for the recent additions to primitives. - Fixed a bug with `quick.synthesis.unitarypreparation.ShannonDecomposition` with the `QUBIT_KEYS` missing some keys causing IndexError. --- quick/primitives/contraction.py | 147 ++++++++++++++++++ quick/primitives/operator.py | 85 ++++++++++ quick/primitives/statevector.py | 94 ++++++++++- .../shannon_decomposition.py | 13 +- tests/primitives/test_operator.py | 54 +++++++ tests/primitives/test_statevector.py | 36 ++++- 6 files changed, 419 insertions(+), 10 deletions(-) create mode 100644 quick/primitives/contraction.py diff --git a/quick/primitives/contraction.py b/quick/primitives/contraction.py new file mode 100644 index 0000000..1988032 --- /dev/null +++ b/quick/primitives/contraction.py @@ -0,0 +1,147 @@ +# Copyright 2023-2025 Qualition Computing LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://github.com/Qualition/quick/blob/main/LICENSE +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" Contraction logic for `quick.primitives` classes. +""" + +from __future__ import annotations + +__all__ = ["contract"] + +import numpy as np +from numpy.typing import NDArray +from quick.primitives import Statevector, Operator + + +def _einsum_tensor_contract( + tensor: NDArray[np.complex128], + op: Operator, + contract_indices: list[int] + ) -> NDArray[np.complex128]: + """ Perform tensor contraction using Einstein's summation convention. + + Notes + ----- + The implementation is based on LSB convention. + + Parameters + ---------- + `tensor` : NDArray[np.complex128] + The tensor the operator is being contracted with. + `op` : quick.primitives.Operator + The operator to contract with the tensor. + `contract_indices` : list[int] + The indices to contract over. + + Returns + ------- + NDArray[np.complex128] + The result of the tensor contraction. + """ + rank = tensor.ndim + + # We tag the tensor indices that will be contracted with respect to + # the order in contract indices + tensor_indices = list(range(rank)) + for i, index in enumerate(contract_indices): + tensor_indices[index] = rank + i + + # Since the order is already taken into account in tensor indices, we + # only need to ensure the indices are present in a constant order + # regardless of different permutations + # The reason we reverse the order is because the gates themselves + # are in LSB convention as well + op_contract_indices = list( + range(rank + len(contract_indices) - 1, rank - 1, -1) + ) + + # The reason we reverse the order is because the gates themselves + # are in LSB convention as well + op_free_indices = list(reversed(contract_indices)) + + op_indices = op_free_indices + op_contract_indices + + # Reshape the passed operator `op` to be a tensor with 2M indices + # of dimension 2 to represent the qubits, resulting in a + # rank-2M tensor where M <= N and 2N is the rank of `tensor` + op_tensor = np.reshape(op.data, op.tensor_shape) + + return np.einsum(tensor, tensor_indices, op_tensor, op_indices) # type: ignore + +def contract( + tensor: Statevector | Operator, + op: NDArray[np.complex128] | Operator, + qubit_indices: list[int] + ) -> None: + """ Contract the operator with an operator on the specified + qubits in place using Einstein's Summation convention. + + Parameters + ---------- + `tensor` : quick.primitives.Statevector | quick.primitives.Operator + The tensor to apply `op` to. + `op` : NDArray[np.complex128] | quick.primitives.Operator + The operator or matrix to contract with. + `qubit_indices` : list[int] + The qubit indices to contract over. + + Raises + ------ + ValueError + ValueError + - If the operator is not unitary. + - If the number of indices is less than the number of qubits for `op`. + - If the number of qubit indices exceeds the number of qubits in `tensor`. + - If any of the qubit indices are out of range of `self`. + + Usage + ----- + >>> tensor.contract(op, [0, 1]) + """ + op_tensor = Operator(np.array(op)) + + num_indices = len(qubit_indices) + + if op_tensor.num_qubits != num_indices: + raise ValueError( + f"Operator requires {op_tensor.num_qubits} qubits. ", + f"Received {num_indices} instead." + ) + + if num_indices > tensor.num_qubits: + raise ValueError( + f"{type(tensor).__name__} supports operators with at most {tensor.num_qubits} qubits." + f"Received an operator with {num_indices} instead." + ) + + if any(i >= tensor.num_qubits for i in qubit_indices): + raise ValueError( + f"Invalid qubit index in {qubit_indices}. " + f"Valid indices are in range(0, {tensor.num_qubits})." + ) + + # Modify the indices to be MSB for correct alignment given how numpy does broadcasting + op_contract_indices = [tensor.num_qubits - 1 - i for i in qubit_indices] + + # Reshape the current operator to be a tensor with X indices + # of dimension 2 to represent the qubits, resulting in a + # rank-X tensor + # For statevectors X is the number of qubits N whereas for operators + # X is 2N + reshaped_tensor = np.reshape(tensor.data, tensor.tensor_shape) + + tensor.data = np.reshape( + _einsum_tensor_contract(reshaped_tensor, op_tensor, op_contract_indices), + tensor.shape + ) \ No newline at end of file diff --git a/quick/primitives/operator.py b/quick/primitives/operator.py index be40bfe..bfa6714 100644 --- a/quick/primitives/operator.py +++ b/quick/primitives/operator.py @@ -91,6 +91,36 @@ def __init__( self.num_qubits = int(np.ceil(np.log2(self.shape[0]))) self.tensor_shape = (2, 2) * self.num_qubits + def conj(self) -> Operator: + """ Take the conjugate of the operator. + + Returns + ------- + quick.primitives.Operator + The conjugate of the operator. + """ + return Operator(np.conjugate(self.data), label=self.label) + + def T(self) -> Operator: + """ Take the transpose of the operator. + + Returns + ------- + quick.primitives.Operator + The transpose of the operator. + """ + return Operator(np.transpose(self.data), label=self.label) + + def adjoint(self) -> Operator: + """ Take the adjoint of the operator. + + Returns + ------- + quick.primitives.Operator + The adjoint of the operator. + """ + return self.conj().T() + def reverse_bits(self) -> None: """ Reverse the order of the qubits in the statevector. This changes MSB to LSB, and vice versa. @@ -104,6 +134,52 @@ def reverse_bits(self) -> None: self.shape ) + def contract( + self, + op: NDArray[np.complex128] | Operator, + qubit_indices: list[int] + ) -> None: + """ Contract the operator with an operator on the specified + qubits in place using Einstein's Summation convention. + + Notes + ----- + This implementation should be used for small systems, + as it requires significant memory and thus may not be + suitable for larger systems. + + For larger systems, consider using the more efficient + `quick.backend.QuimbBackend` which leverages optimal + tensor network contraction for simulating the circuit. + + Alternatively, consider using GPU-based simulators + present in `quick.backend` which can be faster at + scale. + + Parameters + ---------- + `op` : NDArray[np.complex128] | Operator + The operator or matrix to contract with. + `qubit_indices` : list[int] + The qubit indices to contract over. + + Raises + ------ + ValueError + ValueError + - If the operator is not unitary. + - If the number of indices is less than the number of qubits for `op`. + - If the number of qubit indices exceeds the number of qubits in `self`. + - If any of the qubit indices are out of range of `self`. + + Usage + ----- + >>> op1.contract(op2, [0, 1]) + """ + from quick.primitives.contraction import contract + + contract(self, op, qubit_indices) + def _check__mul__( self, other: Any @@ -132,6 +208,15 @@ def _check__mul__( else: raise TypeError(f"Multiplication with {type(other)} is not supported.") + def __array__(self) -> NDArray[np.complex128]: + """ Convert the `quick.primitives.Operator` to a NumPy array. + + Returns + ------- + NDArray[np.complex128] + """ + return np.array(self.data).astype(np.complex128) + def __eq__( self, other: Any diff --git a/quick/primitives/statevector.py b/quick/primitives/statevector.py index 2d2e93c..d35d91d 100644 --- a/quick/primitives/statevector.py +++ b/quick/primitives/statevector.py @@ -21,7 +21,7 @@ import numpy as np from numpy.typing import NDArray -from typing import Any, Literal, SupportsFloat, TypeAlias +from typing import Any, Literal, Self, SupportsFloat, TypeAlias import quick.primitives.operator as operator @@ -54,6 +54,8 @@ class Statevector: The normalization scale. `normalized` : bool Whether the statevector is normalized to 2-norm or not. + `shape` : tuple[int,] + The shape of the quantum statevector data. `num_qubits` : int The number of qubits represented by the statevector. `tensor_shape` : tuple[int, ...] @@ -87,11 +89,47 @@ def __init__( self.data = data.flatten().astype(np.complex128) self.norm_scale = np.linalg.norm(self.data) self.num_qubits = int(np.ceil(np.log2(self.data.size))) + self.shape = (2 ** self.num_qubits,) self.tensor_shape = (2,) * self.num_qubits self.is_normalized() self.is_padded() self.to_quantumstate() + @classmethod + def from_int( + cls, + value: int, + num_qubits: int + ) -> Self: + """ Create a statevector from the basis state + representation of an integer. + + Parameters + ---------- + `value` : int + The integer value to convert. + `num_qubits` : int + The number of qubits to use. + + Returns + ------- + quick.primitives.Statevector + The resulting statevector. + """ + statevector = np.zeros(2 ** num_qubits, dtype=np.complex128) + statevector[value] = 1 + return cls(statevector) + + def conj(self) -> Statevector: + """ Take the conjugate of the statevector. + + Returns + ------- + quick.primitives.Statevector + The conjugate of the statevector. + """ + return Statevector(np.conjugate(self.data), label=self.label) + @staticmethod def validate_data(data: NDArray[np.complex128]) -> None: """ Validate the data to ensure it is a valid statevector. @@ -404,6 +442,51 @@ def partial_trace( return rho + def contract( + self, + op: NDArray[np.complex128] | operator.Operator, + qubit_indices: list[int] + ) -> None: + """ Contract the statevector with an operator on the specified + qubits in place using Einstein's Summation convention. + + Notes + ----- + This implementation should be used for small systems, + as it requires significant memory and thus may not be + suitable for larger systems. + + For larger systems, consider using the more efficient + `quick.backend.QuimbBackend` which leverages optimal + tensor network contraction for simulating the circuit. + + Alternatively, consider using GPU-based simulators + present in `quick.backend` which can be faster at + scale. + + Parameters + ---------- + `op` : NDArray[np.complex128] | quick.primitives.Operator + The operator to contract with. + `qubit_indices` : list[int] + The indices of the qubits to contract over. + + Raises + ------ + ValueError + - If the operator is not unitary. + - If the number of indices is less than the number of qubits for `op`. + - If the number of qubit indices exceeds the number of qubits in `self`. + - If any of the qubit indices are out of range of `self`. + + Usage + ----- + >>> statevector.contract(op, [0, 1]) + """ + from quick.primitives.contraction import contract + + contract(self, op, qubit_indices) + def _check__mul__( self, other: Any @@ -431,6 +514,15 @@ def _check__mul__( else: raise TypeError(f"Multiplication with {type(other)} is not supported.") + def __array__(self) -> NDArray[np.complex128]: + """ Convert the `quick.primitives.Statevector` to a NumPy array. + + Returns + ------- + NDArray[np.complex128] + """ + return np.array(self.data).astype(np.complex128) + def __eq__( self, other: object diff --git a/quick/synthesis/unitarypreparation/shannon_decomposition.py b/quick/synthesis/unitarypreparation/shannon_decomposition.py index e5fccbb..ca5a07d 100644 --- a/quick/synthesis/unitarypreparation/shannon_decomposition.py +++ b/quick/synthesis/unitarypreparation/shannon_decomposition.py @@ -34,11 +34,6 @@ from quick.primitives import Operator from quick.synthesis.unitarypreparation import UnitaryPreparation -# Constants -QUBIT_KEYS = frozenset([ - "qubit_index", "control_index", "target_index" -]) - class ShannonDecomposition(UnitaryPreparation): """ `quick.ShannonDecomposition` is the class for preparing quantum operators @@ -396,6 +391,8 @@ def a2_optimization( `a2_qsd_blocks` : list[list[int]] List of blocks to apply A.2 optimization to. """ + from quick.circuit.circuit import ALL_QUBIT_KEYS as QUBIT_KEYS + # If there are no blocks, or only one block which means # no neighbors to merge diagonal into, then return if len(a2_qsd_blocks) < 2: @@ -428,11 +425,11 @@ def a2_optimization( if block_index == 0: for operation in circuit_1.circuit_log: for key in set(operation.keys()).intersection(QUBIT_KEYS): - operation[key] = 0 if operation[key] == qubit_indices[0] else 1 + operation[key] = 0 if operation[key] == qubit_indices[0] else 1 # type: ignore for operation in circuit_2.circuit_log: for key in set(operation.keys()).intersection(QUBIT_KEYS): - operation[key] = 0 if operation[key] == qubit_indices[0] else 1 + operation[key] = 0 if operation[key] == qubit_indices[0] else 1 # type: ignore circuit_1.update() circuit_2.update() @@ -456,7 +453,7 @@ def a2_optimization( for block in qsd_blocks: # type: ignore for operation in block: # type: ignore for key in set(operation.keys()).intersection(QUBIT_KEYS): - operation[key] = qubit_indices[0] if operation[key] == 0 else qubit_indices[1] + operation[key] = qubit_indices[0] if operation[key] == 0 else qubit_indices[1] # type: ignore # Reconstruct the circuit with the modified blocks in alternating order circuit.reset() diff --git a/tests/primitives/test_operator.py b/tests/primitives/test_operator.py index c854259..1b3df40 100644 --- a/tests/primitives/test_operator.py +++ b/tests/primitives/test_operator.py @@ -19,6 +19,7 @@ import numpy as np from numpy.testing import assert_almost_equal import pytest +from scipy.stats import unitary_group from quick.primitives import Statevector, Operator @@ -41,6 +42,30 @@ def test_init(self) -> None: assert operator.num_qubits == 1 assert operator.label == "A" + def test_conj(self) -> None: + """ Test the conjugate of the `quick.primitives.Operator` class. + """ + unitary = np.array(unitary_group.rvs(8)).astype(complex) + operator = Operator(unitary) + conjugate_operator = operator.conj() + assert_almost_equal(conjugate_operator.data, unitary.conj()) + + def test_T(self) -> None: + """ Test the transpose of `quick.primitives.Operator` class. + """ + unitary = np.array(unitary_group.rvs(8)).astype(complex) + operator = Operator(unitary) + transpose_operator = operator.T() + assert_almost_equal(transpose_operator.data, unitary.T) + + def test_adjoint(self) -> None: + """ Test the adjoint of the `quick.primitives.Operator` class. + """ + unitary = np.array(unitary_group.rvs(8)).astype(complex) + operator = Operator(unitary) + adjoint_operator = operator.adjoint() + assert_almost_equal(adjoint_operator.data, unitary.conj().T) + def test_from_scalar_fail(self) -> None: """ Test the failure of defining a `quick.primitives.Operator` object from a scalar. """ @@ -77,6 +102,35 @@ def test_reverse_bits(self) -> None: checker_operator = Operator(cx_msb) assert_almost_equal(operator.data, checker_operator.data) + def test_contract(self) -> None: + """ Test the application of operators to `quick.primitives.Operator` objects. + """ + from quick.circuit import QiskitCircuit + + uni1 = np.array(unitary_group.rvs(2 ** 5)).astype(complex) + uni2 = np.array(unitary_group.rvs(2 ** 3)).astype(complex) + uni3 = np.array(unitary_group.rvs(2 ** 2)).astype(complex) + + op1 = Operator(uni1) + op2 = Operator(uni2) + op3 = Operator(uni3) + + op1.contract(op2, [3, 0, 1]) + op1.contract(op3, [2, 4]) + + checker_circuit = QiskitCircuit(5) + checker_circuit.unitary(uni1, [0, 1, 2, 3, 4]) + checker_circuit.unitary(uni2, [3, 0, 1]) + checker_circuit.unitary(uni3, [2, 4]) + + assert_almost_equal(checker_circuit.get_unitary(), op1.data) + + def test_array(self) -> None: + """ Test the conversion of the `quick.primitives.Operator` to a NumPy array. + """ + operator = Operator(np.array([[1, 0], [0, 1]])) + assert_almost_equal(np.array(operator), np.array([[1, 0], [0, 1]])) + def test_check_mul(self) -> None: """ Test the multiplication of two `quick.primitives.Operator` objects. """ diff --git a/tests/primitives/test_statevector.py b/tests/primitives/test_statevector.py index 6163bdb..ffae28e 100644 --- a/tests/primitives/test_statevector.py +++ b/tests/primitives/test_statevector.py @@ -19,9 +19,10 @@ import numpy as np from numpy.testing import assert_almost_equal import pytest +from scipy.stats import unitary_group from quick.predicates import is_statevector -from quick.primitives import Statevector +from quick.primitives import Statevector, Operator class TestStatevector: @@ -165,6 +166,39 @@ def test_reverse_bits(self) -> None: checker_statevector = Statevector(np.array([1, 2, 3, 4])) assert_almost_equal(statevector.data, checker_statevector.data) + def test_contract(self) -> None: + """ Test the application of operators to `quick.primitives.Operator` objects. + """ + from quick.circuit import QiskitCircuit + + state = np.zeros(2 ** 5) + state[0] = 1 + statevector = Statevector(state.astype(complex)) + uni1 = np.array(unitary_group.rvs(2 ** 5)).astype(complex) + uni2 = np.array(unitary_group.rvs(2 ** 3)).astype(complex) + uni3 = np.array(unitary_group.rvs(2 ** 2)).astype(complex) + + op1 = Operator(uni1) + op2 = Operator(uni2) + op3 = Operator(uni3) + + op1.contract(op2, [3, 0, 1]) + op1.contract(op3, [2, 4]) + statevector.contract(op1, [0, 1, 2, 3, 4]) + + checker_circuit = QiskitCircuit(5) + checker_circuit.unitary(uni1, [0, 1, 2, 3, 4]) + checker_circuit.unitary(uni2, [3, 0, 1]) + checker_circuit.unitary(uni3, [2, 4]) + + assert_almost_equal(checker_circuit.get_statevector(), statevector.data) + + def test_array(self) -> None: + """ Test the conversion of the `quick.primitives.Statevector` to a NumPy array. + """ + statevector = Statevector(np.array([1, 0, 0, 0])) + assert_almost_equal(np.array(statevector), np.array([1, 0, 0, 0])) + def test_check_mul(self) -> None: """ Test the multiplication of the `quick.primitives.Statevector` object. """ From fb7998baf6bc897bf703944dc447a8916e379557 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Thu, 28 Aug 2025 21:59:35 +0800 Subject: [PATCH 53/58] - Bumped pennylane to 0.42.3 to avoid #8112 error on pennylane repo. - Modified `get_counts` for `quick.circuit.PennylaneCircuit` to account for the version difference functionality. --- pyproject.toml | 2 +- quick/circuit/pennylanecircuit.py | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9a75de2..dd61aa5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ dependencies = [ "cirq-core == 1.4.1", "genQC == 0.1.0", "numpy >= 1.23", - "pennylane == 0.39.0", + "pennylane == 0.42.3", "pytket == 2.4.1", "pytket-qiskit == 0.68.0", "pytket-cirq == 0.40.0", diff --git a/quick/circuit/pennylanecircuit.py b/quick/circuit/pennylanecircuit.py index c5f79cf..b1f6124 100644 --- a/quick/circuit/pennylanecircuit.py +++ b/quick/circuit/pennylanecircuit.py @@ -158,8 +158,7 @@ def _gate_mapping( self.circuit.append( qml.ControlledQubitUnitary( gate_operation, - control_wires=controls, - wires=target_index + wires=controls + [target_index] ) ) return @@ -234,6 +233,8 @@ def get_counts( np.random.seed(0) + num_qubits_to_measure = len(self.measured_qubits) + if len(self.measured_qubits) == 0: raise ValueError("At least one qubit must be measured.") @@ -245,7 +246,7 @@ def compile_circuit() -> qml.measurements.CountsMp: qml.apply(op) - return qml.counts(wires=self.measured_qubits, all_outcomes=True) + return qml.counts(wires=self.measured_qubits) if backend is None: device = qml.device(self.device.name, wires=self.num_qubits, shots=num_shots) @@ -254,6 +255,13 @@ def compile_circuit() -> qml.measurements.CountsMp: list(result.keys())[i]: int(list(result.values())[i]) for i in range(len(result)) } + for i in range(2**num_qubits_to_measure): + basis = format(int(i),"0{}b".format(num_qubits_to_measure)) + if basis not in counts: + counts[basis] = 0 + else: + counts[basis] = int(counts[basis]) + # Sort the counts by their keys (basis states) # This is simply for readability counts = dict(sorted(counts.items())) From 355900fe3178b0b12c4e9925646acb2a0a517278 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Thu, 28 Aug 2025 22:55:26 +0800 Subject: [PATCH 54/58] - Replaced `Self` with `Statevector` given `Self` is not available in 3.10. --- quick/primitives/statevector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quick/primitives/statevector.py b/quick/primitives/statevector.py index d35d91d..f21f687 100644 --- a/quick/primitives/statevector.py +++ b/quick/primitives/statevector.py @@ -21,7 +21,7 @@ import numpy as np from numpy.typing import NDArray -from typing import Any, Literal, Self, SupportsFloat, TypeAlias +from typing import Any, Literal, SupportsFloat, TypeAlias import quick.primitives.operator as operator @@ -100,7 +100,7 @@ def from_int( cls, value: int, num_qubits: int - ) -> Self: + ) -> Statevector: """ Create a statevector from the basis state representation of an integer. From f699e36d32bfd78b8eeb24ca4cddc8086b19ec1b Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Wed, 17 Sep 2025 19:55:57 +0800 Subject: [PATCH 55/58] - Added `.decompose_gate()` to `quick.circuit.Circuit`. - Updated `.decompose()` of `quick.circuit.Circuit` for better clarity and performance. - Fixed a bug with `.control()` in `quick.circuit.Circuit` where gates that do not have C, MC- methods available now get decomposed before being converted to controlled format. - Deprecated `quick.circuit.gate_matrix.Gate`, which will be brought back properly in quick's rewrite. - Added `.control()` to `quick.primitives.Operator` for calculating the controlled unitary of a unitary. - Brought `Scalar` back to `__mul__` for `quick.primitives.Operator` and `quick.primitives.Statevector` to act as global phase. - Added testers for the additions. --- quick/circuit/circuit.py | 177 +++++---- quick/circuit/gate_matrix/__init__.py | 2 - .../gate_matrix/controlled_qubit_gates.py | 355 +++++++++++++++++- quick/circuit/gate_matrix/gate.py | 161 -------- .../circuit/gate_matrix/single_qubit_gates.py | 341 ++++++++--------- quick/predicates/__init__.py | 4 + quick/predicates/predicates.py | 35 +- quick/primitives/contraction.py | 2 +- quick/primitives/operator.py | 74 +++- quick/primitives/statevector.py | 46 +-- .../mcsu2_real_diagonal.py | 6 +- .../two_qubit_decomposition.py | 17 +- tests/circuit/test_circuit_decompose.py | 84 +++++ tests/circuit/test_circuit_to_controlled.py | 33 +- tests/circuit/test_cirq_circuit.py | 64 ++-- tests/circuit/test_control_state.py | 16 +- tests/circuit/test_pennylane_circuit.py | 64 ++-- tests/circuit/test_qiskit_circuit.py | 64 ++-- tests/circuit/test_quimb_circuit.py | 64 ++-- tests/circuit/test_tket_circuit.py | 64 ++-- .../test_uniformly_controlled_gates.py | 88 ++--- tests/predicates/test_predicates.py | 36 +- tests/primitives/test_operator.py | 14 + tests/primitives/test_statevector.py | 12 - 24 files changed, 1114 insertions(+), 709 deletions(-) delete mode 100644 quick/circuit/gate_matrix/gate.py diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index 085463a..5b65c57 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -29,7 +29,7 @@ from numpy.typing import NDArray from types import NotImplementedType from typing import ( - Any, Literal, overload, SupportsFloat, SupportsIndex, TYPE_CHECKING + Any, Literal, overload, SupportsFloat, SupportsIndex, TypeAlias, TYPE_CHECKING ) import qiskit # type: ignore @@ -58,6 +58,8 @@ EPSILON = 1e-15 +CIRCUIT_LOG: TypeAlias = list[dict[str, Any]] + """ Set the frozensets for the keys to be used: - Decorator `Circuit.gatemethod()` - Method `Circuit.vertical_reverse()` @@ -87,7 +89,11 @@ } # List of 1Q gates wrapped by individual frameworks -GATES = Literal["I", "X", "Y", "Z", "H", "S", "Sdg", "T", "Tdg", "RX", "RY", "RZ", "Phase", "U3"] +GATES = Literal[ + "I", "X", "Y", "Z", "H", "S", "Sdg", + "T", "Tdg", "RX", "RY", "RZ", "Phase", + "U3" +] # List of self-adjoint gates SELF_ADJ_GATES = frozenset([ @@ -100,6 +106,20 @@ # these gates cannot be decomposed further PRIMITIVE_GATES = frozenset(["U3", "CX", "GlobalPhase", "measure"]) +# List of gates that are compatible/convertible with `.control()` +CONTROLLABLE_GATES = frozenset([ + "I", "X", "Y", "Z", "H", "S", "Sdg", "T", "Tdg", + "RX", "RY", "RZ", "Phase", "XPow", "YPow", "ZPow", + "RXX", "RYY", "RZZ", "U3", "SWAP" +]) + +# Controlled versions of controllable gates +CONTROLLED_GATES = frozenset([ + *CONTROLLABLE_GATES, + *(f"C{gate}" for gate in CONTROLLABLE_GATES), + *(f"MC{gate}" for gate in CONTROLLABLE_GATES) +]) + # Constants PI = np.pi PI_DOUBLE = 2 * PI @@ -443,10 +463,10 @@ def decompose_last( Usage ----- - >>> def NewGate(qubit_indices: int | Sequence[int]): + >>> def NewGate(self, qubit_indices: int | Sequence[int]): >>> gate = self.process_gate_params(gate="NewGate", params=locals()) - >>> with circuit.decompose_last(): - >>> circuit.X(qubit_indices) + >>> with self.decompose_last(): + >>> self.X(qubit_indices) """ # If the gate is parameterized, and its rotation is effectively zero, return # as no operation is needed @@ -4634,7 +4654,6 @@ def _Diagonal( if isinstance(qubit_indices, int): qubit_indices = [qubit_indices] - # Check if the number of diagonal entries is a power of 2 num_qubits = np.log2(len(diagnoal)) if num_qubits < 1 or not int(num_qubits) == num_qubits: @@ -4801,7 +4820,6 @@ def Multiplexor( if not gate.shape == (2, 2): raise ValueError(f"The dimension of a gate is not equal to 2x2. Received {gate.shape}.") - # Check if number of gates in gate_list is a positive power of two num_controls = int( np.log2( len(single_qubit_gates) @@ -5021,10 +5039,8 @@ def initialize( ----- >>> circuit.initialize([1, 0], qubit_indices=0) """ - # Initialize the state preparation schema isometry = Isometry(output_framework=type(self)) - # Prepare the state self = isometry.apply_state( circuit=self, state=state, @@ -5067,10 +5083,8 @@ def unitary( ... [0, 1, 0, 0], ... [1, 0, 0, 0]], qubit_indices=[0, 1]) """ - # Initialize the unitary preparation schema unitary_preparer = ShannonDecomposition(output_framework=type(self)) - # Prepare the unitary matrix self = unitary_preparer.apply_unitary( circuit=self, unitary=unitary_matrix, @@ -5088,29 +5102,26 @@ def vertical_reverse(self) -> None: @staticmethod def _horizontal_reverse( - circuit_log: list[dict[str, Any]], + circuit_log: CIRCUIT_LOG, adjoint: bool = True - ) -> list[dict[str, Any]]: + ) -> CIRCUIT_LOG: """ Perform a horizontal reverse operation. Parameters ---------- - `circuit_log` : list[dict[str, Any]] + `circuit_log` : CIRCUIT_LOG The circuit log to reverse. `adjoint` : bool, optional, default=True Whether or not to apply the adjoint of the circuit. Returns ------- - list[dict[str, Any]] + CIRCUIT_LOG The reversed circuit log. """ - # Reverse the order of the operations circuit_log = circuit_log[::-1] - # If adjoint is True, then multiply the angles by -1 if adjoint: - # Iterate over every operation, and change the index accordingly for i, operation in enumerate(circuit_log): if "angle" in operation: operation["angle"] = -operation["angle"] @@ -5212,18 +5223,12 @@ def add( else: operation[key] = list(qubit_indices)[operation[key]] # type: ignore - # Iterate over the gate log and apply corresponding gates in the new framework for gate_info in circuit_log: - # Extract gate name and remove it from gate_info for kwargs gate_name = gate_info.pop("gate", None) - - # Extract gate definition and remove it from gate_info for kwargs gate_definition = gate_info.pop("definition", None) - # Use the gate mapping to apply the corresponding gate with remaining kwargs getattr(self, gate_name)(**gate_info) - # Re-insert gate name and definition into gate_info if needed elsewhere gate_info["gate"] = gate_name gate_info["definition"] = gate_definition @@ -5610,6 +5615,59 @@ def remove_measurements( return self._remove_measurements() + def decompose_gate( + self, + target_gates: str | Sequence[str] + ) -> Circuit: + """ Decompose the specified gates in the circuit. + + Parameters + ---------- + `gates` : str | Sequence[str] + The gates to decompose. + + Returns + ------- + `circuit` : quick.circuit.Circuit + The circuit with the decomposed gates. + + Usage + ----- + >>> new_circuit = circuit.decompose_gate("CX") + >>> new_circuit = circuit.decompose_gate(["Z", "Phase"]) + """ + circuit = type(self)(self.num_qubits) + + # We cannot decompose primitive gates and to avoid losing them + # or getting stuck in an infinite loop we simply remove them + # from the target gates + target_gates_set = frozenset(set(target_gates) - PRIMITIVE_GATES) + + circuit_log_copy = copy.deepcopy(self.circuit_log) + + while True: + gates = set([operation["gate"] for operation in circuit_log_copy]) + + if gates.isdisjoint(target_gates_set): + break + + if gates.issubset(PRIMITIVE_GATES): + break + + for operation in circuit_log_copy: + if operation["gate"] in target_gates_set: + circuit.circuit_log.extend(operation["definition"]) + else: + circuit.circuit_log.append(operation) + + circuit_log_copy = circuit.circuit_log + circuit.circuit_log = [] + + circuit.circuit_log = circuit_log_copy + circuit.update() + + return circuit + def decompose( self, reps: int = 1, @@ -5638,46 +5696,39 @@ def decompose( if all([operation["definition"] == [] for operation in self.circuit_log]): return self.copy() - # Create a new circuit to store the decomposed gates circuit = type(self)(self.num_qubits) # Create a copy of the circuit log to use as placeholder for each layer of decomposition circuit_log_copy = copy.deepcopy(self.circuit_log) # Iterate over the circuit log, and use the `definition` key to define the decomposition - # Continue until the circuit log is fully decomposed + # Continue until the circuit log is fully decomposed or until the number of reps is reached if full: - while True: - gates = set([operation["gate"] for operation in circuit_log_copy]) + reps = -1 - if gates.issubset(PRIMITIVE_GATES): - break + current_rep = 0 - for operation in circuit_log_copy: - if operation["definition"] != []: - for op in operation["definition"]: - circuit.circuit_log.append(op) - else: - circuit.circuit_log.append(operation) + while True: + if current_rep == reps: + break - circuit_log_copy = circuit.circuit_log - circuit.circuit_log = [] + gates = set([operation["gate"] for operation in circuit_log_copy]) - # Iterate over the circuit log, and use the `definition` key to define the decomposition - # Each rep will decompose the circuit one layer further - else: - for _ in range(reps): - for operation in circuit_log_copy: - if operation["definition"] != []: - for op in operation["definition"]: - circuit.circuit_log.append(op) - else: - circuit.circuit_log.append(operation) - circuit_log_copy = circuit.circuit_log - circuit.circuit_log = [] + if gates.issubset(PRIMITIVE_GATES): + break - circuit.circuit_log = circuit_log_copy + for operation in circuit_log_copy: + if operation["definition"] != []: + circuit.circuit_log.extend(operation["definition"]) + else: + circuit.circuit_log.append(operation) + + circuit_log_copy = circuit.circuit_log + circuit.circuit_log = [] + current_rep += 1 + + circuit.circuit_log = circuit_log_copy circuit.update() return circuit @@ -5830,21 +5881,14 @@ def convert( if not issubclass(circuit_framework, Circuit): raise TypeError("The circuit framework must be a subclass of `quick.circuit.Circuit`.") - # Define the new circuit using the provided framework converted_circuit = circuit_framework(self.num_qubits) - # Iterate over the gate log and apply corresponding gates in the new framework for gate_info in self.circuit_log: - # Extract gate name and remove it from gate_info for kwargs gate_name = gate_info.pop("gate") - - # Extract gate definition and remove it from gate_info for kwargs gate_definition = gate_info.pop("definition", None) - # Use the gate mapping to apply the corresponding gate with remaining kwargs getattr(converted_circuit, gate_name)(**gate_info) - # Re-insert gate name and definition into gate_info if needed elsewhere gate_info["gate"] = gate_name gate_info["definition"] = gate_definition @@ -5873,23 +5917,22 @@ def control( `controlled_circuit` : quick.circuit.Circuit The circuit as a controlled gate. """ - # Create a copy of the circuit - circuit = self.copy() + # To provide compatibility with gates that are not directly + # controllable we decompose such gates, which adds support + # for user-defined gates + gates = set([operation["gate"] for operation in self.circuit_log]) + + circuit = self.decompose_gate(gates - CONTROLLED_GATES) # type: ignore # When a target gate has global phase, we need to account for that by resetting # the global phase, and then applying it to the control indices using the Phase # or MCPhase gates depending on the number of control indices circuit.circuit_log = [op for op in circuit.circuit_log if op["gate"] != "GlobalPhase"] - # Define a controlled circuit controlled_circuit = type(circuit)(num_qubits=circuit.num_qubits + num_controls) - # Iterate over the gate log and apply corresponding gates in the new framework for gate_info in circuit.circuit_log: - # Extract gate name and remove it from gate_info for kwargs gate_name = gate_info.pop("gate") - - # Extract gate definition and remove it from gate_info for kwargs gate_definition = gate_info.pop("definition", None) # Change the gate name from single qubit and controlled to multi-controlled @@ -5911,15 +5954,11 @@ def control( if isinstance(control_indices, int): control_indices = [control_indices] - # Add control indices gate_info["control_indices"] = list(range(num_controls)) + \ [idx for idx in control_indices if idx not in range(num_controls)] - # Use the gate mapping to apply the corresponding gate with remaining kwargs - # Add the control indices as the first indices given the number of control qubits getattr(controlled_circuit, gate_name)(**gate_info) - # Re-insert gate name and definition into gate_info if needed elsewhere gate_info["gate"] = gate_name gate_info["definition"] = gate_definition diff --git a/quick/circuit/gate_matrix/__init__.py b/quick/circuit/gate_matrix/__init__.py index 2ca9d05..ef8df5a 100644 --- a/quick/circuit/gate_matrix/__init__.py +++ b/quick/circuit/gate_matrix/__init__.py @@ -13,7 +13,6 @@ # limitations under the License. __all__ = [ - "Gate", "PauliX", "PauliY", "PauliZ", @@ -33,7 +32,6 @@ "CT" ] -from quick.circuit.gate_matrix.gate import Gate from quick.circuit.gate_matrix.single_qubit_gates import ( PauliX, PauliY, PauliZ, Hadamard, S, T, RX, RY, RZ, U3, Phase ) diff --git a/quick/circuit/gate_matrix/controlled_qubit_gates.py b/quick/circuit/gate_matrix/controlled_qubit_gates.py index a658059..4abaa6a 100644 --- a/quick/circuit/gate_matrix/controlled_qubit_gates.py +++ b/quick/circuit/gate_matrix/controlled_qubit_gates.py @@ -23,18 +23,355 @@ "CZ", "CH", "CS", - "CT" + "CSdg", + "CT", + "CTdg", + "CRX", + "CRY", + "CRZ", + "CPhase", + "CU3", + "MCX", + "MCY", + "MCZ", + "MCH", + "MCS", + "MCSdg", + "MCT", + "MCTdg", + "MCRX", + "MCRY", + "MCRZ", + "MCPhase", + "MCU3" ] -from quick.circuit.gate_matrix import Gate from quick.circuit.gate_matrix.single_qubit_gates import ( - PauliX, PauliY, PauliZ, Hadamard, S, T + PauliX, PauliY, PauliZ, Hadamard, S, Sdg, T, Tdg, + RX, RY, RZ, Phase, U3 ) +from quick.primitives import Operator -CX: Gate = PauliX().control(1) -CY: Gate = PauliY().control(1) -CZ: Gate = PauliZ().control(1) -CH: Gate = Hadamard().control(1) -CS: Gate = S().control(1) -CT: Gate = T().control(1) \ No newline at end of file +CX = PauliX.control(1) +CY = PauliY.control(1) +CZ = PauliZ.control(1) +CH = Hadamard.control(1) +CS = S.control(1) +CSdg = Sdg.control(1) +CT = T.control(1) +CTdg = Tdg.control(1) + +def CRX(theta: float) -> Operator: + """ Generate the controlled RX rotation gate given angle parameter + theta. + + Parameters + ---------- + `theta` : float + The rotation angle in radians. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the controlled RX rotation gate. + """ + return RX(theta).control(1) + +def CRY(theta: float) -> Operator: + """ Generate the controlled RY rotation gate given angle parameter + theta. + + Parameters + ---------- + `theta` : float + The rotation angle in radians. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the controlled RY rotation gate. + """ + return RY(theta).control(1) + +def CRZ(theta: float) -> Operator: + """ Generate the controlled RZ rotation gate given angle parameter + theta. + + Parameters + ---------- + `theta` : float + The rotation angle in radians. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the controlled RZ rotation gate. + """ + return RZ(theta).control(1) + +def CPhase(theta: float) -> Operator: + """ Generate the controlled Phase gate given angle parameter + theta. + + Parameters + ---------- + `theta` : float + The phase angle in radians. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the controlled Phase gate. + """ + return Phase(theta).control(1) + +def CU3( + theta: float, + phi: float, + lam: float + ) -> Operator: + """ Generate the controlled U3 gate given angle parameters + theta, phi, and lam. + + Parameters + ---------- + `theta` : float + The theta angle in radians. + `phi` : float + The phi angle in radians. + `lam` : float + The lambda angle in radians. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the controlled U3 gate. + """ + return U3(theta, phi, lam).control(1) + +def MCX(num_controls: int) -> Operator: + """ Generate the multi-controlled X gate given the number of control qubits. + + Parameters + ---------- + `num_controls` : int + The number of control qubits. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the multi-controlled X gate. + """ + return PauliX.control(num_controls) + +def MCY(num_controls: int) -> Operator: + """ Generate the multi-controlled Y gate given the number of control qubits. + + Parameters + ---------- + `num_controls` : int + The number of control qubits. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the multi-controlled Y gate. + """ + return PauliY.control(num_controls) + +def MCZ(num_controls: int) -> Operator: + """ Generate the multi-controlled Z gate given the number of control qubits. + + Parameters + ---------- + `num_controls` : int + The number of control qubits. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the multi-controlled Z gate. + """ + return PauliZ.control(num_controls) + +def MCH(num_controls: int) -> Operator: + """ Generate the multi-controlled H gate given the number of control qubits. + + Parameters + ---------- + `num_controls` : int + The number of control qubits. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the multi-controlled H gate. + """ + return Hadamard.control(num_controls) + +def MCS(num_controls: int) -> Operator: + """ Generate the multi-controlled S gate given the number of control qubits. + + Parameters + ---------- + `num_controls` : int + The number of control qubits. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the multi-controlled S gate. + """ + return S.control(num_controls) + +def MCSdg(num_controls: int) -> Operator: + """ Generate the multi-controlled Sdg gate given the number of control qubits. + + Parameters + ---------- + `num_controls` : int + The number of control qubits. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the multi-controlled Sdg gate. + """ + return Sdg.control(num_controls) + +def MCT(num_controls: int) -> Operator: + """ Generate the multi-controlled T gate given the number of control qubits. + + Parameters + ---------- + `num_controls` : int + The number of control qubits. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the multi-controlled T gate. + """ + return T.control(num_controls) + +def MCTdg(num_controls: int) -> Operator: + """ Generate the multi-controlled Tdg gate given the number of control qubits. + + Parameters + ---------- + `num_controls` : int + The number of control qubits. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the multi-controlled Tdg gate. + """ + return Tdg.control(num_controls) + +def MCRX( + num_controls: int, + theta: float + ) -> Operator: + """ Generate the multi-controlled RX gate given the number of control qubits. + + Parameters + ---------- + `num_controls` : int + The number of control qubits. + `theta` : float + The rotation angle in radians. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the multi-controlled RX gate. + """ + return RX(theta).control(num_controls) + +def MCRY( + num_controls: int, + theta: float + ) -> Operator: + """ Generate the multi-controlled RY gate given the number of control qubits. + + Parameters + ---------- + `num_controls` : int + The number of control qubits. + `theta` : float + The rotation angle in radians. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the multi-controlled RY gate. + """ + return RY(theta).control(num_controls) + +def MCRZ( + num_controls: int, + theta: float + ) -> Operator: + """ Generate the multi-controlled RZ gate given the number of control qubits. + + Parameters + ---------- + `num_controls` : int + The number of control qubits. + `theta` : float + The rotation angle in radians. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the multi-controlled RZ gate. + """ + return RZ(theta).control(num_controls) + +def MCPhase( + num_controls: int, + theta: float + ) -> Operator: + """ Generate the multi-controlled Phase gate given the number of control qubits. + + Parameters + ---------- + `num_controls` : int + The number of control qubits. + `theta` : float + The rotation angle in radians. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the multi-controlled Phase gate. + """ + return Phase(theta).control(num_controls) + +def MCU3( + num_controls: int, + theta: float, + phi: float, + lam: float + ) -> Operator: + """ Generate the multi-controlled U3 gate given the number of control qubits. + + Parameters + ---------- + `num_controls` : int + The number of control qubits. + `theta` : float + The theta angle in radians. + `phi` : float + The phi angle in radians. + `lam` : float + The lambda angle in radians. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the multi-controlled U3 gate. + """ + return U3(theta, phi, lam).control(num_controls) \ No newline at end of file diff --git a/quick/circuit/gate_matrix/gate.py b/quick/circuit/gate_matrix/gate.py deleted file mode 100644 index 9a3073e..0000000 --- a/quick/circuit/gate_matrix/gate.py +++ /dev/null @@ -1,161 +0,0 @@ -# Copyright 2023-2025 Qualition Computing LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://github.com/Qualition/quick/blob/main/LICENSE -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" Module for generating the matrix representation of a quantum gate. -""" - -from __future__ import annotations - -__all__ = ["Gate"] - -import numpy as np -from numpy.typing import NDArray -from typing import Literal - -from quick.predicates import is_unitary_matrix - -# Constant -ZERO_PROJECTOR = np.array([ - [1, 0], - [0, 0] -]) -ONE_PROJECTOR = np.array([ - [0, 0], - [0, 1] -]) - - -class Gate: - """ `quick.gate_matrix.Gate` class represents a quantum gate. This class is used to - generate the matrix representation of a quantum gate for testing and classical simulation - purposes. - - Parameters - ---------- - `name`: str - The name of the gate. - `matrix`: NDArray[np.complex128] - The matrix representation of the gate. - - Attributes - ---------- - `name`: str - The name of the gate. - `matrix`: NDArray[np.complex128] - The matrix representation of the gate. - - Raises - ------ - ValueError - - If the matrix is not unitary. - - Usage - ----- - >>> gate = Gate("H", np.array([[1, 1], - ... [1, -1]]) / np.sqrt(2)) - """ - def __init__( - self, - name: str, - matrix: NDArray[np.complex128] - ) -> None: - """ Initialize a `quick.gate_matrix.Gate` instance. - """ - self.name = name - self.matrix = matrix - if not is_unitary_matrix(matrix): - raise ValueError("The matrix must be unitary.") - self.num_qubits = int(np.log2(matrix.shape[0])) - self.ordering = "MSB" - - def adjoint(self) -> NDArray[np.complex128]: - """ Generate the adjoint of the gate. - - Returns - ------- - NDArray[np.complex128] - The adjoint of the gate. - """ - return self.matrix.T.conj() - - def control( - self, - num_control_qubits: int - ) -> Gate: - """ Generate the matrix representation of a controlled version of the gate. - - Parameters - ---------- - `num_control_qubits`: int - The number of control qubits. - - Returns - ------- - `controlled_gate` : quick.gate_matrix.Gate - The controlled gate. - - Raises - ------ - TypeError - - If the number of control qubits is not an integer. - ValueError - - If the number of control qubits is less than 1. - """ - if not isinstance(num_control_qubits, int): - raise TypeError("The number of control qubits must be an integer.") - - if num_control_qubits < 1: - raise ValueError("The number of control qubits must be greater than 0.") - - controlled_matrix = np.kron(ZERO_PROJECTOR, np.eye(2 ** num_control_qubits)) + \ - np.kron(ONE_PROJECTOR, self.matrix) - controlled_gate = Gate(f"C-{self.name}", controlled_matrix.astype(complex)) - - return controlled_gate - - def change_mapping( - self, - ordering: Literal["MSB", "LSB"] - ) -> None: - """ Change the mapping of the qubits in the matrix representation of the gate. - - Parameters - ---------- - `ordering`: Literal["MSB", "LSB"] - The new qubit ordering. - - Returns - ------- - `reordered_matrix` : NDArray[np.complex128] - The new matrix with LSB conversion. - - Raises - ------ - ValueError - - If the ordering is not "MSB" or "LSB". - """ - if ordering not in ["MSB", "LSB"]: - raise ValueError("The ordering must be either 'MSB' or 'LSB'.") - - if ordering == self.ordering: - return - - # Create a new matrix to store the reordered elements - dims = [2] * (self.num_qubits * 2) - reordered_matrix = self.matrix.reshape(dims).transpose().reshape( - (2**self.num_qubits, 2**self.num_qubits) - ) - - self.matrix = reordered_matrix - self.ordering = ordering \ No newline at end of file diff --git a/quick/circuit/gate_matrix/single_qubit_gates.py b/quick/circuit/gate_matrix/single_qubit_gates.py index 7d3ea1f..20db165 100644 --- a/quick/circuit/gate_matrix/single_qubit_gates.py +++ b/quick/circuit/gate_matrix/single_qubit_gates.py @@ -23,186 +23,187 @@ "PauliZ", "Hadamard", "S", + "Sdg", "T", + "Tdg", "RX", "RY", "RZ", - "U3", - "Phase" + "Phase", + "U3" ] import numpy as np -from quick.circuit.gate_matrix import Gate - - -class PauliX(Gate): - """ `quick.gate_matrix.PauliX` class represents the Pauli-X gate. - """ - def __init__(self) -> None: - """ Initialize a `quick.gate_matrix.PauliX` instance. - """ - super().__init__( - "X", - np.array([ - [0, 1], - [1, 0] - ]) - ) - -class PauliY(Gate): - """ `quick.gate_matrix.PauliY` class represents the Pauli-Y gate. - """ - def __init__(self) -> None: - """ Initialize a `quick.gate_matrix.PauliY` instance. - """ - super().__init__( - "Y", - np.array([ - [0, -1j], - [1j, 0] - ]) - ) - -class PauliZ(Gate): - """ `quick.gate_matrix.PauliZ` class represents the Pauli-Z gate. - """ - def __init__(self) -> None: - """ Initialize a `quick.gate_matrix.PauliZ` instance. - """ - super().__init__( - "Z", - np.array([ - [1, 0], - [0, -1] - ]) - ) - -class Hadamard(Gate): - """ `quick.gate_matrix.Hadamard` class represents the Hadamard gate. - """ - def __init__(self) -> None: - """ Initialize a `quick.gate_matrix.Hadamard` instance. - """ - super().__init__( - "H", - np.array([ - [1, 1], - [1, -1] - ]) / np.sqrt(2) - ) - -class S(Gate): - """ `quick.gate_matrix.S` class represents the S gate. - """ - def __init__(self) -> None: - """ Initialize a `quick.gate_matrix.S` instance. - """ - super().__init__( - "S", - np.array([ - [1, 0], - [0, 1j] - ]) - ) - -class T(Gate): - """ `quick.gate_matrix.T` class represents the T gate. - """ - def __init__(self) -> None: - """ Initialize a `quick.gate_matrix.T` instance. - """ - super().__init__( - "T", - np.array([ - [1, 0], - [0, np.exp(1j * np.pi / 4)] - ]) - ) - -class RX(Gate): - """ `quick.gate_matrix.RX` class represents the RX gate. +from quick.primitives import Operator + + +PauliX = Operator( + label="X", + data=np.array([ + [0, 1], + [1, 0] + ]) +) + +PauliY = Operator( + label="Y", + data=np.array([ + [0, -1j], + [1j, 0] + ]) +) + +PauliZ = Operator( + label="Z", + data=np.array([ + [1, 0], + [0, -1] + ]) +) + +Hadamard = Operator( + label="H", + data=np.array([ + [1, 1], + [1, -1] + ]) / np.sqrt(2) +) + +S = Operator( + label="S", + data=np.array([ + [1, 0], + [0, 1j] + ]) +) + +Sdg = S.adjoint() + +T = Operator( + label="T", + data=np.array([ + [1, 0], + [0, np.exp(1j * np.pi / 4)] + ]) +) + +Tdg = T.adjoint() + +def RX(theta: float) -> Operator: + """ Generate the RX rotation gate given angle parameter + theta. + + Parameters + ---------- + `theta` : float + The rotation angle in radians. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the RX rotation gate. """ - def __init__( - self, - theta: float - ) -> None: - """ Initialize a `quick.gate_matrix.RX` instance. - """ - super().__init__( - f"RX({theta})", - np.array([ - [np.cos(theta / 2), -1j * np.sin(theta / 2)], - [-1j * np.sin(theta / 2), np.cos(theta / 2)] - ]) - ) - -class RY(Gate): - """ `quick.gate_matrix.RY` class represents the RY gate. + return Operator( + label=f"RX({theta})", + data=np.array([ + [np.cos(theta / 2), -1j * np.sin(theta / 2)], + [-1j * np.sin(theta / 2), np.cos(theta / 2)] + ]) + ) + +def RY(theta: float) -> Operator: + """ Generate the RY rotation gate given angle parameter + theta. + + Parameters + ---------- + `theta` : float + The rotation angle in radians. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the RY rotation gate. """ - def __init__( - self, - theta: float - ) -> None: - """ Initialize a `quick.gate_matrix.RY` instance. - """ - super().__init__( - f"RY({theta})", - np.array([ - [np.cos(theta / 2), -np.sin(theta / 2)], - [np.sin(theta / 2), np.cos(theta / 2)] - ]) - ) - -class RZ(Gate): - """ `quick.gate_matrix.RZ` class represents the RZ gate. + return Operator( + label=f"RY({theta})", + data=np.array([ + [np.cos(theta / 2), -np.sin(theta / 2)], + [np.sin(theta / 2), np.cos(theta / 2)] + ]) + ) + +def RZ(theta: float) -> Operator: + """ Generate the RZ rotation gate given angle parameter + theta. + + Parameters + ---------- + `theta` : float + The rotation angle in radians. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the RZ rotation gate. """ - def __init__( - self, - theta: float - ) -> None: - """ Initialize a `quick.gate_matrix.RZ` instance. - """ - super().__init__( - f"RZ({theta})", - np.array([ - [np.exp(-1j * theta / 2), 0], - [0, np.exp(1j * theta / 2)] - ]) - ) - -class U3(Gate): - """ `quick.gate_matrix.U3` class represents the U3 gate. + return Operator( + label=f"RZ({theta})", + data=np.array([ + [np.exp(-1j * theta / 2), 0], + [0, np.exp(1j * theta / 2)] + ]) + ) + +def Phase(theta: float) -> Operator: + """ Generate the Phase gate given angle parameter + theta. + + Parameters + ---------- + `theta` : float + The phase angle in radians. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the Phase gate. """ - def __init__( - self, - theta: float, - phi: float, - lam: float - ) -> None: - """ Initialize a `quick.gate_matrix.U3` instance. - """ - super().__init__( - f"U3({theta}, {phi}, {lam})", - np.array([ - [np.cos(theta / 2), -np.exp(1j * lam) * np.sin(theta / 2)], - [np.exp(1j * phi) * np.sin(theta / 2), np.exp(1j * (phi + lam)) * np.cos(theta / 2)] - ]) - ) - -class Phase(Gate): - """ `quick.gate_matrix.Phase` class represents the Phase gate. + return Operator( + label=f"Phase({theta})", + data=np.array([ + [1, 0], + [0, np.exp(1j * theta)] + ]) + ) + +def U3( + theta: float, + phi: float, + lam: float + ) -> Operator: + """ Generate the U3 gate given angle parameters + theta, phi, and lam. + + Parameters + ---------- + `theta` : float + The theta angle in radians. + `phi` : float + The phi angle in radians. + `lam` : float + The lambda angle in radians. + + Returns + ------- + quick.primitives.Operator + The matrix representation of the U3 gate. """ - def __init__( - self, - theta: float - ) -> None: - """ Initialize a `quick.gate_matrix.Phase` instance. - """ - super().__init__( - f"Phase({theta})", - np.array([ - [1, 0], - [0, np.exp(1j * theta)] - ]) - ) \ No newline at end of file + return Operator( + label=f"U3({theta}, {phi}, {lam})", + data=np.array([ + [np.cos(theta / 2), -np.exp(1j * lam) * np.sin(theta / 2)], + [np.exp(1j * phi) * np.sin(theta / 2), np.exp(1j * (phi + lam)) * np.cos(theta / 2)] + ]) + ) \ No newline at end of file diff --git a/quick/predicates/__init__.py b/quick/predicates/__init__.py index 7423163..ec389d7 100644 --- a/quick/predicates/__init__.py +++ b/quick/predicates/__init__.py @@ -13,6 +13,8 @@ # limitations under the License. __all__ = [ + "is_power", + "is_normalized", "is_statevector", "is_square_matrix", "is_diagonal_matrix", @@ -26,6 +28,8 @@ ] from quick.predicates.predicates import ( + is_power, + is_normalized, is_statevector, is_square_matrix, is_diagonal_matrix, diff --git a/quick/predicates/predicates.py b/quick/predicates/predicates.py index e390b80..5af2f4a 100644 --- a/quick/predicates/predicates.py +++ b/quick/predicates/predicates.py @@ -18,6 +18,8 @@ from __future__ import annotations __all__ = [ + "is_power", + "is_normalized", "is_statevector", "is_square_matrix", "is_diagonal_matrix", @@ -38,7 +40,7 @@ RTOL_DEFAULT = 1e-5 -def _is_power( +def is_power( base: int, number: int ) -> bool: @@ -59,6 +61,33 @@ def _is_power( result = math.log(number) / math.log(base) return result == math.floor(result) +def is_normalized( + statevector: NDArray[np.complex128], + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT + ) -> bool: + """ Test if an array is normalized. + + Parameters + ---------- + `statevector` : NDArray[np.complex128] + The input statevector. + `rtol` : float, optional, default=RTOL_DEFAULT + The relative tolerance parameter. + `atol` : float, optional, default=ATOL_DEFAULT + The absolute tolerance parameter. + + Returns + ------- + bool + True if the array is normalized, False otherwise. + + Usage + ----- + >>> is_normalized(np.array([1, 0])) + """ + return bool(np.isclose(np.linalg.norm(statevector), 1.0, rtol=rtol, atol=atol)) + def is_statevector( statevector: NDArray[np.complex128], system_size: int = 2, @@ -97,7 +126,7 @@ def is_statevector( if system_size < 2: raise ValueError("System size must be greater than or equal to 2.") - if not _is_power(system_size, len(statevector)): + if not is_power(system_size, len(statevector)): return False if statevector.ndim == 2: @@ -105,7 +134,7 @@ def is_statevector( statevector = statevector.ravel() return ( - bool(np.isclose(np.linalg.norm(statevector), 1.0, rtol=rtol, atol=atol)) + is_normalized(statevector, rtol=rtol, atol=atol) and statevector.ndim == 1 and len(statevector) > 1 ) diff --git a/quick/primitives/contraction.py b/quick/primitives/contraction.py index 1988032..b2f6899 100644 --- a/quick/primitives/contraction.py +++ b/quick/primitives/contraction.py @@ -68,7 +68,7 @@ def _einsum_tensor_contract( # The reason we reverse the order is because the gates themselves # are in LSB convention as well - op_free_indices = list(reversed(contract_indices)) + op_free_indices = contract_indices[::-1] op_indices = op_free_indices + op_contract_indices diff --git a/quick/primitives/operator.py b/quick/primitives/operator.py index bfa6714..2b72461 100644 --- a/quick/primitives/operator.py +++ b/quick/primitives/operator.py @@ -34,7 +34,7 @@ class Operator: """ `quick.primitives.Operator` class is used to represent a quantum operator. Quantum operators are unitary matrices which represent operations applied to - quantum states (represented with qubits). + quantum states (represented with qubits). It uses LSB convention. Parameters ---------- @@ -54,6 +54,8 @@ class Operator: The shape of the quantum operator data. `num_qubits` : int The number of qubits the quantum operator acts on. + `num_control_qubits` : int + The number of qubits the operator uses as controls. `tensor_shape` : tuple[int, ...] The shape of the quantum operator tensor based on qubits as the physical dimension. @@ -89,6 +91,7 @@ def __init__( self.data = data self.shape = self.data.shape self.num_qubits = int(np.ceil(np.log2(self.shape[0]))) + self.num_control_qubits = 0 self.tensor_shape = (2, 2) * self.num_qubits def conj(self) -> Operator: @@ -122,7 +125,7 @@ def adjoint(self) -> Operator: return self.conj().T() def reverse_bits(self) -> None: - """ Reverse the order of the qubits in the statevector. + """ Reverse the order of the qubits in the operator. This changes MSB to LSB, and vice versa. """ axes = tuple(range(self.num_qubits - 1, -1, -1)) @@ -180,6 +183,53 @@ def contract( contract(self, op, qubit_indices) + def control( + self, + num_controls: int = 1 + ) -> Operator: + """ Generate the controlled version of the operator. + + Parameters + ---------- + `num_controls` : int + The number of control qubits. + + Returns + ------- + quick.primitives.Operator + The controlled version of the operator. + + Raises + ------ + ValueError + - If the number of control qubits is less than 1. + """ + if num_controls < 1: + raise ValueError( + "Number of control qubits must be at least 1." + f"Received {num_controls} instead." + ) + + self.num_control_qubits += num_controls + + zero_projector = np.array([ + [1, 0], + [0, 0] + ]) + one_projector = np.array([ + [0, 0], + [0, 1] + ]) + + controlled_operator = self.data + + for _ in range(num_controls): + control_component = np.kron(np.eye(controlled_operator.shape[0]), zero_projector).astype(np.complex128) + target_component = np.kron(controlled_operator, one_projector).astype(np.complex128) + controlled_operator = control_component + target_component + + return Operator(controlled_operator) + def _check__mul__( self, other: Any @@ -243,6 +293,13 @@ def __eq__( return bool(np.all(np.isclose(self.data, other.data, atol=1e-8, rtol=0))) + @overload + def __mul__( + self, + other: Scalar + ) -> Operator: + ... + @overload def __mul__( self, @@ -259,9 +316,14 @@ def __mul__( def __mul__( self, - other: statevector.Statevector | Operator + other: Scalar | statevector.Statevector | Operator ) -> Operator | statevector.Statevector: - """ Multiply an operator with a statevector or another operator. + """ Multiply an operator with a number, statevector, or another operator. + + Notes + ----- + The multiplication of a number with the operator behaves like the global + phase shift. The multiplication of an operator with a statevector is defined as: - A|ψ⟩ = |ψ'⟩ @@ -299,7 +361,9 @@ def __mul__( ... [0+0j, 1+0j]]) >>> operator1 * operator2 """ - if isinstance(other, statevector.Statevector): + if isinstance(other, Scalar): + return Operator(self.data * complex(other)) + elif isinstance(other, statevector.Statevector): if self.num_qubits != other.num_qubits: raise ValueError("Cannot multiply an operator with an incompatible statevector.") return statevector.Statevector((self.data @ other.data).astype(np.complex128)) diff --git a/quick/primitives/statevector.py b/quick/primitives/statevector.py index f21f687..e03c0bd 100644 --- a/quick/primitives/statevector.py +++ b/quick/primitives/statevector.py @@ -23,6 +23,7 @@ from numpy.typing import NDArray from typing import Any, Literal, SupportsFloat, TypeAlias +from quick.predicates import is_normalized import quick.primitives.operator as operator # `Scalar` is a type alias that represents a scalar value that can be either @@ -34,7 +35,7 @@ class Statevector: """ `quick.primitives.Statevector` is a class that represents a qubit statevector. Qubit statevectors are complex vectors with a magnitude of 1 with 2^N elements where N is the number of qubits used to represent - the statevector. + the statevector. It uses LSB convention. Parameters ---------- @@ -151,31 +152,6 @@ def validate_data(data: NDArray[np.complex128]) -> None: elif data.ndim == 2 and data.shape[0] != 1: raise ValueError("Cannot convert an operator to a statevector.") - @staticmethod - def check_normalization(data: NDArray[np.complex128]) -> bool: - """ Check if a data is normalized to 2-norm. - - Parameters - ---------- - `data` : NDArray[np.complex128] - The data. - - Returns - ------- - bool - Whether the vector is normalized to 2-norm or not. - - Usage - ----- - >>> data = np.array([1, 2, 3, 4]) - >>> check_normalization(data) - """ - return bool( - np.isclose( - np.linalg.norm(data), 1.0, atol=1e-08 - ) - ) - def is_normalized(self) -> None: """ Check if a `quick.primitives.Statevector` instance is normalized to 2-norm. @@ -183,7 +159,7 @@ def is_normalized(self) -> None: ----- >>> statevector.is_normalized() """ - self.normalized = self.check_normalization(self.data) + self.normalized = is_normalized(self.data) @staticmethod def normalize_data( @@ -618,13 +594,6 @@ def __mul__( ) -> Statevector: """ Multiply the statevector by a scalar. - Notes - ----- - The multiplication of a statevector with a scalar does not change - the statevector. This is because the distribution of the statevector - is preserved as the scalar is multiplied with each element of the - statevector. We provide the scalar multiplication for completeness. - Parameters ---------- `other` : Scalar @@ -662,13 +631,6 @@ def __rmul__( ) -> Statevector: """ Multiply the statevector by a scalar. - Notes - ----- - The multiplication of a statevector with a scalar does not change - the statevector. This is because the distribution of the statevector - is preserved as the scalar is multiplied with each element of the - statevector. We provide the scalar multiplication for completeness. - Parameters ---------- `other` : Scalar @@ -704,7 +666,7 @@ def __matmul__( self, other: Statevector ) -> Statevector: - """ Tensor product of two statevectors. + """ Calculate the tensor product of the two statevectors. Parameters ---------- diff --git a/quick/synthesis/gate_decompositions/multi_controlled_decomposition/mcsu2_real_diagonal.py b/quick/synthesis/gate_decompositions/multi_controlled_decomposition/mcsu2_real_diagonal.py index 73e6246..5081f2d 100644 --- a/quick/synthesis/gate_decompositions/multi_controlled_decomposition/mcsu2_real_diagonal.py +++ b/quick/synthesis/gate_decompositions/multi_controlled_decomposition/mcsu2_real_diagonal.py @@ -223,7 +223,7 @@ def MCRX( circuit, control_indices, target_index, - RX(theta).matrix + RX(theta).data ) def MCRY( @@ -271,7 +271,7 @@ def MCRY( circuit, control_indices, target_index, - RY(theta).matrix, + RY(theta).data, ) def MCRZ( @@ -319,5 +319,5 @@ def MCRZ( circuit, control_indices, target_index, - RZ(theta).matrix, + RZ(theta).data, ) \ No newline at end of file diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.py index e7f6d6d..321b31b 100644 --- a/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.py +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.py @@ -108,7 +108,7 @@ [-1j, -1] ], dtype=np.complex128) * SQRT2 -u2la: NDArray[np.complex128] = np.array([ +U2LA: NDArray[np.complex128] = np.array([ [0.5+0.5j, 0.5-0.5j], [-0.5-0.5j, 0.5-0.5j] ], dtype=np.complex128) @@ -378,11 +378,8 @@ def _decomp1(weyl_decomposition: TwoQubitWeylDecomposition) -> tuple[ `U0l` : NDArray[np.complex128] The left unitary matrix. """ - # Get the CX gate in LSB ordering - CX.change_mapping("LSB") - # Use the basis gate as the closest reflection in the Weyl chamber - basis = TwoQubitWeylDecomposition(CX.matrix) + basis = TwoQubitWeylDecomposition(CX.data) U0l = weyl_decomposition.K1l.dot(basis.K1l.T.conj()) U0r = weyl_decomposition.K1r.dot(basis.K1r.T.conj()) @@ -440,8 +437,8 @@ def _decomp2_supercontrolled(weyl_decomposition: TwoQubitWeylDecomposition) -> t """ U0l = weyl_decomposition.K1l.dot(Q0L) U0r = weyl_decomposition.K1r.dot(Q0R) - U1l = Q1LA.dot(RZ(-2 * float(weyl_decomposition.a)).matrix).dot(Q1LB) - U1r = Q1RA.dot(RZ(2 * float(weyl_decomposition.b)).matrix).dot(Q1RB) + U1l = Q1LA.dot(RZ(-2 * float(weyl_decomposition.a)).data).dot(Q1LB) + U1r = Q1RA.dot(RZ(2 * float(weyl_decomposition.b)).data).dot(Q1RB) U2l = Q2L.dot(weyl_decomposition.K2l) U2r = Q2R.dot(weyl_decomposition.K2r) @@ -500,9 +497,9 @@ def _decomp3_supercontrolled(weyl_decomposition: TwoQubitWeylDecomposition) -> t U0l = weyl_decomposition.K1l.dot(U0L) U0r = weyl_decomposition.K1r.dot(U0R) U1l = U1L - U1r = U1RA.dot(RZ(-2 * float(weyl_decomposition.c)).matrix).dot(UR1B) - U2l = u2la.dot(RZ(-2 * float(weyl_decomposition.a)).matrix).dot(U2LB) - U2r = U2RA.dot(RZ(2 * float(weyl_decomposition.b)).matrix).dot(U2RB) + U1r = U1RA.dot(RZ(-2 * float(weyl_decomposition.c)).data).dot(UR1B) + U2l = U2LA.dot(RZ(-2 * float(weyl_decomposition.a)).data).dot(U2LB) + U2r = U2RA.dot(RZ(2 * float(weyl_decomposition.b)).data).dot(U2RB) U3l = U3L.dot(weyl_decomposition.K2l) U3r = U3R.dot(weyl_decomposition.K2r) diff --git a/tests/circuit/test_circuit_decompose.py b/tests/circuit/test_circuit_decompose.py index 305208b..fd19164 100644 --- a/tests/circuit/test_circuit_decompose.py +++ b/tests/circuit/test_circuit_decompose.py @@ -279,6 +279,15 @@ def test_primitive_gates( num_qubits: int ) -> None: """ Test the decomposition of the primitive gates. + + Parameters + ---------- + `framework` : type[quick.circuit.Circuit] + The circuit framework to use. + `gate_func` : Callable[[Circuit], None] + The gate function to apply. + `num_qubits` : int + The number of qubits in the circuit. """ # Define the circuit and apply the gate circuit: Circuit = framework(num_qubits) @@ -294,4 +303,79 @@ def test_primitive_gates( checker_circuit: Circuit = framework(num_qubits) gate_func(checker_circuit) + assert decomposed_circuit == checker_circuit + + @pytest.mark.parametrize("framework", CIRCUIT_FRAMEWORKS) + def test_decompose_gates( + self, + framework: type[Circuit] + ) -> None: + """ Test specific gate decomposition using `decompose_gates` method. + + Parameters + ---------- + `framework` : type[quick.circuit.Circuit] + """ + circuit: Circuit = framework(3) + circuit.UCRZ([0.1, 0.2, 0.3, 0.4], [0, 1], 2) + circuit.H(0) + circuit.Z(0) + + decomposed_circuit = circuit.decompose_gate(["UCRZ", "RZ", "Z"]) + + checker_circuit: Circuit = framework(3) + + checker_circuit.RX(angle=1.5707963267948966, qubit_indices=2) + checker_circuit.RY(angle=-0.25, qubit_indices=2) + checker_circuit.RX(angle=-1.5707963267948966, qubit_indices=2) + checker_circuit.CX(control_index=0, target_index=2) + checker_circuit.RX(angle=1.5707963267948966, qubit_indices=2) + checker_circuit.RY(angle=0.05000000000000002, qubit_indices=2) + checker_circuit.RX(angle=-1.5707963267948966, qubit_indices=2) + checker_circuit.CX(control_index=1, target_index=2) + checker_circuit.RX(angle=1.5707963267948966, qubit_indices=2) + checker_circuit.RX(angle=-1.5707963267948966, qubit_indices=2) + checker_circuit.CX(control_index=0, target_index=2) + checker_circuit.RX(angle=1.5707963267948966, qubit_indices=2) + checker_circuit.RY(angle=0.1, qubit_indices=2) + checker_circuit.RX(angle=-1.5707963267948966, qubit_indices=2) + checker_circuit.CX(control_index=1, target_index=2) + checker_circuit.H(qubit_indices=0) + checker_circuit.Phase(angle=3.141592653589793, qubit_indices=0) + + assert decomposed_circuit == checker_circuit + + @pytest.mark.parametrize("framework", CIRCUIT_FRAMEWORKS) + @pytest.mark.parametrize("gate_func, num_qubits", GATE_TYPES) + def test_decompose_gates_primitive_gates( + self, + framework: type[Circuit], + gate_func, + num_qubits: int + ) -> None: + """ Test the decomposition of the primitive gates with `decompose_gates` method. + + Parameters + ---------- + `framework` : type[quick.circuit.Circuit] + The circuit framework to use. + `gate_func` : Callable[[Circuit], None] + The gate function to apply. + `num_qubits` : int + The number of qubits in the circuit. + """ + # Define the circuit and apply the gate + circuit: Circuit = framework(num_qubits) + gate_func(circuit) + + assert circuit.circuit_log[-1]["definition"] == [] + + # Decompose the circuit + decomposed_circuit = circuit.decompose_gate(circuit.circuit_log[-1]["gate"]) + + # Check the decomposed circuit + # Assert it's unchanged + checker_circuit: Circuit = framework(num_qubits) + gate_func(checker_circuit) + assert decomposed_circuit == checker_circuit \ No newline at end of file diff --git a/tests/circuit/test_circuit_to_controlled.py b/tests/circuit/test_circuit_to_controlled.py index 587e94a..608d1cc 100644 --- a/tests/circuit/test_circuit_to_controlled.py +++ b/tests/circuit/test_circuit_to_controlled.py @@ -1438,4 +1438,35 @@ def test_global_phase_in_target( check_multiple_controlled_circuit.MCPhase(0.5, 0, 1) assert single_controlled_circuit == check_single_controlled_circuit - assert multiple_controlled_circuit == check_multiple_controlled_circuit \ No newline at end of file + assert multiple_controlled_circuit == check_multiple_controlled_circuit + + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + def test_unitary_control( + self, + circuit_framework: type[Circuit] + ) -> None: + """ Test the `.control()` method with a unitary. + + Parameters + ---------- + `circuit_framework` : type[quick.circuit.Circuit] + The framework to convert the circuit to. + """ + from quick.random import generate_random_unitary + from quick.primitives import Operator + from numpy.testing import assert_almost_equal + + unitary = generate_random_unitary(3) + + circuit = circuit_framework(num_qubits=3) + circuit.unitary(unitary, [0, 1, 2]) + + single_controlled_circuit = circuit.control(1) + multiple_controlled_circuit = circuit.control(2) + + operator = Operator(unitary) + single_controlled_checker = operator.control(1) + multiple_controlled_checker = operator.control(2) + + assert_almost_equal(single_controlled_circuit.get_unitary(), single_controlled_checker.data) + assert_almost_equal(multiple_controlled_circuit.get_unitary(), multiple_controlled_checker.data) \ No newline at end of file diff --git a/tests/circuit/test_cirq_circuit.py b/tests/circuit/test_cirq_circuit.py index a269f5b..de15caf 100644 --- a/tests/circuit/test_cirq_circuit.py +++ b/tests/circuit/test_cirq_circuit.py @@ -587,12 +587,12 @@ def test_Tdg( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angle, expected", [ - [1, 0, np.pi/4, RX(np.pi/4).matrix], - [1, 0, 1/3, RX(1/3).matrix], - [1, 0, -1/4, RX(-1/4).matrix], - [2, 1, np.pi/4, np.kron(RX(np.pi/4).matrix, np.eye(2))], - [3, 2, 1/3, np.kron(RX(1/3).matrix, np.eye(4))], - [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RX(np.pi/4).matrix, RX(np.pi/4).matrix))] + [1, 0, np.pi/4, RX(np.pi/4).data], + [1, 0, 1/3, RX(1/3).data], + [1, 0, -1/4, RX(-1/4).data], + [2, 1, np.pi/4, np.kron(RX(np.pi/4).data, np.eye(2))], + [3, 2, 1/3, np.kron(RX(1/3).data, np.eye(4))], + [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RX(np.pi/4).data, RX(np.pi/4).data))] ]) def test_RX( self, @@ -610,12 +610,12 @@ def test_RX( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angle, expected", [ - [1, 0, np.pi/4, RY(np.pi/4).matrix], - [1, 0, 1/3, RY(1/3).matrix], - [1, 0, -1/4, RY(-1/4).matrix], - [2, 1, np.pi/4, np.kron(RY(np.pi/4).matrix, np.eye(2))], - [3, 2, 1/3, np.kron(RY(1/3).matrix, np.eye(4))], - [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RY(np.pi/4).matrix, RY(np.pi/4).matrix))] + [1, 0, np.pi/4, RY(np.pi/4).data], + [1, 0, 1/3, RY(1/3).data], + [1, 0, -1/4, RY(-1/4).data], + [2, 1, np.pi/4, np.kron(RY(np.pi/4).data, np.eye(2))], + [3, 2, 1/3, np.kron(RY(1/3).data, np.eye(4))], + [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RY(np.pi/4).data, RY(np.pi/4).data))] ]) def test_RY( self, @@ -633,12 +633,12 @@ def test_RY( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angle, expected", [ - [1, 0, np.pi/4, RZ(np.pi/4).matrix], - [1, 0, 1/3, RZ(1/3).matrix], - [1, 0, -1/4, RZ(-1/4).matrix], - [2, 1, np.pi/4, np.kron(RZ(np.pi/4).matrix, np.eye(2))], - [3, 2, 1/3, np.kron(RZ(1/3).matrix, np.eye(4))], - [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RZ(np.pi/4).matrix, RZ(np.pi/4).matrix))] + [1, 0, np.pi/4, RZ(np.pi/4).data], + [1, 0, 1/3, RZ(1/3).data], + [1, 0, -1/4, RZ(-1/4).data], + [2, 1, np.pi/4, np.kron(RZ(np.pi/4).data, np.eye(2))], + [3, 2, 1/3, np.kron(RZ(1/3).data, np.eye(4))], + [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RZ(np.pi/4).data, RZ(np.pi/4).data))] ]) def test_RZ( self, @@ -656,12 +656,12 @@ def test_RZ( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angle, expected", [ - [1, 0, np.pi/4, Phase(np.pi/4).matrix], - [1, 0, 1/3, Phase(1/3).matrix], - [1, 0, -1/4, Phase(-1/4).matrix], - [2, 1, np.pi/4, np.kron(Phase(np.pi/4).matrix, np.eye(2))], - [3, 2, 1/3, np.kron(Phase(1/3).matrix, np.eye(4))], - [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(Phase(np.pi/4).matrix, Phase(np.pi/4).matrix))] + [1, 0, np.pi/4, Phase(np.pi/4).data], + [1, 0, 1/3, Phase(1/3).data], + [1, 0, -1/4, Phase(-1/4).data], + [2, 1, np.pi/4, np.kron(Phase(np.pi/4).data, np.eye(2))], + [3, 2, 1/3, np.kron(Phase(1/3).data, np.eye(4))], + [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(Phase(np.pi/4).data, Phase(np.pi/4).data))] ]) def test_Phase( self, @@ -826,22 +826,22 @@ def test_RZZ( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angles, expected", [ - [1, 0, (np.pi/2, np.pi/3, np.pi/4), U3(np.pi/2, np.pi/3, np.pi/4).matrix], - [2, 1, (np.pi/2, -np.pi/3, np.pi/4), np.kron(U3(np.pi/2, -np.pi/3, np.pi/4).matrix, np.eye(2))], - [3, 2, (np.pi/2, np.pi/3, -np.pi/4), np.kron(U3(np.pi/2, np.pi/3, -np.pi/4).matrix, np.eye(4))], - [1, 0, (1/3, 1/4, 1/5), U3(1/3, 1/4, 1/5).matrix], + [1, 0, (np.pi/2, np.pi/3, np.pi/4), U3(np.pi/2, np.pi/3, np.pi/4).data], + [2, 1, (np.pi/2, -np.pi/3, np.pi/4), np.kron(U3(np.pi/2, -np.pi/3, np.pi/4).data, np.eye(2))], + [3, 2, (np.pi/2, np.pi/3, -np.pi/4), np.kron(U3(np.pi/2, np.pi/3, -np.pi/4).data, np.eye(4))], + [1, 0, (1/3, 1/4, 1/5), U3(1/3, 1/4, 1/5).data], [3, [0, 2], (np.pi/2, np.pi/3, np.pi/4), np.kron( - U3(np.pi/2, np.pi/3, np.pi/4).matrix, + U3(np.pi/2, np.pi/3, np.pi/4).data, np.kron( np.eye(2), - U3(np.pi/2, np.pi/3, np.pi/4).matrix + U3(np.pi/2, np.pi/3, np.pi/4).data ) )], [3, [0, 1], (np.pi/2, np.pi/3, np.pi/4), np.kron( np.eye(2), np.kron( - U3(np.pi/2, np.pi/3, np.pi/4).matrix, - U3(np.pi/2, np.pi/3, np.pi/4).matrix + U3(np.pi/2, np.pi/3, np.pi/4).data, + U3(np.pi/2, np.pi/3, np.pi/4).data ) ) ] diff --git a/tests/circuit/test_control_state.py b/tests/circuit/test_control_state.py index 246e0c3..7dc79cd 100644 --- a/tests/circuit/test_control_state.py +++ b/tests/circuit/test_control_state.py @@ -1474,14 +1474,14 @@ def test_Multiplexor_control_state( The circuit framework to test. """ gates = [ - RX(np.pi/2).matrix, - RX(np.pi/3).matrix, - RX(np.pi/4).matrix, - RX(np.pi/5).matrix, - RX(np.pi/6).matrix, - RX(np.pi/7).matrix, - RX(np.pi/8).matrix, - RX(np.pi/9).matrix + RX(np.pi/2).data, + RX(np.pi/3).data, + RX(np.pi/4).data, + RX(np.pi/5).data, + RX(np.pi/6).data, + RX(np.pi/7).data, + RX(np.pi/8).data, + RX(np.pi/9).data ] # Given control state "0" diff --git a/tests/circuit/test_pennylane_circuit.py b/tests/circuit/test_pennylane_circuit.py index d56cbcc..6a620e3 100644 --- a/tests/circuit/test_pennylane_circuit.py +++ b/tests/circuit/test_pennylane_circuit.py @@ -584,12 +584,12 @@ def test_Tdg( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angle, expected", [ - [1, 0, np.pi/4, RX(np.pi/4).matrix], - [1, 0, 1/3, RX(1/3).matrix], - [1, 0, -1/4, RX(-1/4).matrix], - [2, 1, np.pi/4, np.kron(RX(np.pi/4).matrix, np.eye(2))], - [3, 2, 1/3, np.kron(RX(1/3).matrix, np.eye(4))], - [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RX(np.pi/4).matrix, RX(np.pi/4).matrix))] + [1, 0, np.pi/4, RX(np.pi/4).data], + [1, 0, 1/3, RX(1/3).data], + [1, 0, -1/4, RX(-1/4).data], + [2, 1, np.pi/4, np.kron(RX(np.pi/4).data, np.eye(2))], + [3, 2, 1/3, np.kron(RX(1/3).data, np.eye(4))], + [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RX(np.pi/4).data, RX(np.pi/4).data))] ]) def test_RX( self, @@ -607,12 +607,12 @@ def test_RX( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angle, expected", [ - [1, 0, np.pi/4, RY(np.pi/4).matrix], - [1, 0, 1/3, RY(1/3).matrix], - [1, 0, -1/4, RY(-1/4).matrix], - [2, 1, np.pi/4, np.kron(RY(np.pi/4).matrix, np.eye(2))], - [3, 2, 1/3, np.kron(RY(1/3).matrix, np.eye(4))], - [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RY(np.pi/4).matrix, RY(np.pi/4).matrix))] + [1, 0, np.pi/4, RY(np.pi/4).data], + [1, 0, 1/3, RY(1/3).data], + [1, 0, -1/4, RY(-1/4).data], + [2, 1, np.pi/4, np.kron(RY(np.pi/4).data, np.eye(2))], + [3, 2, 1/3, np.kron(RY(1/3).data, np.eye(4))], + [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RY(np.pi/4).data, RY(np.pi/4).data))] ]) def test_RY( self, @@ -630,12 +630,12 @@ def test_RY( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angle, expected", [ - [1, 0, np.pi/4, RZ(np.pi/4).matrix], - [1, 0, 1/3, RZ(1/3).matrix], - [1, 0, -1/4, RZ(-1/4).matrix], - [2, 1, np.pi/4, np.kron(RZ(np.pi/4).matrix, np.eye(2))], - [3, 2, 1/3, np.kron(RZ(1/3).matrix, np.eye(4))], - [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RZ(np.pi/4).matrix, RZ(np.pi/4).matrix))] + [1, 0, np.pi/4, RZ(np.pi/4).data], + [1, 0, 1/3, RZ(1/3).data], + [1, 0, -1/4, RZ(-1/4).data], + [2, 1, np.pi/4, np.kron(RZ(np.pi/4).data, np.eye(2))], + [3, 2, 1/3, np.kron(RZ(1/3).data, np.eye(4))], + [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RZ(np.pi/4).data, RZ(np.pi/4).data))] ]) def test_RZ( self, @@ -653,12 +653,12 @@ def test_RZ( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angle, expected", [ - [1, 0, np.pi/4, Phase(np.pi/4).matrix], - [1, 0, 1/3, Phase(1/3).matrix], - [1, 0, -1/4, Phase(-1/4).matrix], - [2, 1, np.pi/4, np.kron(Phase(np.pi/4).matrix, np.eye(2))], - [3, 2, 1/3, np.kron(Phase(1/3).matrix, np.eye(4))], - [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(Phase(np.pi/4).matrix, Phase(np.pi/4).matrix))] + [1, 0, np.pi/4, Phase(np.pi/4).data], + [1, 0, 1/3, Phase(1/3).data], + [1, 0, -1/4, Phase(-1/4).data], + [2, 1, np.pi/4, np.kron(Phase(np.pi/4).data, np.eye(2))], + [3, 2, 1/3, np.kron(Phase(1/3).data, np.eye(4))], + [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(Phase(np.pi/4).data, Phase(np.pi/4).data))] ]) def test_Phase( self, @@ -823,22 +823,22 @@ def test_RZZ( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angles, expected", [ - [1, 0, (np.pi/2, np.pi/3, np.pi/4), U3(np.pi/2, np.pi/3, np.pi/4).matrix], - [2, 1, (np.pi/2, -np.pi/3, np.pi/4), np.kron(U3(np.pi/2, -np.pi/3, np.pi/4).matrix, np.eye(2))], - [3, 2, (np.pi/2, np.pi/3, -np.pi/4), np.kron(U3(np.pi/2, np.pi/3, -np.pi/4).matrix, np.eye(4))], - [1, 0, (1/3, 1/4, 1/5), U3(1/3, 1/4, 1/5).matrix], + [1, 0, (np.pi/2, np.pi/3, np.pi/4), U3(np.pi/2, np.pi/3, np.pi/4).data], + [2, 1, (np.pi/2, -np.pi/3, np.pi/4), np.kron(U3(np.pi/2, -np.pi/3, np.pi/4).data, np.eye(2))], + [3, 2, (np.pi/2, np.pi/3, -np.pi/4), np.kron(U3(np.pi/2, np.pi/3, -np.pi/4).data, np.eye(4))], + [1, 0, (1/3, 1/4, 1/5), U3(1/3, 1/4, 1/5).data], [3, [0, 2], (np.pi/2, np.pi/3, np.pi/4), np.kron( - U3(np.pi/2, np.pi/3, np.pi/4).matrix, + U3(np.pi/2, np.pi/3, np.pi/4).data, np.kron( np.eye(2), - U3(np.pi/2, np.pi/3, np.pi/4).matrix + U3(np.pi/2, np.pi/3, np.pi/4).data ) )], [3, [0, 1], (np.pi/2, np.pi/3, np.pi/4), np.kron( np.eye(2), np.kron( - U3(np.pi/2, np.pi/3, np.pi/4).matrix, - U3(np.pi/2, np.pi/3, np.pi/4).matrix + U3(np.pi/2, np.pi/3, np.pi/4).data, + U3(np.pi/2, np.pi/3, np.pi/4).data ) ) ] diff --git a/tests/circuit/test_qiskit_circuit.py b/tests/circuit/test_qiskit_circuit.py index 1bcabef..2160ee9 100644 --- a/tests/circuit/test_qiskit_circuit.py +++ b/tests/circuit/test_qiskit_circuit.py @@ -586,12 +586,12 @@ def test_Tdg( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angle, expected", [ - [1, 0, np.pi/4, RX(np.pi/4).matrix], - [1, 0, 1/3, RX(1/3).matrix], - [1, 0, -1/4, RX(-1/4).matrix], - [2, 1, np.pi/4, np.kron(RX(np.pi/4).matrix, np.eye(2))], - [3, 2, 1/3, np.kron(RX(1/3).matrix, np.eye(4))], - [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RX(np.pi/4).matrix, RX(np.pi/4).matrix))] + [1, 0, np.pi/4, RX(np.pi/4).data], + [1, 0, 1/3, RX(1/3).data], + [1, 0, -1/4, RX(-1/4).data], + [2, 1, np.pi/4, np.kron(RX(np.pi/4).data, np.eye(2))], + [3, 2, 1/3, np.kron(RX(1/3).data, np.eye(4))], + [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RX(np.pi/4).data, RX(np.pi/4).data))] ]) def test_RX( self, @@ -609,12 +609,12 @@ def test_RX( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angle, expected", [ - [1, 0, np.pi/4, RY(np.pi/4).matrix], - [1, 0, 1/3, RY(1/3).matrix], - [1, 0, -1/4, RY(-1/4).matrix], - [2, 1, np.pi/4, np.kron(RY(np.pi/4).matrix, np.eye(2))], - [3, 2, 1/3, np.kron(RY(1/3).matrix, np.eye(4))], - [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RY(np.pi/4).matrix, RY(np.pi/4).matrix))] + [1, 0, np.pi/4, RY(np.pi/4).data], + [1, 0, 1/3, RY(1/3).data], + [1, 0, -1/4, RY(-1/4).data], + [2, 1, np.pi/4, np.kron(RY(np.pi/4).data, np.eye(2))], + [3, 2, 1/3, np.kron(RY(1/3).data, np.eye(4))], + [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RY(np.pi/4).data, RY(np.pi/4).data))] ]) def test_RY( self, @@ -632,12 +632,12 @@ def test_RY( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angle, expected", [ - [1, 0, np.pi/4, RZ(np.pi/4).matrix], - [1, 0, 1/3, RZ(1/3).matrix], - [1, 0, -1/4, RZ(-1/4).matrix], - [2, 1, np.pi/4, np.kron(RZ(np.pi/4).matrix, np.eye(2))], - [3, 2, 1/3, np.kron(RZ(1/3).matrix, np.eye(4))], - [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RZ(np.pi/4).matrix, RZ(np.pi/4).matrix))] + [1, 0, np.pi/4, RZ(np.pi/4).data], + [1, 0, 1/3, RZ(1/3).data], + [1, 0, -1/4, RZ(-1/4).data], + [2, 1, np.pi/4, np.kron(RZ(np.pi/4).data, np.eye(2))], + [3, 2, 1/3, np.kron(RZ(1/3).data, np.eye(4))], + [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RZ(np.pi/4).data, RZ(np.pi/4).data))] ]) def test_RZ( self, @@ -655,12 +655,12 @@ def test_RZ( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angle, expected", [ - [1, 0, np.pi/4, Phase(np.pi/4).matrix], - [1, 0, 1/3, Phase(1/3).matrix], - [1, 0, -1/4, Phase(-1/4).matrix], - [2, 1, np.pi/4, np.kron(Phase(np.pi/4).matrix, np.eye(2))], - [3, 2, 1/3, np.kron(Phase(1/3).matrix, np.eye(4))], - [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(Phase(np.pi/4).matrix, Phase(np.pi/4).matrix))] + [1, 0, np.pi/4, Phase(np.pi/4).data], + [1, 0, 1/3, Phase(1/3).data], + [1, 0, -1/4, Phase(-1/4).data], + [2, 1, np.pi/4, np.kron(Phase(np.pi/4).data, np.eye(2))], + [3, 2, 1/3, np.kron(Phase(1/3).data, np.eye(4))], + [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(Phase(np.pi/4).data, Phase(np.pi/4).data))] ]) def test_Phase( self, @@ -825,22 +825,22 @@ def test_RZZ( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angles, expected", [ - [1, 0, (np.pi/2, np.pi/3, np.pi/4), U3(np.pi/2, np.pi/3, np.pi/4).matrix], - [2, 1, (np.pi/2, -np.pi/3, np.pi/4), np.kron(U3(np.pi/2, -np.pi/3, np.pi/4).matrix, np.eye(2))], - [3, 2, (np.pi/2, np.pi/3, -np.pi/4), np.kron(U3(np.pi/2, np.pi/3, -np.pi/4).matrix, np.eye(4))], - [1, 0, (1/3, 1/4, 1/5), U3(1/3, 1/4, 1/5).matrix], + [1, 0, (np.pi/2, np.pi/3, np.pi/4), U3(np.pi/2, np.pi/3, np.pi/4).data], + [2, 1, (np.pi/2, -np.pi/3, np.pi/4), np.kron(U3(np.pi/2, -np.pi/3, np.pi/4).data, np.eye(2))], + [3, 2, (np.pi/2, np.pi/3, -np.pi/4), np.kron(U3(np.pi/2, np.pi/3, -np.pi/4).data, np.eye(4))], + [1, 0, (1/3, 1/4, 1/5), U3(1/3, 1/4, 1/5).data], [3, [0, 2], (np.pi/2, np.pi/3, np.pi/4), np.kron( - U3(np.pi/2, np.pi/3, np.pi/4).matrix, + U3(np.pi/2, np.pi/3, np.pi/4).data, np.kron( np.eye(2), - U3(np.pi/2, np.pi/3, np.pi/4).matrix + U3(np.pi/2, np.pi/3, np.pi/4).data ) )], [3, [0, 1], (np.pi/2, np.pi/3, np.pi/4), np.kron( np.eye(2), np.kron( - U3(np.pi/2, np.pi/3, np.pi/4).matrix, - U3(np.pi/2, np.pi/3, np.pi/4).matrix + U3(np.pi/2, np.pi/3, np.pi/4).data, + U3(np.pi/2, np.pi/3, np.pi/4).data ) ) ] diff --git a/tests/circuit/test_quimb_circuit.py b/tests/circuit/test_quimb_circuit.py index 4f0ad2f..224dead 100644 --- a/tests/circuit/test_quimb_circuit.py +++ b/tests/circuit/test_quimb_circuit.py @@ -584,12 +584,12 @@ def test_Tdg( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angle, expected", [ - [1, 0, np.pi/4, RX(np.pi/4).matrix], - [1, 0, 1/3, RX(1/3).matrix], - [1, 0, -1/4, RX(-1/4).matrix], - [2, 1, np.pi/4, np.kron(RX(np.pi/4).matrix, np.eye(2))], - [3, 2, 1/3, np.kron(RX(1/3).matrix, np.eye(4))], - [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RX(np.pi/4).matrix, RX(np.pi/4).matrix))] + [1, 0, np.pi/4, RX(np.pi/4).data], + [1, 0, 1/3, RX(1/3).data], + [1, 0, -1/4, RX(-1/4).data], + [2, 1, np.pi/4, np.kron(RX(np.pi/4).data, np.eye(2))], + [3, 2, 1/3, np.kron(RX(1/3).data, np.eye(4))], + [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RX(np.pi/4).data, RX(np.pi/4).data))] ]) def test_RX( self, @@ -607,12 +607,12 @@ def test_RX( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angle, expected", [ - [1, 0, np.pi/4, RY(np.pi/4).matrix], - [1, 0, 1/3, RY(1/3).matrix], - [1, 0, -1/4, RY(-1/4).matrix], - [2, 1, np.pi/4, np.kron(RY(np.pi/4).matrix, np.eye(2))], - [3, 2, 1/3, np.kron(RY(1/3).matrix, np.eye(4))], - [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RY(np.pi/4).matrix, RY(np.pi/4).matrix))] + [1, 0, np.pi/4, RY(np.pi/4).data], + [1, 0, 1/3, RY(1/3).data], + [1, 0, -1/4, RY(-1/4).data], + [2, 1, np.pi/4, np.kron(RY(np.pi/4).data, np.eye(2))], + [3, 2, 1/3, np.kron(RY(1/3).data, np.eye(4))], + [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RY(np.pi/4).data, RY(np.pi/4).data))] ]) def test_RY( self, @@ -630,12 +630,12 @@ def test_RY( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angle, expected", [ - [1, 0, np.pi/4, RZ(np.pi/4).matrix], - [1, 0, 1/3, RZ(1/3).matrix], - [1, 0, -1/4, RZ(-1/4).matrix], - [2, 1, np.pi/4, np.kron(RZ(np.pi/4).matrix, np.eye(2))], - [3, 2, 1/3, np.kron(RZ(1/3).matrix, np.eye(4))], - [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RZ(np.pi/4).matrix, RZ(np.pi/4).matrix))] + [1, 0, np.pi/4, RZ(np.pi/4).data], + [1, 0, 1/3, RZ(1/3).data], + [1, 0, -1/4, RZ(-1/4).data], + [2, 1, np.pi/4, np.kron(RZ(np.pi/4).data, np.eye(2))], + [3, 2, 1/3, np.kron(RZ(1/3).data, np.eye(4))], + [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RZ(np.pi/4).data, RZ(np.pi/4).data))] ]) def test_RZ( self, @@ -653,12 +653,12 @@ def test_RZ( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angle, expected", [ - [1, 0, np.pi/4, Phase(np.pi/4).matrix], - [1, 0, 1/3, Phase(1/3).matrix], - [1, 0, -1/4, Phase(-1/4).matrix], - [2, 1, np.pi/4, np.kron(Phase(np.pi/4).matrix, np.eye(2))], - [3, 2, 1/3, np.kron(Phase(1/3).matrix, np.eye(4))], - [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(Phase(np.pi/4).matrix, Phase(np.pi/4).matrix))] + [1, 0, np.pi/4, Phase(np.pi/4).data], + [1, 0, 1/3, Phase(1/3).data], + [1, 0, -1/4, Phase(-1/4).data], + [2, 1, np.pi/4, np.kron(Phase(np.pi/4).data, np.eye(2))], + [3, 2, 1/3, np.kron(Phase(1/3).data, np.eye(4))], + [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(Phase(np.pi/4).data, Phase(np.pi/4).data))] ]) def test_Phase( self, @@ -823,22 +823,22 @@ def test_RZZ( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angles, expected", [ - [1, 0, (np.pi/2, np.pi/3, np.pi/4), U3(np.pi/2, np.pi/3, np.pi/4).matrix], - [2, 1, (np.pi/2, -np.pi/3, np.pi/4), np.kron(U3(np.pi/2, -np.pi/3, np.pi/4).matrix, np.eye(2))], - [3, 2, (np.pi/2, np.pi/3, -np.pi/4), np.kron(U3(np.pi/2, np.pi/3, -np.pi/4).matrix, np.eye(4))], - [1, 0, (1/3, 1/4, 1/5), U3(1/3, 1/4, 1/5).matrix], + [1, 0, (np.pi/2, np.pi/3, np.pi/4), U3(np.pi/2, np.pi/3, np.pi/4).data], + [2, 1, (np.pi/2, -np.pi/3, np.pi/4), np.kron(U3(np.pi/2, -np.pi/3, np.pi/4).data, np.eye(2))], + [3, 2, (np.pi/2, np.pi/3, -np.pi/4), np.kron(U3(np.pi/2, np.pi/3, -np.pi/4).data, np.eye(4))], + [1, 0, (1/3, 1/4, 1/5), U3(1/3, 1/4, 1/5).data], [3, [0, 2], (np.pi/2, np.pi/3, np.pi/4), np.kron( - U3(np.pi/2, np.pi/3, np.pi/4).matrix, + U3(np.pi/2, np.pi/3, np.pi/4).data, np.kron( np.eye(2), - U3(np.pi/2, np.pi/3, np.pi/4).matrix + U3(np.pi/2, np.pi/3, np.pi/4).data ) )], [3, [0, 1], (np.pi/2, np.pi/3, np.pi/4), np.kron( np.eye(2), np.kron( - U3(np.pi/2, np.pi/3, np.pi/4).matrix, - U3(np.pi/2, np.pi/3, np.pi/4).matrix + U3(np.pi/2, np.pi/3, np.pi/4).data, + U3(np.pi/2, np.pi/3, np.pi/4).data ) ) ] diff --git a/tests/circuit/test_tket_circuit.py b/tests/circuit/test_tket_circuit.py index 60049d8..dd0a414 100644 --- a/tests/circuit/test_tket_circuit.py +++ b/tests/circuit/test_tket_circuit.py @@ -586,12 +586,12 @@ def test_Tdg( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angle, expected", [ - [1, 0, np.pi/4, RX(np.pi/4).matrix], - [1, 0, 1/3, RX(1/3).matrix], - [1, 0, -1/4, RX(-1/4).matrix], - [2, 1, np.pi/4, np.kron(RX(np.pi/4).matrix, np.eye(2))], - [3, 2, 1/3, np.kron(RX(1/3).matrix, np.eye(4))], - [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RX(np.pi/4).matrix, RX(np.pi/4).matrix))] + [1, 0, np.pi/4, RX(np.pi/4).data], + [1, 0, 1/3, RX(1/3).data], + [1, 0, -1/4, RX(-1/4).data], + [2, 1, np.pi/4, np.kron(RX(np.pi/4).data, np.eye(2))], + [3, 2, 1/3, np.kron(RX(1/3).data, np.eye(4))], + [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RX(np.pi/4).data, RX(np.pi/4).data))] ]) def test_RX( self, @@ -609,12 +609,12 @@ def test_RX( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angle, expected", [ - [1, 0, np.pi/4, RY(np.pi/4).matrix], - [1, 0, 1/3, RY(1/3).matrix], - [1, 0, -1/4, RY(-1/4).matrix], - [2, 1, np.pi/4, np.kron(RY(np.pi/4).matrix, np.eye(2))], - [3, 2, 1/3, np.kron(RY(1/3).matrix, np.eye(4))], - [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RY(np.pi/4).matrix, RY(np.pi/4).matrix))] + [1, 0, np.pi/4, RY(np.pi/4).data], + [1, 0, 1/3, RY(1/3).data], + [1, 0, -1/4, RY(-1/4).data], + [2, 1, np.pi/4, np.kron(RY(np.pi/4).data, np.eye(2))], + [3, 2, 1/3, np.kron(RY(1/3).data, np.eye(4))], + [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RY(np.pi/4).data, RY(np.pi/4).data))] ]) def test_RY( self, @@ -632,12 +632,12 @@ def test_RY( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angle, expected", [ - [1, 0, np.pi/4, RZ(np.pi/4).matrix], - [1, 0, 1/3, RZ(1/3).matrix], - [1, 0, -1/4, RZ(-1/4).matrix], - [2, 1, np.pi/4, np.kron(RZ(np.pi/4).matrix, np.eye(2))], - [3, 2, 1/3, np.kron(RZ(1/3).matrix, np.eye(4))], - [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RZ(np.pi/4).matrix, RZ(np.pi/4).matrix))] + [1, 0, np.pi/4, RZ(np.pi/4).data], + [1, 0, 1/3, RZ(1/3).data], + [1, 0, -1/4, RZ(-1/4).data], + [2, 1, np.pi/4, np.kron(RZ(np.pi/4).data, np.eye(2))], + [3, 2, 1/3, np.kron(RZ(1/3).data, np.eye(4))], + [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(RZ(np.pi/4).data, RZ(np.pi/4).data))] ]) def test_RZ( self, @@ -655,12 +655,12 @@ def test_RZ( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angle, expected", [ - [1, 0, np.pi/4, Phase(np.pi/4).matrix], - [1, 0, 1/3, Phase(1/3).matrix], - [1, 0, -1/4, Phase(-1/4).matrix], - [2, 1, np.pi/4, np.kron(Phase(np.pi/4).matrix, np.eye(2))], - [3, 2, 1/3, np.kron(Phase(1/3).matrix, np.eye(4))], - [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(Phase(np.pi/4).matrix, Phase(np.pi/4).matrix))] + [1, 0, np.pi/4, Phase(np.pi/4).data], + [1, 0, 1/3, Phase(1/3).data], + [1, 0, -1/4, Phase(-1/4).data], + [2, 1, np.pi/4, np.kron(Phase(np.pi/4).data, np.eye(2))], + [3, 2, 1/3, np.kron(Phase(1/3).data, np.eye(4))], + [3, [0, 1], np.pi/4, np.kron(np.eye(2), np.kron(Phase(np.pi/4).data, Phase(np.pi/4).data))] ]) def test_Phase( self, @@ -825,22 +825,22 @@ def test_RZZ( assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("num_qubits, qubit_indices, angles, expected", [ - [1, 0, (np.pi/2, np.pi/3, np.pi/4), U3(np.pi/2, np.pi/3, np.pi/4).matrix], - [2, 1, (np.pi/2, -np.pi/3, np.pi/4), np.kron(U3(np.pi/2, -np.pi/3, np.pi/4).matrix, np.eye(2))], - [3, 2, (np.pi/2, np.pi/3, -np.pi/4), np.kron(U3(np.pi/2, np.pi/3, -np.pi/4).matrix, np.eye(4))], - [1, 0, (1/3, 1/4, 1/5), U3(1/3, 1/4, 1/5).matrix], + [1, 0, (np.pi/2, np.pi/3, np.pi/4), U3(np.pi/2, np.pi/3, np.pi/4).data], + [2, 1, (np.pi/2, -np.pi/3, np.pi/4), np.kron(U3(np.pi/2, -np.pi/3, np.pi/4).data, np.eye(2))], + [3, 2, (np.pi/2, np.pi/3, -np.pi/4), np.kron(U3(np.pi/2, np.pi/3, -np.pi/4).data, np.eye(4))], + [1, 0, (1/3, 1/4, 1/5), U3(1/3, 1/4, 1/5).data], [3, [0, 2], (np.pi/2, np.pi/3, np.pi/4), np.kron( - U3(np.pi/2, np.pi/3, np.pi/4).matrix, + U3(np.pi/2, np.pi/3, np.pi/4).data, np.kron( np.eye(2), - U3(np.pi/2, np.pi/3, np.pi/4).matrix + U3(np.pi/2, np.pi/3, np.pi/4).data ) )], [3, [0, 1], (np.pi/2, np.pi/3, np.pi/4), np.kron( np.eye(2), np.kron( - U3(np.pi/2, np.pi/3, np.pi/4).matrix, - U3(np.pi/2, np.pi/3, np.pi/4).matrix + U3(np.pi/2, np.pi/3, np.pi/4).data, + U3(np.pi/2, np.pi/3, np.pi/4).data ) ) ] diff --git a/tests/circuit/test_uniformly_controlled_gates.py b/tests/circuit/test_uniformly_controlled_gates.py index db45b72..8b32c06 100644 --- a/tests/circuit/test_uniformly_controlled_gates.py +++ b/tests/circuit/test_uniformly_controlled_gates.py @@ -272,54 +272,54 @@ def test_Diagonal( @pytest.mark.parametrize("multiplexor_simplification", [True, False]) @pytest.mark.parametrize("single_qubit_gates", [ [ - Hadamard().matrix, - PauliX().matrix, - Hadamard().matrix, - PauliX().matrix + Hadamard.data, + PauliX.data, + Hadamard.data, + PauliX.data ], [ - RX(np.pi/2).matrix, - RY(np.pi/3).matrix, - RX(np.pi/4).matrix, - RY(np.pi/5).matrix, - RX(np.pi/6).matrix, - RY(np.pi/7).matrix, - RX(np.pi/8).matrix, - RY(np.pi/9).matrix + RX(np.pi/2).data, + RY(np.pi/3).data, + RX(np.pi/4).data, + RY(np.pi/5).data, + RX(np.pi/6).data, + RY(np.pi/7).data, + RX(np.pi/8).data, + RY(np.pi/9).data ], [ - RY(np.pi).matrix, - RX(np.pi/2).matrix, - RY(np.pi/3).matrix, - RX(np.pi/4).matrix, - RY(np.pi/5).matrix, - RX(np.pi/6).matrix, - RY(np.pi/7).matrix, - RX(np.pi/8).matrix, - RY(np.pi/9).matrix, - RX(np.pi/10).matrix, - RY(np.pi/11).matrix, - RX(np.pi/12).matrix, - RY(np.pi/13).matrix, - RX(np.pi/14).matrix, - RY(np.pi/15).matrix, - RX(np.pi/16).matrix, - RY(np.pi/17).matrix, - RX(np.pi/18).matrix, - RY(np.pi/19).matrix, - RX(np.pi/20).matrix, - RY(np.pi/21).matrix, - RX(np.pi/22).matrix, - RY(np.pi/23).matrix, - RX(np.pi/24).matrix, - RY(np.pi/25).matrix, - RX(np.pi/26).matrix, - RY(np.pi/27).matrix, - RX(np.pi/28).matrix, - RY(np.pi/29).matrix, - RX(np.pi/30).matrix, - RY(np.pi/31).matrix, - RX(np.pi/32).matrix + RY(np.pi).data, + RX(np.pi/2).data, + RY(np.pi/3).data, + RX(np.pi/4).data, + RY(np.pi/5).data, + RX(np.pi/6).data, + RY(np.pi/7).data, + RX(np.pi/8).data, + RY(np.pi/9).data, + RX(np.pi/10).data, + RY(np.pi/11).data, + RX(np.pi/12).data, + RY(np.pi/13).data, + RX(np.pi/14).data, + RY(np.pi/15).data, + RX(np.pi/16).data, + RY(np.pi/17).data, + RX(np.pi/18).data, + RY(np.pi/19).data, + RX(np.pi/20).data, + RY(np.pi/21).data, + RX(np.pi/22).data, + RY(np.pi/23).data, + RX(np.pi/24).data, + RY(np.pi/25).data, + RX(np.pi/26).data, + RY(np.pi/27).data, + RX(np.pi/28).data, + RY(np.pi/29).data, + RX(np.pi/30).data, + RY(np.pi/31).data, + RX(np.pi/32).data ], ]) def test_Multiplexor( diff --git a/tests/predicates/test_predicates.py b/tests/predicates/test_predicates.py index ddd19b5..b46b67c 100644 --- a/tests/predicates/test_predicates.py +++ b/tests/predicates/test_predicates.py @@ -20,6 +20,8 @@ from scipy.stats import unitary_group from quick.predicates import ( + is_power, + is_normalized, is_statevector, is_square_matrix, is_diagonal_matrix, @@ -33,6 +35,21 @@ ) +def test_is_power() -> None: + """ Test the `is_power()` function. + """ + assert is_power(2, 2) is True + assert is_power(3, 2) is False + assert is_power(2, 4) is True + +def test_is_normalized() -> None: + """ Test the `is_normalized()` function. + """ + state = np.arange(10).astype(np.complex128) + assert is_normalized(state) is False + state = state / np.linalg.norm(state) + assert is_normalized(state) is True + @pytest.mark.parametrize("array, system_size, expected", [ (np.array([1, 0]), 2, True), (np.array([0, 1]), 2, True), @@ -47,7 +64,7 @@ def test_is_statevector( system_size: int, expected: bool ) -> None: - """ Test the `.is_statevector()` method. + """ Test the `is_statevector()` function. Parameters ---------- @@ -61,7 +78,7 @@ def test_is_statevector( assert is_statevector(array, system_size) is expected def test_is_statevector_invalid_system_size() -> None: - """ Test the `.is_statevector()` method with invalid system size. + """ Test the `is_statevector()` function with invalid system size. Parameters ---------- @@ -85,7 +102,7 @@ def test_is_square_matrix( array: NDArray[np.complex128], expected: bool ) -> None: - """ Test the `.is_square_matrix()` method. + """ Test the `is_square_matrix()` function. Parameters ---------- @@ -120,7 +137,7 @@ def test_is_diagonal_matrix( array: NDArray[np.complex128], expected: bool ) -> None: - """ Test the `.is_diagonal_matrix()` method with diagonal matrices. + """ Test the `is_diagonal_matrix()` function with diagonal matrices. Parameters ---------- @@ -168,7 +185,7 @@ def test_is_symmetric_matrix( array: NDArray[np.complex128], expected: bool ) -> None: - """ Test the `.is_symmetric_matrix()` method with symmetric matrices. + """ Test the `is_symmetric_matrix()` function with symmetric matrices. Parameters ---------- @@ -194,7 +211,7 @@ def test_is_identity_matrix( array: NDArray[np.complex128], expected: bool ) -> None: - """ Test the `.is_identity_matrix()` method with identity matrices. + """ Test the `is_identity_matrix()` function with identity matrices. Parameters ---------- @@ -220,7 +237,7 @@ def test_is_unitary_matrix( array: NDArray[np.complex128], expected: bool ) -> None: - """ Test the `.is_unitary_matrix()` method. + """ Test the `is_unitary_matrix()` function. Parameters ---------- @@ -264,7 +281,7 @@ def test_is_hermitian_matrix( array: NDArray[np.complex128], expected: bool ) -> None: - """ Test the `.is_hermitian_matrix()` method with Hermitian matrices. + """ Test the `is_hermitian_matrix()` function with Hermitian matrices. Parameters ---------- @@ -306,7 +323,8 @@ def test_is_positive_semidefinite_matrix( array: NDArray[np.complex128], expected: bool ) -> None: - """ Test the `.is_positive_semidefinite_matrix()` method with positive semidefinite matrices. + """ Test the `is_positive_semidefinite_matrix()` function with + positive semidefinite matrices. Parameters ---------- diff --git a/tests/primitives/test_operator.py b/tests/primitives/test_operator.py index 1b3df40..d670f54 100644 --- a/tests/primitives/test_operator.py +++ b/tests/primitives/test_operator.py @@ -125,6 +125,20 @@ def test_contract(self) -> None: assert_almost_equal(checker_circuit.get_unitary(), op1.data) + def test_control(self) -> None: + """ Test the control operation of the `quick.primitives.Operator` class. + """ + from quick.circuit import QiskitCircuit + + unitary = np.array(unitary_group.rvs(8)).astype(complex) + operator = Operator(unitary) + control_operator = operator.control(2) + + op_circuit = QiskitCircuit(3) + op_circuit.unitary(unitary, [0, 1, 2]) + checker_circuit = op_circuit.control(2) + assert_almost_equal(checker_circuit.get_unitary(), control_operator.data) + def test_array(self) -> None: """ Test the conversion of the `quick.primitives.Operator` to a NumPy array. """ diff --git a/tests/primitives/test_statevector.py b/tests/primitives/test_statevector.py index ffae28e..b13d2f0 100644 --- a/tests/primitives/test_statevector.py +++ b/tests/primitives/test_statevector.py @@ -49,18 +49,6 @@ def test_from_operator_fail(self) -> None: with pytest.raises(ValueError): Statevector(np.eye(4, dtype=complex)) - def test_check_normalization(self) -> None: - """ Test the normalization of the `quick.primitives.Statevector` object. - """ - data = np.array([1, 0, 0, 0]) - assert Statevector.check_normalization(data) - - def test_check_normalization_fail(self) -> None: - """ Test the failure of the normalization of the `quick.primitives.Statevector` object. - """ - data = np.array([1, 1, 1, 1]) - assert not Statevector.check_normalization(data) - def test_normalize(self) -> None: """ Test the normalization of the `quick.primitives.Statevector` object. """ From 7231be9fa5164aa3366d9f77e2aadb90a3cc7783 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Fri, 19 Sep 2025 21:32:55 +0800 Subject: [PATCH 56/58] - Added frobenius distance to `quick.metrics`. - Added constructor from general matrix to `quick.primitives.Operator` via using SVD. --- quick/metrics/__init__.py | 6 ++-- quick/metrics/metrics.py | 34 ++++++++++++++++++- quick/primitives/operator.py | 25 ++++++++++++++ .../two_qubit_decomposition.py | 1 - tests/metrics/test_metrics.py | 11 ++++-- tests/primitives/test_operator.py | 9 +++++ 6 files changed, 80 insertions(+), 6 deletions(-) diff --git a/quick/metrics/__init__.py b/quick/metrics/__init__.py index 2a80faa..1b6e1ed 100644 --- a/quick/metrics/__init__.py +++ b/quick/metrics/__init__.py @@ -17,7 +17,8 @@ "calculate_shannon_entropy", "calculate_entanglement_entropy", "calculate_entanglement_entropy_slope", - "calculate_hilbert_schmidt_test" + "calculate_hilbert_schmidt_test", + "calculate_frobenius_distance" ] from quick.metrics.metrics import ( @@ -25,5 +26,6 @@ calculate_shannon_entropy, calculate_entanglement_entropy, calculate_entanglement_entropy_slope, - calculate_hilbert_schmidt_test + calculate_hilbert_schmidt_test, + calculate_frobenius_distance ) \ No newline at end of file diff --git a/quick/metrics/metrics.py b/quick/metrics/metrics.py index 9a59c30..1e13477 100644 --- a/quick/metrics/metrics.py +++ b/quick/metrics/metrics.py @@ -295,4 +295,36 @@ def calculate_hilbert_schmidt_test( ) )**2 - return chst \ No newline at end of file + return chst + +def calculate_frobenius_distance( + matrix_1: NDArray[np.complex128], + matrix_2: NDArray[np.complex128] + ) -> float: + """ Calculate the Frobenius distance between two matrices. + + Parameters + ---------- + `matrix_1` : NDArray[np.complex128] + The first matrix. + `matrix_2` : NDArray[np.complex128] + The second matrix. + + Returns + ------- + float + The Frobenius distance between the two matrices. + + Raises + ------ + ValueError + - If the matrices are not of the same shape. + + Usage + ----- + >>> frobenius_distance = calculate_frobenius_distance(matrix_1, matrix_2) + """ + if matrix_1.shape != matrix_2.shape: + raise ValueError("The matrices must be of the same shape.") + + return float(np.linalg.norm(matrix_1 - matrix_2, 'fro')) \ No newline at end of file diff --git a/quick/primitives/operator.py b/quick/primitives/operator.py index 2b72461..43ac529 100644 --- a/quick/primitives/operator.py +++ b/quick/primitives/operator.py @@ -94,6 +94,31 @@ def __init__( self.num_control_qubits = 0 self.tensor_shape = (2, 2) * self.num_qubits + @classmethod + def from_matrix( + cls, + matrix: NDArray[np.complex128] + ) -> Operator: + """ Create an `quick.primitives.Operator` from a matrix. + + Parameters + ---------- + `matrix` : NDArray[np.complex128] + The matrix to create the operator from. The matrix is not + required to be unitary, but if it is not, we will approximate + it to the nearest unitary matrix using Singular Value Decomposition (SVD). + + Returns + ------- + quick.primitives.Operator + The operator created from the matrix. + """ + if is_unitary_matrix(matrix): + return cls(matrix) + + U, _, Vh = np.linalg.svd(matrix) + return cls(np.array(U @ Vh).astype(complex)) + def conj(self) -> Operator: """ Take the conjugate of the operator. diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.py index 321b31b..b178fd3 100644 --- a/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.py +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.py @@ -493,7 +493,6 @@ def _decomp3_supercontrolled(weyl_decomposition: TwoQubitWeylDecomposition) -> t `U0l` : NDArray[np.complex128] The left unitary matrix. """ - # Calculate the decomposition U0l = weyl_decomposition.K1l.dot(U0L) U0r = weyl_decomposition.K1r.dot(U0R) U1l = U1L diff --git a/tests/metrics/test_metrics.py b/tests/metrics/test_metrics.py index 79eee22..8ac1ec2 100644 --- a/tests/metrics/test_metrics.py +++ b/tests/metrics/test_metrics.py @@ -28,7 +28,8 @@ calculate_shannon_entropy, calculate_entanglement_entropy, calculate_entanglement_entropy_slope, - calculate_hilbert_schmidt_test + calculate_hilbert_schmidt_test, + calculate_frobenius_distance ) @@ -179,4 +180,10 @@ def test_calculate_hilbert_schmidt_fail(self) -> None: with pytest.raises(ValueError): calculate_hilbert_schmidt_test(unitary, np.zeros((4, 3))) # type: ignore with pytest.raises(ValueError): - calculate_hilbert_schmidt_test(np.zeros((4, 4)), unitary) # type: ignore \ No newline at end of file + calculate_hilbert_schmidt_test(np.zeros((4, 4)), unitary) # type: ignore + + def test_calculate_frobenius_distance(self) -> None: + """ Test the `calculate_frobenius_distance` method. + """ + unitary = unitary_group.rvs(4).astype(np.complex128) + assert_almost_equal(0.0, calculate_frobenius_distance(unitary, unitary)) \ No newline at end of file diff --git a/tests/primitives/test_operator.py b/tests/primitives/test_operator.py index d670f54..8253cbc 100644 --- a/tests/primitives/test_operator.py +++ b/tests/primitives/test_operator.py @@ -42,6 +42,15 @@ def test_init(self) -> None: assert operator.num_qubits == 1 assert operator.label == "A" + def test_from_matrix(self) -> None: + """ Test the initialization of the `quick.primitives.Operator` class from a matrix. + """ + from quick.predicates import is_unitary_matrix + + matrix = np.arange(4).reshape(2, 2).astype(complex) + op = Operator.from_matrix(matrix) + assert is_unitary_matrix(op.data) + def test_conj(self) -> None: """ Test the conjugate of the `quick.primitives.Operator` class. """ From 692ca80e8730eaad107f4fc480c48590051b91a3 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sat, 18 Oct 2025 15:21:01 +0800 Subject: [PATCH 57/58] - Added new predicates. - Slightly updated two qubit decomposition. - Added new test cases for weyl coordinates. --- quick/circuit/ansatz.py | 2 +- quick/circuit/circuit.py | 26 +- quick/circuit/{circuit_utils.py => utils.py} | 0 quick/predicates/__init__.py | 20 +- quick/predicates/predicates.py | 289 ++++- quick/random/__init__.py | 10 +- quick/random/random.py | 73 +- .../two_qubit_decomposition.py | 192 ++-- .../two_qubit_decomposition/utils.py | 60 + .../two_qubit_decomposition/weyl.py | 247 ++-- quick/synthesis/statepreparation/isometry.py | 4 +- quick/synthesis/statepreparation/mottonen.py | 2 +- quick/synthesis/statepreparation/shende.py | 2 +- .../{statepreparation_utils.py => utils.py} | 0 .../shannon_decomposition.py | 2 +- .../{unitarypreparation_utils.py => utils.py} | 0 tests/circuit/test_circuit_base.py | 25 +- .../test_uniformly_controlled_gates.py | 2 +- tests/predicates/__init__.py | 22 +- tests/predicates/test_predicates.py | 1000 +++++++++++------ tests/random/test_random.py | 72 +- .../two_qubit_decomposition/test_weyl.py | 239 +++- 22 files changed, 1569 insertions(+), 720 deletions(-) rename quick/circuit/{circuit_utils.py => utils.py} (100%) create mode 100644 quick/synthesis/gate_decompositions/two_qubit_decomposition/utils.py rename quick/synthesis/statepreparation/{statepreparation_utils.py => utils.py} (100%) rename quick/synthesis/unitarypreparation/{unitarypreparation_utils.py => utils.py} (100%) diff --git a/quick/circuit/ansatz.py b/quick/circuit/ansatz.py index bb5e00f..749a759 100644 --- a/quick/circuit/ansatz.py +++ b/quick/circuit/ansatz.py @@ -48,7 +48,7 @@ class Ansatz: ```python from quick.circuit import Ansatz, QiskitCircuit - from quick.circuit.circuit_utils import reshape, flatten + from quick.circuit.utils import reshape, flatten from quick.random import generate_random_state from scipy.optimize import minimize diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index 5b65c57..fd519af 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -40,7 +40,7 @@ if TYPE_CHECKING: from quick.backend import Backend -from quick.circuit.circuit_utils import ( +from quick.circuit.utils import ( multiplexed_rz_angles, decompose_multiplexor_rotations, extract_single_qubits_and_diagonal, @@ -5333,29 +5333,7 @@ def get_depth(self) -> int: ----- >>> circuit.get_depth() """ - circuit = self.copy() - - # Transpile the circuit to both simplify and optimize it - circuit.transpile() - - # Get the depth of the circuit - depth = circuit.get_dag().get_depth() - - return depth - - def get_width(self) -> int: - """ Get the width of the circuit. - - Returns - ------- - `width` : int - The width of the circuit. - - Usage - ----- - >>> circuit.get_width() - """ - return self.num_qubits + return self.get_dag().get_depth() @abstractmethod def get_unitary(self) -> NDArray[np.complex128]: diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/utils.py similarity index 100% rename from quick/circuit/circuit_utils.py rename to quick/circuit/utils.py diff --git a/quick/predicates/__init__.py b/quick/predicates/__init__.py index ec389d7..ae673fe 100644 --- a/quick/predicates/__init__.py +++ b/quick/predicates/__init__.py @@ -17,6 +17,11 @@ "is_normalized", "is_statevector", "is_square_matrix", + "is_orthogonal_matrix", + "is_real_matrix", + "is_special_matrix", + "is_special_orthogonal_matrix", + "is_special_unitary_matrix", "is_diagonal_matrix", "is_symmetric_matrix", "is_identity_matrix", @@ -24,7 +29,10 @@ "is_hermitian_matrix", "is_positive_semidefinite_matrix", "is_isometry", - "is_density_matrix" + "is_density_matrix", + "is_product_matrix", + "is_locally_equivalent", + "is_supercontrolled" ] from quick.predicates.predicates import ( @@ -32,6 +40,11 @@ is_normalized, is_statevector, is_square_matrix, + is_orthogonal_matrix, + is_real_matrix, + is_special_matrix, + is_special_orthogonal_matrix, + is_special_unitary_matrix, is_diagonal_matrix, is_symmetric_matrix, is_identity_matrix, @@ -39,5 +52,8 @@ is_hermitian_matrix, is_positive_semidefinite_matrix, is_isometry, - is_density_matrix + is_density_matrix, + is_product_matrix, + is_locally_equivalent, + is_supercontrolled ) \ No newline at end of file diff --git a/quick/predicates/predicates.py b/quick/predicates/predicates.py index 5af2f4a..ca1de7d 100644 --- a/quick/predicates/predicates.py +++ b/quick/predicates/predicates.py @@ -22,6 +22,11 @@ "is_normalized", "is_statevector", "is_square_matrix", + "is_orthogonal_matrix", + "is_real_matrix", + "is_special_matrix", + "is_special_orthogonal_matrix", + "is_special_unitary_matrix", "is_diagonal_matrix", "is_symmetric_matrix", "is_identity_matrix", @@ -29,7 +34,10 @@ "is_hermitian_matrix", "is_positive_semidefinite_matrix", "is_isometry", - "is_density_matrix" + "is_density_matrix", + "is_product_matrix", + "is_locally_equivalent", + "is_supercontrolled" ] import numpy as np @@ -59,7 +67,7 @@ def is_power( True if the number is a power of the base, False otherwise. """ result = math.log(number) / math.log(base) - return result == math.floor(result) + return bool(result == math.floor(result)) def is_normalized( statevector: NDArray[np.complex128], @@ -133,7 +141,7 @@ def is_statevector( if statevector.shape[1] == 1: statevector = statevector.ravel() - return ( + return bool( is_normalized(statevector, rtol=rtol, atol=atol) and statevector.ndim == 1 and len(statevector) > 1 @@ -159,7 +167,152 @@ def is_square_matrix(matrix: NDArray[np.complex128]) -> bool: if matrix.ndim != 2: return False shape = matrix.shape - return shape[0] == shape[1] + return bool(shape[0] == shape[1]) + +def is_orthogonal_matrix( + matrix: NDArray[np.complex128], + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT + ) -> bool: + """ Test if an array is an orthogonal matrix. + + Parameters + ---------- + `matrix` : NDArray[np.complex128] + The input matrix. + `rtol` : float, optional, default=RTOL_DEFAULT + The relative tolerance parameter. + `atol` : float, optional, default=ATOL_DEFAULT + The absolute tolerance parameter. + + Returns + ------- + bool + True if the matrix is an orthogonal matrix, False otherwise. + + Usage + ----- + >>> is_orthogonal_matrix(np.eye(2)) + """ + return bool(np.allclose(matrix.T, np.linalg.inv(matrix), rtol=rtol, atol=atol)) + +def is_real_matrix( + matrix: NDArray[np.complex128], + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT + ) -> bool: + """ Test if an array is a real matrix. + + Parameters + ---------- + `matrix` : NDArray[np.complex128] + The input matrix. + `rtol` : float, optional, default=RTOL_DEFAULT + The relative tolerance parameter. + `atol` : float, optional, default=ATOL_DEFAULT + The absolute tolerance parameter. + + Returns + ------- + bool + True if the matrix is a real matrix, False otherwise. + + Usage + ----- + >>> is_real_matrix(np.eye(2)) + """ + return bool(np.allclose(matrix, matrix.real, rtol=rtol, atol=atol)) + +def is_special_matrix( + matrix: NDArray[np.complex128], + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT + ) -> bool: + """ Test if an array is a special matrix (i.e., has determinant 1). + + Parameters + ---------- + `matrix` : NDArray[np.complex128] + The input matrix. + `rtol` : float, optional, default=RTOL_DEFAULT + The relative tolerance parameter. + `atol` : float, optional, default=ATOL_DEFAULT + The absolute tolerance parameter. + + Returns + ------- + bool + True if the matrix is a special matrix, False otherwise. + + Usage + ----- + >>> is_special_matrix(np.eye(2)) + """ + if not is_square_matrix(matrix): + return False + + det = np.linalg.det(matrix) + return bool(np.isclose(det, 1.0, rtol=rtol, atol=atol)) + +def is_special_orthogonal_matrix( + matrix: NDArray[np.float64], + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT + ) -> bool: + """ Test if an array is a special orthogonal matrix. + + Parameters + ---------- + `matrix` : NDArray[np.float64] + The input matrix. + `rtol` : float, optional, default=RTOL_DEFAULT + The relative tolerance parameter. + `atol` : float, optional, default=ATOL_DEFAULT + The absolute tolerance parameter. + + Returns + ------- + bool + True if the matrix is a special orthogonal matrix, False otherwise. + + Usage + ----- + >>> is_so(np.eye(2)) + """ + return bool( + is_special_matrix(matrix.astype(complex), rtol=rtol, atol=atol) + and is_orthogonal_matrix(matrix.astype(complex), rtol=rtol, atol=atol) + ) + +def is_special_unitary_matrix( + matrix: NDArray[np.complex128], + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT + ) -> bool: + """ Test if an array is a special unitary matrix. + + Parameters + ---------- + `matrix` : NDArray[np.complex128] + The input matrix. + `rtol` : float, optional, default=RTOL_DEFAULT + The relative tolerance parameter. + `atol` : float, optional, default=ATOL_DEFAULT + The absolute tolerance parameter. + + Returns + ------- + bool + True if the matrix is a special unitary matrix, False otherwise. + + Usage + ----- + >>> is_su(np.eye(2)) + """ + return bool( + is_special_matrix(matrix, rtol=rtol, atol=atol) + and is_unitary_matrix(matrix, rtol=rtol, atol=atol) + ) def is_diagonal_matrix( matrix: NDArray[np.complex128], @@ -189,7 +342,7 @@ def is_diagonal_matrix( if not is_square_matrix(matrix): return False - return np.allclose(matrix, np.diag(np.diagonal(matrix)), rtol=rtol, atol=atol) + return bool(np.allclose(matrix, np.diag(np.diagonal(matrix)), rtol=rtol, atol=atol)) def is_symmetric_matrix( matrix: NDArray[np.complex128], @@ -219,7 +372,7 @@ def is_symmetric_matrix( if not is_square_matrix(matrix): return False - return np.allclose(matrix, matrix.T, rtol=rtol, atol=atol) + return bool(np.allclose(matrix, matrix.T, rtol=rtol, atol=atol)) def is_identity_matrix( matrix: NDArray[np.complex128], @@ -260,7 +413,7 @@ def is_identity_matrix( matrix = np.exp(-1j * theta) * matrix identity = np.eye(len(matrix)) - return np.allclose(matrix, identity, rtol=rtol, atol=atol) + return bool(np.allclose(matrix, identity, rtol=rtol, atol=atol)) def is_unitary_matrix( matrix: NDArray[np.complex128], @@ -291,7 +444,7 @@ def is_unitary_matrix( return False matrix = matrix.conj().T @ matrix - return is_identity_matrix(matrix, ignore_phase=False, rtol=rtol, atol=atol) + return bool(is_identity_matrix(matrix, ignore_phase=False, rtol=rtol, atol=atol)) def is_hermitian_matrix( matrix: NDArray[np.complex128], @@ -321,7 +474,7 @@ def is_hermitian_matrix( if not is_square_matrix(matrix): return False - return np.allclose(matrix, matrix.conj().T, rtol=rtol, atol=atol) + return bool(np.allclose(matrix, matrix.conj().T, rtol=rtol, atol=atol)) def is_positive_semidefinite_matrix( matrix: NDArray[np.complex128], @@ -388,7 +541,7 @@ def is_isometry( identity = np.eye(matrix.shape[1]) matrix = matrix.conj().T @ matrix - return np.allclose(matrix, identity, rtol=rtol, atol=atol) + return bool(np.allclose(matrix, identity, rtol=rtol, atol=atol)) def is_density_matrix( rho: NDArray[np.complex128], @@ -415,11 +568,123 @@ def is_density_matrix( ----- >>> is_density_matrix(np.eye(2)) """ - if not ( + if not bool( is_hermitian_matrix(rho, rtol=rtol, atol=atol) and is_positive_semidefinite_matrix(rho, rtol=rtol, atol=atol) and np.isclose(np.trace(rho), 1.0, rtol=rtol, atol=atol) ): return False - return True \ No newline at end of file + return True + +def is_product_matrix(matrix: NDArray[np.complex128]) -> bool: + """ Test if a two-qubit unitary is a product matrix. + + A two-qubit gate is a product matrix if it can be expressed as + the Kronecker product of two single-qubit gates. + + Parameters + ---------- + `matrix` : NDArray[np.complex128] + The input two-qubit unitary matrix. + + Returns + ------- + bool + True if the matrix is a product matrix, False otherwise. + + Raises + ------ + ValueError + - If the input matrix is not a two-qubit unitary. + + Usage + ----- + >>> from quick.synthesis.gate_decompositions.two_qubit_decomposition import swap + >>> is_product_matrix(swap()) + False + """ + from quick.synthesis.gate_decompositions.two_qubit_decomposition.weyl import weyl_coordinates + + if not is_unitary_matrix(matrix) or matrix.shape != (4, 4): + raise ValueError("Input matrix must be a 4x4 unitary matrix.") + + x, y, z = weyl_coordinates(matrix) + + return bool(np.isclose(x, 0) and np.isclose(y, 0) and np.isclose(z, 0)) + +def is_locally_equivalent( + matrix1: NDArray[np.complex128], + matrix2: NDArray[np.complex128] + ) -> bool: + """ Test if two two-qubit unitaries are locally equivalent. + Two two-qubit gates are locally equivalent if they differ only + by single-qubit gates. + + Parameters + `matrix1` : NDArray[np.complex128] + The first input two-qubit unitary matrix. + `matrix2` : NDArray[np.complex128] + The second input two-qubit unitary matrix. + + Returns + ------- + bool + True if the matrices are locally equivalent, False otherwise. + + Raises + ------ + ValueError + - If either input matrix is not a two-qubit unitary. + + Usage + ----- + >>> is_locally_equivalent(cx, cz) + """ + from quick.synthesis.gate_decompositions.two_qubit_decomposition.weyl import weyl_coordinates + + if not is_unitary_matrix(matrix1) or matrix1.shape != (4, 4): + raise ValueError("First input matrix must be a 4x4 unitary matrix.") + if not is_unitary_matrix(matrix2) or matrix2.shape != (4, 4): + raise ValueError("Second input matrix must be a 4x4 unitary matrix.") + + x1, y1, z1 = weyl_coordinates(matrix1) + x2, y2, z2 = weyl_coordinates(matrix2) + + return bool(np.isclose(x1, x2) and np.isclose(y1, y2) and np.isclose(z1, z2)) + +def is_supercontrolled(matrix: NDArray[np.complex128]) -> bool: + """ Test if a two-qubit unitary is a supercontrolled gate. + + A two-qubit gate is supercontrolled if its Weyl coordinates + are of the form (pi/4, alpha, 0) up to local equivalence. + + Parameters + ---------- + `matrix` : NDArray[np.complex128] + The input two-qubit unitary matrix. + + Returns + ------- + bool + True if the matrix is a supercontrolled gate, False otherwise. + + Raises + ------ + ValueError + - If the input matrix is not a two-qubit unitary. + + Usage + ----- + >>> from quick.synthesis.gate_decompositions.two_qubit_decomposition import cnot + >>> is_supercontrolled(cnot()) + True + """ + from quick.synthesis.gate_decompositions.two_qubit_decomposition.weyl import weyl_coordinates + + if not is_unitary_matrix(matrix) or matrix.shape != (4, 4): + raise ValueError("Input matrix must be a 4x4 unitary matrix.") + + x, _, z = weyl_coordinates(matrix) + + return bool(np.isclose(x, np.pi / 4) and np.isclose(z, 0)) \ No newline at end of file diff --git a/quick/random/__init__.py b/quick/random/__init__.py index ad5e766..dc6dbed 100644 --- a/quick/random/__init__.py +++ b/quick/random/__init__.py @@ -15,11 +15,17 @@ __all__ = [ "generate_random_state", "generate_random_unitary", - "generate_random_density_matrix" + "generate_random_density_matrix", + "generate_random_orthogonal_matrix", + "generate_random_special_orthogonal_matrix", + "generate_random_special_unitary_matrix" ] from quick.random.random import ( generate_random_state, generate_random_unitary, - generate_random_density_matrix + generate_random_density_matrix, + generate_random_orthogonal_matrix, + generate_random_special_orthogonal_matrix, + generate_random_special_unitary_matrix ) \ No newline at end of file diff --git a/quick/random/random.py b/quick/random/random.py index a37f29d..e538e50 100644 --- a/quick/random/random.py +++ b/quick/random/random.py @@ -17,7 +17,10 @@ __all__ = [ "generate_random_state", "generate_random_unitary", - "generate_random_density_matrix" + "generate_random_density_matrix", + "generate_random_orthogonal_matrix", + "generate_random_special_orthogonal_matrix", + "generate_random_special_unitary_matrix" ] import numpy as np @@ -46,7 +49,9 @@ def _generate_ginibre_matrix( entry is sampled from the normal distribution. """ rng = np.random.default_rng() - ginibre_ensemble = rng.normal(size=(num_rows, num_columns)) + 1j * rng.normal(size=(num_rows, num_columns)) + ginibre_ensemble = rng.normal(size=(num_rows, num_columns)) + 1j * rng.normal( + size=(num_rows, num_columns) + ) return ginibre_ensemble def generate_random_state(num_qubits: int) -> NDArray[np.complex128]: @@ -123,4 +128,66 @@ def generate_random_density_matrix( density_matrix = density_matrix @ ginibre_ensemble density_matrix = density_matrix @ density_matrix.conj().T - return density_matrix / np.trace(density_matrix) \ No newline at end of file + return density_matrix / np.trace(density_matrix) + +def generate_random_orthogonal_matrix(num_qubits: int) -> NDArray[np.complex128]: + """ Generate a random orthogonal matrix for the given number of qubits. + + Parameters + ---------- + `num_qubits` : int + The number of qubits in the orthogonal matrix. + + Returns + ------- + `NDArray[np.complex128]` + The random orthogonal matrix. + """ + A = np.random.rand(2**num_qubits, 2**num_qubits) + Q, R = np.linalg.qr(A) + + d = np.sign(np.diag(R)) + d[d == 0] = 1 + Q = Q @ np.diag(d) + + return Q.astype(np.complex128) + +def generate_random_special_orthogonal_matrix(num_qubits: int) -> NDArray[np.float64]: + """ Generate a random special orthogonal matrix for the given number of qubits. + + Parameters + ---------- + `num_qubits` : int + The number of qubits in the special orthogonal matrix. + + Returns + ------- + `NDArray[np.float64]` + The random special orthogonal matrix. + """ + Q = generate_random_orthogonal_matrix(num_qubits) + + if np.linalg.det(Q) < 0: + Q[:, 0] *= -1 + + return Q.astype(np.float64) + +def generate_random_special_unitary_matrix(num_qubits: int) -> NDArray[np.complex128]: + """ Generate a random special unitary matrix for the given number of qubits. + + Parameters + ---------- + `num_qubits` : int + The number of qubits in the special unitary matrix. + + Returns + ------- + `NDArray[np.complex128]` + The random special unitary matrix. + """ + U = generate_random_unitary(num_qubits) + + det = np.linalg.det(U) + U = U / det**(1 / U.shape[0]) + + return U \ No newline at end of file diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.py index b178fd3..12f8f1d 100644 --- a/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.py +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.py @@ -22,7 +22,6 @@ __all__ = ["TwoQubitDecomposition"] -import cmath from collections.abc import Sequence import math import numpy as np @@ -34,6 +33,7 @@ from quick.circuit.gate_matrix import RZ, CX from quick.primitives import Operator from quick.synthesis.gate_decompositions.one_qubit_decomposition import OneQubitDecomposition +from quick.synthesis.gate_decompositions.two_qubit_decomposition.utils import u4_to_su4 from quick.synthesis.gate_decompositions.two_qubit_decomposition.weyl import TwoQubitWeylDecomposition from quick.synthesis.unitarypreparation import UnitaryPreparation @@ -43,6 +43,8 @@ """ Hardcoded basis gates for the KAK decomposition using the CX gate as the basis. """ +CX_BASIS = TwoQubitWeylDecomposition(CX.data) + Q0L = np.array([ [0.5+0.5j, 0.5-0.5j], [-0.5-0.5j, 0.5-0.5j] @@ -142,25 +144,35 @@ class TwoQubitDecomposition(UnitaryPreparation): """ `quick.synthesis.unitarypreparation.TwoQubitDecomposition` is the class for decomposing two-qubit unitary matrices into one qubit - quantum gates and CX gates. + quantum gates and a fixed super-controlled basis gate, which in this + class we hardcode to be the CX gate. Notes ----- - The decomposition is based on the KAK decomposition, which decomposes a 2-qubit unitary matrix - into a sequence of three unitary matrices, each of which is a product of one-qubit gates and a - CX gate. + The decomposition is based on the KAK decomposition, which decomposes a U(4) + matrix into + + .. math:: + U = (K_1^l \otimes K_1^r) \cdot A(a, b, c) \cdot (K_2^l \otimes K_2^r) \cdot e^{i \phi} + + where :math:`A(a, b, c) = e^{(ia X \otimes X + ib Y \otimes Y + ic Z \otimes Z)}`, + :math:`K_1^l, K_1^r, K_2^l, K_2^r` are single-qubit unitaries, and :math:`\phi` is + the global phase. + + The non-local part A(a, b, c) can be expressed in terms of the basis gate + ~A(pi/4, b, 0) (super-controlled basis) using at most 3 uses of the basis gate + and some one-qubit gates. - The up to diagonal decomposition of two qubit unitaries into the product of a diagonal gate - and another unitary gate can be represented by two CX gates instead of the usual three. - This can be used when neighboring gates commute with the diagonal to potentially reduce - overall CX count. + For certain unitaries, we can use fewer basis gates. If the target unitary + is locally equivalent to ~A(0, 0, 0) then we can use 0 basis gates. If the + target unitary is locally equivalent to the basis gate then we can use 1 + basis gate. If the target unitary is locally equivalent to ~A(x, y, 0) + then we can use 2 basis gates. - To use the up to diagonal decomposition, the `apply_unitary_up_to_diagonal` method can be used. + To use the up to diagonal decomposition, the `apply_unitary_up_to_diagonal` + method can be used. - For more information on KAK decomposition, refer to the following paper: - [1] Vidal, Dawson. - A Universal Quantum Circuit for Two-qubit Transformations with 3 CNOT Gates (2003) - https://arxiv.org/pdf/quant-ph/0307177 + https://arxiv.org/pdf/1811.12926 (Passage between (B6) and (B7)) Parameters ---------- @@ -173,6 +185,8 @@ class TwoQubitDecomposition(UnitaryPreparation): The quantum circuit framework. `one_qubit_decomposition` : quick.synthesis.gate_decompositions.OneQubitDecomposition The one-qubit decomposition class. + `decompositions` : list[callable] + The list of decomposition functions. Raises ------ @@ -192,32 +206,20 @@ def __init__( super().__init__(output_framework) self.one_qubit_decomposition = OneQubitDecomposition(output_framework) - - @staticmethod - def u4_to_su4(u4: NDArray[np.complex128]) -> tuple[NDArray[np.complex128], float]: - """ Convert a general 4x4 unitary matrix to a SU(4) matrix. - - Parameters - ---------- - `u4` : NDArray[np.complex128] - The 4x4 unitary matrix. - - Returns - ------- - `su4` : NDArray[np.complex128] - The 4x4 special unitary matrix. - `phase_factor` : float - The phase factor. - """ - phase_factor = np.conj(np.linalg.det(u4) ** (-1 / u4.shape[0])) - su4: NDArray[np.complex128] = u4 / phase_factor - return su4, cmath.phase(phase_factor) + self.decompositions = [ + self._decomp0, + self._decomp1, + self._decomp2_supercontrolled, + self._decomp3_supercontrolled, + ] @staticmethod def traces(target: TwoQubitWeylDecomposition) -> list[complex]: """ Calculate the expected traces $|Tr(U \cdot U_{target}^\dagger)|$ for different number of basis gates. + https://arxiv.org/pdf/1811.12926 (B3) + Parameters ---------- `target` : quick.synthesis.gate_decompositions.two_qubit_decomposition.weyl.TwoQubitWeylDecomposition @@ -280,7 +282,6 @@ def real_trace_transform(U: NDArray[np.complex128]) -> NDArray[np.complex128]: U[0, 0] * U[3, 3] ) - # Initialize theta and phi (they can be arbitrary) theta = 0 phi = 0 @@ -320,8 +321,11 @@ def trace_to_fidelity(trace: complex) -> float: return (4 + abs(trace) ** 2) / 20 @staticmethod - def _decomp0(weyl_decomposition: TwoQubitWeylDecomposition) -> tuple[NDArray[np.complex128], NDArray[np.complex128]]: - """ Decompose target ~Ud(x, y, z) with 0 uses of the basis gate. + def _decomp0(weyl_decomposition: TwoQubitWeylDecomposition) -> tuple[ + NDArray[np.complex128], + NDArray[np.complex128] + ]: + """ Decompose target ~A(x, y, z) with 0 uses of the basis gate. Result Ur has trace: ..math:: @@ -338,9 +342,9 @@ def _decomp0(weyl_decomposition: TwoQubitWeylDecomposition) -> tuple[NDArray[np. Returns ------- `U0r` : NDArray[np.complex128] - The right unitary matrix. + One qubit unitary gate. `U0l` : NDArray[np.complex128] - The left unitary matrix. + One qubit unitary gate. """ U0l = weyl_decomposition.K1l.dot(weyl_decomposition.K2l) U0r = weyl_decomposition.K1r.dot(weyl_decomposition.K2r) @@ -353,15 +357,13 @@ def _decomp1(weyl_decomposition: TwoQubitWeylDecomposition) -> tuple[ NDArray[np.complex128], NDArray[np.complex128] ]: - """ Decompose target ~Ud(x, y, z) with 1 uses of the basis gate ~Ud(a, b, c). + """ Decompose target ~A(x, y, z) with 1 uses of the basis gate ~A(a, b, c). Result Ur has trace: .. math:: |Tr(Ur.U_{target}^\dagger)| = 4|\cos(x-a) \cos(y-b) \cos(z-c) + i \sin(x-a) \sin(y-b) \sin(z-c)| - which is optimal for all targets and bases with z==0 or c==0. - Parameters ---------- `weyl_decomposition` : quick.synthesis.gate_decompositions.two_qubit_decomposition.weyl.TwoQubitWeylDecomposition @@ -370,21 +372,18 @@ def _decomp1(weyl_decomposition: TwoQubitWeylDecomposition) -> tuple[ Returns ------- `U1r` : NDArray[np.complex128] - The right unitary matrix. + One qubit unitary gate. `U1l` : NDArray[np.complex128] - The left unitary matrix. + One qubit unitary gate. `U0r` : NDArray[np.complex128] - The right unitary matrix. + One qubit unitary gate. `U0l` : NDArray[np.complex128] - The left unitary matrix. + One qubit unitary gate. """ - # Use the basis gate as the closest reflection in the Weyl chamber - basis = TwoQubitWeylDecomposition(CX.data) - - U0l = weyl_decomposition.K1l.dot(basis.K1l.T.conj()) - U0r = weyl_decomposition.K1r.dot(basis.K1r.T.conj()) - U1l = basis.K2l.T.conj().dot(weyl_decomposition.K2l) - U1r = basis.K2r.T.conj().dot(weyl_decomposition.K2r) + U0l = weyl_decomposition.K1l.dot(CX_BASIS.K1l.T.conj()) + U0r = weyl_decomposition.K1r.dot(CX_BASIS.K1r.T.conj()) + U1l = CX_BASIS.K2l.T.conj().dot(weyl_decomposition.K2l) + U1r = CX_BASIS.K2r.T.conj().dot(weyl_decomposition.K2r) return U1r, U1l, U0r, U0l @@ -397,24 +396,14 @@ def _decomp2_supercontrolled(weyl_decomposition: TwoQubitWeylDecomposition) -> t NDArray[np.complex128], NDArray[np.complex128] ]: - """ Decompose target ~Ud(x, y, z) with 2 uses of the basis gate. + """ Decompose target ~A(x, y, z) with 2 uses of the super-controlled basis gate. - For supercontrolled basis ~Ud(pi/4, b, 0), all b, result Ur has trace + For supercontrolled basis ~A(pi/4, b, 0), all b, result Ur has trace .. math:: |Tr(Ur.U_{target}^\dagger)| = 4 \cos(z) - which is the optimal approximation for basis of CX-class ``~Ud(pi/4, 0, 0)`` - or DCX-class ``~Ud(pi/4, pi/4, 0)`` and any target. - - Notes - ----- - May be sub-optimal for b!=0 (e.g. there exists exact decomposition for any target using B - ``B~Ud(pi/4, pi/8, 0)``, but not this decomposition.) - This is an exact decomposition for supercontrolled basis and target ``~Ud(x, y, 0)``. - No guarantees for non-supercontrolled basis. - Parameters ---------- `weyl_decomposition` : quick.synthesis.gate_decompositions.two_qubit_decomposition.weyl.TwoQubitWeylDecomposition @@ -423,17 +412,17 @@ def _decomp2_supercontrolled(weyl_decomposition: TwoQubitWeylDecomposition) -> t Returns ------- `U2r` : NDArray[np.complex128] - The right unitary matrix. + One qubit unitary gate. `U2l` : NDArray[np.complex128] - The left unitary matrix. + One qubit unitary gate. `U1r` : NDArray[np.complex128] - The right unitary matrix. + One qubit unitary gate. `U1l` : NDArray[np.complex128] - The left unitary matrix. + One qubit unitary gate. `U0r` : NDArray[np.complex128] - The right unitary matrix. + One qubit unitary gate. `U0l` : NDArray[np.complex128] - The left unitary matrix. + One qubit unitary gate. """ U0l = weyl_decomposition.K1l.dot(Q0L) U0r = weyl_decomposition.K1r.dot(Q0R) @@ -455,19 +444,7 @@ def _decomp3_supercontrolled(weyl_decomposition: TwoQubitWeylDecomposition) -> t NDArray[np.complex128], NDArray[np.complex128] ]: - """ Decompose a 2-qubit unitary matrix into a sequence of one-qubit gates and CX gates. - The decomposition uses three CX gates. - - Notes - ----- - The decomposition is based on the KAK decomposition, which decomposes a 2-qubit unitary matrix - into a sequence of three unitary matrices, each of which is a product of one-qubit gates and a - CX gate. - - For more information on KAK decomposition, refer to the following paper: - - Vidal, Dawson. - A Universal Quantum Circuit for Two-qubit Transformations with 3 CNOT Gates (2003) - https://arxiv.org/pdf/quant-ph/0307177 + """ Decompose target ~A(x, y, z) with 3 uses of the super-controlled basis gate. Parameters ---------- @@ -538,8 +515,6 @@ def apply_unitary( ----- >>> circuit = two_qubit_decomposition.apply_unitary(circuit, unitary, qubit_indices) """ - # Cast the qubit indices to a list if it is an integer - # Note this is only to inform pylance that `qubit_indices` is a list if isinstance(qubit_indices, int): qubit_indices = [qubit_indices] @@ -552,39 +527,40 @@ def apply_unitary( if unitary.num_qubits != 2: raise ValueError("Two-qubit decomposition requires a 4x4 unitary matrix.") - decomposition_functions = [ - self._decomp0, - self._decomp1, - self._decomp2_supercontrolled, - self._decomp3_supercontrolled, - ] - - # Hardcoded global phase for supercontrolled basis ~Ud(pi/4, b, 0), - # all b when using CX as KAK basis - cx_basis_global_phase = -np.pi/4 - target_decomposed = TwoQubitWeylDecomposition(unitary.data) # Calculate the expected fidelities for different number of basis gates + # By default, we choose the decomposition with fidelity of 1.0, however + # if a lower fidelity is permitted we can choose a decomposition with + # fewer basis gates traces = self.traces(target_decomposed) expected_fidelities = [TwoQubitDecomposition.trace_to_fidelity(traces[i]) for i in range(4)] - best_num_basis = int(np.argmax(expected_fidelities)) - decomposition = decomposition_functions[best_num_basis](target_decomposed) - + cx_basis_global_phase = -np.pi/4 overall_global_phase = target_decomposed.global_phase - best_num_basis * cx_basis_global_phase + # Handling the global phase for the up to diagonal case which uses 2 CX gates if best_num_basis == 2: overall_global_phase += np.pi + decomposition = self.decompositions[best_num_basis](target_decomposed) + for i in range(best_num_basis): - self.one_qubit_decomposition.apply_unitary(circuit, decomposition[2 * i], qubit_indices[0]) - self.one_qubit_decomposition.apply_unitary(circuit, decomposition[2 * i + 1], qubit_indices[1]) + self.one_qubit_decomposition.apply_unitary( + circuit, decomposition[2 * i], qubit_indices[0] + ) + self.one_qubit_decomposition.apply_unitary( + circuit, decomposition[2 * i + 1], qubit_indices[1] + ) circuit.CX(qubit_indices[0], qubit_indices[1]) - self.one_qubit_decomposition.apply_unitary(circuit, decomposition[2 * best_num_basis], qubit_indices[0]) - self.one_qubit_decomposition.apply_unitary(circuit, decomposition[2 * best_num_basis + 1], qubit_indices[1]) + self.one_qubit_decomposition.apply_unitary( + circuit, decomposition[2 * best_num_basis], qubit_indices[0] + ) + self.one_qubit_decomposition.apply_unitary( + circuit, decomposition[2 * best_num_basis + 1], qubit_indices[1] + ) circuit.GlobalPhase(overall_global_phase) @@ -624,7 +600,11 @@ def apply_unitary_up_to_diagonal( Usage ----- - >>> circuit, diagonal = two_qubit_decomposition.apply_unitary_up_to_diagonal(circuit, unitary, qubit_indices) + >>> circuit, diagonal = two_qubit_decomposition.apply_unitary_up_to_diagonal( + ... circuit, + ... unitary, + ... qubit_indices + ... ) """ if isinstance(qubit_indices, int): qubit_indices = [qubit_indices] @@ -638,7 +618,7 @@ def apply_unitary_up_to_diagonal( if unitary.num_qubits != 2: raise ValueError("Two-qubit decomposition requires a 4x4 unitary matrix.") - su4, phase = TwoQubitDecomposition.u4_to_su4(unitary.data) + su4, phase = u4_to_su4(unitary.data) diagonal = TwoQubitDecomposition.real_trace_transform(su4) mapped_su4 = diagonal @ su4 diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/utils.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/utils.py new file mode 100644 index 0000000..02128b9 --- /dev/null +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/utils.py @@ -0,0 +1,60 @@ +# Copyright 2023-2025 Qualition Computing LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://github.com/Qualition/quick/blob/main/LICENSE +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" Utility functions for two-qubit gate decompositions. +""" + +from __future__ import annotations + +__all__ = ["u4_to_su4"] + +import cmath +import numpy as np +from numpy.typing import NDArray +import scipy.linalg # type: ignore + + +def u4_to_su4(U: NDArray[np.complex128]) -> tuple[NDArray[np.complex128], float]: + """ Convert a U(4) matrix to an SU(4) matrix by removing the global phase + such that the determinant is 1. + + Parameters + ---------- + `U` : NDArray[np.complex128] + The input U(4) matrix. + + Returns + ------- + `SU4` : NDArray[np.complex128] + The resulting SU(4) matrix. + `global_phase` : float + The global phase that was removed. + + Usage + ----- + >>> SU4, global_phase = u4_to_su4(np.eye(4)) + """ + # We need to cast to complex to avoid NaN errors + U = np.asarray(U, dtype=np.complex128) + + # The code fails with np.linalg.det + U_det = scipy.linalg.det(U) + + # For general U_N we must take to power of -1/U.shape[0] + # but since this implementation is only used for U4 we + # omit this calculation and use hardcoded -1/4 + SU4 = U * U_det ** (-0.25) + global_phase = cmath.phase(U_det) / 4 + + return SU4, global_phase \ No newline at end of file diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py index 10032f7..eaac37a 100644 --- a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py @@ -21,12 +21,9 @@ from __future__ import annotations __all__ = [ - "transform_to_magic_basis", + "M", + "M_DAGGER", "weyl_coordinates", - "partition_eigenvalues", - "remove_global_phase", - "diagonalize_unitary_complex_symmetric", - "decompose_two_qubit_product_gate", "TwoQubitWeylDecomposition" ] @@ -38,28 +35,52 @@ import scipy.linalg # type: ignore import warnings -""" Define the M matrix from section III to -tranform the unitary matrix into the magic basis: -https://arxiv.org/pdf/quant-ph/0308006 +from quick.synthesis.gate_decompositions.two_qubit_decomposition.utils import u4_to_su4 + +""" Define the magic basis matrix from eq(3): +https://arxiv.org/pdf/quant-ph/9703041 + +There is another well-known definition: + +M = 1/np.sqrt(2) * np.array([ + [1, 0, 0, 1j], + [0, 1j, 1, 0], + [0, 1j, -1, 0], + [1, 0, 0, -1j] +], dtype=complex) + +But as far as we can tell, this choice does not affect the +decomposition. The basis M and its adjoint are stored individually unnormalized, -but such that their matrix multiplication is still the identity -This is because they are only used in unitary transformations -(so it's safe to do so), and `sqrt(0.5)` is not exactly representable -in floating point. - -Doing it this way means that every element of the matrix is stored exactly -correctly, and the multiplication is exactly the identity rather than -differing by 1ULP. +but given each has a factor of `sqrt(0.5)`, once we multiply them +together the factor becomes 0.5, so we directly store the factor +0.5 in the adjoint matrix. + +This is done to minimize the floating-point errors that arise +in the M^2 calculation which cause issues for certain OS. + +Notes +----- +The following use our definition of the magic basis: +- https://arxiv.org/pdf/quant-ph/0308006 +- https://arxiv.org/pdf/quant-ph/0011050 +- https://arxiv.org/pdf/0806.4015 +- https://arxiv.org/pdf/quant-ph/0405046 + +And the following use the other definition: +- https://arxiv.org/pdf/cond-mat/0609750 +- https://arxiv.org/pdf/quant-ph/0209120 +- https://arxiv.org/pdf/quant-ph/0507171 """ -M_UNNORMALIZED = np.array([ +M = np.array([ [1, 1j, 0, 0], [0, 0, 1j, 1], [0, 0, 1j, -1], [1, -1j, 0, 0] ], dtype=np.complex128) -M_UNNORMALIZED_DAGGER = 0.5 * M_UNNORMALIZED.conj().T +M_DAGGER = 0.5 * M.conj().T # Pauli matrices in magic basis X_MAGIC_BASIS = np.array([ @@ -88,23 +109,24 @@ def transform_to_magic_basis( U: NDArray[np.complex128], reverse: bool = False ) -> NDArray[np.complex128]: - """ Transform the 4x4 matrix `U` into the magic basis. + """ Transform the SU4 matrix `U` into the magic basis. - Notes - ----- - This method internally uses non-normalized versions of the basis - to minimize (but not eliminate) the floating-point errors that arise - during the transformation. + ..math:: + U_{magic\_basis} = M \cdot U \cdot M^\dagger - This implementation is based on the following paper: - [1] Vatan, Williams. - Optimal Quantum Circuits for General Two-Qubit Gates (2004). - https://arxiv.org/abs/quant-ph/0308006 + for the forward transformation, and + + ..math:: + U_{magic\_basis} = M^\dagger \cdot U \cdot M + + for the reverse transformation. Note that if + we do forward followed by reverse transformation + or vice versa, we get back the original matrix. Parameters ---------- `U` : NDArray[np.complex128] - The input 4-by-4 matrix to be transformed. + The SU4 matrix to be transformed. `reverse` : bool, optional, default=False If True, the transformation is done in the reverse direction. @@ -115,15 +137,16 @@ def transform_to_magic_basis( Usage ----- - >>> U_magic = transform_to_magic_basis(np.eye(4)) + >>> U_magic_basi = transform_to_magic_basis(np.eye(4)) """ if reverse: - return M_UNNORMALIZED_DAGGER @ U @ M_UNNORMALIZED - return M_UNNORMALIZED @ U @ M_UNNORMALIZED_DAGGER + return M_DAGGER @ U @ M + return M @ U @ M_DAGGER def weyl_coordinates(U: NDArray[np.complex128]) -> NDArray[np.float64]: """ Calculate the Weyl coordinates for a given two-qubit unitary matrix. - This is used for unit-testing the Weyl decomposition. + This function is used for unit-testing Weyl decomposition, and certain + predicates in `quick.predicates`. Notes ----- @@ -146,10 +169,10 @@ def weyl_coordinates(U: NDArray[np.complex128]) -> NDArray[np.float64]: ----- >>> weyl_coordinates = weyl_coordinates(np.eye(4)) """ - U /= scipy.linalg.det(U) ** (0.25) + U = u4_to_su4(U)[0] U_magic_basis = transform_to_magic_basis(U, reverse=True) - # We only need the eigenvalues of `M2 = Up.T @ Up` here, not the full diagonalization + # We only need the eigenvalues of `M_squared = Up.T @ Up` here, not the full diagonalization D = scipy.linalg.eigvals(U_magic_basis.T @ U_magic_basis) d = -np.angle(D) / 2 @@ -380,7 +403,7 @@ def decompose_two_qubit_product_gate( R /= np.sqrt(R_det) # Extract the left component - temp = np.kron(np.eye(2), R.T.conj()) + temp = np.kron(np.eye(2), R.conj().T) temp = special_unitary_matrix.dot(temp) L = temp[::2, ::2] L_det = L[0, 0] * L[1, 1] - L[0, 1] * L[1, 0] @@ -404,7 +427,21 @@ def decompose_two_qubit_product_gate( class TwoQubitWeylDecomposition: """ Decompose a two-qubit unitary matrix into the Weyl coordinates and - the product of two single-qubit unitaries. + the product of two single-qubit unitaries via KAK decomposition. + + .. math:: + U = (K_1^l \otimes K_1^r) \cdot A(a, b, c) \cdot (K_2^l \otimes K_2^r) \cdot e^{i \phi} + + where :math:`A(a, b, c) = e^{(ia X \otimes X + ib Y \otimes Y + ic Z \otimes Z)}`, + :math:`K_1^l, K_1^r, K_2^l, K_2^r` are single-qubit unitaries, and :math:`\phi` is + the global phase. + + Notes + ----- + This implementation is based on the following paper: + [1] Vatan, Williams. + Optimal Quantum Circuits for General Two-Qubit Gates (2004). + https://arxiv.org/abs/quant-ph/0308006 Parameters ---------- @@ -414,11 +451,11 @@ class TwoQubitWeylDecomposition: Attributes ---------- `a` : np.float64 - The first Weyl coordinate. + The multiplier for XX term in the canonical gate. `b` : np.float64 - The second Weyl coordinate. + The multiplier for YY term in the canonical gate. `c` : np.float64 - The third Weyl coordinate. + The multiplier for ZZ term in the canonical gate. `K1l` : NDArray[np.complex128] The left component of the first single-qubit unitary. `K1r` : NDArray[np.complex128] @@ -430,119 +467,72 @@ class TwoQubitWeylDecomposition: `global_phase` : float The global phase. """ - def __init__(self, unitary_matrix: NDArray[np.complex128]) -> None: + def __init__( + self, + unitary_matrix: NDArray[np.complex128] + ) -> None: """ Initialize a `quick.synthesis.gate_decompositions.two_qubit_decomposition.weyl. TwoQubitWeylDecomposition` instance. """ - self.a, self.b, self.c, self.K1l, self.K1r, self.K2l, self.K2r, self.global_phase = self.decompose_unitary( - unitary_matrix - ) - - @staticmethod - def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ - np.float64, - np.float64, - np.float64, - NDArray[np.complex128], - NDArray[np.complex128], - NDArray[np.complex128], - NDArray[np.complex128], - float - ]: - """ Decompose a two-qubit unitary matrix into the Weyl coordinates and the - product of two single-qubit unitaries. - - Notes - ----- - M2 diagnolization may be wrong due to floating point errors, but it will work - correctly in Linux. Should you encounter a failure in the code, and the result - fails to encode the correct unitary matrix, please report it at - https://github.com/Qualition/quick/issues/11 - - with the unitary matrix that caused the failure. - - Parameters - ---------- - `unitary_matrix` : NDArray[np.complex128] - The input 4-by-4 unitary matrix. - - Returns - ------- - `a` : np.float64 - The first Weyl coordinate. - `b` : np.float64 - The second Weyl coordinate. - `c` : np.float64 - The third Weyl coordinate. - `K1l` : NDArray[np.complex128] - The left component of the first single-qubit unitary. - `K1r` : NDArray[np.complex128] - The right component of the first single-qubit unitary. - `K2l` : NDArray[np.complex128] - The left component of the second single-qubit unitary. - `K2r` : NDArray[np.complex128] - The right component of the second single-qubit unitary. - `global_phase` : float - The global phase. - - Raises - ------ - ValueError - - If the determinant of the right or left component is - not in the expected range. - - If the decomposition fails due to a deviation from the - expected unitary matrix. - - Usage - ----- - >>> a, b, c, K1l, K1r, K2l, K2r, global_phase = TwoQubitWeylDecomposition.decompose_unitary(np.eye(4)) - """ - # Make U be in SU(4) - U = np.array(unitary_matrix, dtype=np.complex128, copy=True) - U_det = scipy.linalg.det(U) - U *= U_det ** (-0.25) - global_phase = cmath.phase(U_det) / 4 + U, global_phase = u4_to_su4(unitary_matrix) + # Transforming U into the magic basis has two remarkable properties: + # 1) If U is in SO(4) (real, U.T=U, det U=1) then U_magic_basis is in SU(2)xSU(2) + # which means we can decompose it into two single-qubit unitaries + # 2) The magic basis diagonalizes the canonical gate A (as in A from KAK) U_magic_basis = transform_to_magic_basis(U, reverse=True) - M2 = np.round(U_magic_basis.T.dot(U_magic_basis), decimals=15) - D, P = diagonalize_unitary_complex_symmetric(M2) + # Construct type AI global Cartan involution + # To minimize the floating point issues arising in different OS + # we round M^2 + # This is a fix for known issues in Windows and MacOS + # See https://github.com/Qualition/quick/issues/11 + M_squared = np.round(U_magic_basis.T.dot(U_magic_basis), decimals=15) + + # Diagonalize M^2 into D and P where D is complex diagonal + # and P is real-symmetric unitary + # M^2 = P.diag(D).P^T + D, P = diagonalize_unitary_complex_symmetric(M_squared) # Given P is a real-symmetric unitary matrix we only use transpose - if not np.allclose(P.dot(np.diag(D)).dot(P.T), M2, rtol=0, atol=1e-13): + if not np.allclose(P.dot(np.diag(D)).dot(P.T), M_squared, rtol=0, atol=1e-13): warnings.warn( - "Failed to diagonalize M2." + "Failed to diagonalize M_squared. " "Kindly report this at https://github.com/Qualition/quick/issues/11: " f"U: {U}" ) - d = -np.angle(D) / 2 - d[3] = -d[0] - d[1] - d[2] - weyl_coordinates = np.mod((d[:3] + d[3]) / 2, PI_DOUBLE) + # We want M which is P.sqrt(diag(D)).P^T so we take the square root of D + D_sqrt = -np.angle(D) / 2 + D_sqrt[3] = -D_sqrt[0] - D_sqrt[1] - D_sqrt[2] + weyl_coordinates = np.mod((D_sqrt[:3] + D_sqrt[3]) / 2, PI_DOUBLE) # Reorder the eigenvalues to get in the Weyl chamber weyl_coordinates_temp = np.mod(weyl_coordinates, PI2) - np.minimum(weyl_coordinates_temp, PI2 - weyl_coordinates_temp, weyl_coordinates_temp) + weyl_coordinates_temp = np.minimum(weyl_coordinates_temp, PI2 - weyl_coordinates_temp) order = np.argsort(weyl_coordinates_temp)[[1, 2, 0]] weyl_coordinates = weyl_coordinates[order] - d[:3] = d[order] + D_sqrt[:3] = D_sqrt[order] P[:, :3] = P[:, order] - # Fix the sign of P to be in SO(4) + # Sometimes computing the diagonalization of M_squared gives a P with determinant -1 as + # opposed to +1 which results in P not being in SO(4) + # To fix this we can negate the last column of P if np.real(scipy.linalg.det(P)) < 0: - P[:, -1] = -P[:, -1] + P[:, -1] *= -1 # Find K1, K2 so that U = K1.A.K2, with K being product of single-qubit unitaries - K1 = transform_to_magic_basis(U_magic_basis @ P @ np.diag(np.exp(1j * d))) + # SU2 x SU2 + K1 = transform_to_magic_basis(U_magic_basis @ P @ np.diag(np.exp(1j * D_sqrt))) K2 = transform_to_magic_basis(P.T) K1l, K1r, phase_l = decompose_two_qubit_product_gate(K1) K2l, K2r, phase_r = decompose_two_qubit_product_gate(K2) global_phase += phase_l + phase_r - K1l = K1l.copy() - - # Flip into Weyl chamber + # Flip into Weyl chamber such that pi/4 >= a >= b >= |c| >= 0 + # This is because the maximal entangling power of A is symmetric + # around pi/4 and pi/2-periodic in a, b, and c if weyl_coordinates[0] > PI2: weyl_coordinates[0] -= 3 * PI2 K1l = K1l.dot(Y_MAGIC_BASIS) @@ -588,4 +578,11 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ a, b, c = weyl_coordinates[1], weyl_coordinates[0], weyl_coordinates[2] - return a, b, c, K1l, K1r, K2l, K2r, global_phase \ No newline at end of file + self.a = a + self.b = b + self.c = c + self.K1l = K1l + self.K1r = K1r + self.K2l = K2l + self.K2r = K2r + self.global_phase = global_phase \ No newline at end of file diff --git a/quick/synthesis/statepreparation/isometry.py b/quick/synthesis/statepreparation/isometry.py index 8494a8c..f4a8616 100644 --- a/quick/synthesis/statepreparation/isometry.py +++ b/quick/synthesis/statepreparation/isometry.py @@ -29,10 +29,10 @@ if TYPE_CHECKING: from quick.circuit import Circuit -from quick.circuit.circuit_utils import extract_single_qubits_and_diagonal +from quick.circuit.utils import extract_single_qubits_and_diagonal from quick.primitives import Statevector from quick.synthesis.statepreparation import StatePreparation -from quick.synthesis.statepreparation.statepreparation_utils import ( +from quick.synthesis.statepreparation.utils import ( a, b, k_s, diff --git a/quick/synthesis/statepreparation/mottonen.py b/quick/synthesis/statepreparation/mottonen.py index 05c7ec6..91fce8f 100644 --- a/quick/synthesis/statepreparation/mottonen.py +++ b/quick/synthesis/statepreparation/mottonen.py @@ -28,7 +28,7 @@ from quick.circuit import Circuit from quick.primitives import Statevector from quick.synthesis.statepreparation import StatePreparation -from quick.synthesis.statepreparation.statepreparation_utils import ( +from quick.synthesis.statepreparation.utils import ( compute_alpha_y, compute_alpha_z, compute_control_indices, compute_m ) diff --git a/quick/synthesis/statepreparation/shende.py b/quick/synthesis/statepreparation/shende.py index fcad943..1f02d91 100644 --- a/quick/synthesis/statepreparation/shende.py +++ b/quick/synthesis/statepreparation/shende.py @@ -28,7 +28,7 @@ from quick.circuit import Circuit from quick.primitives import Statevector from quick.synthesis.statepreparation import StatePreparation -from quick.synthesis.statepreparation.statepreparation_utils import rotations_to_disentangle +from quick.synthesis.statepreparation.utils import rotations_to_disentangle class Shende(StatePreparation): diff --git a/quick/synthesis/statepreparation/statepreparation_utils.py b/quick/synthesis/statepreparation/utils.py similarity index 100% rename from quick/synthesis/statepreparation/statepreparation_utils.py rename to quick/synthesis/statepreparation/utils.py diff --git a/quick/synthesis/unitarypreparation/shannon_decomposition.py b/quick/synthesis/unitarypreparation/shannon_decomposition.py index ca5a07d..0382cba 100644 --- a/quick/synthesis/unitarypreparation/shannon_decomposition.py +++ b/quick/synthesis/unitarypreparation/shannon_decomposition.py @@ -29,7 +29,7 @@ import quick if TYPE_CHECKING: from quick.circuit import Circuit -from quick.circuit.circuit_utils import decompose_multiplexor_rotations +from quick.circuit.utils import decompose_multiplexor_rotations from quick.predicates import is_hermitian_matrix from quick.primitives import Operator from quick.synthesis.unitarypreparation import UnitaryPreparation diff --git a/quick/synthesis/unitarypreparation/unitarypreparation_utils.py b/quick/synthesis/unitarypreparation/utils.py similarity index 100% rename from quick/synthesis/unitarypreparation/unitarypreparation_utils.py rename to quick/synthesis/unitarypreparation/utils.py diff --git a/tests/circuit/test_circuit_base.py b/tests/circuit/test_circuit_base.py index c3634c8..9aa9551 100644 --- a/tests/circuit/test_circuit_base.py +++ b/tests/circuit/test_circuit_base.py @@ -650,34 +650,15 @@ def test_get_depth( # Define the `quick.circuit.Circuit` instance circuit = circuit_framework(4) - # Apply the MCX gate circuit.MCX([0, 1], [2, 3]) - # Get the depth of the circuit, and ensure it is correct + assert circuit.get_depth() == 1 + + circuit.transpile() depth = circuit.get_depth() assert depth == 25 - @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) - def test_get_width( - self, - circuit_framework: type[Circuit] - ) -> None: - """ Test the width of the circuit. - - Parameters - ---------- - `circuit_framework`: type[quick.circuit.Circuit] - The circuit framework to test. - """ - # Define the `quick.circuit.Circuit` instance - circuit = circuit_framework(4) - - # Get the width of the circuit, and ensure it is correct - width = circuit.get_width() - - assert width == 4 - @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_get_instructions( self, diff --git a/tests/circuit/test_uniformly_controlled_gates.py b/tests/circuit/test_uniformly_controlled_gates.py index 8b32c06..7a82e56 100644 --- a/tests/circuit/test_uniformly_controlled_gates.py +++ b/tests/circuit/test_uniformly_controlled_gates.py @@ -343,7 +343,7 @@ def test_Multiplexor( `multiplexor_simplification` : bool, optional, default=True Determines if the multiplexor is simplified. """ - from quick.circuit.circuit_utils import ( + from quick.circuit.utils import ( extract_single_qubits_and_diagonal, simplify ) diff --git a/tests/predicates/__init__.py b/tests/predicates/__init__.py index ef97a38..0aaa5d2 100644 --- a/tests/predicates/__init__.py +++ b/tests/predicates/__init__.py @@ -12,24 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -__all__ = [ - "test_is_square_matrix", - "test_is_diagonal_matrix", - "test_is_symmetric_matrix", - "test_is_identity_matrix", - "test_is_unitary_matrix", - "test_is_hermitian_matrix", - "test_is_positive_semidefinite_matrix", - "test_is_isometry" -] +__all__ = ["TestPredicates"] -from tests.predicates.test_predicates import ( - test_is_square_matrix, - test_is_diagonal_matrix, - test_is_symmetric_matrix, - test_is_identity_matrix, - test_is_unitary_matrix, - test_is_hermitian_matrix, - test_is_positive_semidefinite_matrix, - test_is_isometry -) +from tests.predicates.test_predicates import TestPredicates \ No newline at end of file diff --git a/tests/predicates/test_predicates.py b/tests/predicates/test_predicates.py index b46b67c..927debe 100644 --- a/tests/predicates/test_predicates.py +++ b/tests/predicates/test_predicates.py @@ -14,16 +14,23 @@ from __future__ import annotations +__all__ = ["TestPredicates"] + import numpy as np from numpy.typing import NDArray import pytest from scipy.stats import unitary_group -from quick.predicates import ( +from quick.predicates.predicates import ( is_power, is_normalized, is_statevector, is_square_matrix, + is_orthogonal_matrix, + is_real_matrix, + is_special_matrix, + is_special_orthogonal_matrix, + is_special_unitary_matrix, is_diagonal_matrix, is_symmetric_matrix, is_identity_matrix, @@ -31,378 +38,633 @@ is_hermitian_matrix, is_positive_semidefinite_matrix, is_isometry, - is_density_matrix + is_density_matrix, + is_product_matrix, + is_locally_equivalent, + is_supercontrolled ) -def test_is_power() -> None: - """ Test the `is_power()` function. +class TestPredicates: + """ `tests.predicates.TestPredicates` is the tester class for `quick.predicates` + module. """ - assert is_power(2, 2) is True - assert is_power(3, 2) is False - assert is_power(2, 4) is True + def test_is_power(self) -> None: + """ Test the `is_power()` function. + """ + assert is_power(2, 2) is True + assert is_power(3, 2) is False + assert is_power(2, 4) is True -def test_is_normalized() -> None: - """ Test the `is_normalized()` function. - """ - state = np.arange(10).astype(np.complex128) - assert is_normalized(state) is False - state = state / np.linalg.norm(state) - assert is_normalized(state) is True - -@pytest.mark.parametrize("array, system_size, expected", [ - (np.array([1, 0]), 2, True), - (np.array([0, 1]), 2, True), - (np.array([1, 0, 0]), 3, True), - (np.array([1, 2]), 2, False), - (np.array([1, 2, 3]), 3, False), - (np.array([1, 0, 0, 0]), 2, True), - (np.array([[0.5], [0.5], [0.5], [0.5]]), 2, True) -]) -def test_is_statevector( - array: NDArray[np.complex128], - system_size: int, - expected: bool - ) -> None: - """ Test the `is_statevector()` function. - - Parameters - ---------- - `array` : NDArray[np.complex128] - The input array to check if it is a statevector. - `system_size` : int - The size of the system. If it's 2, then it's a qubit system. - `expected` : bool - The expected output of the function. - """ - assert is_statevector(array, system_size) is expected + def test_is_normalized(self) -> None: + """ Test the `is_normalized()` function. + """ + state = np.arange(10).astype(np.complex128) + assert is_normalized(state) is False + state = state / np.linalg.norm(state) + assert is_normalized(state) is True -def test_is_statevector_invalid_system_size() -> None: - """ Test the `is_statevector()` function with invalid system size. + @pytest.mark.parametrize("array, system_size, expected", [ + (np.array([1, 0]), 2, True), + (np.array([0, 1]), 2, True), + (np.array([1, 0, 0]), 3, True), + (np.array([1, 2]), 2, False), + (np.array([1, 2, 3]), 3, False), + (np.array([1, 0, 0, 0]), 2, True), + (np.array([[0.5], [0.5], [0.5], [0.5]]), 2, True) + ]) + def test_is_statevector( + self, + array: NDArray[np.complex128], + system_size: int, + expected: bool + ) -> None: + """ Test the `is_statevector()` function. - Parameters - ---------- - `array` : NDArray[np.complex128] - The input array to check if it is a statevector. - """ - with pytest.raises(ValueError): - is_statevector(np.array([1, 0]), system_size=0) - -@pytest.mark.parametrize("array, expected", [ - (np.random.rand(2, 2), True), - (np.random.rand(3, 3), True), - (np.random.rand(4, 4), True), - (np.random.rand(5, 5), True), - (np.random.rand(2, 1), False), - (np.random.rand(1, 3), False), - (np.random.rand(4, 12), False), - (np.random.rand(2, 3, 3), False) -]) -def test_is_square_matrix( - array: NDArray[np.complex128], - expected: bool - ) -> None: - """ Test the `is_square_matrix()` function. - - Parameters - ---------- - `array` : NDArray[np.complex128] - The input array to check if it is a square matrix. - `expected` : bool - The expected output of the function. - """ - assert is_square_matrix(array) is expected - -@pytest.mark.parametrize("array, expected", [ - (np.diag([1, 2, 3]), True), - (np.diag([4, 5, 6, 7]), True), - (np.diag([8, 9]), True), - (np.array([ - [1, 2, 0], - [0, 3, 4], - [0, 0, 5] - ]), False), - (np.array([ - [1, 0, 0], - [2, 3, 0], - [0, 0, 4] - ]), False), - (np.array([ - [1, 0], - [1, 1] - ]), False), - (np.random.rand(2, 3, 3), False) -]) -def test_is_diagonal_matrix( - array: NDArray[np.complex128], - expected: bool - ) -> None: - """ Test the `is_diagonal_matrix()` function with diagonal matrices. - - Parameters - ---------- - `array` : NDArray[np.complex128] - The input array to check if it is a diagonal matrix. - `expected` : bool - The expected output of the function. - """ - assert is_diagonal_matrix(array) is expected - -@pytest.mark.parametrize("array, expected", [ - (np.array([ - [1, 2, 3], - [2, 4, 5], - [3, 5, 6] - ]), True), - (np.array([ - [1, 2, 3, 4], - [2, 5, 6, 7], - [3, 6, 8, 9], - [4, 7, 9, 10] - ]), True), - (np.array([ - [1, 2], - [2, 3] - ]), True), - (np.array([ - [1, 2, 3], - [4, 5, 6], - [7, 8, 9] - ]), False), - (np.array([ - [1, 2, 3, 4], - [5, 6, 7, 8], - [9, 10, 11, 12], - [13, 14, 15, 16] - ]), False), - (np.array([ - [1, 2], - [3, 4] - ]), False), - (np.random.rand(2, 3, 3), False) -]) -def test_is_symmetric_matrix( - array: NDArray[np.complex128], - expected: bool - ) -> None: - """ Test the `is_symmetric_matrix()` function with symmetric matrices. - - Parameters - ---------- - `array` : NDArray[np.complex128] - The input array to check if it is a symmetric matrix. - `expected` : bool - The expected output of the function. - """ - assert is_symmetric_matrix(array) is expected - -@pytest.mark.parametrize("array, expected", [ - (np.eye(2), True), - (np.eye(3), True), - (np.eye(4), True), - (np.eye(5), True), - (np.random.rand(2, 2), False), - (np.random.rand(3, 3), False), - (np.random.rand(4, 4), False), - (np.random.rand(3, 4), False), - (np.random.rand(2, 3, 3), False) -]) -def test_is_identity_matrix( - array: NDArray[np.complex128], - expected: bool - ) -> None: - """ Test the `is_identity_matrix()` function with identity matrices. - - Parameters - ---------- - `array` : NDArray[np.complex128] - The input array to check if it is an identity matrix. - `expected` : bool - The expected output of the function. - """ - assert is_identity_matrix(array) is expected - -@pytest.mark.parametrize("array, expected", [ - (unitary_group.rvs(2), True), - (unitary_group.rvs(3), True), - (unitary_group.rvs(4), True), - (unitary_group.rvs(5), True), - (np.random.rand(2, 2), False), - (np.random.rand(3, 3), False), - (np.random.rand(3, 4), False), - (np.random.rand(5, 2), False), - (np.random.rand(2, 3, 3), False) -]) -def test_is_unitary_matrix( - array: NDArray[np.complex128], - expected: bool - ) -> None: - """ Test the `is_unitary_matrix()` function. - - Parameters - ---------- - `array` : NDArray[np.complex128] - The input array to check if it is a unitary matrix. - `expected` : bool - The expected output of the function. - """ - assert is_unitary_matrix(array) is expected - -@pytest.mark.parametrize("array, expected", [ - (np.array([ - [1, 2 + 1j, 3], - [2 - 1j, 4, 5 + 2j], - [3, 5 - 2j, 6] - ]), True), - (np.array([ - [1, 2 + 1j], - [2 - 1j, 3] - ]), True), - (np.array([ - [1, 0], - [0, 1] - ]), True), - (np.array([ - [1, 2 + 1j, 3], - [2 + 1j, 4, 5 + 2j], - [3, 5 - 2j, 6] - ]), False), - (np.array([ - [1, 2 + 1j], - [2 + 1j, 3] - ]), False), - (np.array([ - [1, 2], - [3, 4] - ]), False), - (np.random.rand(2, 3, 3), False) -]) -def test_is_hermitian_matrix( - array: NDArray[np.complex128], - expected: bool - ) -> None: - """ Test the `is_hermitian_matrix()` function with Hermitian matrices. - - Parameters - ---------- - `array` : NDArray[np.complex128] - The input array to check if it is a Hermitian matrix. - `expected` : bool - The expected output of the function. - """ - assert is_hermitian_matrix(array) is expected - -@pytest.mark.parametrize("array, expected", [ - (np.array([ - [1, 0], - [0, 1] - ]), True), - (np.array([ - [1, 0], - [0, 0] - ]), True), - (np.array([ - [1, 2], - [2, 3] - ]), False), - (np.array([ - [1, 2], - [3, 4] - ]), False), - (np.array([ - [1, 0], - [0, -1] - ]), False), - (np.array([ - [1, 2], - [2, 1] - ]), False), - (np.random.rand(2, 3, 3), False) -]) -def test_is_positive_semidefinite_matrix( - array: NDArray[np.complex128], - expected: bool - ) -> None: - """ Test the `is_positive_semidefinite_matrix()` function with - positive semidefinite matrices. - - Parameters - ---------- - `array` : NDArray[np.complex128] - The input array to check if it is a positive semidefinite matrix. - `expected` : bool - The expected output of the function. - """ - assert is_positive_semidefinite_matrix(array) is expected - -@pytest.mark.parametrize("array, expected", [ - (np.array([ - [0.56078693+0.13052803j, -0.31583062-0.08879493j], - [0.7123732 +0.2419316j, 0.39227097-0.22401521j], - [0.30203025+0.10607406j, -0.38000351+0.7374988j] - ], dtype=np.complex128), True), - (np.array([ - [0.08849653+0.24435482j], - [-0.72166734+0.64160373j] - ], dtype=np.complex128), True), - (np.array([ - [0.02572621+0.08711405j, -0.84637795+0.52477964j], - [0.86175428-0.4991281j, -0.06470557-0.06374861j] - ]), True), - (np.array([ - [1, 1], - [1, 1] - ], dtype=np.complex128), False), - (np.random.rand(3, 3), False), - (np.random.rand(1, 2), False), - (np.array([1, 0]), False), -]) -def test_is_isometry( - array: NDArray[np.complex128], - expected: bool - ) -> None: - """ Test the `is_isometry` function with various matrices. - - Parameters - ---------- - `array` : NDArray[np.complex128] - The input array to check if it is an isometry. - `expected` : bool - The expected output of the function. - """ - assert is_isometry(array) is expected - -@pytest.mark.parametrize("array, expected", [ - (np.array([ - [0.55282636+0.j, 0.19339888+0.1369917j], - [0.19339888-0.1369917j, 0.44717364+0.j] - ], dtype=np.complex128), True), - (np.array([ - [0.19834677+0.j, 0.21077084+0.08851485j, 0.07369894-0.03780167j], - [0.21077084-0.08851485j, 0.5556912 +0.j, 0.09721694-0.13294078j], - [0.07369894+0.03780167j, 0.09721694+0.13294078j, 0.24596203+0.j] - ], dtype=np.complex128), True), - (np.array([ - [1, 2 + 1j], - [2 + 1j, 3] - ]), False), - (np.array([ - [1, 2], - [3, 4] - ]), False), - (np.random.rand(2, 3, 3), False) -]) -def test_is_density_matrix( - array: NDArray[np.complex128], - expected: bool - ) -> None: - """ Test the `is_density_matrix` function with various matrices. - - Parameters - ---------- - `array` : NDArray[np.complex128] - The input array to check if it is an isometry. - `expected` : bool - The expected out of the function. - """ - assert is_density_matrix(array) is expected \ No newline at end of file + Parameters + ---------- + `array` : NDArray[np.complex128] + The input array to check if it is a statevector. + `system_size` : int + The size of the system. If it's 2, then it's a qubit system. + `expected` : bool + The expected output of the function. + """ + assert is_statevector(array, system_size) is expected + + def test_is_statevector_invalid_system_size(self) -> None: + """ Test the `is_statevector()` function with invalid system size. + """ + with pytest.raises(ValueError): + is_statevector(np.array([1, 0]), system_size=0) + + @pytest.mark.parametrize("array, expected", [ + (np.random.rand(2, 2), True), + (np.random.rand(3, 3), True), + (np.random.rand(4, 4), True), + (np.random.rand(5, 5), True), + (np.random.rand(2, 1), False), + (np.random.rand(1, 3), False), + (np.random.rand(4, 12), False), + (np.random.rand(2, 3, 3), False) + ]) + def test_is_square_matrix( + self, + array: NDArray[np.complex128], + expected: bool + ) -> None: + """ Test the `is_square_matrix()` function. + + Parameters + ---------- + `array` : NDArray[np.complex128] + The input array to check if it is a square matrix. + `expected` : bool + The expected output of the function. + """ + assert is_square_matrix(array) is expected + + @pytest.mark.parametrize("array, expected", [ + (np.array([ + [1, 0], + [0, 1] + ]), True), + (np.array([ + [0, 1], + [1, 0] + ]), True), + (np.array([ + [1, 2], + [3, 4] + ]), False) + ]) + def test_is_orthogonal_matrix( + self, + array: NDArray[np.complex128], + expected: bool + ) -> None: + """ Test the `is_orthogonal_matrix()` function. + + Parameters + ---------- + `array` : NDArray[np.complex128] + The input array to check if it is an orthogonal matrix. + `expected` : bool + The expected output of the function. + """ + assert is_orthogonal_matrix(array) is expected + + @pytest.mark.parametrize("array, expected", [ + (np.array([1, 2, 3]), True), + (np.array([1+1j, 2, 3]), False) + ]) + def test_is_real_matrix( + self, + array: NDArray[np.complex128], + expected: bool + ) -> None: + """ Test the `is_real_matrix()` function. + + Parameters + ---------- + `array` : NDArray[np.complex128] + The input array to check if it is a real matrix. + `expected` : bool + The expected output of the function. + """ + assert is_real_matrix(array) is expected + + @pytest.mark.parametrize("array, expected", [ + (np.array([ + [1, 0], + [0, 1] + ]), True), + (np.array([ + [0, -1], + [1, 0] + ]), True), + (np.array([ + [1, 1], + [1, 1] + ]), False) + ]) + def test_is_special_matrix( + self, + array: NDArray[np.complex128], + expected: bool + ) -> None: + """ Test the `is_special_matrix()` function. + + Parameters + ---------- + `array` : NDArray[np.complex128] + The input array to check if it is a special matrix. + `expected` : bool + The expected output of the function. + """ + assert is_special_matrix(array) is expected + + @pytest.mark.parametrize("array, expected", [ + (np.array([ + [-0.24665563, 0.52914357, -0.3101837, 0.7503027], + [-0.33006535, 0.74061563, 0.27071591, -0.51890099], + [-0.6150982, -0.31194876, 0.66273065, 0.2917709], + [-0.6722143, -0.27236654, -0.62552942, -0.28750192] + ]), True), + (np.array([ + [0.0181244, 0.8330495 , 0.55219219, -0.02799671], + [0.8772641, -0.06010757, 0.03781642, -0.47472592], + [0.1060249, 0.5481764 , -0.82739822, 0.0606098], + [0.46780116, -0.04379778, 0.09521496, 0.87759782] + ]), True), + (np.array([ + [1, 2, 3], + [3, 4, 5] + ]), False), + (np.array([ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9] + ]), False) + ]) + def test_is_special_orthogonal_matrix( + self, + array: NDArray[np.float64], + expected: bool + ) -> None: + """ Test the `is_special_orthogonal_matrix()` function. + + Parameters + ---------- + `array` : NDArray[np.float64] + The input array to check if it is a special orthogonal matrix. + `expected` : bool + The expected output of the function. + """ + assert is_special_orthogonal_matrix(array) is expected + + @pytest.mark.parametrize("array, expected", [ + (np.array([ + [0.38422631+2.83471478e-01j, 0.25943494+7.65221683e-02j, 0.56281274+3.91037228e-02j, 0.54010984-2.98070487e-01j], + [0.34951286-5.51982645e-01j, -0.1755857 +1.77050098e-01j, 0.38094516+5.26882445e-01j, -0.28000149+9.92657401e-02j], + [0.01016025+4.69180869e-01j, 0.05238197-4.75213843e-01j, 0.44501419+5.84109815e-03j, -0.54594179+2.34669613e-01j], + [0.17806077+3.05336581e-01j, 0.1054005 +7.90556427e-01j, -0.00897634-2.46649688e-01j, -0.42196931+6.80411917e-04j] + ]), True), + (np.array([ + [0.05270704-0.35904018j, -0.54492897-0.41275395j, -0.14347842-0.07950459j, -0.23602791-0.56425393j], + [0.19398055+0.62964354j, -0.18457864+0.15122235j, -0.61785577-0.32821084j, 0.04745637-0.13138828j], + [0.64639778+0.04023874j, -0.3498751 +0.00456368j, 0.43706868-0.33284833j, -0.14751464+0.36679658j], + [0.12091386-0.01277784j, 0.43253418+0.407713j, 0.26579003-0.33341187j, -0.35797256-0.56740522j] + ]), False) + ]) + def test_is_special_unitary_matrix( + self, + array: NDArray[np.complex128], + expected: bool + ) -> None: + """ Test the `is_special_unitary_matrix()` function. + + Parameters + ---------- + `array` : NDArray[np.complex128] + The input array to check if it is a special unitary matrix. + `expected` : bool + The expected output of the function. + """ + assert is_special_unitary_matrix(array) is expected + + @pytest.mark.parametrize("array, expected", [ + (np.diag([1, 2, 3]), True), + (np.diag([4, 5, 6, 7]), True), + (np.diag([8, 9]), True), + (np.array([ + [1, 2, 0], + [0, 3, 4], + [0, 0, 5] + ]), False), + (np.array([ + [1, 0, 0], + [2, 3, 0], + [0, 0, 4] + ]), False), + (np.array([ + [1, 0], + [1, 1] + ]), False), + (np.random.rand(2, 3, 3), False) + ]) + def test_is_diagonal_matrix( + self, + array: NDArray[np.complex128], + expected: bool + ) -> None: + """ Test the `is_diagonal_matrix()` function with diagonal matrices. + + Parameters + ---------- + `array` : NDArray[np.complex128] + The input array to check if it is a diagonal matrix. + `expected` : bool + The expected output of the function. + """ + assert is_diagonal_matrix(array) is expected + + @pytest.mark.parametrize("array, expected", [ + (np.array([ + [1, 2, 3], + [2, 4, 5], + [3, 5, 6] + ]), True), + (np.array([ + [1, 2, 3, 4], + [2, 5, 6, 7], + [3, 6, 8, 9], + [4, 7, 9, 10] + ]), True), + (np.array([ + [1, 2], + [2, 3] + ]), True), + (np.array([ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9] + ]), False), + (np.array([ + [1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12], + [13, 14, 15, 16] + ]), False), + (np.array([ + [1, 2], + [3, 4] + ]), False), + (np.random.rand(2, 3, 3), False) + ]) + def test_is_symmetric_matrix( + self, + array: NDArray[np.complex128], + expected: bool + ) -> None: + """ Test the `is_symmetric_matrix()` function with symmetric matrices. + + Parameters + ---------- + `array` : NDArray[np.complex128] + The input array to check if it is a symmetric matrix. + `expected` : bool + The expected output of the function. + """ + assert is_symmetric_matrix(array) is expected + + @pytest.mark.parametrize("array, expected", [ + (np.eye(2), True), + (np.eye(3), True), + (np.eye(4), True), + (np.eye(5), True), + (np.random.rand(2, 2), False), + (np.random.rand(3, 3), False), + (np.random.rand(4, 4), False), + (np.random.rand(3, 4), False), + (np.random.rand(2, 3, 3), False) + ]) + def test_is_identity_matrix( + self, + array: NDArray[np.complex128], + expected: bool + ) -> None: + """ Test the `is_identity_matrix()` function with identity matrices. + + Parameters + ---------- + `array` : NDArray[np.complex128] + The input array to check if it is an identity matrix. + `expected` : bool + The expected output of the function. + """ + assert is_identity_matrix(array) is expected + + @pytest.mark.parametrize("array, expected", [ + (unitary_group.rvs(2), True), + (unitary_group.rvs(3), True), + (unitary_group.rvs(4), True), + (unitary_group.rvs(5), True), + (np.random.rand(2, 2), False), + (np.random.rand(3, 3), False), + (np.random.rand(3, 4), False), + (np.random.rand(5, 2), False), + (np.random.rand(2, 3, 3), False) + ]) + def test_is_unitary_matrix( + self, + array: NDArray[np.complex128], + expected: bool + ) -> None: + """ Test the `is_unitary_matrix()` function. + + Parameters + ---------- + `array` : NDArray[np.complex128] + The input array to check if it is a unitary matrix. + `expected` : bool + The expected output of the function. + """ + assert is_unitary_matrix(array) is expected + + @pytest.mark.parametrize("array, expected", [ + (np.array([ + [1, 2 + 1j, 3], + [2 - 1j, 4, 5 + 2j], + [3, 5 - 2j, 6] + ]), True), + (np.array([ + [1, 2 + 1j], + [2 - 1j, 3] + ]), True), + (np.array([ + [1, 0], + [0, 1] + ]), True), + (np.array([ + [1, 2 + 1j, 3], + [2 + 1j, 4, 5 + 2j], + [3, 5 - 2j, 6] + ]), False), + (np.array([ + [1, 2 + 1j], + [2 + 1j, 3] + ]), False), + (np.array([ + [1, 2], + [3, 4] + ]), False), + (np.random.rand(2, 3, 3), False) + ]) + def test_is_hermitian_matrix( + self, + array: NDArray[np.complex128], + expected: bool + ) -> None: + """ Test the `is_hermitian_matrix()` function with Hermitian matrices. + + Parameters + ---------- + `array` : NDArray[np.complex128] + The input array to check if it is a Hermitian matrix. + `expected` : bool + The expected output of the function. + """ + assert is_hermitian_matrix(array) is expected + + @pytest.mark.parametrize("array, expected", [ + (np.array([ + [1, 0], + [0, 1] + ]), True), + (np.array([ + [1, 0], + [0, 0] + ]), True), + (np.array([ + [1, 2], + [2, 3] + ]), False), + (np.array([ + [1, 2], + [3, 4] + ]), False), + (np.array([ + [1, 0], + [0, -1] + ]), False), + (np.array([ + [1, 2], + [2, 1] + ]), False), + (np.random.rand(2, 3, 3), False) + ]) + def test_is_positive_semidefinite_matrix( + self, + array: NDArray[np.complex128], + expected: bool + ) -> None: + """ Test the `is_positive_semidefinite_matrix()` function with + positive semidefinite matrices. + + Parameters + ---------- + `array` : NDArray[np.complex128] + The input array to check if it is a positive semidefinite matrix. + `expected` : bool + The expected output of the function. + """ + assert is_positive_semidefinite_matrix(array) is expected + + @pytest.mark.parametrize("array, expected", [ + (np.array([ + [0.56078693+0.13052803j, -0.31583062-0.08879493j], + [0.7123732 +0.2419316j, 0.39227097-0.22401521j], + [0.30203025+0.10607406j, -0.38000351+0.7374988j] + ], dtype=np.complex128), True), + (np.array([ + [0.08849653+0.24435482j], + [-0.72166734+0.64160373j] + ], dtype=np.complex128), True), + (np.array([ + [0.02572621+0.08711405j, -0.84637795+0.52477964j], + [0.86175428-0.4991281j, -0.06470557-0.06374861j] + ]), True), + (np.array([ + [1, 1], + [1, 1] + ], dtype=np.complex128), False), + (np.random.rand(3, 3), False), + (np.random.rand(1, 2), False), + (np.array([1, 0]), False), + ]) + def test_is_isometry( + self, + array: NDArray[np.complex128], + expected: bool + ) -> None: + """ Test the `is_isometry` function with various matrices. + + Parameters + ---------- + `array` : NDArray[np.complex128] + The input array to check if it is an isometry. + `expected` : bool + The expected output of the function. + """ + assert is_isometry(array) is expected + + @pytest.mark.parametrize("array, expected", [ + (np.array([ + [0.55282636+0.j, 0.19339888+0.1369917j], + [0.19339888-0.1369917j, 0.44717364+0.j] + ], dtype=np.complex128), True), + (np.array([ + [0.19834677+0.j, 0.21077084+0.08851485j, 0.07369894-0.03780167j], + [0.21077084-0.08851485j, 0.5556912 +0.j, 0.09721694-0.13294078j], + [0.07369894+0.03780167j, 0.09721694+0.13294078j, 0.24596203+0.j] + ], dtype=np.complex128), True), + (np.array([ + [1, 2 + 1j], + [2 + 1j, 3] + ]), False), + (np.array([ + [1, 2], + [3, 4] + ]), False), + (np.random.rand(2, 3, 3), False) + ]) + def test_is_density_matrix( + self, + array: NDArray[np.complex128], + expected: bool + ) -> None: + """ Test the `is_density_matrix` function with various matrices. + + Parameters + ---------- + `array` : NDArray[np.complex128] + The input array to check if it is an isometry. + `expected` : bool + The expected out of the function. + """ + assert is_density_matrix(array) is expected + + def test_is_product_matrix(self) -> None: + """ Test the `is_product_matrix` function. + """ + u1 = unitary_group.rvs(2) + u2 = unitary_group.rvs(2) + u3 = np.kron(u1, u2).astype(complex) + assert is_product_matrix(u3) is True + cx = np.array([ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 0, 1], + [0, 0, 1, 0] + ], dtype=complex) + assert is_product_matrix(cx) is False + + def test_is_locally_equivalent(self) -> None: + """ Test the `is_locally_equivalent` function. + """ + cx = np.array([ + [1, 0, 0, 0], + [0, 0, 0, 1], + [0, 0, 1, 0], + [0, 1, 0, 0] + ]) + cz = np.array([ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, -1] + ], dtype=complex) + assert is_locally_equivalent(cx, cz) is True + + swap = np.array([ + [1, 0, 0, 0], + [0, 0, 1, 0], + [0, 1, 0, 0], + [0, 0, 0, 1] + ], dtype=complex) + assert is_locally_equivalent(cx, swap) is False + + @pytest.mark.parametrize("array, expected", [ + (np.array([ + [1, 0, 0, 0], + [0, 0, 0, 1], + [0, 0, 1, 0], + [0, 1, 0, 0] + ], dtype=complex), True), + (np.array([ + [1, 0, 0, 0], + [0, 0, 0, -1j], + [0, 0, 1, 0], + [0, 1j, 0, 0] + ], dtype=complex), True), + (np.array([ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, -1] + ], dtype=complex), True), + (np.array([ + [1, 0, 0, 0], + [0, 0, 1j, 0], + [0, 1j, 0, 0], + [0, 0, 0, 1] + ], dtype=complex), True), + (np.array([ + [1, 0, 0, 0], + [0, 0, 1, 0], + [0, 1, 0, 0], + [0, 0, 0, 1] + ], dtype=complex), False), + (np.array([ + [1, 0, 0, 0], + [0, 0.5+0.5j, 0, 0.5-0.5j], + [0, 0, 1, 0], + [0, 0.5-0.5j, 0, 0.5+0.5j] + ], dtype=complex), False) + ]) + def test_is_supercontrolled( + self, + array: NDArray[np.complex128], + expected: bool + ) -> None: + """ Test the `is_supercontrolled` function with various matrices. + + Parameters + ---------- + `array` : NDArray[np.complex128] + The input array to check if it is supercontrolled. + `expected` : bool + The expected output of the function. + """ + assert is_supercontrolled(array) is expected \ No newline at end of file diff --git a/tests/random/test_random.py b/tests/random/test_random.py index 765cdca..93d70ee 100644 --- a/tests/random/test_random.py +++ b/tests/random/test_random.py @@ -18,11 +18,21 @@ import pytest -from quick.predicates import is_unitary_matrix, is_statevector, is_density_matrix +from quick.predicates import ( + is_unitary_matrix, + is_statevector, + is_density_matrix, + is_orthogonal_matrix, + is_special_orthogonal_matrix, + is_special_unitary_matrix +) from quick.random import ( generate_random_state, generate_random_unitary, - generate_random_density_matrix + generate_random_density_matrix, + generate_random_orthogonal_matrix, + generate_random_special_orthogonal_matrix, + generate_random_special_unitary_matrix ) @@ -35,7 +45,7 @@ def test_generate_random_state( self, num_qubits: int ) -> None: - """ Test the `generate_random_state` function. + """ Test the `generate_random_state()` function. Parameters ---------- @@ -51,7 +61,7 @@ def test_generate_random_unitary( self, num_qubits: int ) -> None: - """ Test the `generate_random_unitary` function. + """ Test the `generate_random_unitary()` function. Parameters ---------- @@ -72,7 +82,7 @@ def test_generate_random_density_matrix( generator: str, rank: int ) -> None: - """ Test the `generate_random_density_matrix` function. + """ Test the `generate_random_density_matrix()` function. Parameters ---------- @@ -94,7 +104,7 @@ def test_generate_random_density_matrix( def test_generate_random_density_matrix_invalid_generator( self ) -> None: - """ Test the `generate_random_density_matrix` function with an + """ Test the `generate_random_density_matrix()` function with an invalid generator. """ with pytest.raises(ValueError): @@ -102,4 +112,52 @@ def test_generate_random_density_matrix_invalid_generator( num_qubits=2, rank=1, generator="invalid-generator" # type: ignore - ) \ No newline at end of file + ) + + @pytest.mark.parametrize("num_qubits", [1, 2, 3, 4, 5]) + def test_generate_random_orthogonal_matrix( + self, + num_qubits: int + ) -> None: + """ Test the `generate_random_orthogonal_matrix()` function. + + Parameters + ---------- + `num_qubits` : int + The number of qubits in the SO matrix. + """ + o_matrix = generate_random_orthogonal_matrix(num_qubits) + + assert is_orthogonal_matrix(o_matrix) + + @pytest.mark.parametrize("num_qubits", [1, 2, 3, 4, 5]) + def test_generate_random_special_orthogonal_matrix( + self, + num_qubits: int + ) -> None: + """ Test the `generate_random_special_orthogonal_matrix()` function. + + Parameters + ---------- + `num_qubits` : int + The number of qubits in the SO matrix. + """ + so_matrix = generate_random_special_orthogonal_matrix(num_qubits) + + assert is_special_orthogonal_matrix(so_matrix) + + @pytest.mark.parametrize("num_qubits", [1, 2, 3, 4, 5]) + def test_generate_random_special_unitary_matrix( + self, + num_qubits: int + ) -> None: + """ Test the `generate_random_special_unitary_matrix()` function. + + Parameters + ---------- + `num_qubits` : int + The number of qubits in the SU matrix. + """ + su_matrix = generate_random_special_unitary_matrix(num_qubits) + + assert is_special_unitary_matrix(su_matrix) \ No newline at end of file diff --git a/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_weyl.py b/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_weyl.py index a7d9531..ccadaf8 100644 --- a/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_weyl.py +++ b/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_weyl.py @@ -21,18 +21,20 @@ from numpy.typing import NDArray from scipy.stats import unitary_group -from quick.synthesis.gate_decompositions.two_qubit_decomposition.weyl import weyl_coordinates +from quick.synthesis.gate_decompositions.two_qubit_decomposition.weyl import ( + M, + M_DAGGER, + weyl_coordinates +) # Tolerance for floating point comparisons INVARIANT_TOL = 1e-12 -# Bell "Magic" basis -MAGIC = 1/np.sqrt(2) * np.array([ - [1, 0, 0, 1j], - [0, 1j, 1, 0], - [0, 1j, -1, 0], - [1, 0, 0, -1j] -], dtype=complex) +# Constants +PI = np.pi +PI_DOUBLE = 2 * PI +PI2 = PI / 2 +PI4 = PI / 4 def two_qubit_local_invariants(U: NDArray[np.complex128]) -> NDArray[np.float64]: @@ -60,17 +62,19 @@ def two_qubit_local_invariants(U: NDArray[np.complex128]) -> NDArray[np.float64] raise ValueError("Unitary must correspond to a two-qubit gate.") # Transform to bell basis - Um = MAGIC.conj().T.dot(U.dot(MAGIC)) - # Get determinate since +- one is allowed. - det_um = np.linalg.det(Um) - M = Um.T.dot(Um) + U_magic_basis = M_DAGGER @ U @ M + + # Get det since +- one is allowed. + det_um = np.linalg.det(U_magic_basis) + M_squared = U_magic_basis.T @ U_magic_basis + # trace(M)**2 - m_tr2 = M.trace() + m_tr2 = M_squared.trace() m_tr2 *= m_tr2 # Table II of Ref. 1 or Eq. 28 of Ref. 2. G1 = m_tr2 / (16 * det_um) - G2 = (m_tr2 - np.trace(M.dot(M))) / (4 * det_um) + G2 = (m_tr2 - np.trace(M_squared.dot(M_squared))) / (4 * det_um) # Here we split the real and imag pieces of G1 into two so as # to better equate to the Weyl chamber coordinates (c0,c1,c2) @@ -114,21 +118,127 @@ class TestWeyl: class. """ def test_weyl_coordinates_simple(self) -> None: - """ Check Weyl coordinates against known cases. + """ Check Weyl coordinates against known basis gates within the Weyl tetrahedron. + + .. math:: + A(a, b, c) = e^{(ia X \otimes X + ib Y \otimes Y + ic Z \otimes Z)} + + Reference for Weyl coordinates, however, we modify the coordinates slightly to match + the above representation instead: + https://threeplusone.com/pubs/on_gates.pdf Section 6 """ # Identity [0,0,0] U = np.identity(4).astype(complex) weyl = weyl_coordinates(U) assert_almost_equal(weyl, [0, 0, 0], decimal=8) - # CNOT [pi/4, 0, 0] - U = np.array([[1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0]], dtype=complex) + # CX [pi/4, 0, 0] + U = np.array([ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 0, 1], + [0, 0, 1, 0] + ], dtype=complex) weyl = weyl_coordinates(U) assert_almost_equal(weyl, [np.pi / 4, 0, 0], decimal=8) - # SWAP [pi/4, pi/4 ,pi/4] - U = np.array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]], dtype=complex) + # CY [pi/4, 0, 0] + U = np.array([ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 0, -1j], + [0, 0, 1j, 0] + ], dtype=complex) + weyl = weyl_coordinates(U) + assert_almost_equal(weyl, [np.pi / 4, 0, 0], decimal=8) + + # CZ [pi/4, 0, 0] + U = np.array([[ + 1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, -1] + ], dtype=complex) + weyl = weyl_coordinates(U) + assert_almost_equal(weyl, [np.pi / 4, 0, 0], decimal=8) + + # CH [pi/4, 0, 0] + U = np.array([ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1/np.sqrt(2), 1/np.sqrt(2)], + [0, 0, 1/np.sqrt(2), -1/np.sqrt(2)] + ], dtype=complex) + weyl = weyl_coordinates(U) + assert_almost_equal(weyl, [np.pi / 4, 0, 0], decimal=8) + + # Mølmer–Sørensen [pi/4, 0, 0] + U = np.array([ + [1, 0, 0, 1j], + [0, 1, 1j, 0], + [0, 1j, 1, 0], + [1j, 0, 0, 1] + ], dtype=complex) * 1 / np.sqrt(2) + weyl = weyl_coordinates(U) + assert_almost_equal(weyl, [np.pi/4, 0, 0], decimal=8) + + # Magic [pi/4, 0, 0] + U = np.array([ + [1, 1j, 0, 0], + [0, 0, 1j, 1], + [0, 0, 1j, -1], + [1, -1j, 0, 0] + ], dtype=complex) * 1 / np.sqrt(2) + weyl = weyl_coordinates(U) + assert_almost_equal(weyl, [np.pi / 4, 0, 0], decimal=8) + + # ISWAP (imaginary SWAP) [pi/4, pi/4, 0] + U = np.array([ + [1, 0, 0, 0], + [0, 0, 1j, 0], + [0, 1j, 0, 0], + [0, 0, 0, 1] + ], dtype=complex) + weyl = weyl_coordinates(U) + assert_almost_equal(weyl, [np.pi / 4, np.pi / 4, 0], decimal=8) + + # Fermionic SWAP [pi/4, pi/4, 0] + U = np.array([ + [1, 0, 0, 0], + [0, 0, 1, 0], + [0, 1, 0, 0], + [0, 0, 0, -1] + ], dtype=complex) + weyl = weyl_coordinates(U) + assert_almost_equal(weyl, [np.pi / 4, np.pi / 4, 0], decimal=8) + + # DCX [pi/4, pi/4, 0] + U = np.array([ + [1, 0, 0, 0], + [0, 0, 0, 1], + [0, 1, 0, 0], + [0, 0, 1, 0] + ], dtype=complex) + weyl = weyl_coordinates(U) + assert_almost_equal(weyl, [np.pi / 4, np.pi / 4, 0], decimal=8) + + # Inverse DCX [pi/4, pi/4, 0] + U = np.array([ + [1, 0, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1], + [0, 1, 0, 0] + ], dtype=complex) + weyl = weyl_coordinates(U) + assert_almost_equal(weyl, [np.pi / 4, np.pi / 4, 0], decimal=8) + # SWAP [pi/4, pi/4, pi/4] + U = np.array([ + [1, 0, 0, 0], + [0, 0, 1, 0], + [0, 1, 0, 0], + [0, 0, 0, 1] + ], dtype=complex) weyl = weyl_coordinates(U) assert_almost_equal(weyl, [np.pi / 4, np.pi / 4, np.pi / 4], decimal=8) @@ -142,14 +252,101 @@ def test_weyl_coordinates_simple(self) -> None: ], dtype=complex, ) - weyl = weyl_coordinates(U) assert_almost_equal(weyl, [np.pi / 8, np.pi / 8, 0], decimal=8) + # Ising XX [t/2, 0, 0] + t = 0.1 + U = np.array([ + [np.cos(t/2), 0, 0, -1j * np.sin(t/2)], + [0, np.cos(t/2), -1j * np.sin(t/2), 0], + [0, -1j * np.sin(t/2), np.cos(t/2), 0], + [-1j * np.sin(t/2), 0, 0, np.cos(t/2)] + ], dtype=complex) + weyl = weyl_coordinates(U) + assert_almost_equal(weyl, [t/2, 0, 0], decimal=8) + + # Ising YY [t/2, 0, 0] + t = 0.2 + U = np.array([ + [np.cos(t/2), 0, 0, 1j * np.sin(t/2)], + [0, np.cos(t/2), -1j * np.sin(t/2), 0], + [0, -1j * np.sin(t/2), np.cos(t/2), 0], + [1j * np.sin(t/2), 0, 0, np.cos(t/2)] + ], dtype=complex) + weyl = weyl_coordinates(U) + assert_almost_equal(weyl, [t/2, 0, 0], decimal=8) + + # Ising ZZ [t/2, 0, 0] + t = 0.3 + U = np.array([ + [np.exp(-1j * t/2), 0, 0, 0], + [0, np.exp(1j * t/2), 0, 0], + [0, 0, np.exp(1j * t/2), 0], + [0, 0, 0, np.exp(-1j * t/2)] + ], dtype=complex) + weyl = weyl_coordinates(U) + assert_almost_equal(weyl, [t/2, 0, 0], decimal=8) + + # CSX [pi/8, 0, 0] + U = np.array([ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, (1+1j)/2, (1-1j)/2], + [0, 0, (1-1j)/2, (1+1j)/2] + ], dtype=complex) + weyl = weyl_coordinates(U) + assert_almost_equal(weyl, [np.pi/8, 0, 0], decimal=8) + + # XY [t, t, 0] + t = 0.1 + U = np.array([ + [1, 0, 0, 0], + [0, np.cos(2*t), -1j * np.sin(2*t), 0], + [0, -1j * np.sin(2*t), np.cos(2*t), 0], + [0, 0, 0, 1] + ], dtype=complex) + weyl = weyl_coordinates(U) + assert_almost_equal(weyl, [t, t, 0], decimal=8) + + # Givens [t/2, t/2, 0] + t = 0.1 + U = np.array([ + [1, 0, 0, 0], + [0, np.cos(t), -np.sin(t), 0], + [0, np.sin(t), np.cos(t), 0], + [0, 0, 0, 1] + ], dtype=complex) + weyl = weyl_coordinates(U) + assert_almost_equal(weyl, [t/2, t/2, 0], decimal=8) + + # DB [3pi/16, 3pi/16, 0] + U = np.array([ + [1, 0, 0, 0], + [0, np.cos(3 * np.pi / 8), -np.sin(3 * np.pi / 8), 0], + [0, np.sin(3 * np.pi / 8), np.cos(3 * np.pi / 8), 0], + [0, 0, 0, 1] + ], dtype=complex) + weyl = weyl_coordinates(U) + assert_almost_equal(weyl, [3 * np.pi / 16, 3 * np.pi / 16, 0], decimal=8) + + # SQRT SWAP [pi/8, pi/8, -pi/8] + U = np.array([ + [1, 0, 0, 0], + [0, (1 + 1j)/2, (1 - 1j)/2, 0], + [0, (1 - 1j)/2, (1 + 1j)/2, 0], + [0, 0, 0, 1] + ]) + weyl = weyl_coordinates(U) + assert_almost_equal(weyl, [np.pi/8, np.pi/8, -np.pi/8], decimal=8) + def test_weyl_coordinates_random(self) -> None: """ Randomly check Weyl coordinates with local invariants. + This test is useful for verifying the correctness of the + decomposition for arbitrary basis gates, which is useful + for transpilation if the decomposition supports it. """ - for _ in range(10): + for _ in range(30): U = unitary_group.rvs(4).astype(complex) weyl = weyl_coordinates(U) local_equiv = local_equivalence(weyl.astype(float)) From 5730c60493d23d50e94c61856494384c40c1aa69 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sat, 18 Oct 2025 15:53:15 +0800 Subject: [PATCH 58/58] - Removed trailing whitespaces in some test files. --- quick/predicates/predicates.py | 2 +- tests/primitives/test_operator.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/quick/predicates/predicates.py b/quick/predicates/predicates.py index ca1de7d..242fed7 100644 --- a/quick/predicates/predicates.py +++ b/quick/predicates/predicates.py @@ -168,7 +168,7 @@ def is_square_matrix(matrix: NDArray[np.complex128]) -> bool: return False shape = matrix.shape return bool(shape[0] == shape[1]) - + def is_orthogonal_matrix( matrix: NDArray[np.complex128], rtol: float = RTOL_DEFAULT, diff --git a/tests/primitives/test_operator.py b/tests/primitives/test_operator.py index 8253cbc..b259a2e 100644 --- a/tests/primitives/test_operator.py +++ b/tests/primitives/test_operator.py @@ -142,7 +142,7 @@ def test_control(self) -> None: unitary = np.array(unitary_group.rvs(8)).astype(complex) operator = Operator(unitary) control_operator = operator.control(2) - + op_circuit = QiskitCircuit(3) op_circuit.unitary(unitary, [0, 1, 2]) checker_circuit = op_circuit.control(2)