diff --git a/docs/_toc.yaml b/docs/_toc.yaml index b1eaf922..7806a14c 100644 --- a/docs/_toc.yaml +++ b/docs/_toc.yaml @@ -68,3 +68,7 @@ toc: path: /cirq/experiments/toric_code - title: "Ground State" path: /cirq/experiments/toric_code/toric_code_ground_state + +- heading: "Rabi oscillations" +- title: "Rabi Oscillation Experiment" + path: /cirq/experiments/benchmarks/rabi_oscillations.ipynb diff --git a/docs/benchmarks/rabi_oscillations.ipynb b/docs/benchmarks/rabi_oscillations.ipynb new file mode 100644 index 00000000..14c2cfba --- /dev/null +++ b/docs/benchmarks/rabi_oscillations.ipynb @@ -0,0 +1,489 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "zJAHLtnyQah6" + }, + "source": [ + "##### Copyright 2020 The Cirq Developers" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "cellView": "form", + "id": "zuEmbgh8QaG1" + }, + "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": "W31l4SmqQSrM" + }, + "source": [ + "# Rabi oscillation experiment" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "2eDV4QFhQhlO" + }, + "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": null, + "metadata": { + "id": "bd9529db1c0b" + }, + "outputs": [], + "source": [ + "try:\n", + " import cirq\n", + " import recirq\n", + "except ImportError:\n", + " !pip install -U pip\n", + " !pip install --quiet cirq\n", + " !pip install --quiet recirq\n", + " import cirq\n", + " import recirq\n", + "\n", + "import numpy as np\n", + "import cirq_google" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "kL2C06ln6h48" + }, + "source": [ + "In this experiment, you are going to use Cirq to check that rotating a qubit by an increasing angle, and then measuring the qubit, produces Rabi oscillations. This requires you to do the following things:\n", + "\n", + "1. Prepare the $|0\\rangle$ state.\n", + "2. Rotate by an angle $\\theta$ around the $X$ axis.\n", + "3. Measure to see if the result is a 1 or a 0.\n", + "4. Repeat steps 1-3 $k$ times.\n", + "5. Report the fraction of $\\frac{\\text{Number of 1's}}{k}$\n", + "found in step 3." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ACqqV6tJ7xXp" + }, + "source": [ + "## 1. Getting to know Cirq\n", + "\n", + "Cirq emphasizes the details of implementing quantum algorithms on near term devices.\n", + "For example, when you work on a qubit in Cirq you don't operate on an unspecified qubit that will later be mapped onto a device by a hidden step.\n", + "Instead, you are always operating on specific qubits at specific locations that you specify.\n", + "\n", + "Suppose you are working with a 54 qubit Sycamore chip.\n", + "This device is included in Cirq by default.\n", + "It is called `cirq_google.Sycamore`, and you can see its layout by printing it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "rKoMKEw46XY7" + }, + "outputs": [], + "source": [ + "working_device = cirq_google.Sycamore\n", + "print(working_device)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "FJJEbuk-98Gj" + }, + "source": [ + "For this experiment you only need one qubit and you can just pick whichever one you like." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "XoXekxuQ8bI0" + }, + "outputs": [], + "source": [ + "my_qubit = cirq.GridQubit(5, 6)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "8Tucm7os-uET" + }, + "source": [ + "Once you've chosen your qubit you can build circuits that use it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "niH8sty--Hu0" + }, + "outputs": [], + "source": [ + "from cirq.contrib.svg import SVGCircuit\n", + "\n", + "# Create a circuit with X, Ry(pi/2) and H.\n", + "my_circuit = cirq.Circuit(\n", + " # Rotate the qubit pi/2 radians around the X axis.\n", + " cirq.rx(np.pi / 2).on(my_qubit),\n", + " # Measure the qubit.\n", + " cirq.measure(my_qubit, key=\"out\"),\n", + ")\n", + "SVGCircuit(my_circuit)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "-zbI-2KUMU66" + }, + "source": [ + "Now you can simulate sampling from your circuit using `cirq.Simulator`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "IqUn4uv9_IVo" + }, + "outputs": [], + "source": [ + "sim = cirq.Simulator()\n", + "samples = sim.sample(my_circuit, repetitions=10)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "k-uAT6sHdGib" + }, + "source": [ + "You can also get properties of the circuit, such as the density matrix of the circuit's output or the state vector just before the terminal measurement." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "83OqpReyHyUK" + }, + "outputs": [], + "source": [ + "state_vector_before_measurement = sim.simulate(my_circuit[:-1])\n", + "sampled_state_vector_after_measurement = sim.simulate(my_circuit)\n", + "\n", + "print(f\"State before measurement:\")\n", + "print(state_vector_before_measurement)\n", + "print(f\"State after measurement:\")\n", + "print(sampled_state_vector_after_measurement)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "1raIf8dsWHLJ" + }, + "source": [ + "You can also examine the outputs from a noisy environment.\n", + "For example, an environment where 10% depolarization is applied to each qubit after each operation in the circuit:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "P7VW97ugWE_h" + }, + "outputs": [], + "source": [ + "noisy_sim = cirq.DensityMatrixSimulator(noise=cirq.depolarize(0.1))\n", + "noisy_post_measurement_state = noisy_sim.simulate(my_circuit)\n", + "noisy_pre_measurement_state = noisy_sim.simulate(my_circuit[:-1])\n", + "\n", + "print(\"Noisy state after measurement:\" + str(noisy_post_measurement_state))\n", + "print(\"Noisy state before measurement:\" + str(noisy_pre_measurement_state))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "2h6yoOl4Rmwt" + }, + "source": [ + "## 2. Parameterized Circuits and Sweeps\n", + "\n", + "Now that you have some of the basics end to end, you can create a parameterized circuit that rotates by an angle $\\theta$:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "n6h6yuyGM58s" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "theta = sympy.Symbol(\"theta\")\n", + "\n", + "parameterized_circuit = cirq.Circuit(\n", + " cirq.rx(theta).on(my_qubit), cirq.measure(my_qubit, key=\"out\")\n", + ")\n", + "SVGCircuit(parameterized_circuit)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "rU3BBOp0S4sM" + }, + "source": [ + "In the above block you saw that there is a `sympy.Symbol` that you placed in the circuit. Cirq supports symbolic computation involving circuits. What this means is that when you construct `cirq.Circuit` objects you can put placeholders in many of the classical control parameters of the circuit which you can fill with values later on.\n", + "\n", + "Now if you wanted to use `cirq.simulate` or `cirq.sample` with the parameterized circuit you would also need to specify a value for `theta`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "SMdz-yAZSwrU" + }, + "outputs": [], + "source": [ + "sim.sample(parameterized_circuit, params={theta: 2}, repetitions=10)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "H_H13Hc8g873" + }, + "source": [ + "You can also specify *multiple* values of `theta`, and get samples back for each value." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "0zjZxGY6hIsu" + }, + "outputs": [], + "source": [ + "sim.sample(parameterized_circuit, params=[{theta: 0.5}, {theta: np.pi}], repetitions=10)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "juuWvOEphaaE" + }, + "source": [ + "Cirq has shorthand notation you can use to sweep `theta` over a range of values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "8lCb3049hqXn" + }, + "outputs": [], + "source": [ + "sim.sample(\n", + " parameterized_circuit,\n", + " params=cirq.Linspace(theta, start=0, stop=np.pi, length=5),\n", + " repetitions=5,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "wqaORMoKiAIW" + }, + "source": [ + "The result value being returned by `sim.sample` is a `pandas.DataFrame` object.\n", + "Pandas is a common library for working with table data in python.\n", + "You can use standard pandas methods to analyze and summarize your results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "bLzGV8nFiS9o" + }, + "outputs": [], + "source": [ + "import pandas\n", + "\n", + "big_results = sim.sample(\n", + " parameterized_circuit,\n", + " params=cirq.Linspace(theta, start=0, stop=np.pi, length=20),\n", + " repetitions=10_000,\n", + ")\n", + "\n", + "# big_results is too big to look at. Plot cross tabulated data instead.\n", + "pandas.crosstab(big_results.theta, big_results.out).plot()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "b2TkL28AmBSQ" + }, + "source": [ + "## 3. The ReCirq experiment\n", + "\n", + "[ReCirq](https://github.com/quantumlib/ReCirq) comes with a pre-written Rabi oscillation experiment `recirq.benchmarks.rabi_oscillations`, which performs the steps outlined at the start of this tutorial to create a circuit that exhibits Rabi Oscillations or [Rabi Cycles](https://en.wikipedia.org/wiki/Rabi_cycle). \n", + "\n", + "This method takes a `cirq.Sampler`, which could be a simulator or a network connection to real hardware, as well as a qubit to test and two iteration parameters, `num_points` and `repetitions`. It then runs `repetitions` many experiments on the provided `sampler`, where each experiment is a circuit that rotates the chosen `qubit` by some $\\theta$ Rabi angle around the $X$ axis (by applying an exponentiated $X$ gate). The result is a sequence of the expected probabilities of the chosen qubit at each of the Rabi angles. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "ma0pVZwSThQx" + }, + "outputs": [], + "source": [ + "import datetime\n", + "from recirq.benchmarks import rabi_oscillations\n", + "\n", + "result = rabi_oscillations(\n", + " sampler=noisy_sim, qubit=my_qubit, num_points=50, repetitions=10000\n", + ")\n", + "result.plot()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "U-oezaJAnzJ8" + }, + "source": [ + "Notice that you can tell from the plot that you used the noisy simulator you defined earlier.\n", + "You can also tell that the amount of depolarization is roughly 10%." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "V6uE-yFxoT-3" + }, + "source": [ + "## 4. Exercise: Find the best qubit\n", + "\n", + "As you have seen, you can use Cirq to perform a Rabi oscillation experiment.\n", + "You can either make the experiment yourself out of the basic pieces made available by Cirq, or use the prebuilt experiment method.\n", + "\n", + "Now you're going to put this knowledge to the test.\n", + "\n", + "There is some amount of depolarizing noise on each qubit.\n", + "Your goal is to characterize every qubit from the Sycamore chip using a Rabi oscillation experiment, and find the qubit with the lowest noise according to the secret noise model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "-eISq1eqXYWx" + }, + "outputs": [], + "source": [ + "import hashlib\n", + "\n", + "\n", + "class SecretNoiseModel(cirq.NoiseModel):\n", + " def noisy_operation(self, op):\n", + " # Hey! No peeking!\n", + " q = op.qubits[0]\n", + " v = hashlib.sha256(str(q).encode()).digest()[0] / 256\n", + " yield cirq.depolarize(v).on(q)\n", + " yield op\n", + "\n", + "\n", + "secret_noise_sampler = cirq.DensityMatrixSimulator(noise=SecretNoiseModel())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Rvf87Wqrp-lu" + }, + "outputs": [], + "source": [ + "q = cirq_google.Sycamore.qubits[3]\n", + "print(\"qubit\", repr(q))\n", + "rabi_oscillations(sampler=secret_noise_sampler, qubit=q).plot()" + ] + } + ], + "metadata": { + "colab": { + "collapsed_sections": [ + "V6uE-yFxoT-3" + ], + "name": "rabi_oscillations.ipynb", + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/recirq/benchmarks/__init__.py b/recirq/benchmarks/__init__.py index 2b6ff390..44f117b5 100644 --- a/recirq/benchmarks/__init__.py +++ b/recirq/benchmarks/__init__.py @@ -11,3 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from recirq.benchmarks.rabi_oscillations import ( + rabi_oscillations, + RabiResult, +) diff --git a/recirq/benchmarks/rabi_oscillations.py b/recirq/benchmarks/rabi_oscillations.py new file mode 100644 index 00000000..3a8b5916 --- /dev/null +++ b/recirq/benchmarks/rabi_oscillations.py @@ -0,0 +1,114 @@ +# Copyright 2022 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Any, Optional, Sequence, Tuple +import numpy as np +import sympy + +from matplotlib import pyplot as plt + +# this is for older systems with matplotlib <3.2 otherwise 3d projections fail +from mpl_toolkits import mplot3d # pylint: disable=unused-import +import cirq + + +class RabiResult: + """Results from a Rabi oscillation experiment. This consists of a set of x-axis + angles following a sine wave and corresponding measurement probabilities for each. + """ + + def __init__( + self, rabi_angles: Sequence[float], excited_state_probabilities: Sequence[float] + ): + """Initializes RabiResult. + + Args: + rabi_angles: The rotation angles of the qubit around the x-axis + of the Bloch sphere. + excited_state_probabilities: The corresponding probabilities that + the qubit is in the excited state. + """ + self._rabi_angles = rabi_angles + self._excited_state_probs = excited_state_probabilities + + @property + def data(self) -> Sequence[Tuple[float, float]]: + """Returns a sequence of tuple pairs with the first item being a Rabi + angle and the second item being the corresponding excited state + probability. + """ + return list(zip(self._rabi_angles, self._excited_state_probs)) + + def plot(self, ax: Optional[plt.Axes] = None, **plot_kwargs: Any) -> plt.Axes: + """Plots excited state probability vs the Rabi angle (angle of rotation + around the x-axis). + + Args: + ax: the plt.Axes to plot on. If not given, a new figure is created, + plotted on, and shown. + **plot_kwargs: Arguments to be passed to 'plt.Axes.plot'. + Returns: + The plt.Axes containing the plot. + """ + show_plot = not ax + if not ax: + fig, ax = plt.subplots(1, 1, figsize=(8, 8)) + ax.set_ylim([0, 1]) + ax.plot(self._rabi_angles, self._excited_state_probs, "ro-", **plot_kwargs) + ax.set_xlabel(r"Rabi Angle (Radian)") + ax.set_ylabel("Excited State Probability") + if show_plot: + fig.show() + return ax + + +def rabi_oscillations( + sampler: cirq.Sampler, + qubit: cirq.Qid, + max_angle: float = 2 * np.pi, + *, + repetitions: int = 1000, + num_points: int = 200, +) -> RabiResult: + """Runs a Rabi oscillation experiment. + + Rotates a qubit around the x-axis of the Bloch sphere by a sequence of Rabi + angles evenly spaced between 0 and max_angle. For each rotation, repeat + the circuit a number of times and measure the average probability of the + qubit being in the |1> state. + + Args: + sampler: The quantum engine or simulator to run the circuits. + qubit: The qubit under test. + max_angle: The final Rabi angle in radians. + repetitions: The number of repetitions of the circuit for each Rabi + angle. + num_points: The number of Rabi angles. + + Returns: + A RabiResult object that stores and plots the result. + """ + theta = sympy.Symbol("theta") + circuit = cirq.Circuit(cirq.X(qubit) ** (theta / np.pi)) + circuit.append(cirq.measure(qubit, key="z")) + sweep = cirq.study.Linspace( + key="theta", start=0.0, stop=max_angle, length=num_points + ) + results = sampler.run_sweep(circuit, params=sweep, repetitions=repetitions) + angles = np.linspace(0.0, max_angle, num_points) + excited_state_probs = np.zeros(num_points) + for i in range(num_points): + excited_state_probs[i] = np.mean(results[i].measurements["z"]) + + return RabiResult(angles, excited_state_probs) diff --git a/recirq/benchmarks/rabi_oscillations_test.py b/recirq/benchmarks/rabi_oscillations_test.py new file mode 100644 index 00000000..08122830 --- /dev/null +++ b/recirq/benchmarks/rabi_oscillations_test.py @@ -0,0 +1,35 @@ +# Copyright 2022 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np + +import cirq +from recirq.benchmarks import rabi_oscillations + + +def test_rabi_oscillations(): + """Check that the excited state population matches the ideal case within a + small statistical error. + """ + simulator = cirq.sim.Simulator() + qubit = cirq.GridQubit(0, 0) + results = rabi_oscillations( + simulator, qubit, np.pi, repetitions=1000, num_points=1000 + ) + data = np.asarray(results.data) + angles = data[:, 0] + actual_pops = data[:, 1] + target_pops = 0.5 - 0.5 * np.cos(angles) + rms_err = np.sqrt(np.mean((target_pops - actual_pops) ** 2)) + assert rms_err < 0.1