{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Circuit analysis\n", "\n", "**Download this notebook - {nb-download}`circuit_analysis_example.ipynb`**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This notebook will introduce the basic methods of analysis and visualization of circuits available in `pytket`.
\n", "
\n", "It makes use of the modules `pytket_qiskit` and `pytket_cirq` for visualization; these need to be installed (with `pip`) in addition to `pytket`.
\n", "
\n", "We'll start by generating a small circuit to use as an example, and give it a name." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from pytket.circuit import Circuit, OpType" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[CU1(0.5) q[0], q[1]; Y q[2]; Z q[3]; H q[0]; X q[1]; H q[3]; X q[0]; CX q[1], q[2]; Y q[0]; Y q[1]; Z q[2]; Z q[0]; Z q[1]; CU1(0.5) q[2], q[3]; H q[1]; H q[2]; X q[3]; X q[2]; Y q[3]; CX q[3], q[0]; ]" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "c = Circuit(4, name=\"example\")\n", "c.add_gate(OpType.CU1, 0.5, [0, 1])\n", "c.H(0).X(1).Y(2).Z(3)\n", "c.X(0).CX(1, 2).Y(1).Z(2).H(3)\n", "c.Y(0).Z(1)\n", "c.add_gate(OpType.CU1, 0.5, [2, 3])\n", "c.H(2).X(3)\n", "c.Z(0).H(1).X(2).Y(3).CX(3, 0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Basic statistics" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "From the circuit we can easily read off the number of qubits ..." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "c.n_qubits" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "... the name ..." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'example'" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "c.name" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "... the overall depth of the circuit ..." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "8" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "c.depth()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "... and the depth by type of gate:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "from pytket.circuit import OpType" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(2, 2)" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "c.depth_by_type(OpType.CU1), c.depth_by_type(OpType.H)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This last method counts the number of instances of the specified gate type that cannot be parallelized. Notice that although there are 4 H gates in the circuit, the H-depth is 2 because pairs of them can be parallelized (as will be clear from the visualizations below)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Visualization" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are several ways to produce useful visualizations of circuits from `pytket`: we can use the methods in the `pytket.circuit.display` class; we can use the built-in `Graph` class to visualize the circuit as a directed acyclic graph (DAG); we can convert the circuit to either Qiskit or Cirq and use the tools provided by those modules; or we can export to Latex." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Via the `render_circuit_jupyter` method" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "from pytket.circuit.display import render_circuit_jupyter" ] }, { "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" } ], "source": [ "render_circuit_jupyter(c)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that although the `render_circuit_jupyter` method is the recommended way to render a circuit as jupyter cell output, one of the other methods should be used when working with scripts or python shells." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Via the `Graph` class" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "from pytket.utils import Graph" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "Circuit\n", "\n", "\n", "cluster_q_inputs\n", "\n", "\n", "\n", "cluster_q_outputs\n", "\n", "\n", "\n", "cluster_8\n", "\n", "CU1(0.5)\n", "\n", "\n", "cluster_9\n", "\n", "H\n", "\n", "\n", "cluster_10\n", "\n", "X\n", "\n", "\n", "cluster_11\n", "\n", "Y\n", "\n", "\n", "cluster_12\n", "\n", "Z\n", "\n", "\n", "cluster_13\n", "\n", "X\n", "\n", "\n", "cluster_14\n", "\n", "CX\n", "\n", "\n", "cluster_15\n", "\n", "Y\n", "\n", "\n", "cluster_16\n", "\n", "Z\n", "\n", "\n", "cluster_17\n", "\n", "H\n", "\n", "\n", "cluster_18\n", "\n", "Y\n", "\n", "\n", "cluster_19\n", "\n", "Z\n", "\n", "\n", "cluster_20\n", "\n", "CU1(0.5)\n", "\n", "\n", "cluster_21\n", "\n", "H\n", "\n", "\n", "cluster_22\n", "\n", "X\n", "\n", "\n", "cluster_23\n", "\n", "Z\n", "\n", "\n", "cluster_24\n", "\n", "H\n", "\n", "\n", "cluster_25\n", "\n", "X\n", "\n", "\n", "cluster_26\n", "\n", "Y\n", "\n", "\n", "cluster_27\n", "\n", "CX\n", "\n", "\n", "\n", "(0, 0)\n", "\n", "q[0]\n", "\n", "\n", "\n", "(8, 0)\n", "\n", "0\n", "\n", "\n", "\n", "(0, 0)->(8, 0)\n", "\n", "\n", "\n", "\n", "\n", "(2, 0)\n", "\n", "q[1]\n", "\n", "\n", "\n", "(8, 1)\n", "\n", "1\n", "\n", "\n", "\n", "(2, 0)->(8, 1)\n", "\n", "\n", "\n", "\n", "\n", "(4, 0)\n", "\n", "q[2]\n", "\n", "\n", "\n", "(11, 0)\n", "\n", "\n", "\n", "\n", "(4, 0)->(11, 0)\n", "\n", "\n", "\n", "\n", "\n", "(6, 0)\n", "\n", "q[3]\n", "\n", "\n", "\n", "(12, 0)\n", "\n", "\n", "\n", "\n", "(6, 0)->(12, 0)\n", "\n", "\n", "\n", "\n", "\n", "(1, 0)\n", "\n", "q[0]\n", "\n", "\n", "\n", "(3, 0)\n", "\n", "q[1]\n", "\n", "\n", "\n", "(5, 0)\n", "\n", "q[2]\n", "\n", "\n", "\n", "(7, 0)\n", "\n", "q[3]\n", "\n", "\n", "\n", "(9, 0)\n", "\n", "\n", "\n", "\n", "(8, 0)->(9, 0)\n", "\n", "\n", "\n", "\n", "\n", "(10, 0)\n", "\n", "\n", "\n", "\n", "(8, 1)->(10, 0)\n", "\n", "\n", "\n", "\n", "\n", "(13, 0)\n", "\n", "\n", "\n", "\n", "(9, 0)->(13, 0)\n", "\n", "\n", "\n", "\n", "\n", "(14, 0)\n", "\n", "0\n", "\n", "\n", "\n", "(10, 0)->(14, 0)\n", "\n", "\n", "\n", "\n", "\n", "(14, 1)\n", "\n", "1\n", "\n", "\n", "\n", "(11, 0)->(14, 1)\n", "\n", "\n", "\n", "\n", "\n", "(17, 0)\n", "\n", "\n", "\n", "\n", "(12, 0)->(17, 0)\n", "\n", "\n", "\n", "\n", "\n", "(18, 0)\n", "\n", "\n", "\n", "\n", "(13, 0)->(18, 0)\n", "\n", "\n", "\n", "\n", "\n", "(15, 0)\n", "\n", "\n", "\n", "\n", "(14, 0)->(15, 0)\n", "\n", "\n", "\n", "\n", "\n", "(16, 0)\n", "\n", "\n", "\n", "\n", "(14, 1)->(16, 0)\n", "\n", "\n", "\n", "\n", "\n", "(19, 0)\n", "\n", "\n", "\n", "\n", "(15, 0)->(19, 0)\n", "\n", "\n", "\n", "\n", "\n", "(20, 0)\n", "\n", "0\n", "\n", "\n", "\n", "(16, 0)->(20, 0)\n", "\n", "\n", "\n", "\n", "\n", "(20, 1)\n", "\n", "1\n", "\n", "\n", "\n", "(17, 0)->(20, 1)\n", "\n", "\n", "\n", "\n", "\n", "(23, 0)\n", "\n", "\n", "\n", "\n", "(18, 0)->(23, 0)\n", "\n", "\n", "\n", "\n", "\n", "(24, 0)\n", "\n", "\n", "\n", "\n", "(19, 0)->(24, 0)\n", "\n", "\n", "\n", "\n", "\n", "(21, 0)\n", "\n", "\n", "\n", "\n", "(20, 0)->(21, 0)\n", "\n", "\n", "\n", "\n", "\n", "(22, 0)\n", "\n", "\n", "\n", "\n", "(20, 1)->(22, 0)\n", "\n", "\n", "\n", "\n", "\n", "(25, 0)\n", "\n", "\n", "\n", "\n", "(21, 0)->(25, 0)\n", "\n", "\n", "\n", "\n", "\n", "(26, 0)\n", "\n", "\n", "\n", "\n", "(22, 0)->(26, 0)\n", "\n", "\n", "\n", "\n", "\n", "(27, 1)\n", "\n", "1\n", "\n", "\n", "\n", "(23, 0)->(27, 1)\n", "\n", "\n", "\n", "\n", "\n", "(24, 0)->(3, 0)\n", "\n", "\n", "\n", "\n", "\n", "(25, 0)->(5, 0)\n", "\n", "\n", "\n", "\n", "\n", "(27, 0)\n", "\n", "0\n", "\n", "\n", "\n", "(26, 0)->(27, 0)\n", "\n", "\n", "\n", "\n", "\n", "(27, 0)->(7, 0)\n", "\n", "\n", "\n", "\n", "\n", "(27, 1)->(1, 0)\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "G = Graph(c)\n", "G.get_DAG()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The small numbers (0 and 1) shown at the entry to and exit from the two-qubit gates represent \"port numbers\" on the gates; these allow us to track individual qubits, which may be placed in a different order on entry and exit in order to simplify the layout.
\n", "
\n", "The `Graph` class also has methods to save this image to a file and to open it in a PDF viewer.
\n", "
\n", "We can also view the qubit connectivity graph of a circuit:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "Qubit connectivity\n", "\n", "\n", "\n", "q[0]\n", "\n", "q[0]\n", "\n", "\n", "\n", "q[1]\n", "\n", "q[1]\n", "\n", "\n", "\n", "q[0]--q[1]\n", "\n", "\n", "\n", "\n", "q[3]\n", "\n", "q[3]\n", "\n", "\n", "\n", "q[0]--q[3]\n", "\n", "\n", "\n", "\n", "q[2]\n", "\n", "q[2]\n", "\n", "\n", "\n", "q[1]--q[2]\n", "\n", "\n", "\n", "\n", "q[2]--q[3]\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "G.get_qubit_graph()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Via Qiskit" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "from pytket.extensions.qiskit import tk_to_qiskit" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " ┌───┐┌───┐┌───┐ ┌───┐ ┌───┐\n", "q_0: ─■────────┤ H ├┤ X ├┤ Y ├──┤ Z ├─────────────┤ X ├\n", " │U1(π/2) ├───┤└───┘├───┤ ├───┤ ┌───┐ └─┬─┘\n", "q_1: ─■────────┤ X ├──■──┤ Y ├──┤ Z ├───┤ H ├───────┼──\n", " ┌───┐ └───┘┌─┴─┐├───┤ └───┘ ├───┤┌───┐ │ \n", "q_2: ──┤ Y ├────────┤ X ├┤ Z ├─■────────┤ H ├┤ X ├──┼──\n", " ├───┤ ┌───┐└───┘└───┘ │U1(π/2) ├───┤├───┤ │ \n", "q_3: ──┤ Z ├───┤ H ├───────────■────────┤ X ├┤ Y ├──■──\n", " └───┘ └───┘ └───┘└───┘ \n" ] } ], "source": [ "print(tk_to_qiskit(c))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Via Cirq" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "from pytket.extensions.cirq import tk_to_cirq" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0: ───@───────H───X───Y───Z───────────────X───\n", " │ │\n", "1: ───@^0.5───X───@───Y───Z───────H───────┼───\n", " │ │\n", "2: ───Y───────────X───Z───@───────H───X───┼───\n", " │ │\n", "3: ───Z───────H───────────@^0.5───X───Y───@───\n" ] } ], "source": [ "print(tk_to_cirq(c))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(Note that Cirq cannot represent all gates diagrammatically.)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Via Latex" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can create a Latex document containing a diagram of the circuit using the `to_latex_file()` method. This uses the `quantikz` library. The document can be viewed on its own or the Latex can easily be copied and pasted into a larger document." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "c.to_latex_file(\"c.tex\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Commands" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can retrieve a list of operations comprising a circuit, each represented as a `Command` object:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[CU1(0.5) q[0], q[1];, Y q[2];, Z q[3];, H q[0];, X q[1];, H q[3];, X q[0];, CX q[1], q[2];, Y q[0];, Y q[1];, Z q[2];, Z q[0];, Z q[1];, CU1(0.5) q[2], q[3];, H q[1];, H q[2];, X q[3];, X q[2];, Y q[3];, CX q[3], q[0];]\n" ] } ], "source": [ "cmds = c.get_commands()\n", "print(cmds)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Each `Command` is defined by an operation and the qubits it operates on (as well as the classical bits and conditions, if any). For example:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CU1(0.5)\n", "[q[0], q[1]]\n" ] } ], "source": [ "cmd0 = cmds[0]\n", "op0 = cmd0.op\n", "print(op0)\n", "qubits0 = cmd0.args\n", "print(qubits0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "From the `Op` we can read off the string representation (in normal or Latex form), the parameters and the type:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'CU1(0.5)'" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "op0.get_name() # normal form" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'CU1(0.5)'" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "op0.get_name(latex=True) # Latex form" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(OpType.CU1, [0.5])" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "op0.type, op0.params" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that some compilation passes introduce implicit wire swaps at the end of the circuit, which are not represented in the command list. (The internal representation of the circuit as a directed acyclic graph reduces explicit permutations of qubits to implicit features of the graph.)" ] } ], "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.11.2" } }, "nbformat": 4, "nbformat_minor": 2 }