{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:46:54.537984Z", "iopub.status.busy": "2024-08-16T09:46:54.537389Z", "iopub.status.idle": "2024-08-16T09:46:54.541592Z", "shell.execute_reply": "2024-08-16T09:46:54.540910Z" }, "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": 2, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:46:54.545083Z", "iopub.status.busy": "2024-08-16T09:46:54.544529Z", "iopub.status.idle": "2024-08-16T09:47:11.799922Z", "shell.execute_reply": "2024-08-16T09:47:11.799101Z" }, "id": "d4c447ddd24e" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "installing cirq...\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\r\n", "tensorflow-metadata 1.15.0 requires protobuf<4.21,>=3.20.3; python_version < \"3.11\", but you have protobuf 4.25.4 which is incompatible.\u001b[0m\u001b[31m\r\n", "\u001b[0m" ] }, { "name": "stdout", "output_type": "stream", "text": [ "installed cirq.\n" ] } ], "source": [ "try:\n", " import cirq\n", "except ImportError:\n", " print(\"installing cirq...\")\n", " !pip install --quiet cirq\n", " import cirq\n", "\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](../experiments/textbook_algorithms.ipynb#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, `H` 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 `cirq.ClassicallyControlledOperation` with a `cirq.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": 3, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:47:11.803757Z", "iopub.status.busy": "2024-08-16T09:47:11.803321Z", "iopub.status.idle": "2024-08-16T09:47:12.346315Z", "shell.execute_reply": "2024-08-16T09:47:12.345401Z" }, "id": "df3dd6e3b308" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0: ───H───M────────────────\n", " ║\n", "1: ───────╫───H───M('b')───\n", " ║ ║\n", "a: ═══════@═══^════════════\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Counter({0: 760, 1: 240})\n" ] } ], "source": [ "q0, q1 = cirq.LineQubit.range(2)\n", "circuit = cirq.Circuit(\n", " cirq.H(q0),\n", " cirq.measure(q0, key='a'),\n", " cirq.H(q1).with_classical_controls('a'),\n", " cirq.measure(q1, key='b'),\n", ")\n", "print(circuit)\n", "print(cirq.Simulator().run(circuit, repetitions=1000).histogram(key='b'))" ] }, { "cell_type": "markdown", "metadata": { "id": "4e416431b695" }, "source": [ "The results from running the circuit on the simulator match expectation. `H` applied to qubit `q0` means that qubit will be $|1\\rangle$ half of the time on average. When `H` is then applied to qubit `q1`, (half of the time), `q1` will measure $|1\\rangle$ a quarter of the time and $|0\\rangle$ three-quarters of the time.\n", "\n", "Using just these conditions, we can construct the [quantum teleportation](../experiments/textbook_algorithms.ipynb#quantum_teleportation) circuit:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:47:12.350046Z", "iopub.status.busy": "2024-08-16T09:47:12.349458Z", "iopub.status.idle": "2024-08-16T09:47:12.364479Z", "shell.execute_reply": "2024-08-16T09:47:12.363788Z" }, "id": "01ccc99c6a3c" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0: ───H───M────────────────\n", " ║\n", "1: ───────╫───H───M('b')───\n", " ║ ║\n", "a: ═══════@═══^════════════\n", "Message Qubit State: [ 0.14283165 -0.91899776 -0.36748084]\n", "Teleported Bob's Qubit state: [ 0.14283164 -0.91899776 -0.3674809 ]\n" ] } ], "source": [ "# Teleports `_message` from Alice to Bob.\n", "alice = cirq.NamedQubit('alice')\n", "bob = cirq.NamedQubit('bob')\n", "message = cirq.NamedQubit('_message')\n", "\n", "message_circuit = cirq.Circuit(\n", " # Create the message.\n", " cirq.X(message) ** 0.371,\n", " cirq.Y(message) ** 0.882,\n", ")\n", "\n", "teleport_circuit = cirq.Circuit(\n", " # Create Bell state to be shared between Alice and Bob.\n", " cirq.H(alice),\n", " cirq.CNOT(alice, bob),\n", " # Prepare message circuit\n", " message_circuit,\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)\n", "\n", "# Simulate the message and teleport circuits for Bloch vectors to compare\n", "# the state of the teleported qubit before and after teleportation.\n", "sim = cirq.Simulator()\n", "message_bloch_vector = cirq.bloch_vector_from_state_vector(\n", " sim.simulate(message_circuit).final_state_vector, index=0\n", ")\n", "teleport_bloch_vector = cirq.bloch_vector_from_state_vector(\n", " sim.simulate(teleport_circuit).final_state_vector, index=2\n", ")\n", "print(f\"Message Qubit State: {message_bloch_vector}\")\n", "print(f\"Teleported Bob's Qubit state: {teleport_bloch_vector}\")" ] }, { "cell_type": "markdown", "metadata": { "id": "ca879df5fb2d" }, "source": [ "This example separately simulated the message qubit after its construction, and Bob's qubit after teleportation of the message. The fact that the Bloch vectors of each respective qubit are the same indicate that the circuit successfully teleported the message qubit's state onto Bob's qubit. " ] }, { "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$. This is verifiable with the simulated result data, where the `c` measurement key for qubit `q2` is always `1` when `a` and `b` are `00` or `11`, and `0` otherwise." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:47:12.368019Z", "iopub.status.busy": "2024-08-16T09:47:12.367509Z", "iopub.status.idle": "2024-08-16T09:47:12.426748Z", "shell.execute_reply": "2024-08-16T09:47:12.426069Z" }, "id": "9a7ff41b51c0" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " ┌──┐\n", "0: ───H────M─────────────────────────────────────────\n", " ║\n", "1: ───H────╫M────────────────────────────────────────\n", " ║║\n", "2: ────────╫╫────X(conditions=[Eq(a, b)])───M('c')───\n", " ║║ ║\n", "a: ════════@╬════^═══════════════════════════════════\n", " ║ ║\n", "b: ═════════@════^═══════════════════════════════════\n", " └──┘\n", " a b c\n", "0 0 0 1\n", "1 0 0 1\n", "2 0 1 0\n", "3 0 1 0\n", "4 0 1 0\n", "5 1 1 1\n", "6 1 0 0\n", "7 0 0 1\n" ] } ], "source": [ "import sympy\n", "\n", "q0, q1, q2 = cirq.LineQubit.range(3)\n", "a, b = sympy.symbols('a b')\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", " cirq.measure(q2, key='c'),\n", ")\n", "print(circuit)\n", "results = cirq.Simulator(seed=2).run(circuit, repetitions=8)\n", "print(results.data)" ] }, { "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": 6, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:47:12.430111Z", "iopub.status.busy": "2024-08-16T09:47:12.429507Z", "iopub.status.idle": "2024-08-16T09:47:12.455454Z", "shell.execute_reply": "2024-08-16T09:47:12.454774Z" }, "id": "8be2002669fc" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " ┌──┐ ┌──────────────────────────────────────────────────────┐\n", "0: ───H────M─────────────────────────────────────────────────────────────────────────\n", " ║\n", "1: ───H────M─────────────────────────────────────────────────────────────────────────\n", " ║\n", "2: ───H────╫M────────────────────────────────────────────────────────────────────────\n", " ║║\n", "3: ────────╫╫─────X(conditions=[b, Eq(a, 0)])───────────────────────────────M('c')───\n", " ║║ ║\n", "4: ────────╫╫─────╫──────────────────────────X(conditions=[Eq(a, 0), b])────M('d')───\n", " ║║ ║ ║\n", "a: ════════@╬═════^══════════════════════════^═══════════════════════════════════════\n", " ║ ║ ║\n", "b: ═════════@═════^══════════════════════════^═══════════════════════════════════════\n", " └──┘ └──────────────────────────────────────────────────────┘\n", " a b c d\n", "0 1 1 0 0\n", "1 0 0 0 0\n", "2 1 1 0 0\n", "3 0 1 1 1\n", "4 1 1 0 0\n", "5 3 1 0 0\n", "6 3 1 0 0\n", "7 0 1 1 1\n" ] } ], "source": [ "q0, q1, q2, q3, q4 = cirq.LineQubit.range(5)\n", "a = sympy.symbols('a')\n", "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(q3).with_classical_controls('b', sympy_cond),\n", " cirq.X(q4).with_classical_controls('b').with_classical_controls(sympy_cond),\n", " cirq.measure(q3, key='c'),\n", " cirq.measure(q4, key='d'),\n", ")\n", "print(circuit)\n", "results = cirq.Simulator(seed=1).run(circuit, repetitions=8)\n", "print(results.data)" ] }, { "cell_type": "markdown", "metadata": { "id": "7d9e124484f3" }, "source": [ "First, remember that the value of a measurement key for multiple qubits will be an integer representative of the bit string of those qubits' measurements. You can see this in the data for `a`, the measurement key for both `q0` and `q1`, which has values in the range `[0, 3]`. The sympy condition `Eq(a, 0)` will then only trigger when both of those qubits individually measure `0`. \n", "\n", "This means that `X(q3).with_classical_controls('b', sympy_cond)` only triggers when `b`'s qubit `q2` measures `1` and `a = 0` is true (`q0` and `q1` measure `0`). This is consistent with the simulated results, for both `c` (`q3`'s key) and `d` (`q4`'s key).\n", "\n", "Finally, the fact that `c` and `d` are always identical serves as a reminder that chaining multiple calls of `with_classical_controls()` together is equivalent to calling it once with multiple arguments." ] }, { "cell_type": "markdown", "metadata": { "id": "34d0fe9226e1" }, "source": [ "## Variable scope\n", "\n", "When used with `cirq.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": 7, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:47:12.458718Z", "iopub.status.busy": "2024-08-16T09:47:12.458305Z", "iopub.status.idle": "2024-08-16T09:47:12.468271Z", "shell.execute_reply": "2024-08-16T09:47:12.467614Z" }, "id": "6a7441827bd6" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Original Circuit\n", " [ 0: ───M───X─── ]\n", "0: ───M───[ ║ ║ ]────────────X───\n", " ║ [ a: ═══@═══^═══ ](loops=2) ║\n", " ║ ║\n", "a: ═══@═════════════════════════════════^═══\n", "Circuit with nested circuit unrolled.\n", "0: ─────M───M───X───M───X───X───\n", " ║ ║ ║ ║ ║ ║\n", "0:a: ═══╬═══@═══^═══╬═══╬═══╬═══\n", " ║ ║ ║ ║\n", "1:a: ═══╬═══════════@═══^═══╬═══\n", " ║ ║\n", "a: ═════@═══════════════════^═══\n" ] } ], "source": [ "q0 = cirq.LineQubit(0)\n", "subcircuit = cirq.FrozenCircuit(cirq.measure(q0, key='a'), cirq.X(q0).with_classical_controls('a'))\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(\"Original Circuit\")\n", "print(circuit)\n", "print(\"Circuit with nested circuit unrolled.\")\n", "print(cirq.CircuitOperation(cirq.FrozenCircuit(circuit)).mapped_circuit(deep=True))" ] }, { "cell_type": "markdown", "metadata": { "id": "b0807e8edb7f" }, "source": [ "The measurement key `a` is present both in the outer circuit and the `FrozenCircuit` nested within it, but these two keys are different due to their different scopes. After unrolling the inner circuit twice, these inner `a`s get prefixed by the repetition number and becomes new, separate measurement keys, `0:a` and `1:a`, that don't interact with each other or the original `a`. \n", "\n", "More complex scoping behavior is described in the [classically controlled operation tests](https://github.com/quantumlib/Cirq/blob/main/cirq-core/cirq/ops/classically_controlled_operation_test.py)." ] }, { "cell_type": "markdown", "metadata": { "id": "520a5bbbea93" }, "source": [ "## Using with transformers\n", "\n", "Cirq [transformers](../transform/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/quantumlib/Cirq/blob/6e0e164e8ac1c2f28a1f3389370fffb50a4d2a4f/cirq-core/cirq/transformers/measurement_transformers.py#L58) can convert a classically-controlled circuit into a purely-quantum circuit:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:47:12.471540Z", "iopub.status.busy": "2024-08-16T09:47:12.471117Z", "iopub.status.idle": "2024-08-16T09:47:12.479277Z", "shell.execute_reply": "2024-08-16T09:47:12.478626Z" }, "id": "e7bda8edb27a" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Original circuit:\n", "0: ───M────────────────\n", " ║\n", "1: ───╫───X───M('b')───\n", " ║ ║\n", "a: ═══@═══^════════════\n", "Measurement deferred:\n", "0: ───────────────────@────────────────\n", " │\n", "1: ───────────────────┼───X───M('b')───\n", " │ │\n", "M('a[0]', q=q(0)): ───X───@───M('a')───\n" ] } ], "source": [ "q0 = cirq.LineQubit(0)\n", "circuit = cirq.Circuit(\n", " cirq.measure(q0, key='a'), cirq.X(q1).with_classical_controls('a'), 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 do 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" }, "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.10.14" } }, "nbformat": 4, "nbformat_minor": 0 }