diff --git a/cirq-core/cirq/ops/raw_types.py b/cirq-core/cirq/ops/raw_types.py index d64c1fe923e..dd51abfdfa5 100644 --- a/cirq-core/cirq/ops/raw_types.py +++ b/cirq-core/cirq/ops/raw_types.py @@ -626,9 +626,11 @@ def with_classical_controls( """Returns a classically controlled version of this operation. An operation that is classically controlled is executed iff all - conditions evaluate to True. Currently the only condition type is a - measurement key. A measurement key evaluates to True iff any qubit in - the corresponding measurement operation evaluated to a non-zero value. + conditions evaluate to True. Conditions can be either a measurement key + or a user-specified `cirq.Condition`. A measurement key evaluates to + True iff any qubit in the corresponding measurement operation evaluated + to a non-zero value; `cirq.Condition` supports more complex, + user-defined conditions. If no conditions are specified, returns self. diff --git a/docs/classical_control.ipynb b/docs/classical_control.ipynb new file mode 100644 index 00000000000..bdfefbaabc9 --- /dev/null +++ b/docs/classical_control.ipynb @@ -0,0 +1,311 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "b952a1c0faad" + }, + "outputs": [], + "source": [ + "#@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", + "# https://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "3556e78efd03" + }, + "source": [ + "# Classical control" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "925dbb45c75e" + }, + "source": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " View on QuantumAI\n", + " \n", + " Run in Google Colab\n", + " \n", + " View source on GitHub\n", + " \n", + " Download notebook\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "d4c447ddd24e" + }, + "outputs": [], + "source": [ + "try:\n", + " import cirq\n", + "except ImportError:\n", + " print(\"installing cirq...\")\n", + " !pip install --quiet cirq\n", + " import cirq\n", + " print(\"installed cirq.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "8ccb64c25e3a" + }, + "source": [ + "While some quantum algorithms can be defined entirely at the quantum level, there are many others (notably including [teleportation](/cirq/tutorials/educators/textbook_algorithms#quantum_teleportation) and [error correction](https://www.nature.com/articles/s41586-021-03588-y)) which rely on classical measurement results from one part of the algorithm to control operations in a later section.\n", + "\n", + "To represent this, Cirq provides the `ClassicallyControlledOperation`. Following the pattern of controlled operations, a classically-controlled version of any `Operation` can be constructed by calling its `with_classical_controls` method with the control condition(s)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "b3ed39be4c06" + }, + "source": [ + "## Basic conditions\n", + "\n", + "In the example below, `X` will only be applied to `q1` if the previous measurement \"a\" returns a 1. More generally, providing some string `\"cond\"` to `with_classical_controls` creates a `ClassicallyControlledOperation` with a `KeyCondition` whose key is `\"cond\"`. A `KeyCondition` will only trigger and apply the operation it controls if a preceding measurement with the same key measured one or more qubits in the $|1\\rangle$ state." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "df3dd6e3b308" + }, + "outputs": [], + "source": [ + "q0, q1, q2 = cirq.LineQubit.range(3)\n", + "circuit = cirq.Circuit(\n", + " cirq.H(q0),\n", + " cirq.measure(q0, key='a'),\n", + " cirq.X(q1).with_classical_controls('a'),\n", + ")\n", + "print(circuit)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "4e416431b695" + }, + "source": [ + "Using just these conditions, we can construct the [quantum teleportation](/cirq/tutorials/educators/textbook_algorithms#quantum_teleportation) circuit:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "01ccc99c6a3c" + }, + "outputs": [], + "source": [ + "# Teleports `_message` from Alice to Bob.\n", + "alice = cirq.NamedQubit('alice')\n", + "bob = cirq.NamedQubit('bob')\n", + "message = cirq.NamedQubit('_message')\n", + "circuit = cirq.Circuit(\n", + " # Create Bell state to be shared between Alice and Bob.\n", + " cirq.H(alice),\n", + " cirq.CNOT(alice, bob),\n", + " # Create the message.\n", + " cirq.X(message) ** 0.371,\n", + " cirq.Y(message) ** 0.882,\n", + " # Bell measurement of the message and Alice's entangled qubit.\n", + " cirq.CNOT(message, alice),\n", + " cirq.H(message),\n", + " cirq.measure(message, key='M'),\n", + " cirq.measure(alice, key='A'),\n", + " # Uses the two classical bits from the Bell measurement to recover the\n", + " # original quantum message on Bob's entangled qubit.\n", + " cirq.X(bob).with_classical_controls('A'),\n", + " cirq.Z(bob).with_classical_controls('M'),\n", + ")\n", + "print(circuit)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "3c64d94110ba" + }, + "source": [ + "## Sympy conditions\n", + "\n", + "Cirq also supports more complex control conditions: providing some `sympy` expression `\"expr\"` to `with_classical_controls` creates a `ClassicallyControlledOperation` with a `SympyCondition`. That condition will only trigger if `\"expr\"` evaluates to a \"truthy\" value (`bool(expr) == True`), and uses measurement results to resolve any variables in the expression.\n", + "\n", + "In this example, `X` will only be applied to `q2` if `a == b`; in other words, $|q_0q_1\\rangle$ must be either $|00\\rangle$ or $|11\\rangle$." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "9a7ff41b51c0" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "a, b, c = sympy.symbols('a b c')\n", + "sympy_cond = sympy.Eq(a, b)\n", + "circuit = cirq.Circuit(\n", + " cirq.H.on_each(q0, q1),\n", + " cirq.measure(q0, key='a'),\n", + " cirq.measure(q1, key='b'),\n", + " cirq.X(q2).with_classical_controls(sympy_cond)\n", + ")\n", + "print(circuit)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "dfb58a6f479c" + }, + "source": [ + "## Combining conditions\n", + "\n", + "Multiple conditions of either type can be specified to `with_classical_controls`, in which case the resulting `ClassicallyControlledOperation` will only trigger if _all_ conditions trigger. Similarly, calling `with_classical_controls` on an existing `ClassicallyControlledOperation` will require all new and pre-existing conditions to trigger for the operation to trigger." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "8be2002669fc" + }, + "outputs": [], + "source": [ + "sympy_cond = sympy.Eq(a, 0)\n", + "circuit = cirq.Circuit(\n", + " cirq.H.on_each(q0, q1, q2),\n", + " cirq.measure(q0, q1, key='a'),\n", + " cirq.measure(q2, key='b'),\n", + " cirq.X(q0).with_classical_controls('b', sympy_cond),\n", + " cirq.CZ(q1, q2).with_classical_controls('b').with_classical_controls(sympy_cond),\n", + ")\n", + "print(circuit)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "34d0fe9226e1" + }, + "source": [ + "## Variable scope\n", + "\n", + "When used with `CircuitOperation`, classically controlled operations will be resolved using local repetition IDs, if any. This is the only way to create a non-global variable scope within a circuit. A simple example of this is shown below, where the controls inside and outside a subcircuit rely on measurements in their respective scopes:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "6a7441827bd6" + }, + "outputs": [], + "source": [ + "subcircuit = cirq.FrozenCircuit(\n", + " cirq.measure(q0, key='a'), cirq.X(q0).with_classical_controls('a')\n", + ")\n", + "circuit = cirq.Circuit(\n", + " cirq.measure(q0, key='a'),\n", + " cirq.CircuitOperation(subcircuit, repetitions=2),\n", + " cirq.X(q0).with_classical_controls('a')\n", + ")\n", + "print(circuit)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "b0807e8edb7f" + }, + "source": [ + "More complex scoping behavior is described in the [classically controlled operation tests](https://github.com/quantumlib/Cirq/blob/master/cirq-core/cirq/ops/classically_controlled_operation_test.py)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "520a5bbbea93" + }, + "source": [ + "## Using with transformers\n", + "\n", + "Cirq [transformers](transformers.ipynb) are aware of classical control and will avoid changes which move a control before its corresponding measurement. Additionally, for some simple cases the [`defer_measurements` transformer](https://github.com/daxfohl/Cirq/blob/e68ff85e9bb0c7373572cdc212c10f226cd40b0f/cirq-core/cirq/transformers/measurement_transformers.py#L58) can convert a classically-controlled circuit into a purely-quantum circuit:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "e7bda8edb27a" + }, + "outputs": [], + "source": [ + "circuit = cirq.Circuit(\n", + " cirq.measure(q0, key='a'),\n", + " cirq.X(q1).with_classical_controls('a'),\n", + " cirq.measure(q1, key='b'),\n", + ")\n", + "deferred = cirq.defer_measurements(circuit)\n", + "print(\"Original circuit:\")\n", + "print(circuit)\n", + "print(\"Measurement deferred:\")\n", + "print(deferred)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "48666318febe" + }, + "source": [ + "## Compatibility\n", + "\n", + "The Cirq built-in simulators provide support for classical control, but caution should be exercised when exporting these circuits to other environments. `ClassicallyControlledOperation` is fundamentally different from other operations in that it requires access to the measurement results, and simulators or hardware that does not explicitly support this will not be able to run `ClassicallyControlledOperation`s." + ] + } + ], + "metadata": { + "colab": { + "name": "classical_control.ipynb", + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +}