diff --git a/examples/advanced/quantum_utility_sim_4a.ipynb b/examples/advanced/quantum_utility_sim_4a.ipynb new file mode 100644 index 00000000000..688d7b62bef --- /dev/null +++ b/examples/advanced/quantum_utility_sim_4a.ipynb @@ -0,0 +1,310 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "code", + "source": [ + "# Copyright 2023 The Cirq Developers\n", + "#\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", + "#\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." + ], + "metadata": { + "id": "0w8r0YTyFMC4" + }, + "execution_count": 1, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "# Quantum utility\n", + "\n", + "This colab contains code for simulating a circuit described in [1] on a subset\n", + "of qubits sufficient for reproducing the results in Fig. 4a. Running this simulation requires ~24GB of RAM, which may require a local runtime.\n", + "Additional RAM and/or compute cores can improve performance.\n", + "\n", + "[1] Kim, Y., Eddins, A., Anand, S. et al. Evidence for the utility of quantum\n", + "computing before fault tolerance. Nature 618, 500–505 (2023).\n", + "https://doi.org/10.1038/s41586-023-06096-3" + ], + "metadata": { + "id": "cA7ijEELYIh2" + } + }, + { + "cell_type": "code", + "source": [ + "try:\n", + " import cirq\n", + "except ImportError:\n", + " print(\"installing cirq...\")\n", + " !pip install --quiet cirq\n", + " print(\"installed cirq.\")\n", + "\n", + "try:\n", + " import qsimcirq\n", + "except ImportError:\n", + " print(\"installing qsimcirq...\")\n", + " !pip install --quiet qsimcirq\n", + " print(f\"installed qsimcirq.\")" + ], + "metadata": { + "id": "kyyLlwIt2cs1" + }, + "execution_count": 2, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "import cirq\n", + "import qsimcirq\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import sympy" + ], + "metadata": { + "id": "oxlQXSsI2dOC" + }, + "execution_count": 3, + "outputs": [] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "id": "cMvJ0vVs0MVr" + }, + "outputs": [], + "source": [ + "# These 30 qubits contain the measured observable:\n", + "#\n", + "# 37-38-39-40-41-42 . X--Y--39-Y--X--Y .\n", + "# | | . | | .\n", + "# 52 53 . X 53 .\n", + "# | | . | | .\n", + "# 56-57-58-59-60-61-62-63 . X--57-X--59-60-61-X--Y .\n", + "# | | . | | .\n", + "# 71 72 . 71 Y .\n", + "# | | . | | .\n", + "# 75-76-77-78-79-80-81 . Z--76-77-78-X--Y--81 .\n", + "# | | . | | .\n", + "# 90 91 . Y Y .\n", + "# | | . | | .\n", + "# 94-95 98 . 94-95 98 .\n", + "#\n", + "qubit_ids = [\n", + " *range(37, 43), # row 0\n", + " *range(52, 54), # row 1\n", + " *range(56, 64), # row 2\n", + " *range(71, 73), # row 3\n", + " *range(75, 82), # row 4\n", + " *range(90, 92), # row 5\n", + " 94, 95, 98, # row 6\n", + "]\n", + "q = {i: cirq.NamedQubit(f'q{i}') for i in qubit_ids}\n", + "qubits = list(q.values())\n", + "\n", + "# This parameter will be used to sweep over X rotation angles.\n", + "theta = sympy.Symbol('theta')\n", + "x_rotations = cirq.Moment(cirq.rx(theta).on_each(qubits))\n", + "\n", + "# This is the ZZ(-pi/2) gate described in equation (2).\n", + "zz_pi_2 = cirq.ZZ ** -0.5\n", + "\n", + "# Each of these moments performs ZZ interactions along\n", + "# 1/3 of the edges in the region, corresponding to the\n", + "# red, blue, and green edges in Fig. 1b.\n", + "zz_layer_1 = cirq.Moment(\n", + " zz_pi_2(q[37], q[52]),\n", + " zz_pi_2(q[41], q[53]),\n", + " zz_pi_2(q[56], q[57]),\n", + " zz_pi_2(q[58], q[71]),\n", + " zz_pi_2(q[59], q[60]),\n", + " zz_pi_2(q[61], q[62]),\n", + " zz_pi_2(q[72], q[81]),\n", + " zz_pi_2(q[76], q[77]),\n", + " zz_pi_2(q[78], q[79]),\n", + " zz_pi_2(q[94], q[95]),\n", + ")\n", + "zz_layer_2 = cirq.Moment(\n", + " zz_pi_2(q[38], q[39]),\n", + " zz_pi_2(q[40], q[41]),\n", + " zz_pi_2(q[53], q[60]),\n", + " zz_pi_2(q[57], q[58]),\n", + " zz_pi_2(q[62], q[63]),\n", + " zz_pi_2(q[71], q[77]),\n", + " zz_pi_2(q[75], q[76]),\n", + " zz_pi_2(q[79], q[80]),\n", + " zz_pi_2(q[90], q[94]),\n", + " zz_pi_2(q[91], q[98]),\n", + ")\n", + "zz_layer_3 = cirq.Moment(\n", + " zz_pi_2(q[37], q[38]),\n", + " zz_pi_2(q[39], q[40]),\n", + " zz_pi_2(q[41], q[42]),\n", + " zz_pi_2(q[52], q[56]),\n", + " zz_pi_2(q[58], q[59]),\n", + " zz_pi_2(q[60], q[61]),\n", + " zz_pi_2(q[62], q[72]),\n", + " zz_pi_2(q[75], q[90]),\n", + " zz_pi_2(q[77], q[78]),\n", + " zz_pi_2(q[79], q[91]),\n", + " zz_pi_2(q[80], q[81]),\n", + ")\n", + "\n", + "# This circuit encapsulates a single \"step\", as shown\n", + "# in Fig. 1a.\n", + "step = cirq.FrozenCircuit(\n", + " x_rotations,\n", + " zz_layer_1,\n", + " zz_layer_2,\n", + " zz_layer_3,\n", + ")\n", + "# Uncomment this line to print the circuit diagram for\n", + "# a single step of the circuit.\n", + "# print(step)" + ] + }, + { + "cell_type": "code", + "source": [ + "# The circuit used to generate Fig. 4a consists of 5 steps + final rotations.\n", + "# Changing \"repetitions\" here will adjust the number of steps simulated.\n", + "all_steps = cirq.CircuitOperation(step, repetitions=5)\n", + "circuit = cirq.Circuit(all_steps, x_rotations)\n", + "\n", + "# These are the component parts of the observable used in Fig. 4a.\n", + "x_obs = cirq.DensePauliString('X' * 8).on(*(q[i] for i in [37, 41, 52, 56, 57, 58, 62, 79]))\n", + "y_obs = cirq.DensePauliString('Y' * 8).on(*(q[i] for i in [38, 40, 42, 63, 72, 80, 90, 91]))\n", + "z_obs = cirq.DensePauliString('Z').on(q[75])\n", + "observables = [x_obs * y_obs * z_obs]\n", + "\n", + "# These are approximately the values of theta plotted for experimental values\n", + "# in Fig. 4a. Changing this list will adjust the simulation to test other\n", + "# theta values.\n", + "theta_values = [0, 0.25, 0.5, 1, *np.linspace(1.2, 1.5, 7), np.pi / 2]\n", + "params = cirq.Points(key=\"theta\", points=theta_values)\n", + "\n", + "# These options are used to tune qsim performance.\n", + "# On CPU, \"cpu_threads\" should be set to the number of cores available.\n", + "opt = qsimcirq.QSimOptions(max_fused_gate_size=4, cpu_threads=24)\n", + "# To use GPU instead, uncomment this line:\n", + "# opt = qsimcirq.QSimOptions(use_gpu=True, gpu_mode=1)\n", + "simulator = qsimcirq.QSimSimulator(qsim_options=opt)" + ], + "metadata": { + "id": "d3eqfPR_K4SY" + }, + "execution_count": 5, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "%%time\n", + "# This will log after each value of theta is simulated. Its purpose is to\n", + "# give an indication of total runtime before all simulations finish.\n", + "results = []\n", + "for i, result in enumerate(simulator.simulate_expectation_values_sweep_iter(circuit, observables, params)):\n", + " results.append(result)\n", + " print(f\"Completed theta={theta_values[i]:.3f}; value={result}\")\n", + "\n", + "# Runtimes logged in the output of this cell were achieved using a machine with\n", + "# 24 cores and 80GB of RAM." + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "jabNt3Q72QWa", + "outputId": "55258f37-a139-446f-cded-15c2b8ed5b35" + }, + "execution_count": 6, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Completed theta=0.000; value=[0j]\n", + "Completed theta=0.250; value=[(4.377516467318132e-10-3.8454568700662104e-17j)]\n", + "Completed theta=0.500; value=[(-5.663425648032262e-07-6.273358064614394e-15j)]\n", + "Completed theta=1.000; value=[(-0.002025490629580058-1.285792463169913e-12j)]\n", + "Completed theta=1.200; value=[(-0.08619230321475829-4.390203478532584e-13j)]\n", + "Completed theta=1.250; value=[(-0.16428450008848153-1.3782378016635732e-15j)]\n", + "Completed theta=1.300; value=[(-0.28045473459690645-6.927717947913248e-13j)]\n", + "Completed theta=1.350; value=[(-0.43263143032440227+2.2862267634593536e-13j)]\n", + "Completed theta=1.400; value=[(-0.6074061541964849+7.668017696499385e-13j)]\n", + "Completed theta=1.450; value=[(-0.7798960015332366-1.6847268480453537e-12j)]\n", + "Completed theta=1.500; value=[(-0.9182744564246872+4.436968438601037e-13j)]\n", + "Completed theta=1.571; value=[(-0.9999907156716326-4.9043406579730134e-17j)]\n", + "CPU times: user 2h 3min 1s, sys: 4min, total: 2h 7min 2s\n", + "Wall time: 6min 8s\n" + ] + } + ] + }, + { + "cell_type": "code", + "source": [ + "# Each element of \"results\" is a list of one expectation value.\n", + "# For visual simplicity, the results are negated.\n", + "plot_results = [-x[0].real for x in results]\n", + "\n", + "# Plot the results in the format of Fig. 4a.\n", + "plt.plot(np.array(theta_values), plot_results, 'bo')\n", + "plt.xlabel(r\"$ R_X $ angle $ \\theta_h $\")\n", + "plt.ylabel(r\"$ \\langle X_{37,41,52,56,57,58,62,79} Y_{38,40,42,63,72,80,90,91} Z_{75} \\rangle $\")\n", + "plt.xticks(np.linspace(0, np.pi / 2, 5), [\"0\", \"π/8\", \"π/4\", \"3π/8\", \"π/2\"])\n", + "plt.yticks(np.linspace(0, 1, 6))\n", + "plt.show()" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 283 + }, + "id": "1jiA6Yy7KGpy", + "outputId": "99ae5c8c-8bfb-4985-861c-da1f8f4108ca" + }, + "execution_count": 7, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "
" + ], + "image/png": "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\n" + }, + "metadata": { + "needs_background": "light" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/examples/advanced/quantum_utility_sim_4b.ipynb b/examples/advanced/quantum_utility_sim_4b.ipynb new file mode 100644 index 00000000000..0f275948e5a --- /dev/null +++ b/examples/advanced/quantum_utility_sim_4b.ipynb @@ -0,0 +1,298 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "code", + "source": [ + "# Copyright 2023 The Cirq Developers\n", + "#\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", + "#\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." + ], + "metadata": { + "id": "NNegRqB9GoU_" + }, + "execution_count": 1, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "# Quantum utility\n", + "\n", + "This colab contains code for simulating a circuit described in [1] on a subset\n", + "of qubits sufficient for reproducing the results in Fig. 4b. Running this simulation requires ~6GB of RAM, which may require a local runtime.\n", + "Additional RAM and/or compute cores can improve performance.\n", + "\n", + "[1] Kim, Y., Eddins, A., Anand, S. et al. Evidence for the utility of quantum\n", + "computing before fault tolerance. Nature 618, 500–505 (2023).\n", + "https://doi.org/10.1038/s41586-023-06096-3" + ], + "metadata": { + "id": "_7E8VdZOYL-V" + } + }, + { + "cell_type": "code", + "source": [ + "try:\n", + " import cirq\n", + "except ImportError:\n", + " print(\"installing cirq...\")\n", + " !pip install --quiet cirq\n", + " print(\"installed cirq.\")\n", + "\n", + "try:\n", + " import qsimcirq\n", + "except ImportError:\n", + " print(\"installing qsimcirq...\")\n", + " !pip install --quiet qsimcirq\n", + " print(f\"installed qsimcirq.\")" + ], + "metadata": { + "id": "X8KW5a0cnRhr" + }, + "execution_count": 2, + "outputs": [] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "id": "82JvqINQiEpy" + }, + "outputs": [], + "source": [ + "import cirq\n", + "import qsimcirq\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import sympy" + ] + }, + { + "cell_type": "code", + "source": [ + "# These 28 qubits construct the three \"bricks\" around qubit 62:\n", + "#\n", + "# 41-42-43-44-45\n", + "# | |\n", + "# 53 54\n", + "# | |\n", + "# 58-59-60-61-62-63-64-65-66\n", + "# | | |\n", + "# 71 72 73\n", + "# | | |\n", + "# 77-78-79-80-81-82-83-84-85\n", + "#\n", + "qubit_ids = [\n", + " *range(41, 46), # row 0\n", + " *range(53, 55), # row 1\n", + " *range(58, 67), # row 2\n", + " *range(71, 74), # row 3\n", + " *range(77, 86), # row 4\n", + "]\n", + "q = {i: cirq.NamedQubit(f'q{i}') for i in qubit_ids}\n", + "qubits = list(q.values())\n", + "\n", + "# This parameter will be used to sweep over X rotation angles.\n", + "theta = sympy.Symbol('theta')\n", + "x_rotations = cirq.Moment(cirq.rx(theta).on_each(qubits))\n", + "\n", + "# This is the ZZ(-pi/2) gate described in equation (2).\n", + "zz_pi_2 = cirq.ZZ ** -0.5\n", + "\n", + "# Each of these moments performs ZZ interactions along\n", + "# 1/3 of the edges in the region, corresponding to the\n", + "# red, blue, and green edges in Fig. 1b.\n", + "zz_layer_1 = cirq.Moment(\n", + " zz_pi_2(q[41], q[53]),\n", + " zz_pi_2(q[43], q[44]),\n", + " zz_pi_2(q[58], q[71]),\n", + " zz_pi_2(q[59], q[60]),\n", + " zz_pi_2(q[61], q[62]),\n", + " zz_pi_2(q[63], q[64]),\n", + " zz_pi_2(q[72], q[81]),\n", + " zz_pi_2(q[73], q[85]),\n", + " zz_pi_2(q[78], q[79]),\n", + " zz_pi_2(q[83], q[84]),\n", + ")\n", + "zz_layer_2 = cirq.Moment(\n", + " zz_pi_2(q[42], q[43]),\n", + " zz_pi_2(q[44], q[45]),\n", + " zz_pi_2(q[53], q[60]),\n", + " zz_pi_2(q[54], q[64]),\n", + " zz_pi_2(q[62], q[63]),\n", + " zz_pi_2(q[65], q[66]),\n", + " zz_pi_2(q[71], q[77]),\n", + " zz_pi_2(q[79], q[80]),\n", + " zz_pi_2(q[81], q[82]),\n", + " zz_pi_2(q[84], q[85]),\n", + ")\n", + "zz_layer_3 = cirq.Moment(\n", + " zz_pi_2(q[41], q[42]),\n", + " zz_pi_2(q[45], q[54]),\n", + " zz_pi_2(q[58], q[59]),\n", + " zz_pi_2(q[60], q[61]),\n", + " zz_pi_2(q[62], q[72]),\n", + " zz_pi_2(q[64], q[65]),\n", + " zz_pi_2(q[66], q[73]),\n", + " zz_pi_2(q[77], q[78]),\n", + " zz_pi_2(q[80], q[81]),\n", + " zz_pi_2(q[82], q[83]),\n", + ")\n", + "\n", + "# This circuit encapsulates a single \"step\", as shown\n", + "# in Fig. 1a.\n", + "step = cirq.FrozenCircuit(\n", + " x_rotations,\n", + " zz_layer_1,\n", + " zz_layer_2,\n", + " zz_layer_3,\n", + ")\n", + "# Uncomment this line to print the circuit diagram for\n", + "# a single step of the circuit.\n", + "# print(step)" + ], + "metadata": { + "id": "k6D7DylOnQw1" + }, + "execution_count": 4, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# The circuit used to generate Fig. 4b consists of 20 steps.\n", + "# Changing \"repetitions\" here will adjust the number of steps simulated.\n", + "all_steps = cirq.CircuitOperation(step, repetitions=20)\n", + "circuit = cirq.Circuit(all_steps)\n", + "\n", + "# This is the Z observable on qubit 62.\n", + "observables = [cirq.Z(q[62])]\n", + "\n", + "# These are approximately the values of theta plotted for experimental values\n", + "# in Fig. 4b. Changing this list will adjust the simulation to test other\n", + "# theta values.\n", + "theta_values = [*np.linspace(0, 0.8, 9), 1, np.pi / 2]\n", + "params = cirq.Points(key=\"theta\", points=theta_values)\n", + "\n", + "# These options are used to tune qsim performance.\n", + "# On CPU, \"cpu_threads\" should be set to the number of cores available.\n", + "opt = qsimcirq.QSimOptions(max_fused_gate_size=4, cpu_threads=24)\n", + "# To use GPU instead, uncomment this line:\n", + "# opt = qsimcirq.QSimOptions(use_gpu=True, gpu_mode=1)\n", + "simulator = qsimcirq.QSimSimulator(qsim_options=opt)" + ], + "metadata": { + "id": "ZtqG4RpBK1IY" + }, + "execution_count": 5, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "%%time\n", + "# This will log after each value of theta is simulated. Its purpose is to\n", + "# give an indication of total runtime before all simulations finish.\n", + "results = []\n", + "for i, result in enumerate(simulator.simulate_expectation_values_sweep_iter(circuit, observables, params)):\n", + " results.append(result)\n", + " print(f\"Completed theta={theta_values[i]:.3f}; value={result}\")\n", + "\n", + "# Runtimes logged in the output of this cell were achieved using a machine with\n", + "# 24 cores and 80GB of RAM." + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Sw0bgSywunbR", + "outputId": "084ecb1c-6204-43c5-c3d7-fd68c1535814" + }, + "execution_count": 6, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Completed theta=0.000; value=[(1+0j)]\n", + "Completed theta=0.100; value=[(0.9957248494988796-1.0945159604072678e-09j)]\n", + "Completed theta=0.200; value=[(0.9785691808859784+5.244455029047104e-09j)]\n", + "Completed theta=0.300; value=[(0.9227771228638144+1.0042918680018413e-08j)]\n", + "Completed theta=0.400; value=[(0.8549387301071374-1.899016572015007e-10j)]\n", + "Completed theta=0.500; value=[(0.7790027681081058-6.695167987532044e-09j)]\n", + "Completed theta=0.600; value=[(0.6093954499261214-4.028657081304886e-09j)]\n", + "Completed theta=0.700; value=[(0.4257824480390532-1.4948143005177584e-10j)]\n", + "Completed theta=0.800; value=[(0.20900853480698267-9.495178461289215e-11j)]\n", + "Completed theta=1.000; value=[(0.011624567839324501+1.1215127671718956e-12j)]\n", + "Completed theta=1.571; value=[(-4.8872551798147e-09-6.909408682133451e-16j)]\n", + "CPU times: user 1h 25min 36s, sys: 38 s, total: 1h 26min 14s\n", + "Wall time: 3min 55s\n" + ] + } + ] + }, + { + "cell_type": "code", + "source": [ + "# Each element of \"results\" is a list of one expectation value (for Z[62]).\n", + "plot_results = [x[0].real for x in results]\n", + "\n", + "# Plot the results in the format of Fig. 4b.\n", + "plt.plot(np.array(theta_values), plot_results, 'bo')\n", + "plt.xlabel(r\"$ R_X $ angle $ \\theta_h $\")\n", + "plt.ylabel(r\"$ \\langle Z_{62} \\rangle $\")\n", + "plt.xticks(np.linspace(0, np.pi / 2, 5), [\"0\", \"π/8\", \"π/4\", \"3π/8\", \"π/2\"])\n", + "plt.yticks(np.linspace(0, 1, 6))\n", + "plt.show()" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 283 + }, + "id": "uCpJDwfwcaTg", + "outputId": "c958f2f0-83ac-4ee4-a92a-0c0e520d5a26" + }, + "execution_count": 7, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "
" + ], + "image/png": "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\n" + }, + "metadata": { + "needs_background": "light" + } + } + ] + } + ] +} \ No newline at end of file