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": [
+ "
"
+ ]
+ },
+ {
+ "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
+}