{
"cells": [
{
"cell_type": "markdown",
"id": "b5cd31d7",
"metadata": {},
"source": [
"# Guppy Program Optimization using `tket`\n",
"\n",
"**Download this notebook - {nb-download}`Guppy-opt-example.ipynb`**"
]
},
{
"cell_type": "markdown",
"id": "cc35ddf1",
"metadata": {},
"source": [
"This notebook can be run with the following package versions"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bdf94fdd",
"metadata": {},
"outputs": [],
"source": [
"!uv pip install \\\n",
" \"pytket~=2.11.0\" \\\n",
" \"guppylang~=0.21.6\" \\\n",
" \"hugr~=0.14.4\" \\\n",
" \"tket~=0.12.15\""
]
},
{
"cell_type": "markdown",
"id": "ac97adb5",
"metadata": {},
"source": [
"In this notebook tutorial we will show how to call pytket optimization passes on quantum programs written in [Guppy](https://docs.quantinuum.com/guppy/). Guppy programs compile to [HUGR](https://github.com/Quantinuum/hugr) which is an intermediate representation for hybrid quantum programs.\n",
"\n",
"As of `tket-py` version 0.12.13 `(tket>=0.12.13)` we can call any serializable pass from [pytket.passes](https://docs.quantinuum.com/tket/api-docs/passes.html) on Guppy programs. The `PytketHugrPass` interface allows the user to define a `Hugr` -> `Hugr` transformation with a pass from the pytket package.\n",
"\n",
"The `tket=0.12.15` release contains some key bug fixes for the optimization of Guppy programs. \n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "69bd0b7e-b8ed-434a-9736-d518d342a89c",
"metadata": {},
"outputs": [],
"source": [
"from graphviz import Digraph\n",
"\n",
"from guppylang import guppy\n",
"from guppylang.std.angles import pi\n",
"from guppylang.std.quantum import cx, h, qubit, t, rz\n",
"\n",
"from hugr.hugr.base import Hugr\n",
"from hugr.hugr.render import DotRenderer, RenderConfig\n",
"\n",
"from tket.passes import NormalizeGuppy, PytketHugrPass, PassResult\n",
"from pytket.passes import RemoveRedundancies, SquashRzPhasedX"
]
},
{
"cell_type": "markdown",
"id": "6e3199d7-22e2-43b6-aa5b-70a54fcd3375",
"metadata": {},
"source": [
"## Getting started - Flattening the Guppy generated HUGR\n",
"\n",
"Currently the Guppy compiler produces a number of compilation artifacts which its helpful to remove before thinking about optimizing the quantum gates in the program.\n",
"\n",
"This HUGR cleanup can be done with the [NormalizeGuppy](https://quantinuum.github.io/tket2/generated/tket.passes.NormalizeGuppy#tket.passes.NormalizeGuppy) pass from `tket` which removes `MakeTuple` nodes and dead functions from the HUGR graph. This simplification may be built in to the Guppy compiler in the future."
]
},
{
"cell_type": "markdown",
"id": "2bb6c3ad",
"metadata": {},
"source": [
"As a first example lets define a simple Guppy function which we can compile to HUGR. "
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "bc1851fd-ba4a-49f5-b6c1-05cfddd40db7",
"metadata": {},
"outputs": [],
"source": [
"@guppy\n",
"def pauli_zz_rotation(q0: qubit, q1: qubit) -> None:\n",
" cx(q0, q1)\n",
" t(q1)\n",
" cx(q0, q1)\n",
"\n",
"\n",
"zz_hugr_graph: Hugr = pauli_zz_rotation.compile_function().modules[0]"
]
},
{
"cell_type": "markdown",
"id": "25114910",
"metadata": {},
"source": [
"We can now draw this HUGR graph using the [DotRenderer](https://quantinuum.github.io/hugr/generated/hugr.hugr.render.DotRenderer.html)."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "2b189cdc",
"metadata": {},
"outputs": [],
"source": [
"# Configure Hugr visualizer\n",
"my_renderer = DotRenderer(RenderConfig(display_node_id=True, display_metadata=False))\n",
"\n",
"\n",
"def draw(hugr: Hugr) -> Digraph:\n",
" \"\"\"Draw a Hugr graph with the DotRenderer.\"\"\"\n",
" return my_renderer.render(hugr)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "fcd46e31-a9c0-441e-9d4c-1650a4b40e3e",
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"draw(zz_hugr_graph)"
]
},
{
"cell_type": "markdown",
"id": "df1a9ded",
"metadata": {},
"source": [
"We can also print the data for each of the nodes of the graph."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "e94ab99d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Node(0) NodeData(op=Module(), parent=None, metadata={'name': '__main__', 'core.used_extensions': [{'name': 'tket.bool', 'version': '0.2.0'}, {'name': 'tket.debug', 'version': '0.2.0'}, {'name': 'tket.futures', 'version': '0.2.0'}, {'name': 'tket.global_phase', 'version': '0.1.0'}, {'name': 'tket.guppy', 'version': '0.2.0'}, {'name': 'tket.modifier', 'version': '0.1.0'}, {'name': 'tket.qsystem', 'version': '0.5.0'}, {'name': 'tket.qsystem.random', 'version': '0.2.1'}, {'name': 'tket.qsystem.utils', 'version': '0.3.0'}, {'name': 'tket.quantum', 'version': '0.2.1'}, {'name': 'tket.result', 'version': '0.2.0'}, {'name': 'tket.rotation', 'version': '0.2.0'}, {'name': 'tket.wasm', 'version': '0.4.1'}, {'name': 'guppylang', 'version': '0.1.0'}, {'name': 'prelude', 'version': '0.2.1'}, {'name': 'collections.array', 'version': '0.1.1'}, {'name': 'arithmetic.float', 'version': '0.1.0'}, {'name': 'arithmetic.float.types', 'version': '0.1.0'}, {'name': 'arithmetic.int', 'version': '0.1.0'}, {'name': 'arithmetic.int.types', 'version': '0.1.0'}, {'name': 'logic', 'version': '0.1.0'}], 'core.generator': {'name': 'guppylang (guppylang-internals-v0.26.0)', 'version': '0.21.7'}})\n",
"Node(1) NodeData(op=FuncDefn(f_name='pauli_zz_rotation', inputs=[Qubit, Qubit], params=[], visibility='Private'), parent=Node(0), metadata={'unitary': 0})\n",
"Node(2) NodeData(op=Input(types=[Qubit, Qubit]), parent=Node(1), metadata={})\n",
"Node(3) NodeData(op=Output(), parent=Node(1), metadata={})\n",
"Node(4) NodeData(op=CFG(inputs=[Qubit, Qubit]), parent=Node(1), metadata={})\n",
"Node(5) NodeData(op=DataflowBlock(inputs=[Qubit, Qubit], _sum=Unit), parent=Node(4), metadata={})\n",
"Node(6) NodeData(op=Input(types=[Qubit, Qubit]), parent=Node(5), metadata={})\n",
"Node(7) NodeData(op=Output(), parent=Node(5), metadata={})\n",
"Node(8) NodeData(op=ExitBlock(), parent=Node(4), metadata={})\n",
"Node(9) NodeData(op=ExtOp(_op_def=OpDef(name='CX', signature=OpDefSig(poly_func=PolyFuncType(params=[], body=FunctionType([Qubit, Qubit], [Qubit, Qubit])), binary=False), description='CX', misc={'commutation': [[0, 'Z'], [1, 'X']]}), signature=FunctionType([Qubit, Qubit], [Qubit, Qubit]), args=[]), parent=Node(5), metadata={})\n",
"Node(10) NodeData(op=MakeTuple([]), parent=Node(5), metadata={})\n",
"Node(11) NodeData(op=ExtOp(_op_def=OpDef(name='T', signature=OpDefSig(poly_func=PolyFuncType(params=[], body=FunctionType([Qubit], [Qubit])), binary=False), description='T', misc={'commutation': [[0, 'Z']]}), signature=FunctionType([Qubit], [Qubit]), args=[]), parent=Node(5), metadata={})\n",
"Node(12) NodeData(op=MakeTuple([]), parent=Node(5), metadata={})\n",
"Node(13) NodeData(op=ExtOp(_op_def=OpDef(name='CX', signature=OpDefSig(poly_func=PolyFuncType(params=[], body=FunctionType([Qubit, Qubit], [Qubit, Qubit])), binary=False), description='CX', misc={'commutation': [[0, 'Z'], [1, 'X']]}), signature=FunctionType([Qubit, Qubit], [Qubit, Qubit]), args=[]), parent=Node(5), metadata={})\n",
"Node(14) NodeData(op=MakeTuple([]), parent=Node(5), metadata={})\n",
"Node(15) NodeData(op=Tag(tag=0, sum_ty=Unit), parent=Node(5), metadata={})\n"
]
}
],
"source": [
"for node, data in zz_hugr_graph.nodes():\n",
" print(node, data)"
]
},
{
"cell_type": "markdown",
"id": "047269ec",
"metadata": {},
"source": [
"In addtion, we can define a `count_ops` helper function to count the number of operations of a particular type.\n",
"\n",
"We pass the string name of a particular `Op` as listed above and we can get the corresponding count for the number of instances in a graph."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "1194dba3",
"metadata": {},
"outputs": [],
"source": [
"def count_ops(hugr: Hugr, string_name: str) -> int:\n",
" count = 0\n",
" for _, data in hugr.nodes():\n",
" if string_name in data.op.name():\n",
" count += 1\n",
"\n",
" return count"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "01279ff6",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Total number of HUGR nodes = 16\n",
"# CX operations = 2\n",
"# MakeTuple operations = 3\n"
]
}
],
"source": [
"print(f\"Total number of HUGR nodes = {zz_hugr_graph.num_nodes()}\")\n",
"print(f\"# CX operations = {count_ops(zz_hugr_graph, 'CX')}\")\n",
"print(f\"# MakeTuple operations = {count_ops(zz_hugr_graph, 'MakeTuple')}\")"
]
},
{
"cell_type": "markdown",
"id": "7f4589bb",
"metadata": {},
"source": [
"We can simplify the structure of our HUGR graph with the [NormalizeGuppy](https://quantinuum.github.io/tket2/generated/tket.passes.NormalizeGuppy#tket.passes.NormalizeGuppy) pass. This is recommded to \"clear the way\" for optimizations which reduce the number of quantum gates."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "9271bab5-d165-4431-86fb-f027a12ec561",
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"normalize = NormalizeGuppy()\n",
"\n",
"cleaned_zz_hugr = normalize(\n",
" zz_hugr_graph\n",
") # Invoke the NormalizeGuppy pass via the __call__ method.\n",
"\n",
"draw(cleaned_zz_hugr)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "e5dbf9dc",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Total number of HUGR nodes = 7\n",
"# CX operations = 2\n",
"# MakeTuple operations = 0\n"
]
}
],
"source": [
"print(f\"Total number of HUGR nodes = {zz_hugr_graph.num_nodes()}\")\n",
"print(f\"# CX operations = {count_ops(zz_hugr_graph, 'CX')}\")\n",
"print(f\"# MakeTuple operations = {count_ops(zz_hugr_graph, 'MakeTuple')}\")"
]
},
{
"cell_type": "markdown",
"id": "73f265bc",
"metadata": {},
"source": [
"As you can see the normalization has simplified the structure of the HUGR whilst preserving the number of quantum gates."
]
},
{
"cell_type": "markdown",
"id": "7936acde-b9fc-4854-9321-db645445aac9",
"metadata": {},
"source": [
"## Example 1: Cancelling redundant CX gates \n",
"\n",
"Now let's consider some simple examples of optimizations that reduce the number of quantum gates.\n",
"\n",
"Lets first define a Guppy function which has a pair of adjacent CX gates. These can be cancelled as the CX gate is self-inverse."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "a28e42c5-bf55-4830-b2c2-3332f350b85b",
"metadata": {},
"outputs": [],
"source": [
"@guppy\n",
"def redundant_cx(q0: qubit, q1: qubit) -> None:\n",
" h(q0)\n",
" # Two adjacent CX gates with the same control and target can be cancelled.\n",
" cx(q0, q1)\n",
" cx(q0, q1)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "56c17e8f-31df-4392-91ee-839876858826",
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"my_hugr_graph = normalize(\n",
" redundant_cx.compile_function().modules[0]\n",
") # get normalized hugr for program above\n",
"draw(my_hugr_graph)"
]
},
{
"cell_type": "markdown",
"id": "67b17f0d",
"metadata": {},
"source": [
"These CX gates can be cancelled using the `RemoveRedundancies` pass from pytket. To call this pass on a `Hugr` object we can create a [PytketHugrPass](https://quantinuum.github.io/tket2/generated/tket.passes.PytketHugrPass#tket.passes.PytketHugrPass) instance from the `pytket.passes` transformation."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "4df73474-91de-4ace-a0fc-fed6df209164",
"metadata": {},
"outputs": [],
"source": [
"rr_pass = PytketHugrPass(RemoveRedundancies())\n",
"\n",
"pass_result: PassResult = rr_pass.run(my_hugr_graph)"
]
},
{
"cell_type": "markdown",
"id": "c90ec617",
"metadata": {},
"source": [
"Here we are using the `PytketHugrPass.run` method which returns a [PassResult](https://quantinuum.github.io/tket2/generated/tket.passes.PassResult#tket.passes.PassResult) object."
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "b52ed22d",
"metadata": {},
"outputs": [],
"source": [
"optimized_hugr = pass_result.hugr"
]
},
{
"cell_type": "markdown",
"id": "da12af9e",
"metadata": {},
"source": [
"In addition to the transformed `Hugr` instance, we can also see some more information from `PassResult`."
]
},
{
"cell_type": "markdown",
"id": "65f5453b",
"metadata": {},
"source": [
"We can check whether our optimization was performed in place."
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "d97591ac",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"True\n"
]
}
],
"source": [
"print(pass_result.inplace)"
]
},
{
"cell_type": "markdown",
"id": "04bd0b47",
"metadata": {},
"source": [
"We can also check whether the `Hugr` was modified by the `PytketHugrPass` as follows."
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "07632beb",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"True\n"
]
}
],
"source": [
"print(pass_result.modified)"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "46b3f860-4bf5-4e37-9764-cc4d8e82ce1c",
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"draw(optimized_hugr)"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "aff9a582",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"# CX gates = 0\n"
]
}
],
"source": [
"print(f\"# CX gates = {count_ops(my_hugr_graph, 'CX')}\")"
]
},
{
"cell_type": "markdown",
"id": "ff2a9cc5",
"metadata": {},
"source": [
"We see how both CX gates have been cancelled from our program."
]
},
{
"cell_type": "markdown",
"id": "ecffd448-2966-4987-a856-7ecc3df5e80c",
"metadata": {},
"source": [
"## Example 2: Simplifying sequences of single qubit gates\n",
"\n",
"\n",
"The second example we will show is simplifying sequences of single qubit gates using the [SqaushRzPhasedX](https://docs.quantinuum.com/tket/api-docs/passes.html#pytket.passes.SquashRzPhasedX) pass from pytket. "
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "78746bc6-34fe-4a68-bf94-063e3397e4e0",
"metadata": {},
"outputs": [],
"source": [
"@guppy\n",
"def redundant_1q_gates(q0: qubit) -> None:\n",
" rz(q0, pi / 2)\n",
" rz(q0, pi / 2)\n",
" rz(q0, pi / 2)\n",
"\n",
"\n",
"normalize2 = NormalizeGuppy(constant_folding=True)\n",
"\n",
"my_1q_hugr_graph = normalize2(\n",
" redundant_1q_gates.compile_function().modules[0]\n",
") # get normalized hugr for program above"
]
},
{
"cell_type": "markdown",
"id": "2f73dd41",
"metadata": {},
"source": [
"As before, we can create a `Hugr` -> `Hugr` transformation with the `PytketHugrPass` interface."
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "e7b88bdb-9cf6-4863-9d09-4c369b0016b4",
"metadata": {},
"outputs": [],
"source": [
"squash_pass = PytketHugrPass(SquashRzPhasedX())\n",
"\n",
"optimized_1q_hugr = squash_pass(my_1q_hugr_graph)"
]
},
{
"cell_type": "markdown",
"id": "135b6431",
"metadata": {},
"source": [
"We should see that our optimisation has reduced the number of Rz rotations from 3 to 1 by combining the rotation angles."
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "90c31269-05d3-45d2-8e91-abcda394bfe1",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"#Rz gates in optimized program = 1\n"
]
}
],
"source": [
"print(f\"#Rz gates in optimized program = {count_ops(optimized_1q_hugr, 'Rz')}\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"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.14.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}