From 4ed276c619d3afd9ea69971b2eb05c23e0bd032d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agust=C3=ADn=20Borgna?= <121866228+aborgna-q@users.noreply.github.com> Date: Fri, 28 Jun 2024 14:17:42 +0100 Subject: [PATCH] docs: Add some example notebooks for the python package. (#443) The code in the notebooks requires #442, #441, and #444 Closes #434 --- README.md | 8 + tket2-py/README.md | 7 + tket2-py/examples/1-Getting-Started.ipynb | 669 ++++++++++++++ tket2-py/examples/2-Rewriting-Circuits.ipynb | 886 +++++++++++++++++++ tket2-py/examples/utils/__init__.py | 21 + 5 files changed, 1591 insertions(+) create mode 100644 tket2-py/examples/1-Getting-Started.ipynb create mode 100644 tket2-py/examples/2-Rewriting-Circuits.ipynb create mode 100644 tket2-py/examples/utils/__init__.py diff --git a/README.md b/README.md index c0054cfa..273696a2 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,14 @@ See the respective [Python](https://github.com/CQCL/tket2/blob/main/tket2-py) READMEs for more information. +## Usage + +The rust crate documentation is available at [docs.rs](https://docs.rs/tket2). + +See the [Getting Started][getting-started] notebook for a quick introduction to using `tket2` in Python. + + [getting-started]: https://github.com/CQCL/tket2/blob/main/tket2-py/examples/1-Getting-Started.ipynb + ## Developing TKET2 See [DEVELOPMENT.md][] for instructions on setting up the development environment. diff --git a/tket2-py/README.md b/tket2-py/README.md index 2fffe844..5b798293 100644 --- a/tket2-py/README.md +++ b/tket2-py/README.md @@ -25,6 +25,13 @@ TKET2 can be installed via `pip`. Requires Python >= 3.10. pip install tket2 ``` +## Usage + +See the [Getting Started][getting-started] guide and the other [examples]. + + [getting-started]: https://github.com/CQCL/tket2/blob/main/tket2-py/examples/1-Getting-Started.ipynb + [examples]: https://github.com/CQCL/tket2/blob/main/tket2-py/examples/ + ## Development This package uses [pyo3](https://pyo3.rs/v0.16.4/) and diff --git a/tket2-py/examples/1-Getting-Started.ipynb b/tket2-py/examples/1-Getting-Started.ipynb new file mode 100644 index 00000000..16ace287 --- /dev/null +++ b/tket2-py/examples/1-Getting-Started.ipynb @@ -0,0 +1,669 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5662ddb4", + "metadata": {}, + "source": [ + "# Getting started with tket2\n", + "\n", + "This demo notebook gives an overview of currently implemented tket2 features.\n", + "\n", + "Be aware that the library is still in development and some features may not be\n", + "fully implemented or may change in the future.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "87af9fa1", + "metadata": {}, + "source": [ + "## Installation\n", + "\n", + "To install the library, you can use pip:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "25ac0737", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: tket2 in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (0.0.0a1)\n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "%pip install tket2" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "0c2a523d", + "metadata": {}, + "outputs": [], + "source": [ + "from tket2.circuit import Tk2Circuit\n", + "import math" + ] + }, + { + "cell_type": "markdown", + "id": "d9d52817", + "metadata": {}, + "source": [ + "Let's configure pretty printing for the circuits, using the mermaid renderer.\n", + "This will render the circuit graphs in `jupyter-lab`, but it is not currently supported when viewing the library in vscode." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2896f51f", + "metadata": {}, + "outputs": [], + "source": [ + "from tket2.circuit import render_circuit_mermaid\n", + "\n", + "setattr(\n", + " Tk2Circuit,\n", + " \"_repr_markdown_\",\n", + " lambda self: f\"```mermaid\\n{render_circuit_mermaid(self)}\\n```\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "d403fe3b", + "metadata": {}, + "source": [ + "# Defining circuits\n", + "\n", + "There are multiple ways for defining circuits in tket2.\n", + "The library provides two limited builders, and it supports importing circuits from `guppy` and `pytket`.\n", + "\n", + "### Using the commands-based builder\n", + "\n", + "The simplest way is to use the commands-based builder interface `CircuitBuild`.\n", + "It supports constructing pure circuits by listing a series of commands applied to specific qubits." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "71f02038", + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "```mermaid\n", + "graph LR\n", + " subgraph 0 [\"(0) DFG\"]\n", + " direction LR\n", + " 1[\"(1) Input\"]\n", + " 1--\"0:0
qubit\"-->3\n", + " 1--\"1:1
qubit\"-->4\n", + " 2[\"(2) Output\"]\n", + " 3[\"(3) quantum.tket2.H\"]\n", + " 3--\"0:0
qubit\"-->4\n", + " 4[\"(4) quantum.tket2.CX\"]\n", + " 4--\"0:0
qubit\"-->2\n", + " 4--\"1:1
qubit\"-->2\n", + " end\n", + "\n", + "```" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from tket2.circuit.build import CircBuild, H, CX\n", + "\n", + "builder = CircBuild(n_qb=2)\n", + "\n", + "builder.extend([ H(0), CX(0, 1)])\n", + "circ = builder.finish()\n", + "circ" + ] + }, + { + "cell_type": "markdown", + "id": "f851efee-a33e-4261-ade8-a78f878e45e2", + "metadata": {}, + "source": [ + "### Using the Dataflow Builder\n", + "\n", + "The Dataflow Builder is more flexible than `CircBuild`. It lets you connect arbitrary inputs and outputs to each operation.\n", + "This way, you can define circuits that read the same boolean multiple times, or allocate qubits dynamically." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "3e3c2ed0-892b-42d7-b5e8-1bc81970635b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "```mermaid\n", + "graph LR\n", + " subgraph 0 [\"(0) DFG\"]\n", + " direction LR\n", + " 1[\"(1) Input\"]\n", + " 1--\"0:0
qubit\"-->4\n", + " 2[\"(2) Output\"]\n", + " 3[\"(3) quantum.tket2.QAlloc\"]\n", + " 3--\"0:1
qubit\"-->5\n", + " 4[\"(4) quantum.tket2.H\"]\n", + " 4--\"0:0
qubit\"-->5\n", + " 5[\"(5) quantum.tket2.CX\"]\n", + " 5--\"0:0
qubit\"-->6\n", + " 5--\"1:0
qubit\"-->7\n", + " 6[\"(6) quantum.tket2.Measure\"]\n", + " 6--\"0:0
qubit\"-->8\n", + " 6--\"1:0
[]+[]\"-->2\n", + " 7[\"(7) quantum.tket2.Measure\"]\n", + " 7--\"0:0
qubit\"-->9\n", + " 7--\"1:1
[]+[]\"-->2\n", + " 8[\"(8) quantum.tket2.QFree\"]\n", + " 9[\"(9) quantum.tket2.QFree\"]\n", + " end\n", + "\n", + "```" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from tket2.circuit.build import Dfg, QB_T, BOOL_T\n", + "from tket2.ops import Tk2Op\n", + "\n", + "# Start building DFG with one qubit input and two boolean outputs\n", + "builder = Dfg(input_types=[QB_T], output_types=[BOOL_T, BOOL_T])\n", + "\n", + "# Qubits and booleans are identified by their \"Wires\" in the graph.\n", + "# We can get the wire for the single input qubit.\n", + "[q0] = builder.inputs()\n", + "\n", + "# And allocate a new qubit\n", + "[q1] = builder.add_op(Tk2Op.QAlloc, []).outs(1)\n", + "\n", + "# Each operation returns the new wires it creates.\n", + "[q0] = builder.add_op(Tk2Op.H, [q0]).outs(1)\n", + "q0, q1 = builder.add_op(Tk2Op.CX, [q0, q1]).outs(2)\n", + "\n", + "# Some operations may have different numbers of inputs and outputs.\n", + "[q0, b0] = builder.add_op(Tk2Op.Measure, [q0]).outs(2)\n", + "[q1, b1] = builder.add_op(Tk2Op.Measure, [q1]).outs(2)\n", + "\n", + "# And some may have no outputs at all.\n", + "builder.add_op(Tk2Op.QFree, [q0])\n", + "builder.add_op(Tk2Op.QFree, [q1])\n", + "\n", + "# To get the final circuit, we need to call finish() with the desired output wires.\n", + "circ = builder.finish([b0, b1])\n", + "\n", + "circ" + ] + }, + { + "cell_type": "markdown", + "id": "e3de0d13", + "metadata": {}, + "source": [ + "### Using pytket\n", + "\n", + "We can convert from and to `pytket` circuits." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "0ffe2aed", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: pytket in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (1.29.0)\n", + "Requirement already satisfied: sympy~=1.6 in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (from pytket) (1.12.1)\n", + "Requirement already satisfied: numpy<2.0,>=1.21.4 in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (from pytket) (1.26.4)\n", + "Requirement already satisfied: lark~=1.1 in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (from pytket) (1.1.9)\n", + "Requirement already satisfied: scipy~=1.13 in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (from pytket) (1.13.1)\n", + "Requirement already satisfied: networkx>=2.8.8 in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (from pytket) (3.3)\n", + "Requirement already satisfied: graphviz~=0.14 in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (from pytket) (0.20.3)\n", + "Requirement already satisfied: jinja2~=3.0 in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (from pytket) (3.1.4)\n", + "Requirement already satisfied: types-pkg-resources in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (from pytket) (0.1.3)\n", + "Requirement already satisfied: typing-extensions~=4.2 in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (from pytket) (4.12.2)\n", + "Requirement already satisfied: qwasm~=1.0 in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (from pytket) (1.0.1)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (from jinja2~=3.0->pytket) (2.1.5)\n", + "Requirement already satisfied: setuptools in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (from qwasm~=1.0->pytket) (70.0.0)\n", + "Requirement already satisfied: mpmath<1.4.0,>=1.1.0 in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (from sympy~=1.6->pytket) (1.3.0)\n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "%pip install pytket\n", + "\n", + "from pytket.circuit import Circuit as PytketCircuit\n", + "from pytket.circuit.display import render_circuit_jupyter" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "f47e0f2d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + " \n", + "
\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/markdown": [ + "```mermaid\n", + "graph LR\n", + " subgraph 0 [\"(0) FuncDefn\"]\n", + " direction LR\n", + " 1[\"(1) Input\"]\n", + " 1--\"0:0
qubit\"-->3\n", + " 1--\"1:1
qubit\"-->4\n", + " 2[\"(2) Output\"]\n", + " 3[\"(3) quantum.tket2.H\"]\n", + " 3--\"0:0
qubit\"-->4\n", + " 4[\"(4) quantum.tket2.CX\"]\n", + " 4--\"0:0
qubit\"-->2\n", + " 4--\"1:1
qubit\"-->2\n", + " end\n", + "\n", + "```" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tk1_circ = PytketCircuit(2).H(0).CX(0, 1)\n", + "render_circuit_jupyter(tk1_circ)\n", + "\n", + "circ = Tk2Circuit(tk1_circ)\n", + "circ" + ] + }, + { + "cell_type": "markdown", + "id": "e5450c5d", + "metadata": {}, + "source": [ + "### Using guppy\n", + "\n", + "Finally, if you have a circuit defined in `guppy` it can be imported directly into a `Tk2Circuit` object." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "975dbe01", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: guppylang in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (0.5.2)\n", + "Requirement already satisfied: graphviz<0.21.0,>=0.20.1 in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (from guppylang) (0.20.3)\n", + "Requirement already satisfied: hugr<0.3.0,>=0.2.1 in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (from guppylang) (0.2.1)\n", + "Requirement already satisfied: networkx<4.0.0,>=3.2.1 in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (from guppylang) (3.3)\n", + "Requirement already satisfied: pydantic<3.0.0,>=2.7.0b1 in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (from guppylang) (2.7.3)\n", + "Requirement already satisfied: typing-extensions<5.0.0,>=4.9.0 in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (from guppylang) (4.12.2)\n", + "Requirement already satisfied: annotated-types>=0.4.0 in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (from pydantic<3.0.0,>=2.7.0b1->guppylang) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.18.4 in /Users/agustinborgna/src/tket2/.venv/lib/python3.12/site-packages (from pydantic<3.0.0,>=2.7.0b1->guppylang) (2.18.4)\n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "%pip install guppylang\n", + "\n", + "from guppylang import guppy\n", + "from guppylang.module import GuppyModule\n", + "from guppylang.prelude import quantum\n", + "from guppylang.prelude.builtins import py\n", + "from guppylang.prelude.quantum import measure, phased_x, qubit, rz, zz_max\n", + "\n", + "# We define a utility function to convert a GuppyModule to a Tk2Circuit.\n", + "# This will be included with guppy in the future.\n", + "from utils import guppy_to_circuit # type: ignore" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "96065d20", + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "```mermaid\n", + "graph LR\n", + " subgraph 1 [\"(1) DFG\"]\n", + " direction LR\n", + " 2[\"(2) Input\"]\n", + " 2--\"0:0
qubit\"-->8\n", + " 2--\"1:0
qubit\"-->16\n", + " 27[\"(27) Output\"]\n", + " 4[\"(4) const:custom:f64(1.5707963267948966)\"]\n", + " 4--\"0:0
float64\"-->5\n", + " 5[\"(5) LoadConstant\"]\n", + " 5--\"0:1
float64\"-->8\n", + " 6[\"(6) const:custom:f64(-1.5707963267948966)\"]\n", + " 6--\"0:0
float64\"-->7\n", + " 7[\"(7) LoadConstant\"]\n", + " 7--\"0:2
float64\"-->8\n", + " 8[\"(8) quantum.tket2.PhasedX\"]\n", + " 8--\"0:0
qubit\"-->11\n", + " 9[\"(9) const:custom:f64(3.141592653589793)\"]\n", + " 9--\"0:0
float64\"-->10\n", + " 10[\"(10) LoadConstant\"]\n", + " 10--\"0:1
float64\"-->11\n", + " 11[\"(11) quantum.tket2.RzF64\"]\n", + " 11--\"0:0
qubit\"-->20\n", + " 12[\"(12) const:custom:f64(1.5707963267948966)\"]\n", + " 12--\"0:0
float64\"-->13\n", + " 13[\"(13) LoadConstant\"]\n", + " 13--\"0:1
float64\"-->16\n", + " 14[\"(14) const:custom:f64(-1.5707963267948966)\"]\n", + " 14--\"0:0
float64\"-->15\n", + " 15[\"(15) LoadConstant\"]\n", + " 15--\"0:2
float64\"-->16\n", + " 16[\"(16) quantum.tket2.PhasedX\"]\n", + " 16--\"0:0
qubit\"-->19\n", + " 17[\"(17) const:custom:f64(3.141592653589793)\"]\n", + " 17--\"0:0
float64\"-->18\n", + " 18[\"(18) LoadConstant\"]\n", + " 18--\"0:1
float64\"-->19\n", + " 19[\"(19) quantum.tket2.RzF64\"]\n", + " 19--\"0:1
qubit\"-->20\n", + " 20[\"(20) quantum.tket2.ZZMax\"]\n", + " 20--\"0:0
qubit\"-->23\n", + " 20--\"1:0
qubit\"-->25\n", + " 23[\"(23) quantum.tket2.Measure\"]\n", + " 23--\"0:0
qubit\"-->24\n", + " 24[\"(24) quantum.tket2.QFree\"]\n", + " 25[\"(25) quantum.tket2.Measure\"]\n", + " 25--\"0:0
qubit\"-->26\n", + " 25--\"1:0
[]+[]\"-->27\n", + " 26[\"(26) quantum.tket2.QFree\"]\n", + " end\n", + "\n", + "```" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Define a guppy module with a quantum function\n", + "\n", + "module = GuppyModule(\"test\")\n", + "module.load(quantum)\n", + "\n", + "@guppy(module)\n", + "def my_func(q0: qubit, q1: qubit) -> bool:\n", + " q0 = phased_x(q0, py(math.pi / 2), py(-math.pi / 2))\n", + " q0 = rz(q0, py(math.pi))\n", + " q1 = phased_x(q1, py(math.pi / 2), py(-math.pi / 2))\n", + " q1 = rz(q1, py(math.pi))\n", + " q0, q1 = zz_max(q0, q1)\n", + " _ = measure(q0)\n", + " return measure(q1)\n", + "\n", + "circ = guppy_to_circuit(my_func)\n", + "circ" + ] + }, + { + "cell_type": "markdown", + "id": "55974636", + "metadata": {}, + "source": [ + "This can be combined with the pytket conversion to obtain a `pytket` circuit from a guppy definition!" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "77feb50b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + " \n", + "
\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "module = GuppyModule(\"test\")\n", + "module.load(quantum)\n", + "\n", + "@guppy(module)\n", + "def my_func(q0: qubit, q1: qubit) -> bool:\n", + " q0 = phased_x(q0, py(math.pi / 2), py(-math.pi / 2))\n", + " q0 = rz(q0, py(math.pi))\n", + " q1 = phased_x(q1, py(math.pi / 2), py(-math.pi / 2))\n", + " q1 = rz(q1, py(math.pi))\n", + " q0, q1 = zz_max(q0, q1)\n", + " _ = measure(q0)\n", + " return measure(q1)\n", + "\n", + "circ = guppy_to_circuit(my_func)\n", + "tk1_circ = circ.to_tket1()\n", + "\n", + "render_circuit_jupyter(tk1_circ)" + ] + } + ], + "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.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tket2-py/examples/2-Rewriting-Circuits.ipynb b/tket2-py/examples/2-Rewriting-Circuits.ipynb new file mode 100644 index 00000000..57fe279c --- /dev/null +++ b/tket2-py/examples/2-Rewriting-Circuits.ipynb @@ -0,0 +1,886 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Manually rewriting tket2 circuits\n", + "\n", + "Tket2 includes a rewrite engine based on pattern matching and replacement. This allows for the application of a sequence of rewrite rules to a circuit, which can be used to simplify or optimize the circuit. In this notebook, we will demonstrate how to use this feature to rewrite circuits." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import itertools\n", + "import copy\n", + "\n", + "from tket2.circuit import Tk2Circuit\n", + "from tket2.circuit.build import CircBuild, H, from_coms, CX, PauliX, PauliY, PauliZ\n", + "from tket2.pattern import Rule, RuleMatcher\n", + "\n", + "from pytket import Circuit as Tk1Circuit\n", + "from pytket.circuit.display import render_circuit_jupyter\n", + "\n", + "from guppylang import guppy\n", + "from guppylang.module import GuppyModule\n", + "from guppylang.prelude import quantum\n", + "from guppylang.prelude.quantum import qubit, cx, rz, zz_phase\n", + "\n", + "# We define a utility function to convert a GuppyModule to a Tk2Circuit.\n", + "# This will be included with guppy in the future.\n", + "from utils import guppy_to_circuit # type: ignore" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Applying simple rewrite rules\n", + "\n", + "In the following example, we will define a couple simplification rules and apply them to a circuit. The rules are:\n", + "\n", + "1. A sequence of `CX` - `Rz` - `CX` gates can be replaced by a single `ZZPhase`\n", + "2. A `ZZPhase(0.5)` gate can be replaced with a single `CX` and some single-qubit gates\n", + "\n", + "Let's define these rules first." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# Note: Most of this boilerplate will be removed in the future.\n", + "\n", + "# First we define the pattern we want to match, as a Guppy function\n", + "module = GuppyModule(\"m\")\n", + "module.load(quantum)\n", + "@guppy(module)\n", + "def cnots_to_zzphase_lhs(q0: qubit, q1: qubit, angle: float) -> tuple[qubit, qubit]:\n", + " q0, q1 = cx(q0, q1)\n", + " q1 = rz(q1, angle)\n", + " q0, q1 = cx(q0, q1)\n", + " return (q0, q1)\n", + "cnots_to_zzphase_lhs = guppy_to_circuit(cnots_to_zzphase_lhs)\n", + "\n", + "# Then we define the replacement circuit\n", + "module = GuppyModule(\"m\")\n", + "module.load(quantum)\n", + "@guppy(module)\n", + "def cnots_to_zzphase_rhs(q0: qubit, q1: qubit, angle: float) -> tuple[qubit, qubit]:\n", + " q0, q1 = zz_phase(q0, q1, angle)\n", + " return (q0, q1)\n", + "cnots_to_zzphase_rhs = guppy_to_circuit(cnots_to_zzphase_rhs)\n", + "\n", + "# define the Rule, and create a RuleMatcher with it\n", + "cnots_to_zzphase = Rule(cnots_to_zzphase_lhs, cnots_to_zzphase_rhs)\n", + "cnots_matcher = RuleMatcher([cnots_to_zzphase])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now apply the rules to a circuit." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Original circuit:\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + " \n", + "
\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Rewritten circuit:\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + " \n", + "
\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "original_circ = Tk1Circuit(3).CX(1, 2).Rz(1/4, 2).CX(1, 2).CX(0, 1).Rz(1/2, 1).CX(0, 1)\n", + "\n", + "print(\"Original circuit:\")\n", + "render_circuit_jupyter(original_circ)\n", + "\n", + "# Match our rewrite rule once, then apply it.\n", + "merged_circ = Tk2Circuit(original_circ)\n", + "while rewrite := cnots_matcher.find_match(merged_circ):\n", + " merged_circ.apply_rewrite(rewrite)\n", + "\n", + "print(\"Rewritten circuit:\")\n", + "render_circuit_jupyter(merged_circ.to_tket1())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since the second rewrite rule does not have free parameters, we can define the pattern in pytket." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Rewritten circuit:\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + " \n", + "
\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# For circuits with no free parameters, we can use pytket directly\n", + "zzphase_to_cnot = Rule(\n", + " Tk1Circuit(2).ZZPhase(0.5, 0, 1),\n", + " Tk1Circuit(2).Rx(3.5, 0).Rz(0.5, 1).Rz(1.5, 0).CX(1, 0).Rz(0.5, 0).Rx(0.5, 0).Rz(0.5, 0)\n", + ")\n", + "zzphase_matcher = RuleMatcher([zzphase_to_cnot])\n", + "\n", + "# Apply the matcher to the previous circuit\n", + "final_circ = copy.deepcopy(merged_circ)\n", + "while rewrite := zzphase_matcher.find_match(final_circ):\n", + " final_circ.apply_rewrite(rewrite)\n", + "\n", + "print(\"Rewritten circuit:\")\n", + "render_circuit_jupyter(final_circ.to_tket1())\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The power of `RuleMatcher` gets unlocked when it is defined with multiple rules to apply at once. It can efficiently match millions of rules simultaneously." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Pauli propagation using rewrite rules\n", + "\n", + "In this example we want to propagate Pauli gates through a circuit, using hand-written rules.\n", + "\n", + "Let's start by defining how Pauli gates propagate through CNOT and Hadamard gates.\n", + "For each pair of (pauli, gate) we define a corresponding replacement (gate, pauli).\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# 'from_coms' takes a list of commands and returns a circuit.\n", + "# It's a shorthand for using the 'CircBuild' builder to build non-parametric circuits.\n", + "\n", + "hadamard_rules = [\n", + " Rule(from_coms(PauliX(0), H(0)), from_coms(H(0), PauliZ(0))),\n", + " Rule(from_coms(PauliZ(0), H(0)), from_coms(H(0), PauliX(0))),\n", + "]\n", + "\n", + "cx_rules = [\n", + " Rule(from_coms(PauliZ(0), CX(0, 1)), from_coms(CX(0, 1), PauliZ(0))),\n", + " Rule(from_coms(PauliX(1), CX(0, 1)), from_coms(CX(0, 1), PauliX(1))),\n", + " Rule(from_coms(PauliZ(1), CX(0, 1)), from_coms(CX(0, 1), PauliZ(0), PauliZ(1))),\n", + " Rule(from_coms(PauliX(0), CX(0, 1)), from_coms(CX(0, 1), PauliX(0), PauliX(1))),\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also describe how multiple Pauli gates can be combined with each other." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "def merge_rules() -> list[Rule]:\n", + " paulis = [PauliX(0), PauliY(0), PauliZ(0)]\n", + " identities = [\n", + " Rule(CircBuild(1).extend((p, p)).finish(), CircBuild(1).finish())\n", + " for p in paulis\n", + " ]\n", + "\n", + " off_diag = [\n", + " Rule(\n", + " CircBuild(1).extend((p0, p1)).finish(),\n", + " CircBuild(1).extend((p2,)).finish(),\n", + " )\n", + " for p0, p1, p2 in itertools.permutations(paulis)\n", + " ]\n", + " return [*identities, *off_diag]\n", + "merge_rules = merge_rules()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now construct a rule matcher that will efficiently find matches for all of these rules in a circuit,\n", + "along with a function to apply all rewrite rules exhaustively." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "matcher = RuleMatcher([*hadamard_rules, *cx_rules, *merge_rules])\n", + "\n", + "def apply_exhaustive(circ: Tk2Circuit, matcher: RuleMatcher) -> int:\n", + " \"\"\"Apply the first matching rule until no more matches are found. Return the\n", + " number of rewrites applied.\"\"\"\n", + " match_count = 0\n", + " while match := matcher.find_match(circ):\n", + " match_count += 1\n", + " circ.apply_rewrite(match)\n", + "\n", + " return match_count" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we can use our rewriter to a circuit." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + " \n", + "
\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Applied 5 rewrites\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + " \n", + "
\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "circ = CircBuild(2).extend([PauliX(0), PauliZ(1), H(0), CX(0, 1), H(1)]).finish()\n", + "render_circuit_jupyter(circ.to_tket1())\n", + "\n", + "matches = apply_exhaustive(circ, matcher)\n", + "print(f\"Applied {matches} rewrites\")\n", + "\n", + "render_circuit_jupyter(circ.to_tket1())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Supercompilation with Badger\n", + "\n", + "Tket2 includes a compilation pass called Badger which tries to simplify circuits by applying a large number of rewrite rules simultaneously, and searching for the best sequence of rules to apply. This is a computationally expensive process, but can lead to significant simplifications for big circuits." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + " \n", + "
\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + " \n", + "
\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from tket2.passes import badger_pass\n", + "\n", + "circ = Tk1Circuit(2).H(1).CZ(0, 1).H(0).H(0).H(1).CX(0, 1)\n", + "render_circuit_jupyter(circ)\n", + "\n", + "badger_pass(rebase=True).apply(circ)\n", + "render_circuit_jupyter(circ)" + ] + } + ], + "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.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tket2-py/examples/utils/__init__.py b/tket2-py/examples/utils/__init__.py new file mode 100644 index 00000000..4da38b54 --- /dev/null +++ b/tket2-py/examples/utils/__init__.py @@ -0,0 +1,21 @@ +"""Some utility functions for the example notebooks.""" + +from tket2.passes import lower_to_pytket +from tket2.circuit import Tk2Circuit + +from guppylang.definition.function import RawFunctionDef + + +# We need to define this helper function for now. It will be included in guppy in the future. +def guppy_to_circuit(func_def: RawFunctionDef) -> Tk2Circuit: + """Convert a Guppy function definition to a `Tk2Circuit`.""" + module = func_def.id.module + assert module is not None, "Function definition must belong to a module" + + hugr = module.compile() + assert hugr is not None, "Module must be compilable" + + json = hugr.to_raw().to_json() + circ = Tk2Circuit.from_guppy_json(json, func_def.name) + + return lower_to_pytket(circ)