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": [
+ "
"
+ ]
+ },
+ {
+ "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