{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "f6b480b258ee" }, "source": [ "##### Copyright 2022 The Cirq Developers" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "cellView": "form", "execution": { "iopub.execute_input": "2024-08-16T10:26:48.110512Z", "iopub.status.busy": "2024-08-16T10:26:48.110007Z", "iopub.status.idle": "2024-08-16T10:26:48.114056Z", "shell.execute_reply": "2024-08-16T10:26:48.113396Z" }, "id": "906e07f6e562" }, "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": "EQvWLKKRgZR9" }, "source": [ "# Parameter Sweeps" ] }, { "cell_type": "markdown", "metadata": { "id": "EvZ_JecKga2p" }, "source": [ "
\n",
" ![]() | \n",
" \n",
" ![]() | \n",
" \n",
" ![]() | \n",
" \n",
" ![]() | \n",
"
0: ───H───T───H───M───" ], "text/plain": [ "0: ───H───T───H───M───" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# circuit1 has theta = 0.5\n", "cirq.resolve_parameters(circuit, {\"theta\": 0.5})\n", "# circuit2 has theta = 0.25\n", "cirq.resolve_parameters(circuit, {\"theta\": 0.25})" ] }, { "cell_type": "markdown", "metadata": { "id": "6463c8d810fa" }, "source": [ "More interestingly, you can combine parameterized circuits with a list of parameter assignments when doing things like running circuits or simulating them. These lists of parameter assignements are called \"sweeps\". For example you can use a simulator's `run_sweep` method to run simulations for the parameters corresponding to the two circuits defined above. " ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T10:27:05.465490Z", "iopub.status.busy": "2024-08-16T10:27:05.464870Z", "iopub.status.idle": "2024-08-16T10:27:05.474364Z", "shell.execute_reply": "2024-08-16T10:27:05.473467Z" }, "id": "5ad9784a61b5" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "param: cirq.ParamResolver({'theta': 0.5}), result: q(0)=1000110010011110111100011\n", "param: cirq.ParamResolver({'theta': 0.25}), result: q(0)=0101010000011000011001010\n" ] } ], "source": [ "sim = cirq.Simulator()\n", "results = sim.run_sweep(circuit, repetitions=25, params=[{\"theta\": 0.5}, {\"theta\": 0.25}])\n", "for result in results:\n", " print(f\"param: {result.params}, result: {result}\")" ] }, { "cell_type": "markdown", "metadata": { "id": "75e583a33046" }, "source": [ "To recap, you can construct parameterized circuits that depend on parameters that have not yet been assigned a value. These parameterized circuits can then be resolved to circuits with actual values via a dictionary that maps the sympy variable name to the value that parameter should take. You can also construct lists of dictionaries of parameter assignments, called sweeps, and pass this to many functions in Cirq that use circuits to do an action (such as `simulate` or `run`). For each of the elements in the sweep, the function will execute using the parameters as described by the element." ] }, { "cell_type": "markdown", "metadata": { "id": "985ff927ebdd" }, "source": [ "## Constructing Sweeps\n", "\n", "The previous example constructed a sweep by simply constructing a list of parameter assignments, `[{\"theta\": 0.5}, {\"theta\": 0.25}]`. Cirq also provides other ways to construct sweeps. \n", "\n", "One useful method for constructing parameter sweeps is `cirq.Linspace` which creates a sweep over a list of equally spaced elements. " ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T10:27:05.477768Z", "iopub.status.busy": "2024-08-16T10:27:05.477305Z", "iopub.status.idle": "2024-08-16T10:27:05.482045Z", "shell.execute_reply": "2024-08-16T10:27:05.481164Z" }, "id": "fcf802956766" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "cirq.ParamResolver({'theta': 0.0})\n", "cirq.ParamResolver({'theta': 0.625})\n", "cirq.ParamResolver({'theta': 1.25})\n", "cirq.ParamResolver({'theta': 1.875})\n", "cirq.ParamResolver({'theta': 2.5})\n" ] } ], "source": [ "# Create a sweep over 5 equally spaced values from 0 to 2.5.\n", "params = cirq.Linspace(key=\"theta\", start=0, stop=2.5, length=5)\n", "for param in params:\n", " print(param)" ] }, { "cell_type": "markdown", "metadata": { "id": "e0d86edee975" }, "source": [ "Note: The `Linspace` sweep is composed of `cirq.ParamResolver` instances instead of simple dictionaries. However, you can think of them as effectively the same for most use cases. \n", "\n", "If you need to explicitly and individually specify each parameter resolution, you can do it by constructing a list of dictionaries as before. However, you can also use `cirq.Points` to do this more succinctly." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T10:27:05.485499Z", "iopub.status.busy": "2024-08-16T10:27:05.484887Z", "iopub.status.idle": "2024-08-16T10:27:05.489535Z", "shell.execute_reply": "2024-08-16T10:27:05.488658Z" }, "id": "8371d9c02076" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "cirq.ParamResolver({'theta': 0})\n", "cirq.ParamResolver({'theta': 1})\n", "cirq.ParamResolver({'theta': 3})\n" ] } ], "source": [ "params = cirq.Points(key=\"theta\", points=[0, 1, 3])\n", "for param in params:\n", " print(param)" ] }, { "cell_type": "markdown", "metadata": { "id": "cd97c725e966" }, "source": [ "If you're working with parameterized circuits, it is very likely you'll need to keep track of multiple parameters. Two common use cases necessitate building a sweep from two constituent sweeps, where the new sweep includes: \n", "- Every possible combination of the elements of each sweep: A cartesian product. \n", "- A element-wise pairing of the two sweeps: A zip.\n", "\n", "The following are examples of using the `*` and `+` operators to combine sweeps by cartesian product and zipping, respectively. " ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T10:27:05.492627Z", "iopub.status.busy": "2024-08-16T10:27:05.492359Z", "iopub.status.idle": "2024-08-16T10:27:05.497509Z", "shell.execute_reply": "2024-08-16T10:27:05.496638Z" }, "id": "bd9ace411791" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "cirq.ParamResolver({'theta': 0.0, 'gamma': 0})\n", "cirq.ParamResolver({'theta': 0.0, 'gamma': 3})\n", "cirq.ParamResolver({'theta': 0.25, 'gamma': 0})\n", "cirq.ParamResolver({'theta': 0.25, 'gamma': 3})\n", "cirq.ParamResolver({'theta': 0.5, 'gamma': 0})\n", "cirq.ParamResolver({'theta': 0.5, 'gamma': 3})\n", "cirq.ParamResolver({'theta': 0.75, 'gamma': 0})\n", "cirq.ParamResolver({'theta': 0.75, 'gamma': 3})\n", "cirq.ParamResolver({'theta': 1.0, 'gamma': 0})\n", "cirq.ParamResolver({'theta': 1.0, 'gamma': 3})\n" ] } ], "source": [ "sweep1 = cirq.Linspace(\"theta\", 0, 1, 5)\n", "sweep2 = cirq.Points(\"gamma\", [0, 3])\n", "# By taking the product of these two sweeps, you can sweep over all possible\n", "# combinations of the parameters.\n", "for param in sweep1 * sweep2:\n", " print(param)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T10:27:05.500964Z", "iopub.status.busy": "2024-08-16T10:27:05.500251Z", "iopub.status.idle": "2024-08-16T10:27:05.505300Z", "shell.execute_reply": "2024-08-16T10:27:05.504442Z" }, "id": "c55ea3d1b5ae" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "cirq.ParamResolver({'theta': 1, 'gamma': 0})\n", "cirq.ParamResolver({'theta': 2, 'gamma': 3})\n", "cirq.ParamResolver({'theta': 3, 'gamma': 4})\n" ] } ], "source": [ "sweep1 = cirq.Points(\"theta\", [1, 2, 3])\n", "sweep2 = cirq.Points(\"gamma\", [0, 3, 4])\n", "# By taking the sum of these two sweeps, you can combine the sweeps\n", "# elementwise (similar to python's zip function):\n", "for param in sweep1 + sweep2:\n", " print(param)" ] }, { "cell_type": "markdown", "metadata": { "id": "b9d5e67f80e5" }, "source": [ "`cirq.Linspace` and `cirq.Points` are instances of the `cirq.Sweep` class, which explicitly supports cartesian product with the `*` operation, and zipping with the `+` operation. The `*` operation produces a `cirq.Product` object, and `+` produces a `cirq.Zip` object, both of which are also `Sweep`s. Other mathematical operations will not work in general *between sweeps*." ] }, { "cell_type": "markdown", "metadata": { "id": "a3eac1b9cf98" }, "source": [ "## Symbols and Expressions" ] }, { "cell_type": "markdown", "metadata": { "id": "802e8fabd18c" }, "source": [ "[SymPy](https://www.sympy.org/en/index.html){:external} is a general symbolic mathematics toolset, and you can leverage this in Cirq to define more complex parameters than have been shown so far. For example, you can define an expression in Sympy and use it to construct circuits that depend on this expression:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T10:27:05.508988Z", "iopub.status.busy": "2024-08-16T10:27:05.508384Z", "iopub.status.idle": "2024-08-16T10:27:05.547433Z", "shell.execute_reply": "2024-08-16T10:27:05.546536Z" }, "id": "54f791f74371" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.5*a + 0.25\n" ] } ], "source": [ "# Construct an expression for 0.5 * a + 0.25:\n", "expr = 0.5 * sympy.Symbol(\"a\") + 0.25\n", "print(expr)" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T10:27:05.550966Z", "iopub.status.busy": "2024-08-16T10:27:05.550183Z", "iopub.status.idle": "2024-08-16T10:27:05.555980Z", "shell.execute_reply": "2024-08-16T10:27:05.555100Z" }, "id": "2f207f4bc301" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "circuit:\n", "0: ───X^(0.5*a + 0.25)───M───\n" ] } ], "source": [ "# Use the expression in the circuit:\n", "circuit = cirq.Circuit(cirq.X(q0)**expr, cirq.measure(q0))\n", "print(f\"circuit:\\n{circuit}\")" ] }, { "cell_type": "markdown", "metadata": { "id": "d6501bf32491" }, "source": [ "Both the exponents and parameter arguments of circuit operations can in fact be any general Sympy expression: The previous examples just used single-variable expressions. When you resolve parameters for this circuit, the expressions are evaluated under the given assignments to the variables in the expression. " ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T10:27:05.559593Z", "iopub.status.busy": "2024-08-16T10:27:05.558815Z", "iopub.status.idle": "2024-08-16T10:27:05.564446Z", "shell.execute_reply": "2024-08-16T10:27:05.563550Z" }, "id": "310b5976c042" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0: ───X^0.25───M───\n" ] } ], "source": [ "print(cirq.resolve_parameters(circuit, {\"a\": 0}))" ] }, { "cell_type": "markdown", "metadata": { "id": "3853e96f6abd" }, "source": [ "Just as before, you can pass a sweep over variable values to `run` or `simulate`, and Cirq will evaluate the expression for each possible value. " ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T10:27:05.567931Z", "iopub.status.busy": "2024-08-16T10:27:05.567321Z", "iopub.status.idle": "2024-08-16T10:27:05.575441Z", "shell.execute_reply": "2024-08-16T10:27:05.574503Z" }, "id": "9b36047325eb" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "param: cirq.ParamResolver({'a': 0}), result: q(0)=0000000000000100000100000\n", "param: cirq.ParamResolver({'a': 1}), result: q(0)=0111111110011011111111111\n" ] } ], "source": [ "sim = cirq.Simulator()\n", "results = sim.run_sweep(circuit, repetitions=25, params=cirq.Points('a', [0, 1]))\n", "for result in results:\n", " print(f\"param: {result.params}, result: {result}\")" ] }, { "cell_type": "markdown", "metadata": { "id": "dae5c1c38113" }, "source": [ "Sympy supports a large number of numeric functions and methods, which can be used to create fairly sophisticated expressions, like cosine, exponentiation, and more:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T10:27:05.578943Z", "iopub.status.busy": "2024-08-16T10:27:05.578322Z", "iopub.status.idle": "2024-08-16T10:27:05.584680Z", "shell.execute_reply": "2024-08-16T10:27:05.583787Z" }, "id": "b5f03870ccf3" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "cos(a)**b\n" ] } ], "source": [ "print(sympy.cos(sympy.Symbol(\"a\"))**sympy.Symbol(\"b\"))" ] }, { "cell_type": "markdown", "metadata": { "id": "93ddeb7cee7d" }, "source": [ "Cirq can numerically evaluate all of the expressions Sympy can evalute. However, if you are running a parameterized circuit on a service (such as on a hardware backed quantum computing service) that service may not support evaluating all expressions. See documentation for the particular service you're using for details. \n", "\n", "As a general workaround, you can instead use Cirq's flattening ability to evaluate the parameters before sending them off to the service." ] }, { "cell_type": "markdown", "metadata": { "id": "d734d8b4ccdb" }, "source": [ "### Flattening Expressions\n", "\n", "Suppose you build a circuit that includes multiple different expressions:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T10:27:05.588522Z", "iopub.status.busy": "2024-08-16T10:27:05.587742Z", "iopub.status.idle": "2024-08-16T10:27:05.595233Z", "shell.execute_reply": "2024-08-16T10:27:05.594357Z" }, "id": "4a521736bfcd" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0: ───X^(a/4)───Y^(1 - a/2)───M───\n" ] } ], "source": [ "a = sympy.Symbol('a')\n", "circuit = cirq.Circuit(cirq.X(q0)**(a / 4), cirq.Y(q0)**(1 - a / 2), cirq.measure(q0))\n", "print(circuit)" ] }, { "cell_type": "markdown", "metadata": { "id": "a7592df0d861" }, "source": [ "Flattening replaces every expression in the circuit with a new symbol that is representative of the value of that expression. Additionally, it keeps track of the new symbols and provices a `cirq.ExpressionMap` object to map the old sympy expression objects to the new symbols that replaced them. " ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T10:27:05.598837Z", "iopub.status.busy": "2024-08-16T10:27:05.598070Z", "iopub.status.idle": "2024-08-16T10:27:05.604055Z", "shell.execute_reply": "2024-08-16T10:27:05.603190Z" }, "id": "7547e141b34b" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0: ───X^()───Y^(<1 - a/2>)───M───\n", "cirq.ExpressionMap({a/4: , 1 - a/2: <1 - a/2>})\n" ] } ], "source": [ "# Flatten returns two objects, the circuit with new symbols, and the mapping from old to new values.\n", "c_flat, expr_map = cirq.flatten(circuit)\n", "print(c_flat)\n", "print(expr_map)" ] }, { "cell_type": "markdown", "metadata": { "id": "756ad9a92137" }, "source": [ "Notice that the new circuit has new symbols, `` and `<1-a/2>`, which are explicitly not expressions. You can see this by looking at the value of the exponent in the first gate:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "execution": { "iopub.execute_input": "2024-08-16T10:27:05.607414Z", "iopub.status.busy": "2024-08-16T10:27:05.606958Z", "iopub.status.idle": "2024-08-16T10:27:05.611550Z", "shell.execute_reply": "2024-08-16T10:27:05.610685Z" }, "id": "66a3c56a21e2" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "