diff --git a/docs/transform.ipynb b/docs/transform.ipynb index fb9058723bf..09ba089b619 100644 --- a/docs/transform.ipynb +++ b/docs/transform.ipynb @@ -1,14 +1,5 @@ { "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "cedf868076a2" - }, - "source": [ - "##### Copyright 2020 The Cirq Developers" - ] - }, { "cell_type": "code", "execution_count": null, @@ -18,7 +9,8 @@ }, "outputs": [], "source": [ - "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "#@title Copyright 2022 The Cirq Developers\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", "# you may not use this file except in compliance with the License.\n", "# You may obtain a copy of the License at\n", "#\n", @@ -37,7 +29,7 @@ "id": "8bbd73c03ac2" }, "source": [ - "# Transforming circuits" + "# Installing Cirq" ] }, { @@ -75,6 +67,7 @@ "except ImportError:\n", " print(\"installing cirq...\")\n", " !pip install --quiet cirq\n", + " import cirq\n", " print(\"installed cirq.\")" ] }, @@ -84,11 +77,285 @@ "id": "9d3d49b9ca2a" }, "source": [ - "## Circuit optimizers\n", + "# What is a Transformer?\n", + "A transformer in Cirq is any callable, that satisfies the `cirq.TRANSFORMER` API, and *transforms* an input circuit into an output circuit.\n", + "\n", + "Circuit transformations are often necessary to compile a user-defined circuit to an equivalent circuit, which can be executed on a specific device or simulator. The compilation process often involves steps like:\n", + "- Gate Decompositions: Rewrite the circuit using only gates that belong to the device target gateset, i.e. set of gates which the device can execute. \n", + "- Qubit Mapping and Routing: Map the logic qubits in the input circuit to physical qubits on the device and insert appropriate swap operations such that final circuit respects the hardware topology. \n", + "- Circuit Optimizations: Perform hardware specific optimizations, like merging and replacing connected components of 1 and 2 operations with more efficient rewrites, commuting Z gates through the circuit, aligning gates in moments etc.\n", + "\n", + "\n", + "Cirq provides many out-of-the-box transformers which can be used as individual compilation passes and also provides a general framework for users to create their own transformers using powerful primitives and bundle a bunch of transformers together to enable compiling circuits for specific targets. " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "KUor1pGZi0Iw" + }, + "source": [ + "# Built-in Transformers in Cirq" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "861ea1ada088" + }, + "source": [ + "## Overview\n", + "Transformers that come with cirq can be found in the `cirq.transformers` package.\n", + "\n", + "A few notable examples are:\n", + "* **cirq.align_left / cirq.align_right**: Align gates to the left/right of the circuit.\n", + "* **cirq.defer_measurements**: Moves all (non-terminal) measurements in a circuit to the end of circuit by implementing the deferred measurement principle.\n", + "* **cirq.drop_empty_moments** / **cirq.drop_negligible_operations**: Removes moments that are empty or operations that have very small effects, respectively.\n", + "* **cirq.eject_phased_paulis**: Pushes X, Y, and PhasedX gates towards the end of the circuit, potentially absorbing Z gates and modifying gates along the way.\n", + "* **cirq.eject_z**: Pushes Z gates towards the end of the circuit, potentially adjusting phases of gates that they pass through.\n", + "* **cirq.expand_composite**: Uses `cirq.decompose` to expand composite gates.\n", + "* **cirq.merge_k_qubit_unitaries**: Replaces connected components of unitary operations, acting on <= k qubits, with op-tree given by `rewriter(circuit_op)`.\n", + "* **cirq.optimize_for_target_gateset**: Attempts to convert a circuit into and equivalent circuit using only gates from a given target gateset.\n", + "* **cirq.stratified_circuit**: Repacks the circuit to ensure that moments only contain operations from the same category.\n", + "* **cirq.synchronize_terminal_measurements**: Moves all terminal measurements in a circuit to the final moment, if possible.\n", + "\n", + "\n", + "Below you can see how to implement a transformer pipeline called `optimize_circuit`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "AqTylIpssdex" + }, + "outputs": [], + "source": [ + "def optimize_circuit(circuit, context = None, k=2):\n", + " # Merge 2-qubit connected components into circuit operations.\n", + " optimized_circuit = cirq.merge_k_qubit_unitaries(circuit, k=k, rewriter=lambda op: op.with_tags(\"merged\"), context=context)\n", + "\n", + " # Drop operations with negligible effect / close to identity.\n", + " optimized_circuit = cirq.drop_negligible_operations(optimized_circuit, context=context)\n", + "\n", + " # Expand all remaining merged connected components. \n", + " optimized_circuit = cirq.expand_composite(optimized_circuit, no_decomp=lambda op: \"merged\" not in op.tags, context=context)\n", + "\n", + " # Synchronize terminal measurements to be in the same moment. \n", + " optimized_circuit = cirq.synchronize_terminal_measurements(optimized_circuit, context=context)\n", + "\n", + " # Assert the original and optimized circuit are equivalent.\n", + " cirq.testing.assert_circuits_with_terminal_measurements_are_equivalent(circuit, optimized_circuit)\n", + " \n", + " return optimized_circuit\n", + "\n", + "q = cirq.LineQubit.range(3)\n", + "circuit = cirq.Circuit(\n", + " cirq.H(q[1]), cirq.CNOT(*q[1:]), \n", + " cirq.H(q[0]), cirq.CNOT(*q[:2]), cirq.H(q[1]), \n", + " cirq.CZ(*q[:2]), cirq.H.on_each(*q[:2]),\n", + " cirq.CNOT(q[2], q[0]),\n", + " cirq.measure_each(*q)\n", + ")\n", + "print(\"Original Circuit:\", circuit, sep=\"\\n\")\n", + "print(\"Optimized Circuit:\", optimize_circuit(circuit), sep=\"\\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "mu83U5hGzNcH" + }, + "source": [ + "## Inspecting transformer actions\n", + "\n", + "Every transformer in Cirq accepts a `cirq.TransformerContext` instance, which stores common configurable options useful for all transformers. \n", + "\n", + "One of the members of transformer context dataclass is `cirq.TransformerLogger` instance. When a logger instance is specified, every cirq transformer logs it's action on the input circuit using the given logger instance. The logs can then be inspected to understand the action of each individual transformer on the circuit. \n", + "\n", + "Below, you can inspect the action of each transformer in the `optimize_circuit` method defined above. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "1cqUa2Kc0Hni" + }, + "outputs": [], + "source": [ + "context = cirq.TransformerContext(logger=cirq.TransformerLogger())\n", + "optimized_circuit = optimize_circuit(circuit, context)\n", + "context.logger.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "kLm-5LHr0YAd" + }, + "source": [ + "## Support for no-compile tags. \n", + "\n", + "Cirq also supports tagging operations with no-compile tags such that these tagged operations are ignored when applying transformations on the circuit. This allows users to gain more fine-grained conrol over the compilation process. \n", + "\n", + "Any valid tag can be used as a \"no-compile\" tag by adding it to the `tags_to_ignore` field in `cirq.TransformerContext`. When called with a context, cirq transformers will inspect the `context.tags_to_ignore` field and ignore an operation if `op.tags & context.tags_to_ignore` is not empty. \n", + "\n", + "Below, you can use no-compile tags when transforming a circuit using the `optimize_circuit` mehod defined above." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "HEhXziehR29V" + }, + "outputs": [], + "source": [ + "# Echo pulses inserted in the circuit to prevent dephasing during idling should be ignored.\n", + "circuit = cirq.Circuit(\n", + " cirq.H(q[0]), cirq.CNOT(*q[:2]),\n", + " [op.with_tags(\"spin_echoes\") for op in [cirq.X(q[0]) ** 0.5, cirq.X(q[0])** -0.5]],\n", + " [cirq.CNOT(*q[1:]), cirq.CNOT(*q[1:])],\n", + " [cirq.CNOT(*q[:2]), cirq.H(q[0])],\n", + " cirq.measure_each(*q),\n", + ")\n", + "# Original Circuit\n", + "print(\"Original Circuit:\", circuit, \"\\n\", sep=\"\\n\")\n", + "\n", + "# Optimized Circuit without tags_to_ignore\n", + "print(\"Optimized Circuit without specifying tags_to_ignore:\")\n", + "print(optimize_circuit(circuit, k=1), \"\\n\")\n", + "\n", + "# Optimized Circuit ignoring operations marked with tags_to_ignore. \n", + "print(\"Optimized Circuit while ignoring operations marked with tags_to_ignore:\")\n", + "context=cirq.TransformerContext(tags_to_ignore=[\"spin_echoes\"])\n", + "print(optimize_circuit(circuit, k=1, context=context), \"\\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "3f3c7851a571" + }, + "source": [ + "## Support for recursively transforming sub-circuits.\n", + "\n", + "By default, an operation `op` of type `cirq.CircuitOperation` is considered as a single top-level operation by cirq transformers. As a result, the sub-circuits wrapped inside circuit operations will often be left as it is and a transformer will only modify the top-level circuit. \n", + "\n", + "If you wish to recursively run a transformer on every nested sub-circuit wrapped inside a `cirq.CircuitOperation`, you can set `context.deep=True` in the `cirq.TransformerContext` object. Note that tagged circuit operations marked with any of `context.tags_to_ignore` will be ignored even if `context.deep is True`. See the example below for a better understanding. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "994ffb523487" + }, + "outputs": [], + "source": [ + "q = cirq.LineQubit.range(2)\n", + "circuit_op = cirq.CircuitOperation(\n", + " cirq.FrozenCircuit(\n", + " cirq.I.on_each(*q),\n", + " cirq.CNOT(*q),\n", + " cirq.I(q[0]).with_tags(\"ignore\"),\n", + " )\n", + ")\n", + "circuit = cirq.Circuit(\n", + " cirq.I(q[0]), cirq.I(q[1]).with_tags(\"ignore\"),\n", + " circuit_op,\n", + " circuit_op.with_tags(\"ignore\"),\n", + ")\n", + "print(\"Original Circuit:\", circuit, \"\\n\", sep=\"\\n\\n\")\n", + "\n", + "context = cirq.TransformerContext(tags_to_ignore=[\"ignore\"], deep=True)\n", + "print(\"Optimized Circuit with deep=True and tags_to_ignore=['ignore']:\\n\")\n", + "print(cirq.drop_negligible_operations(circuit, context=context), \"\\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "iIHUZvLRlHFj" + }, + "source": [ + "## Compiling to NISQ targets: `cirq.CompilationTargetGateset`\n", + "Cirq's philosophy on compiling circuits for execution on a NISQ target device or simulator is that it would often require running only a handful of individual compilation passes on the input circuit, one after the other. \n", + "\n", + "**`cirq.CompilationTargetGateset`** is an abstraction in Cirq to represent such compilation targets as well as the bundles of transformer passes which should be executed to compile a circuit to this target. Cirq has implementations for common target gatesets like `cirq.CZTargetGateset`, `cirq.SqrtIswapTargetGateset` etc.\n", + "\n", + "\n", + "**`cirq.optimize_for_target_gateset`** is a transformer which compiles a given circuit for a `cirq.CompilationTargetGateset` via the following steps:\n", "\n", - "Cirq comes with the concept of an optimizer. Optimizers will pass over a circuit and perform tasks that will modify the circuit in place. These can be used to transform a circuit in specific ways, such as combining single-qubit gates, commuting Z gates through the circuit, or readying the circuit for certain hardware or gate set configurations.\n", + "1. Run all `gateset.preprocess_transformers`\n", + "2. Convert operations using built-in `cirq.decompose` + `gateset.decompose_to_target_gateset`.\n", + "3. Run all `gateset.postprocess_transformers`\n", "\n", - "Optimizers will have a function `optimize_circuit()` that can be used to perform this optimization. Here is a simple example that removes empty moments:" + "\n", + "The preprocess transformers often includes optimizations like merging connected components of 1/2 qubit unitaries into a single unitary matrix, which can then be replaced with an efficient analytical decomposition as part of step-2. \n", + "\n", + "The post-process transformers often includes cleanups and optimizations like dropping negligible operations, \n", + "converting single qubit rotations into desired form, circuit alignments etc. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "-QFPCdW0qS3R" + }, + "outputs": [], + "source": [ + "# Original QFT Circuit on 3 qubits.\n", + "q = cirq.LineQubit.range(3)\n", + "circuit = cirq.Circuit(cirq.QuantumFourierTransformGate(3).on(*q), cirq.measure(*q))\n", + "print(\"Original Circuit:\", circuit, \"\\n\", sep=\"\\n\")\n", + "\n", + "# Compile the circuit for CZ Target Gateset.\n", + "gateset = cirq.CZTargetGateset(allow_partial_czs=True)\n", + "cz_circuit = cirq.optimize_for_target_gateset(circuit, gateset = gateset)\n", + "cirq.testing.assert_circuits_with_terminal_measurements_are_equivalent(circuit, cz_circuit)\n", + "print(\"Circuit compiled for CZ Target Gateset:\", cz_circuit, \"\\n\", sep=\"\\n\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "140330585db5" + }, + "outputs": [], + "source": [ + "# `cirq.optimize_for_target_gateset` also supports all the features discussed above, using `cirq.TransformerContext`.\n", + "# For example, you can compile the circuit for Sqrt-Iswap Target Gateset and inspect action of individual transformers.\n", + "context = cirq.TransformerContext(logger=cirq.TransformerLogger())\n", + "gateset = cirq.SqrtIswapTargetGateset()\n", + "sqrt_iswap_circuit = cirq.optimize_for_target_gateset(circuit, gateset = gateset, context=context)\n", + "cirq.testing.assert_circuits_with_terminal_measurements_are_equivalent(circuit, sqrt_iswap_circuit)\n", + "context.logger.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "jyNdxrkI4CYX" + }, + "source": [ + "# Building custom Transformers" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "6da0Ra7bz2St" + }, + "source": [ + "## `cirq.TRANSFORMER` API and `@cirq.transformer` decorator.\n", + "\n", + "Any callable that satisfies the `cirq.TRANSFORMER` contract, i.e. takes a `cirq.AbstractCircuit` and `cirq.TransformerContext` and returns a transformed `cirq.AbstractCircuit`, is a valid transformer in Cirq. \n", + "\n", + "You can create a custom transformer by simply decorating a class/method, that satisfies the above contract, with `@cirq.transformer` decorator. \n" ] }, { @@ -99,78 +366,258 @@ }, "outputs": [], "source": [ - "import cirq\n", - "c=cirq.Circuit()\n", - "c.append(cirq.Moment([]))\n", - "c.append(cirq.Moment([cirq.X(cirq.GridQubit(1,1))]))\n", - "c.append(cirq.Moment([]))\n", - "print(f'Before optimization, Circuit has {len(c)} moments')\n", + "@cirq.transformer\n", + "def reverse_circuit(circuit, *, context = None):\n", + " \"\"\"Transformer to reverse the input circuit.\"\"\"\n", + " return circuit[::-1]\n", "\n", - "cirq.DropEmptyMoments().optimize_circuit(circuit=c)\n", - "print(f'After optimization, Circuit has {len(c)} moments')" + "\n", + "@cirq.transformer\n", + "class SubstituteGate:\n", + " \"\"\"Transformer to substitute `source` gates with `target` in the input circuit.\"\"\"\n", + " def __init__(self, source, target):\n", + " self._source = source\n", + " self._target = target\n", + "\n", + " def __call__(self, circuit, *, context = None):\n", + " batch_replace = []\n", + " for i, op in circuit.findall_operations(lambda op: op.gate == self._source):\n", + " batch_replace.append((i, op, self._target.on(*op.qubits)))\n", + " transformed_circuit = circuit.unfreeze(copy=True)\n", + " transformed_circuit.batch_replace(batch_replace)\n", + " return transformed_circuit\n", + "\n", + "q = cirq.NamedQubit(\"q\")\n", + "circuit = cirq.Circuit(cirq.X(q), cirq.CircuitOperation(cirq.FrozenCircuit(cirq.X(q), cirq.Y(q))), cirq.Z(q))\n", + "substitute_gate = SubstituteGate(cirq.X, cirq.S)\n", + "print(\"Original Circuit:\", circuit, \"\\n\", sep=\"\\n\")\n", + "print(\"Reversed Circuit:\", reverse_circuit(circuit), \"\\n\", sep=\"\\n\")\n", + "print(\"Substituted Circuit:\", substitute_gate(circuit), sep=\"\\n\")" ] }, { "cell_type": "markdown", "metadata": { - "id": "861ea1ada088" + "id": "OLJrXdyTCGev" }, "source": [ - "Optimizers that come with cirq can be found in the `cirq.optimizers` package.\n", + "## `cirq.TransformerContext` to store common configurable options. \n", + "`cirq.TransformerContext` is a dataclass that stores common configurable options for all transformers. All cirq transformers should accept the transformer context as an optional keyword argument. \n", + "\n", + "The `@cirq.transformer` decorator inspects the `cirq.TransformerContext` argument and automatically appends useful functionality like support for automated logging and recursively running the transformer on nested sub-circuits. \n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "5OuMLlNAFPcW" + }, + "source": [ + "### `cirq.TransformerLogger` and support for automated logging.\n", + "The `cirq.TransformerLogger` class is used to log the actions of a transformer on an input circuit. `@cirq.transformer` decorator automatically adds support for logging the initial and final circuits for each transfomer step. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "1-1r6Ha9F1c3" + }, + "outputs": [], + "source": [ + "context = cirq.TransformerContext(logger=cirq.TransformerLogger())\n", + "transformed_circuit = reverse_circuit(circuit, context=context)\n", + "transformed_circuit = substitute_gate(transformed_circuit, context=context)\n", + "context.logger.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "UvFdvxQ6MZD9" + }, + "source": [ + "### Support for `deep=True`.\n", + "You can call `@cirq.transformer(add_deep_support=True)` to automatically add the functionality of recursively running the custom transformer on circuits wrapped inside `cirq.CircuitOperation`. The recursive execution behavior of the transformer can then be controlled by setting `deep=True` in the transformer context. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "BobP3UFBJhP1" + }, + "outputs": [], + "source": [ + "@cirq.transformer(add_deep_support=True)\n", + "def reverse_circuit_deep(circuit, *, context = None):\n", + " \"\"\"Transformer to reverse the input circuit.\"\"\"\n", + " return circuit[::-1]\n", "\n", - "A few notable examples are:\n", "\n", - "* **ConvertToCzAndSingleGates**: Attempts to convert a circuit into CZ gates and single qubit gates. This uses gate's unitary and decompose methods to transform them into CZ + single qubit gates.\n", - "* **DropEmptyMoments** / **DropNegligible**: Removes moments that are empty or have very small effects, respectively.\n", - "* **EjectPhasedPaulis**: Pushes X, Y, and PhasedX gates towards the end of the circuit, potentially absorbing Z gates and modifying gates along the way.\n", - "* **EjectZ**: Pushes Z gates towards the end of the circuit, potentially adjusting phases of gates that they pass through.\n", - "* **ExpandComposite**: Uses `cirq.decompose` to expand composite gates.\n", - "* **MergeInteractions**: Combines series of adjacent one and two-qubit gates acting on a pair of qubits.\n", - "* **MergeSingleQubitGates**: Combines series of adjacent unitary 1-qubit operations\n", - "* **SynchronizeTerminalMeasurements**: Moves all measurements in a circuit to the final moment if possible.\n" + "@cirq.transformer(add_deep_support=True)\n", + "class SubstituteGateDeep(SubstituteGate):\n", + " \"\"\"Transformer to substitute `source` gates with `target` in the input circuit.\"\"\"\n", + " pass\n", + "\n", + "context = cirq.TransformerContext(deep=True)\n", + "substitute_gate_deep = SubstituteGateDeep(cirq.X, cirq.S)\n", + "print(\"Original Circuit:\", circuit, \"\\n\", sep=\"\\n\")\n", + "print(\"Reversed Circuit with deep=True:\", reverse_circuit_deep(circuit, context=context), \"\\n\", sep=\"\\n\")\n", + "print(\"Substituted Circuit with deep=True:\", substitute_gate_deep(circuit, context=context), sep=\"\\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "bDizgWMmPLq0" + }, + "source": [ + "# Transformer Primitives and Decompositions\n" ] }, { "cell_type": "markdown", "metadata": { - "id": "c6c7e3ed57ba" + "id": "sXbcG1VdPRkm" }, "source": [ - "### Create your own optimizers\n", + "## Moment preserving transformer primitives\n", "\n", - "You can create your own optimizers to transform and modify circuits to fit hardware, gate sets, or other requirements. Optimizers can also be used to generate noise. See [noise](noise.ipynb) for details.\n", + "Cirq provides useful abstractions to implement common transformer patterns, while preserving the moment structure of input circuit. Some of the notable transformer primitives are:\n", "\n", - "You can do this by implementing the function `optimize_circuit`.\n", + "- **`cirq.map_operations`**: Applies local transformations on operations, by calling `map_func(op)` for each `op`.\n", + "- **`cirq.map_moments`**: Applies local transformation on moments, by calling `map_func(m)` for each moment `m`.\n", + "- **`cirq.merge_operations`**: Merges connected component of operations by calling `merge_func(op1, op2)` on iteratively for every mergeable operation `op1` and `op2`.\n", + "- **`cirq.merge_moments`**: Merges adjacent moments, from left to right, by calling `merge_func(m1, m2)`.\n", "\n", - "If your optimizer is a local optimizer and depends primarily on operator being examined, you can alternatively inherit `cirq.PointOptimizer` and implement the function `optimization_at(self, circuit, index, op)` that optimizes a single operation.\n", "\n", - "Below is an example of implementing a simple `PointOptimizer` that removes measurements." + "An important property of these primitives is that they have support for common configurable options present in `cirq.TransformerContext`, such as `tags_to_ignore` and `deep`. See the example below for a better understanding." ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "id": "e046ef24c70e" + "id": "BCXc94owfIJh" }, "outputs": [], "source": [ - "class RemoveMeasurements(cirq.PointOptimizer):\n", - " def optimization_at(self, circuit: cirq.Circuit, index: int, op: cirq.Operation):\n", - " if isinstance(op.gate, cirq.MeasurementGate):\n", - " return cirq.PointOptimizationSummary(clear_span=1,\n", - " new_operations=[],\n", - " clear_qubits=op.qubits)\n", - " else:\n", - " return None\n", - "\n", - "q=cirq.LineQubit(0)\n", - "c=cirq.Circuit(cirq.X(q), cirq.measure(q))\n", - "print('Before optimization')\n", - "print(c)\n", - "RemoveMeasurements().optimize_circuit(c)\n", - "print('After optimization')\n", - "print(c)\n" + "@cirq.transformer\n", + "def substitute_gate_using_primitives(circuit, *, context = None, source = cirq.X, target= cirq.S):\n", + " \"\"\"Transformer to substitute `source` gates with `target` in the input circuit.\n", + " \n", + " The transformer is implemented using `cirq.map_operations` primitive and hence\n", + " has built-in support for \n", + " 1. Recursively running the transformer on sub-circuits if `context.deep is True`.\n", + " 2. Ignoring operations tagged with any of `context.tags_to_ignore`. \n", + " \"\"\"\n", + " return cirq.map_operations(\n", + " circuit, \n", + " map_func=lambda op, _: target.on(*op.qubits) if op.gate == source else op,\n", + " deep = context.deep if context else False,\n", + " tags_to_ignore=context.tags_to_ignore if context else ()\n", + " )\n", + "\n", + "x_y_x = [cirq.X(q), cirq.Y(q), cirq.X(q).with_tags(\"ignore\")]\n", + "circuit = cirq.Circuit(x_y_x, cirq.CircuitOperation(cirq.FrozenCircuit(x_y_x)), x_y_x)\n", + "context = cirq.TransformerContext(deep=True, tags_to_ignore=(\"ignore\",))\n", + "print(\"Original Circuit:\", circuit, \"\\n\", sep=\"\\n\")\n", + "print(\"Substituted Circuit:\", substitute_gate_using_primitives(circuit, context=context), \"\\n\", sep=\"\\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "NzTPE_wGjWkJ" + }, + "source": [ + "## Analytical Gate Decompositions\n", + "\n", + "Gate decomposition is the process of implementing / decomposing a given unitary `U` using only gates that belong to a specific target gateset. \n", + "\n", + "Cirq provides many analytical decomposition methods, often based on [KAK Decomposition](https://arxiv.org/abs/quant-ph/0507171), to decompose two qubit unitaries into specific target gatesets. Some notable decompositions are:\n", + "\n", + "* **`cirq.single_qubit_matrix_to_pauli_rotations`**: Decomposes a single qubit matrix to ZPow/XPow/YPow rotations. \n", + "* **`cirq.single_qubit_matrix_to_phased_x_z`**: Decomposes a single-qubit matrix to a PhasedX and Z gate.\n", + "* **`cirq.two_qubit_matrix_to_sqrt_iswap_operations`**: Decomposes any two-qubit unitary matrix into ZPow/XPow/YPow/sqrt-iSWAP gates.\n", + "* **`cirq.two_qubit_matrix_to_cz_operations`**: Decomposes any two-qubit unitary matrix into ZPow/XPow/YPow/CZ gates.\n", + "* **`cirq.three_qubit_matrix_to_operations`**: Decomposes any three-qubit unitary matrix into CZ/CNOT and single qubit rotations.\n", + "\n", + "\n", + "\n", + "You can use these analytical decomposition methods to build transformers which can rewrite a given circuit using only gates from the target gateset. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Pb7sXwEFcIxB" + }, + "outputs": [], + "source": [ + "@cirq.transformer\n", + "def convert_to_cz_target(circuit, *, context=None, atol=1e-8, allow_partial_czs=True):\n", + " \"\"\"Transformer to rewrite the given circuit using CZs + 1-qubit rotations.\n", + "\n", + " Note that the transformer decomposes only operations on <= 2-qubits and is\n", + " presented as an illustration of using transformer primitives + analytical \n", + " decomposition methods. \n", + " \"\"\"\n", + " def map_func(op: cirq.Operation, _) -> cirq.OP_TREE:\n", + " if not (cirq.has_unitary(op) and cirq.num_qubits(op) <= 2):\n", + " return op\n", + " mat = cirq.unitary(op)\n", + " q = op.qubits\n", + " if cirq.num_qubits(op) == 1:\n", + " g = cirq.single_qubit_matrix_to_phxz(mat)\n", + " return g.on(*q) if g else []\n", + " return cirq.two_qubit_matrix_to_cz_operations(*q, mat, allow_partial_czs=allow_partial_czs, atol=atol)\n", + " \n", + " return cirq.map_operations_and_unroll(\n", + " circuit, \n", + " map_func,\n", + " deep=context.deep if context else False,\n", + " tags_to_ignore=context.tags_to_ignore if context else ()\n", + " )\n", + "circuit = cirq.testing.random_circuit(qubits=3, n_moments=5, op_density=0.8, random_state=1234)\n", + "converted_circuit = convert_to_cz_target(circuit)\n", + "cirq.testing.assert_circuits_with_terminal_measurements_are_equivalent(circuit, converted_circuit)\n", + "print(f\"Original Circuit\", circuit, \"\\n\", sep=\"\\n\")\n", + "print(f\"Circuit compiled for CZ Target Gateset using custom transformer\", converted_circuit, \"\\n\", sep=\"\\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "jE7XXZs7bmTJ" + }, + "source": [ + "## Heuristic Gate Decompositions\n", + "Cirq also provides heuristic methods for decomposing any two qubit unitary matrix in terms of any specified two qubit target unitary + single qubit rotations. These methods are useful when accurate analytical decompositions for the target unitary are not known or when gate decomposition fidelity (i.e. accuracy of decomposition) can be traded off against decomposition depth (i.e. number of 2q gates in resulting decomposition) to achieve a higher overall gate fidelity. \n", + "\n", + "\n", + "See the following resources for more details on heuristic gate decomposition:\n", + "\n", + "* **`cirq.two_qubit_gate_product_tabulation`**\n", + "* **https://arxiv.org/pdf/2106.15490.pdf**" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "aTNKFcPesNpy" + }, + "source": [ + "# Summary\n", + "Cirq provides a flexible and powerful framework to\n", + "* Use built-in transformer primitives and analytical tools to create powerful custom transformers AND \n", + "* Easily integrate custom transformers with built-in infrastructure to augment functionality like automated logging, recursive execution on sub-circuits, support for no-compile tags etc.\n", + "\n", + "Cirq also provides a plethora of built-in transformers which can be composed together into useful abstractions, like `cirq.CompilationTargetGateset`, which in-turn can be serialized and can be used as a parameter in larger compilation pipelines and experiment workflows. \n", + "\n", + "Try using these transformers to compile your circuits and refer to the API reference docs of [`cirq.transformers`](https://quantumai.google/reference/python/cirq/transformers) for more details. " ] } ],