{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "DkA0Fobtb9dM" }, "source": [ "##### Copyright 2022 The Cirq Developers" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "cellView": "form", "execution": { "iopub.execute_input": "2024-08-16T09:40:20.627663Z", "iopub.status.busy": "2024-08-16T09:40:20.627214Z", "iopub.status.idle": "2024-08-16T09:40:20.631244Z", "shell.execute_reply": "2024-08-16T09:40:20.630628Z" }, "id": "tUshu7YfcAAW" }, "outputs": [], "source": [ "# @title 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": "igOQCrBOcF5d" }, "source": [ "# Circuits" ] }, { "cell_type": "markdown", "metadata": { "id": "LHRAvc9TcHOH" }, "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:40:20.634768Z", "iopub.status.busy": "2024-08-16T09:40:20.634345Z", "iopub.status.idle": "2024-08-16T09:40:37.879744Z", "shell.execute_reply": "2024-08-16T09:40:37.878950Z" }, "id": "bd9529db1c0b" }, "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": "doUaJJGSAwCO" }, "source": [ "## Conceptual overview\n", "\n", "The primary representation of quantum programs in Cirq is the `Circuit` class. A `Circuit` is a collection of `Moments`. A `Moment` is a collection of `Operations` that all act during the same abstract time slice. An `Operation` is some effect that operates on a specific subset of Qubits; the most common type of `Operation` is a `GateOperation`.\n" ] }, { "cell_type": "markdown", "metadata": { "id": "77zB_UqkAwCR" }, "source": [ "![Circuits and Moments](../images/CircuitMomentOperation.png)" ] }, { "cell_type": "markdown", "metadata": { "id": "uB8cQJ0PAwCT" }, "source": [ "Let's unpack this.\n", "\n", "At the base of this construction is the notion of a qubit. In Cirq, qubits and other quantum objects are identified by instances of subclasses of the `cirq.Qid` base class. Different subclasses of `Qid` can be used for different purposes. For example, the qubits that Google’s devices use are often arranged on the vertices of a square grid. For this, the class `cirq.GridQubit` subclasses `cirq.Qid`. For example, you can create a 3 by 3 grid of qubits using:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:40:37.884045Z", "iopub.status.busy": "2024-08-16T09:40:37.883278Z", "iopub.status.idle": "2024-08-16T09:40:37.887751Z", "shell.execute_reply": "2024-08-16T09:40:37.887076Z" }, "id": "G30Zl1VwAwCU" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "q(0, 0)\n", "[cirq.GridQubit(0, 0), cirq.GridQubit(0, 1), cirq.GridQubit(0, 2), cirq.GridQubit(1, 0), cirq.GridQubit(1, 1), cirq.GridQubit(1, 2), cirq.GridQubit(2, 0), cirq.GridQubit(2, 1), cirq.GridQubit(2, 2)]\n" ] } ], "source": [ "qubits = cirq.GridQubit.square(3)\n", "\n", "print(qubits[0])\n", "print(qubits)" ] }, { "cell_type": "markdown", "metadata": { "id": "gpi9rwuiAwCZ" }, "source": [ "The next level up is the notion of `cirq.Gate`. A `cirq.Gate` represents a physical process that occurs on a qubit. The important property of a gate is that it can be applied to one or more qubits. This can be done via the `gate.on(*qubits)` method itself or via `gate(*qubits)`. Doing this turns a `cirq.Gate` into a `cirq.Operation`." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:40:37.891107Z", "iopub.status.busy": "2024-08-16T09:40:37.890552Z", "iopub.status.idle": "2024-08-16T09:40:37.894656Z", "shell.execute_reply": "2024-08-16T09:40:37.893978Z" }, "id": "-X_LL4_nAwCa" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "X(q(0, 0))\n" ] } ], "source": [ "# This is a Pauli X gate. It is an object instance.\n", "x_gate = cirq.X\n", "# Applying it to the qubit at location (0, 0) (defined above)\n", "# turns it into an operation.\n", "x_op = x_gate(qubits[0])\n", "\n", "print(x_op)" ] }, { "cell_type": "markdown", "metadata": { "id": "0N7X3nmFAwCd" }, "source": [ "A `cirq.Moment` is simply a collection of operations, each of which operates on a different set of qubits, and which conceptually represents these operations as occurring during this abstract time slice. The `Moment` structure itself is not required to be related to the actual scheduling of the operations on a quantum computer or via a simulator, though it can be. For example, here is a `Moment` in which **Pauli** `X` and a `CZ` gate operate on three qubits:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:40:37.897905Z", "iopub.status.busy": "2024-08-16T09:40:37.897346Z", "iopub.status.idle": "2024-08-16T09:40:37.902992Z", "shell.execute_reply": "2024-08-16T09:40:37.902335Z" }, "id": "naO0-nS0AwCe" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " ╷ 0 1 2\n", "╶─┼───────\n", "0 │ @─@ X\n", " │\n" ] } ], "source": [ "cz = cirq.CZ(qubits[0], qubits[1])\n", "x = cirq.X(qubits[2])\n", "moment = cirq.Moment(x, cz)\n", "\n", "print(moment)" ] }, { "cell_type": "markdown", "metadata": { "id": "8TxICnXCAwCh" }, "source": [ "The above is not the only way one can construct moments, nor even the typical method, but illustrates that a `Moment` is just a collection of operations on disjoint sets of qubits.\n", "\n", "Finally, at the top level, a `cirq.Circuit` is an ordered series of `cirq.Moment` objects. The first `Moment` in this series contains the first `Operations` that will be applied. Here, for example, is a simple circuit made up of two moments:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:40:37.906291Z", "iopub.status.busy": "2024-08-16T09:40:37.905712Z", "iopub.status.idle": "2024-08-16T09:40:37.911523Z", "shell.execute_reply": "2024-08-16T09:40:37.910853Z" }, "id": "W7ToOyp9AwCi" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0, 0): ───@───────\n", " │\n", "(0, 1): ───@───@───\n", " │\n", "(0, 2): ───X───@───\n" ] } ], "source": [ "cz01 = cirq.CZ(qubits[0], qubits[1])\n", "x2 = cirq.X(qubits[2])\n", "cz12 = cirq.CZ(qubits[1], qubits[2])\n", "moment0 = cirq.Moment([cz01, x2])\n", "moment1 = cirq.Moment([cz12])\n", "circuit = cirq.Circuit((moment0, moment1))\n", "\n", "print(circuit)" ] }, { "cell_type": "markdown", "metadata": { "id": "YMXONr0SAwCl" }, "source": [ "Note that the above is one of the many ways to construct a `Circuit`, which illustrates the concept that a `Circuit` is an iterable of `Moment` objects." ] }, { "cell_type": "markdown", "metadata": { "id": "93Wlr_HjAwCm" }, "source": [ "## Constructing circuits\n", "\n", "Constructing Circuits as a series of hand-crafted `Moment` objects is tedious. Instead, Cirq provides a variety of different ways to create a `Circuit`.\n", "\n", "One of the most useful ways to construct a `Circuit` is by appending onto the `Circuit` with the `Circuit.append` method.\n" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:40:37.914768Z", "iopub.status.busy": "2024-08-16T09:40:37.914327Z", "iopub.status.idle": "2024-08-16T09:40:37.919522Z", "shell.execute_reply": "2024-08-16T09:40:37.918855Z" }, "id": "7xtjmYzKAwCn" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0, 0): ───@───\n", " │\n", "(1, 0): ───@───\n", "\n", "(2, 0): ───H───\n" ] } ], "source": [ "q0, q1, q2 = [cirq.GridQubit(i, 0) for i in range(3)]\n", "circuit = cirq.Circuit()\n", "circuit.append([cirq.CZ(q0, q1), cirq.H(q2)])\n", "\n", "print(circuit)" ] }, { "cell_type": "markdown", "metadata": { "id": "CKuRApDeAwCp" }, "source": [ "This appended a new moment to the qubit, which you can continue to do:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:40:37.922752Z", "iopub.status.busy": "2024-08-16T09:40:37.922187Z", "iopub.status.idle": "2024-08-16T09:40:37.926942Z", "shell.execute_reply": "2024-08-16T09:40:37.926284Z" }, "id": "HO8WYyU9AwCq" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0, 0): ───@───H───\n", " │\n", "(1, 0): ───@───@───\n", " │\n", "(2, 0): ───H───@───\n" ] } ], "source": [ "circuit.append([cirq.H(q0), cirq.CZ(q1, q2)])\n", "\n", "print(circuit)" ] }, { "cell_type": "markdown", "metadata": { "id": "8HNpFd0UAwCs" }, "source": [ "These two examples appended full moments. What happens when you append all of these at once?" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:40:37.930179Z", "iopub.status.busy": "2024-08-16T09:40:37.929624Z", "iopub.status.idle": "2024-08-16T09:40:37.934651Z", "shell.execute_reply": "2024-08-16T09:40:37.933991Z" }, "id": "Q3qzMlQmAwCt" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0, 0): ───@───H───\n", " │\n", "(1, 0): ───@───@───\n", " │\n", "(2, 0): ───H───@───\n" ] } ], "source": [ "circuit = cirq.Circuit()\n", "circuit.append([cirq.CZ(q0, q1), cirq.H(q2), cirq.H(q0), cirq.CZ(q1, q2)])\n", "\n", "print(circuit)" ] }, { "cell_type": "markdown", "metadata": { "id": "OQX8FTMyAwCw" }, "source": [ "This has again created two `Moment` objects. How did `Circuit` know how to do this? The `Circuit.append` method (and its cousin, `Circuit.insert`) both take an argument called `strategy` of type `cirq.InsertStrategy`. By default, `InsertStrategy` is `InsertStrategy.EARLIEST`." ] }, { "cell_type": "markdown", "metadata": { "id": "2t7qgbPkAwCx" }, "source": [ "### InsertStrategies\n", "\n", "`cirq.InsertStrategy` defines how `Operations` are placed in a `Circuit` when requested to be inserted at a given location. Here, a location is identified by the index of the `Moment` (in the `Circuit`) where the insertion is requested to be placed at (in the case of `Circuit.append`, this means inserting at the `Moment`, at an index one greater than the maximum moment index in the `Circuit`). \n", "\n", "There are four such strategies: `InsertStrategy.EARLIEST`, `InsertStrategy.NEW`, `InsertStrategy.INLINE` and `InsertStrategy.NEW_THEN_INLINE`.\n", "\n", "`InsertStrategy.EARLIEST`, which is the default, is defined as:\n", "\n", "*Scans backward from the insert location until a moment with operations touching qubits affected by the operation to insert is found. The operation is added to the moment just after that location.*\n", "\n", "For example, if you first create an `Operation` in a single moment, and then use `InsertStrategy.EARLIEST`, the `Operation` can slide back to this first `Moment` if there is space:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:40:37.937900Z", "iopub.status.busy": "2024-08-16T09:40:37.937447Z", "iopub.status.idle": "2024-08-16T09:40:37.942605Z", "shell.execute_reply": "2024-08-16T09:40:37.941918Z" }, "id": "Hd5IGmQrAwCx" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0, 0): ───@───H───\n", " │\n", "(1, 0): ───@───────\n", "\n", "(2, 0): ───H───────\n" ] } ], "source": [ "from cirq.circuits import InsertStrategy\n", "\n", "circuit = cirq.Circuit()\n", "circuit.append([cirq.CZ(q0, q1)])\n", "circuit.append([cirq.H(q0), cirq.H(q2)], strategy=InsertStrategy.EARLIEST)\n", "\n", "print(circuit)" ] }, { "cell_type": "markdown", "metadata": { "id": "BGnlt-kPAwCz" }, "source": [ "After creating the first moment with a `CZ` gate, the second append uses the `InsertStrategy.EARLIEST` strategy. The `H` on `q0` cannot slide back, while the `H` on `q2` can and so ends up in the first `Moment`.\n", "\n", "Contrast this with `InsertStrategy.NEW` that is defined as:\n", "\n", "*Every operation that is inserted is created in a new moment.*" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:40:37.945901Z", "iopub.status.busy": "2024-08-16T09:40:37.945331Z", "iopub.status.idle": "2024-08-16T09:40:37.950318Z", "shell.execute_reply": "2024-08-16T09:40:37.949647Z" }, "id": "Yupv8gQOAwC0" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0, 0): ───H───────────\n", "\n", "(1, 0): ───────H───────\n", "\n", "(2, 0): ───────────H───\n" ] } ], "source": [ "circuit = cirq.Circuit()\n", "circuit.append([cirq.H(q0), cirq.H(q1), cirq.H(q2)], strategy=InsertStrategy.NEW)\n", "\n", "print(circuit)" ] }, { "cell_type": "markdown", "metadata": { "id": "F7ziPs17AwC2" }, "source": [ "Here every operator processed by the append ends up in a new moment. `InsertStrategy.NEW` is most useful when you are inserting a single operation and do not want it to interfere with other `Moments`." ] }, { "cell_type": "markdown", "metadata": { "id": "Ceb0nBxeAwC3" }, "source": [ "Another strategy is `InsertStrategy.INLINE`:" ] }, { "cell_type": "markdown", "metadata": { "id": "zsbTh2QhAwC4" }, "source": [ "*Attempts to add the operation to insert into the moment just before the desired insert location. But, if there’s already an existing operation affecting any of the qubits touched by the operation to insert, a new moment is created instead.*" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:40:37.953783Z", "iopub.status.busy": "2024-08-16T09:40:37.953260Z", "iopub.status.idle": "2024-08-16T09:40:37.958925Z", "shell.execute_reply": "2024-08-16T09:40:37.958233Z" }, "id": "J3LTjH9-AwC5" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0, 0): ───────H───────\n", "\n", "(1, 0): ───@───@───H───\n", " │ │\n", "(2, 0): ───@───@───H───\n" ] } ], "source": [ "circuit = cirq.Circuit()\n", "circuit.append([cirq.CZ(q1, q2)])\n", "circuit.append([cirq.CZ(q1, q2)])\n", "circuit.append([cirq.H(q0), cirq.H(q1), cirq.H(q2)], strategy=InsertStrategy.INLINE)\n", "\n", "print(circuit)" ] }, { "cell_type": "markdown", "metadata": { "id": "7iDg6j4fAwC7" }, "source": [ "After two initial `CZ` between the second and third qubit, the example inserts three `H` operations. The `H` on the first qubit is inserted into the previous `Moment`, but the `H` on the second and third qubits cannot be inserted into the previous `Moment`, so a new `Moment` is created.\n", "\n", "Finally, `InsertStrategy.NEW_THEN_INLINE` is a useful strategy to start a new moment and then continue\n", "inserting from that point onwards." ] }, { "cell_type": "markdown", "metadata": { "id": "O-LfqxSWAwC8" }, "source": [ "*Creates a new moment at the desired insert location for the first operation, but then switches to inserting operations according to `InsertStrategy.INLINE`.*" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:40:37.962296Z", "iopub.status.busy": "2024-08-16T09:40:37.961750Z", "iopub.status.idle": "2024-08-16T09:40:37.966831Z", "shell.execute_reply": "2024-08-16T09:40:37.966160Z" }, "id": "LmT3IKEEAwC9" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0, 0): ───H───H───\n", "\n", "(1, 0): ───────@───\n", " │\n", "(2, 0): ───────@───\n" ] } ], "source": [ "circuit = cirq.Circuit()\n", "circuit.append([cirq.H(q0)])\n", "circuit.append([cirq.CZ(q1, q2), cirq.H(q0)], strategy=InsertStrategy.NEW_THEN_INLINE)\n", "\n", "print(circuit)" ] }, { "cell_type": "markdown", "metadata": { "id": "m2SoYFsFAwC_" }, "source": [ "The first append creates a single moment with an `H` on the first qubit. Then, the append with the `InsertStrategy.NEW_THEN_INLINE` strategy begins by inserting the `CZ` in a new `Moment` (the `InsertStrategy.NEW` in `InsertStrategy.NEW_THEN_INLINE`). Subsequent appending is done `InsertStrategy.INLINE`, so the next `H` on the first qubit is appending in the just created `Moment`.\n", "\n", "Here is a picture showing simple examples of appending 1 and then 2 using the different strategies" ] }, { "cell_type": "markdown", "metadata": { "id": "6633b1d21176" }, "source": [ "![Insert Strategies](../images/InsertStrategy.png)" ] }, { "cell_type": "markdown", "metadata": { "id": "Ij28qJdtAwC_" }, "source": [ "### Patterns for arguments to append and insert\n", "\n", "In the above examples, you used a series of `Circuit.append` calls with a list of different `Operations` added to the circuit. However, the argument where you have supplied a list can also take more than just list values. For instance:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:40:37.970311Z", "iopub.status.busy": "2024-08-16T09:40:37.969760Z", "iopub.status.idle": "2024-08-16T09:40:37.975406Z", "shell.execute_reply": "2024-08-16T09:40:37.974764Z" }, "id": "kmt8hAfZAwDA" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CZ(q(0, 0), q(1, 0))\n", "[cirq.H(cirq.GridQubit(0, 0)), cirq.H(cirq.GridQubit(1, 0)), cirq.H(cirq.GridQubit(2, 0))]\n", "[cirq.CZ(cirq.GridQubit(1, 0), cirq.GridQubit(2, 0))]\n", "[cirq.H(cirq.GridQubit(0, 0)), [cirq.CZ(cirq.GridQubit(1, 0), cirq.GridQubit(2, 0))]]\n" ] } ], "source": [ "def my_layer():\n", " yield cirq.CZ(q0, q1)\n", " yield [cirq.H(q) for q in (q0, q1, q2)]\n", " yield [cirq.CZ(q1, q2)]\n", " yield [cirq.H(q0), [cirq.CZ(q1, q2)]]\n", "\n", "\n", "circuit = cirq.Circuit()\n", "circuit.append(my_layer())\n", "\n", "for x in my_layer():\n", " print(x)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:40:37.978482Z", "iopub.status.busy": "2024-08-16T09:40:37.977944Z", "iopub.status.idle": "2024-08-16T09:40:37.982746Z", "shell.execute_reply": "2024-08-16T09:40:37.982105Z" }, "id": "LXs7ffVWAwDC" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0, 0): ───@───H───H───────\n", " │\n", "(1, 0): ───@───H───@───@───\n", " │ │\n", "(2, 0): ───H───────@───@───\n" ] } ], "source": [ "print(circuit)" ] }, { "cell_type": "markdown", "metadata": { "id": "DpGimiozAwDE" }, "source": [ "Recall that Python functions with a `yield` are generators. Generators are functions that act as iterators. The above example iterates over `my_layer()` with a `for` loop. In this case, each of the `yield`s produces:\n", "\n", "* `Operations`, \n", "* lists of `Operations`,\n", "* or lists of `Operations` mixed with lists of `Operations`. \n", "\n", "\n", "When you pass an iterator to the `append` method, `Circuit` is able to flatten all of these and pass them as one giant list to `Circuit.append` (this also works for `Circuit.insert`).\n", "\n", "The above idea uses the concept of `cirq.OP_TREE`. An `OP_TREE` is not a class, but a *contract*. The basic idea is that, if the input can be iteratively flattened into a list of operations, then the input is an `OP_TREE`.\n", "\n", "A very nice pattern emerges from this structure: define generators for sub-circuits, which can vary by size or `Operation` parameters.\n", "\n", "Another useful method to construct a `Circuit` fully formed from an `OP_TREE` is to pass the `OP_TREE` into `Circuit` when initializing it:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:40:37.985813Z", "iopub.status.busy": "2024-08-16T09:40:37.985383Z", "iopub.status.idle": "2024-08-16T09:40:37.989900Z", "shell.execute_reply": "2024-08-16T09:40:37.989227Z" }, "id": "dC6iBIqrAwDF" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0, 0): ───H───\n", "\n", "(1, 0): ───H───\n" ] } ], "source": [ "circuit = cirq.Circuit(cirq.H(q0), cirq.H(q1))\n", "print(circuit)" ] }, { "cell_type": "markdown", "metadata": { "id": "dE0kxo6tAwDI" }, "source": [ "### Slicing and iterating over circuits\n", "\n", "Circuits can be iterated over and sliced. When they are iterated, each item in the iteration is a moment:\n" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:40:37.993037Z", "iopub.status.busy": "2024-08-16T09:40:37.992508Z", "iopub.status.idle": "2024-08-16T09:40:37.997456Z", "shell.execute_reply": "2024-08-16T09:40:37.996829Z" }, "id": "z2csEbbyAwDI" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " ╷ 0\n", "╶─┼───\n", "0 │ H\n", " │\n", " ╷ 0\n", "╶─┼───\n", "0 │ @\n", " │ │\n", "1 │ @\n", " │\n" ] } ], "source": [ "circuit = cirq.Circuit(cirq.H(q0), cirq.CZ(q0, q1))\n", "for moment in circuit:\n", " print(moment)" ] }, { "cell_type": "markdown", "metadata": { "id": "8gT1t2drAwDL" }, "source": [ "Slicing a `Circuit`, on the other hand, produces a new `Circuit` with only the moments corresponding to the slice:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:40:38.000535Z", "iopub.status.busy": "2024-08-16T09:40:38.000112Z", "iopub.status.idle": "2024-08-16T09:40:38.004843Z", "shell.execute_reply": "2024-08-16T09:40:38.004198Z" }, "id": "hxczWjkMAwDL" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0, 0): ───@───────\n", " │\n", "(1, 0): ───@───H───\n" ] } ], "source": [ "circuit = cirq.Circuit(cirq.H(q0), cirq.CZ(q0, q1), cirq.H(q1), cirq.CZ(q0, q1))\n", "print(circuit[1:3])" ] }, { "cell_type": "markdown", "metadata": { "id": "cDAVDT7bAwDO" }, "source": [ "Two especially useful applications of this are dropping the last moment (which are often just measurements): `circuit[:-1]`, and reversing a circuit: `circuit[::-1]`.\n" ] }, { "cell_type": "markdown", "metadata": { "id": "e31b4ff57134" }, "source": [ "### Nesting circuits with CircuitOperation\n", "\n", "Circuits can be nested inside one another with `cirq.CircuitOperation`. This is useful for concisely defining large, repetitive circuits, as the repeated section can be defined once and then be reused elsewhere. Circuits that need to be serialized especially benefit from this, as loops and functions used in the Python construction of a circuit are otherwise not captured in serialization.\n", "\n", "The subcircuit must first be \"frozen\" to indicate that no further changes will be made to it." ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:40:38.008159Z", "iopub.status.busy": "2024-08-16T09:40:38.007613Z", "iopub.status.idle": "2024-08-16T09:40:38.014498Z", "shell.execute_reply": "2024-08-16T09:40:38.013827Z" }, "id": "b3772732f57b" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " [ (0, 0): ───────@─────────── ]\n", " [ │ ]\n", "(0, 0): ───H───[ (1, 0): ───H───@───@───H─── ]───\n", " [ │ ]\n", " [ (2, 0): ───────────@─────── ]\n", " │\n", "(1, 0): ───────#2────────────────────────────────\n", " │\n", "(2, 0): ───H───#3────────────────────────────────\n" ] } ], "source": [ "subcircuit = cirq.Circuit(cirq.H(q1), cirq.CZ(q0, q1), cirq.CZ(q2, q1), cirq.H(q1))\n", "subcircuit_op = cirq.CircuitOperation(subcircuit.freeze())\n", "circuit = cirq.Circuit(cirq.H(q0), cirq.H(q2), subcircuit_op)\n", "print(circuit)" ] }, { "cell_type": "markdown", "metadata": { "id": "3557398496c2" }, "source": [ "Frozen circuits can also be constructed directly, for convenience." ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:40:38.017746Z", "iopub.status.busy": "2024-08-16T09:40:38.017184Z", "iopub.status.idle": "2024-08-16T09:40:38.023141Z", "shell.execute_reply": "2024-08-16T09:40:38.022479Z" }, "id": "3d80776e09fe" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " [ (0, 0): ───────@─────────── ]\n", " [ │ ]\n", "(0, 0): ───[ (1, 0): ───H───@───@───H─── ]───\n", " [ │ ]\n", " [ (2, 0): ───────────@─────── ]\n", " │\n", "(1, 0): ───#2────────────────────────────────\n", " │\n", "(2, 0): ───#3────────────────────────────────\n" ] } ], "source": [ "circuit = cirq.Circuit(\n", " cirq.CircuitOperation(\n", " cirq.FrozenCircuit(cirq.H(q1), cirq.CZ(q0, q1), cirq.CZ(q2, q1), cirq.H(q1))\n", " )\n", ")\n", "print(circuit)" ] }, { "cell_type": "markdown", "metadata": { "id": "e2c83225f05a" }, "source": [ "A `CircuitOperation` is sort of like a function: by default, it will behave like the circuit it contains, but you can also pass arguments to it that alter the qubits it operates on, the number of times it repeats, and other properties. `CircuitOperation`s can also be referenced multiple times within the same \"outer\" circuit for conciseness." ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:40:38.026388Z", "iopub.status.busy": "2024-08-16T09:40:38.025830Z", "iopub.status.idle": "2024-08-16T09:40:38.032104Z", "shell.execute_reply": "2024-08-16T09:40:38.031469Z" }, "id": "62d50a44bdb3" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " [ (0, 0): ───@─── ]\n", "(0, 0): ───[ │ ]────────────────────────────────────────────────────────────────\n", " [ (1, 0): ───@─── ](loops=2)\n", " │\n", "(1, 0): ───#2─────────────────────────────#2──────────────────────────────────────────────────\n", " │\n", " [ (0, 0): ───@─── ]\n", "(2, 0): ──────────────────────────────────[ │ ]─────────────────────────────────\n", " [ (1, 0): ───@─── ](qubit_map={q(0, 0): q(2, 0)})\n" ] } ], "source": [ "subcircuit_op = cirq.CircuitOperation(cirq.FrozenCircuit(cirq.CZ(q0, q1)))\n", "\n", "# Create a copy of subcircuit_op that repeats twice...\n", "repeated_subcircuit_op = subcircuit_op.repeat(2)\n", "\n", "# ...and another copy that replaces q0 with q2 to perform CZ(q2, q1).\n", "moved_subcircuit_op = subcircuit_op.with_qubit_mapping({q0: q2})\n", "circuit = cirq.Circuit(repeated_subcircuit_op, moved_subcircuit_op)\n", "print(circuit)" ] }, { "cell_type": "markdown", "metadata": { "id": "4e9d0e01508a" }, "source": [ "For the most part, a `CircuitOperation` behaves just like a regular `Operation`: its qubits are the qubits of the contained circuit (after applying any provided mapping), and it can be placed inside any `Moment` that doesn't already contain operations on those qubits. This means that `CircuitOperation`s can be used to represent more complex operation timing, such as three operations on one qubit in parallel with two operations on another:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:40:38.035323Z", "iopub.status.busy": "2024-08-16T09:40:38.034784Z", "iopub.status.idle": "2024-08-16T09:40:38.040179Z", "shell.execute_reply": "2024-08-16T09:40:38.039542Z" }, "id": "1b06bfaddfdf" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0, 0): ───[ (0, 0): ───H─── ](loops=3)─────────────────────────────────\n", "\n", "(1, 0): ───[ (0, 0): ───H─── ](qubit_map={q(0, 0): q(1, 0)}, loops=2)───\n" ] } ], "source": [ "subcircuit_op = cirq.CircuitOperation(cirq.FrozenCircuit(cirq.H(q0)))\n", "circuit = cirq.Circuit(\n", " subcircuit_op.repeat(3), subcircuit_op.repeat(2).with_qubit_mapping({q0: q1})\n", ")\n", "print(circuit)" ] }, { "cell_type": "markdown", "metadata": { "id": "c8a67e3ec86d" }, "source": [ "In the above example, even though the top `CircuitOperation` is iterated three times and the bottom one is iterated two times, they still reside within the same `Moment`, meaning they can be thought of conceptually as executing simultaneously in the same time step. However, this may not hold when the circuit is run on hardware or a simulator.\n", "\n", "`CircuitOperation`s can also be nested within each other to arbitrary depth." ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:40:38.043454Z", "iopub.status.busy": "2024-08-16T09:40:38.042909Z", "iopub.status.idle": "2024-08-16T09:40:38.047711Z", "shell.execute_reply": "2024-08-16T09:40:38.047094Z" }, "id": "4acf1a942b75" }, "outputs": [], "source": [ "qft_1 = cirq.CircuitOperation(cirq.FrozenCircuit(cirq.H(q0)))\n", "qft_2 = cirq.CircuitOperation(cirq.FrozenCircuit(cirq.H(q1), cirq.CZ(q0, q1) ** 0.5, qft_1))\n", "qft_3 = cirq.CircuitOperation(\n", " cirq.FrozenCircuit(cirq.H(q2), cirq.CZ(q1, q2) ** 0.5, cirq.CZ(q0, q2) ** 0.25, qft_2)\n", ")\n", "# etc." ] }, { "cell_type": "markdown", "metadata": { "id": "98a7293a6e4b" }, "source": [ "Finally, the `mapped_circuit` method will return the circuit that a `CircuitOperation` represents after all repetitions and remappings have been applied. By default, this only \"unrolls\" a single layer of `CircuitOperation`s. To recursively unroll all layers, you can pass `deep=True` to this method." ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T09:40:38.050789Z", "iopub.status.busy": "2024-08-16T09:40:38.050355Z", "iopub.status.idle": "2024-08-16T09:40:38.061445Z", "shell.execute_reply": "2024-08-16T09:40:38.060800Z" }, "id": "7a0b7fc127f2" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Original qft_3 CircuitOperation\n", "[ [ (0, 0): ───────@───────[ (0, 0): ───H─── ]─── ] ]\n", "[ (0, 0): ───────────────@────────[ │ ]─── ]\n", "[ │ [ (1, 0): ───H───@^0.5───────────────────────── ] ]\n", "[ │ │ ]\n", "[ (1, 0): ───────@───────┼────────#2────────────────────────────────────────────────── ]\n", "[ │ │ ]\n", "[ (2, 0): ───H───@^0.5───@^0.25─────────────────────────────────────────────────────── ]\n", "Single layer unroll:\n", " [ (0, 0): ───────@───────[ (0, 0): ───H─── ]─── ]\n", "(0, 0): ───────────────@────────[ │ ]───\n", " │ [ (1, 0): ───H───@^0.5───────────────────────── ]\n", " │ │\n", "(1, 0): ───────@───────┼────────#2──────────────────────────────────────────────────\n", " │ │\n", "(2, 0): ───H───@^0.5───@^0.25───────────────────────────────────────────────────────\n", "Recursive unroll:\n", "(0, 0): ───────────────@────────────@───────H───\n", " │ │\n", "(1, 0): ───────@───────┼────────H───@^0.5───────\n", " │ │\n", "(2, 0): ───H───@^0.5───@^0.25───────────────────\n" ] } ], "source": [ "# A large CircuitOperation with other sub-CircuitOperations.\n", "print('Original qft_3 CircuitOperation')\n", "print(qft_3)\n", "# Unroll the outermost CircuitOperation to a normal circuit.\n", "print('Single layer unroll:')\n", "print(qft_3.mapped_circuit(deep=False))\n", "# Unroll all of the CircuitOperations recursively.\n", "print('Recursive unroll:')\n", "print(qft_3.mapped_circuit(deep=True))" ] }, { "cell_type": "markdown", "metadata": { "id": "7af5e8ea5b45" }, "source": [ "# Summary\n", "\n", "`Circuit`s are sliceable iterables of `Moments`, which are sliceable iterables of `Operation`s, which are `Gate`s applied to `Qubit`s. Cirq provides intuitive and flexible ways to construct, add to, divide, and nest `Circuits`, with generators, slicing, and `CircuitOperation`s. \n", "\n", "Knowing how to create a circuit is useful, but what you put into a circuit is the interesting part. Read more about these constituent structures at: \n", "- [Qubits](qubits.ipynb) - the software representation of quantum bits\n", "- [Gates](gates.ipynb) - quantum gates that become operations when applied to qubits\n", "- [Operators](operators.ipynb) - more complicated structures to put in circuits\n", "\n", "Once you've built your circuit, read these to learn what you can do with it: \n", "- [Simulation](../simulate/simulation.ipynb) - run your circuit on the Cirq simulator to see what it does\n", "- [Quantum Virtual Machine](../simulate/quantum_virtual_machine.ipynb) - run your circuit on a specialized simulator that mimics actual quantum hardware, including noise.\n", "- [Transform circuits](../transform/transformers.ipynb) - run transformer functions to change your circuit in different ways\n", "\n", "If you need to import or export circuits into or out of Cirq, see: \n", "- [Import/export circuits](interop.ipynb) - features to serialize/deserialize circuits into/from different formats" ] } ], "metadata": { "colab": { "collapsed_sections": [], "name": "circuits.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 }