From f1b7513ca0ef6e50931ee4cfa15e720e6c31a8fb Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Fri, 10 Jun 2022 13:10:50 -0700 Subject: [PATCH 1/3] Minor updates to classical control page --- docs/classical_control.ipynb | 115 ++++++++++++++++++++++++++--------- 1 file changed, 86 insertions(+), 29 deletions(-) diff --git a/docs/classical_control.ipynb b/docs/classical_control.ipynb index bdfefbaabc9..290020ee143 100644 --- a/docs/classical_control.ipynb +++ b/docs/classical_control.ipynb @@ -8,7 +8,7 @@ }, "outputs": [], "source": [ - "#@title Copyright 2022 The Cirq Developers\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", @@ -39,16 +39,16 @@ "source": [ "\n", " \n", " \n", " \n", " \n", "
\n", - " View on QuantumAI\n", + " View on QuantumAI\n", " \n", - " Run in Google Colab\n", + " Run in Google Colab\n", " \n", - " View source on GitHub\n", + " View source on GitHub\n", " \n", - " Download notebook\n", + " Download notebook\n", "
" ] @@ -67,6 +67,7 @@ " print(\"installing cirq...\")\n", " !pip install --quiet cirq\n", " import cirq\n", + "\n", " print(\"installed cirq.\")" ] }, @@ -89,7 +90,7 @@ "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." + "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." ] }, { @@ -100,13 +101,15 @@ }, "outputs": [], "source": [ - "q0, q1, q2 = cirq.LineQubit.range(3)\n", + "q0, q1 = cirq.LineQubit.range(2)\n", "circuit = cirq.Circuit(\n", " cirq.H(q0),\n", " cirq.measure(q0, key='a'),\n", - " cirq.X(q1).with_classical_controls('a'),\n", + " cirq.H(q1).with_classical_controls('a'),\n", + " cirq.measure(q1, key='b'),\n", ")\n", - "print(circuit)" + "print(circuit)\n", + "print(cirq.Simulator().run(circuit, repetitions=1000).histogram(key='b'))" ] }, { @@ -115,6 +118,8 @@ "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](/cirq/tutorials/educators/textbook_algorithms#quantum_teleportation) circuit:" ] }, @@ -130,13 +135,19 @@ "alice = cirq.NamedQubit('alice')\n", "bob = cirq.NamedQubit('bob')\n", "message = cirq.NamedQubit('_message')\n", - "circuit = cirq.Circuit(\n", + "\n", + "message_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", + ")\n", + "\n", + "teleport_circuit = cirq.Circuit(\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", @@ -147,7 +158,27 @@ " cirq.X(bob).with_classical_controls('A'),\n", " cirq.Z(bob).with_classical_controls('M'),\n", ")\n", - "print(circuit)" + "print(circuit)\n", + "\n", + "# Simulate the message and teleport circuits for bloch vectors\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(message_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 it's 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 circuit's state onto Bob's qubit. " ] }, { @@ -160,7 +191,7 @@ "\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$." + "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." ] }, { @@ -173,15 +204,19 @@ "source": [ "import sympy\n", "\n", - "a, b, c = sympy.symbols('a b c')\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.X(q2).with_classical_controls(sympy_cond),\n", + " cirq.measure(q2, key='c'),\n", ")\n", - "print(circuit)" + "print(circuit)\n", + "results = cirq.Simulator(seed=2).run(circuit, repetitions=8)\n", + "print(results.data)" ] }, { @@ -203,15 +238,34 @@ }, "outputs": [], "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(q0).with_classical_controls('b', sympy_cond),\n", - " cirq.CZ(q1, q2).with_classical_controls('b').with_classical_controls(sympy_cond),\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)" + "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." ] }, { @@ -222,7 +276,7 @@ "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:" + "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:" ] }, { @@ -233,15 +287,17 @@ }, "outputs": [], "source": [ - "subcircuit = cirq.FrozenCircuit(\n", - " cirq.measure(q0, key='a'), cirq.X(q0).with_classical_controls('a')\n", - ")\n", + "(q0,) = cirq.LineQubit.range(1)\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", + " cirq.X(q0).with_classical_controls('a'),\n", ")\n", - "print(circuit)" + "print(\"Original Circuit\")\n", + "print(circuit)\n", + "print(\"Circuit with nested circuit unrolled.\")\n", + "print(cirq.CircuitOperation(cirq.FrozenCircuit(circuit)).mapped_circuit(deep=True))" ] }, { @@ -250,6 +306,8 @@ "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/master/cirq-core/cirq/ops/classically_controlled_operation_test.py)." ] }, @@ -261,7 +319,7 @@ "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:" + "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:" ] }, { @@ -272,10 +330,9 @@ }, "outputs": [], "source": [ + "(q0,) = cirq.LineQubit.range(1)\n", "circuit = cirq.Circuit(\n", - " cirq.measure(q0, key='a'),\n", - " cirq.X(q1).with_classical_controls('a'),\n", - " cirq.measure(q1, key='b'),\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", @@ -292,7 +349,7 @@ "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." + "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." ] } ], From 5776035eeba7be054093ce4e7e74c6ce8a7d3e15 Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Tue, 14 Jun 2022 12:11:50 -0700 Subject: [PATCH 2/3] Feedback changes --- docs/classical_control.ipynb | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/classical_control.ipynb b/docs/classical_control.ipynb index 290020ee143..d007e704216 100644 --- a/docs/classical_control.ipynb +++ b/docs/classical_control.ipynb @@ -137,15 +137,15 @@ "message = cirq.NamedQubit('_message')\n", "\n", "message_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", ")\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", @@ -160,16 +160,16 @@ ")\n", "print(circuit)\n", "\n", - "# Simulate the message and teleport circuits for bloch vectors\n", + "# Simulate the message and teleport circuits for Bloch vectors\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(message_circuit).final_state_vector, index=2\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}\")" + "print(f\"Teleported Bob's Qubit state: {teleport_bloch_vector}\")" ] }, { @@ -178,7 +178,7 @@ "id": "ca879df5fb2d" }, "source": [ - "This example separately simulated the message qubit after it's 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 circuit's state onto Bob's qubit. " + "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. " ] }, { @@ -265,7 +265,7 @@ "\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." + "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." ] }, { @@ -287,7 +287,7 @@ }, "outputs": [], "source": [ - "(q0,) = cirq.LineQubit.range(1)\n", + "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", @@ -319,7 +319,7 @@ "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:" + "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/quantumlib/Cirq/blob/6e0e164e8ac1c2f28a1f3389370fffb50a4d2a4f/cirq-core/cirq/transformers/measurement_transformers.py#L58) can convert a classically-controlled circuit into a purely-quantum circuit:" ] }, { @@ -330,7 +330,7 @@ }, "outputs": [], "source": [ - "(q0,) = cirq.LineQubit.range(1)\n", + "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", From f878e8ce654956eb9c5a59c471f96ec52b88efe4 Mon Sep 17 00:00:00 2001 From: Auguste Hirth Date: Wed, 15 Jun 2022 10:07:28 -0700 Subject: [PATCH 3/3] Explain why we use bloch --- docs/classical_control.ipynb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/classical_control.ipynb b/docs/classical_control.ipynb index d007e704216..aa6364a8a43 100644 --- a/docs/classical_control.ipynb +++ b/docs/classical_control.ipynb @@ -160,7 +160,8 @@ ")\n", "print(circuit)\n", "\n", - "# Simulate the message and teleport circuits for Bloch vectors\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",