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 ba5fdaf..58bfae5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,8 +9,9 @@ jobs: test: runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: - os: [ubuntu-latest] + os: [ubuntu-latest, windows-latest, macos-latest] python-version: ["3.10", "3.11", "3.12"] steps: diff --git a/notebooks/Training Ansatzes.ipynb b/notebooks/Training Ansatzes.ipynb new file mode 100644 index 0000000..dd11cac --- /dev/null +++ b/notebooks/Training Ansatzes.ipynb @@ -0,0 +1,413 @@ +{ + "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": "iVBORw0KGgoAAAANSUhEUgAAAZgAAAGdCAYAAAAv9mXmAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAGA1JREFUeJzt3XFs1IXdx/HP0aMH0/YEpNCuR0FFEbAdUiBYHSgITx8kuD+QEHxWwS2RHANsTFz/eIbJMo7leWbQhVRgrpjHMdjMCmoGFVBKfKCjlDQBTRAUpQOhc5G70j8O6P2efx5v64DS37Xf/vjV9yv5JbvL7/h9Yozv/e5KL+A4jiMAAHrZAK8HAAD6JwIDADBBYAAAJggMAMAEgQEAmCAwAAATBAYAYILAAABMBPv6gqlUSufOnVNOTo4CgUBfXx4A0AOO46itrU0FBQUaMKDre5Q+D8y5c+cUiUT6+rIAgF7U0tKiwsLCLs/p88Dk5ORIkh7WvyuogX19eQBAD1zVFX2oP6f/W96VPg/MN2+LBTVQwQCBAQBf+f/fXtmdjzj4kB8AYILAAABMEBgAgAkCAwAwQWAAACYIDADABIEBAJggMAAAEwQGAGCCwAAATBAYAIAJAgMAMEFgAAAmCAwAwASBAQCYIDAAABMZBWbDhg0aPXq0Bg0apGnTpunw4cO9vQsA4HOuA7N9+3ZVVlZqzZo1Onr0qEpKSjR37ly1trZa7AMA+JTrwLz88sv68Y9/rKVLl2r8+PF67bXX9J3vfEe//e1vLfYBAHzKVWAuX76spqYmzZ49+x9/wIABmj17tg4dOnTd1ySTSSUSiU4HAKD/cxWYr776Sh0dHRoxYkSn50eMGKHz589f9zWxWEzhcDh9RCKRzNcCAHzD/KfIqqqqFI/H00dLS4v1JQEAt4Cgm5PvvPNOZWVl6cKFC52ev3DhgkaOHHnd14RCIYVCocwXAgB8ydUdTHZ2tiZPnqx9+/aln0ulUtq3b5+mT5/e6+MAAP7l6g5GkiorK1VRUaHS0lJNnTpV69evV3t7u5YuXWqxDwDgU64Ds2jRIv3tb3/Tz372M50/f17f+973tHv37ms++AcAfLsFHMdx+vKCiURC4XBYM7VAwcDAvrw0AKCHrjpXtF87FY/HlZub2+W5/C4yAIAJAgMAMEFgAAAmCAwAwASBAQCYIDAAABMEBgBggsAAAEwQGACACQIDADBBYAAAJggMAMAEgQEAmCAwAAATBAYAYML1F44B6L9a/vMhrydk5I6TKa8nZCxnW4PXE8xwBwMAMEFgAAAmCAwAwASBAQCYIDAAABMEBgBggsAAAEwQGACACQIDADBBYAAAJggMAMAEgQEAmCAwAAATBAYAYILAAABMEBgAgAkCAwAwQWAAACYIDADAhOvAHDhwQPPnz1dBQYECgYB27NhhMAsA4HeuA9Pe3q6SkhJt2LDBYg8AoJ8Iun1BeXm5ysvLLbYAAPoR14FxK5lMKplMph8nEgnrSwIAbgHmH/LHYjGFw+H0EYlErC8JALgFmAemqqpK8Xg8fbS0tFhfEgBwCzB/iywUCikUCllfBgBwi+HvwQAATLi+g7l06ZJOnTqVfnz69Gk1Nzdr6NChGjVqVK+OAwD4l+vAHDlyRI8++mj6cWVlpSSpoqJCW7Zs6bVhAAB/cx2YmTNnynEciy0AgH6Ez2AAACYIDADABIEBAJggMAAAEwQGAGCCwAAATBAYAIAJAgMAMEFgAAAmCAwAwASBAQCYIDAAABMEBgBggsAAAEwQGACACdffBwPg5rLuu8frCRlZuXin1xMyUjt+uNcTcB3cwQAATBAYAIAJAgMAMEFgAAAmCAwAwASBAQCYIDAAABMEBgBggsAAAEwQGACACQIDADBBYAAAJggMAMAEgQEAmCAwAAATBAYAYILAAABMEBgAgAkCAwAw4SowsVhMU6ZMUU5OjvLy8vTkk0/qxIkTVtsAAD7mKjD19fWKRqNqaGjQnj17dOXKFc2ZM0ft7e1W+wAAPhV0c/Lu3bs7Pd6yZYvy8vLU1NSk73//+706DADgb64C86/i8bgkaejQoTc8J5lMKplMph8nEomeXBIA4BMZf8ifSqW0evVqlZWVaeLEiTc8LxaLKRwOp49IJJLpJQEAPpJxYKLRqI4fP65t27Z1eV5VVZXi8Xj6aGlpyfSSAAAfyegtshUrVujdd9/VgQMHVFhY2OW5oVBIoVAoo3EAAP9yFRjHcfSTn/xEtbW12r9/v8aMGWO1CwDgc64CE41GtXXrVu3cuVM5OTk6f/68JCkcDmvw4MEmAwEA/uTqM5jq6mrF43HNnDlT+fn56WP79u1W+wAAPuX6LTIAALqD30UGADBBYAAAJggMAMAEgQEAmCAwAAATBAYAYILAAABMEBgAgAkCAwAwQWAAACYIDADABIEBAJggMAAAEwQGAGCCwAAATBAYAIAJV184BvS1ATk5Xk/IyJ8/eMvrCRmZW/A9ryegH+EOBgBggsAAAEwQGACACQIDADBBYAAAJggMAMAEgQEAmCAwAAATBAYAYILAAABMEBgAgAkCAwAwQWAAACYIDADABIEBAJggMAAAEwQGAGCCwAAATLgKTHV1tYqLi5Wbm6vc3FxNnz5du3btstoGAPAxV4EpLCzUunXr1NTUpCNHjuixxx7TggUL9NFHH1ntAwD4VNDNyfPnz+/0+Be/+IWqq6vV0NCgCRMm9OowAIC/uQrMP+vo6NAf//hHtbe3a/r06Tc8L5lMKplMph8nEolMLwkA8BHXH/IfO3ZMt99+u0KhkJ577jnV1tZq/PjxNzw/FospHA6nj0gk0qPBAAB/cB2Y++67T83NzfrLX/6i5cuXq6KiQh9//PENz6+qqlI8Hk8fLS0tPRoMAPAH12+RZWdn65577pEkTZ48WY2NjXrllVe0cePG654fCoUUCoV6thIA4Ds9/nswqVSq02csAABILu9gqqqqVF5erlGjRqmtrU1bt27V/v37VVdXZ7UPAOBTrgLT2tqqH/7wh/ryyy8VDodVXFysuro6Pf7441b7AAA+5Sowr7/+utUOAEA/w+8iAwCYIDAAABMEBgBggsAAAEwQGACACQIDADBBYAAAJggMAMAEgQEAmCAwAAATBAYAYILAAABMEBgAgAkCAwAwQWAAACYIDADAhKsvHAP62n8fe8/rCRn5t6IZXk/I0GWvB6Af4Q4GAGCCwAAATBAYAIAJAgMAMEFgAAAmCAwAwASBAQCYIDAAABMEBgBggsAAAEwQGACACQIDADBBYAAAJggMAMAEgQEAmCAwAAATBAYAYILAAABM9Cgw69atUyAQ0OrVq3tpDgCgv8g4MI2Njdq4caOKi4t7cw8AoJ/IKDCXLl3SkiVLtHnzZg0ZMqS3NwEA+oGMAhONRjVv3jzNnj27t/cAAPqJoNsXbNu2TUePHlVjY2O3zk8mk0omk+nHiUTC7SUBAD7k6g6mpaVFq1at0u9+9zsNGjSoW6+JxWIKh8PpIxKJZDQUAOAvrgLT1NSk1tZWPfjggwoGgwoGg6qvr9err76qYDCojo6Oa15TVVWleDyePlpaWnptPADg1uXqLbJZs2bp2LFjnZ5bunSpxo0bpxdffFFZWVnXvCYUCikUCvVsJQDAd1wFJicnRxMnTuz03G233aZhw4Zd8zwA4NuNv8kPADDh+qfI/tX+/ft7YQYAoL/hDgYAYILAAABMEBgAgAkCAwAwQWAAACYIDADABIEBAJggMAAAEwQGAGCCwAAATBAYAIAJAgMAMEFgAAAmCAwAwASBAQCYIDAAABM9/sIx3PrO/vQhrydk7KmN/txeeOWg1xMAz3EHAwAwQWAAACYIDADABIEBAJggMAAAEwQGAGCCwAAATBAYAIAJAgMAMEFgAAAmCAwAwASBAQCYIDAAABMEBgBggsAAAEwQGACACQIDADBBYAAAJggMAMCEq8C89NJLCgQCnY5x48ZZbQMA+FjQ7QsmTJigvXv3/uMPCLr+IwAA3wKu6xAMBjVy5EiLLQCAfsT1ZzAnT55UQUGB7rrrLi1ZskRnzpzp8vxkMqlEItHpAAD0f64CM23aNG3ZskW7d+9WdXW1Tp8+rUceeURtbW03fE0sFlM4HE4fkUikx6MBALe+gOM4TqYvvnjxooqKivTyyy/r2Wefve45yWRSyWQy/TiRSCgSiWimFigYGJjppeHC2Z8+5PWEjDkBrxdkpjB20OsJgImrzhXt107F43Hl5uZ2eW6PPqG/4447dO+99+rUqVM3PCcUCikUCvXkMgAAH+rR34O5dOmSPv30U+Xn5/fWHgBAP+EqMC+88ILq6+v1+eef6+DBg/rBD36grKwsLV682GofAMCnXL1F9te//lWLFy/W3//+dw0fPlwPP/ywGhoaNHz4cKt9AACfchWYbdu2We0AAPQz/C4yAIAJAgMAMEFgAAAmCAwAwASBAQCYIDAAABMEBgBggsAAAEwQGACACQIDADBBYAAAJggMAMAEgQEAmCAwAAATBAYAYMLV98HAn96P/pfXEzK2dMYSrydk5KrXA4BbAHcwAAATBAYAYILAAABMEBgAgAkCAwAwQWAAACYIDADABIEBAJggMAAAEwQGAGCCwAAATBAYAIAJAgMAMEFgAAAmCAwAwASBAQCYIDAAABMEBgBggsAAAEy4DszZs2f19NNPa9iwYRo8eLAeeOABHTlyxGIbAMDHgm5O/vrrr1VWVqZHH31Uu3bt0vDhw3Xy5EkNGTLEah8AwKdcBeaXv/ylIpGIampq0s+NGTOm10cBAPzP1Vtkb7/9tkpLS7Vw4ULl5eVp0qRJ2rx5c5evSSaTSiQSnQ4AQP/nKjCfffaZqqurNXbsWNXV1Wn58uVauXKl3njjjRu+JhaLKRwOp49IJNLj0QCAW1/AcRynuydnZ2ertLRUBw8eTD+3cuVKNTY26tChQ9d9TTKZVDKZTD9OJBKKRCKaqQUKBgb2YDq6639a/tfrCRlbOmOJ1xMycvWzz72eAJi46lzRfu1UPB5Xbm5ul+e6uoPJz8/X+PHjOz13//3368yZMzd8TSgUUm5ubqcDAND/uQpMWVmZTpw40em5Tz75REVFRb06CgDgf64C8/zzz6uhoUFr167VqVOntHXrVm3atEnRaNRqHwDAp1wFZsqUKaqtrdXvf/97TZw4UT//+c+1fv16LVniz/fJAQB2XP09GEl64okn9MQTT1hsAQD0I/wuMgCACQIDADBBYAAAJggMAMAEgQEAmCAwAAATBAYAYILAAABMEBgAgAkCAwAwQWAAACYIDADABIEBAJggMAAAEwQGAGCCwAAATLj+wjH4z39Eyrye0AOfez0AQIa4gwEAmCAwAAATBAYAYILAAABMEBgAgAkCAwAwQWAAACYIDADABIEBAJggMAAAEwQGAGCCwAAATBAYAIAJAgMAMEFgAAAmCAwAwASBAQCYIDAAABOuAjN69GgFAoFrjmg0arUPAOBTQTcnNzY2qqOjI/34+PHjevzxx7Vw4cJeHwYA8DdXgRk+fHinx+vWrdPdd9+tGTNm9OooAID/uQrMP7t8+bLefPNNVVZWKhAI3PC8ZDKpZDKZfpxIJDK9JADARzL+kH/Hjh26ePGinnnmmS7Pi8ViCofD6SMSiWR6SQCAjwQcx3EyeeHcuXOVnZ2td955p8vzrncHE4lENFMLFAwMzOTSAACPXHWuaL92Kh6PKzc3t8tzM3qL7IsvvtDevXv1pz/96abnhkIhhUKhTC4DAPCxjN4iq6mpUV5enubNm9fbewAA/YTrwKRSKdXU1KiiokLBYMY/IwAA6OdcB2bv3r06c+aMli1bZrEHANBPuL4FmTNnjjL8uQAAwLcIv4sMAGCCwAAATBAYAIAJAgMAMEFgAAAmCAwAwASBAQCYIDAAABMEBgBggsAAAEwQGACACQIDADBBYAAAJggMAMAEgQEAmOjzr6T85rtkruqKxNfKAICvXNUVSerW94L1eWDa2tokSR/qz319aQBAL2lra1M4HO7ynIDTx19PmUqldO7cOeXk5CgQCPTqn51IJBSJRNTS0qLc3Nxe/bMtsbtvsbvv+XU7u6/lOI7a2tpUUFCgAQO6/pSlz+9gBgwYoMLCQtNr5Obm+upfhm+wu2+xu+/5dTu7O7vZncs3+JAfAGCCwAAATPSrwIRCIa1Zs0ahUMjrKa6wu2+xu+/5dTu7e6bPP+QHAHw79Ks7GADArYPAAABMEBgAgAkCAwAw0W8Cs2HDBo0ePVqDBg3StGnTdPjwYa8n3dSBAwc0f/58FRQUKBAIaMeOHV5P6pZYLKYpU6YoJydHeXl5evLJJ3XixAmvZ91UdXW1iouL03/5bPr06dq1a5fXs1xbt26dAoGAVq9e7fWULr300ksKBAKdjnHjxnk9q1vOnj2rp59+WsOGDdPgwYP1wAMP6MiRI17PuqnRo0df8888EAgoGo16sqdfBGb79u2qrKzUmjVrdPToUZWUlGju3LlqbW31elqX2tvbVVJSog0bNng9xZX6+npFo1E1NDRoz549unLliubMmaP29navp3WpsLBQ69atU1NTk44cOaLHHntMCxYs0EcffeT1tG5rbGzUxo0bVVxc7PWUbpkwYYK+/PLL9PHhhx96Pemmvv76a5WVlWngwIHatWuXPv74Y/3qV7/SkCFDvJ52U42NjZ3+ee/Zs0eStHDhQm8GOf3A1KlTnWg0mn7c0dHhFBQUOLFYzMNV7khyamtrvZ6RkdbWVkeSU19f7/UU14YMGeL85je/8XpGt7S1tTljx4519uzZ48yYMcNZtWqV15O6tGbNGqekpMTrGa69+OKLzsMPP+z1jF6xatUq5+6773ZSqZQn1/f9Hczly5fV1NSk2bNnp58bMGCAZs+erUOHDnm47NsjHo9LkoYOHerxku7r6OjQtm3b1N7erunTp3s9p1ui0ajmzZvX6d/1W93JkydVUFCgu+66S0uWLNGZM2e8nnRTb7/9tkpLS7Vw4ULl5eVp0qRJ2rx5s9ezXLt8+bLefPNNLVu2rNd/sXB3+T4wX331lTo6OjRixIhOz48YMULnz5/3aNW3RyqV0urVq1VWVqaJEyd6Peemjh07pttvv12hUEjPPfecamtrNX78eK9n3dS2bdt09OhRxWIxr6d027Rp07Rlyxbt3r1b1dXVOn36tB555JH0V3bcqj777DNVV1dr7Nixqqur0/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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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": [ + "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 - abs(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": [ + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(0.5896664357293144+0.807564869009721j)" + ] + }, + "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": "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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": [ + "
" + ] + }, + "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.\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." + ] + }, + { + "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/pyproject.toml b/pyproject.toml index 2a35ed8..dd61aa5 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", - "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", + "numpy >= 1.23", + "pennylane == 0.42.3", + "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 = [ @@ -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" diff --git a/quick/backend/backend.py b/quick/backend/backend.py index 378f9ca..f2c0028 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 @@ -59,14 +58,14 @@ class Backend(ABC): """ def __init__( self, - device: str="CPU" + device: str = "CPU" ) -> None: """ Initialize a `quick.backend.Backend` instance. """ 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): @@ -177,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. @@ -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. @@ -332,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. """ @@ -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). @@ -381,12 +380,12 @@ class FakeBackend(Backend, ABC): """ def __init__( self, - device: str="CPU" + device: str = "CPU" ) -> None: """ 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 @@ -452,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..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 @@ -89,7 +90,7 @@ def __init__( self, hardware_name: str, qiskit_runtime: QiskitRuntimeService, - device: str="CPU" + device: str = "CPU" ) -> None: super().__init__(device=device) @@ -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") @@ -161,7 +162,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/__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..749a759 --- /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.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.py b/quick/circuit/circuit.py index 085518c..fd519af 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,32 +29,36 @@ from numpy.typing import NDArray from types import NotImplementedType from typing import ( - Any, Callable, Literal, overload, SupportsFloat, SupportsIndex, Type, TYPE_CHECKING + Any, Literal, overload, SupportsFloat, SupportsIndex, TypeAlias, TYPE_CHECKING ) import qiskit # type: ignore import cirq # type: ignore import pennylane as qml # type: ignore -import pytket -import pytket.circuit +import pytket # type: ignore import quimb.tensor as qtn # type: ignore 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 +from quick.circuit.utils import ( + 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.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 ( UnitaryPreparation, ShannonDecomposition, QiskitUnitaryTranspiler ) -EPSILON = 1e-10 +EPSILON = 1e-15 + +CIRCUIT_LOG: TypeAlias = list[dict[str, Any]] """ Set the frozensets for the keys to be used: - Decorator `Circuit.gatemethod()` @@ -85,12 +89,36 @@ } # 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 = [ +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([ "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"]) + +# 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 @@ -252,7 +280,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 +313,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 +426,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." @@ -432,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 @@ -525,7 +556,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. @@ -540,7 +571,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. @@ -4623,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: @@ -4646,7 +4676,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 @@ -4707,8 +4737,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. @@ -4777,6 +4807,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] @@ -4784,18 +4820,22 @@ 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_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 @@ -4808,23 +4848,19 @@ 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)) - control_indices = [qubits[len(control_indices) + 1 - i] for i in new_controls] + new_controls, single_qubit_gates = simplify(single_qubit_gates, num_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( + single_qubit_gates, diagonal = extract_single_qubits_and_diagonal( single_qubit_gates, 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 @@ -4834,7 +4870,7 @@ def Multiplexor( 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) @@ -4851,12 +4887,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 == len(single_qubit_gates) - 1: + 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)) @@ -4897,9 +4931,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. @@ -4976,14 +5010,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. @@ -4991,7 +5025,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]. @@ -5005,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, @@ -5051,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, @@ -5068,33 +5098,30 @@ 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( - circuit_log: list[dict[str, Any]], - adjoint: bool=True - ) -> list[dict[str, Any]]: + circuit_log: CIRCUIT_LOG, + adjoint: bool = True + ) -> 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"] @@ -5127,7 +5154,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 @@ -5196,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 @@ -5312,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]: @@ -5352,7 +5351,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. @@ -5569,7 +5568,7 @@ def remove_measurements( def remove_measurements( self, - inplace: bool=False + inplace: bool = False ) -> Circuit | None: """ Remove the measurement instructions from the circuit. @@ -5594,10 +5593,63 @@ 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, - full: bool=False + reps: int = 1, + full: bool = False ) -> Circuit: """ Decompose the gates in the circuit to their implementation gates. @@ -5622,53 +5674,46 @@ 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(set(["U3", "CX", "GlobalPhase", "measure"])): - 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 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. @@ -5729,23 +5774,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 +5813,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]] @@ -5797,7 +5833,7 @@ def change_mapping( def convert( self, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> Circuit: """ Convert the circuit to another circuit framework. @@ -5823,21 +5859,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", None) - - # Extract gate definition and remove it from gate_info for kwargs + gate_name = gate_info.pop("gate") 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 @@ -5866,23 +5895,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 @@ -5904,15 +5932,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 @@ -5946,7 +5970,7 @@ def update(self) -> None: @abstractmethod def to_qasm( self, - qasm_version: int=2 + qasm_version: int = 2 ) -> str: """ Convert the circuit to QASM. @@ -5973,7 +5997,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 +6029,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`. @@ -6030,20 +6054,14 @@ 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 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 +6093,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 +6125,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 +6164,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`. @@ -6214,7 +6232,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. @@ -6250,6 +6268,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 @@ -6328,8 +6447,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/cirqcircuit.py b/quick/circuit/cirqcircuit.py index dfcc464..69123ff 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 @@ -159,33 +159,46 @@ 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) + + # 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 +221,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 +263,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 +286,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,15 +301,20 @@ 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( self, - qasm_version: int=2 + qasm_version: int = 2 ) -> str: from quick.circuit import QiskitCircuit 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/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 6888d0a..8b5e998 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 @@ -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. @@ -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..dfc4a10 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: @@ -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 @@ -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_pennylane.py b/quick/circuit/from_framework/from_pennylane.py new file mode 100644 index 0000000..30a6e43 --- /dev/null +++ b/quick/circuit/from_framework/from_pennylane.py @@ -0,0 +1,235 @@ +# 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)() + + 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 6f9f781..b75fd96 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 @@ -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. @@ -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) @@ -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 171df16..98027c6 100644 --- a/quick/circuit/from_framework/from_tket.py +++ b/quick/circuit/from_framework/from_tket.py @@ -20,11 +20,11 @@ __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 typing import Type, TYPE_CHECKING +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: from quick.circuit import Circuit @@ -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. @@ -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/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/circuit/pennylanecircuit.py b/quick/circuit/pennylanecircuit.py index 5f26493..b1f6124 100644 --- a/quick/circuit/pennylanecircuit.py +++ b/quick/circuit/pennylanecircuit.py @@ -19,10 +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 @@ -127,30 +128,42 @@ 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) + + # 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, - wires=target_index + wires=controls + [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 +191,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) + + # 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.measured_qubits.add(qubit_index) self.circuit.append((qml.measure(qubit_index), False)) # type: ignore @@ -190,26 +208,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 +219,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) @@ -233,44 +233,35 @@ 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.") - # 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) 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)) } + 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())) @@ -281,27 +272,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 +290,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,15 +305,20 @@ 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( 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 17b0059..1875a4f 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 @@ -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( @@ -174,8 +178,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) @@ -213,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() @@ -245,7 +248,6 @@ def get_counts( def get_unitary(self) -> NDArray[np.complex128]: unitary = Operator(self.circuit).data - return np.array(unitary) def reset_qubit( @@ -255,15 +257,14 @@ 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) 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 4b7b073..f9b448d 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 @@ -129,29 +129,42 @@ 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) + + # 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 +187,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 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 qubit_indices: + for qubit_index in qubits: self.measured_qubits.add(qubit_index) def get_statevector( @@ -185,18 +203,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 +223,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 +232,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] @@ -254,7 +283,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 a2ecfa4..be61183 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 @@ -130,29 +130,42 @@ 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) + + # 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 +185,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 index in 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 qubits: self.circuit.Measure(index, index) self.measured_qubits.add(index) @@ -184,18 +202,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 +222,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 +237,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 +249,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,15 +264,20 @@ 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( self, - qasm_version: int=2 + qasm_version: int = 2 ) -> str: from quick.circuit import QiskitCircuit diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/utils.py similarity index 79% rename from quick/circuit/circuit_utils.py rename to quick/circuit/utils.py index 1c9595c..ef3721f 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/utils.py @@ -22,10 +22,11 @@ "multiplexed_rz_angles", "extract_uvr_matrices", "extract_single_qubits_and_diagonal", - "multiplexor_diagonal_matrix", "simplify", "repetition_search", - "repetition_verify" + "repetition_verify", + "flatten", + "reshape" ] import numpy as np @@ -37,13 +38,18 @@ """ 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 + +# Type hint for nested lists of floats +Params = list[list[float] | float] | list[float] + def decompose_multiplexor_rotations( angles: NDArray[np.float64], @@ -180,7 +186,7 @@ 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) @@ -202,20 +208,24 @@ 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) - # Put the eigenvalues into a diagonal form - diagonal = np.diag(np.sqrt(eigenvalues)) + eigenvalues, u = np.linalg.eig(rxr) # type: ignore - # 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) < EPSILON: + eigenvalues = np.flipud(eigenvalues) u = np.fliplr(u) + 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 + return v, u, r # type: ignore def extract_single_qubits_and_diagonal( single_qubit_gates: list[NDArray[np.complex128]], @@ -279,7 +289,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 @@ -300,46 +310,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 @@ -366,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]], @@ -414,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 @@ -463,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. @@ -484,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). @@ -514,7 +483,84 @@ 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 - return True, mux_copy \ No newline at end of file + 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 + """ Flatten a Tree into a list of floats and + the original shape. + + Parameters + ---------- + `array` : Params + The nested list of floats. + + Returns + ------- + `flattened` : list[float] + The flattened list of parameters. + `shape` : Params + 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: # pragma: no cover + """ Reshape a flattened list given a shape instruction. + + Parameters + ---------- + `flattened` : list[float] + The flat list of floats. + `shape` : Params + The shape instruction. + + Returns + ------- + `reshaped` : Params + 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/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 75% rename from quick/compiler/compiler.py rename to quick/compiler/shende_compiler.py index 7a675aa..b95c864 100644 --- a/quick/compiler/compiler.py +++ b/quick/compiler/shende_compiler.py @@ -17,35 +17,35 @@ from __future__ import annotations -__all__ = ["Compiler"] +__all__ = ["ShendeCompiler"] 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 -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]]] -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 ------ @@ -90,10 +86,9 @@ class Compiler: """ def __init__( self, - circuit_framework: Type[Circuit], - state_prep: Type[StatePreparation]=Isometry, - unitary_prep: Type[UnitaryPreparation]=ShannonDecomposition, - optimizer: Optimizer | None=None + circuit_framework: type[Circuit], + state_prep: type[StatePreparation] = Isometry, + unitary_prep: type[UnitaryPreparation] = ShannonDecomposition, ) -> None: """ Initialize a `quick.compiler.Compiler` object. """ @@ -103,23 +98,20 @@ 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, - 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 @@ -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. @@ -200,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.") @@ -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, @@ -285,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)) @@ -325,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): @@ -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/quick/metrics/__init__.py b/quick/metrics/__init__.py index d83ed15..1b6e1ed 100644 --- a/quick/metrics/__init__.py +++ b/quick/metrics/__init__.py @@ -15,11 +15,17 @@ __all__ = [ "calculate_entanglement_range", "calculate_shannon_entropy", - "calculate_entanglement_entropy" + "calculate_entanglement_entropy", + "calculate_entanglement_entropy_slope", + "calculate_hilbert_schmidt_test", + "calculate_frobenius_distance" ] from quick.metrics.metrics import ( calculate_entanglement_range, calculate_shannon_entropy, - calculate_entanglement_entropy + calculate_entanglement_entropy, + calculate_entanglement_entropy_slope, + 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 9a3795a..1e13477 100644 --- a/quick/metrics/metrics.py +++ b/quick/metrics/metrics.py @@ -20,81 +20,69 @@ __all__ = [ "calculate_entanglement_range", "calculate_shannon_entropy", - "calculate_entanglement_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 quick.predicates import is_density_matrix, is_statevector, is_unitary_matrix +from quick.primitives import Statevector -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. - 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]. +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. - 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. @@ -109,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 @@ -124,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 ------- @@ -143,26 +138,193 @@ 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(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 + ---------- + `data` : NDArray[np.complex128] + The data, which can be a statevector or a density matrix. + + Returns + ------- + float + 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(data) + """ + # 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(statevector: NDArray[np.complex128]) -> float: - """ Calculate the entanglement entropy of the circuit. +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 isinstance(statevector, Statevector): + state = Statevector(statevector) + else: + state = statevector + + 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 = 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 + # 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] + ) -> 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 + +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 entanglement entropy of the circuit. + The Frobenius distance between the two matrices. + + Raises + ------ + ValueError + - If the matrices are not of the same shape. Usage ----- - >>> entanglement_entropy = calculate_entanglement_entropy(statevector) + >>> frobenius_distance = calculate_frobenius_distance(matrix_1, matrix_2) """ - 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 + 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/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/predicates/__init__.py b/quick/predicates/__init__.py index 4aec12a..ae673fe 100644 --- a/quick/predicates/__init__.py +++ b/quick/predicates/__init__.py @@ -13,23 +13,47 @@ # limitations under the License. __all__ = [ + "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", "is_unitary_matrix", "is_hermitian_matrix", "is_positive_semidefinite_matrix", - "is_isometry" + "is_isometry", + "is_density_matrix", + "is_product_matrix", + "is_locally_equivalent", + "is_supercontrolled" ] 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, is_unitary_matrix, is_hermitian_matrix, is_positive_semidefinite_matrix, - is_isometry + is_isometry, + 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 a0f9352..242fed7 100644 --- a/quick/predicates/predicates.py +++ b/quick/predicates/predicates.py @@ -18,23 +18,135 @@ from __future__ import annotations __all__ = [ + "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", "is_unitary_matrix", "is_hermitian_matrix", "is_positive_semidefinite_matrix", - "is_isometry" + "is_isometry", + "is_density_matrix", + "is_product_matrix", + "is_locally_equivalent", + "is_supercontrolled" ] 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 bool(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, + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT + ) -> 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. + `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 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 + + if statevector.ndim == 2: + if statevector.shape[1] == 1: + statevector = statevector.ravel() + + return bool( + is_normalized(statevector, 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. @@ -55,12 +167,157 @@ 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], - 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. @@ -85,12 +342,12 @@ 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], - 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. @@ -115,13 +372,13 @@ 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], - 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. @@ -156,12 +413,12 @@ 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], - 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. @@ -186,13 +443,13 @@ def is_unitary_matrix( if not is_square_matrix(matrix): return False - matrix = np.conj(matrix.T).dot(matrix) - return is_identity_matrix(matrix, ignore_phase=False, rtol=rtol, atol=atol) + matrix = matrix.conj().T @ matrix + return bool(is_identity_matrix(matrix, ignore_phase=False, rtol=rtol, atol=atol)) 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. @@ -217,12 +474,12 @@ 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 bool(np.allclose(matrix, matrix.conj().T, rtol=rtol, atol=atol)) 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. @@ -256,8 +513,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. @@ -279,6 +536,155 @@ 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 + matrix = matrix.conj().T @ matrix + return bool(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 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 + +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/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/contraction.py b/quick/primitives/contraction.py new file mode 100644 index 0000000..b2f6899 --- /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 = contract_indices[::-1] + + 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/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..43ac529 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 unitary matrices which represent operations applied to + quantum states (represented with qubits). It uses LSB convention. 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. @@ -53,13 +54,15 @@ 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. 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,52 +80,180 @@ def __init__( """ if label is None: self.label = "\N{LATIN CAPITAL LETTER A}\N{COMBINING CIRCUMFLEX ACCENT}" - self.is_unitary(data) + else: + self.label = label + + data = np.array(data) + + 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]))) + 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. + + 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() - @staticmethod - def is_unitary(data: NDArray[np.complex128]) -> None: - """ Check if a matrix is Hermitian. + def reverse_bits(self) -> None: + """ 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)) + 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 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 ---------- - `data` : NDArray[np.complex128] - The matrix to check. + `op` : NDArray[np.complex128] | Operator + The operator or matrix to contract with. + `qubit_indices` : list[int] + The qubit indices to contract over. 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. + 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 ----- - >>> data = np.array([[1+0j, 0+0j], - ... [0+0j, 1+0j]]) - >>> ishermitian(data) + >>> op1.contract(op2, [0, 1]) """ - # 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.") + from quick.primitives.contraction import 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, @@ -138,21 +269,54 @@ 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.") + + 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 + ) -> 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__( @@ -164,8 +328,8 @@ def __mul__( @overload def __mul__( self, - other: ket.Ket - ) -> ket.Ket: + other: statevector.Statevector + ) -> statevector.Statevector: ... @overload @@ -177,30 +341,29 @@ def __mul__( def __mul__( self, - other: Scalar | ket.Ket | Operator - ) -> Operator | ket.Ket: - """ Multiply an operator with a scalar, ket or another operator. + other: Scalar | statevector.Statevector | Operator + ) -> Operator | statevector.Statevector: + """ Multiply an operator with a number, statevector, or another operator. - The multiplication of an operator with a ket is defined as: + 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|ψ⟩ = |ψ'⟩ 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 +371,59 @@ 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, 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 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__( + raise TypeError(f"Multiplication with {type(other)} is not supported.") + + def __matmul__( self, - other: Scalar + other: Operator ) -> 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. + """ Calculate the tensor product of the two operators. Parameters ---------- - `other` : quick.primitives.Scalar - The scalar to multiply with. + `other` : quick.primitives.Operator + The operator to tensor with. Returns ------- quick.primitives.Operator - The operator multiplied by the scalar. + The tensor product of the two operators. Raises ------ - NotImplementedError - - If the `other` type is incompatible - - Usage - ----- - >>> scalar = 2 - >>> operator = Operator([[1+0j, 0+0j], - ... [0+0j, 1+0j]]) - >>> scalar * operator + TypeError + - If the `other` is not a `quick.primitives.Operator` instance. """ - if isinstance(other, (SupportsFloat, complex)): - return Operator((self.data * other).astype(np.complex128)) # type: ignore + if not isinstance(other, Operator): + raise TypeError(f"Cannot tensor Operator with {type(other)}.") - raise NotImplementedError(f"Multiplication with {type(other)} is not supported.") + return Operator(np.kron(self.data, other.data)) def __str__(self) -> str: """ Return the string representation of the operator. @@ -288,7 +434,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 +445,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..e03c0bd --- /dev/null +++ b/quick/primitives/statevector.py @@ -0,0 +1,728 @@ +# 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 + +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 +# 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. It uses LSB convention. + + 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. + `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, ...] + The shape of the statevector tensor based on qubits + as the physical dimension. + + 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.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 + ) -> Statevector: + """ 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. + + 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.") + + def is_normalized(self) -> None: + """ Check if a `quick.primitives.Statevector` instance is normalized to 2-norm. + + Usage + ----- + >>> statevector.is_normalized() + """ + self.normalized = is_normalized(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 + ----- + >>> statevector.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.primitives.Statevector` instance is padded to a power of 2. + + Usage + ----- + >>> statevector.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.primitives.Statevector` instance. + + Usage + ----- + >>> statevector.pad() + """ + if self.padded: + return + + self.data = self.pad_data(self.data, 2 ** self.num_qubits) + self.padded = True + + def to_quantumstate(self) -> None: + """ Ensure the statevector is in a valid quantum state. + + Usage + ----- + >>> statevector.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 + ----- + >>> statevector.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 + ----- + >>> statevector.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 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. + + 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) + + 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(self.tensor_shape) + rho = np.tensordot(state, state.conj(), axes=(trace_systems, trace_systems)) + rho = np.reshape(rho, traced_shape) + + 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 + ) -> 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 __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 + ) -> 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. + + 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 number. + + 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. + + 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 number. + + 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 __matmul__( + self, + other: Statevector + ) -> Statevector: + """ Calculate the tensor product of the 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. + + 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/random/__init__.py b/quick/random/__init__.py index b2f6305..dc6dbed 100644 --- a/quick/random/__init__.py +++ b/quick/random/__init__.py @@ -14,7 +14,18 @@ __all__ = [ "generate_random_state", - "generate_random_unitary" + "generate_random_unitary", + "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 \ No newline at end of file +from quick.random.random import ( + generate_random_state, + generate_random_unitary, + 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 d848c93..e538e50 100644 --- a/quick/random/random.py +++ b/quick/random/random.py @@ -16,14 +16,44 @@ __all__ = [ "generate_random_state", - "generate_random_unitary" + "generate_random_unitary", + "generate_random_density_matrix", + "generate_random_orthogonal_matrix", + "generate_random_special_orthogonal_matrix", + "generate_random_special_unitary_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 +83,111 @@ 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) + +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/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/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/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/two_qubit_decomposition.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.py index 7a351bd..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,124 +43,136 @@ """ 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] -], 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([ +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): """ `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,24 +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. """ - # 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) - - 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 @@ -400,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 @@ -426,22 +412,22 @@ 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) - 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) @@ -458,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 ---------- @@ -496,13 +470,12 @@ 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 - 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) @@ -542,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] @@ -556,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) @@ -628,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] @@ -642,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 56724eb..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" ] @@ -36,45 +33,70 @@ 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: -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=complex) +], 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([ [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 @@ -85,25 +107,26 @@ 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. + """ 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. @@ -114,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 ----- @@ -145,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 @@ -185,7 +209,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. @@ -283,7 +307,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 +322,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 +386,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] @@ -381,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] @@ -405,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 ---------- @@ -415,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] @@ -431,105 +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. - - 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. - - 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=complex, 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 = 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 - - D, P = diagonalize_unitary_complex_symmetric(M2) - d = -np.angle(D) / 2 - d[3] = -d[0] - d[1] - d[2] - weyl_coordinates = np.mod((d[:3] + d[3]) / 2, PI_DOUBLE) + 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) + + # 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), M_squared, rtol=0, atol=1e-13): + warnings.warn( + "Failed to diagonalize M_squared. " + "Kindly report this at https://github.com/Qualition/quick/issues/11: " + f"U: {U}" + ) + + # 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) @@ -575,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 4a46d76..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.primitives import Bra, Ket +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, @@ -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" + 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 16dd907..91fce8f 100644 --- a/quick/synthesis/statepreparation/mottonen.py +++ b/quick/synthesis/statepreparation/mottonen.py @@ -26,9 +26,9 @@ 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 ( +from quick.synthesis.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" + 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 2f3059e..1f02d91 100644 --- a/quick/synthesis/statepreparation/shende.py +++ b/quick/synthesis/statepreparation/shende.py @@ -26,9 +26,9 @@ 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 +from quick.synthesis.statepreparation.utils import rotations_to_disentangle class Shende(StatePreparation): @@ -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" + 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 c784d86..12ea854 100644 --- a/quick/synthesis/statepreparation/statepreparation.py +++ b/quick/synthesis/statepreparation/statepreparation.py @@ -24,12 +24,12 @@ 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: from quick.circuit import Circuit -from quick.primitives import Bra, Ket +from quick.primitives import Statevector class StatePreparation(ABC): @@ -56,7 +56,7 @@ class StatePreparation(ABC): """ def __init__( self, - output_framework: Type[Circuit] + output_framework: type[Circuit] ) -> None: """ Initalize a State Preparation instance. """ @@ -67,15 +67,15 @@ def __init__( def prepare_state( self, - state: NDArray[np.complex128] | Bra | Ket, - compression_percentage: float=0.0, - index_type: Literal["row", "snake"]="row" + state: NDArray[np.complex128] | Statevector, + compression_percentage: float = 0.0, + index_type: Literal["row", "snake"] = "row" ) -> Circuit: """ Prepare the quantum 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,10 +113,10 @@ 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" + compression_percentage: float = 0.0, + index_type: Literal["row", "snake"] = "row" ) -> Circuit: """ Apply the quantum state to a quantum circuit. @@ -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/quick/synthesis/statepreparation/statepreparation_utils.py b/quick/synthesis/statepreparation/utils.py similarity index 96% rename from quick/synthesis/statepreparation/statepreparation_utils.py rename to quick/synthesis/statepreparation/utils.py index 7530a1c..401c1a0 100644 --- a/quick/synthesis/statepreparation/statepreparation_utils.py +++ b/quick/synthesis/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/diffusion.py b/quick/synthesis/unitarypreparation/diffusion.py index 6e8de97..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: @@ -98,11 +99,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 096411d..12684a2 100644 --- a/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.py +++ b/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.py @@ -23,12 +23,12 @@ 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 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 @@ -96,9 +96,9 @@ class QiskitUnitaryTranspiler(UnitaryPreparation): """ def __init__( self, - output_framework: Type[Circuit], - ai_transpilation: bool=False, - unitary_synthesis_plugin: str="default", + output_framework: type[Circuit], + 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 6df9dcf..0382cba 100644 --- a/quick/synthesis/unitarypreparation/shannon_decomposition.py +++ b/quick/synthesis/unitarypreparation/shannon_decomposition.py @@ -29,16 +29,11 @@ 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 -# Constants -QUBIT_KEYS = frozenset([ - "qubit_index", "control_index", "target_index" -]) - class ShannonDecomposition(UnitaryPreparation): """ `quick.ShannonDecomposition` is the class for preparing quantum operators @@ -142,7 +137,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 +221,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. @@ -291,8 +286,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 @@ -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/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/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/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/circuit.pyi b/stubs/quick/circuit/circuit.pyi deleted file mode 100644 index 26ff27f..0000000 --- a/stubs/quick/circuit/circuit.pyi +++ /dev/null @@ -1,346 +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, Type - -__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 __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 e2f018a..0000000 --- a/stubs/quick/circuit/circuit_utils.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 -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" -] - -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]]]: ... 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 16743e1..0000000 --- a/stubs/quick/circuit/dag/dagnode.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. - -from dataclasses import dataclass -from typing import Hashable - -__all__ = ["DAGNode"] - -@dataclass -class DAGNode: - name: Hashable = ... - parents: set[DAGNode] = ... - children: set[DAGNode] = ... - def to(self, child: DAGNode) -> None: ... - @property - def depth(self) -> int: ... - def generate_paths(self) -> set[tuple[Hashable]]: ... - def __hash__(self) -> int: ... - 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 9f8bb91..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, Type - -__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 a683aea..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 Type, 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 1a4bdda..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, Type - -__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 732d893..0000000 --- a/stubs/quick/synthesis/unitarypreparation/unitarypreparation.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 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 -from typing import Type - -__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/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 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/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/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 1e30cd2..0000000 Binary files a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_3qubits_01control_HXHX.npy and /dev/null differ diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_3qubits_10control_HYHY.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_3qubits_10control_HYHY.npy deleted file mode 100644 index 0ac8dc2..0000000 Binary files a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_3qubits_10control_HYHY.npy and /dev/null differ 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 f9200c3..0000000 Binary files a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY.npy and /dev/null differ 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 33376d8..0000000 Binary files a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY.npy and /dev/null differ diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_3qubits_01control_HXHX.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_3qubits_01control_HXHX.npy deleted file mode 100644 index 12df6a3..0000000 Binary files a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_3qubits_01control_HXHX.npy and /dev/null differ 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 f6152f0..0000000 Binary files a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_3qubits_10control_HYHY.npy and /dev/null differ diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_4qubits_023control_RXRYRXRYRXRY.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_4qubits_023control_RXRYRXRYRXRY.npy deleted file mode 100644 index f9200c3..0000000 Binary files a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_4qubits_023control_RXRYRXRYRXRY.npy and /dev/null differ 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 33376d8..0000000 Binary files a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY.npy and /dev/null differ 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 13aad2c..0000000 Binary files a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_3qubits_01control_HXHX.npy and /dev/null differ 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 060bc31..0000000 Binary files a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_3qubits_10control_HYHY.npy and /dev/null differ 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 ba5dd70..0000000 Binary files a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY.npy and /dev/null differ diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY.npy deleted file mode 100644 index 226cf2c..0000000 Binary files a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY.npy and /dev/null differ diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_3qubits_01control_HXHX.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_3qubits_01control_HXHX.npy deleted file mode 100644 index ff9755a..0000000 Binary files a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_3qubits_01control_HXHX.npy and /dev/null differ 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 711ae84..0000000 Binary files a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_3qubits_10control_HYHY.npy and /dev/null differ diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_4qubits_023control_RXRYRXRYRXRY.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_4qubits_023control_RXRYRXRYRXRY.npy deleted file mode 100644 index ba5dd70..0000000 Binary files a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_4qubits_023control_RXRYRXRYRXRY.npy and /dev/null differ diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY.npy deleted file mode 100644 index 226cf2c..0000000 Binary files a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY.npy and /dev/null differ diff --git a/tests/circuit/gate_utils.py b/tests/circuit/gate_utils.py index cb82384..6a61b03 100644 --- a/tests/circuit/gate_utils.py +++ b/tests/circuit/gate_utils.py @@ -357,22 +357,6 @@ "UCRZ_unitary_matrix_3qubits_10control", "UCRZ_unitary_matrix_4qubits_023control", "UCRZ_unitary_matrix_4qubits_213control", - "UC_unitary_matrix_no_diagonal_no_simplification_3qubits_01control_HXHX", - "UC_unitary_matrix_no_diagonal_no_simplification_3qubits_10control_HYHY", - "UC_unitary_matrix_no_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY", - "UC_unitary_matrix_no_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY", - "UC_unitary_matrix_diagonal_no_simplification_3qubits_01control_HXHX", - "UC_unitary_matrix_diagonal_no_simplification_3qubits_10control_HYHY", - "UC_unitary_matrix_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY", - "UC_unitary_matrix_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY", - "UC_unitary_matrix_no_diagonal_simplification_3qubits_01control_HXHX", - "UC_unitary_matrix_no_diagonal_simplification_3qubits_10control_HYHY", - "UC_unitary_matrix_no_diagonal_simplification_4qubits_023control_RXRYRXRYRXRY", - "UC_unitary_matrix_no_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY", - "UC_unitary_matrix_diagonal_simplification_3qubits_01control_HXHX", - "UC_unitary_matrix_diagonal_simplification_3qubits_10control_HYHY", - "UC_unitary_matrix_diagonal_simplification_4qubits_023control_RXRYRXRYRXRY", - "UC_unitary_matrix_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY", "qft_no_swap_no_inverse_approx0_5qubits", "qft_no_swap_no_inverse_approx0_6qubits", "qft_no_swap_no_inverse_approx0_7qubits", @@ -436,7 +420,7 @@ "qft_swap_inverse_approx3_5qubits", "qft_swap_inverse_approx3_6qubits", "qft_swap_inverse_approx3_7qubits", - "qft_swap_inverse_approx3_8qubits" + "qft_swap_inverse_approx3_8qubits", ] import numpy as np @@ -519,7 +503,9 @@ `global_shift` = 1/3 `qubit_indices` = 0 """ -XPow_global_shift_unitary_matrix = np.load(prefix + "XPow_global_shift_unitary_matrix.npy") +XPow_global_shift_unitary_matrix = np.load( + prefix + "XPow_global_shift_unitary_matrix.npy" +) """ circuit.YPow() tester @@ -539,7 +525,9 @@ `global_shift` = 1/3 `qubit_indices` = 0 """ -YPow_global_shift_unitary_matrix = np.load(prefix + "YPow_global_shift_unitary_matrix.npy") +YPow_global_shift_unitary_matrix = np.load( + prefix + "YPow_global_shift_unitary_matrix.npy" +) """ circuit.ZPow() tester @@ -559,7 +547,9 @@ `global_shift` = 1/3 `qubit_indices` = 0 """ -ZPow_global_shift_unitary_matrix = np.load(prefix + "ZPow_global_shift_unitary_matrix.npy") +ZPow_global_shift_unitary_matrix = np.load( + prefix + "ZPow_global_shift_unitary_matrix.npy" +) """ circuit.RXX() tester @@ -569,7 +559,9 @@ `first_qubit_index` = 0 `second_qubit_index` = 1 """ -RXX_unitary_matrix_pi_over_4_01qubits = np.load(prefix + "RXX_unitary_matrix_pi_over_4_01qubits.npy") +RXX_unitary_matrix_pi_over_4_01qubits = np.load( + prefix + "RXX_unitary_matrix_pi_over_4_01qubits.npy" +) """ circuit.RXX() tester @@ -579,7 +571,9 @@ `first_qubit_index` = 1 `second_qubit_index` = 0 """ -RXX_unitary_matrix_pi_over_4_10qubits = np.load(prefix + "RXX_unitary_matrix_pi_over_4_10qubits.npy") +RXX_unitary_matrix_pi_over_4_10qubits = np.load( + prefix + "RXX_unitary_matrix_pi_over_4_10qubits.npy" +) """ circuit.RXX() tester @@ -589,7 +583,9 @@ `first_qubit_index` = 0 `second_qubit_index` = 2 """ -RXX_unitary_matrix_1_over_4_02qubits = np.load(prefix + "RXX_unitary_matrix_1_over_4_02qubits.npy") +RXX_unitary_matrix_1_over_4_02qubits = np.load( + prefix + "RXX_unitary_matrix_1_over_4_02qubits.npy" +) """ circuit.RXX() tester @@ -599,7 +595,9 @@ `first_qubit_index` = 2 `second_qubit_index` = 0 """ -RXX_unitary_matrix_1_over_4_20qubits = np.load(prefix + "RXX_unitary_matrix_1_over_4_20qubits.npy") +RXX_unitary_matrix_1_over_4_20qubits = np.load( + prefix + "RXX_unitary_matrix_1_over_4_20qubits.npy" +) """ circuit.RXX() tester @@ -609,7 +607,9 @@ `first_qubit_index` = 1 `second_qubit_index` = 2 """ -RXX_unitary_matrix_pi_over_4_12qubits = np.load(prefix + "RXX_unitary_matrix_pi_over_4_12qubits.npy") +RXX_unitary_matrix_pi_over_4_12qubits = np.load( + prefix + "RXX_unitary_matrix_pi_over_4_12qubits.npy" +) """ circuit.RYY() tester @@ -619,7 +619,9 @@ `first_qubit_index` = 0 `second_qubit_index` = 1 """ -RYY_unitary_matrix_pi_over_4_01qubits = np.load(prefix + "RYY_unitary_matrix_pi_over_4_01qubits.npy") +RYY_unitary_matrix_pi_over_4_01qubits = np.load( + prefix + "RYY_unitary_matrix_pi_over_4_01qubits.npy" +) """ circuit.RYY() tester @@ -629,7 +631,9 @@ `first_qubit_index` = 1 `second_qubit_index` = 0 """ -RYY_unitary_matrix_pi_over_4_10qubits = np.load(prefix + "RYY_unitary_matrix_pi_over_4_10qubits.npy") +RYY_unitary_matrix_pi_over_4_10qubits = np.load( + prefix + "RYY_unitary_matrix_pi_over_4_10qubits.npy" +) """ circuit.RYY() tester @@ -639,7 +643,9 @@ `first_qubit_index` = 0 `second_qubit_index` = 2 """ -RYY_unitary_matrix_1_over_4_02qubits = np.load(prefix + "RYY_unitary_matrix_1_over_4_02qubits.npy") +RYY_unitary_matrix_1_over_4_02qubits = np.load( + prefix + "RYY_unitary_matrix_1_over_4_02qubits.npy" +) """ circuit.RYY() tester @@ -649,7 +655,9 @@ `first_qubit_index` = 2 `second_qubit_index` = 0 """ -RYY_unitary_matrix_1_over_4_20qubits = np.load(prefix + "RYY_unitary_matrix_1_over_4_20qubits.npy") +RYY_unitary_matrix_1_over_4_20qubits = np.load( + prefix + "RYY_unitary_matrix_1_over_4_20qubits.npy" +) """ circuit.RYY() tester @@ -659,7 +667,9 @@ `first_qubit_index` = 1 `second_qubit_index` = 2 """ -RYY_unitary_matrix_pi_over_4_12qubits = np.load(prefix + "RYY_unitary_matrix_pi_over_4_12qubits.npy") +RYY_unitary_matrix_pi_over_4_12qubits = np.load( + prefix + "RYY_unitary_matrix_pi_over_4_12qubits.npy" +) """ circuit.RZZ() tester @@ -669,7 +679,9 @@ `first_qubit_index` = 0 `second_qubit_index` = 1 """ -RZZ_unitary_matrix_pi_over_4_01qubits = np.load(prefix + "RZZ_unitary_matrix_pi_over_4_01qubits.npy") +RZZ_unitary_matrix_pi_over_4_01qubits = np.load( + prefix + "RZZ_unitary_matrix_pi_over_4_01qubits.npy" +) """ circuit.RZZ() tester @@ -679,7 +691,9 @@ `first_qubit_index` = 1 `second_qubit_index` = 0 """ -RZZ_unitary_matrix_pi_over_4_10qubits = np.load(prefix + "RZZ_unitary_matrix_pi_over_4_10qubits.npy") +RZZ_unitary_matrix_pi_over_4_10qubits = np.load( + prefix + "RZZ_unitary_matrix_pi_over_4_10qubits.npy" +) """ circuit.RZZ() tester @@ -689,7 +703,9 @@ `first_qubit_index` = 0 `second_qubit_index` = 2 """ -RZZ_unitary_matrix_1_over_4_02qubits = np.load(prefix + "RZZ_unitary_matrix_1_over_4_02qubits.npy") +RZZ_unitary_matrix_1_over_4_02qubits = np.load( + prefix + "RZZ_unitary_matrix_1_over_4_02qubits.npy" +) """ circuit.RZZ() tester @@ -699,7 +715,9 @@ `first_qubit_index` = 2 `second_qubit_index` = 0 """ -RZZ_unitary_matrix_1_over_4_20qubits = np.load(prefix + "RZZ_unitary_matrix_1_over_4_20qubits.npy") +RZZ_unitary_matrix_1_over_4_20qubits = np.load( + prefix + "RZZ_unitary_matrix_1_over_4_20qubits.npy" +) """ circuit.RZZ() tester @@ -709,7 +727,9 @@ `first_qubit_index` = 1 `second_qubit_index` = 2 """ -RZZ_unitary_matrix_pi_over_4_12qubits = np.load(prefix + "RZZ_unitary_matrix_pi_over_4_12qubits.npy") +RZZ_unitary_matrix_pi_over_4_12qubits = np.load( + prefix + "RZZ_unitary_matrix_pi_over_4_12qubits.npy" +) """ circuit.SWAP() tester @@ -1124,7 +1144,9 @@ `control_qubit_index` = 0 `target_qubit_index` = 1 """ -CRX_unitary_matrix_pi_over_4_01qubits = np.load(prefix + "CRX_unitary_matrix_pi_over_4_01qubits.npy") +CRX_unitary_matrix_pi_over_4_01qubits = np.load( + prefix + "CRX_unitary_matrix_pi_over_4_01qubits.npy" +) """ circuit.CRX() tester @@ -1134,7 +1156,9 @@ `control_qubit_index` = 1 `target_qubit_index` = 0 """ -CRX_unitary_matrix_pi_over_4_10qubits = np.load(prefix + "CRX_unitary_matrix_pi_over_4_10qubits.npy") +CRX_unitary_matrix_pi_over_4_10qubits = np.load( + prefix + "CRX_unitary_matrix_pi_over_4_10qubits.npy" +) """ circuit.CRX() tester @@ -1144,7 +1168,9 @@ `control_qubit_index` = 0 `target_qubit_index` = 2 """ -CRX_unitary_matrix_1_over_4_02qubits = np.load(prefix + "CRX_unitary_matrix_1_over_4_02qubits.npy") +CRX_unitary_matrix_1_over_4_02qubits = np.load( + prefix + "CRX_unitary_matrix_1_over_4_02qubits.npy" +) """ circuit.CRX() tester @@ -1154,7 +1180,9 @@ `control_qubit_index` = 2 `target_qubit_index` = 0 """ -CRX_unitary_matrix_1_over_4_20qubits = np.load(prefix + "CRX_unitary_matrix_1_over_4_20qubits.npy") +CRX_unitary_matrix_1_over_4_20qubits = np.load( + prefix + "CRX_unitary_matrix_1_over_4_20qubits.npy" +) """ circuit.CRX() tester @@ -1164,7 +1192,9 @@ `control_qubit_index` = 1 `target_qubit_index` = 2 """ -CRX_unitary_matrix_pi_over_4_12qubits = np.load(prefix + "CRX_unitary_matrix_pi_over_4_12qubits.npy") +CRX_unitary_matrix_pi_over_4_12qubits = np.load( + prefix + "CRX_unitary_matrix_pi_over_4_12qubits.npy" +) """ circuit.CRY() tester @@ -1174,7 +1204,9 @@ `control_qubit_index` = 0 `target_qubit_index` = 1 """ -CRY_unitary_matrix_pi_over_4_01qubits = np.load(prefix + "CRY_unitary_matrix_pi_over_4_01qubits.npy") +CRY_unitary_matrix_pi_over_4_01qubits = np.load( + prefix + "CRY_unitary_matrix_pi_over_4_01qubits.npy" +) """ circuit.CRY() tester @@ -1184,7 +1216,9 @@ `control_qubit_index` = 1 `target_qubit_index` = 0 """ -CRY_unitary_matrix_pi_over_4_10qubits = np.load(prefix + "CRY_unitary_matrix_pi_over_4_10qubits.npy") +CRY_unitary_matrix_pi_over_4_10qubits = np.load( + prefix + "CRY_unitary_matrix_pi_over_4_10qubits.npy" +) """ circuit.CRY() tester @@ -1194,7 +1228,9 @@ `control_qubit_index` = 0 `target_qubit_index` = 2 """ -CRY_unitary_matrix_1_over_4_02qubits = np.load(prefix + "CRY_unitary_matrix_1_over_4_02qubits.npy") +CRY_unitary_matrix_1_over_4_02qubits = np.load( + prefix + "CRY_unitary_matrix_1_over_4_02qubits.npy" +) """ circuit.CRY() tester @@ -1204,7 +1240,9 @@ `control_qubit_index` = 2 `target_qubit_index` = 0 """ -CRY_unitary_matrix_1_over_4_20qubits = np.load(prefix + "CRY_unitary_matrix_1_over_4_20qubits.npy") +CRY_unitary_matrix_1_over_4_20qubits = np.load( + prefix + "CRY_unitary_matrix_1_over_4_20qubits.npy" +) """ circuit.CRY() tester @@ -1214,7 +1252,9 @@ `control_qubit_index` = 1 `target_qubit_index` = 2 """ -CRY_unitary_matrix_pi_over_4_12qubits = np.load(prefix + "CRY_unitary_matrix_pi_over_4_12qubits.npy") +CRY_unitary_matrix_pi_over_4_12qubits = np.load( + prefix + "CRY_unitary_matrix_pi_over_4_12qubits.npy" +) """ circuit.CRZ() tester @@ -1224,7 +1264,9 @@ `control_qubit_index` = 0 `target_qubit_index` = 1 """ -CRZ_unitary_matrix_pi_over_4_01qubits = np.load(prefix + "CRZ_unitary_matrix_pi_over_4_01qubits.npy") +CRZ_unitary_matrix_pi_over_4_01qubits = np.load( + prefix + "CRZ_unitary_matrix_pi_over_4_01qubits.npy" +) """ circuit.CRZ() tester @@ -1234,7 +1276,9 @@ `control_qubit_index` = 1 `target_qubit_index` = 0 """ -CRZ_unitary_matrix_pi_over_4_10qubits = np.load(prefix + "CRZ_unitary_matrix_pi_over_4_10qubits.npy") +CRZ_unitary_matrix_pi_over_4_10qubits = np.load( + prefix + "CRZ_unitary_matrix_pi_over_4_10qubits.npy" +) """ circuit.CRZ() tester @@ -1244,7 +1288,9 @@ `control_qubit_index` = 0 `target_qubit_index` = 2 """ -CRZ_unitary_matrix_1_over_4_02qubits = np.load(prefix + "CRZ_unitary_matrix_1_over_4_02qubits.npy") +CRZ_unitary_matrix_1_over_4_02qubits = np.load( + prefix + "CRZ_unitary_matrix_1_over_4_02qubits.npy" +) """ circuit.CRZ() tester @@ -1254,7 +1300,9 @@ `control_qubit_index` = 2 `target_qubit_index` = 0 """ -CRZ_unitary_matrix_1_over_4_20qubits = np.load(prefix + "CRZ_unitary_matrix_1_over_4_20qubits.npy") +CRZ_unitary_matrix_1_over_4_20qubits = np.load( + prefix + "CRZ_unitary_matrix_1_over_4_20qubits.npy" +) """ circuit.CRZ() tester @@ -1264,7 +1312,9 @@ `control_qubit_index` = 1 `target_qubit_index` = 2 """ -CRZ_unitary_matrix_pi_over_4_12qubits = np.load(prefix + "CRZ_unitary_matrix_pi_over_4_12qubits.npy") +CRZ_unitary_matrix_pi_over_4_12qubits = np.load( + prefix + "CRZ_unitary_matrix_pi_over_4_12qubits.npy" +) """ circuit.CPhase() tester @@ -1274,7 +1324,9 @@ `control_qubit_index` = 0 `target_qubit_index` = 1 """ -CPhase_unitary_matrix_pi_over_4_01qubits = np.load(prefix + "CPhase_unitary_matrix_pi_over_4_01qubits.npy") +CPhase_unitary_matrix_pi_over_4_01qubits = np.load( + prefix + "CPhase_unitary_matrix_pi_over_4_01qubits.npy" +) """ circuit.CPhase() tester @@ -1284,7 +1336,9 @@ `control_qubit_index` = 1 `target_qubit_index` = 0 """ -CPhase_unitary_matrix_pi_over_4_10qubits = np.load(prefix + "CPhase_unitary_matrix_pi_over_4_10qubits.npy") +CPhase_unitary_matrix_pi_over_4_10qubits = np.load( + prefix + "CPhase_unitary_matrix_pi_over_4_10qubits.npy" +) """ circuit.CPhase() tester @@ -1294,7 +1348,9 @@ `control_qubit_index` = 0 `target_qubit_index` = 2 """ -CPhase_unitary_matrix_1_over_4_02qubits = np.load(prefix + "CPhase_unitary_matrix_1_over_4_02qubits.npy") +CPhase_unitary_matrix_1_over_4_02qubits = np.load( + prefix + "CPhase_unitary_matrix_1_over_4_02qubits.npy" +) """ circuit.CPhase() tester @@ -1304,7 +1360,9 @@ `control_qubit_index` = 2 `target_qubit_index` = 0 """ -CPhase_unitary_matrix_1_over_4_20qubits = np.load(prefix + "CPhase_unitary_matrix_1_over_4_20qubits.npy") +CPhase_unitary_matrix_1_over_4_20qubits = np.load( + prefix + "CPhase_unitary_matrix_1_over_4_20qubits.npy" +) """ circuit.CPhase() tester @@ -1314,7 +1372,9 @@ `control_qubit_index` = 1 `target_qubit_index` = 2 """ -CPhase_unitary_matrix_pi_over_4_12qubits = np.load(prefix + "CPhase_unitary_matrix_pi_over_4_12qubits.npy") +CPhase_unitary_matrix_pi_over_4_12qubits = np.load( + prefix + "CPhase_unitary_matrix_pi_over_4_12qubits.npy" +) """ circuit.CXPow() tester @@ -1325,7 +1385,9 @@ `control_qubit_index` = 0 `target_qubit_index` = 1 """ -CXPow_unitary_matrix_1_over_4_0_shift_01qubits = np.load(prefix + "CXPow_unitary_matrix_1_over_4_0_shift_01qubits.npy") +CXPow_unitary_matrix_1_over_4_0_shift_01qubits = np.load( + prefix + "CXPow_unitary_matrix_1_over_4_0_shift_01qubits.npy" +) """ circuit.CXPow() tester @@ -1336,7 +1398,9 @@ `control_qubit_index` = 1 `target_qubit_index` = 0 """ -CXPow_unitary_matrix_1_over_4_0_shift_10qubits = np.load(prefix + "CXPow_unitary_matrix_1_over_4_0_shift_10qubits.npy") +CXPow_unitary_matrix_1_over_4_0_shift_10qubits = np.load( + prefix + "CXPow_unitary_matrix_1_over_4_0_shift_10qubits.npy" +) """ circuit.CXPow() tester @@ -1347,7 +1411,9 @@ `control_qubit_index` = 0 `target_qubit_index` = 2 """ -CXPow_unitary_matrix_1_over_4_1_over_3_shift_02qubits = np.load(prefix + "CXPow_unitary_matrix_1_over_4_1_over_3_shift_02qubits.npy") +CXPow_unitary_matrix_1_over_4_1_over_3_shift_02qubits = np.load( + prefix + "CXPow_unitary_matrix_1_over_4_1_over_3_shift_02qubits.npy" +) """ circuit.CXPow() tester @@ -1358,7 +1424,9 @@ `control_qubit_index` = 2 `target_qubit_index` = 0 """ -CXPow_unitary_matrix_1_over_4_1_over_3_shift_20qubits = np.load(prefix + "CXPow_unitary_matrix_1_over_4_1_over_3_shift_20qubits.npy") +CXPow_unitary_matrix_1_over_4_1_over_3_shift_20qubits = np.load( + prefix + "CXPow_unitary_matrix_1_over_4_1_over_3_shift_20qubits.npy" +) """ circuit.CXPow() tester @@ -1369,7 +1437,9 @@ `control_qubit_index` = 1 `target_qubit_index` = 2 """ -CXPow_unitary_matrix_negative1_over_4_0_shift_12qubits = np.load(prefix + "CXPow_unitary_matrix_negative1_over_4_0_shift_12qubits.npy") +CXPow_unitary_matrix_negative1_over_4_0_shift_12qubits = np.load( + prefix + "CXPow_unitary_matrix_negative1_over_4_0_shift_12qubits.npy" +) """ circuit.CYPow() tester @@ -1380,7 +1450,9 @@ `control_qubit_index` = 0 `target_qubit_index` = 1 """ -CYPow_unitary_matrix_1_over_4_0_shift_01qubits = np.load(prefix + "CYPow_unitary_matrix_1_over_4_0_shift_01qubits.npy") +CYPow_unitary_matrix_1_over_4_0_shift_01qubits = np.load( + prefix + "CYPow_unitary_matrix_1_over_4_0_shift_01qubits.npy" +) """ circuit.CYPow() tester @@ -1391,7 +1463,9 @@ `control_qubit_index` = 1 `target_qubit_index` = 0 """ -CYPow_unitary_matrix_1_over_4_0_shift_10qubits = np.load(prefix + "CYPow_unitary_matrix_1_over_4_0_shift_10qubits.npy") +CYPow_unitary_matrix_1_over_4_0_shift_10qubits = np.load( + prefix + "CYPow_unitary_matrix_1_over_4_0_shift_10qubits.npy" +) """ circuit.CYPow() tester @@ -1402,7 +1476,9 @@ `control_qubit_index` = 0 `target_qubit_index` = 2 """ -CYPow_unitary_matrix_1_over_4_1_over_3_shift_02qubits = np.load(prefix + "CYPow_unitary_matrix_1_over_4_1_over_3_shift_02qubits.npy") +CYPow_unitary_matrix_1_over_4_1_over_3_shift_02qubits = np.load( + prefix + "CYPow_unitary_matrix_1_over_4_1_over_3_shift_02qubits.npy" +) """ circuit.CYPow() tester @@ -1413,7 +1489,9 @@ `control_qubit_index` = 2 `target_qubit_index` = 0 """ -CYPow_unitary_matrix_1_over_4_1_over_3_shift_20qubits = np.load(prefix + "CYPow_unitary_matrix_1_over_4_1_over_3_shift_20qubits.npy") +CYPow_unitary_matrix_1_over_4_1_over_3_shift_20qubits = np.load( + prefix + "CYPow_unitary_matrix_1_over_4_1_over_3_shift_20qubits.npy" +) """ circuit.CYPow() tester @@ -1424,7 +1502,9 @@ `control_qubit_index` = 1 `target_qubit_index` = 2 """ -CYPow_unitary_matrix_negative1_over_4_0_shift_12qubits = np.load(prefix + "CYPow_unitary_matrix_negative1_over_4_0_shift_12qubits.npy") +CYPow_unitary_matrix_negative1_over_4_0_shift_12qubits = np.load( + prefix + "CYPow_unitary_matrix_negative1_over_4_0_shift_12qubits.npy" +) """ circuit.CZPow() tester @@ -1435,7 +1515,9 @@ `control_qubit_index` = 0 `target_qubit_index` = 1 """ -CZPow_unitary_matrix_1_over_4_0_shift_01qubits = np.load(prefix + "CZPow_unitary_matrix_1_over_4_0_shift_01qubits.npy") +CZPow_unitary_matrix_1_over_4_0_shift_01qubits = np.load( + prefix + "CZPow_unitary_matrix_1_over_4_0_shift_01qubits.npy" +) """ circuit.CZPow() tester @@ -1446,7 +1528,9 @@ `control_qubit_index` = 1 `target_qubit_index` = 0 """ -CZPow_unitary_matrix_1_over_4_0_shift_10qubits = np.load(prefix + "CZPow_unitary_matrix_1_over_4_0_shift_10qubits.npy") +CZPow_unitary_matrix_1_over_4_0_shift_10qubits = np.load( + prefix + "CZPow_unitary_matrix_1_over_4_0_shift_10qubits.npy" +) """ circuit.CZPow() tester @@ -1457,7 +1541,9 @@ `control_qubit_index` = 0 `target_qubit_index` = 2 """ -CZPow_unitary_matrix_1_over_4_1_over_3_shift_02qubits = np.load(prefix + "CZPow_unitary_matrix_1_over_4_1_over_3_shift_02qubits.npy") +CZPow_unitary_matrix_1_over_4_1_over_3_shift_02qubits = np.load( + prefix + "CZPow_unitary_matrix_1_over_4_1_over_3_shift_02qubits.npy" +) """ circuit.CZPow() tester @@ -1468,7 +1554,9 @@ `control_qubit_index` = 2 `target_qubit_index` = 0 """ -CZPow_unitary_matrix_1_over_4_1_over_3_shift_20qubits = np.load(prefix + "CZPow_unitary_matrix_1_over_4_1_over_3_shift_20qubits.npy") +CZPow_unitary_matrix_1_over_4_1_over_3_shift_20qubits = np.load( + prefix + "CZPow_unitary_matrix_1_over_4_1_over_3_shift_20qubits.npy" +) """ circuit.CZPow() tester @@ -1479,7 +1567,9 @@ `control_qubit_index` = 1 `target_qubit_index` = 2 """ -CZPow_unitary_matrix_negative1_over_4_0_shift_12qubits = np.load(prefix + "CZPow_unitary_matrix_negative1_over_4_0_shift_12qubits.npy") +CZPow_unitary_matrix_negative1_over_4_0_shift_12qubits = np.load( + prefix + "CZPow_unitary_matrix_negative1_over_4_0_shift_12qubits.npy" +) """ circuit.CRXX() tester @@ -1490,7 +1580,9 @@ `first_target_index` = 1 `second_target_index` = 2 """ -CRXX_unitary_matrix_pi_over_4_012qubits = np.load(prefix + "CRXX_unitary_matrix_pi_over_4_012qubits.npy") +CRXX_unitary_matrix_pi_over_4_012qubits = np.load( + prefix + "CRXX_unitary_matrix_pi_over_4_012qubits.npy" +) """ circuit.CRXX() tester @@ -1501,7 +1593,9 @@ `first_target_index` = 0 `second_target_index` = 2 """ -CRXX_unitary_matrix_pi_over_4_102qubits = np.load(prefix + "CRXX_unitary_matrix_pi_over_4_102qubits.npy") +CRXX_unitary_matrix_pi_over_4_102qubits = np.load( + prefix + "CRXX_unitary_matrix_pi_over_4_102qubits.npy" +) """ circuit.CRXX() tester @@ -1512,7 +1606,9 @@ `first_target_index` = 2 `second_target_index` = 3 """ -CRXX_unitary_matrix_1_over_4_123qubits = np.load(prefix + "CRXX_unitary_matrix_1_over_4_123qubits.npy") +CRXX_unitary_matrix_1_over_4_123qubits = np.load( + prefix + "CRXX_unitary_matrix_1_over_4_123qubits.npy" +) """ circuit.CRXX() tester @@ -1523,7 +1619,9 @@ `first_target_index` = 1 `second_target_index` = 3 """ -CRXX_unitary_matrix_1_over_4_213qubits = np.load(prefix + "CRXX_unitary_matrix_1_over_4_213qubits.npy") +CRXX_unitary_matrix_1_over_4_213qubits = np.load( + prefix + "CRXX_unitary_matrix_1_over_4_213qubits.npy" +) """ circuit.CRXX() tester @@ -1534,7 +1632,9 @@ `first_target_index` = 2 `second_target_index` = 3 """ -CRXX_unitary_matrix_pi_over_4_023qubits = np.load(prefix + "CRXX_unitary_matrix_pi_over_4_023qubits.npy") +CRXX_unitary_matrix_pi_over_4_023qubits = np.load( + prefix + "CRXX_unitary_matrix_pi_over_4_023qubits.npy" +) """ circuit.CRYY() tester @@ -1545,7 +1645,9 @@ `first_target_index` = 1 `second_target_index` = 2 """ -CRYY_unitary_matrix_pi_over_4_012qubits = np.load(prefix + "CRYY_unitary_matrix_pi_over_4_012qubits.npy") +CRYY_unitary_matrix_pi_over_4_012qubits = np.load( + prefix + "CRYY_unitary_matrix_pi_over_4_012qubits.npy" +) """ circuit.CRYY() tester @@ -1556,7 +1658,9 @@ `first_target_index` = 0 `second_target_index` = 2 """ -CRYY_unitary_matrix_pi_over_4_102qubits = np.load(prefix + "CRYY_unitary_matrix_pi_over_4_102qubits.npy") +CRYY_unitary_matrix_pi_over_4_102qubits = np.load( + prefix + "CRYY_unitary_matrix_pi_over_4_102qubits.npy" +) """ circuit.CRYY() tester @@ -1567,7 +1671,9 @@ `first_target_index` = 2 `second_target_index` = 3 """ -CRYY_unitary_matrix_1_over_4_123qubits = np.load(prefix + "CRYY_unitary_matrix_1_over_4_123qubits.npy") +CRYY_unitary_matrix_1_over_4_123qubits = np.load( + prefix + "CRYY_unitary_matrix_1_over_4_123qubits.npy" +) """ circuit.CRYY() tester @@ -1578,7 +1684,9 @@ `first_target_index` = 1 `second_target_index` = 3 """ -CRYY_unitary_matrix_1_over_4_213qubits = np.load(prefix + "CRYY_unitary_matrix_1_over_4_213qubits.npy") +CRYY_unitary_matrix_1_over_4_213qubits = np.load( + prefix + "CRYY_unitary_matrix_1_over_4_213qubits.npy" +) """ circuit.CRYY() tester @@ -1589,7 +1697,9 @@ `first_target_index` = 2 `second_target_index` = 3 """ -CRYY_unitary_matrix_pi_over_4_023qubits = np.load(prefix + "CRYY_unitary_matrix_pi_over_4_023qubits.npy") +CRYY_unitary_matrix_pi_over_4_023qubits = np.load( + prefix + "CRYY_unitary_matrix_pi_over_4_023qubits.npy" +) """ circuit.CRZZ() tester @@ -1600,7 +1710,9 @@ `first_target_index` = 1 `second_target_index` = 2 """ -CRZZ_unitary_matrix_pi_over_4_012qubits = np.load(prefix + "CRZZ_unitary_matrix_pi_over_4_012qubits.npy") +CRZZ_unitary_matrix_pi_over_4_012qubits = np.load( + prefix + "CRZZ_unitary_matrix_pi_over_4_012qubits.npy" +) """ circuit.CRZZ() tester @@ -1611,7 +1723,9 @@ `first_target_index` = 0 `second_target_index` = 2 """ -CRZZ_unitary_matrix_pi_over_4_102qubits = np.load(prefix + "CRZZ_unitary_matrix_pi_over_4_102qubits.npy") +CRZZ_unitary_matrix_pi_over_4_102qubits = np.load( + prefix + "CRZZ_unitary_matrix_pi_over_4_102qubits.npy" +) """ circuit.CRZZ() tester @@ -1622,7 +1736,9 @@ `first_target_index` = 2 `second_target_index` = 3 """ -CRZZ_unitary_matrix_1_over_4_123qubits = np.load(prefix + "CRZZ_unitary_matrix_1_over_4_123qubits.npy") +CRZZ_unitary_matrix_1_over_4_123qubits = np.load( + prefix + "CRZZ_unitary_matrix_1_over_4_123qubits.npy" +) """ circuit.CRZZ() tester @@ -1633,7 +1749,9 @@ `first_target_index` = 1 `second_target_index` = 3 """ -CRZZ_unitary_matrix_1_over_4_213qubits = np.load(prefix + "CRZZ_unitary_matrix_1_over_4_213qubits.npy") +CRZZ_unitary_matrix_1_over_4_213qubits = np.load( + prefix + "CRZZ_unitary_matrix_1_over_4_213qubits.npy" +) """ circuit.CRZZ() tester @@ -1644,7 +1762,9 @@ `first_target_index` = 2 `second_target_index` = 3 """ -CRZZ_unitary_matrix_pi_over_4_023qubits = np.load(prefix + "CRZZ_unitary_matrix_pi_over_4_023qubits.npy") +CRZZ_unitary_matrix_pi_over_4_023qubits = np.load( + prefix + "CRZZ_unitary_matrix_pi_over_4_023qubits.npy" +) """ circuit.CU3() tester @@ -1654,7 +1774,9 @@ `control_index` = 0 `target_index` = 1 """ -CU3_unitary_matrix_pi2_pi3_pi4_01qubits = np.load(prefix + "CU3_unitary_matrix_pi2_pi3_pi4_01qubits.npy") +CU3_unitary_matrix_pi2_pi3_pi4_01qubits = np.load( + prefix + "CU3_unitary_matrix_pi2_pi3_pi4_01qubits.npy" +) """ circuit.CU3() tester @@ -1664,7 +1786,9 @@ `control_index` = 1 `target_index` = 0 """ -CU3_unitary_matrix_pi2_pi3_pi4_10qubits = np.load(prefix + "CU3_unitary_matrix_pi2_pi3_pi4_10qubits.npy") +CU3_unitary_matrix_pi2_pi3_pi4_10qubits = np.load( + prefix + "CU3_unitary_matrix_pi2_pi3_pi4_10qubits.npy" +) """ circuit.CU3() tester @@ -1674,7 +1798,9 @@ `control_index` = 0 `target_index` = 1 """ -CU3_unitary_matrix_pi2_pi3_pi4_02qubits = np.load(prefix + "CU3_unitary_matrix_pi2_pi3_pi4_02qubits.npy") +CU3_unitary_matrix_pi2_pi3_pi4_02qubits = np.load( + prefix + "CU3_unitary_matrix_pi2_pi3_pi4_02qubits.npy" +) """ circuit.CU3() tester @@ -1684,7 +1810,9 @@ `control_index` = 0 `target_index` = 1 """ -CU3_unitary_matrix_pi2_pi3_pi4_20qubits = np.load(prefix + "CU3_unitary_matrix_pi2_pi3_pi4_20qubits.npy") +CU3_unitary_matrix_pi2_pi3_pi4_20qubits = np.load( + prefix + "CU3_unitary_matrix_pi2_pi3_pi4_20qubits.npy" +) """ circuit.CU3() tester @@ -1694,7 +1822,9 @@ `control_index` = 1 `target_index` = 2 """ -CU3_unitary_matrix_pi2_pi3_pi4_12qubits = np.load(prefix + "CU3_unitary_matrix_pi2_pi3_pi4_12qubits.npy") +CU3_unitary_matrix_pi2_pi3_pi4_12qubits = np.load( + prefix + "CU3_unitary_matrix_pi2_pi3_pi4_12qubits.npy" +) """ circuit.CSWAP() tester @@ -1750,89 +1880,107 @@ Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [2, 3] +`control_indices` = [0, 1] +`target_indices` = [2, 3] """ -MCX_unitary_matrix_01_23_qubits = np.load(prefix + "MCX_unitary_matrix_01_23_qubits.npy") +MCX_unitary_matrix_01_23_qubits = np.load( + prefix + "MCX_unitary_matrix_01_23_qubits.npy" +) """ circuit.MCX() tester Parameters ---------- -`control_qubits` = [1, 0] -`target_qubits` = [2, 3] +`control_indices` = [1, 0] +`target_indices` = [2, 3] """ -MCX_unitary_matrix_10_23_qubits = np.load(prefix + "MCX_unitary_matrix_10_23_qubits.npy") +MCX_unitary_matrix_10_23_qubits = np.load( + prefix + "MCX_unitary_matrix_10_23_qubits.npy" +) """ circuit.MCX() tester Parameters ---------- -`control_qubits` = [0, 2] -`target_qubits` = [1, 3] +`control_indices` = [0, 2] +`target_indices` = [1, 3] """ -MCX_unitary_matrix_02_13_qubits = np.load(prefix + "MCX_unitary_matrix_02_13_qubits.npy") +MCX_unitary_matrix_02_13_qubits = np.load( + prefix + "MCX_unitary_matrix_02_13_qubits.npy" +) """ circuit.MCX() tester Parameters ---------- -`control_qubits` = [2, 0] -`target_qubits` = [3, 4] +`control_indices` = [2, 0] +`target_indices` = [3, 4] """ -MCX_unitary_matrix_20_34_qubits = np.load(prefix + "MCX_unitary_matrix_20_34_qubits.npy") +MCX_unitary_matrix_20_34_qubits = np.load( + prefix + "MCX_unitary_matrix_20_34_qubits.npy" +) """ circuit.MCX() tester Parameters ---------- -`control_qubits` = [1, 2] -`target_qubits` = [0, 4] +`control_indices` = [1, 2] +`target_indices` = [0, 4] """ -MCX_unitary_matrix_12_04_qubits = np.load(prefix + "MCX_unitary_matrix_12_04_qubits.npy") +MCX_unitary_matrix_12_04_qubits = np.load( + prefix + "MCX_unitary_matrix_12_04_qubits.npy" +) """ circuit.MCX() tester Parameters ---------- -`control_qubits` = [5, 3] -`target_qubits` = [0, 1] +`control_indices` = [5, 3] +`target_indices` = [0, 1] """ -MCX_unitary_matrix_53_01_qubits = np.load(prefix + "MCX_unitary_matrix_53_01_qubits.npy") +MCX_unitary_matrix_53_01_qubits = np.load( + prefix + "MCX_unitary_matrix_53_01_qubits.npy" +) """ circuit.MCX() tester Parameters ---------- -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4] """ -MCX_unitary_matrix_012_34_qubits = np.load(prefix + "MCX_unitary_matrix_012_34_qubits.npy") +MCX_unitary_matrix_012_34_qubits = np.load( + prefix + "MCX_unitary_matrix_012_34_qubits.npy" +) """ circuit.MCX() tester Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [3, 4, 5] +`control_indices` = [0, 1] +`target_indices` = [3, 4, 5] """ -MCX_unitary_matrix_01_234_qubits = np.load(prefix + "MCX_unitary_matrix_01_234_qubits.npy") +MCX_unitary_matrix_01_234_qubits = np.load( + prefix + "MCX_unitary_matrix_01_234_qubits.npy" +) """ circuit.MCX() tester Parameters ---------- -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4, 5] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4, 5] """ -MCX_unitary_matrix_012_345_qubits = np.load(prefix + "MCX_unitary_matrix_012_345_qubits.npy") +MCX_unitary_matrix_012_345_qubits = np.load( + prefix + "MCX_unitary_matrix_012_345_qubits.npy" +) """ circuit.MCX() tester Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [2] +`control_indices` = [0, 1] +`target_indices` = [2] """ MCX_unitary_matrix_01_2_qubits = np.load(prefix + "MCX_unitary_matrix_01_2_qubits.npy") @@ -1840,8 +1988,8 @@ Parameters ---------- -`control_qubits` = [0] -`target_qubits` = [2, 3] +`control_indices` = [0] +`target_indices` = [2, 3] """ MCX_unitary_matrix_0_23_qubits = np.load(prefix + "MCX_unitary_matrix_0_23_qubits.npy") @@ -1849,89 +1997,107 @@ Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [2, 3] +`control_indices` = [0, 1] +`target_indices` = [2, 3] """ -MCY_unitary_matrix_01_23_qubits = np.load(prefix + "MCY_unitary_matrix_01_23_qubits.npy") +MCY_unitary_matrix_01_23_qubits = np.load( + prefix + "MCY_unitary_matrix_01_23_qubits.npy" +) """ circuit.MCY() tester Parameters ---------- -`control_qubits` = [1, 0] -`target_qubits` = [2, 3] +`control_indices` = [1, 0] +`target_indices` = [2, 3] """ -MCY_unitary_matrix_10_23_qubits = np.load(prefix + "MCY_unitary_matrix_10_23_qubits.npy") +MCY_unitary_matrix_10_23_qubits = np.load( + prefix + "MCY_unitary_matrix_10_23_qubits.npy" +) """ circuit.MCY() tester Parameters ---------- -`control_qubits` = [0, 2] -`target_qubits` = [1, 3] +`control_indices` = [0, 2] +`target_indices` = [1, 3] """ -MCY_unitary_matrix_02_13_qubits = np.load(prefix + "MCY_unitary_matrix_02_13_qubits.npy") +MCY_unitary_matrix_02_13_qubits = np.load( + prefix + "MCY_unitary_matrix_02_13_qubits.npy" +) """ circuit.MCY() tester Parameters ---------- -`control_qubits` = [2, 0] -`target_qubits` = [3, 4] +`control_indices` = [2, 0] +`target_indices` = [3, 4] """ -MCY_unitary_matrix_20_34_qubits = np.load(prefix + "MCY_unitary_matrix_20_34_qubits.npy") +MCY_unitary_matrix_20_34_qubits = np.load( + prefix + "MCY_unitary_matrix_20_34_qubits.npy" +) """ circuit.MCY() tester Parameters ---------- -`control_qubits` = [1, 2] -`target_qubits` = [0, 4] +`control_indices` = [1, 2] +`target_indices` = [0, 4] """ -MCY_unitary_matrix_12_04_qubits = np.load(prefix + "MCY_unitary_matrix_12_04_qubits.npy") +MCY_unitary_matrix_12_04_qubits = np.load( + prefix + "MCY_unitary_matrix_12_04_qubits.npy" +) """ circuit.MCY() tester Parameters ---------- -`control_qubits` = [5, 3] -`target_qubits` = [0, 1] +`control_indices` = [5, 3] +`target_indices` = [0, 1] """ -MCY_unitary_matrix_53_01_qubits = np.load(prefix + "MCY_unitary_matrix_53_01_qubits.npy") +MCY_unitary_matrix_53_01_qubits = np.load( + prefix + "MCY_unitary_matrix_53_01_qubits.npy" +) """ circuit.MCY() tester Parameters ---------- -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4] """ -MCY_unitary_matrix_012_34_qubits = np.load(prefix + "MCY_unitary_matrix_012_34_qubits.npy") +MCY_unitary_matrix_012_34_qubits = np.load( + prefix + "MCY_unitary_matrix_012_34_qubits.npy" +) """ circuit.MCY() tester Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [3, 4, 5] +`control_indices` = [0, 1] +`target_indices` = [3, 4, 5] """ -MCY_unitary_matrix_01_234_qubits = np.load(prefix + "MCY_unitary_matrix_01_234_qubits.npy") +MCY_unitary_matrix_01_234_qubits = np.load( + prefix + "MCY_unitary_matrix_01_234_qubits.npy" +) """ circuit.MCY() tester Parameters ---------- -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4, 5] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4, 5] """ -MCY_unitary_matrix_012_345_qubits = np.load(prefix + "MCY_unitary_matrix_012_345_qubits.npy") +MCY_unitary_matrix_012_345_qubits = np.load( + prefix + "MCY_unitary_matrix_012_345_qubits.npy" +) """ circuit.MCY() tester Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [2] +`control_indices` = [0, 1] +`target_indices` = [2] """ MCY_unitary_matrix_01_2_qubits = np.load(prefix + "MCY_unitary_matrix_01_2_qubits.npy") @@ -1939,8 +2105,8 @@ Parameters ---------- -`control_qubits` = [0] -`target_qubits` = [2, 3] +`control_indices` = [0] +`target_indices` = [2, 3] """ MCY_unitary_matrix_0_23_qubits = np.load(prefix + "MCY_unitary_matrix_0_23_qubits.npy") @@ -1948,89 +2114,107 @@ Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [2, 3] +`control_indices` = [0, 1] +`target_indices` = [2, 3] """ -MCZ_unitary_matrix_01_23_qubits = np.load(prefix + "MCZ_unitary_matrix_01_23_qubits.npy") +MCZ_unitary_matrix_01_23_qubits = np.load( + prefix + "MCZ_unitary_matrix_01_23_qubits.npy" +) """ circuit.MCZ() tester Parameters ---------- -`control_qubits` = [1, 0] -`target_qubits` = [2, 3] +`control_indices` = [1, 0] +`target_indices` = [2, 3] """ -MCZ_unitary_matrix_10_23_qubits = np.load(prefix + "MCZ_unitary_matrix_10_23_qubits.npy") +MCZ_unitary_matrix_10_23_qubits = np.load( + prefix + "MCZ_unitary_matrix_10_23_qubits.npy" +) """ circuit.MCZ() tester Parameters ---------- -`control_qubits` = [0, 2] -`target_qubits` = [1, 3] +`control_indices` = [0, 2] +`target_indices` = [1, 3] """ -MCZ_unitary_matrix_02_13_qubits = np.load(prefix + "MCZ_unitary_matrix_02_13_qubits.npy") +MCZ_unitary_matrix_02_13_qubits = np.load( + prefix + "MCZ_unitary_matrix_02_13_qubits.npy" +) """ circuit.MCZ() tester Parameters ---------- -`control_qubits` = [2, 0] -`target_qubits` = [3, 4] +`control_indices` = [2, 0] +`target_indices` = [3, 4] """ -MCZ_unitary_matrix_20_34_qubits = np.load(prefix + "MCZ_unitary_matrix_20_34_qubits.npy") +MCZ_unitary_matrix_20_34_qubits = np.load( + prefix + "MCZ_unitary_matrix_20_34_qubits.npy" +) """ circuit.MCZ() tester Parameters ---------- -`control_qubits` = [1, 2] -`target_qubits` = [0, 4] +`control_indices` = [1, 2] +`target_indices` = [0, 4] """ -MCZ_unitary_matrix_12_04_qubits = np.load(prefix + "MCZ_unitary_matrix_12_04_qubits.npy") +MCZ_unitary_matrix_12_04_qubits = np.load( + prefix + "MCZ_unitary_matrix_12_04_qubits.npy" +) """ circuit.MCZ() tester Parameters ---------- -`control_qubits` = [5, 3] -`target_qubits` = [0, 1] +`control_indices` = [5, 3] +`target_indices` = [0, 1] """ -MCZ_unitary_matrix_53_01_qubits = np.load(prefix + "MCZ_unitary_matrix_53_01_qubits.npy") +MCZ_unitary_matrix_53_01_qubits = np.load( + prefix + "MCZ_unitary_matrix_53_01_qubits.npy" +) """ circuit.MCZ() tester Parameters ---------- -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4] """ -MCZ_unitary_matrix_012_34_qubits = np.load(prefix + "MCZ_unitary_matrix_012_34_qubits.npy") +MCZ_unitary_matrix_012_34_qubits = np.load( + prefix + "MCZ_unitary_matrix_012_34_qubits.npy" +) """ circuit.MCZ() tester Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [3, 4, 5] +`control_indices` = [0, 1] +`target_indices` = [3, 4, 5] """ -MCZ_unitary_matrix_01_234_qubits = np.load(prefix + "MCZ_unitary_matrix_01_234_qubits.npy") +MCZ_unitary_matrix_01_234_qubits = np.load( + prefix + "MCZ_unitary_matrix_01_234_qubits.npy" +) """ circuit.MCZ() tester Parameters ---------- -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4, 5] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4, 5] """ -MCZ_unitary_matrix_012_345_qubits = np.load(prefix + "MCZ_unitary_matrix_012_345_qubits.npy") +MCZ_unitary_matrix_012_345_qubits = np.load( + prefix + "MCZ_unitary_matrix_012_345_qubits.npy" +) """ circuit.MCZ() tester Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [2] +`control_indices` = [0, 1] +`target_indices` = [2] """ MCZ_unitary_matrix_01_2_qubits = np.load(prefix + "MCZ_unitary_matrix_01_2_qubits.npy") @@ -2038,8 +2222,8 @@ Parameters ---------- -`control_qubits` = [0] -`target_qubits` = [2, 3] +`control_indices` = [0] +`target_indices` = [2, 3] """ MCZ_unitary_matrix_0_23_qubits = np.load(prefix + "MCZ_unitary_matrix_0_23_qubits.npy") @@ -2047,89 +2231,107 @@ Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [2, 3] +`control_indices` = [0, 1] +`target_indices` = [2, 3] """ -MCH_unitary_matrix_01_23_qubits = np.load(prefix + "MCH_unitary_matrix_01_23_qubits.npy") +MCH_unitary_matrix_01_23_qubits = np.load( + prefix + "MCH_unitary_matrix_01_23_qubits.npy" +) """ circuit.MCH() tester Parameters ---------- -`control_qubits` = [1, 0] -`target_qubits` = [2, 3] +`control_indices` = [1, 0] +`target_indices` = [2, 3] """ -MCH_unitary_matrix_10_23_qubits = np.load(prefix + "MCH_unitary_matrix_10_23_qubits.npy") +MCH_unitary_matrix_10_23_qubits = np.load( + prefix + "MCH_unitary_matrix_10_23_qubits.npy" +) """ circuit.MCH() tester Parameters ---------- -`control_qubits` = [0, 2] -`target_qubits` = [1, 3] +`control_indices` = [0, 2] +`target_indices` = [1, 3] """ -MCH_unitary_matrix_02_13_qubits = np.load(prefix + "MCH_unitary_matrix_02_13_qubits.npy") +MCH_unitary_matrix_02_13_qubits = np.load( + prefix + "MCH_unitary_matrix_02_13_qubits.npy" +) """ circuit.MCH() tester Parameters ---------- -`control_qubits` = [2, 0] -`target_qubits` = [3, 4] +`control_indices` = [2, 0] +`target_indices` = [3, 4] """ -MCH_unitary_matrix_20_34_qubits = np.load(prefix + "MCH_unitary_matrix_20_34_qubits.npy") +MCH_unitary_matrix_20_34_qubits = np.load( + prefix + "MCH_unitary_matrix_20_34_qubits.npy" +) """ circuit.MCH() tester Parameters ---------- -`control_qubits` = [1, 2] -`target_qubits` = [0, 4] +`control_indices` = [1, 2] +`target_indices` = [0, 4] """ -MCH_unitary_matrix_12_04_qubits = np.load(prefix + "MCH_unitary_matrix_12_04_qubits.npy") +MCH_unitary_matrix_12_04_qubits = np.load( + prefix + "MCH_unitary_matrix_12_04_qubits.npy" +) """ circuit.MCH() tester Parameters ---------- -`control_qubits` = [5, 3] -`target_qubits` = [0, 1] +`control_indices` = [5, 3] +`target_indices` = [0, 1] """ -MCH_unitary_matrix_53_01_qubits = np.load(prefix + "MCH_unitary_matrix_53_01_qubits.npy") +MCH_unitary_matrix_53_01_qubits = np.load( + prefix + "MCH_unitary_matrix_53_01_qubits.npy" +) """ circuit.MCH() tester Parameters ---------- -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4] """ -MCH_unitary_matrix_012_34_qubits = np.load(prefix + "MCH_unitary_matrix_012_34_qubits.npy") +MCH_unitary_matrix_012_34_qubits = np.load( + prefix + "MCH_unitary_matrix_012_34_qubits.npy" +) """ circuit.MCH() tester Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [3, 4, 5] +`control_indices` = [0, 1] +`target_indices` = [3, 4, 5] """ -MCH_unitary_matrix_01_234_qubits = np.load(prefix + "MCH_unitary_matrix_01_234_qubits.npy") +MCH_unitary_matrix_01_234_qubits = np.load( + prefix + "MCH_unitary_matrix_01_234_qubits.npy" +) """ circuit.MCH() tester Parameters ---------- -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4, 5] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4, 5] """ -MCH_unitary_matrix_012_345_qubits = np.load(prefix + "MCH_unitary_matrix_012_345_qubits.npy") +MCH_unitary_matrix_012_345_qubits = np.load( + prefix + "MCH_unitary_matrix_012_345_qubits.npy" +) """ circuit.MCH() tester Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [2] +`control_indices` = [0, 1] +`target_indices` = [2] """ MCH_unitary_matrix_01_2_qubits = np.load(prefix + "MCH_unitary_matrix_01_2_qubits.npy") @@ -2137,8 +2339,8 @@ Parameters ---------- -`control_qubits` = [0] -`target_qubits` = [2, 3] +`control_indices` = [0] +`target_indices` = [2, 3] """ MCH_unitary_matrix_0_23_qubits = np.load(prefix + "MCH_unitary_matrix_0_23_qubits.npy") @@ -2146,89 +2348,107 @@ Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [2, 3] +`control_indices` = [0, 1] +`target_indices` = [2, 3] """ -MCS_unitary_matrix_01_23_qubits = np.load(prefix + "MCS_unitary_matrix_01_23_qubits.npy") +MCS_unitary_matrix_01_23_qubits = np.load( + prefix + "MCS_unitary_matrix_01_23_qubits.npy" +) """ circuit.MCS() tester Parameters ---------- -`control_qubits` = [1, 0] -`target_qubits` = [2, 3] +`control_indices` = [1, 0] +`target_indices` = [2, 3] """ -MCS_unitary_matrix_10_23_qubits = np.load(prefix + "MCS_unitary_matrix_10_23_qubits.npy") +MCS_unitary_matrix_10_23_qubits = np.load( + prefix + "MCS_unitary_matrix_10_23_qubits.npy" +) """ circuit.MCS() tester Parameters ---------- -`control_qubits` = [0, 2] -`target_qubits` = [1, 3] +`control_indices` = [0, 2] +`target_indices` = [1, 3] """ -MCS_unitary_matrix_02_13_qubits = np.load(prefix + "MCS_unitary_matrix_02_13_qubits.npy") +MCS_unitary_matrix_02_13_qubits = np.load( + prefix + "MCS_unitary_matrix_02_13_qubits.npy" +) """ circuit.MCS() tester Parameters ---------- -`control_qubits` = [2, 0] -`target_qubits` = [3, 4] +`control_indices` = [2, 0] +`target_indices` = [3, 4] """ -MCS_unitary_matrix_20_34_qubits = np.load(prefix + "MCS_unitary_matrix_20_34_qubits.npy") +MCS_unitary_matrix_20_34_qubits = np.load( + prefix + "MCS_unitary_matrix_20_34_qubits.npy" +) """ circuit.MCS() tester Parameters ---------- -`control_qubits` = [1, 2] -`target_qubits` = [0, 4] +`control_indices` = [1, 2] +`target_indices` = [0, 4] """ -MCS_unitary_matrix_12_04_qubits = np.load(prefix + "MCS_unitary_matrix_12_04_qubits.npy") +MCS_unitary_matrix_12_04_qubits = np.load( + prefix + "MCS_unitary_matrix_12_04_qubits.npy" +) """ circuit.MCS() tester Parameters ---------- -`control_qubits` = [5, 3] -`target_qubits` = [0, 1] +`control_indices` = [5, 3] +`target_indices` = [0, 1] """ -MCS_unitary_matrix_53_01_qubits = np.load(prefix + "MCS_unitary_matrix_53_01_qubits.npy") +MCS_unitary_matrix_53_01_qubits = np.load( + prefix + "MCS_unitary_matrix_53_01_qubits.npy" +) """ circuit.MCS() tester Parameters ---------- -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4] """ -MCS_unitary_matrix_012_34_qubits = np.load(prefix + "MCS_unitary_matrix_012_34_qubits.npy") +MCS_unitary_matrix_012_34_qubits = np.load( + prefix + "MCS_unitary_matrix_012_34_qubits.npy" +) """ circuit.MCS() tester Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [3, 4, 5] +`control_indices` = [0, 1] +`target_indices` = [3, 4, 5] """ -MCS_unitary_matrix_01_234_qubits = np.load(prefix + "MCS_unitary_matrix_01_234_qubits.npy") +MCS_unitary_matrix_01_234_qubits = np.load( + prefix + "MCS_unitary_matrix_01_234_qubits.npy" +) """ circuit.MCS() tester Parameters ---------- -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4, 5] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4, 5] """ -MCS_unitary_matrix_012_345_qubits = np.load(prefix + "MCS_unitary_matrix_012_345_qubits.npy") +MCS_unitary_matrix_012_345_qubits = np.load( + prefix + "MCS_unitary_matrix_012_345_qubits.npy" +) """ circuit.MCS() tester Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [2] +`control_indices` = [0, 1] +`target_indices` = [2] """ MCS_unitary_matrix_01_2_qubits = np.load(prefix + "MCS_unitary_matrix_01_2_qubits.npy") @@ -2236,8 +2456,8 @@ Parameters ---------- -`control_qubits` = [0] -`target_qubits` = [2, 3] +`control_indices` = [0] +`target_indices` = [2, 3] """ MCS_unitary_matrix_0_23_qubits = np.load(prefix + "MCS_unitary_matrix_0_23_qubits.npy") @@ -2245,188 +2465,228 @@ Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [2, 3] +`control_indices` = [0, 1] +`target_indices` = [2, 3] """ -MCSdg_unitary_matrix_01_23_qubits = np.load(prefix + "MCSdg_unitary_matrix_01_23_qubits.npy") +MCSdg_unitary_matrix_01_23_qubits = np.load( + prefix + "MCSdg_unitary_matrix_01_23_qubits.npy" +) """ circuit.MCSdg() tester Parameters ---------- -`control_qubits` = [1, 0] -`target_qubits` = [2, 3] +`control_indices` = [1, 0] +`target_indices` = [2, 3] """ -MCSdg_unitary_matrix_10_23_qubits = np.load(prefix + "MCSdg_unitary_matrix_10_23_qubits.npy") +MCSdg_unitary_matrix_10_23_qubits = np.load( + prefix + "MCSdg_unitary_matrix_10_23_qubits.npy" +) """ circuit.MCSdg() tester Parameters ---------- -`control_qubits` = [0, 2] -`target_qubits` = [1, 3] +`control_indices` = [0, 2] +`target_indices` = [1, 3] """ -MCSdg_unitary_matrix_02_13_qubits = np.load(prefix + "MCSdg_unitary_matrix_02_13_qubits.npy") +MCSdg_unitary_matrix_02_13_qubits = np.load( + prefix + "MCSdg_unitary_matrix_02_13_qubits.npy" +) """ circuit.MCSdg() tester Parameters ---------- -`control_qubits` = [2, 0] -`target_qubits` = [3, 4] +`control_indices` = [2, 0] +`target_indices` = [3, 4] """ -MCSdg_unitary_matrix_20_34_qubits = np.load(prefix + "MCSdg_unitary_matrix_20_34_qubits.npy") +MCSdg_unitary_matrix_20_34_qubits = np.load( + prefix + "MCSdg_unitary_matrix_20_34_qubits.npy" +) """ circuit.MCSdg() tester Parameters ---------- -`control_qubits` = [1, 2] -`target_qubits` = [0, 4] +`control_indices` = [1, 2] +`target_indices` = [0, 4] """ -MCSdg_unitary_matrix_12_04_qubits = np.load(prefix + "MCSdg_unitary_matrix_12_04_qubits.npy") +MCSdg_unitary_matrix_12_04_qubits = np.load( + prefix + "MCSdg_unitary_matrix_12_04_qubits.npy" +) """ circuit.MCSdg() tester Parameters ---------- -`control_qubits` = [5, 3] -`target_qubits` = [0, 1] +`control_indices` = [5, 3] +`target_indices` = [0, 1] """ -MCSdg_unitary_matrix_53_01_qubits = np.load(prefix + "MCSdg_unitary_matrix_53_01_qubits.npy") +MCSdg_unitary_matrix_53_01_qubits = np.load( + prefix + "MCSdg_unitary_matrix_53_01_qubits.npy" +) """ circuit.MCSdg() tester Parameters ---------- -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4] """ -MCSdg_unitary_matrix_012_34_qubits = np.load(prefix + "MCSdg_unitary_matrix_012_34_qubits.npy") +MCSdg_unitary_matrix_012_34_qubits = np.load( + prefix + "MCSdg_unitary_matrix_012_34_qubits.npy" +) """ circuit.MCSdg() tester Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [3, 4, 5] +`control_indices` = [0, 1] +`target_indices` = [3, 4, 5] """ -MCSdg_unitary_matrix_01_234_qubits = np.load(prefix + "MCSdg_unitary_matrix_01_234_qubits.npy") +MCSdg_unitary_matrix_01_234_qubits = np.load( + prefix + "MCSdg_unitary_matrix_01_234_qubits.npy" +) """ circuit.MCSdg() tester Parameters ---------- -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4, 5] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4, 5] """ -MCSdg_unitary_matrix_012_345_qubits = np.load(prefix + "MCSdg_unitary_matrix_012_345_qubits.npy") +MCSdg_unitary_matrix_012_345_qubits = np.load( + prefix + "MCSdg_unitary_matrix_012_345_qubits.npy" +) """ circuit.MCSdg() tester Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [2] +`control_indices` = [0, 1] +`target_indices` = [2] """ -MCSdg_unitary_matrix_01_2_qubits = np.load(prefix + "MCSdg_unitary_matrix_01_2_qubits.npy") +MCSdg_unitary_matrix_01_2_qubits = np.load( + prefix + "MCSdg_unitary_matrix_01_2_qubits.npy" +) """ circuit.MCSdg() tester Parameters ---------- -`control_qubits` = [0] -`target_qubits` = [2, 3] +`control_indices` = [0] +`target_indices` = [2, 3] """ -MCSdg_unitary_matrix_0_23_qubits = np.load(prefix + "MCSdg_unitary_matrix_0_23_qubits.npy") +MCSdg_unitary_matrix_0_23_qubits = np.load( + prefix + "MCSdg_unitary_matrix_0_23_qubits.npy" +) """ circuit.MCT() tester Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [2, 3] +`control_indices` = [0, 1] +`target_indices` = [2, 3] """ -MCT_unitary_matrix_01_23_qubits = np.load(prefix + "MCT_unitary_matrix_01_23_qubits.npy") +MCT_unitary_matrix_01_23_qubits = np.load( + prefix + "MCT_unitary_matrix_01_23_qubits.npy" +) """ circuit.MCT() tester Parameters ---------- -`control_qubits` = [1, 0] -`target_qubits` = [2, 3] +`control_indices` = [1, 0] +`target_indices` = [2, 3] """ -MCT_unitary_matrix_10_23_qubits = np.load(prefix + "MCT_unitary_matrix_10_23_qubits.npy") +MCT_unitary_matrix_10_23_qubits = np.load( + prefix + "MCT_unitary_matrix_10_23_qubits.npy" +) """ circuit.MCT() tester Parameters ---------- -`control_qubits` = [0, 2] -`target_qubits` = [1, 3] +`control_indices` = [0, 2] +`target_indices` = [1, 3] """ -MCT_unitary_matrix_02_13_qubits = np.load(prefix + "MCT_unitary_matrix_02_13_qubits.npy") +MCT_unitary_matrix_02_13_qubits = np.load( + prefix + "MCT_unitary_matrix_02_13_qubits.npy" +) """ circuit.MCT() tester Parameters ---------- -`control_qubits` = [2, 0] -`target_qubits` = [3, 4] +`control_indices` = [2, 0] +`target_indices` = [3, 4] """ -MCT_unitary_matrix_20_34_qubits = np.load(prefix + "MCT_unitary_matrix_20_34_qubits.npy") +MCT_unitary_matrix_20_34_qubits = np.load( + prefix + "MCT_unitary_matrix_20_34_qubits.npy" +) """ circuit.MCT() tester Parameters ---------- -`control_qubits` = [1, 2] -`target_qubits` = [0, 4] +`control_indices` = [1, 2] +`target_indices` = [0, 4] """ -MCT_unitary_matrix_12_04_qubits = np.load(prefix + "MCT_unitary_matrix_12_04_qubits.npy") +MCT_unitary_matrix_12_04_qubits = np.load( + prefix + "MCT_unitary_matrix_12_04_qubits.npy" +) """ circuit.MCT() tester Parameters ---------- -`control_qubits` = [5, 3] -`target_qubits` = [0, 1] +`control_indices` = [5, 3] +`target_indices` = [0, 1] """ -MCT_unitary_matrix_53_01_qubits = np.load(prefix + "MCT_unitary_matrix_53_01_qubits.npy") +MCT_unitary_matrix_53_01_qubits = np.load( + prefix + "MCT_unitary_matrix_53_01_qubits.npy" +) """ circuit.MCT() tester Parameters ---------- -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4] """ -MCT_unitary_matrix_012_34_qubits = np.load(prefix + "MCT_unitary_matrix_012_34_qubits.npy") +MCT_unitary_matrix_012_34_qubits = np.load( + prefix + "MCT_unitary_matrix_012_34_qubits.npy" +) """ circuit.MCT() tester Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [3, 4, 5] +`control_indices` = [0, 1] +`target_indices` = [3, 4, 5] """ -MCT_unitary_matrix_01_234_qubits = np.load(prefix + "MCT_unitary_matrix_01_234_qubits.npy") +MCT_unitary_matrix_01_234_qubits = np.load( + prefix + "MCT_unitary_matrix_01_234_qubits.npy" +) """ circuit.MCT() tester Parameters ---------- -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4, 5] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4, 5] """ -MCT_unitary_matrix_012_345_qubits = np.load(prefix + "MCT_unitary_matrix_012_345_qubits.npy") +MCT_unitary_matrix_012_345_qubits = np.load( + prefix + "MCT_unitary_matrix_012_345_qubits.npy" +) """ circuit.MCT() tester Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [2] +`control_indices` = [0, 1] +`target_indices` = [2] """ MCT_unitary_matrix_01_2_qubits = np.load(prefix + "MCT_unitary_matrix_01_2_qubits.npy") @@ -2434,8 +2694,8 @@ Parameters ---------- -`control_qubits` = [0] -`target_qubits` = [2, 3] +`control_indices` = [0] +`target_indices` = [2, 3] """ MCT_unitary_matrix_0_23_qubits = np.load(prefix + "MCT_unitary_matrix_0_23_qubits.npy") @@ -2443,630 +2703,758 @@ Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [2, 3] +`control_indices` = [0, 1] +`target_indices` = [2, 3] """ -MCTdg_unitary_matrix_01_23_qubits = np.load(prefix + "MCTdg_unitary_matrix_01_23_qubits.npy") +MCTdg_unitary_matrix_01_23_qubits = np.load( + prefix + "MCTdg_unitary_matrix_01_23_qubits.npy" +) """ circuit.MCTdg() tester Parameters ---------- -`control_qubits` = [1, 0] -`target_qubits` = [2, 3] +`control_indices` = [1, 0] +`target_indices` = [2, 3] """ -MCTdg_unitary_matrix_10_23_qubits = np.load(prefix + "MCTdg_unitary_matrix_10_23_qubits.npy") +MCTdg_unitary_matrix_10_23_qubits = np.load( + prefix + "MCTdg_unitary_matrix_10_23_qubits.npy" +) """ circuit.MCTdg() tester Parameters ---------- -`control_qubits` = [0, 2] -`target_qubits` = [1, 3] +`control_indices` = [0, 2] +`target_indices` = [1, 3] """ -MCTdg_unitary_matrix_02_13_qubits = np.load(prefix + "MCTdg_unitary_matrix_02_13_qubits.npy") +MCTdg_unitary_matrix_02_13_qubits = np.load( + prefix + "MCTdg_unitary_matrix_02_13_qubits.npy" +) """ circuit.MCTdg() tester Parameters ---------- -`control_qubits` = [2, 0] -`target_qubits` = [3, 4] +`control_indices` = [2, 0] +`target_indices` = [3, 4] """ -MCTdg_unitary_matrix_20_34_qubits = np.load(prefix + "MCTdg_unitary_matrix_20_34_qubits.npy") +MCTdg_unitary_matrix_20_34_qubits = np.load( + prefix + "MCTdg_unitary_matrix_20_34_qubits.npy" +) """ circuit.MCTdg() tester Parameters ---------- -`control_qubits` = [1, 2] -`target_qubits` = [0, 4] +`control_indices` = [1, 2] +`target_indices` = [0, 4] """ -MCTdg_unitary_matrix_12_04_qubits = np.load(prefix + "MCTdg_unitary_matrix_12_04_qubits.npy") +MCTdg_unitary_matrix_12_04_qubits = np.load( + prefix + "MCTdg_unitary_matrix_12_04_qubits.npy" +) """ circuit.MCTdg() tester Parameters ---------- -`control_qubits` = [5, 3] -`target_qubits` = [0, 1] +`control_indices` = [5, 3] +`target_indices` = [0, 1] """ -MCTdg_unitary_matrix_53_01_qubits = np.load(prefix + "MCTdg_unitary_matrix_53_01_qubits.npy") +MCTdg_unitary_matrix_53_01_qubits = np.load( + prefix + "MCTdg_unitary_matrix_53_01_qubits.npy" +) """ circuit.MCTdg() tester Parameters ---------- -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4] """ -MCTdg_unitary_matrix_012_34_qubits = np.load(prefix + "MCTdg_unitary_matrix_012_34_qubits.npy") +MCTdg_unitary_matrix_012_34_qubits = np.load( + prefix + "MCTdg_unitary_matrix_012_34_qubits.npy" +) """ circuit.MCTdg() tester Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [3, 4, 5] +`control_indices` = [0, 1] +`target_indices` = [3, 4, 5] """ -MCTdg_unitary_matrix_01_234_qubits = np.load(prefix + "MCTdg_unitary_matrix_01_234_qubits.npy") +MCTdg_unitary_matrix_01_234_qubits = np.load( + prefix + "MCTdg_unitary_matrix_01_234_qubits.npy" +) """ circuit.MCTdg() tester Parameters ---------- -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4, 5] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4, 5] """ -MCTdg_unitary_matrix_012_345_qubits = np.load(prefix + "MCTdg_unitary_matrix_012_345_qubits.npy") +MCTdg_unitary_matrix_012_345_qubits = np.load( + prefix + "MCTdg_unitary_matrix_012_345_qubits.npy" +) """ circuit.MCTdg() tester Parameters ---------- -`control_qubits` = [0, 1] -`target_qubits` = [2] +`control_indices` = [0, 1] +`target_indices` = [2] """ -MCTdg_unitary_matrix_01_2_qubits = np.load(prefix + "MCTdg_unitary_matrix_01_2_qubits.npy") +MCTdg_unitary_matrix_01_2_qubits = np.load( + prefix + "MCTdg_unitary_matrix_01_2_qubits.npy" +) """ circuit.MCT() tester Parameters ---------- -`control_qubits` = [0] -`target_qubits` = [2, 3] +`control_indices` = [0] +`target_indices` = [2, 3] """ -MCTdg_unitary_matrix_0_23_qubits = np.load(prefix + "MCTdg_unitary_matrix_0_23_qubits.npy") +MCTdg_unitary_matrix_0_23_qubits = np.load( + prefix + "MCTdg_unitary_matrix_0_23_qubits.npy" +) """ circuit.MCRX() tester Parameters ---------- `angle` = np.pi/4 -`control_qubits` = [0, 1] -`target_qubits` = [2, 3] +`control_indices` = [0, 1] +`target_indices` = [2, 3] """ -MCRX_unitary_matrix_pi_over_4_01_23_qubits = np.load(prefix + "MCRX_unitary_matrix_pi_over_4_01_23_qubits.npy") +MCRX_unitary_matrix_pi_over_4_01_23_qubits = np.load( + prefix + "MCRX_unitary_matrix_pi_over_4_01_23_qubits.npy" +) """ circuit.MCRX() tester Parameters ---------- `angle` = np.pi/4 -`control_qubits` = [1, 0] -`target_qubits` = [2, 3] +`control_indices` = [1, 0] +`target_indices` = [2, 3] """ -MCRX_unitary_matrix_pi_over_4_10_23_qubits = np.load(prefix + "MCRX_unitary_matrix_pi_over_4_10_23_qubits.npy") +MCRX_unitary_matrix_pi_over_4_10_23_qubits = np.load( + prefix + "MCRX_unitary_matrix_pi_over_4_10_23_qubits.npy" +) """ circuit.MCRX() tester Parameters ---------- `angle` = 1/4 -`control_qubits` = [0, 2] -`target_qubits` = [1, 3] +`control_indices` = [0, 2] +`target_indices` = [1, 3] """ -MCRX_unitary_matrix_1_over_4_02_13_qubits = np.load(prefix + "MCRX_unitary_matrix_1_over_4_02_13_qubits.npy") +MCRX_unitary_matrix_1_over_4_02_13_qubits = np.load( + prefix + "MCRX_unitary_matrix_1_over_4_02_13_qubits.npy" +) """ circuit.MCRX() tester Parameters ---------- `angle` = 1/4 -`control_qubits` = [2, 0] -`target_qubits` = [3, 4] +`control_indices` = [2, 0] +`target_indices` = [3, 4] """ -MCRX_unitary_matrix_1_over_4_20_34_qubits = np.load(prefix + "MCRX_unitary_matrix_1_over_4_20_34_qubits.npy") +MCRX_unitary_matrix_1_over_4_20_34_qubits = np.load( + prefix + "MCRX_unitary_matrix_1_over_4_20_34_qubits.npy" +) """ circuit.MCRX() tester Parameters ---------- `angle` = -1/4 -`control_qubits` = [1, 2] -`target_qubits` = [0, 4] +`control_indices` = [1, 2] +`target_indices` = [0, 4] """ -MCRX_unitary_matrix_negative1_over_4_12_04_qubits = np.load(prefix + "MCRX_unitary_matrix_negative1_over_4_12_04_qubits.npy") +MCRX_unitary_matrix_negative1_over_4_12_04_qubits = np.load( + prefix + "MCRX_unitary_matrix_negative1_over_4_12_04_qubits.npy" +) """ circuit.MCRX() tester Parameters ---------- `angle` = -1/4 -`control_qubits` = [5, 3] -`target_qubits` = [0, 1] +`control_indices` = [5, 3] +`target_indices` = [0, 1] """ -MCRX_unitary_matrix_negative1_over_4_53_01_qubits = np.load(prefix + "MCRX_unitary_matrix_negative1_over_4_53_01_qubits.npy") +MCRX_unitary_matrix_negative1_over_4_53_01_qubits = np.load( + prefix + "MCRX_unitary_matrix_negative1_over_4_53_01_qubits.npy" +) """ circuit.MCRX() tester Parameters ---------- `angle` = 1/3 -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4] """ -MCRX_unitary_matrix_1_over_3_012_34_qubits = np.load(prefix + "MCRX_unitary_matrix_1_over_3_012_34_qubits.npy") +MCRX_unitary_matrix_1_over_3_012_34_qubits = np.load( + prefix + "MCRX_unitary_matrix_1_over_3_012_34_qubits.npy" +) """ circuit.MCRX() tester Parameters ---------- `angle` = 1/3 -`control_qubits` = [0, 1] -`target_qubits` = [2, 3, 4] +`control_indices` = [0, 1] +`target_indices` = [2, 3, 4] """ -MCRX_unitary_matrix_1_over_3_01_234_qubits = np.load(prefix + "MCRX_unitary_matrix_1_over_3_01_234_qubits.npy") +MCRX_unitary_matrix_1_over_3_01_234_qubits = np.load( + prefix + "MCRX_unitary_matrix_1_over_3_01_234_qubits.npy" +) """ circuit.MCRX() tester Parameters ---------- `angle` = pi/4 -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4, 5] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4, 5] """ -MCRX_unitary_matrix_pi_over_4_012_345_qubits = np.load(prefix + "MCRX_unitary_matrix_pi_over_4_012_345_qubits.npy") +MCRX_unitary_matrix_pi_over_4_012_345_qubits = np.load( + prefix + "MCRX_unitary_matrix_pi_over_4_012_345_qubits.npy" +) """ circuit.MCRX() tester Parameters ---------- `angle` = pi/4 -`control_qubits` = [0, 1] -`target_qubits` = [2] +`control_indices` = [0, 1] +`target_indices` = [2] """ -MCRX_unitary_matrix_pi_over_4_01_2_qubits = np.load(prefix + "MCRX_unitary_matrix_pi_over_4_01_2_qubits.npy") +MCRX_unitary_matrix_pi_over_4_01_2_qubits = np.load( + prefix + "MCRX_unitary_matrix_pi_over_4_01_2_qubits.npy" +) """ circuit.MCRX() tester Parameters ---------- `angle` = pi/4 -`control_qubits` = [0] -`target_qubits` = [2, 3] +`control_indices` = [0] +`target_indices` = [2, 3] """ -MCRX_unitary_matrix_pi_over_4_0_23_qubits = np.load(prefix + "MCRX_unitary_matrix_pi_over_4_0_23_qubits.npy") +MCRX_unitary_matrix_pi_over_4_0_23_qubits = np.load( + prefix + "MCRX_unitary_matrix_pi_over_4_0_23_qubits.npy" +) """ circuit.MCRX() tester Parameters ---------- `angle` = 0.1 -`control_qubits` = [0, 1, 2, 3, 4, 5] -`target_qubits` = [6] +`control_indices` = [0, 1, 2, 3, 4, 5] +`target_indices` = [6] """ -MCRX_unitary_matrix_0dot1_012345_6_qubits = np.load(prefix + "MCRX_unitary_matrix_0dot1_012345_6_qubits.npy") +MCRX_unitary_matrix_0dot1_012345_6_qubits = np.load( + prefix + "MCRX_unitary_matrix_0dot1_012345_6_qubits.npy" +) """ circuit.MCRX() tester Parameters ---------- `angle` = 0.1 -`control_qubits` = [0, 1, 2, 3, 4, 5, 6] -`target_qubits` = [7] +`control_indices` = [0, 1, 2, 3, 4, 5, 6] +`target_indices` = [7] """ -MCRX_unitary_matrix_0dot1_0123456_7_qubits = np.load(prefix + "MCRX_unitary_matrix_0dot1_0123456_7_qubits.npy") +MCRX_unitary_matrix_0dot1_0123456_7_qubits = np.load( + prefix + "MCRX_unitary_matrix_0dot1_0123456_7_qubits.npy" +) """ circuit.MCRX() tester Parameters ---------- `angle` = 0.1 -`control_qubits` = [0, 1, 2, 3, 4, 5, 6, 7] -`target_qubits` = [8] +`control_indices` = [0, 1, 2, 3, 4, 5, 6, 7] +`target_indices` = [8] """ -MCRX_unitary_matrix_0dot1_01234567_8_qubits = np.load(prefix + "MCRX_unitary_matrix_0dot1_01234567_8_qubits.npy") +MCRX_unitary_matrix_0dot1_01234567_8_qubits = np.load( + prefix + "MCRX_unitary_matrix_0dot1_01234567_8_qubits.npy" +) """ circuit.MCRY() tester Parameters ---------- `angle` = np.pi/4 -`control_qubits` = [0, 1] -`target_qubits` = [2, 3] +`control_indices` = [0, 1] +`target_indices` = [2, 3] """ -MCRY_unitary_matrix_pi_over_4_01_23_qubits = np.load(prefix + "MCRY_unitary_matrix_pi_over_4_01_23_qubits.npy") +MCRY_unitary_matrix_pi_over_4_01_23_qubits = np.load( + prefix + "MCRY_unitary_matrix_pi_over_4_01_23_qubits.npy" +) """ circuit.MCRY() tester Parameters ---------- `angle` = np.pi/4 -`control_qubits` = [1, 0] -`target_qubits` = [2, 3] +`control_indices` = [1, 0] +`target_indices` = [2, 3] """ -MCRY_unitary_matrix_pi_over_4_10_23_qubits = np.load(prefix + "MCRY_unitary_matrix_pi_over_4_10_23_qubits.npy") +MCRY_unitary_matrix_pi_over_4_10_23_qubits = np.load( + prefix + "MCRY_unitary_matrix_pi_over_4_10_23_qubits.npy" +) """ circuit.MCRY() tester Parameters ---------- `angle` = 1/4 -`control_qubits` = [0, 2] -`target_qubits` = [1, 3] +`control_indices` = [0, 2] +`target_indices` = [1, 3] """ -MCRY_unitary_matrix_1_over_4_02_13_qubits = np.load(prefix + "MCRY_unitary_matrix_1_over_4_02_13_qubits.npy") +MCRY_unitary_matrix_1_over_4_02_13_qubits = np.load( + prefix + "MCRY_unitary_matrix_1_over_4_02_13_qubits.npy" +) """ circuit.MCRY() tester Parameters ---------- `angle` = 1/4 -`control_qubits` = [2, 0] -`target_qubits` = [3, 4] +`control_indices` = [2, 0] +`target_indices` = [3, 4] """ -MCRY_unitary_matrix_1_over_4_20_34_qubits = np.load(prefix + "MCRY_unitary_matrix_1_over_4_20_34_qubits.npy") +MCRY_unitary_matrix_1_over_4_20_34_qubits = np.load( + prefix + "MCRY_unitary_matrix_1_over_4_20_34_qubits.npy" +) """ circuit.MCRY() tester Parameters ---------- `angle` = -1/4 -`control_qubits` = [1, 2] -`target_qubits` = [0, 4] +`control_indices` = [1, 2] +`target_indices` = [0, 4] """ -MCRY_unitary_matrix_negative1_over_4_12_04_qubits = np.load(prefix + "MCRY_unitary_matrix_negative1_over_4_12_04_qubits.npy") +MCRY_unitary_matrix_negative1_over_4_12_04_qubits = np.load( + prefix + "MCRY_unitary_matrix_negative1_over_4_12_04_qubits.npy" +) """ circuit.MCRY() tester Parameters ---------- `angle` = -1/4 -`control_qubits` = [5, 3] -`target_qubits` = [0, 1] +`control_indices` = [5, 3] +`target_indices` = [0, 1] """ -MCRY_unitary_matrix_negative1_over_4_53_01_qubits = np.load(prefix + "MCRY_unitary_matrix_negative1_over_4_53_01_qubits.npy") +MCRY_unitary_matrix_negative1_over_4_53_01_qubits = np.load( + prefix + "MCRY_unitary_matrix_negative1_over_4_53_01_qubits.npy" +) """ circuit.MCRY() tester Parameters ---------- `angle` = 1/3 -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4] """ -MCRY_unitary_matrix_1_over_3_012_34_qubits = np.load(prefix + "MCRY_unitary_matrix_1_over_3_012_34_qubits.npy") +MCRY_unitary_matrix_1_over_3_012_34_qubits = np.load( + prefix + "MCRY_unitary_matrix_1_over_3_012_34_qubits.npy" +) """ circuit.MCRY() tester Parameters ---------- `angle` = 1/3 -`control_qubits` = [0, 1] -`target_qubits` = [2, 3, 4] +`control_indices` = [0, 1] +`target_indices` = [2, 3, 4] """ -MCRY_unitary_matrix_1_over_3_01_234_qubits = np.load(prefix + "MCRY_unitary_matrix_1_over_3_01_234_qubits.npy") +MCRY_unitary_matrix_1_over_3_01_234_qubits = np.load( + prefix + "MCRY_unitary_matrix_1_over_3_01_234_qubits.npy" +) """ circuit.MCRY() tester Parameters ---------- `angle` = pi/4 -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4, 5] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4, 5] """ -MCRY_unitary_matrix_pi_over_4_012_345_qubits = np.load(prefix + "MCRY_unitary_matrix_pi_over_4_012_345_qubits.npy") +MCRY_unitary_matrix_pi_over_4_012_345_qubits = np.load( + prefix + "MCRY_unitary_matrix_pi_over_4_012_345_qubits.npy" +) """ circuit.MCRY() tester Parameters ---------- `angle` = pi/4 -`control_qubits` = [0, 1] -`target_qubits` = [2] +`control_indices` = [0, 1] +`target_indices` = [2] """ -MCRY_unitary_matrix_pi_over_4_01_2_qubits = np.load(prefix + "MCRY_unitary_matrix_pi_over_4_01_2_qubits.npy") +MCRY_unitary_matrix_pi_over_4_01_2_qubits = np.load( + prefix + "MCRY_unitary_matrix_pi_over_4_01_2_qubits.npy" +) """ circuit.MCRY() tester Parameters ---------- `angle` = pi/4 -`control_qubits` = [0] -`target_qubits` = [2, 3] +`control_indices` = [0] +`target_indices` = [2, 3] """ -MCRY_unitary_matrix_pi_over_4_0_23_qubits = np.load(prefix + "MCRY_unitary_matrix_pi_over_4_0_23_qubits.npy") +MCRY_unitary_matrix_pi_over_4_0_23_qubits = np.load( + prefix + "MCRY_unitary_matrix_pi_over_4_0_23_qubits.npy" +) """ circuit.MCRY() tester Parameters ---------- `angle` = 0.1 -`control_qubits` = [0, 1, 2, 3, 4, 5] -`target_qubits` = [6] +`control_indices` = [0, 1, 2, 3, 4, 5] +`target_indices` = [6] """ -MCRY_unitary_matrix_0dot1_012345_6_qubits = np.load(prefix + "MCRY_unitary_matrix_0dot1_012345_6_qubits.npy") +MCRY_unitary_matrix_0dot1_012345_6_qubits = np.load( + prefix + "MCRY_unitary_matrix_0dot1_012345_6_qubits.npy" +) """ circuit.MCRY() tester Parameters ---------- `angle` = 0.1 -`control_qubits` = [0, 1, 2, 3, 4, 5, 6] -`target_qubits` = [7] +`control_indices` = [0, 1, 2, 3, 4, 5, 6] +`target_indices` = [7] """ -MCRY_unitary_matrix_0dot1_0123456_7_qubits = np.load(prefix + "MCRY_unitary_matrix_0dot1_0123456_7_qubits.npy") +MCRY_unitary_matrix_0dot1_0123456_7_qubits = np.load( + prefix + "MCRY_unitary_matrix_0dot1_0123456_7_qubits.npy" +) """ circuit.MCRY() tester Parameters ---------- `angle` = 0.1 -`control_qubits` = [0, 1, 2, 3, 4, 5, 6, 7] -`target_qubits` = [8] +`control_indices` = [0, 1, 2, 3, 4, 5, 6, 7] +`target_indices` = [8] """ -MCRY_unitary_matrix_0dot1_01234567_8_qubits = np.load(prefix + "MCRY_unitary_matrix_0dot1_01234567_8_qubits.npy") +MCRY_unitary_matrix_0dot1_01234567_8_qubits = np.load( + prefix + "MCRY_unitary_matrix_0dot1_01234567_8_qubits.npy" +) """ circuit.MCRZ() tester Parameters ---------- `angle` = np.pi/4 -`control_qubits` = [0, 1] -`target_qubits` = [2, 3] +`control_indices` = [0, 1] +`target_indices` = [2, 3] """ -MCRZ_unitary_matrix_pi_over_4_01_23_qubits = np.load(prefix + "MCRZ_unitary_matrix_pi_over_4_01_23_qubits.npy") +MCRZ_unitary_matrix_pi_over_4_01_23_qubits = np.load( + prefix + "MCRZ_unitary_matrix_pi_over_4_01_23_qubits.npy" +) """ circuit.MCRZ() tester Parameters ---------- `angle` = np.pi/4 -`control_qubits` = [1, 0] -`target_qubits` = [2, 3] +`control_indices` = [1, 0] +`target_indices` = [2, 3] """ -MCRZ_unitary_matrix_pi_over_4_10_23_qubits = np.load(prefix + "MCRZ_unitary_matrix_pi_over_4_10_23_qubits.npy") +MCRZ_unitary_matrix_pi_over_4_10_23_qubits = np.load( + prefix + "MCRZ_unitary_matrix_pi_over_4_10_23_qubits.npy" +) """ circuit.MCRZ() tester Parameters ---------- `angle` = 1/4 -`control_qubits` = [0, 2] -`target_qubits` = [1, 3] +`control_indices` = [0, 2] +`target_indices` = [1, 3] """ -MCRZ_unitary_matrix_1_over_4_02_13_qubits = np.load(prefix + "MCRZ_unitary_matrix_1_over_4_02_13_qubits.npy") +MCRZ_unitary_matrix_1_over_4_02_13_qubits = np.load( + prefix + "MCRZ_unitary_matrix_1_over_4_02_13_qubits.npy" +) """ circuit.MCRZ() tester Parameters ---------- `angle` = 1/4 -`control_qubits` = [2, 0] -`target_qubits` = [3, 4] +`control_indices` = [2, 0] +`target_indices` = [3, 4] """ -MCRZ_unitary_matrix_1_over_4_20_34_qubits = np.load(prefix + "MCRZ_unitary_matrix_1_over_4_20_34_qubits.npy") +MCRZ_unitary_matrix_1_over_4_20_34_qubits = np.load( + prefix + "MCRZ_unitary_matrix_1_over_4_20_34_qubits.npy" +) """ circuit.MCRZ() tester Parameters ---------- `angle` = -1/4 -`control_qubits` = [1, 2] -`target_qubits` = [0, 4] +`control_indices` = [1, 2] +`target_indices` = [0, 4] """ -MCRZ_unitary_matrix_negative1_over_4_12_04_qubits = np.load(prefix + "MCRZ_unitary_matrix_negative1_over_4_12_04_qubits.npy") +MCRZ_unitary_matrix_negative1_over_4_12_04_qubits = np.load( + prefix + "MCRZ_unitary_matrix_negative1_over_4_12_04_qubits.npy" +) """ circuit.MCRZ() tester Parameters ---------- `angle` = -1/4 -`control_qubits` = [5, 3] -`target_qubits` = [0, 1] +`control_indices` = [5, 3] +`target_indices` = [0, 1] """ -MCRZ_unitary_matrix_negative1_over_4_53_01_qubits = np.load(prefix + "MCRZ_unitary_matrix_negative1_over_4_53_01_qubits.npy") +MCRZ_unitary_matrix_negative1_over_4_53_01_qubits = np.load( + prefix + "MCRZ_unitary_matrix_negative1_over_4_53_01_qubits.npy" +) """ circuit.MCRZ() tester Parameters ---------- `angle` = 1/3 -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4] """ -MCRZ_unitary_matrix_1_over_3_012_34_qubits = np.load(prefix + "MCRZ_unitary_matrix_1_over_3_012_34_qubits.npy") +MCRZ_unitary_matrix_1_over_3_012_34_qubits = np.load( + prefix + "MCRZ_unitary_matrix_1_over_3_012_34_qubits.npy" +) """ circuit.MCRZ() tester Parameters ---------- `angle` = 1/3 -`control_qubits` = [0, 1] -`target_qubits` = [2, 3, 4] +`control_indices` = [0, 1] +`target_indices` = [2, 3, 4] """ -MCRZ_unitary_matrix_1_over_3_01_234_qubits = np.load(prefix + "MCRZ_unitary_matrix_1_over_3_01_234_qubits.npy") +MCRZ_unitary_matrix_1_over_3_01_234_qubits = np.load( + prefix + "MCRZ_unitary_matrix_1_over_3_01_234_qubits.npy" +) """ circuit.MCRZ() tester Parameters ---------- `angle` = pi/4 -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4, 5] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4, 5] """ -MCRZ_unitary_matrix_pi_over_4_012_345_qubits = np.load(prefix + "MCRZ_unitary_matrix_pi_over_4_012_345_qubits.npy") +MCRZ_unitary_matrix_pi_over_4_012_345_qubits = np.load( + prefix + "MCRZ_unitary_matrix_pi_over_4_012_345_qubits.npy" +) """ circuit.MCRY() tester Parameters ---------- `angle` = pi/4 -`control_qubits` = [0, 1] -`target_qubits` = [2] +`control_indices` = [0, 1] +`target_indices` = [2] """ -MCRZ_unitary_matrix_pi_over_4_01_2_qubits = np.load(prefix + "MCRZ_unitary_matrix_pi_over_4_01_2_qubits.npy") +MCRZ_unitary_matrix_pi_over_4_01_2_qubits = np.load( + prefix + "MCRZ_unitary_matrix_pi_over_4_01_2_qubits.npy" +) """ circuit.MCRZ() tester Parameters ---------- `angle` = pi/4 -`control_qubits` = [0] -`target_qubits` = [2, 3] +`control_indices` = [0] +`target_indices` = [2, 3] """ -MCRZ_unitary_matrix_pi_over_4_0_23_qubits = np.load(prefix + "MCRZ_unitary_matrix_pi_over_4_0_23_qubits.npy") +MCRZ_unitary_matrix_pi_over_4_0_23_qubits = np.load( + prefix + "MCRZ_unitary_matrix_pi_over_4_0_23_qubits.npy" +) """ circuit.MCRZ() tester Parameters ---------- `angle` = 0.1 -`control_qubits` = [0, 1, 2, 3, 4, 5] -`target_qubits` = [6] +`control_indices` = [0, 1, 2, 3, 4, 5] +`target_indices` = [6] """ -MCRZ_unitary_matrix_0dot1_012345_6_qubits = np.load(prefix + "MCRZ_unitary_matrix_0dot1_012345_6_qubits.npy") +MCRZ_unitary_matrix_0dot1_012345_6_qubits = np.load( + prefix + "MCRZ_unitary_matrix_0dot1_012345_6_qubits.npy" +) """ circuit.MCRZ() tester Parameters ---------- `angle` = 0.1 -`control_qubits` = [0, 1, 2, 3, 4, 5, 6] -`target_qubits` = [7] +`control_indices` = [0, 1, 2, 3, 4, 5, 6] +`target_indices` = [7] """ -MCRZ_unitary_matrix_0dot1_0123456_7_qubits = np.load(prefix + "MCRZ_unitary_matrix_0dot1_0123456_7_qubits.npy") +MCRZ_unitary_matrix_0dot1_0123456_7_qubits = np.load( + prefix + "MCRZ_unitary_matrix_0dot1_0123456_7_qubits.npy" +) """ circuit.MCRZ() tester Parameters ---------- `angle` = 0.1 -`control_qubits` = [0, 1, 2, 3, 4, 5, 6, 7] -`target_qubits` = [8] +`control_indices` = [0, 1, 2, 3, 4, 5, 6, 7] +`target_indices` = [8] """ -MCRZ_unitary_matrix_0dot1_01234567_8_qubits = np.load(prefix + "MCRZ_unitary_matrix_0dot1_01234567_8_qubits.npy") +MCRZ_unitary_matrix_0dot1_01234567_8_qubits = np.load( + prefix + "MCRZ_unitary_matrix_0dot1_01234567_8_qubits.npy" +) """ circuit.MCPhase() tester Parameters ---------- `angle` = np.pi/4 -`control_qubits` = [0, 1] -`target_qubits` = [2, 3] +`control_indices` = [0, 1] +`target_indices` = [2, 3] """ -MCPhase_unitary_matrix_pi_over_4_01_23_qubits = np.load(prefix + "MCPhase_unitary_matrix_pi_over_4_01_23_qubits.npy") +MCPhase_unitary_matrix_pi_over_4_01_23_qubits = np.load( + prefix + "MCPhase_unitary_matrix_pi_over_4_01_23_qubits.npy" +) """ circuit.MCPhase() tester Parameters ---------- `angle` = np.pi/4 -`control_qubits` = [1, 0] -`target_qubits` = [2, 3] +`control_indices` = [1, 0] +`target_indices` = [2, 3] """ -MCPhase_unitary_matrix_pi_over_4_10_23_qubits = np.load(prefix + "MCPhase_unitary_matrix_pi_over_4_10_23_qubits.npy") +MCPhase_unitary_matrix_pi_over_4_10_23_qubits = np.load( + prefix + "MCPhase_unitary_matrix_pi_over_4_10_23_qubits.npy" +) """ circuit.MCPhase() tester Parameters ---------- `angle` = 1/4 -`control_qubits` = [0, 2] -`target_qubits` = [1, 3] +`control_indices` = [0, 2] +`target_indices` = [1, 3] """ -MCPhase_unitary_matrix_1_over_4_02_13_qubits = np.load(prefix + "MCPhase_unitary_matrix_1_over_4_02_13_qubits.npy") +MCPhase_unitary_matrix_1_over_4_02_13_qubits = np.load( + prefix + "MCPhase_unitary_matrix_1_over_4_02_13_qubits.npy" +) """ circuit.MCPhase() tester Parameters ---------- `angle` = 1/4 -`control_qubits` = [2, 0] -`target_qubits` = [3, 4] +`control_indices` = [2, 0] +`target_indices` = [3, 4] """ -MCPhase_unitary_matrix_1_over_4_20_34_qubits = np.load(prefix + "MCPhase_unitary_matrix_1_over_4_20_34_qubits.npy") +MCPhase_unitary_matrix_1_over_4_20_34_qubits = np.load( + prefix + "MCPhase_unitary_matrix_1_over_4_20_34_qubits.npy" +) """ circuit.MCPhase() tester Parameters ---------- `angle` = -1/4 -`control_qubits` = [1, 2] -`target_qubits` = [0, 4] +`control_indices` = [1, 2] +`target_indices` = [0, 4] """ -MCPhase_unitary_matrix_negative1_over_4_12_04_qubits = np.load(prefix + "MCPhase_unitary_matrix_negative1_over_4_12_04_qubits.npy") +MCPhase_unitary_matrix_negative1_over_4_12_04_qubits = np.load( + prefix + "MCPhase_unitary_matrix_negative1_over_4_12_04_qubits.npy" +) """ circuit.MCPhase() tester Parameters ---------- `angle` = -1/4 -`control_qubits` = [5, 3] -`target_qubits` = [0, 1] +`control_indices` = [5, 3] +`target_indices` = [0, 1] """ -MCPhase_unitary_matrix_negative1_over_4_53_01_qubits = np.load(prefix + "MCPhase_unitary_matrix_negative1_over_4_53_01_qubits.npy") +MCPhase_unitary_matrix_negative1_over_4_53_01_qubits = np.load( + prefix + "MCPhase_unitary_matrix_negative1_over_4_53_01_qubits.npy" +) """ circuit.MCPhase() tester Parameters ---------- `angle` = 1/3 -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4] """ -MCPhase_unitary_matrix_1_over_3_012_34_qubits = np.load(prefix + "MCPhase_unitary_matrix_1_over_3_012_34_qubits.npy") +MCPhase_unitary_matrix_1_over_3_012_34_qubits = np.load( + prefix + "MCPhase_unitary_matrix_1_over_3_012_34_qubits.npy" +) """ circuit.MCPhase() tester Parameters ---------- `angle` = 1/3 -`control_qubits` = [0, 1] -`target_qubits` = [2, 3, 4] +`control_indices` = [0, 1] +`target_indices` = [2, 3, 4] """ -MCPhase_unitary_matrix_1_over_3_01_234_qubits = np.load(prefix + "MCPhase_unitary_matrix_1_over_3_01_234_qubits.npy") +MCPhase_unitary_matrix_1_over_3_01_234_qubits = np.load( + prefix + "MCPhase_unitary_matrix_1_over_3_01_234_qubits.npy" +) """ circuit.MCPhase() tester Parameters ---------- `angle` = pi/4 -`control_qubits` = [0, 1, 2] -`target_qubits` = [3, 4, 5] +`control_indices` = [0, 1, 2] +`target_indices` = [3, 4, 5] """ -MCPhase_unitary_matrix_pi_over_4_012_345_qubits = np.load(prefix + "MCPhase_unitary_matrix_pi_over_4_012_345_qubits.npy") +MCPhase_unitary_matrix_pi_over_4_012_345_qubits = np.load( + prefix + "MCPhase_unitary_matrix_pi_over_4_012_345_qubits.npy" +) """ circuit.MCPhase() tester Parameters ---------- `angle` = pi/4 -`control_qubits` = [0, 1] -`target_qubits` = [2] +`control_indices` = [0, 1] +`target_indices` = [2] """ -MCPhase_unitary_matrix_pi_over_4_01_2_qubits = np.load(prefix + "MCPhase_unitary_matrix_pi_over_4_01_2_qubits.npy") +MCPhase_unitary_matrix_pi_over_4_01_2_qubits = np.load( + prefix + "MCPhase_unitary_matrix_pi_over_4_01_2_qubits.npy" +) """ circuit.MCPhase() tester Parameters ---------- `angle` = pi/4 -`control_qubits` = [0] -`target_qubits` = [2, 3] +`control_indices` = [0] +`target_indices` = [2, 3] """ -MCPhase_unitary_matrix_pi_over_4_0_23_qubits = np.load(prefix + "MCPhase_unitary_matrix_pi_over_4_0_23_qubits.npy") +MCPhase_unitary_matrix_pi_over_4_0_23_qubits = np.load( + prefix + "MCPhase_unitary_matrix_pi_over_4_0_23_qubits.npy" +) """ circuit.MCXPow() tester @@ -3074,10 +3462,12 @@ ---------- `power` = 1/4 `global_shift` = 0 -`control_qubits` = [0, 1] -`target_qubits` = [2, 3] +`control_indices` = [0, 1] +`target_indices` = [2, 3] """ -MCXPow_unitary_matrix_1_over_4_0_shift_01_23_qubits = np.load(prefix + "MCXPow_unitary_matrix_1_over_4_0_shift_01_23_qubits.npy") +MCXPow_unitary_matrix_1_over_4_0_shift_01_23_qubits = np.load( + prefix + "MCXPow_unitary_matrix_1_over_4_0_shift_01_23_qubits.npy" +) """ circuit.MCXPow() tester @@ -3085,10 +3475,12 @@ ---------- `power` = 1/4 `global_shift` = 0 -`control_qubits` = [1, 0] -`target_qubits` = [2, 3] +`control_indices` = [1, 0] +`target_indices` = [2, 3] """ -MCXPow_unitary_matrix_1_over_4_0_shift_10_23_qubits = np.load(prefix + "MCXPow_unitary_matrix_1_over_4_0_shift_10_23_qubits.npy") +MCXPow_unitary_matrix_1_over_4_0_shift_10_23_qubits = np.load( + prefix + "MCXPow_unitary_matrix_1_over_4_0_shift_10_23_qubits.npy" +) """ circuit.MCXPow() tester @@ -3096,10 +3488,12 @@ ---------- `power` = 1/4 `global_shift` = 0 -`control_qubits` = [0, 2] -`target_qubits` = [1, 3] +`control_indices` = [0, 2] +`target_indices` = [1, 3] """ -MCXPow_unitary_matrix_1_over_4_0_shift_02_13_qubits = np.load(prefix + "MCXPow_unitary_matrix_1_over_4_0_shift_02_13_qubits.npy") +MCXPow_unitary_matrix_1_over_4_0_shift_02_13_qubits = np.load( + prefix + "MCXPow_unitary_matrix_1_over_4_0_shift_02_13_qubits.npy" +) """ circuit.MCXPow() tester @@ -3107,10 +3501,12 @@ ---------- `power` = 1/4 `global_shift` = 0 -`control_qubits` = [2, 0] -`target_qubits` = [3, 4] +`control_indices` = [2, 0] +`target_indices` = [3, 4] """ -MCXPow_unitary_matrix_1_over_4_0_shift_20_34_qubits = np.load(prefix + "MCXPow_unitary_matrix_1_over_4_0_shift_20_34_qubits.npy") +MCXPow_unitary_matrix_1_over_4_0_shift_20_34_qubits = np.load( + prefix + "MCXPow_unitary_matrix_1_over_4_0_shift_20_34_qubits.npy" +) """ circuit.MCXPow() tester @@ -3118,10 +3514,12 @@ ---------- `power` = -1/4 `global_shift` = 0 -`control_qubits` = [1, 2] -`target_qubits` = [0, 4] +`control_indices` = [1, 2] +`target_indices` = [0, 4] """ -MCXPow_unitary_matrix_negative1_over_4_0_shift_12_04_qubits = np.load(prefix + "MCXPow_unitary_matrix_negative1_over_4_0_shift_12_04_qubits.npy") +MCXPow_unitary_matrix_negative1_over_4_0_shift_12_04_qubits = np.load( + prefix + "MCXPow_unitary_matrix_negative1_over_4_0_shift_12_04_qubits.npy" +) """ circuit.MCXPow() tester @@ -3129,10 +3527,12 @@ ---------- `power` = 1/4 `global_shift` = 1/3 -`control_qubits` = [0, 1] -`target_qubits` = [2] +`control_indices` = [0, 1] +`target_indices` = [2] """ -MCXPow_unitary_matrix_1_over_4_1_over_3_shift_01_2_qubits = np.load(prefix + "MCXPow_unitary_matrix_1_over_4_1_over_3_shift_01_2_qubits.npy") +MCXPow_unitary_matrix_1_over_4_1_over_3_shift_01_2_qubits = np.load( + prefix + "MCXPow_unitary_matrix_1_over_4_1_over_3_shift_01_2_qubits.npy" +) """ circuit.MCXPow() tester @@ -3140,10 +3540,12 @@ ---------- `power` = 1/4 `global_shift` = 1/3 -`control_qubits` = [0] -`target_qubits` = [2, 3] +`control_indices` = [0] +`target_indices` = [2, 3] """ -MCXPow_unitary_matrix_1_over_4_1_over_3_shift_0_23_qubits = np.load(prefix + "MCXPow_unitary_matrix_1_over_4_1_over_3_shift_0_23_qubits.npy") +MCXPow_unitary_matrix_1_over_4_1_over_3_shift_0_23_qubits = np.load( + prefix + "MCXPow_unitary_matrix_1_over_4_1_over_3_shift_0_23_qubits.npy" +) """ circuit.MCXPow() tester @@ -3151,10 +3553,13 @@ ---------- `power` = -1/4 `global_shift` = 1/3 -`control_qubits` = [0, 1] -`target_qubits` = [2, 3] +`control_indices` = [0, 1] +`target_indices` = [2, 3] """ -MCXPow_unitary_matrix_negative1_over_4_negative1_over_3_shift_01_23_qubits = np.load(prefix + "MCXPow_unitary_matrix_negative1_over_4_negative1_over_3_shift_01_23_qubits.npy") +MCXPow_unitary_matrix_negative1_over_4_negative1_over_3_shift_01_23_qubits = np.load( + prefix + + "MCXPow_unitary_matrix_negative1_over_4_negative1_over_3_shift_01_23_qubits.npy" +) """ circuit.MCYPow() tester @@ -3162,10 +3567,12 @@ ---------- `power` = 1/4 `global_shift` = 0 -`control_qubits` = [0, 1] -`target_qubits` = [2, 3] +`control_indices` = [0, 1] +`target_indices` = [2, 3] """ -MCYPow_unitary_matrix_1_over_4_0_shift_01_23_qubits = np.load(prefix + "MCYPow_unitary_matrix_1_over_4_0_shift_01_23_qubits.npy") +MCYPow_unitary_matrix_1_over_4_0_shift_01_23_qubits = np.load( + prefix + "MCYPow_unitary_matrix_1_over_4_0_shift_01_23_qubits.npy" +) """ circuit.MCYPow() tester @@ -3173,10 +3580,12 @@ ---------- `power` = 1/4 `global_shift` = 0 -`control_qubits` = [1, 0] -`target_qubits` = [2, 3] +`control_indices` = [1, 0] +`target_indices` = [2, 3] """ -MCYPow_unitary_matrix_1_over_4_0_shift_10_23_qubits = np.load(prefix + "MCYPow_unitary_matrix_1_over_4_0_shift_10_23_qubits.npy") +MCYPow_unitary_matrix_1_over_4_0_shift_10_23_qubits = np.load( + prefix + "MCYPow_unitary_matrix_1_over_4_0_shift_10_23_qubits.npy" +) """ circuit.MCYPow() tester @@ -3184,10 +3593,12 @@ ---------- `power` = 1/4 `global_shift` = 0 -`control_qubits` = [0, 2] -`target_qubits` = [1, 3] +`control_indices` = [0, 2] +`target_indices` = [1, 3] """ -MCYPow_unitary_matrix_1_over_4_0_shift_02_13_qubits = np.load(prefix + "MCYPow_unitary_matrix_1_over_4_0_shift_02_13_qubits.npy") +MCYPow_unitary_matrix_1_over_4_0_shift_02_13_qubits = np.load( + prefix + "MCYPow_unitary_matrix_1_over_4_0_shift_02_13_qubits.npy" +) """ circuit.MCYPow() tester @@ -3195,10 +3606,12 @@ ---------- `power` = 1/4 `global_shift` = 0 -`control_qubits` = [2, 0] -`target_qubits` = [3, 4] +`control_indices` = [2, 0] +`target_indices` = [3, 4] """ -MCYPow_unitary_matrix_1_over_4_0_shift_20_34_qubits = np.load(prefix + "MCYPow_unitary_matrix_1_over_4_0_shift_20_34_qubits.npy") +MCYPow_unitary_matrix_1_over_4_0_shift_20_34_qubits = np.load( + prefix + "MCYPow_unitary_matrix_1_over_4_0_shift_20_34_qubits.npy" +) """ circuit.MCYPow() tester @@ -3206,10 +3619,12 @@ ---------- `power` = -1/4 `global_shift` = 0 -`control_qubits` = [1, 2] -`target_qubits` = [0, 4] +`control_indices` = [1, 2] +`target_indices` = [0, 4] """ -MCYPow_unitary_matrix_negative1_over_4_0_shift_12_04_qubits = np.load(prefix + "MCYPow_unitary_matrix_negative1_over_4_0_shift_12_04_qubits.npy") +MCYPow_unitary_matrix_negative1_over_4_0_shift_12_04_qubits = np.load( + prefix + "MCYPow_unitary_matrix_negative1_over_4_0_shift_12_04_qubits.npy" +) """ circuit.MCYPow() tester @@ -3217,10 +3632,12 @@ ---------- `power` = 1/4 `global_shift` = 1/3 -`control_qubits` = [0, 1] -`target_qubits` = [2] +`control_indices` = [0, 1] +`target_indices` = [2] """ -MCYPow_unitary_matrix_1_over_4_1_over_3_shift_01_2_qubits = np.load(prefix + "MCYPow_unitary_matrix_1_over_4_1_over_3_shift_01_2_qubits.npy") +MCYPow_unitary_matrix_1_over_4_1_over_3_shift_01_2_qubits = np.load( + prefix + "MCYPow_unitary_matrix_1_over_4_1_over_3_shift_01_2_qubits.npy" +) """ circuit.MCYPow() tester @@ -3228,10 +3645,12 @@ ---------- `power` = 1/4 `global_shift` = 1/3 -`control_qubits` = [0] -`target_qubits` = [2, 3] +`control_indices` = [0] +`target_indices` = [2, 3] """ -MCYPow_unitary_matrix_1_over_4_1_over_3_shift_0_23_qubits = np.load(prefix + "MCYPow_unitary_matrix_1_over_4_1_over_3_shift_0_23_qubits.npy") +MCYPow_unitary_matrix_1_over_4_1_over_3_shift_0_23_qubits = np.load( + prefix + "MCYPow_unitary_matrix_1_over_4_1_over_3_shift_0_23_qubits.npy" +) """ circuit.MCYPow() tester @@ -3239,10 +3658,13 @@ ---------- `power` = -1/4 `global_shift` = 1/3 -`control_qubits` = [0, 1] -`target_qubits` = [2, 3] +`control_indices` = [0, 1] +`target_indices` = [2, 3] """ -MCYPow_unitary_matrix_negative1_over_4_negative1_over_3_shift_01_23_qubits = np.load(prefix + "MCYPow_unitary_matrix_negative1_over_4_negative1_over_3_shift_01_23_qubits.npy") +MCYPow_unitary_matrix_negative1_over_4_negative1_over_3_shift_01_23_qubits = np.load( + prefix + + "MCYPow_unitary_matrix_negative1_over_4_negative1_over_3_shift_01_23_qubits.npy" +) """ circuit.MCZPow() tester @@ -3250,10 +3672,12 @@ ---------- `power` = 1/4 `global_shift` = 0 -`control_qubits` = [0, 1] -`target_qubits` = [2, 3] +`control_indices` = [0, 1] +`target_indices` = [2, 3] """ -MCZPow_unitary_matrix_1_over_4_0_shift_01_23_qubits = np.load(prefix + "MCZPow_unitary_matrix_1_over_4_0_shift_01_23_qubits.npy") +MCZPow_unitary_matrix_1_over_4_0_shift_01_23_qubits = np.load( + prefix + "MCZPow_unitary_matrix_1_over_4_0_shift_01_23_qubits.npy" +) """ circuit.MCZPow() tester @@ -3261,10 +3685,12 @@ ---------- `power` = 1/4 `global_shift` = 0 -`control_qubits` = [1, 0] -`target_qubits` = [2, 3] +`control_indices` = [1, 0] +`target_indices` = [2, 3] """ -MCZPow_unitary_matrix_1_over_4_0_shift_10_23_qubits = np.load(prefix + "MCZPow_unitary_matrix_1_over_4_0_shift_10_23_qubits.npy") +MCZPow_unitary_matrix_1_over_4_0_shift_10_23_qubits = np.load( + prefix + "MCZPow_unitary_matrix_1_over_4_0_shift_10_23_qubits.npy" +) """ circuit.MCZPow() tester @@ -3272,10 +3698,12 @@ ---------- `power` = 1/4 `global_shift` = 0 -`control_qubits` = [0, 2] -`target_qubits` = [1, 3] +`control_indices` = [0, 2] +`target_indices` = [1, 3] """ -MCZPow_unitary_matrix_1_over_4_0_shift_02_13_qubits = np.load(prefix + "MCZPow_unitary_matrix_1_over_4_0_shift_02_13_qubits.npy") +MCZPow_unitary_matrix_1_over_4_0_shift_02_13_qubits = np.load( + prefix + "MCZPow_unitary_matrix_1_over_4_0_shift_02_13_qubits.npy" +) """ circuit.MCZPow() tester @@ -3283,10 +3711,12 @@ ---------- `power` = 1/4 `global_shift` = 0 -`control_qubits` = [2, 0] -`target_qubits` = [3, 4] +`control_indices` = [2, 0] +`target_indices` = [3, 4] """ -MCZPow_unitary_matrix_1_over_4_0_shift_20_34_qubits = np.load(prefix + "MCZPow_unitary_matrix_1_over_4_0_shift_20_34_qubits.npy") +MCZPow_unitary_matrix_1_over_4_0_shift_20_34_qubits = np.load( + prefix + "MCZPow_unitary_matrix_1_over_4_0_shift_20_34_qubits.npy" +) """ circuit.MCZPow() tester @@ -3294,10 +3724,12 @@ ---------- `power` = -1/4 `global_shift` = 0 -`control_qubits` = [1, 2] -`target_qubits` = [0, 4] +`control_indices` = [1, 2] +`target_indices` = [0, 4] """ -MCZPow_unitary_matrix_negative1_over_4_0_shift_12_04_qubits = np.load(prefix + "MCZPow_unitary_matrix_negative1_over_4_0_shift_12_04_qubits.npy") +MCZPow_unitary_matrix_negative1_over_4_0_shift_12_04_qubits = np.load( + prefix + "MCZPow_unitary_matrix_negative1_over_4_0_shift_12_04_qubits.npy" +) """ circuit.MCZPow() tester @@ -3305,10 +3737,12 @@ ---------- `power` = 1/4 `global_shift` = 1/3 -`control_qubits` = [0, 1] -`target_qubits` = [2] +`control_indices` = [0, 1] +`target_indices` = [2] """ -MCZPow_unitary_matrix_1_over_4_1_over_3_shift_01_2_qubits = np.load(prefix + "MCZPow_unitary_matrix_1_over_4_1_over_3_shift_01_2_qubits.npy") +MCZPow_unitary_matrix_1_over_4_1_over_3_shift_01_2_qubits = np.load( + prefix + "MCZPow_unitary_matrix_1_over_4_1_over_3_shift_01_2_qubits.npy" +) """ circuit.MCZPow() tester @@ -3316,10 +3750,12 @@ ---------- `power` = 1/4 `global_shift` = 1/3 -`control_qubits` = [0] -`target_qubits` = [2, 3] +`control_indices` = [0] +`target_indices` = [2, 3] """ -MCZPow_unitary_matrix_1_over_4_1_over_3_shift_0_23_qubits = np.load(prefix + "MCZPow_unitary_matrix_1_over_4_1_over_3_shift_0_23_qubits.npy") +MCZPow_unitary_matrix_1_over_4_1_over_3_shift_0_23_qubits = np.load( + prefix + "MCZPow_unitary_matrix_1_over_4_1_over_3_shift_0_23_qubits.npy" +) """ circuit.MCZPow() tester @@ -3327,810 +3763,864 @@ ---------- `power` = -1/4 `global_shift` = 1/3 -`control_qubits` = [0, 1] -`target_qubits` = [2, 3] +`control_indices` = [0, 1] +`target_indices` = [2, 3] """ -MCZPow_unitary_matrix_negative1_over_4_negative1_over_3_shift_01_23_qubits = np.load(prefix + "MCZPow_unitary_matrix_negative1_over_4_negative1_over_3_shift_01_23_qubits.npy") +MCZPow_unitary_matrix_negative1_over_4_negative1_over_3_shift_01_23_qubits = np.load( + prefix + + "MCZPow_unitary_matrix_negative1_over_4_negative1_over_3_shift_01_23_qubits.npy" +) """ circuit.MCRXX() tester Parameters ---------- `angle` = np.pi/4 -`control_qubits` = [0, 1] +`control_indices` = [0, 1] `first_target_index` = 2 `second_target_index` = 3 """ -MCRXX_unitary_matrix_pi_over_4_01_23_qubits = np.load(prefix + "MCRXX_unitary_matrix_pi_over_4_01_23_qubits.npy") +MCRXX_unitary_matrix_pi_over_4_01_23_qubits = np.load( + prefix + "MCRXX_unitary_matrix_pi_over_4_01_23_qubits.npy" +) """ circuit.MCRXX() tester Parameters ---------- `angle` = np.pi/4 -`control_qubits` = [1, 0] +`control_indices` = [1, 0] `first_target_index` = 2 `second_target_index` = 3 """ -MCRXX_unitary_matrix_pi_over_4_10_23_qubits = np.load(prefix + "MCRXX_unitary_matrix_pi_over_4_10_23_qubits.npy") +MCRXX_unitary_matrix_pi_over_4_10_23_qubits = np.load( + prefix + "MCRXX_unitary_matrix_pi_over_4_10_23_qubits.npy" +) """ circuit.MCRXX() tester Parameters ---------- `angle` = 1/4 -`control_qubits` = [0, 2] +`control_indices` = [0, 2] `first_target_index` = 1 `second_target_index` = 3 """ -MCRXX_unitary_matrix_1_over_4_02_13_qubits = np.load(prefix + "MCRXX_unitary_matrix_1_over_4_02_13_qubits.npy") +MCRXX_unitary_matrix_1_over_4_02_13_qubits = np.load( + prefix + "MCRXX_unitary_matrix_1_over_4_02_13_qubits.npy" +) """ circuit.MCRXX() tester Parameters ---------- `angle` = 1/4 -`control_qubits` = [2, 0] +`control_indices` = [2, 0] `first_target_index` = 3 `second_target_index` = 4 """ -MCRXX_unitary_matrix_1_over_4_20_34_qubits = np.load(prefix + "MCRXX_unitary_matrix_1_over_4_20_34_qubits.npy") +MCRXX_unitary_matrix_1_over_4_20_34_qubits = np.load( + prefix + "MCRXX_unitary_matrix_1_over_4_20_34_qubits.npy" +) """ circuit.MCRXX() tester Parameters ---------- `angle` = -1/4 -`control_qubits` = [1, 2] +`control_indices` = [1, 2] `first_target_index` = 0 `second_target_index` = 4 """ -MCRXX_unitary_matrix_negative1_over_4_12_04_qubits = np.load(prefix + "MCRXX_unitary_matrix_negative1_over_4_12_04_qubits.npy") +MCRXX_unitary_matrix_negative1_over_4_12_04_qubits = np.load( + prefix + "MCRXX_unitary_matrix_negative1_over_4_12_04_qubits.npy" +) """ circuit.MCRXX() tester Parameters ---------- `angle` = -1/4 -`control_qubits` = [5, 3] +`control_indices` = [5, 3] `first_target_index` = 0 `second_target_index` = 1 """ -MCRXX_unitary_matrix_negative1_over_4_53_01_qubits = np.load(prefix + "MCRXX_unitary_matrix_negative1_over_4_53_01_qubits.npy") +MCRXX_unitary_matrix_negative1_over_4_53_01_qubits = np.load( + prefix + "MCRXX_unitary_matrix_negative1_over_4_53_01_qubits.npy" +) """ circuit.MCRXX() tester Parameters ---------- `angle` = 1/3 -`control_qubits` = [0, 1, 2] +`control_indices` = [0, 1, 2] `first_target_index` = 3 `second_target_index` = 4 """ -MCRXX_unitary_matrix_1_over_3_012_34_qubits = np.load(prefix + "MCRXX_unitary_matrix_1_over_3_012_34_qubits.npy") +MCRXX_unitary_matrix_1_over_3_012_34_qubits = np.load( + prefix + "MCRXX_unitary_matrix_1_over_3_012_34_qubits.npy" +) """ circuit.MCRXX() tester Parameters ---------- `angle` = 1/3 -`control_qubits` = [0] +`control_indices` = [0] `first_target_index` = 2 `second_target_index` = 3 """ -MCRXX_unitary_matrix_1_over_3_0_23_qubits = np.load(prefix + "MCRXX_unitary_matrix_1_over_3_0_23_qubits.npy") +MCRXX_unitary_matrix_1_over_3_0_23_qubits = np.load( + prefix + "MCRXX_unitary_matrix_1_over_3_0_23_qubits.npy" +) """ circuit.MCRYY() tester Parameters ---------- `angle` = np.pi/4 -`control_qubits` = [0, 1] +`control_indices` = [0, 1] `first_target_index` = 2 `second_target_index` = 3 """ -MCRYY_unitary_matrix_pi_over_4_01_23_qubits = np.load(prefix + "MCRYY_unitary_matrix_pi_over_4_01_23_qubits.npy") +MCRYY_unitary_matrix_pi_over_4_01_23_qubits = np.load( + prefix + "MCRYY_unitary_matrix_pi_over_4_01_23_qubits.npy" +) """ circuit.MCRYY() tester Parameters ---------- `angle` = np.pi/4 -`control_qubits` = [1, 0] +`control_indices` = [1, 0] `first_target_index` = 2 `second_target_index` = 3 """ -MCRYY_unitary_matrix_pi_over_4_10_23_qubits = np.load(prefix + "MCRYY_unitary_matrix_pi_over_4_10_23_qubits.npy") +MCRYY_unitary_matrix_pi_over_4_10_23_qubits = np.load( + prefix + "MCRYY_unitary_matrix_pi_over_4_10_23_qubits.npy" +) """ circuit.MCRYY() tester Parameters ---------- `angle` = 1/4 -`control_qubits` = [0, 2] +`control_indices` = [0, 2] `first_target_index` = 1 `second_target_index` = 3 """ -MCRYY_unitary_matrix_1_over_4_02_13_qubits = np.load(prefix + "MCRYY_unitary_matrix_1_over_4_02_13_qubits.npy") +MCRYY_unitary_matrix_1_over_4_02_13_qubits = np.load( + prefix + "MCRYY_unitary_matrix_1_over_4_02_13_qubits.npy" +) """ circuit.MCRYY() tester Parameters ---------- `angle` = 1/4 -`control_qubits` = [2, 0] +`control_indices` = [2, 0] `first_target_index` = 3 `second_target_index` = 4 """ -MCRYY_unitary_matrix_1_over_4_20_34_qubits = np.load(prefix + "MCRYY_unitary_matrix_1_over_4_20_34_qubits.npy") +MCRYY_unitary_matrix_1_over_4_20_34_qubits = np.load( + prefix + "MCRYY_unitary_matrix_1_over_4_20_34_qubits.npy" +) """ circuit.MCRYY() tester Parameters ---------- `angle` = -1/4 -`control_qubits` = [1, 2] +`control_indices` = [1, 2] `first_target_index` = 0 `second_target_index` = 4 """ -MCRYY_unitary_matrix_negative1_over_4_12_04_qubits = np.load(prefix + "MCRYY_unitary_matrix_negative1_over_4_12_04_qubits.npy") +MCRYY_unitary_matrix_negative1_over_4_12_04_qubits = np.load( + prefix + "MCRYY_unitary_matrix_negative1_over_4_12_04_qubits.npy" +) """ circuit.MCRYY() tester Parameters ---------- `angle` = -1/4 -`control_qubits` = [5, 3] +`control_indices` = [5, 3] `first_target_index` = 0 `second_target_index` = 1 """ -MCRYY_unitary_matrix_negative1_over_4_53_01_qubits = np.load(prefix + "MCRYY_unitary_matrix_negative1_over_4_53_01_qubits.npy") +MCRYY_unitary_matrix_negative1_over_4_53_01_qubits = np.load( + prefix + "MCRYY_unitary_matrix_negative1_over_4_53_01_qubits.npy" +) """ circuit.MCRYY() tester Parameters ---------- `angle` = 1/3 -`control_qubits` = [0, 1, 2] +`control_indices` = [0, 1, 2] `first_target_index` = 3 `second_target_index` = 4 """ -MCRYY_unitary_matrix_1_over_3_012_34_qubits = np.load(prefix + "MCRYY_unitary_matrix_1_over_3_012_34_qubits.npy") +MCRYY_unitary_matrix_1_over_3_012_34_qubits = np.load( + prefix + "MCRYY_unitary_matrix_1_over_3_012_34_qubits.npy" +) """ circuit.MCRYY() tester Parameters ---------- `angle` = 1/3 -`control_qubits` = [0] +`control_indices` = [0] `first_target_index` = 2 `second_target_index` = 3 """ -MCRYY_unitary_matrix_1_over_3_0_23_qubits = np.load(prefix + "MCRYY_unitary_matrix_1_over_3_0_23_qubits.npy") +MCRYY_unitary_matrix_1_over_3_0_23_qubits = np.load( + prefix + "MCRYY_unitary_matrix_1_over_3_0_23_qubits.npy" +) """ circuit.MCRZZ() tester Parameters ---------- `angle` = np.pi/4 -`control_qubits` = [0, 1] +`control_indices` = [0, 1] `first_target_index` = 2 `second_target_index` = 3 """ -MCRZZ_unitary_matrix_pi_over_4_01_23_qubits = np.load(prefix + "MCRZZ_unitary_matrix_pi_over_4_01_23_qubits.npy") +MCRZZ_unitary_matrix_pi_over_4_01_23_qubits = np.load( + prefix + "MCRZZ_unitary_matrix_pi_over_4_01_23_qubits.npy" +) """ circuit.MCRZZ() tester Parameters ---------- `angle` = np.pi/4 -`control_qubits` = [1, 0] +`control_indices` = [1, 0] `first_target_index` = 2 `second_target_index` = 3 """ -MCRZZ_unitary_matrix_pi_over_4_10_23_qubits = np.load(prefix + "MCRZZ_unitary_matrix_pi_over_4_10_23_qubits.npy") +MCRZZ_unitary_matrix_pi_over_4_10_23_qubits = np.load( + prefix + "MCRZZ_unitary_matrix_pi_over_4_10_23_qubits.npy" +) """ circuit.MCRZZ() tester Parameters ---------- `angle` = 1/4 -`control_qubits` = [0, 2] +`control_indices` = [0, 2] `first_target_index` = 1 `second_target_index` = 3 """ -MCRZZ_unitary_matrix_1_over_4_02_13_qubits = np.load(prefix + "MCRZZ_unitary_matrix_1_over_4_02_13_qubits.npy") +MCRZZ_unitary_matrix_1_over_4_02_13_qubits = np.load( + prefix + "MCRZZ_unitary_matrix_1_over_4_02_13_qubits.npy" +) """ circuit.MCRZZ() tester Parameters ---------- `angle` = 1/4 -`control_qubits` = [2, 0] +`control_indices` = [2, 0] `first_target_index` = 3 `second_target_index` = 4 """ -MCRZZ_unitary_matrix_1_over_4_20_34_qubits = np.load(prefix + "MCRZZ_unitary_matrix_1_over_4_20_34_qubits.npy") +MCRZZ_unitary_matrix_1_over_4_20_34_qubits = np.load( + prefix + "MCRZZ_unitary_matrix_1_over_4_20_34_qubits.npy" +) """ circuit.MCRZZ() tester Parameters ---------- `angle` = -1/4 -`control_qubits` = [1, 2] +`control_indices` = [1, 2] `first_target_index` = 0 `second_target_index` = 4 """ -MCRZZ_unitary_matrix_negative1_over_4_12_04_qubits = np.load(prefix + "MCRZZ_unitary_matrix_negative1_over_4_12_04_qubits.npy") +MCRZZ_unitary_matrix_negative1_over_4_12_04_qubits = np.load( + prefix + "MCRZZ_unitary_matrix_negative1_over_4_12_04_qubits.npy" +) """ circuit.MCRZZ() tester Parameters ---------- `angle` = -1/4 -`control_qubits` = [5, 3] +`control_indices` = [5, 3] `first_target_index` = 0 `second_target_index` = 1 """ -MCRZZ_unitary_matrix_negative1_over_4_53_01_qubits = np.load(prefix + "MCRZZ_unitary_matrix_negative1_over_4_53_01_qubits.npy") +MCRZZ_unitary_matrix_negative1_over_4_53_01_qubits = np.load( + prefix + "MCRZZ_unitary_matrix_negative1_over_4_53_01_qubits.npy" +) """ circuit.MCRZZ() tester Parameters ---------- `angle` = 1/3 -`control_qubits` = [0, 1, 2] +`control_indices` = [0, 1, 2] `first_target_index` = 3 `second_target_index` = 4 """ -MCRZZ_unitary_matrix_1_over_3_012_34_qubits = np.load(prefix + "MCRZZ_unitary_matrix_1_over_3_012_34_qubits.npy") +MCRZZ_unitary_matrix_1_over_3_012_34_qubits = np.load( + prefix + "MCRZZ_unitary_matrix_1_over_3_012_34_qubits.npy" +) """ circuit.MCRZZ() tester Parameters ---------- `angle` = 1/3 -`control_qubits` = [0] +`control_indices` = [0] `first_target_index` = 2 `second_target_index` = 3 """ -MCRZZ_unitary_matrix_1_over_3_0_23_qubits = np.load(prefix + "MCRZZ_unitary_matrix_1_over_3_0_23_qubits.npy") +MCRZZ_unitary_matrix_1_over_3_0_23_qubits = np.load( + prefix + "MCRZZ_unitary_matrix_1_over_3_0_23_qubits.npy" +) """ circuit.MCU3() tester Parameters ---------- `angles` = (np.pi/2, np.pi/3, np.pi/4) -`control_qubits` = [0, 1] -`target_qubits` = [2, 3] +`control_indices` = [0, 1] +`target_indices` = [2, 3] """ -MCU3_unitary_matrix_pi_over_2_pi_over_3_pi_over_4_01_23_qubits = np.load(prefix + "MCU3_unitary_matrix_pi_over_2_pi_over_3_pi_over_4_01_23_qubits.npy") +MCU3_unitary_matrix_pi_over_2_pi_over_3_pi_over_4_01_23_qubits = np.load( + prefix + "MCU3_unitary_matrix_pi_over_2_pi_over_3_pi_over_4_01_23_qubits.npy" +) """ circuit.MCU3() tester Parameters ---------- `angles` = (np.pi/2, np.pi/3, np.pi/4) -`control_qubits` = [1, 0] -`target_qubits` = [2, 3] +`control_indices` = [1, 0] +`target_indices` = [2, 3] """ -MCU3_unitary_matrix_pi_over_2_pi_over_3_pi_over_4_10_23_qubits = np.load(prefix + "MCU3_unitary_matrix_pi_over_2_pi_over_3_pi_over_4_10_23_qubits.npy") +MCU3_unitary_matrix_pi_over_2_pi_over_3_pi_over_4_10_23_qubits = np.load( + prefix + "MCU3_unitary_matrix_pi_over_2_pi_over_3_pi_over_4_10_23_qubits.npy" +) """ circuit.MCU3() tester Parameters ---------- `angles` = (1/2, 1/3, 1/4) -`control_qubits` = [0, 2] -`target_qubits` = [1, 3] +`control_indices` = [0, 2] +`target_indices` = [1, 3] """ -MCU3_unitary_matrix_1_over_2_1_over_3_1_over_4_02_13_qubits = np.load(prefix + "MCU3_unitary_matrix_1_over_2_1_over_3_1_over_4_02_13_qubits.npy") +MCU3_unitary_matrix_1_over_2_1_over_3_1_over_4_02_13_qubits = np.load( + prefix + "MCU3_unitary_matrix_1_over_2_1_over_3_1_over_4_02_13_qubits.npy" +) """ circuit.MCU3() tester Parameters ---------- `angles` = (1/2, 1/3, 1/4) -`control_qubits` = [2, 0] -`target_qubits` = [3, 4] +`control_indices` = [2, 0] +`target_indices` = [3, 4] """ -MCU3_unitary_matrix_1_over_2_1_over_3_1_over_4_20_34_qubits = np.load(prefix + "MCU3_unitary_matrix_1_over_2_1_over_3_1_over_4_20_34_qubits.npy") +MCU3_unitary_matrix_1_over_2_1_over_3_1_over_4_20_34_qubits = np.load( + prefix + "MCU3_unitary_matrix_1_over_2_1_over_3_1_over_4_20_34_qubits.npy" +) """ circuit.MCU3() tester Parameters ---------- `angles` = (-1/2, -1/3, -1/4) -`control_qubits` = [1, 2] -`target_qubits` = [0, 4] +`control_indices` = [1, 2] +`target_indices` = [0, 4] """ -MCU3_unitary_matrix_negative1_over_2_negative1_over_3_negative1_over_4_12_04_qubits = np.load(prefix + "MCU3_unitary_matrix_negative1_over_2_negative1_over_3_negative1_over_4_12_04_qubits.npy") +MCU3_unitary_matrix_negative1_over_2_negative1_over_3_negative1_over_4_12_04_qubits = np.load( + prefix + + "MCU3_unitary_matrix_negative1_over_2_negative1_over_3_negative1_over_4_12_04_qubits.npy" +) """ circuit.MCU3() tester Parameters ---------- `angles` = (np.pi/2, np.pi/3, np.pi/4) -`control_qubits` = [1, 2, 3] -`target_qubits` = [4, 5] +`control_indices` = [1, 2, 3] +`target_indices` = [4, 5] """ -MCU3_unitary_matrix_pi_over_2_pi_over_3_pi_over_4_123_45_qubits = np.load(prefix + "MCU3_unitary_matrix_pi_over_2_pi_over_3_pi_over_4_123_45_qubits.npy") +MCU3_unitary_matrix_pi_over_2_pi_over_3_pi_over_4_123_45_qubits = np.load( + prefix + "MCU3_unitary_matrix_pi_over_2_pi_over_3_pi_over_4_123_45_qubits.npy" +) """ circuit.MCU3() tester Parameters ---------- `angles` = (np.pi/2, np.pi/3, np.pi/4) -`control_qubits` = [0] -`target_qubits` = [1, 2] +`control_indices` = [0] +`target_indices` = [1, 2] """ -MCU3_unitary_matrix_pi_over_2_pi_over_3_pi_over_4_0_12_qubits = np.load(prefix + "MCU3_unitary_matrix_pi_over_2_pi_over_3_pi_over_4_0_12_qubits.npy") +MCU3_unitary_matrix_pi_over_2_pi_over_3_pi_over_4_0_12_qubits = np.load( + prefix + "MCU3_unitary_matrix_pi_over_2_pi_over_3_pi_over_4_0_12_qubits.npy" +) """ circuit.MCSWAP() tester Parameters ---------- -`control_qubits` = [0, 1] +`control_indices` = [0, 1] `first_target_index` = 2 `second_target_index` = 3 """ -MCSWAP_unitary_matrix_01_23_qubits = np.load(prefix + "MCSWAP_unitary_matrix_01_23_qubits.npy") +MCSWAP_unitary_matrix_01_23_qubits = np.load( + prefix + "MCSWAP_unitary_matrix_01_23_qubits.npy" +) """ circuit.MCSWAP() tester Parameters ---------- -`control_qubits` = [1, 0] +`control_indices` = [1, 0] `first_target_index` = 2 `second_target_index` = 3 """ -MCSWAP_unitary_matrix_10_23_qubits = np.load(prefix + "MCSWAP_unitary_matrix_10_23_qubits.npy") +MCSWAP_unitary_matrix_10_23_qubits = np.load( + prefix + "MCSWAP_unitary_matrix_10_23_qubits.npy" +) """ circuit.MCSWAP() tester Parameters ---------- -`control_qubits` = [0, 2] +`control_indices` = [0, 2] `first_target_index` = 1 `second_target_index` = 3 """ -MCSWAP_unitary_matrix_02_13_qubits = np.load(prefix + "MCSWAP_unitary_matrix_02_13_qubits.npy") +MCSWAP_unitary_matrix_02_13_qubits = np.load( + prefix + "MCSWAP_unitary_matrix_02_13_qubits.npy" +) """ circuit.MCSWAP() tester Parameters ---------- -`control_qubits` = [2, 0] +`control_indices` = [2, 0] `first_target_index` = 3 `second_target_index` = 4 """ -MCSWAP_unitary_matrix_20_34_qubits = np.load(prefix + "MCSWAP_unitary_matrix_20_34_qubits.npy") +MCSWAP_unitary_matrix_20_34_qubits = np.load( + prefix + "MCSWAP_unitary_matrix_20_34_qubits.npy" +) """ circuit.MCSWAP() tester Parameters ---------- -`control_qubits` = [1, 2, 3] +`control_indices` = [1, 2, 3] `first_target_index` = 4 `second_target_index` = 5 """ -MCSWAP_unitary_matrix_123_45_qubits = np.load(prefix + "MCSWAP_unitary_matrix_123_45_qubits.npy") +MCSWAP_unitary_matrix_123_45_qubits = np.load( + prefix + "MCSWAP_unitary_matrix_123_45_qubits.npy" +) """ circuit.MCSWAP() tester Parameters ---------- -`control_qubits` = [0] +`control_indices` = [0] `first_target_index` = 2 `second_target_index` = 3 """ -MCSWAP_unitary_matrix_0_23_qubits = np.load(prefix + "MCSWAP_unitary_matrix_0_23_qubits.npy") +MCSWAP_unitary_matrix_0_23_qubits = np.load( + prefix + "MCSWAP_unitary_matrix_0_23_qubits.npy" +) """ circuit.UCRX() tester Parameters ---------- `angles` = (np.pi/2, np.pi/3, np.pi/4, np.pi/5) -`control_qubits` = [0, 1] -`target_qubits` = 2 +`control_indices` = [0, 1] +`target_index` = 2 """ -UCRX_unitary_matrix_3qubits_01control = np.load(prefix + "UCRX_unitary_matrix_3qubits_01control.npy") +UCRX_unitary_matrix_3qubits_01control = np.load( + prefix + "UCRX_unitary_matrix_3qubits_01control.npy" +) """ circuit.UCRX() tester Parameters ---------- `angles` = (np.pi/2, np.pi/3, np.pi/4, np.pi/5) -`control_qubits` = [1, 0] -`target_qubits` = 2 +`control_indices` = [1, 0] +`target_index` = 2 """ -UCRX_unitary_matrix_3qubits_10control = np.load(prefix + "UCRX_unitary_matrix_3qubits_10control.npy") +UCRX_unitary_matrix_3qubits_10control = np.load( + prefix + "UCRX_unitary_matrix_3qubits_10control.npy" +) """ circuit.UCRX() tester Parameters ---------- `angles` = (np.pi/2, np.pi/3, np.pi/4, np.pi/5, np.pi/6, np.pi/7, np.pi/8, np.pi/9) -`control_qubits` = [0, 2, 3] -`target_qubits` = 1 +`control_indices` = [0, 2, 3] +`target_index` = 1 """ -UCRX_unitary_matrix_4qubits_023control = np.load(prefix + "UCRX_unitary_matrix_4qubits_023control.npy") +UCRX_unitary_matrix_4qubits_023control = np.load( + prefix + "UCRX_unitary_matrix_4qubits_023control.npy" +) """ circuit.UCRX() tester Parameters ---------- `angles` = (np.pi/2, np.pi/3, np.pi/4, np.pi/5, np.pi/6, np.pi/7, np.pi/8, np.pi/9) -`control_qubits` = [2, 1, 3] -`target_qubits` = 0 +`control_indices` = [2, 1, 3] +`target_index` = 0 """ -UCRX_unitary_matrix_4qubits_213control = np.load(prefix + "UCRX_unitary_matrix_4qubits_213control.npy") +UCRX_unitary_matrix_4qubits_213control = np.load( + prefix + "UCRX_unitary_matrix_4qubits_213control.npy" +) """ circuit.UCRY() tester Parameters ---------- `angles` = (np.pi/2, np.pi/3, np.pi/4, np.pi/5) -`control_qubits` = [0, 1] -`target_qubits` = 2 +`control_indices` = [0, 1] +`target_index` = 2 """ -UCRY_unitary_matrix_3qubits_01control = np.load(prefix + "UCRY_unitary_matrix_3qubits_01control.npy") +UCRY_unitary_matrix_3qubits_01control = np.load( + prefix + "UCRY_unitary_matrix_3qubits_01control.npy" +) """ circuit.UCRY() tester Parameters ---------- `angles` = (np.pi/2, np.pi/3, np.pi/4, np.pi/5) -`control_qubits` = [1, 0] -`target_qubits` = 2 +`control_indices` = [1, 0] +`target_index` = 2 """ -UCRY_unitary_matrix_3qubits_10control = np.load(prefix + "UCRY_unitary_matrix_3qubits_10control.npy") +UCRY_unitary_matrix_3qubits_10control = np.load( + prefix + "UCRY_unitary_matrix_3qubits_10control.npy" +) """ circuit.UCRY() tester Parameters ---------- `angles` = (np.pi/2, np.pi/3, np.pi/4, np.pi/5, np.pi/6, np.pi/7, np.pi/8, np.pi/9) -`control_qubits` = [0, 2, 3] -`target_qubits` = 1 +`control_indices` = [0, 2, 3] +`target_index` = 1 """ -UCRY_unitary_matrix_4qubits_023control = np.load(prefix + "UCRY_unitary_matrix_4qubits_023control.npy") +UCRY_unitary_matrix_4qubits_023control = np.load( + prefix + "UCRY_unitary_matrix_4qubits_023control.npy" +) """ circuit.UCRY() tester Parameters ---------- `angles` = (np.pi/2, np.pi/3, np.pi/4, np.pi/5, np.pi/6, np.pi/7, np.pi/8, np.pi/9) -`control_qubits` = [2, 1, 3] -`target_qubits` = 0 +`control_indices` = [2, 1, 3] +`target_index` = 0 """ -UCRY_unitary_matrix_4qubits_213control = np.load(prefix + "UCRY_unitary_matrix_4qubits_213control.npy") +UCRY_unitary_matrix_4qubits_213control = np.load( + prefix + "UCRY_unitary_matrix_4qubits_213control.npy" +) """ circuit.UCRZ() tester Parameters ---------- `angles` = (np.pi/2, np.pi/3, np.pi/4, np.pi/5) -`control_qubits` = [0, 1] -`target_qubits` = 2 +`control_indices` = [0, 1] +`target_index` = 2 """ -UCRZ_unitary_matrix_3qubits_01control = np.load(prefix + "UCRZ_unitary_matrix_3qubits_01control.npy") +UCRZ_unitary_matrix_3qubits_01control = np.load( + prefix + "UCRZ_unitary_matrix_3qubits_01control.npy" +) """ circuit.UCRZ() tester Parameters ---------- `angles` = (np.pi/2, np.pi/3, np.pi/4, np.pi/5) -`control_qubits` = [1, 0] -`target_qubits` = 2 +`control_indices` = [1, 0] +`target_index` = 2 """ -UCRZ_unitary_matrix_3qubits_10control = np.load(prefix + "UCRZ_unitary_matrix_3qubits_10control.npy") +UCRZ_unitary_matrix_3qubits_10control = np.load( + prefix + "UCRZ_unitary_matrix_3qubits_10control.npy" +) """ circuit.UCRZ() tester Parameters ---------- `angles` = (np.pi/2, np.pi/3, np.pi/4, np.pi/5, np.pi/6, np.pi/7, np.pi/8, np.pi/9) -`control_qubits` = [0, 2, 3] -`target_qubits` = 1 +`control_indices` = [0, 2, 3] +`target_index` = 1 """ -UCRZ_unitary_matrix_4qubits_023control = np.load(prefix + "UCRZ_unitary_matrix_4qubits_023control.npy") +UCRZ_unitary_matrix_4qubits_023control = np.load( + prefix + "UCRZ_unitary_matrix_4qubits_023control.npy" +) """ circuit.UCRZ() tester Parameters ---------- `angles` = (np.pi/2, np.pi/3, np.pi/4, np.pi/5, np.pi/6, np.pi/7, np.pi/8, np.pi/9) -`control_qubits` = [2, 1, 3] -`target_qubits` = 0 -""" -UCRZ_unitary_matrix_4qubits_213control = np.load(prefix + "UCRZ_unitary_matrix_4qubits_213control.npy") - -""" circuit.UC() tester - -Parameters ----------- -`gates` = [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix] -`control_qubits` = [0, 1] -`target_qubits` = 2 -`up_to_diagonal` = False -`multiplexor_simplification` = False -""" -UC_unitary_matrix_no_diagonal_no_simplification_3qubits_01control_HXHX = np.load(prefix + "UC_unitary_matrix_no_diagonal_no_simplification_3qubits_01control_HXHX.npy") - -""" circuit.UC() tester - -Parameters ----------- -`gates` = [Hadamard().matrix, PauliY().matrix, Hadamard().matrix, PauliY().matrix] -`control_qubits` = [1, 0] -`target_qubits` = 2 -`up_to_diagonal` = False -`multiplexor_simplification` = False -""" -UC_unitary_matrix_no_diagonal_no_simplification_3qubits_10control_HYHY = np.load(prefix + "UC_unitary_matrix_no_diagonal_no_simplification_3qubits_10control_HYHY.npy") - -""" circuit.UC() tester - -Parameters ----------- -`gates` = [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] -`control_qubits` = [0, 2, 3] -`target_qubits` = 1 -`up_to_diagonal` = False -`multiplexor_simplification` = False -""" -UC_unitary_matrix_no_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY = np.load(prefix + "UC_unitary_matrix_no_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY.npy") - -""" circuit.UC() tester - -Parameters ----------- -`gates` = [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] -`control_qubits` = [2, 1, 3] -`target_qubits` = 0 -`up_to_diagonal` = False -`multiplexor_simplification` = False -""" -UC_unitary_matrix_no_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY = np.load(prefix + "UC_unitary_matrix_no_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY.npy") - -""" circuit.UC() tester - -Parameters ----------- -`gates` = [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix] -`control_qubits` = [0, 1] -`target_qubits` = 2 -`up_to_diagonal` = True -`multiplexor_simplification` = False -""" -UC_unitary_matrix_diagonal_no_simplification_3qubits_01control_HXHX = np.load(prefix + "UC_unitary_matrix_diagonal_no_simplification_3qubits_01control_HXHX.npy") - -""" circuit.UC() tester - -Parameters ----------- -`gates` = [Hadamard().matrix, PauliY().matrix, Hadamard().matrix, PauliY().matrix] -`control_qubits` = [1, 0] -`target_qubits` = 2 -`up_to_diagonal` = True -`multiplexor_simplification` = False -""" -UC_unitary_matrix_diagonal_no_simplification_3qubits_10control_HYHY = np.load(prefix + "UC_unitary_matrix_diagonal_no_simplification_3qubits_10control_HYHY.npy") - -""" circuit.UC() tester - -Parameters ----------- -`gates` = [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] -`control_qubits` = [0, 2, 3] -`target_qubits` = 1 -`up_to_diagonal` = True -`multiplexor_simplification` = False -""" -UC_unitary_matrix_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY = np.load(prefix + "UC_unitary_matrix_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY.npy") - -""" circuit.UC() tester - -Parameters ----------- -`gates` = [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] -`control_qubits` = [2, 1, 3] -`target_qubits` = 0 -`up_to_diagonal` = True -`multiplexor_simplification` = False -""" -UC_unitary_matrix_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY = np.load(prefix + "UC_unitary_matrix_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY.npy") - -""" circuit.UC() tester - -Parameters ----------- -`gates` = [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix] -`control_qubits` = [0, 1] -`target_qubits` = 2 -`up_to_diagonal` = False -`multiplexor_simplification` = True -""" -UC_unitary_matrix_no_diagonal_simplification_3qubits_01control_HXHX = np.load(prefix + "UC_unitary_matrix_no_diagonal_simplification_3qubits_01control_HXHX.npy") - -""" circuit.UC() tester - -Parameters ----------- -`gates` = [Hadamard().matrix, PauliY().matrix, Hadamard().matrix, PauliY().matrix] -`control_qubits` = [1, 0] -`target_qubits` = 2 -`up_to_diagonal` = False -`multiplexor_simplification` = True -""" -UC_unitary_matrix_no_diagonal_simplification_3qubits_10control_HYHY = np.load(prefix + "UC_unitary_matrix_no_diagonal_simplification_3qubits_10control_HYHY.npy") - -""" circuit.UC() tester - -Parameters ----------- -`gates` = [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] -`control_qubits` = [0, 2, 3] -`target_qubits` = 1 -`up_to_diagonal` = False -`multiplexor_simplification` = True -""" -UC_unitary_matrix_no_diagonal_simplification_4qubits_023control_RXRYRXRYRXRY = np.load(prefix + "UC_unitary_matrix_no_diagonal_simplification_4qubits_023control_RXRYRXRYRXRY.npy") - -""" circuit.UC() tester - -Parameters ----------- -`gates` = [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] -`control_qubits` = [2, 1, 3] -`target_qubits` = 0 -`up_to_diagonal` = False -`multiplexor_simplification` = True -""" -UC_unitary_matrix_no_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY = np.load(prefix + "UC_unitary_matrix_no_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY.npy") - -""" circuit.UC() tester - -Parameters ----------- -`gates` = [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix] -`control_qubits` = [0, 1] -`target_qubits` = 2 -`up_to_diagonal` = True -`multiplexor_simplification` = True -""" -UC_unitary_matrix_diagonal_simplification_3qubits_01control_HXHX = np.load(prefix + "UC_unitary_matrix_diagonal_simplification_3qubits_01control_HXHX.npy") - -""" circuit.UC() tester - -Parameters ----------- -`gates` = [Hadamard().matrix, PauliY().matrix, Hadamard().matrix, PauliY().matrix] -`control_qubits` = [1, 0] -`target_qubits` = 2 -`up_to_diagonal` = True -`multiplexor_simplification` = True -""" -UC_unitary_matrix_diagonal_simplification_3qubits_10control_HYHY = np.load(prefix + "UC_unitary_matrix_diagonal_simplification_3qubits_10control_HYHY.npy") - -""" circuit.UC() tester - -Parameters ----------- -`gates` = [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] -`control_qubits` = [0, 2, 3] -`target_qubits` = 1 -`up_to_diagonal` = True -`multiplexor_simplification` = True -""" -UC_unitary_matrix_diagonal_simplification_4qubits_023control_RXRYRXRYRXRY = np.load(prefix + "UC_unitary_matrix_diagonal_simplification_4qubits_023control_RXRYRXRYRXRY.npy") - -""" circuit.UC() tester - -Parameters ----------- -`gates` = [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] -`control_qubits` = [2, 1, 3] -`target_qubits` = 0 -`up_to_diagonal` = True -`multiplexor_simplification` = True +`control_indices` = [2, 1, 3] +`target_index` = 0 """ -UC_unitary_matrix_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY = np.load(prefix + "UC_unitary_matrix_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY.npy") +UCRZ_unitary_matrix_4qubits_213control = np.load( + prefix + "UCRZ_unitary_matrix_4qubits_213control.npy" +) # QFT testers for (5, 6, 7, 8) qubits # QFT(no swap, no inverse, approximation_degree = 0) -qft_no_swap_no_inverse_approx0_5qubits = np.load(prefix + "qft_no_swap_no_inverse_approx0_5qubits.npy") -qft_no_swap_no_inverse_approx0_6qubits = np.load(prefix + "qft_no_swap_no_inverse_approx0_6qubits.npy") -qft_no_swap_no_inverse_approx0_7qubits = np.load(prefix + "qft_no_swap_no_inverse_approx0_7qubits.npy") -qft_no_swap_no_inverse_approx0_8qubits = np.load(prefix + "qft_no_swap_no_inverse_approx0_8qubits.npy") + +qft_no_swap_no_inverse_approx0_5qubits = np.load( + prefix + "qft_no_swap_no_inverse_approx0_5qubits.npy" +) +qft_no_swap_no_inverse_approx0_6qubits = np.load( + prefix + "qft_no_swap_no_inverse_approx0_6qubits.npy" +) +qft_no_swap_no_inverse_approx0_7qubits = np.load( + prefix + "qft_no_swap_no_inverse_approx0_7qubits.npy" +) +qft_no_swap_no_inverse_approx0_8qubits = np.load( + prefix + "qft_no_swap_no_inverse_approx0_8qubits.npy" +) # QFT(no swap, no inverse, approximation_degree = 1) -qft_no_swap_no_inverse_approx1_5qubits = np.load(prefix + "qft_no_swap_no_inverse_approx1_5qubits.npy") -qft_no_swap_no_inverse_approx1_6qubits = np.load(prefix + "qft_no_swap_no_inverse_approx1_6qubits.npy") -qft_no_swap_no_inverse_approx1_7qubits = np.load(prefix + "qft_no_swap_no_inverse_approx1_7qubits.npy") -qft_no_swap_no_inverse_approx1_8qubits = np.load(prefix + "qft_no_swap_no_inverse_approx1_8qubits.npy") + +qft_no_swap_no_inverse_approx1_5qubits = np.load( + prefix + "qft_no_swap_no_inverse_approx1_5qubits.npy" +) +qft_no_swap_no_inverse_approx1_6qubits = np.load( + prefix + "qft_no_swap_no_inverse_approx1_6qubits.npy" +) +qft_no_swap_no_inverse_approx1_7qubits = np.load( + prefix + "qft_no_swap_no_inverse_approx1_7qubits.npy" +) +qft_no_swap_no_inverse_approx1_8qubits = np.load( + prefix + "qft_no_swap_no_inverse_approx1_8qubits.npy" +) # QFT(no swap, no inverse, approximation_degree = 2) -qft_no_swap_no_inverse_approx2_5qubits = np.load(prefix + "qft_no_swap_no_inverse_approx2_5qubits.npy") -qft_no_swap_no_inverse_approx2_6qubits = np.load(prefix + "qft_no_swap_no_inverse_approx2_6qubits.npy") -qft_no_swap_no_inverse_approx2_7qubits = np.load(prefix + "qft_no_swap_no_inverse_approx2_7qubits.npy") -qft_no_swap_no_inverse_approx2_8qubits = np.load(prefix + "qft_no_swap_no_inverse_approx2_8qubits.npy") + +qft_no_swap_no_inverse_approx2_5qubits = np.load( + prefix + "qft_no_swap_no_inverse_approx2_5qubits.npy" +) +qft_no_swap_no_inverse_approx2_6qubits = np.load( + prefix + "qft_no_swap_no_inverse_approx2_6qubits.npy" +) +qft_no_swap_no_inverse_approx2_7qubits = np.load( + prefix + "qft_no_swap_no_inverse_approx2_7qubits.npy" +) +qft_no_swap_no_inverse_approx2_8qubits = np.load( + prefix + "qft_no_swap_no_inverse_approx2_8qubits.npy" +) # QFT(no swap, no inverse, approximation_degree = 3) -qft_no_swap_no_inverse_approx3_5qubits = np.load(prefix + "qft_no_swap_no_inverse_approx3_5qubits.npy") -qft_no_swap_no_inverse_approx3_6qubits = np.load(prefix + "qft_no_swap_no_inverse_approx3_6qubits.npy") -qft_no_swap_no_inverse_approx3_7qubits = np.load(prefix + "qft_no_swap_no_inverse_approx3_7qubits.npy") -qft_no_swap_no_inverse_approx3_8qubits = np.load(prefix + "qft_no_swap_no_inverse_approx3_8qubits.npy") + +qft_no_swap_no_inverse_approx3_5qubits = np.load( + prefix + "qft_no_swap_no_inverse_approx3_5qubits.npy" +) +qft_no_swap_no_inverse_approx3_6qubits = np.load( + prefix + "qft_no_swap_no_inverse_approx3_6qubits.npy" +) +qft_no_swap_no_inverse_approx3_7qubits = np.load( + prefix + "qft_no_swap_no_inverse_approx3_7qubits.npy" +) +qft_no_swap_no_inverse_approx3_8qubits = np.load( + prefix + "qft_no_swap_no_inverse_approx3_8qubits.npy" +) # QFT(swap, no inverse, approximation_degree = 0) -qft_swap_no_inverse_approx0_5qubits = np.load(prefix + "qft_swap_no_inverse_approx0_5qubits.npy") -qft_swap_no_inverse_approx0_6qubits = np.load(prefix + "qft_swap_no_inverse_approx0_6qubits.npy") -qft_swap_no_inverse_approx0_7qubits = np.load(prefix + "qft_swap_no_inverse_approx0_7qubits.npy") -qft_swap_no_inverse_approx0_8qubits = np.load(prefix + "qft_swap_no_inverse_approx0_8qubits.npy") + +qft_swap_no_inverse_approx0_5qubits = np.load( + prefix + "qft_swap_no_inverse_approx0_5qubits.npy" +) +qft_swap_no_inverse_approx0_6qubits = np.load( + prefix + "qft_swap_no_inverse_approx0_6qubits.npy" +) +qft_swap_no_inverse_approx0_7qubits = np.load( + prefix + "qft_swap_no_inverse_approx0_7qubits.npy" +) +qft_swap_no_inverse_approx0_8qubits = np.load( + prefix + "qft_swap_no_inverse_approx0_8qubits.npy" +) # QFT(swap, no inverse, approximation_degree = 1) -qft_swap_no_inverse_approx1_5qubits = np.load(prefix + "qft_swap_no_inverse_approx1_5qubits.npy") -qft_swap_no_inverse_approx1_6qubits = np.load(prefix + "qft_swap_no_inverse_approx1_6qubits.npy") -qft_swap_no_inverse_approx1_7qubits = np.load(prefix + "qft_swap_no_inverse_approx1_7qubits.npy") -qft_swap_no_inverse_approx1_8qubits = np.load(prefix + "qft_swap_no_inverse_approx1_8qubits.npy") + +qft_swap_no_inverse_approx1_5qubits = np.load( + prefix + "qft_swap_no_inverse_approx1_5qubits.npy" +) +qft_swap_no_inverse_approx1_6qubits = np.load( + prefix + "qft_swap_no_inverse_approx1_6qubits.npy" +) +qft_swap_no_inverse_approx1_7qubits = np.load( + prefix + "qft_swap_no_inverse_approx1_7qubits.npy" +) +qft_swap_no_inverse_approx1_8qubits = np.load( + prefix + "qft_swap_no_inverse_approx1_8qubits.npy" +) # QFT(swap, no inverse, approximation_degree = 2) -qft_swap_no_inverse_approx2_5qubits = np.load(prefix + "qft_swap_no_inverse_approx2_5qubits.npy") -qft_swap_no_inverse_approx2_6qubits = np.load(prefix + "qft_swap_no_inverse_approx2_6qubits.npy") -qft_swap_no_inverse_approx2_7qubits = np.load(prefix + "qft_swap_no_inverse_approx2_7qubits.npy") -qft_swap_no_inverse_approx2_8qubits = np.load(prefix + "qft_swap_no_inverse_approx2_8qubits.npy") + +qft_swap_no_inverse_approx2_5qubits = np.load( + prefix + "qft_swap_no_inverse_approx2_5qubits.npy" +) +qft_swap_no_inverse_approx2_6qubits = np.load( + prefix + "qft_swap_no_inverse_approx2_6qubits.npy" +) +qft_swap_no_inverse_approx2_7qubits = np.load( + prefix + "qft_swap_no_inverse_approx2_7qubits.npy" +) +qft_swap_no_inverse_approx2_8qubits = np.load( + prefix + "qft_swap_no_inverse_approx2_8qubits.npy" +) # QFT(swap, no inverse, approximation_degree = 3) -qft_swap_no_inverse_approx3_5qubits = np.load(prefix + "qft_swap_no_inverse_approx3_5qubits.npy") -qft_swap_no_inverse_approx3_6qubits = np.load(prefix + "qft_swap_no_inverse_approx3_6qubits.npy") -qft_swap_no_inverse_approx3_7qubits = np.load(prefix + "qft_swap_no_inverse_approx3_7qubits.npy") -qft_swap_no_inverse_approx3_8qubits = np.load(prefix + "qft_swap_no_inverse_approx3_8qubits.npy") + +qft_swap_no_inverse_approx3_5qubits = np.load( + prefix + "qft_swap_no_inverse_approx3_5qubits.npy" +) +qft_swap_no_inverse_approx3_6qubits = np.load( + prefix + "qft_swap_no_inverse_approx3_6qubits.npy" +) +qft_swap_no_inverse_approx3_7qubits = np.load( + prefix + "qft_swap_no_inverse_approx3_7qubits.npy" +) +qft_swap_no_inverse_approx3_8qubits = np.load( + prefix + "qft_swap_no_inverse_approx3_8qubits.npy" +) # QFT(no swap, inverse, approximation_degree = 0) -qft_no_swap_inverse_approx0_5qubits = np.load(prefix + "qft_no_swap_inverse_approx0_5qubits.npy") -qft_no_swap_inverse_approx0_6qubits = np.load(prefix + "qft_no_swap_inverse_approx0_6qubits.npy") -qft_no_swap_inverse_approx0_7qubits = np.load(prefix + "qft_no_swap_inverse_approx0_7qubits.npy") -qft_no_swap_inverse_approx0_8qubits = np.load(prefix + "qft_no_swap_inverse_approx0_8qubits.npy") + +qft_no_swap_inverse_approx0_5qubits = np.load( + prefix + "qft_no_swap_inverse_approx0_5qubits.npy" +) +qft_no_swap_inverse_approx0_6qubits = np.load( + prefix + "qft_no_swap_inverse_approx0_6qubits.npy" +) +qft_no_swap_inverse_approx0_7qubits = np.load( + prefix + "qft_no_swap_inverse_approx0_7qubits.npy" +) +qft_no_swap_inverse_approx0_8qubits = np.load( + prefix + "qft_no_swap_inverse_approx0_8qubits.npy" +) # QFT(no swap, inverse, approximation_degree = 1) -qft_no_swap_inverse_approx1_5qubits = np.load(prefix + "qft_no_swap_inverse_approx1_5qubits.npy") -qft_no_swap_inverse_approx1_6qubits = np.load(prefix + "qft_no_swap_inverse_approx1_6qubits.npy") -qft_no_swap_inverse_approx1_7qubits = np.load(prefix + "qft_no_swap_inverse_approx1_7qubits.npy") -qft_no_swap_inverse_approx1_8qubits = np.load(prefix + "qft_no_swap_inverse_approx1_8qubits.npy") + +qft_no_swap_inverse_approx1_5qubits = np.load( + prefix + "qft_no_swap_inverse_approx1_5qubits.npy" +) +qft_no_swap_inverse_approx1_6qubits = np.load( + prefix + "qft_no_swap_inverse_approx1_6qubits.npy" +) +qft_no_swap_inverse_approx1_7qubits = np.load( + prefix + "qft_no_swap_inverse_approx1_7qubits.npy" +) +qft_no_swap_inverse_approx1_8qubits = np.load( + prefix + "qft_no_swap_inverse_approx1_8qubits.npy" +) # QFT(no swap, inverse, approximation_degree = 2) -qft_no_swap_inverse_approx2_5qubits = np.load(prefix + "qft_no_swap_inverse_approx2_5qubits.npy") -qft_no_swap_inverse_approx2_6qubits = np.load(prefix + "qft_no_swap_inverse_approx2_6qubits.npy") -qft_no_swap_inverse_approx2_7qubits = np.load(prefix + "qft_no_swap_inverse_approx2_7qubits.npy") -qft_no_swap_inverse_approx2_8qubits = np.load(prefix + "qft_no_swap_inverse_approx2_8qubits.npy") + +qft_no_swap_inverse_approx2_5qubits = np.load( + prefix + "qft_no_swap_inverse_approx2_5qubits.npy" +) +qft_no_swap_inverse_approx2_6qubits = np.load( + prefix + "qft_no_swap_inverse_approx2_6qubits.npy" +) +qft_no_swap_inverse_approx2_7qubits = np.load( + prefix + "qft_no_swap_inverse_approx2_7qubits.npy" +) +qft_no_swap_inverse_approx2_8qubits = np.load( + prefix + "qft_no_swap_inverse_approx2_8qubits.npy" +) # QFT(no swap, inverse, approximation_degree = 3) -qft_no_swap_inverse_approx3_5qubits = np.load(prefix + "qft_no_swap_inverse_approx3_5qubits.npy") -qft_no_swap_inverse_approx3_6qubits = np.load(prefix + "qft_no_swap_inverse_approx3_6qubits.npy") -qft_no_swap_inverse_approx3_7qubits = np.load(prefix + "qft_no_swap_inverse_approx3_7qubits.npy") -qft_no_swap_inverse_approx3_8qubits = np.load(prefix + "qft_no_swap_inverse_approx3_8qubits.npy") + +qft_no_swap_inverse_approx3_5qubits = np.load( + prefix + "qft_no_swap_inverse_approx3_5qubits.npy" +) +qft_no_swap_inverse_approx3_6qubits = np.load( + prefix + "qft_no_swap_inverse_approx3_6qubits.npy" +) +qft_no_swap_inverse_approx3_7qubits = np.load( + prefix + "qft_no_swap_inverse_approx3_7qubits.npy" +) +qft_no_swap_inverse_approx3_8qubits = np.load( + prefix + "qft_no_swap_inverse_approx3_8qubits.npy" +) # QFT(swap, inverse, approximation_degree = 0) -qft_swap_inverse_approx0_5qubits = np.load(prefix + "qft_swap_inverse_approx0_5qubits.npy") -qft_swap_inverse_approx0_6qubits = np.load(prefix + "qft_swap_inverse_approx0_6qubits.npy") -qft_swap_inverse_approx0_7qubits = np.load(prefix + "qft_swap_inverse_approx0_7qubits.npy") -qft_swap_inverse_approx0_8qubits = np.load(prefix + "qft_swap_inverse_approx0_8qubits.npy") + +qft_swap_inverse_approx0_5qubits = np.load( + prefix + "qft_swap_inverse_approx0_5qubits.npy" +) +qft_swap_inverse_approx0_6qubits = np.load( + prefix + "qft_swap_inverse_approx0_6qubits.npy" +) +qft_swap_inverse_approx0_7qubits = np.load( + prefix + "qft_swap_inverse_approx0_7qubits.npy" +) +qft_swap_inverse_approx0_8qubits = np.load( + prefix + "qft_swap_inverse_approx0_8qubits.npy" +) # QFT(swap, inverse, approximation_degree = 1) -qft_swap_inverse_approx1_5qubits = np.load(prefix + "qft_swap_inverse_approx1_5qubits.npy") -qft_swap_inverse_approx1_6qubits = np.load(prefix + "qft_swap_inverse_approx1_6qubits.npy") -qft_swap_inverse_approx1_7qubits = np.load(prefix + "qft_swap_inverse_approx1_7qubits.npy") -qft_swap_inverse_approx1_8qubits = np.load(prefix + "qft_swap_inverse_approx1_8qubits.npy") + +qft_swap_inverse_approx1_5qubits = np.load( + prefix + "qft_swap_inverse_approx1_5qubits.npy" +) +qft_swap_inverse_approx1_6qubits = np.load( + prefix + "qft_swap_inverse_approx1_6qubits.npy" +) +qft_swap_inverse_approx1_7qubits = np.load( + prefix + "qft_swap_inverse_approx1_7qubits.npy" +) +qft_swap_inverse_approx1_8qubits = np.load( + prefix + "qft_swap_inverse_approx1_8qubits.npy" +) # QFT(swap, inverse, approximation_degree = 2) -qft_swap_inverse_approx2_5qubits = np.load(prefix + "qft_swap_inverse_approx2_5qubits.npy") -qft_swap_inverse_approx2_6qubits = np.load(prefix + "qft_swap_inverse_approx2_6qubits.npy") -qft_swap_inverse_approx2_7qubits = np.load(prefix + "qft_swap_inverse_approx2_7qubits.npy") -qft_swap_inverse_approx2_8qubits = np.load(prefix + "qft_swap_inverse_approx2_8qubits.npy") + +qft_swap_inverse_approx2_5qubits = np.load( + prefix + "qft_swap_inverse_approx2_5qubits.npy" +) +qft_swap_inverse_approx2_6qubits = np.load( + prefix + "qft_swap_inverse_approx2_6qubits.npy" +) +qft_swap_inverse_approx2_7qubits = np.load( + prefix + "qft_swap_inverse_approx2_7qubits.npy" +) +qft_swap_inverse_approx2_8qubits = np.load( + prefix + "qft_swap_inverse_approx2_8qubits.npy" +) # QFT(swap, inverse, approximation_degree = 3) -qft_swap_inverse_approx3_5qubits = np.load(prefix + "qft_swap_inverse_approx3_5qubits.npy") -qft_swap_inverse_approx3_6qubits = np.load(prefix + "qft_swap_inverse_approx3_6qubits.npy") -qft_swap_inverse_approx3_7qubits = np.load(prefix + "qft_swap_inverse_approx3_7qubits.npy") -qft_swap_inverse_approx3_8qubits = np.load(prefix + "qft_swap_inverse_approx3_8qubits.npy") \ No newline at end of file + +qft_swap_inverse_approx3_5qubits = np.load( + prefix + "qft_swap_inverse_approx3_5qubits.npy" +) +qft_swap_inverse_approx3_6qubits = np.load( + prefix + "qft_swap_inverse_approx3_6qubits.npy" +) +qft_swap_inverse_approx3_7qubits = np.load( + prefix + "qft_swap_inverse_approx3_7qubits.npy" +) +qft_swap_inverse_approx3_8qubits = np.load( + prefix + "qft_swap_inverse_approx3_8qubits.npy" +) \ No newline at end of file diff --git a/tests/circuit/test_ansatz.py b/tests/circuit/test_ansatz.py new file mode 100644 index 0000000..c2d397e --- /dev/null +++ b/tests/circuit/test_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. + +from __future__ import annotations + +__all__ = ["TestAnsatz"] + +import pytest +from quick.circuit import Ansatz, Circuit + +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 diff --git a/tests/circuit/test_circuit_base.py b/tests/circuit/test_circuit_base.py index 3ee9882..9aa9551 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 @@ -34,7 +33,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 +47,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 +62,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 +74,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 +96,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 +118,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 +140,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 +162,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 +185,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 +207,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 +226,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 +245,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 +270,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 +300,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 +327,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 +349,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 +375,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 +406,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 +509,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 +549,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 +583,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 +612,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 +638,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. @@ -651,38 +650,19 @@ 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, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the instructions of the circuit. @@ -709,7 +689,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 +727,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 +758,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 +781,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 +810,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 +829,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. @@ -866,9 +846,9 @@ 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] + circuit_framework: type[Circuit] ) -> None: """ Test the `circuit.convert()` method. @@ -1011,7 +991,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 +1009,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 +1036,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. @@ -1094,10 +1074,79 @@ 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, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `__getitem__` dunder method. @@ -1150,7 +1199,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 +1254,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. @@ -1225,10 +1274,119 @@ 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, - circuit_framework: Type[Circuit] + circuit_framework: type[Circuit] ) -> None: """ Test the `__len__` dunder method. @@ -1250,7 +1408,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 +1430,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 +1452,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 +1479,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..fd19164 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 @@ -40,7 +39,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 +69,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 +118,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 +172,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,11 +274,20 @@ 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: """ 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) @@ -295,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 696766d..608d1cc 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 @@ -30,7 +29,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 +64,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 +99,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 +134,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 +169,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 +204,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 +239,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 +274,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 +309,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 +344,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 +379,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. @@ -409,18 +408,13 @@ 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 @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 +449,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 +481,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 +513,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 +545,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 +577,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 +609,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 +641,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 +673,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 +705,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 +737,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 +769,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 +801,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 +833,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 +865,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 +897,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 +929,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 +961,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 +993,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 +1025,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 +1057,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 +1089,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 +1121,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 +1153,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 +1185,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 +1217,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 +1249,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 +1281,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 +1313,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 +1345,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 +1377,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 +1409,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. @@ -1444,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 8e2d5e2..7dc79cd 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 @@ -33,14 +32,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 +58,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 +84,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 +110,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 +136,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 +162,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 +188,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 +214,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 +240,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" @@ -261,22 +260,20 @@ 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 @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 +292,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 +318,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 +344,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 +370,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 +396,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 +422,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 +448,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 +474,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 +500,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 +530,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 +556,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 +594,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 +632,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 +670,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 +708,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 +746,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 +784,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 +822,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 +860,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 +900,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 +940,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 +980,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 +1020,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 +1061,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 +1102,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 +1143,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 +1183,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 +1223,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 +1263,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 +1305,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 +1343,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 +1383,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 +1423,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,25 +1463,25 @@ 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 = [ - 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_qft_circuit.py b/tests/circuit/test_qft_circuit.py index c3a77f8..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 @@ -119,7 +118,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 +127,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 +166,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 +175,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 +214,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 +223,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 +262,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 +271,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 +292,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 +309,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_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 e940949..7a82e56 100644 --- a/tests/circuit/test_uniformly_controlled_gates.py +++ b/tests/circuit/test_uniformly_controlled_gates.py @@ -20,10 +20,10 @@ from numpy.testing import assert_almost_equal from numpy.typing import NDArray import pytest -from typing import Type +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 ( @@ -39,22 +39,6 @@ UCRZ_unitary_matrix_3qubits_10control, UCRZ_unitary_matrix_4qubits_023control, UCRZ_unitary_matrix_4qubits_213control, - UC_unitary_matrix_no_diagonal_no_simplification_3qubits_01control_HXHX, - UC_unitary_matrix_no_diagonal_no_simplification_3qubits_10control_HYHY, - UC_unitary_matrix_no_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY, - UC_unitary_matrix_no_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY, - UC_unitary_matrix_diagonal_no_simplification_3qubits_01control_HXHX, - UC_unitary_matrix_diagonal_no_simplification_3qubits_10control_HYHY, - UC_unitary_matrix_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY, - UC_unitary_matrix_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY, - UC_unitary_matrix_no_diagonal_simplification_3qubits_01control_HXHX, - UC_unitary_matrix_no_diagonal_simplification_3qubits_10control_HYHY, - UC_unitary_matrix_no_diagonal_simplification_4qubits_023control_RXRYRXRYRXRY, - UC_unitary_matrix_no_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY, - UC_unitary_matrix_diagonal_simplification_3qubits_01control_HXHX, - UC_unitary_matrix_diagonal_simplification_3qubits_10control_HYHY, - UC_unitary_matrix_diagonal_simplification_4qubits_023control_RXRYRXRYRXRY, - UC_unitary_matrix_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY ) @@ -98,7 +82,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 +141,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 +200,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 +243,7 @@ def test_UCRZ( ]) def test_Diagonal( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], diagonal: list[int], qubit_indices: list[int] ) -> None: @@ -284,59 +268,68 @@ def test_Diagonal( assert_almost_equal(circuit.get_unitary(), np.diag(diagonal).astype(complex), decimal=8) @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) - @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ + @pytest.mark.parametrize("up_to_diagonal", [True, False]) + @pytest.mark.parametrize("multiplexor_simplification", [True, False]) + @pytest.mark.parametrize("single_qubit_gates", [ [ - [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], - [0, 1], - 2, - UC_unitary_matrix_no_diagonal_no_simplification_3qubits_01control_HXHX + Hadamard.data, + PauliX.data, + Hadamard.data, + PauliX.data ], [ - [Hadamard().matrix, PauliY().matrix, Hadamard().matrix, PauliY().matrix], - [1, 0], - 2, - UC_unitary_matrix_no_diagonal_no_simplification_3qubits_10control_HYHY + 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/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_no_simplification_4qubits_023control_RXRYRXRYRXRY + 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 ], - [ - [ - 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_no_simplification_4qubits_213control_RXRYRXRYRXRY - ] ]) - def test_Multiplexor_no_diagonal_no_simplification( + def test_Multiplexor( self, - circuit_framework: Type[Circuit], + circuit_framework: type[Circuit], single_qubit_gates: list[NDArray[np.complex128]], - control_indices: list[int], - target_index: int, - expected: NDArray[np.complex128] + up_to_diagonal: bool, + multiplexor_simplification: bool ) -> None: - """ Test the `Multiplexor` gate without diagonal and without simplification. + """ Test the `Multiplexor` gate. Parameters ---------- @@ -344,276 +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.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 - ] - ]) - 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 + num_controls = int( + np.log2( + len(single_qubit_gates) + ) ) - # Ensure the unitary matrix is correct - assert_almost_equal(circuit.get_unitary(), expected, 8) + qubits = list(range(num_controls + 1)) + target_index = qubits[0] + control_indices = qubits[1:] - @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 - ] - ]) - 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. - - 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 - ] - ]) - 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. + 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() - 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) + 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( 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..3a66e15 100644 --- a/tests/compiler/test_shende_compiler.py +++ b/tests/compiler/test_shende_compiler.py @@ -22,47 +22,40 @@ import pytest from quick.circuit import TKETCircuit -from quick.compiler import Compiler -from quick.primitives import Bra, Ket, Operator +from quick.compiler import ShendeCompiler +from quick.primitives import Statevector, 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) -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) -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 - - def test_init_invalid_optimizer(self) -> None: - with pytest.raises(TypeError): - Compiler(circuit_framework=TKETCircuit, optimizer=0) # type: ignore + ShendeCompiler(circuit_framework=TKETCircuit, unitary_prep=int) # 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) @@ -71,11 +64,11 @@ 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 - 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,24 +79,19 @@ 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) - 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) 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,66 +111,50 @@ 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)) - 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)) 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): - 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 - 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([ - (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 = Compiler(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 = Compiler(circuit_framework=TKETCircuit) + 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 - 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 +167,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) @@ -204,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) @@ -217,7 +189,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): @@ -229,35 +201,22 @@ 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 = Compiler(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 = Compiler(circuit_framework=TKETCircuit) + 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 - 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 +229,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) @@ -279,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) @@ -292,14 +251,12 @@ 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) - 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) @@ -308,19 +265,17 @@ 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]) ]) - # TODO: Add a check for the optimized circuit - # Get the state of the circuit 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/metrics/area_law_state.npy b/tests/metrics/area_law_state.npy new file mode 100644 index 0000000..c227d45 Binary files /dev/null and b/tests/metrics/area_law_state.npy differ diff --git a/tests/metrics/test_metrics.py b/tests/metrics/test_metrics.py index 5d2de8c..8ac1ec2 100644 --- a/tests/metrics/test_metrics.py +++ b/tests/metrics/test_metrics.py @@ -20,12 +20,16 @@ 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_entanglement_entropy_slope, + calculate_hilbert_schmidt_test, + calculate_frobenius_distance ) @@ -47,6 +51,15 @@ def test_calculate_entanglement_range(self) -> 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. """ @@ -54,23 +67,123 @@ 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.5], + [0.5], + [0.5], + [0.5] + ]), + 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), + np.array([ + [1], + [2], + [3] + ], 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_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_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. + """ + 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 + + def test_calculate_frobenius_distance(self) -> None: + """ Test the `calculate_frobenius_distance` method. """ - assert_almost_equal(0.0, calculate_entanglement_entropy(data)) \ No newline at end of file + 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/metrics/volume_law_state.npy b/tests/metrics/volume_law_state.npy new file mode 100644 index 0000000..28fe0fe Binary files /dev/null and b/tests/metrics/volume_law_state.npy differ 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 8e9be1f..927debe 100644 --- a/tests/predicates/test_predicates.py +++ b/tests/predicates/test_predicates.py @@ -14,312 +14,657 @@ from __future__ import annotations -__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"] 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, is_unitary_matrix, is_hermitian_matrix, is_positive_semidefinite_matrix, - is_isometry + is_isometry, + is_density_matrix, + is_product_matrix, + is_locally_equivalent, + is_supercontrolled ) -@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()` method. - - Parameters - ---------- - `array` : NDArray[np.complex128] - The input array to check if it is a square matrix. - `expected` : bool - The expected output of the function. +class TestPredicates: + """ `tests.predicates.TestPredicates` is the tester class for `quick.predicates` + module. """ - assert is_square_matrix(array) == 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()` method 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) == 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()` method 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) == 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()` method 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) == 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()` method. - - 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) == 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()` method 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) == 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()` method 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) == 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) -]) -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) == expected \ No newline at end of file + 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(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 + + @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. + `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/primitives/__init__.py b/tests/primitives/__init__.py index 78b2e5e..f500ef0 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_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 diff --git a/tests/primitives/test_bra.py b/tests/primitives/test_bra.py deleted file mode 100644 index 817bbbd..0000000 --- a/tests/primitives/test_bra.py +++ /dev/null @@ -1,247 +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])) - 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 deleted file mode 100644 index f52ba47..0000000 --- a/tests/primitives/test_ket.py +++ /dev/null @@ -1,280 +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])) - print(ket1 + ket2) - 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..b259a2e 100644 --- a/tests/primitives/test_operator.py +++ b/tests/primitives/test_operator.py @@ -10,4 +10,259 @@ # 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_almost_equal +import pytest +from scipy.stats import unitary_group + +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_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" + + 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. + """ + 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. + """ + 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_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_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_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. + """ + 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. + """ + 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_almost_equal(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_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. + """ + 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_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. + """ + 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..b13d2f0 --- /dev/null +++ b/tests/primitives/test_statevector.py @@ -0,0 +1,303 @@ +# 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 scipy.stats import unitary_group + +from quick.predicates import is_statevector +from quick.primitives import Statevector, Operator + + +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])) + 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. + """ + 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_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_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. + """ + 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_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_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. + """ + 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_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. + """ + 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/random/test_random.py b/tests/random/test_random.py index 1b8bc2b..93d70ee 100644 --- a/tests/random/test_random.py +++ b/tests/random/test_random.py @@ -18,8 +18,22 @@ 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, + 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_orthogonal_matrix, + generate_random_special_orthogonal_matrix, + generate_random_special_unitary_matrix +) class TestRandom: @@ -31,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 ---------- @@ -40,15 +54,14 @@ 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( self, num_qubits: int ) -> None: - """ Test the `generate_random_unitary` function. + """ Test the `generate_random_unitary()` function. Parameters ---------- @@ -58,4 +71,93 @@ 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, None]) + 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 + ) + + @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_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. """ 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)) 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 diff --git a/tests/synthesis/unitarypreparation/test_diffusion.py b/tests/synthesis/unitarypreparation/test_diffusion.py index 2465bca..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 @@ -29,7 +28,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.