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)