{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%matplotlib inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n# Defining Custom Compilers\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Building a general compiler which is aware of all possible simplification rules would\nresult in an overly complex and rigid tool, and would be difficult to generalize for\nall hardware implementations. By leaving the compiler itself unadorned and allowing\nfor custom rules, very complex compilation instructions can be expressed in a simple\nand readable fashion.\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With this in mind, |True-Q|\\'s :py:class:`~trueq.compilation.Compiler` works by\napplying an ordered list of built-in and/or user-specified\n:py:class:`~trueq.compilation.base.Pass`\\es to a circuit in order.\n:py:class:`~trueq.compilation.base.Pass` objects define rules for how to decompose,\nreplace, or remove :py:class:`~trueq.Gate`\\s (or more generally,\n:py:class:`~trueq.Operation`\\s), while possibly also adding or removing\n:py:class:`~trueq.Cycle`\\s.\n\n## Getting started\n\nLet's start with a simple example that demonstrates the actions of a\ncompiler. Consider the circuit below:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import trueq as tq\n\ncircuit = tq.Circuit(\n cycles=[\n tq.Cycle({(0, 1): tq.Gate.cz}),\n tq.Cycle({0: tq.Gate.h, 1: tq.Gate.h}),\n tq.Cycle({(2, 1): tq.Gate.cx}),\n tq.Cycle({(0, 1): tq.Gate.cy, 2: tq.Gate.z}),\n ]\n)\ncircuit.draw()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We want to define a compiler that can replace certain cycles within this circuit\nwith different cycles, and furthermore merge any resulting additional single- and\ntwo-qudit operations. To do that, we define two types of passes: a\n:py:class:`~trueq.compilation.CycleReplacement` and a\n:py:class:`~trueq.compilation.Merge` pass:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "replace_pass = tq.compilation.CycleReplacement(\n target=tq.Cycle({(0, 1): tq.Gate.cz}),\n replacement=3 * [tq.Cycle({(0, 1): tq.Gate.cz})],\n)\nmerge_pass = tq.compilation.Merge(max_sys=2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The :py:class:`~trueq.compilation.CycleReplacement` replaces any occurrence of\nthe ``target`` cycle with the ``replacement`` cycle. In this case, our\n``replace_pass`` will replace ``tq.Gate.cz`` on qubits ``(0, 1)`` with three\nconsecutive ``tq.Gate.cz`` on qubits ``(0, 1)``. The\n:py:class:`~trueq.compilation.Merge` pass looks for any neighbouring gates which\nact on ``max_sys`` qudits with the same labels and merge them into a single gate.\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To see how those passes work in practice, let's define two different compilers:\none which applies only ``replace_pass`` and one which applies only ``merge_pass``.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "replace_compiler = tq.compilation.Compiler(passes=[replace_pass])\nmerge_compiler = tq.compilation.Compiler(passes=[merge_pass])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here is the output from the first compiler:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "compilation1 = replace_compiler.compile(circuit)\ncompilation1.draw()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that the compiler constructed with ``replace_pass`` replaced the\n``tq.Gate.cz`` with three consecutive ``tq.Gate.cz`` gates on qubits ``(0, 1)``, as\nexpected.\n\nNow let's apply our ``merge_compiler`` to this circuit:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "compilation2 = merge_compiler.compile(compilation1)\ncompilation2.draw()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that the compiler constructed using the ``merge_pass`` merged the first four\ncycles into a single cycle, as intended.\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Applying first our ``replace_compiler`` and then our ``merge_compiler`` has the effect\nof the cycle replacement pass and the cycle merging pass to be executed in that order.\n\nThe same can be achieved through a single compiler that we initialize with a list\ncontaining both passes:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "composite_compiler = tq.compilation.Compiler(passes=[replace_pass, merge_pass])\n\ncomposite_compilation = composite_compiler.compile(circuit)\ncomposite_compilation.draw()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Predefined Pass Lists\n\nIn addition to the passes shown above, there are several other useful\npass classes available as members of the\n:py:class:`~trueq.compilation.Compiler` class, for example:\n:py:attr:`~trueq.compilation.Compiler.HARDWARE_PASSES`\\,\n:py:attr:`~trueq.compilation.Compiler.SIMPLIFY_PASSES`\\,\n:py:attr:`~trueq.compilation.Compiler.RC_PASSES`\\, and\n:py:attr:`~trueq.compilation.Compiler.NATIVE2Q_PASSES`\\. Click on the links to their\ndocumentation for further details on each.\n\nNote that these pass classes need to be instantiated before they can be given to the\ncompiler constructor. For certain types of advanced usage, directly invoking the\nconstructor may be the preferred method. However, the compiler also has two static\ncovenience methods to automate pass instantiation:\n:py:meth:`~trueq.compilation.Compiler.from_config` and\n:py:meth:`~trueq.compilation.Compiler.basic`\\. The first of these uses a\n:py:class:`~trueq.Config` object to pass relevant gate factories to each pass, and the\nsecond is a further specialization that auto-instantiates these factories based on\na desired entangling gate and mode (which may not be relevant to all passes).\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# define a compiler to simplify circuits\ncompiler1 = tq.Compiler.basic(passes=tq.Compiler.SIMPLIFY_PASSES)\n\n# define a compiler that does randomized compiling\ncompiler2 = tq.Compiler.basic(passes=tq.Compiler.RC_PASSES)\n\n# define a compiler that decomposes two-qubit gates into the specified entangling gate,\n# then randomly compiles, and then converts all gates into hardware compatable gates.\n# for this example, we use the maximally entangling cross-resonance gate.\npasses = (\n tq.Compiler.NATIVE2Q_PASSES + tq.Compiler.RC_PASSES + tq.Compiler.HARDWARE_PASSES\n)\ncompiler3 = tq.Compiler.basic(entangler=tq.Gate.rp(\"ZX\", 90), passes=passes)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We demonstrate the last of these compilers by defining the following circuit:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "circuit = tq.Circuit(\n [{range(4): tq.Gate.h}, {(0, 1): tq.Gate.rp(\"ZZ\", 22), (2, 3): tq.Gate.cz}]\n).measure_all()\ncircuit" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We see that the compiled circuit contains only CNOT gates, Z rotations, and X90\nrotations. Adjacent single qubit gates have been merged together on each qubit.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "compiler3.compile(circuit)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Custom Pass Lists\nCustom lists of passes can be used if none of the predifined lists have the desired\nbehaviour. We can pass lists of these classes to\n:py:meth:`~trueq.compilation.Compiler.from_config` or\n:py:meth:`~trueq.compilation.Compiler.basic`\\, as discussed above, or we can use the\ncompiler constructor directly, as in the following example.\n\nFirst, we define a device configuration. Here, we use the Berkeley gate as the\nentangling operation.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "b = tq.Gate.from_generators(\"XX\", 90, \"YY\", 45)\nconfig = tq.Config(\n factories=[\n tq.config.GateFactory.from_matrix(\"B\", b),\n tq.config.GateFactory.from_hamiltonian(\"x90\", [[\"X\", 90]]),\n tq.config.GateFactory.from_hamiltonian(\"z\", [[\"Z\", \"phi\"]]),\n ],\n mode=\"ZXZXZ\",\n)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we define a compiler. Unlike,\n:py:attr:`~trueq.compilation.Compiler.HARDWARE_PASSES`\\, this compiler starts by\nmerging adjacent two-qubit gates. Also, we know that any two qubit gate can be\ndecomposed into two Berkeley gates interleaved with single qubit gates. We use\n:py:class:`tq.compilation.NativeDecomp` fixed at ``depth=2`` to force every\ntwo-qubit gate to decompose into two Berkeley gates, even if only one is required.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "decomposer = tq.compilation.NativeDecomp(depth=2, factories=config.factories)\ncompiler = tq.Compiler(\n [\n tq.compilation.Merge(max_sys=2),\n tq.compilation.Parallel(decomposer),\n tq.compilation.Merge(),\n tq.compilation.Native1Q(config.factories),\n tq.compilation.RemoveEmptyCycle(),\n ]\n)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we define a circuit to test this compiler on.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "circuit = tq.Circuit(\n [\n {range(4): tq.Gate.h},\n {(0, 1): tq.Gate.rp(\"ZZ\", 22)},\n {(0, 1): tq.Gate.cnot, (2, 3): tq.Gate.swap},\n ]\n).measure_all()\ncircuit" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The resulting compiled circuit is as follows.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "compiler.compile(circuit)" ] } ], "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.9.12" } }, "nbformat": 4, "nbformat_minor": 0 }