modules/data_science/scripts/build/notebooks/Quantum_Simulation_qsimcirq.ipynb (725 lines of code) (raw):

{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "8M2lhf2RBGZj" }, "source": [ "##### Copyright 2023 Google" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "cellView": "form", "id": "FMqHt2UBBJzR" }, "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": "vAx5g2rPnlzt" }, "source": [ "# Get started with qsimcirq" ] }, { "cell_type": "markdown", "metadata": { "id": "5jqw8k0zBsb-" }, "source": [ "<table class=\"tfo-notebook-buttons\" align=\"left\">\n", " <td>\n", " <a target=\"_blank\" href=\"https://quantumai.google/qsim/tutorials/qsimcirq\"><img src=\"https://quantumai.google/site-assets/images/buttons/quantumai_logo_1x.png\" />View on QuantumAI</a>\n", " </td>\n", " <td>\n", " <a target=\"_blank\" href=\"https://colab.research.google.com/github/quantumlib/qsim/blob/master/docs/tutorials/qsimcirq.ipynb\"><img src=\"https://quantumai.google/site-assets/images/buttons/colab_logo_1x.png\" />Run in Google Colab</a>\n", " </td>\n", " <td>\n", " <a target=\"_blank\" href=\"https://github.com/quantumlib/qsim/blob/master/docs/tutorials/qsimcirq.ipynb\"><img src=\"https://quantumai.google/site-assets/images/buttons/github_logo_1x.png\" />View source on GitHub</a>\n", " </td>\n", " <td>\n", " <a href=\"https://storage.googleapis.com/tensorflow_docs/qsim/docs/tutorials/qsimcirq.ipynb\"><img src=\"https://quantumai.google/site-assets/images/buttons/download_icon_1x.png\" />Download notebook</a>\n", " </td>\n", "</table>" ] }, { "cell_type": "markdown", "metadata": { "id": "VgdpPfgTCjQB" }, "source": [ "The qsim library provides a Python interface to Cirq in the **qsimcirq** PyPI package." ] }, { "cell_type": "markdown", "metadata": { "id": "8vr-44j-Csxf" }, "source": [ "## Setup\n", "\n", "Install the Cirq and qsimcirq packages:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "id": "XcAhsIQdivnp" }, "outputs": [], "source": [ "try:\n", " import cirq\n", "except ImportError:\n", " !pip install cirq --quiet\n", " import cirq\n", "\n", "try:\n", " import qsimcirq\n", "except ImportError:\n", " !pip install qsimcirq --quiet\n", " import qsimcirq" ] }, { "cell_type": "markdown", "metadata": { "id": "7lYC4qxpDKMN" }, "source": [ "Simulating Cirq circuits with qsim is easy: just define the circuit as you normally would, then create a `QSimSimulator` to perform the simulation. This object implements Cirq's [simulator.py](https://github.com/quantumlib/Cirq/blob/master/cirq/sim/simulator.py) interfaces, so you can drop it in anywhere the basic Cirq simulator is used." ] }, { "cell_type": "markdown", "metadata": { "id": "FXytmQlBoeTT" }, "source": [ "## Full state-vector simulation\n", "\n", "qsim is optimized for computing the final state vector of a circuit. Try it by running the example below." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "id": "jd8DaJMbjkCM" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Circuit:\n", "0: ───H───@───\n", " │\n", "1: ───────X───\n", "\n", "Cirq results:\n", "measurements: (no measurements)\n", "output vector: 0.707|00⟩ + 0.707|11⟩\n", "\n", "qsim results:\n", "measurements: (no measurements)\n", "output vector: 0.707|00⟩ + 0.707|11⟩\n" ] } ], "source": [ "# Define qubits and a short circuit.\n", "q0, q1 = cirq.LineQubit.range(2)\n", "circuit = cirq.Circuit(cirq.H(q0), cirq.CX(q0, q1))\n", "print(\"Circuit:\")\n", "print(circuit)\n", "print()\n", "\n", "# Simulate the circuit with Cirq and return the full state vector.\n", "print('Cirq results:')\n", "cirq_simulator = cirq.Simulator()\n", "cirq_results = cirq_simulator.simulate(circuit)\n", "print(cirq_results)\n", "print()\n", "\n", "# Simulate the circuit with qsim and return the full state vector.\n", "print('qsim results:')\n", "qsim_simulator = qsimcirq.QSimSimulator()\n", "qsim_results = qsim_simulator.simulate(circuit)\n", "print(qsim_results)" ] }, { "cell_type": "markdown", "metadata": { "id": "-kyXlKujtZcp" }, "source": [ "To sample from this state, you can invoke Cirq's `sample_state_vector` method:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "id": "-m6uzQ6ms9I7" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[1 1]\n", " [1 1]\n", " [1 1]\n", " [0 0]\n", " [1 1]\n", " [0 0]\n", " [1 1]\n", " [0 0]\n", " [1 1]\n", " [1 1]]\n" ] } ], "source": [ "samples = cirq.sample_state_vector(\n", " qsim_results.state_vector(), indices=[0, 1], repetitions=10)\n", "print(samples)" ] }, { "cell_type": "markdown", "metadata": { "id": "pOhybi46sHwI" }, "source": [ "## Measurement sampling\n", "\n", "qsim also supports sampling from user-defined measurement gates. \n", "\n", "> *Note*: Since qsim and Cirq use different random number generators, identical runs on both simulators may give different results, even if they use the same seed." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "id": "NRJvtqYrnylJ" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Circuit:\n", "0: ───H───@───M('qubit_0')───\n", " │\n", "1: ───X───X───M('qubit_1')───\n", "\n", "Cirq results:\n", "qubit_0=10000\n", "qubit_1=01111\n", "\n", "qsim results:\n", "Provided circuit has no intermediate measurements. Sampling repeatedly from final state vector.\n", "qubit_0=10100\n", "qubit_1=01011\n" ] } ], "source": [ "# Define a circuit with measurements.\n", "q0, q1 = cirq.LineQubit.range(2)\n", "circuit = cirq.Circuit(\n", " cirq.H(q0), cirq.X(q1), cirq.CX(q0, q1),\n", " cirq.measure(q0, key='qubit_0'),\n", " cirq.measure(q1, key='qubit_1'),\n", ")\n", "print(\"Circuit:\")\n", "print(circuit)\n", "print()\n", "\n", "# Simulate the circuit with Cirq and return just the measurement values.\n", "print('Cirq results:')\n", "cirq_simulator = cirq.Simulator()\n", "cirq_results = cirq_simulator.run(circuit, repetitions=5)\n", "print(cirq_results)\n", "print()\n", "\n", "# Simulate the circuit with qsim and return just the measurement values.\n", "print('qsim results:')\n", "qsim_simulator = qsimcirq.QSimSimulator()\n", "qsim_results = qsim_simulator.run(circuit, repetitions=5)\n", "print(qsim_results)" ] }, { "cell_type": "markdown", "metadata": { "id": "6NoSr0E_wgR_" }, "source": [ "The warning above highlights an important distinction between the `simulate` and `run` methods:\n", "\n", "* `simulate` only executes the circuit once. \n", " - Sampling from the resulting state is fast, but if there are intermediate measurements the final state vector depends on the results of those measurements.\n", "* `run` will execute the circuit once for each repetition requested. \n", " - As a result, sampling is much slower, but intermediate measurements are re-sampled for each repetition. If there are no intermediate measurements, `run` redirects to `simulate` for faster execution.\n", "\n", "The warning goes away if intermediate measurements are present:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "id": "hFrwYjWM0Hpa" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Circuit:\n", "0: ───X^0.5───M('m0')───X^0.5───M('m1')───X^0.5───M('m2')───\n", "\n", "qsim results:\n", "m0=00111\n", "m1=11000\n", "m2=10111\n" ] } ], "source": [ "# Define a circuit with intermediate measurements.\n", "q0 = cirq.LineQubit(0)\n", "circuit = cirq.Circuit(\n", " cirq.X(q0)**0.5, cirq.measure(q0, key='m0'),\n", " cirq.X(q0)**0.5, cirq.measure(q0, key='m1'),\n", " cirq.X(q0)**0.5, cirq.measure(q0, key='m2'),\n", ")\n", "print(\"Circuit:\")\n", "print(circuit)\n", "print()\n", "\n", "# Simulate the circuit with qsim and return just the measurement values.\n", "print('qsim results:')\n", "qsim_simulator = qsimcirq.QSimSimulator()\n", "qsim_results = qsim_simulator.run(circuit, repetitions=5)\n", "print(qsim_results)" ] }, { "cell_type": "markdown", "metadata": { "id": "KhPHBKpi0lYE" }, "source": [ "## Amplitude evaluation\n", "\n", "qsim can also calculate amplitudes for specific output bitstrings." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "id": "aCbvXO6M1jWB" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Circuit:\n", "0: ───H───@───\n", " │\n", "1: ───────X───\n", "\n", "Cirq results:\n", "[0.70710677+0.j 0. +0.j]\n", "\n", "qsim results:\n", "[(0.7071067690849304+0j), 0j]\n" ] } ], "source": [ "# Define a simple circuit.\n", "q0, q1 = cirq.LineQubit.range(2)\n", "circuit = cirq.Circuit(cirq.H(q0), cirq.CX(q0, q1))\n", "print(\"Circuit:\")\n", "print(circuit)\n", "print()\n", "\n", "# Simulate the circuit with qsim and return the amplitudes for |00) and |01).\n", "print('Cirq results:')\n", "cirq_simulator = cirq.Simulator()\n", "cirq_results = cirq_simulator.compute_amplitudes(\n", " circuit, bitstrings=[0b00, 0b01])\n", "print(cirq_results)\n", "print()\n", "\n", "# Simulate the circuit with qsim and return the amplitudes for |00) and |01).\n", "print('qsim results:')\n", "qsim_simulator = qsimcirq.QSimSimulator()\n", "qsim_results = qsim_simulator.compute_amplitudes(\n", " circuit, bitstrings=[0b00, 0b01])\n", "print(qsim_results)" ] }, { "cell_type": "markdown", "metadata": { "id": "zy5XRdkm219l" }, "source": [ "## Performance benchmark\n", "\n", "The code below generates a depth-16 circuit on a 4x5 qubit grid, then runs it against the basic Cirq simulator. For a circuit of this size, the difference in runtime can be significant - try it out!" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "id": "SyRpm08R3qCy" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Cirq runtime: 5.360399961471558 seconds.\n", "\n", "qsim runtime: 0.1109762191772461 seconds.\n" ] } ], "source": [ "import time\n", "\n", "# Get a rectangular grid of qubits.\n", "qubits = cirq.GridQubit.rect(4, 5)\n", "\n", "# Generates a random circuit on the provided qubits.\n", "circuit = cirq.experiments.random_rotations_between_grid_interaction_layers_circuit(\n", " qubits=qubits, depth=16)\n", "\n", "# Simulate the circuit with Cirq and print the runtime.\n", "cirq_simulator = cirq.Simulator()\n", "cirq_start = time.time()\n", "cirq_results = cirq_simulator.simulate(circuit)\n", "cirq_elapsed = time.time() - cirq_start\n", "print(f'Cirq runtime: {cirq_elapsed} seconds.')\n", "print()\n", "\n", "# Simulate the circuit with qsim and print the runtime.\n", "qsim_simulator = qsimcirq.QSimSimulator()\n", "qsim_start = time.time()\n", "qsim_results = qsim_simulator.simulate(circuit)\n", "qsim_elapsed = time.time() - qsim_start\n", "print(f'qsim runtime: {qsim_elapsed} seconds.')" ] }, { "cell_type": "markdown", "metadata": { "id": "OUjhKoRVZGKZ" }, "source": [ "qsim performance can be tuned further by passing options to the simulator constructor. These options use the same format as the qsim_base binary - a full description can be found in the qsim [usage doc](https://github.com/quantumlib/qsim/blob/master/docs/usage.md). The example below demonstrates enabling multithreading in qsim; for best performance, use the same number of threads as the number of cores (or virtual cores) on your machine." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "id": "yE6ZXAKzZL5P" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "qsim runtime: 0.12410116195678711 seconds.\n" ] } ], "source": [ "# Use eight threads to parallelize simulation.\n", "options = {'t': 8}\n", "\n", "qsim_simulator = qsimcirq.QSimSimulator(options)\n", "qsim_start = time.time()\n", "qsim_results = qsim_simulator.simulate(circuit)\n", "qsim_elapsed = time.time() - qsim_start\n", "print(f'qsim runtime: {qsim_elapsed} seconds.')" ] }, { "cell_type": "markdown", "metadata": { "id": "HVkTbqfH4zls" }, "source": [ "Another option is to adjust the maximum number of qubits over which to fuse gates. Increasing this value (as demonstrated below) increases arithmetic intensity, which may improve performance with the right environment settings." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "id": "kkQ5ARpI5phJ" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "qsim runtime: 0.12036395072937012 seconds.\n" ] } ], "source": [ "# Increase maximum fused gate size to three qubits.\n", "options = {'f': 3}\n", "\n", "qsim_simulator = qsimcirq.QSimSimulator(options)\n", "qsim_start = time.time()\n", "qsim_results = qsim_simulator.simulate(circuit)\n", "qsim_elapsed = time.time() - qsim_start\n", "print(f'qsim runtime: {qsim_elapsed} seconds.')" ] }, { "cell_type": "markdown", "metadata": { "id": "LgWOEMLKutsq" }, "source": [ "## Advanced applications: Distributed execution\n", "\n", "qsimh (qsim-hybrid) is a second library in the qsim repository that takes a slightly different approach to circuit simulation. When simulating a quantum circuit, it's possible to simplify the execution by decomposing a subset of two-qubit gates into pairs of one-qubit gates with shared indices. This operation is called \"slicing\" (or \"cutting\") the gates.\n", "\n", "qsimh takes advantage of the \"slicing\" operation by selecting a set of gates to \"slice\" and assigning each possible value of the shared indices across a set of executors running in parallel. By adding up the results afterwards, the total state can be recovered." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "id": "t3OkT3FKuuhp" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Circuit:\n", "(0, 0): ───H───@───────\n", " │\n", "(0, 1): ───────X───X───\n" ] } ], "source": [ "# Pick a pair of qubits.\n", "q0 = cirq.GridQubit(0, 0)\n", "q1 = cirq.GridQubit(0, 1)\n", "\n", "# Create a circuit that entangles the pair.\n", "circuit = cirq.Circuit(\n", " cirq.H(q0), cirq.CX(q0, q1), cirq.X(q1)\n", ")\n", "print(\"Circuit:\")\n", "print(circuit)" ] }, { "cell_type": "markdown", "metadata": { "id": "KUAwCOGeu0wA" }, "source": [ "In order to let qsimh know how we want to split up the circuit, we need to pass it some additional options. More detail on these can be found in the qsim [usage doc](https://github.com/quantumlib/qsim/blob/master/docs/usage.md), but the fundamentals are explained below." ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "id": "VXc-A8e7u262" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0j, (0.7071067690849304+0j), 0j, 0j]\n" ] } ], "source": [ "options = {}\n", "\n", "# 'k' indicates the qubits on one side of the cut.\n", "# We'll use qubit 0 for this.\n", "options['k'] = [0]\n", "\n", "# 'p' and 'r' control when values are assigned to cut indices.\n", "# There are some intricacies in choosing values for these options,\n", "# but for now we'll set p=1 and r=0.\n", "# This allows us to pre-assign the value of the CX indices\n", "# and distribute its execution to multiple jobs.\n", "options['p'] = 1\n", "options['r'] = 0\n", "\n", "# 'w' indicates the value pre-assigned to the cut.\n", "# This should change for each execution.\n", "options['w'] = 0\n", "\n", "# Create the qsimh simulator with those options.\n", "qsimh_simulator = qsimcirq.QSimhSimulator(options)\n", "results_0 = qsimh_simulator.compute_amplitudes(\n", " circuit, bitstrings=[0b00, 0b01, 0b10, 0b11])\n", "print(results_0)" ] }, { "cell_type": "markdown", "metadata": { "id": "eij3DsN4u5Om" }, "source": [ "Now to run the other side of the cut..." ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "id": "py6I1omau6Gk" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0j, 0j, (0.7071067690849304+0j), 0j]\n" ] } ], "source": [ "options['w'] = 1\n", "\n", "qsimh_simulator = qsimcirq.QSimhSimulator(options)\n", "results_1 = qsimh_simulator.compute_amplitudes(\n", " circuit, bitstrings=[0b00, 0b01, 0b10, 0b11])\n", "print(results_1)" ] }, { "cell_type": "markdown", "metadata": { "id": "wnjmLDedu7j4" }, "source": [ "...and add the two together. The results of a normal qsim simulation are shown for comparison." ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "id": "YQoyZ6ldu7--" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "qsimh results:\n", "[0j, (0.7071067690849304+0j), (0.7071067690849304+0j), 0j]\n", "qsim results:\n", "[0j, (0.7071067690849304+0j), (0.7071067690849304+0j), 0j]\n" ] } ], "source": [ "results = [r0 + r1 for r0, r1 in zip(results_0, results_1)]\n", "print(\"qsimh results:\")\n", "print(results)\n", "\n", "qsim_simulator = qsimcirq.QSimSimulator()\n", "qsim_simulator.compute_amplitudes(circuit, bitstrings=[0b00, 0b01, 0b10, 0b11])\n", "print(\"qsim results:\")\n", "print(results)" ] }, { "cell_type": "markdown", "metadata": { "id": "o11JcJJ8vEfW" }, "source": [ "The key point to note here is that `results_0` and `results_1` are completely independent - they can be run in parallel on two separate machines, with no communication between the two. Getting the full result requires `2^p` executions, but each individual result is much cheaper to calculate than trying to do the whole circuit at once." ] } ], "metadata": { "colab": { "collapsed_sections": [], "name": "qsimcirq.ipynb", "provenance": [], "toc_visible": true }, "environment": { "name": "tf2-cpu.2-5.m76", "type": "gcloud", "uri": "gcr.io/deeplearning-platform-release/tf2-cpu.2-5:m76" }, "kernelspec": { "display_name": "Python 3", "language": "python", "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.7.10" } }, "nbformat": 4, "nbformat_minor": 4 }