diff --git a/.gitignore b/.gitignore index 0752b089..a76cfa98 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,4 @@ dmypy.json .vscode .DS_Store + diff --git a/notebooks/2. Circuits of Phase Gadgets.ipynb b/notebooks/2. Circuits of Phase Gadgets.ipynb index 8754c565..ed9934f4 100644 --- a/notebooks/2. Circuits of Phase Gadgets.ipynb +++ b/notebooks/2. Circuits of Phase Gadgets.ipynb @@ -31,7 +31,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Current working directory: C:\\Users\\Stefa\\Documents\\git\\pauliopt\n" + "Current working directory: /Users/davidwinderl/Documents/Workspaces/Workspace/pauliopt\n" ] } ], @@ -70,7 +70,7 @@ "name": "stderr", "output_type": "stream", "text": [ - ":4: DeprecationWarning: `set_matplotlib_formats` is deprecated since IPython 7.23, directly use `matplotlib_inline.backend_inline.set_matplotlib_formats()`\n", + "/var/folders/gx/p4btzntx3q93kpwt35k29yvr0000gn/T/ipykernel_5769/4084885951.py:4: DeprecationWarning: `set_matplotlib_formats` is deprecated since IPython 7.23, directly use `matplotlib_inline.backend_inline.set_matplotlib_formats()`\n", " set_matplotlib_formats('svg')\n" ] } @@ -210,7 +210,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "hash(gadget) = 624374175457367365\n", + "hash(gadget) = 1441341330045463407\n", "(gadget == same_gadget) = True\n", "(gadget == other_gadget) = False\n" ] @@ -291,9 +291,73 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 12, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "0\n", + "0\n", + "\n", + "1\n", + "1\n", + "\n", + "2\n", + "2\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "π/2\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "π\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "7π/4\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "π/4\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "π/2\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "from pauliopt.phase import PhaseCircuit\n", "gadgets = [\n", @@ -303,7 +367,8 @@ " Z(pi/4) @ {0, 2},\n", " X(pi/2) @ {0, 1},\n", "]\n", - "phase_circuit = PhaseCircuit(3, gadgets)" + "phase_circuit = PhaseCircuit(3, gadgets)\n", + "display(phase_circuit)" ] }, { @@ -1582,7 +1647,7 @@ "metadata": { "celltoolbar": "Slideshow", "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -1596,7 +1661,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.7" + "version": "3.9.16" } }, "nbformat": 4, diff --git a/notebooks/4. Phase Circuit Optimization.ipynb b/notebooks/4. Phase Circuit Optimization.ipynb index ab0dde71..6f8f4e56 100644 --- a/notebooks/4. Phase Circuit Optimization.ipynb +++ b/notebooks/4. Phase Circuit Optimization.ipynb @@ -13401,7 +13401,7 @@ "metadata": { "celltoolbar": "Slideshow", "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -13415,7 +13415,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.7" + "version": "3.9.16" } }, "nbformat": 4, diff --git a/notebooks/6. Pauli Polynomials.ipynb b/notebooks/6. Pauli Polynomials.ipynb new file mode 100644 index 00000000..e5edd004 --- /dev/null +++ b/notebooks/6. Pauli Polynomials.ipynb @@ -0,0 +1,505 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "4abbaa6c", + "metadata": {}, + "outputs": [], + "source": [ + "from pauliopt.pauli.pauli_polynomial import PauliPolynomial\n", + "from pauliopt.pauli.pauli_gadget import PPhase\n", + "from pauliopt.pauli.utils import I, Z, X, Y\n", + "from pauliopt.utils import Angle, pi\n", + "from pauliopt.topologies import Topology" + ] + }, + { + "cell_type": "markdown", + "id": "173ba092", + "metadata": {}, + "source": [ + "# Pauli Polynomials\n", + "\n", + "## Pauli Gadgets\n", + "\n", + "Pauli Gadgets are mathematical expressions defined as:\n", + "\n", + "$$\n", + "P = \\exp(-i \\frac{\\alpha}{2} \\bigotimes_i P_i)\n", + "$$\n", + "\n", + "Here, \\(P_i\\) represents one of the Pauli matrices:\n", + "\n", + "- Pauli-X:\n", + "$$\n", + "X = \\begin{bmatrix} 0 & 1 \\\\ 1 & 0 \\end{bmatrix}\n", + "$$\n", + "- Pauli-Y:\n", + "$$\n", + "Y = \\begin{bmatrix} 0 & -i \\\\ i & 0 \\end{bmatrix}\n", + "$$\n", + "- Pauli-Z:\n", + "$$\n", + "Z = \\begin{bmatrix} 1 & 0 \\\\ 0 & -1 \\end{bmatrix}\n", + "$$\n", + "- Identity:\n", + "$$\n", + "I = \\begin{bmatrix} 1 & 0 \\\\ 0 & 1 \\end{bmatrix}\n", + "$$\n", + "\n", + "A formal definition and further information on Pauli gadgets can be found [here](https://arxiv.org/abs/1906.01734).\n", + "\n", + "Within `pauliopt`, Pauli gadgets are constructed in a similar way to Phase gadgets, following these steps:\n", + "- An angle is required, which can be an instance of `pauliopt.utils.Angle` or any object that satisfies the `pauliopt.utils.AngleProtocol` protocol.\n", + "- A list of legs is defined, where each leg corresponds to one of the Pauli matrices (X, Y, Z, I).\n", + "- The qubits spanned by the Pauli gadget are determined by the length of the list. If there are no qubits to be acted on, the Identity matrix is used.\n", + "\n", + "Example:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a22d780c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(0.5) @ { I, Z, X, Y }" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pauli_gadget = PPhase(0.5) @ [I, Z, X, Y] # TODO Angle(pi)\n", + "pauli_gadget" + ] + }, + { + "cell_type": "markdown", + "id": "641c0890", + "metadata": {}, + "source": [ + "We can define a Pauli gadget on a quantum circuit by following these steps:\n", + "\n", + "1. *Apply a set of Clifford gates:*\n", + " - If the Pauli gadget is X, place the H-Gate on the corresponding qubit.\n", + " - If the Pauli gadget is Y, apply the $\\sqrt{X}$ or V-Gate on the corresponding qubit.\n", + " - If the Pauli gadget is Z or the Identity, no additional gate is applied.\n", + "2. *Create a CNOT-Ladder:*\n", + " - Implement a sequence of CNOT gates between the target qubit and the control qubits.\n", + "3. *Perform an Rz(alpha) rotation:*\n", + " - Apply the Rz(alpha) gate on the target qubit.\n", + "4. *Undo the process:*\n", + " - Reverse the CNOT-Ladder by applying the CNOT gates in the opposite order.\n", + " - Reapply the Clifford gates in the reverse order to undo their effects.\n", + "\n", + "Here's an example of a circuit showcasing these steps:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "71d6fc47", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " \n", + "q_0: ──────────────────────────────────────────────────────\n", + " ┌───┐┌─────────┐┌───┐ \n", + "q_1: ────────────────┤ X ├┤ Rz(0.5) ├┤ X ├─────────────────\n", + " ┌───┐ ┌───┐└─┬─┘└─────────┘└─┬─┘┌───┐ ┌───┐ \n", + "q_2: ───┤ H ├───┤ X ├──■───────────────■──┤ X ├───┤ H ├────\n", + " ┌──┴───┴──┐└─┬─┘ └─┬─┘┌──┴───┴───┐\n", + "q_3: ┤ Rx(π/2) ├──■─────────────────────────■──┤ Rx(-π/2) ├\n", + " └─────────┘ └──────────┘\n" + ] + } + ], + "source": [ + "print(pauli_gadget.to_qiskit(Topology.line(4)))" + ] + }, + { + "cell_type": "markdown", + "id": "49fe3a52", + "metadata": {}, + "source": [ + "## Pauli Polynomial\n", + "\n", + "To construct a Pauli polynomial, we can chain together multiple Pauli gadgets. Each Pauli gadget acts on a specific set of qubits and contributes to the overall transformation of the system (you can view this as n-dimensional rotations acting sequentially). \n", + "\n", + "Here's an example illustrating the construction of a Pauli polynomial:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "1c72d4e0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(π) @ { I, I, X, Z, Y }\n", + "(π/2) @ { X, X, I, I, Y }\n", + "(π/256) @ { X, I, I, Z, Y }\n", + "(π/8) @ { X, X, X, Z, Y }\n", + "(π/4) @ { X, Z, I, I, Y }\n", + "(π/2) @ { X, I, I, Y, Y }\n" + ] + } + ], + "source": [ + "pp = PauliPolynomial(5)\n", + "\n", + "pp >>= PPhase(Angle(pi)) @ [I, I, X, Z, Y]\n", + "pp >>= PPhase(Angle(pi/2)) @ [X, X, I, I, Y]\n", + "pp >>= PPhase(Angle(pi/256)) @ [X, I, I, Z, Y]\n", + "pp >>= PPhase(Angle(pi/8)) @ [X, X, X, Z, Y]\n", + "pp >>= PPhase(Angle(pi/4)) @ [X, Z, I, I, Y]\n", + "pp >>= PPhase(Angle(pi/2)) @ [X, I, I, Y, Y]\n", + "\n", + "# Representation in CLI applications\n", + "print(pp)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "f2007e30", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "π\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "π/2\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "π/256\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "π/8\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "π/4\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "π/2\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "(π) @ { I, I, X, Z, Y }\n", + "(π/2) @ { X, X, I, I, Y }\n", + "(π/256) @ { X, I, I, Z, Y }\n", + "(π/8) @ { X, X, X, Z, Y }\n", + "(π/4) @ { X, Z, I, I, Y }\n", + "(π/2) @ { X, I, I, Y, Y }" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# SVG representation in e.g. Jupyter notebooks\n", + "pp" + ] + }, + { + "cell_type": "markdown", + "id": "b74b67a2", + "metadata": {}, + "source": [ + "An example circuit of the Pauli Polynomial above, can be generated as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "08d1f442", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " ┌───┐ ┌───┐┌───┐»\n", + "q_0: ───┤ H ├───────────────────────────────────────────────────────┤ X ├┤ X ├»\n", + " ├───┤ └─┬─┘└─┬─┘»\n", + "q_1: ───┤ H ├─────────────────────────────────────────────────────────┼────■──»\n", + " ├───┤ ┌───┐┌───┐┌───────┐┌───┐┌───┐ ┌───┐ ┌───┐ │ »\n", + "q_2: ───┤ H ├───┤ X ├┤ X ├┤ Rz(π) ├┤ X ├┤ X ├───┤ H ├───────┤ H ├─────┼───────»\n", + " └───┘ └─┬─┘└─┬─┘└───────┘└─┬─┘└─┬─┘ └───┘ └───┘ │ »\n", + "q_3: ─────────────┼────■─────────────■────┼───────────────────────────┼───────»\n", + " ┌─────────┐ │ │ ┌──────────┐┌─────────┐ │ »\n", + "q_4: ┤ Rx(π/2) ├──■───────────────────────■──┤ Rx(-π/2) ├┤ Rx(π/2) ├──■───────»\n", + " └─────────┘ └──────────┘└─────────┘ »\n", + "« ┌─────────┐┌───┐┌───┐ ┌───┐ ┌───┐ ┌───┐┌───┐┌───────────┐┌───┐»\n", + "«q_0: ┤ Rz(π/2) ├┤ X ├┤ X ├───┤ H ├───────┤ H ├───┤ X ├┤ X ├┤ Rz(π/256) ├┤ X ├»\n", + "« └─────────┘└─┬─┘└─┬─┘ ├───┤ ├───┤ └─┬─┘└─┬─┘└───────────┘└─┬─┘»\n", + "«q_1: ─────────────■────┼─────┤ H ├───────┤ H ├─────┼────┼─────────────────┼──»\n", + "« │ └───┘ └───┘ │ │ │ »\n", + "«q_2: ──────────────────┼───────────────────────────┼────┼─────────────────┼──»\n", + "« │ │ │ │ »\n", + "«q_3: ──────────────────┼───────────────────────────┼────■─────────────────■──»\n", + "« │ ┌──────────┐┌─────────┐ │ »\n", + "«q_4: ──────────────────■──┤ Rx(-π/2) ├┤ Rx(π/2) ├──■─────────────────────────»\n", + "« └──────────┘└─────────┘ »\n", + "« ┌───┐ ┌───┐ ┌───┐ ┌───┐┌───┐┌───┐┌───┐┌─────────┐┌───┐┌───┐»\n", + "«q_0: ┤ X ├───┤ H ├───────┤ H ├───┤ X ├┤ X ├┤ X ├┤ X ├┤ Rz(π/8) ├┤ X ├┤ X ├»\n", + "« └─┬─┘ └───┘ └───┘ └─┬─┘└─┬─┘└─┬─┘└─┬─┘└─────────┘└─┬─┘└─┬─┘»\n", + "«q_1: ──┼───────────────────────────┼────┼────┼────■───────────────■────┼──»\n", + "« │ │ │ │ │ »\n", + "«q_2: ──┼───────────────────────────┼────┼────■─────────────────────────■──»\n", + "« │ │ │ »\n", + "«q_3: ──┼───────────────────────────┼────■─────────────────────────────────»\n", + "« │ ┌──────────┐┌─────────┐ │ »\n", + "«q_4: ──■──┤ Rx(-π/2) ├┤ Rx(π/2) ├──■──────────────────────────────────────»\n", + "« └──────────┘└─────────┘ »\n", + "« ┌───┐┌───┐ ┌───┐ ┌───┐ ┌───┐┌───┐┌─────────┐┌───┐┌───┐»\n", + "«q_0: ─────┤ X ├┤ X ├───┤ H ├───────┤ H ├───┤ X ├┤ X ├┤ Rz(π/4) ├┤ X ├┤ X ├»\n", + "« ┌───┐└─┬─┘└─┬─┘ └───┘ └───┘ └─┬─┘└─┬─┘└─────────┘└─┬─┘└─┬─┘»\n", + "«q_1: ┤ H ├──┼────┼───────────────────────────┼────■───────────────■────┼──»\n", + "« ├───┤ │ │ │ │ »\n", + "«q_2: ┤ H ├──┼────┼───────────────────────────┼─────────────────────────┼──»\n", + "« └───┘ │ │ ┌─────────┐ │ │ »\n", + "«q_3: ───────■────┼──┤ Rx(π/2) ├──────────────┼─────────────────────────┼──»\n", + "« │ ├─────────┴┐┌─────────┐ │ │ »\n", + "«q_4: ────────────■──┤ Rx(-π/2) ├┤ Rx(π/2) ├──■─────────────────────────■──»\n", + "« └──────────┘└─────────┘ »\n", + "« ┌───┐ ┌───┐ ┌───┐┌───┐┌─────────┐┌───┐┌───┐ ┌───┐ \n", + "«q_0: ───┤ H ├───────┤ H ├───┤ X ├┤ X ├┤ Rz(π/2) ├┤ X ├┤ X ├───┤ H ├────\n", + "« └───┘ └───┘ └─┬─┘└─┬─┘└─────────┘└─┬─┘└─┬─┘ └───┘ \n", + "«q_1: ─────────────────────────┼────┼───────────────┼────┼──────────────\n", + "« │ │ │ │ \n", + "«q_2: ─────────────────────────┼────┼───────────────┼────┼──────────────\n", + "« │ │ │ │ ┌──────────┐\n", + "«q_3: ─────────────────────────┼────■───────────────■────┼──┤ Rx(-π/2) ├\n", + "« ┌──────────┐┌─────────┐ │ │ ├──────────┤\n", + "«q_4: ┤ Rx(-π/2) ├┤ Rx(π/2) ├──■─────────────────────────■──┤ Rx(-π/2) ├\n", + "« └──────────┘└─────────┘ └──────────┘\n" + ] + } + ], + "source": [ + "print(pp.to_qiskit())" + ] + }, + { + "cell_type": "markdown", + "id": "dc2407ad", + "metadata": {}, + "source": [ + "It is also possible to route such a polynomial on a certain type of architecture" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "774fb7c8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " ┌───┐ »\n", + "q_0: ───┤ H ├─────────────────────────────────────────────────────────────────»\n", + " ├───┤ »\n", + "q_1: ───┤ H ├─────────────────────────────────────────────────────────────────»\n", + " ├───┤ ┌───┐┌───────┐┌───┐┌───┐ »\n", + "q_2: ───┤ H ├────────┤ X ├┤ Rz(π) ├┤ X ├┤ H ├─────────────────────────────────»\n", + " └───┘ ┌───┐└─┬─┘└───────┘└─┬─┘├───┤ ┌───┐»\n", + "q_3: ───────────┤ X ├──■─────────────■──┤ X ├─────────────────────────■──┤ X ├»\n", + " ┌─────────┐└─┬─┘ └─┬─┘┌──────────┐┌─────────┐┌─┴─┐└─┬─┘»\n", + "q_4: ┤ Rx(π/2) ├──■───────────────────────■──┤ Rx(-π/2) ├┤ Rx(π/2) ├┤ X ├──■──»\n", + " └─────────┘ └──────────┘└─────────┘└───┘ »\n", + "« ┌───┐┌─────────┐┌───┐┌───┐┌───┐ »\n", + "«q_0: ───────────────┤ X ├┤ Rz(π/2) ├┤ X ├┤ H ├┤ H ├───────────────────────────»\n", + "« ┌───┐└─┬─┘└─────────┘└─┬─┘├───┤├───┤ »\n", + "«q_1: ──────────┤ X ├──■───────────────■──┤ X ├┤ H ├───────────────────────────»\n", + "« ┌───┐└─┬─┘ └─┬─┘├───┤ »\n", + "«q_2: ──■──┤ X ├──■─────────────────────────■──┤ X ├──■────────────────────────»\n", + "« ┌─┴─┐└─┬─┘ └─┬─┘┌─┴─┐┌───┐ »\n", + "«q_3: ┤ X ├──■───────────────────────────────────■──┤ X ├┤ X ├──■──────────────»\n", + "« └───┘ └───┘└─┬─┘┌─┴─┐┌──────────┐»\n", + "«q_4: ─────────────────────────────────────────────────────■──┤ X ├┤ Rx(-π/2) ├»\n", + "« └───┘└──────────┘»\n", + "« ┌───┐┌───────────┐┌───┐┌───┐┌───┐»\n", + "«q_0: ────────────────────────────────────┤ X ├┤ Rz(π/256) ├┤ X ├┤ H ├┤ H ├»\n", + "« ┌───┐└─┬─┘└───────────┘└─┬─┘├───┤└───┘»\n", + "«q_1: ────────────────────────────■──┤ X ├──■─────────────────■──┤ X ├──■──»\n", + "« ┌───┐┌─┴─┐└─┬─┘ └─┬─┘┌─┴─┐»\n", + "«q_2: ──────────────────■──┤ X ├┤ X ├──■───────────────────────────■──┤ X ├»\n", + "« ┌───┐┌─┴─┐└─┬─┘└───┘ └───┘»\n", + "«q_3: ───────────┤ X ├┤ X ├──■─────────────────────────────────────────────»\n", + "« ┌─────────┐└─┬─┘└───┘ »\n", + "«q_4: ┤ Rx(π/2) ├──■───────────────────────────────────────────────────────»\n", + "« └─────────┘ »\n", + "« ┌───┐┌─────────┐»\n", + "«q_0: ─────────────────────────────────────────────────────┤ X ├┤ Rz(π/8) ├»\n", + "« ┌───┐ ┌───┐└─┬─┘└─────────┘»\n", + "«q_1: ┤ H ├───────────────────────────────────────────┤ X ├──■─────────────»\n", + "« ├───┤ ┌───┐ ┌───┐└─┬─┘ »\n", + "«q_2: ┤ X ├──■──┤ H ├────────────────────────────┤ X ├──■──────────────────»\n", + "« └─┬─┘┌─┴─┐├───┤ ┌───┐└─┬─┘ »\n", + "«q_3: ──■──┤ X ├┤ X ├───────────────────────┤ X ├──■───────────────────────»\n", + "« └───┘└─┬─┘┌──────────┐┌─────────┐└─┬─┘ »\n", + "«q_4: ────────────■──┤ Rx(-π/2) ├┤ Rx(π/2) ├──■────────────────────────────»\n", + "« └──────────┘└─────────┘ »\n", + "« ┌───┐┌───┐┌───┐ ┌───┐»\n", + "«q_0: ┤ X ├┤ H ├┤ H ├─────────────────────────────────────────────────────┤ X ├»\n", + "« └─┬─┘├───┤├───┤ ┌───┐└─┬─┘»\n", + "«q_1: ──■──┤ X ├┤ H ├────────────────────────────────────────────────┤ X ├──■──»\n", + "« └─┬─┘├───┤┌───┐ ┌───┐└─┬─┘ »\n", + "«q_2: ───────■──┤ X ├┤ H ├───────────────────────────────────■──┤ X ├──■───────»\n", + "« └─┬─┘├───┤ ┌───┐┌─┴─┐└─┬─┘ »\n", + "«q_3: ────────────■──┤ X ├─────────────────────────■──┤ X ├┤ X ├──■────────────»\n", + "« └─┬─┘┌──────────┐┌─────────┐┌─┴─┐└─┬─┘└───┘ »\n", + "«q_4: ─────────────────■──┤ Rx(-π/2) ├┤ Rx(π/2) ├┤ X ├──■──────────────────────»\n", + "« └──────────┘└─────────┘└───┘ »\n", + "« ┌─────────┐┌───┐┌───┐┌───┐ »\n", + "«q_0: ┤ Rz(π/4) ├┤ X ├┤ H ├┤ H ├───────────────────────────────────────────»\n", + "« └─────────┘└─┬─┘├───┤└───┘ »\n", + "«q_1: ─────────────■──┤ X ├────────────────────────────────────────────────»\n", + "« └─┬─┘┌───┐ »\n", + "«q_2: ──────────────────■──┤ X ├──■────────────────────────────────────────»\n", + "« └─┬─┘┌─┴─┐┌───┐ ┌─────────┐ ┌───┐»\n", + "«q_3: ───────────────────────■──┤ X ├┤ X ├──■──┤ Rx(π/2) ├────────────┤ X ├»\n", + "« └───┘└─┬─┘┌─┴─┐├─────────┴┐┌─────────┐└─┬─┘»\n", + "«q_4: ─────────────────────────────────■──┤ X ├┤ Rx(-π/2) ├┤ Rx(π/2) ├──■──»\n", + "« └───┘└──────────┘└─────────┘ »\n", + "« ┌───┐┌─────────┐┌───┐┌───┐ »\n", + "«q_0: ────────────────────┤ X ├┤ Rz(π/2) ├┤ X ├┤ H ├────────────────────»\n", + "« ┌───┐└─┬─┘└─────────┘└─┬─┘├───┤ »\n", + "«q_1: ────────────■──┤ X ├──■───────────────■──┤ X ├──■─────────────────»\n", + "« ┌───┐┌─┴─┐└─┬─┘ └─┬─┘┌─┴─┐┌───┐ »\n", + "«q_2: ──■──┤ X ├┤ X ├──■─────────────────────────■──┤ X ├┤ X ├──■───────»\n", + "« ┌─┴─┐└─┬─┘└───┘ └───┘└─┬─┘┌─┴─┐┌───┐»\n", + "«q_3: ┤ X ├──■─────────────────────────────────────────────■──┤ X ├┤ X ├»\n", + "« └───┘ └───┘└─┬─┘»\n", + "«q_4: ───────────────────────────────────────────────────────────────■──»\n", + "« »\n", + "« \n", + "«q_0: ────────────\n", + "« \n", + "«q_1: ────────────\n", + "« \n", + "«q_2: ────────────\n", + "« ┌──────────┐\n", + "«q_3: ┤ Rx(-π/2) ├\n", + "« ├──────────┤\n", + "«q_4: ┤ Rx(-π/2) ├\n", + "« └──────────┘\n" + ] + } + ], + "source": [ + "print(pp.to_qiskit(Topology.line(pp.num_qubits)))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a674d6c8", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pauliopt/pauli/pauli_gadget.py b/pauliopt/pauli/pauli_gadget.py index fa304361..bae6e93b 100644 --- a/pauliopt/pauli/pauli_gadget.py +++ b/pauliopt/pauli/pauli_gadget.py @@ -106,7 +106,7 @@ def to_qiskit(self, topology=None): column = np.asarray(self.paulis) column_binary = np.where(column == I, 0, 1) if np.all(column_binary == 0): - circ.global_phase += self.angle + circ.global_phase += self.angle.to_qiskit return circ cnot_ladder, q0 = find_minimal_cx_assignment(column_binary, topology) @@ -126,13 +126,19 @@ def to_qiskit(self, topology=None): for (pauli_idx, target) in reversed(cnot_ladder): circ.cx(pauli_idx, target) - circ.rz(self.angle, q0) + if isinstance(self.angle, float): + circ.rz(self.angle, q0) + elif isinstance(self.angle, AngleExpr): + circ.rz(self.angle.to_qiskit, q0) for (pauli_idx, target) in cnot_ladder: circ.cx(pauli_idx, target) else: target = np.argmax(column_binary) - circ.rz(self.angle, target) + if isinstance(self.angle, float): + circ.rz(self.angle, target) + elif isinstance(self.angle, AngleExpr): + circ.rz(self.angle.to_qiskit, target) for pauli_idx in range(len(column)): if column[pauli_idx] == Pauli.I: diff --git a/pauliopt/pauli/pauli_polynomial.py b/pauliopt/pauli/pauli_polynomial.py index 37120094..d1ed96ac 100644 --- a/pauliopt/pauli/pauli_polynomial.py +++ b/pauliopt/pauli/pauli_polynomial.py @@ -2,6 +2,77 @@ from pauliopt.pauli.pauli_gadget import PauliGadget from pauliopt.topologies import Topology +import math +from pauliopt.pauli.utils import X, Y, Z, I +from pauliopt.utils import SVGBuilder + +LATEX_HEADER = """\documentclass[preview]{standalone} + +\\usepackage{tikz} +\\usetikzlibrary{zx-calculus} +\\usetikzlibrary{quantikz} +\\usepackage{graphicx} + +\\tikzset{ +diagonal fill/.style 2 args={fill=#2, path picture={ +\\fill[#1, sharp corners] (path picture bounding box.south west) -| + (path picture bounding box.north east) -- cycle;}}, +reversed diagonal fill/.style 2 args={fill=#2, path picture={ +\\fill[#1, sharp corners] (path picture bounding box.north west) |- + (path picture bounding box.south east) -- cycle;}} +} + +\\tikzset{ +diagonal fill/.style 2 args={fill=#2, path picture={ +\\fill[#1, sharp corners] (path picture bounding box.south west) -| + (path picture bounding box.north east) -- cycle;}} +} + +\\tikzset{ +pauliY/.style={ +zxAllNodes, +zxSpiders, +inner sep=0mm, +minimum size=2mm, +shape=rectangle, +%fill=colorZxX +diagonal fill={colorZxX}{colorZxZ} +} +} + +\\tikzset{ +pauliX/.style={ +zxAllNodes, +zxSpiders, +inner sep=0mm, +minimum size=2mm, +shape=rectangle, +fill=colorZxX +} +} + +\\tikzset{ +pauliZ/.style={ +zxAllNodes, +zxSpiders, +inner sep=0mm, +minimum size=2mm, +shape=rectangle, +fill=colorZxZ +} +} + +\\tikzset{ +pauliPhase/.style={ +zxAllNodes, +zxSpiders, +inner sep=0.5mm, +minimum size=2mm, +shape=rectangle, +fill=white +} +} +""" class PauliPolynomial: @@ -11,7 +82,8 @@ def __init__(self, num_qubits): def __irshift__(self, gadget: PauliGadget): if not len(gadget) == self.num_qubits: - raise Exception(f"Pauli Polynomial has {self.num_qubits}, but Pauli gadget has: {len(gadget)}") + raise Exception( + f"Pauli Polynomial has {self.num_qubits}, but Pauli gadget has: {len(gadget)}") self.pauli_gadgets.append(gadget) return self @@ -26,6 +98,10 @@ def __repr__(self): def __len__(self): return len(self.pauli_gadgets) + @property + def num_gadgets(self): + return len(self.pauli_gadgets) + def to_qiskit(self, topology=None): num_qubits = self.num_qubits if topology is None: @@ -60,3 +136,147 @@ def two_qubit_count(self, topology, leg_cache=None): for gadget in self.pauli_gadgets: count += gadget.two_qubit_count(topology, leg_cache=leg_cache) return count + + def to_svg(self, hscale: float = 1.0, vscale: float = 1.0, scale: float = 1.0, + svg_code_only=False): + vscale *= scale + hscale *= scale + + x_color = "#CCFFCC" + z_color = "#FF8888" + y_color = "ycolor" + + num_qubits = self.num_qubits + num_gadgets = self.num_gadgets + + # general width and height of a square + square_width = int(math.ceil(20 * vscale)) + square_height = int(math.ceil(20 * vscale)) + + # width of the text of the phases # TODO round floats (!!) + text_width = int(math.ceil(50 * vscale)) + + bend_degree = int(math.ceil(10)) + + # margins between the angle and the legs + margin_angle_x = int(math.ceil(20 * hscale)) + margin_angle_y = int(math.ceil(20 * hscale)) + + # margins between each element + margin_x = int(math.ceil(10 * hscale)) + margin_y = int(math.ceil(10 * hscale)) + + font_size = int(10) + + width = num_gadgets * ( + square_width + margin_x + margin_angle_x + text_width) + margin_x + height = (num_qubits) * (square_height + margin_y) + ( + square_height + margin_y + margin_angle_y) + + builder = SVGBuilder(width, height) + builder = builder.add_diagonal_fill(x_color, z_color, y_color) + + prev_x = {qubit: 0 for qubit in range(num_qubits)} + + x = margin_x + + for gadget in self.pauli_gadgets: + paulis = gadget.paulis + y = margin_y + text_coords = (square_width + margin_x + margin_angle_x + x, y) + text_left_lower_corder = (text_coords[0], text_coords[1] + square_height) + for qubit in range(num_qubits): + if qubit == 0: + y += square_height + margin_y + margin_angle_y + else: + y += square_height + margin_y + center_coords = (x + square_width, y) + if paulis[qubit] == I: + continue + + builder.line((prev_x[qubit], y + square_height // 2), + (x, y + square_height // 2)) + prev_x[qubit] = x + square_width + builder.line_bend(text_left_lower_corder, center_coords, + degree=qubit * bend_degree) + if paulis[qubit] == X: + builder.square((x, y), square_width, square_height, x_color) + elif paulis[qubit] == Y: + builder.square((x, y), square_width, square_height, y_color) + elif paulis[qubit] == Z: + builder.square((x, y), square_width, square_height, z_color) + + builder = builder.text_with_square(text_coords, text_width, square_height, + str(gadget.angle)) + x += square_width + margin_x + text_width + margin_angle_x + y = margin_y + for qubit in range(num_qubits): + if qubit == 0: + y += square_height + margin_y + margin_angle_y + else: + y += square_height + margin_y + builder.line((prev_x[qubit], y + square_height // 2), + (width, y + square_height // 2)) + svg_code = repr(builder) + + if svg_code_only: + return svg_code + try: + # pylint: disable = import-outside-toplevel + from IPython.core.display import SVG # type: ignore + except ModuleNotFoundError as e: + raise ModuleNotFoundError("You must install the 'IPython' library.") from e + + return SVG(svg_code) + + def _repr_svg_(self): + """ + Magic method for IPython/Jupyter pretty-printing. + See https://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html + """ + return self.to_svg(svg_code_only=True) + + def to_latex(self, file_name=None): + out_str = LATEX_HEADER + out_str += "\\begin{document}\n" + out_str += "\\begin{ZX}\n" + + angle_line = "\zxNone{} \t\t&" + + angle_pad_max = max( + [len(str(gadget.angle.repr_latex)) for gadget in self.pauli_gadgets]) + lines = {q: "\\zxNone{} \\rar \t&" for q in range(self.num_qubits)} + for gadget in self.pauli_gadgets: + assert isinstance(gadget, PauliGadget) + pad_ = ''.join([' ' for _ in range(self.num_qubits + 26)]) + pad_angle = "".join([' ' for _ in range(angle_pad_max - + len(str(gadget.angle.repr_latex)))]) + angle_line += f" \\zxNone{{}} {pad_}&" \ + f" |[pauliPhase]| {gadget.angle.repr_latex} {pad_angle}&" \ + f" \\zxNone{{}} &" + paulis = gadget.paulis + for q in range(self.num_qubits): + us = ''.join(['u' for _ in range(q)]) + + pad_angle = "".join([' ' for _ in range(angle_pad_max)]) + if paulis[q] != I: + pad_ = ''.join([' ' for _ in range(self.num_qubits - q)]) + lines[q] += f" |[pauli{paulis[q].value}]| " \ + f"\\ar[ruu{us}, bend right] \\rar {pad_}&" \ + f" \\zxNone{{}} \\rar {pad_angle} &" \ + f" \\zxNone{{}} \\rar &" + else: + pad_ = ''.join([' ' for _ in range(self.num_qubits + 22)]) + lines[q] += f" \\zxNone{{}} \\rar {pad_}& " \ + f"\\zxNone{{}} \\rar {pad_angle} & " \ + f"\\zxNone{{}} \\rar &" + out_str += angle_line + "\\\\ \n" + out_str += "\\\\ \n" + for q in range(self.num_qubits): + out_str += lines[q] + "\\\\ \n" + out_str += "\\end{ZX} \n" + out_str += "\\end{document}\n" + if file_name is not None: + with open(f"{file_name}.tex", "w") as f: + f.write(out_str) + return out_str diff --git a/pauliopt/utils.py b/pauliopt/utils.py index 568bc64e..77061074 100644 --- a/pauliopt/utils.py +++ b/pauliopt/utils.py @@ -7,12 +7,30 @@ from decimal import Decimal from fractions import Fraction from types import MappingProxyType -from typing import (Any, Callable, ClassVar, Dict, Final, List, Literal, Mapping, Optional, overload, +from typing import (Any, Callable, ClassVar, Dict, Final, List, Literal, Mapping, + Optional, overload, Protocol, runtime_checkable, Sequence, Tuple, Union) import numpy as np + +def calculate_orthogonal_point(a, b, d, left): + direction_vector = b - a + magnitude = np.linalg.norm(direction_vector) + normalized_direction_vector = direction_vector / magnitude + if left: + orthogonal_vector = np.array( + [-normalized_direction_vector[1], normalized_direction_vector[0]]) + else: + orthogonal_vector = np.array( + [normalized_direction_vector[1], -normalized_direction_vector[0]]) + midpoint = (a + b) / 2 + orthogonal_point = midpoint + d * orthogonal_vector + return int(orthogonal_point[0]), int(orthogonal_point[1]) + + AngleInitT = Union[int, Fraction, str, Decimal] + class AngleExpr(ABC): """ A container class for angle expressions. @@ -51,7 +69,7 @@ def __rmul__(self, other: int) -> "AngleExpr": def __truediv__(self, other: int) -> "AngleExpr": if isinstance(other, int): - return SumprodAngleExpr(self, coeffs=Fraction(1,other)) + return SumprodAngleExpr(self, coeffs=Fraction(1, other)) return NotImplemented @abstractmethod @@ -96,7 +114,7 @@ def _repr_latex_(self) -> str: Magic method for IPython/Jupyter pretty-printing. See https://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html """ - return "$%s$"%self.repr_latex + return "$%s$" % self.repr_latex @abstractmethod def __eq__(self, other: Any) -> bool: @@ -122,7 +140,7 @@ def value(self) -> Fraction: """ The value of this angle as a fraction of PI. """ - return self._value%2 + return self._value % 2 @property def as_root_of_unity(self) -> Tuple[int, int]: @@ -133,8 +151,8 @@ def as_root_of_unity(self) -> Tuple[int, int]: """ num = self.value.numerator den = self.value.denominator - a: int = num//2 if num%2 == 0 else num - order: int = den if num % 2 == 0 else 2*den + a: int = num // 2 if num % 2 == 0 else num + order: int = den if num % 2 == 0 else 2 * den return (a, order) @property @@ -144,7 +162,7 @@ def order(self) -> int: """ num = self.value.numerator den = self.value.denominator - return den if num % 2 == 0 else 2*den + return den if num % 2 == 0 else 2 * den @property def is_zero_or_pi(self) -> bool: @@ -162,7 +180,7 @@ def is_zero(self) -> bool: """ num = self.value.numerator den = self.value.denominator - return num % (2*den) == 0 + return num % (2 * den) == 0 @property def is_pi(self) -> bool: @@ -171,7 +189,7 @@ def is_pi(self) -> bool: """ num = self.value.numerator den = self.value.denominator - return num % den == 0 and not num % (2*den) == 0 + return num % den == 0 and not num % (2 * den) == 0 @property def to_qiskit(self) -> float: @@ -223,7 +241,7 @@ def __mod__(self, other: "AngleExpr") -> "AngleExpr": return super().__mod__(other) def _mul(self, other: Union[int, Fraction]) -> "Angle": - return Angle(self._value*other) + return Angle(self._value * other) def __mul__(self, other: int) -> "Angle": if isinstance(other, int): @@ -251,8 +269,8 @@ def __str__(self) -> str: if num == 1: if den == 1: return "π" - return "π/%d"%den - return "%dπ/%d"%(num, den) + return "π/%d" % den + return "%dπ/%d" % (num, den) def __repr__(self) -> str: num = self.value.numerator @@ -262,8 +280,8 @@ def __repr__(self) -> str: if num == 1: if den == 1: return "pi" - return "pi/%d"%den - return "%d*pi/%d"%(num, den) + return "pi/%d" % den + return "%d*pi/%d" % (num, den) @property def repr_latex(self) -> str: @@ -277,15 +295,15 @@ def repr_latex(self) -> str: if num == 1: if den == 1: return "\\pi" - return "\\frac{\\pi}{%d}"%den - return "\\frac{%d\\pi}{%d}"%(num, den) + return "\\frac{\\pi}{%d}" % den + return "\\frac{%d\\pi}{%d}" % (num, den) def _repr_latex_(self) -> str: """ Magic method for IPython/Jupyter pretty-printing. See https://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html """ - return "$%s$"%self.repr_latex + return "$%s$" % self.repr_latex def __eq__(self, other: Any) -> bool: if other == 0: @@ -295,12 +313,12 @@ def __eq__(self, other: Any) -> bool: return self.value == other.value def __float__(self) -> float: - return float(self.value)*math.pi + return float(self.value) * math.pi @overload @staticmethod def random(subdivision: int = 4, *, - size: Literal[1]=1, + size: Literal[1] = 1, rng_seed: Optional[int] = None, nonzero: bool = False) -> "Angle": ... @@ -335,35 +353,36 @@ def random(subdivision: int = 4, *, size: int = 1, raise TypeError("RNG seed must be integer or 'None'.") rng = np.random.default_rng(seed=rng_seed) if nonzero: - rs = 1+rng.integers(2*subdivision-1, size=size) # type: ignore[attr-defined] + rs = 1 + rng.integers(2 * subdivision - 1, + size=size) # type: ignore[attr-defined] else: - rs = rng.integers(2*subdivision, size=size) # type: ignore[attr-defined] + rs = rng.integers(2 * subdivision, size=size) # type: ignore[attr-defined] if size == 1: return Angle(Fraction(int(rs[0]), subdivision)) return tuple(Angle(Fraction(int(r), subdivision)) for r in rs) - zero: Final["Angle"] # type: ignore + zero: Final["Angle"] # type: ignore """ A constant for the angle 0. """ - pi: Final["Angle"] # type: ignore + pi: Final["Angle"] # type: ignore """ A constant for the angle pi. """ -# Set static constants for Angle: -Angle.zero = Angle(0) # type: ignore -Angle.pi = Angle(1) # type: ignore +# Set static constants for Angle: +Angle.zero = Angle(0) # type: ignore +Angle.pi = Angle(1) # type: ignore pi: Final[Angle] = Angle.pi """ Constant for `Angle.pi`. """ -π: Final[Angle] = Angle.pi # pylint: disable=non-ascii-name +π: Final[Angle] = Angle.pi # pylint: disable=non-ascii-name """ Constant for `Angle.pi`. """ def SumprodAngleExpr(*exprs: AngleExpr, coeffs: Union[int, Fraction, Sequence[Union[int, Fraction]]] = 1 - ) -> AngleExpr: + ) -> AngleExpr: if not isinstance(coeffs, Sequence): coeffs = (coeffs,) if len(coeffs) != len(exprs): @@ -373,10 +392,10 @@ def SumprodAngleExpr(*exprs: AngleExpr, _const: Angle = Angle.zero for e, c in zip(exprs, coeffs): if isinstance(e, Angle): - _const += e._mul(c) # pylint: disable = protected-access + _const += e._mul(c) # pylint: disable = protected-access elif isinstance(e, _SumprodAngleExpr): for sub_e, sub_c in e.coeffs.items(): - new_c = c*sub_c + new_c = c * sub_c if sub_e in _coeffs: new_c += _coeffs[sub_e] _coeffs[sub_e] = new_c @@ -426,7 +445,8 @@ def is_pi(self) -> bool: @property def to_qiskit(self) -> Any: - return sum((c*e.to_qiskit for e, c in self.coeffs.items()), self.const.to_qiskit) + return sum((c * e.to_qiskit for e, c in self.coeffs.items()), + self.const.to_qiskit) def __hash__(self) -> int: return hash((_SumprodAngleExpr, tuple(self.coeffs.items()), self.const)) @@ -437,12 +457,12 @@ def _str_repr(self, f: Callable[[Union[Angle, AngleExpr]], str]) -> str: pos_sub_e = {e: c for e, c in self.coeffs.items() if c > 0} neg_sub_e = {e: c for e, c in self.coeffs.items() if c < 0} s = "+".join( - ("" if c == 1 else str(c))+f(e) + ("" if c == 1 else str(c)) + f(e) for e, c in pos_sub_e.items() ) if neg_sub_e: - s += "-"+"".join( - ("-" if c == -1 else str(c))+f(e) + s += "-" + "".join( + ("-" if c == -1 else str(c)) + f(e) for e, c in pos_sub_e.items() ) if self.const != 0: @@ -469,18 +489,21 @@ def __eq__(self, other: Any) -> bool: return False return NotImplemented + def ModAngleExpr(expr: AngleExpr, mod: AngleExpr) -> AngleExpr: if isinstance(expr, Angle) and isinstance(mod, Angle): - return expr%mod + return expr % mod return _ModAngleExpr(expr, mod) + class _ModAngleExpr(AngleExpr): _expr: AngleExpr _mod: AngleExpr def __init__(self, expr: AngleExpr, mod: AngleExpr): if isinstance(expr, Angle) and isinstance(mod, Angle): - raise ValueError("Arguments to _ModAngleExpr constructor cannot both be Angle.") + raise ValueError( + "Arguments to _ModAngleExpr constructor cannot both be Angle.") self._expr = expr self._mod = mod @@ -498,7 +521,7 @@ def is_zero(self) -> bool: @property def to_qiskit(self) -> Any: - return self.expr.to_qiskit%self.mod.to_qiskit + return self.expr.to_qiskit % self.mod.to_qiskit def __hash__(self) -> int: return hash((_ModAngleExpr, self.expr, self.mod)) @@ -523,6 +546,7 @@ def __eq__(self, other: Any) -> bool: return False return NotImplemented + class AngleVar(AngleExpr): _global_id: ClassVar[int] = 0 _qiskit_bindings: ClassVar[Dict[int, Any]] @@ -544,7 +568,7 @@ def to_qiskit(self) -> Any: return AngleVar._qiskit_bindings[self._id] try: # pylint: disable = import-outside-toplevel - from qiskit.circuit import Parameter # type: ignore + from qiskit.circuit import Parameter # type: ignore except ModuleNotFoundError as e: raise ModuleNotFoundError("You must install the 'qiskit' library.") from e p = Parameter(self._repr_latex_) @@ -575,13 +599,13 @@ def __eq__(self, other: Any) -> bool: return NotImplemented - def _validate_vec2(vec2: Tuple[int, int]) -> None: if not isinstance(vec2, tuple) or len(vec2) != 2: raise TypeError("Expected pair.") if not all(isinstance(x, int) for x in vec2): raise TypeError("Expected pair of integers.") + class SVGBuilder: """ Utility class for building certain SVG images. @@ -599,6 +623,7 @@ def __init__(self, width: int, height: int): raise TypeError("Height should be positive integer.") self._width = width self._height = height + self._def_object_ids = [] self._tags = [] @property @@ -653,6 +678,60 @@ def line(self, fro: Tuple[int, int], to: Tuple[int, int]) -> "SVGBuilder": self._tags.append(tag) return self + def line_bend(self, fro: Tuple[int, int], to: Tuple[int, int], left=False, degree=5): + _validate_vec2(fro) + _validate_vec2(to) + + fx, fy = fro + tx, ty = to + bx, by = calculate_orthogonal_point(np.asarray(fro), np.asarray(to), d=degree, + left=left) + + tag = f'' + self._tags.append(tag) + return self + + def add_diagonal_fill(self, color_1: str, color_2: str, id: str) -> "SVGBuilder": + tag = f'' \ + f'' \ + f'' \ + f'' \ + f'' \ + f'' + + self._def_object_ids.append(id) + self._tags.append(tag) + return self + + def square(self, centre: Tuple[int, int], width: int, height: int, + fill) -> "SVGBuilder": + _validate_vec2(centre) + x, y = centre + if fill in self._def_object_ids: + tag = f'' + self._tags.append(tag) + elif isinstance(fill, str): + tag = f'' + self._tags.append(tag) + else: + raise TypeError(f"Fill must be string or a defined Tag. Got: {fill} ") + return self + + def text_with_square(self, centre: Tuple[int, int], width: int, height: int, + text: str) -> "SVGBuilder": + _validate_vec2(centre) + x, y = centre + tag = f'' \ + f'' \ + f'{text}' \ + f'' + self._tags.append(tag) + return self + def circle(self, centre: Tuple[int, int], r: int, fill: str) -> "SVGBuilder": """ Draws a circle with given centre and radius. @@ -666,7 +745,8 @@ def circle(self, centre: Tuple[int, int], r: int, fill: str) -> "SVGBuilder": self._tags.append(tag) return self - def text(self, pos: Tuple[int, int], text: str, *, font_size: int = 10) -> "SVGBuilder": + def text(self, pos: Tuple[int, int], text: str, *, + font_size: int = 10) -> "SVGBuilder": """ Draws text at the given position (stroke/fill not used). """ @@ -676,7 +756,7 @@ def text(self, pos: Tuple[int, int], text: str, *, font_size: int = 10) -> "SVGB if not isinstance(font_size, int) or font_size <= 0: raise TypeError("Font size must be positive integer.") x, y = pos - tag = f'{text}' + tag = f'{text}' self._tags.append(tag) return self @@ -709,6 +789,7 @@ class TempSchedule(Protocol): def __call__(self, it: int, num_iters: int) -> float: ... + @runtime_checkable class TempScheduleProvider(Protocol): """ @@ -716,11 +797,13 @@ class TempScheduleProvider(Protocol): from an initial and final temperatures. """ - def __call__(self, t_init: Union[int, float], t_final: Union[int, float]) -> TempSchedule: + def __call__(self, t_init: Union[int, float], + t_final: Union[int, float]) -> TempSchedule: ... -def linear_temp_schedule(t_init: Union[int, float], t_final: Union[int, float]) -> TempSchedule: +def linear_temp_schedule(t_init: Union[int, float], + t_final: Union[int, float]) -> TempSchedule: """ Returns a straight/linear temperature schedule for given initial and final temperatures, from https://link.springer.com/article/10.1007/BF00143921 @@ -729,12 +812,15 @@ def linear_temp_schedule(t_init: Union[int, float], t_final: Union[int, float]) raise TypeError(f"Expected int or float, found {type(t_init)}.") if not isinstance(t_final, (int, float)): raise TypeError(f"Expected int or float, found {type(t_final)}.") + def temp_schedule(it: int, num_iters: int) -> float: - return t_init + (t_final-t_init)*it/(num_iters-1) + return t_init + (t_final - t_init) * it / (num_iters - 1) + return temp_schedule -def geometric_temp_schedule(t_init: Union[int, float], t_final: Union[int, float]) -> TempSchedule: +def geometric_temp_schedule(t_init: Union[int, float], + t_final: Union[int, float]) -> TempSchedule: """ Returns a geometric temperature schedule for given initial and final temperatures, from https://link.springer.com/article/10.1007/BF00143921 @@ -743,12 +829,16 @@ def geometric_temp_schedule(t_init: Union[int, float], t_final: Union[int, float raise TypeError(f"Expected int or float, found {type(t_init)}.") if not isinstance(t_final, (int, float)): raise TypeError(f"Expected int or float, found {type(t_final)}.") + def temp_schedule(it: int, num_iters: int) -> float: - return t_init * ((t_final/t_init)**(it/(num_iters-1.0))) # type: ignore[no-any-return] + return t_init * ((t_final / t_init) ** ( + it / (num_iters - 1.0))) # type: ignore[no-any-return] + return temp_schedule -def reciprocal_temp_schedule(t_init: Union[int, float], t_final: Union[int, float]) -> TempSchedule: +def reciprocal_temp_schedule(t_init: Union[int, float], + t_final: Union[int, float]) -> TempSchedule: """ Returns a reciprocal temperature schedule for given initial and final temperatures, from https://link.springer.com/article/10.1007/BF00143921 @@ -757,14 +847,17 @@ def reciprocal_temp_schedule(t_init: Union[int, float], t_final: Union[int, floa raise TypeError(f"Expected int or float, found {type(t_init)}.") if not isinstance(t_final, (int, float)): raise TypeError(f"Expected int or float, found {type(t_final)}.") + def temp_schedule(it: int, num_iters: int) -> float: - num = t_init*t_final*(num_iters-1) - denom = (t_final*num_iters-t_init)+(t_init-t_final)*(it+1) - return num/denom + num = t_init * t_final * (num_iters - 1) + denom = (t_final * num_iters - t_init) + (t_init - t_final) * (it + 1) + return num / denom + return temp_schedule -def log_temp_schedule(t_init: Union[int, float], t_final: Union[int, float]) -> TempSchedule: +def log_temp_schedule(t_init: Union[int, float], + t_final: Union[int, float]) -> TempSchedule: """ Returns a logarithmic temperature schedule for given initial and final temperatures, from https://link.springer.com/article/10.1007/BF00143921 @@ -773,10 +866,13 @@ def log_temp_schedule(t_init: Union[int, float], t_final: Union[int, float]) -> raise TypeError(f"Expected int or float, found {type(t_init)}.") if not isinstance(t_final, (int, float)): raise TypeError(f"Expected int or float, found {type(t_final)}.") + def temp_schedule(it: int, num_iters: int) -> float: - num = t_init*t_final*(math.log(num_iters+1)-math.log(2)) - denom = (t_final*math.log(num_iters+1)-t_init*math.log(2))+(t_init-t_final)*math.log(it+2) - return num/denom + num = t_init * t_final * (math.log(num_iters + 1) - math.log(2)) + denom = (t_final * math.log(num_iters + 1) - t_init * math.log(2)) + ( + t_init - t_final) * math.log(it + 2) + return num / denom + return temp_schedule @@ -785,14 +881,12 @@ def temp_schedule(it: int, num_iters: int) -> float: Names of the standard temperature schedules. """ - StandardTempSchedule = Tuple[StandardTempScheduleName, Union[int, float], Union[int, float]] """ Type for standard temperature schedules. """ - StandardTempSchedules: Final[Mapping[StandardTempScheduleName, TempScheduleProvider]] = { "linear": linear_temp_schedule, "geometric": geometric_temp_schedule, diff --git a/test.py b/test.py new file mode 100644 index 00000000..0f662baf --- /dev/null +++ b/test.py @@ -0,0 +1,21 @@ +import unittest + +from pauliopt.pauli.pauli_gadget import PPhase +from pauliopt.pauli.pauli_polynomial import PauliPolynomial +from pauliopt.pauli.utils import * +from pauliopt.utils import Angle, pi + + + +def main(): + pp = PauliPolynomial(5) + + pp >>= PPhase(Angle(pi)) @ [I, I, X, Z, Y] + pp >>= PPhase(Angle(pi)) @ [I, I, X, Z, Y] + pp >>= PPhase(Angle(pi/2)) @ [I, I, X, Z, Y] + + print(pp.to_latex()) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/tests/test_pauli_annealing.py b/tests/test_pauli_annealing.py index c090749b..5b4ae4f1 100644 --- a/tests/test_pauli_annealing.py +++ b/tests/test_pauli_annealing.py @@ -15,6 +15,7 @@ from pauliopt.pauli.pauli_polynomial import PauliPolynomial from pauliopt.pauli.utils import X, Y, Z, I from pauliopt.topologies import Topology +from pauliopt.utils import pi PAULI_TO_TKET = { X: Pauli.X, @@ -33,7 +34,7 @@ def pauli_poly_to_tket(pp: PauliPolynomial): for gadget in pp.pauli_gadgets: circuit.add_pauliexpbox( PauliExpBox([PAULI_TO_TKET[p] for p in gadget.paulis], - gadget.angle / np.pi), + gadget.angle.to_qiskit / np.pi), list(range(pp.num_qubits))) Transform.DecomposeBoxes().apply(circuit) return tket_to_qiskit(circuit) @@ -55,7 +56,7 @@ def verify_equality(qc_in, qc_out): def generate_all_combination_pauli_polynomial(n_qubits=2): - allowed_angels = [2 * np.pi, np.pi, 0.5 * np.pi, 0.25 * np.pi, 0.125 * np.pi] + allowed_angels = [2 * pi, pi, pi / 2, pi / 4, pi / 8] pp = PauliPolynomial(n_qubits) for comb in itertools.product([X, Y, Z, I], repeat=n_qubits): pp >>= PPhase(np.random.choice(allowed_angels)) @ list(comb) diff --git a/tests/test_pauli_propagation.py b/tests/test_pauli_propagation.py index 4a5d0dac..bed248f1 100644 --- a/tests/test_pauli_propagation.py +++ b/tests/test_pauli_propagation.py @@ -10,13 +10,15 @@ from pytket._tket.pauli import Pauli from qiskit import QuantumCircuit -from pauliopt.pauli.clifford_gates import CX, CY, CZ, H, S, V, generate_random_clifford, CliffordType, CliffordGate, \ +from pauliopt.pauli.clifford_gates import CX, CY, CZ, H, S, V, generate_random_clifford, \ + CliffordType, CliffordGate, \ ControlGate, SingleQubitGate, clifford_to_qiskit from pauliopt.pauli.pauli_gadget import PPhase from pauliopt.pauli.pauli_polynomial import PauliPolynomial from pauliopt.pauli.utils import X, Y, Z, I from pauliopt.topologies import Topology +from pauliopt.utils import pi PAULI_TO_TKET = { X: Pauli.X, @@ -35,7 +37,7 @@ def pauli_poly_to_tket(pp: PauliPolynomial): for gadget in pp.pauli_gadgets: circuit.add_pauliexpbox( PauliExpBox([PAULI_TO_TKET[p] for p in gadget.paulis], - gadget.angle / np.pi), + gadget.angle.to_qiskit / np.pi), list(range(pp.num_qubits))) Transform.DecomposeBoxes().apply(circuit) return tket_to_qiskit(circuit) @@ -57,7 +59,7 @@ def verify_equality(qc_in, qc_out): def generate_all_combination_pauli_polynomial(n_qubits=2): - allowed_angels = [2 * np.pi, np.pi, 0.5 * np.pi, 0.25 * np.pi, 0.125 * np.pi] + allowed_angels = [2 * pi, pi, pi / 2, pi / 4, pi / 8] pp = PauliPolynomial(n_qubits) for comb in itertools.product([X, Y, Z, I], repeat=n_qubits): pp >>= PPhase(np.random.choice(allowed_angels)) @ list(comb) @@ -104,7 +106,8 @@ def test_circuit_construction(self): "The resulting Quantum Circuits were not equivalent") self.assertTrue(check_matching_architecture(our_synth, topology.to_nx), "The Pauli Polynomial did not match the architecture") - self.assertEqual(get_two_qubit_count(our_synth), pp.two_qubit_count(topology), + self.assertEqual(get_two_qubit_count(our_synth), + pp.two_qubit_count(topology), "Two qubit count needs to be equivalent to to two qubit count of the circuit") def test_gate_propagation(self): diff --git a/tests/test_pauli_representation.py b/tests/test_pauli_representation.py index 5309a276..25cb3cc1 100644 --- a/tests/test_pauli_representation.py +++ b/tests/test_pauli_representation.py @@ -3,20 +3,104 @@ from pauliopt.pauli.pauli_gadget import PPhase from pauliopt.pauli.pauli_polynomial import PauliPolynomial from pauliopt.pauli.utils import * +from pauliopt.utils import Angle, pi +import os -_PAULI_REPR = "(0.5) @ { I, X, Y, Z }\n(0.25) @ { X, X, Y, X }" +_PAULI_REPR = "(π/2) @ { I, X, Y, Z }\n(π/4) @ { X, X, Y, X }" + +SVG_CODE_PAULI = '\n\n\n\n\n\n\n\n\n\n\n\nπ\n\n\n\n\n\n\n\n\n\nπ/2\n\n\n\n\n\n\n\n\n\nπ/256\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nπ/8\n\n\n\n\n\n\n\n\n\nπ/4\n\n\n\n\n\n\n\n\n\nπ/2\n\n\n\n\n\n' + +LATEX_CODE_PAULI = """\documentclass[preview]{standalone} + +\\usepackage{tikz} +\\usetikzlibrary{zx-calculus} +\\usetikzlibrary{quantikz} +\\usepackage{graphicx} + +\\tikzset{ +diagonal fill/.style 2 args={fill=#2, path picture={ +\\fill[#1, sharp corners] (path picture bounding box.south west) -| + (path picture bounding box.north east) -- cycle;}}, +reversed diagonal fill/.style 2 args={fill=#2, path picture={ +\\fill[#1, sharp corners] (path picture bounding box.north west) |- + (path picture bounding box.south east) -- cycle;}} +} + +\\tikzset{ +diagonal fill/.style 2 args={fill=#2, path picture={ +\\fill[#1, sharp corners] (path picture bounding box.south west) -| + (path picture bounding box.north east) -- cycle;}} +} + +\\tikzset{ +pauliY/.style={ +zxAllNodes, +zxSpiders, +inner sep=0mm, +minimum size=2mm, +shape=rectangle, +%fill=colorZxX +diagonal fill={colorZxX}{colorZxZ} +} +} + +\\tikzset{ +pauliX/.style={ +zxAllNodes, +zxSpiders, +inner sep=0mm, +minimum size=2mm, +shape=rectangle, +fill=colorZxX +} +} + +\\tikzset{ +pauliZ/.style={ +zxAllNodes, +zxSpiders, +inner sep=0mm, +minimum size=2mm, +shape=rectangle, +fill=colorZxZ +} +} + +\\tikzset{ +pauliPhase/.style={ +zxAllNodes, +zxSpiders, +inner sep=0.5mm, +minimum size=2mm, +shape=rectangle, +fill=white +} +} +\\begin{document} +\\begin{ZX} +\\zxNone{} & \\zxNone{} & |[pauliPhase]| \\pi & \\zxNone{} & \\zxNone{} & |[pauliPhase]| \\pi & \\zxNone{} & \\zxNone{} & |[pauliPhase]| \\frac{\\pi}{2} & \\zxNone{} &\\\\ +\\\\ +\\zxNone{} \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar &\\\\ +\\zxNone{} \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar &\\\\ +\\zxNone{} \\rar & |[pauliX]| \\ar[ruuuu, bend right] \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar & |[pauliX]| \\ar[ruuuu, bend right] \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar & |[pauliX]| \\ar[ruuuu, bend right] \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar &\\\\ +\\zxNone{} \\rar & |[pauliZ]| \\ar[ruuuuu, bend right] \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar & |[pauliZ]| \\ar[ruuuuu, bend right] \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar & |[pauliZ]| \\ar[ruuuuu, bend right] \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar &\\\\ +\\zxNone{} \\rar & |[pauliY]| \\ar[ruuuuuu, bend right] \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar & |[pauliY]| \\ar[ruuuuuu, bend right] \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar & |[pauliY]| \\ar[ruuuuuu, bend right] \\rar & \\zxNone{} \\rar & \\zxNone{} \\rar &\\\\ +\\end{ZX} +\\end{document} +""" class TestPauliConversion(unittest.TestCase): + def test_circuit_construction(self): pp = PauliPolynomial(4) - pp >>= PPhase(0.5) @ [I, X, Y, Z] + pp >>= PPhase(Angle(pi / 2)) @ [I, X, Y, Z] self.assertEqual(pp.num_qubits, 4) self.assertEqual(len(pp), 1) - pp >>= PPhase(0.25) @ [X, X, Y, X] + pp >>= PPhase(Angle(pi / 4)) @ [X, X, Y, X] self.assertEqual(pp.__repr__(), _PAULI_REPR) self.assertEqual(len(pp), 2) @@ -24,4 +108,33 @@ def test_circuit_construction(self): pp_ = PauliPolynomial(num_qubits=4) pp_ >> pp - self.assertEqual(pp.__repr__(), pp_.__repr__(), "Right shift resulted in different pauli Polynomials.") + self.assertEqual(pp.__repr__(), pp_.__repr__(), + "Right shift resulted in different pauli Polynomials.") + + def test_circuit_visualisation_svg(self): + pp = PauliPolynomial(5) + + pp >>= PPhase(Angle(pi)) @ [I, I, X, Z, Y] + pp >>= PPhase(Angle(pi / 2)) @ [X, X, I, I, Y] + pp >>= PPhase(Angle(pi / 256)) @ [X, I, I, Z, Y] + pp >>= PPhase(Angle(pi / 8)) @ [X, X, X, Z, Y] + pp >>= PPhase(Angle(pi / 4)) @ [X, Z, I, I, Y] + pp >>= PPhase(Angle(pi / 2)) @ [X, I, I, Y, Y] + + self.assertEqual(pp.to_svg(svg_code_only=True), SVG_CODE_PAULI) + + def test_circuit_visualization_latex(self): + pp = PauliPolynomial(5) + + pp >>= PPhase(Angle(pi)) @ [I, I, X, Z, Y] + pp >>= PPhase(Angle(pi)) @ [I, I, X, Z, Y] + pp >>= PPhase(Angle(pi / 2)) @ [I, I, X, Z, Y] + + self.assertEqual(pp.to_latex(), LATEX_CODE_PAULI) + + pp.to_latex(file_name="test") + self.assertTrue(os.path.isfile("./test.tex")) + with open("./test.tex", "r") as f: + content = f.read() + self.assertEqual(LATEX_CODE_PAULI, content) + os.remove("./test.tex") \ No newline at end of file