From da6a97e863b7363034c68ed1058bd7a42e7aad93 Mon Sep 17 00:00:00 2001 From: Vic Putz Date: Wed, 29 Jul 2020 16:25:48 -0500 Subject: [PATCH 1/6] Added comparison/benchmark notebook with pauli expectation values --- .../qiskit_to_quasar_graphing.ipynb | 493 ++++++++++++++++++ 1 file changed, 493 insertions(+) create mode 100644 notebook_examples/qiskit_to_quasar_graphing.ipynb diff --git a/notebook_examples/qiskit_to_quasar_graphing.ipynb b/notebook_examples/qiskit_to_quasar_graphing.ipynb new file mode 100644 index 0000000..cd0fa5c --- /dev/null +++ b/notebook_examples/qiskit_to_quasar_graphing.ipynb @@ -0,0 +1,493 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Running a Qiskit QAOA circuit with Quasar/Vulcan\n", + "\n", + "In this notebook, we will show how to take a QAOA circuit written by Qiskit, transform it into a Quasar circuit with *qusetta*, and then run it with various Quasar features. If you want to install *qusetta* in a virtual enviroment and then add your virtual enviroment to your list of jupyter kernels, see [this post](https://janakiev.com/blog/jupyter-virtual-envs/) for more details.\n", + "\n", + "**Contents**\n", + "1. [The problem](#1.-The-problem)\n", + "2. [The circuits](#2.-The-circuits)\n", + "3. [The cost function](#3.-The-cost-function)\n", + "4. [Getting the cover](#4.-Getting-the-cover)\n", + "5. [Simulating the circuits](#5.-Simulating-the-circuits)\n", + "6. [Optimizing the QAOA parameters](#6.-Optimizing-the-QAOA-parameters)\n", + "7. [Outlook](#7.-Outlook)\n", + "---" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!pip install qiskit-aer\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + " !pip install git+https://github.com/qcware/qusetta" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# install tqdm for progress bars\n", + "!pip install tqdm\n", + "from tqdm.notebook import tnrange, tqdm" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. The problem\n", + "\n", + "For this example, we will work with the Vertex Cover problem. We will follow the procedure in this [qiskit example notebook](https://github.com/Qiskit/qiskit-community-tutorials/blob/master/optimization/vertex_cover.ipynb) to create the problem and the QAOA circuit in qiskit.\n", + "\n", + "We'll begin exactly as they begin -- by creating a random graph (and seeding random for reproducability of the notebook)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "#!pip install qiskit==0.19.0\n", + "#!pip install qiskit-aqua\n", + "from qiskit.optimization.applications.ising.common import random_graph\n", + "\n", + "np.random.seed(123)\n", + "num_nodes = 6\n", + "w = random_graph(num_nodes, edge_prob=0.8, weight_range=10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next we'll create the qubit operator and the corresponding QAOA instance. We'll fix a depth $p$ to work with throughout this notebook." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from qiskit.optimization.applications.ising import vertex_cover\n", + "from qiskit.aqua.algorithms import QAOA\n", + "\n", + "qubit_op, offset = vertex_cover.get_operator(w)\n", + "\n", + "p = 4\n", + "qaoa = QAOA(qubit_op, p=p)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. The circuits\n", + "\n", + "Now we'll write a function that takes in $\\beta_1, \\dots, \\beta_p$, and $\\gamma_1, \\dots, \\gamma_p$ and outputs the corrresponding qiskit QAOA circuit. Note that `params` is a list such that `params[:p]` is $[\\gamma_1, \\dots, \\gamma_p]$ and `params[p:]` is $[\\beta_1, \\dots, \\beta_p]$." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import qiskit\n", + "from typing import List\n", + "\n", + "def create_qiskit_circuit(params: List[float]) -> qiskit.QuantumCircuit:\n", + " assert len(params) == 2 * p, \"invalid number of angles\"\n", + " return qaoa.var_form.construct_circuit(params)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next we'll write a function that uses *qusetta* to convert the qiskit circuit to a quasar circuit." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#!pip install git+https://github.com/qcware/qusetta\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import qusetta as qs\n", + "import quasar\n", + "# import vulcan\n", + "\n", + "def create_quasar_circuit(params: List[float]) -> quasar.Circuit:\n", + " qiskit_circuit = create_qiskit_circuit(params)\n", + " return qs.Qiskit.to_quasar(qiskit_circuit)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. The cost function\n", + "\n", + "The cost function of the circuit is the expectation value of the qubit operator." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def expectation_value(statevector: np.ndarray) -> float:\n", + " # note that the second element (eg [1]) is the standard deviation\n", + " return offset + qubit_op.evaluate_with_statevector(statevector)[0].real" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Getting the cover\n", + "\n", + "We can get the Vertex Cover from the statevector outputted by the circuit. We'll choose the cover that we have the highest probability of sampling from the statevector as is done in qiskit's original notebook." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from qiskit.optimization.applications.ising.common import sample_most_likely\n", + "\n", + "def get_cover(statevector: np.ndarray) -> List[int]:\n", + " return [\n", + " int(x) for x in\n", + " vertex_cover.get_graph_solution(sample_most_likely(statevector))\n", + " ]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Benchmarking #\n", + "\n", + "Let's generate a number of circuits in increasing size using Qiskit, convert them to Quasar, and then time evaluations of those circuits with some expectation value operators applied to the statevectors." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Generation of Circuits in Qiskit #\n", + "\n", + "Let's create a list of circuits:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "import numpy as np\n", + "from qiskit.optimization.applications.ising.common import random_graph\n", + "from qiskit.optimization.applications.ising import vertex_cover\n", + "from qiskit.aqua.algorithms import QAOA\n", + "from qcware.circuits.quasar_backend import QuasarBackend\n", + "\n", + "Max_circuit_width = 20\n", + "# Our circuit widths in number of qubits\n", + "Circuit_widths = list(range(4, Max_circuit_width+1, 2))\n", + "# right now hardcoding the parameters at 4, which is a little ridiculous as they should \n", + "# expand as we increase qubits\n", + "Num_parameters = [1]*len(Circuit_widths)\n", + "Parameters = [list(np.random.random(2*x)*np.pi) for x in Num_parameters]\n", + "# print(Parameters)\n", + "\n", + "def create_qiskit_circuit(num_nodes: int, params: List[float], edge_prob=0.8, weight_range=10)->qiskit.QuantumCircuit:\n", + " # print(locals())\n", + " np.random.seed(123)\n", + " w = random_graph(num_nodes, edge_prob=edge_prob, weight_range=weight_range)\n", + " qubit_op, offset = vertex_cover.get_operator(w)\n", + " qaoa = QAOA(qubit_op, p=int(len(params)/2))\n", + " return qaoa.var_form.construct_circuit(params)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Building the Circuits #\n", + "\n", + "Now let's build the circuits; this can take a couple of minutes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "qiskit_circuits = [create_qiskit_circuit(width, parameters) for (width, parameters) in tqdm(list(zip(Circuit_widths, Parameters)), desc=\"Creating Qiskit circuits\")]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Conversion to Quasar ##\n", + "\n", + "We'll also make a list of quasar circuits converted from these qiskit circuits using `qusetta`. As you can see, conversion is extremely fast:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "quasar_circuits = [qs.Qiskit.to_quasar(circuit) for circuit in tqdm(qiskit_circuits, desc=\"Converting Qiskit circuits to Quasar\")]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Pauli expectation values ##\n", + "\n", + "For each circuit we're going to evaluate the statevector and then calculate some expectation value on the result. These expectation values will be calculated by the creation of a Pauli operator using the `quasar` toolkit." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "I,X,Y,Z = quasar.Pauli.IXYZ()\n", + "\n", + "def x_expectation_pauli(num_qubits: int)->quasar.Pauli:\n", + " result = quasar.Pauli()\n", + " for i in range(num_qubits):\n", + " result += X[i]\n", + " return result\n", + "\n", + "#p = x_expectation_pauli(3)\n", + "#print(p)\n", + "#print(p.to_matrix())\n", + "paulis = [x_expectation_pauli(x) for x in tqdm(Circuit_widths, desc=\"Generating Paulis\")]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Timing the implementations #\n", + "\n", + "We'll build just a simple decorator to take the clock time before and after a function so we can time how long it takes to evaluate the circuits we've built and apply some expectation value operator to them. This isn't a sophisticated benchmark, but should give us a good idea of the speedup to expect. It simply takes a function and decorates it so that it returns a tuple of `(time in evaluation, function result)`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from functools import wraps\n", + "import time\n", + "def timed_function(f):\n", + " @wraps(f)\n", + " def wrapper(*args, **kwargs):\n", + " start_time = time.time()\n", + " result = f(*args, **kwargs)\n", + " end_time = time.time()\n", + " time_taken = end_time - start_time\n", + " return (time_taken, result)\n", + " return wrapper\n", + " \n", + "# to demonstrate\n", + "@timed_function\n", + "def simple_demo():\n", + " return 42*42\n", + " \n", + "print(simple_demo())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Timing the Qiskit engine #\n", + "\n", + "Now let's get timing information for our circuits by timing the evaluation in Qiskit:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "@timed_function\n", + "def eval_qiskit(circuit: qiskit.QuantumCircuit, pauli: quasar.Pauli):\n", + " backend = qiskit.BasicAer.get_backend('statevector_simulator')\n", + " statevector = qiskit.execute(circuit, backend).result().get_statevector()\n", + " # print(statevector)\n", + " # do magical statevector expectation operator pauli stuff here\n", + " # op = pauli.to_matrix()\n", + " # return statevector * op * statevector.T\n", + " # that runs out of memory real fast obviously so use the sparser\n", + " # Pauli matrix_vector_product\n", + " return np.sum(statevector.conj() * pauli.matrix_vector_product(statevector))\n", + " \n", + "#print(qiskit_circuits[0])\n", + "#eval_qiskit(qiskit_circuits[0], paulis[0])\n", + "qiskit_results = [eval_qiskit(circuit, pauli) for (circuit, pauli) in tqdm(list(zip(qiskit_circuits, paulis)), desc=\"Evaluating qiskit circuits\")]\n", + "qiskit_times = [x[0] for x in qiskit_results]\n", + "qiskit_expectation_values = [x[1] for x in qiskit_results]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Timing Vulcan #\n", + "\n", + "We can use the QCWare Vulcan engine to evaluate the same circuits in a fraction of the time:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import qcware\n", + "from qcware.circuits.quasar_backend import QuasarBackend\n", + "qcware.config.set_host(\"https://api.hammer.qcware.com\")\n", + "qcware.config.set_api_key(\"QCWARE\")\n", + "\n", + "@timed_function\n", + "def eval_vulcan(circuit: quasar.Circuit, pauli: quasar.Pauli):\n", + " backend = QuasarBackend('vulcan/simulator')\n", + " #sv = backend.run_statevector(circuit=circuit)\n", + " #print(sv)\n", + " result = backend.run_pauli_expectation_value(circuit=circuit, pauli=pauli)\n", + " return result\n", + " \n", + "#print(quasar_circuits[0])\n", + "#eval_vulcan(quasar_circuits[0], paulis[0])\n", + "vulcan_results = [eval_vulcan(circuit, pauli) for (circuit, pauli) in tqdm(list(zip(quasar_circuits, paulis)), desc=\"Evaluating circuits with vulcan\")]\n", + "vulcan_times = [x[0] for x in vulcan_results]\n", + "vulcan_expectation_values = [x[1] for x in vulcan_results]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"Are the results similar?\")\n", + "print(np.allclose(qiskit_expectation_values, vulcan_expectation_values))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that sometimes even very small numerical differences between the simulators can have an effect on the optimization routine (this often has a bigger effect as $p$ gets larger), so the results may be different." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. Outlook\n", + "\n", + "The quasar simulator slightly outperformed the qiskit simulator. But quasar's Vulcan simulator *significantly* speeds up the optimization process." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "xs = Circuit_widths\n", + "plt.figure(figsize=(10,7))\n", + "plt.plot(xs, qiskit_times, 'ro-', label=\"qiskit\" )\n", + "plt.plot(xs, vulcan_times, 'go-', label=\"vulcan forge measurement\")\n", + "plt.xlabel('number of qubits')\n", + "plt.ylabel('seconds for evaluation')\n", + "plt.legend()\n", + "ax2 = plt.gca().twinx()\n", + "ax2.set_ylabel(\"speedup\")\n", + "ax2.plot(xs, np.array(qiskit_times)/np.array(vulcan_times), 'bo-', label='speedup')\n", + "ax2.legend(loc='upper center')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Back to top](#Running-a-Qiskit-QAOA-circuit-with-Quasar/Vulcan)." + ] + } + ], + "metadata": { + "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.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From cfd96e20fbd7a23421be338483d35820edb6f127 Mon Sep 17 00:00:00 2001 From: Vic Putz Date: Wed, 26 Aug 2020 16:07:56 -0500 Subject: [PATCH 2/6] Initial optional import for Qusetta --- qusetta/__init__.py | 17 +++++++++++++++-- qusetta/_cirq.py | 1 - qusetta/_qiskit.py | 1 - setup.py | 11 +++++++---- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/qusetta/__init__.py b/qusetta/__init__.py index fa3310e..720821b 100644 --- a/qusetta/__init__.py +++ b/qusetta/__init__.py @@ -22,8 +22,21 @@ from ._gates import * from ._conversions import * -from ._cirq import * -from ._qiskit import * +import warnings + +try: + import qiskit + from ._qiskit import * +except (ImportError, ModuleNotFoundError) as e: + pass + + +try: + import cirq + from ._cirq import * +except (ImportError, ModuleNotFoundError) as e: + pass + from ._quasar import * __all__ = "Cirq", "Qiskit", "Quasar" diff --git a/qusetta/_cirq.py b/qusetta/_cirq.py index bd6994d..27002de 100644 --- a/qusetta/_cirq.py +++ b/qusetta/_cirq.py @@ -1,5 +1,4 @@ """Translating circuits to and from ``cirq``.""" - import cirq import qusetta as qs from typing import List diff --git a/qusetta/_qiskit.py b/qusetta/_qiskit.py index 84616cf..aa61dea 100644 --- a/qusetta/_qiskit.py +++ b/qusetta/_qiskit.py @@ -1,5 +1,4 @@ """Translating circuits to and from ``qiskit``.""" - import qiskit import qusetta as qs from typing import List diff --git a/setup.py b/setup.py index 8a94389..bc1852e 100644 --- a/setup.py +++ b/setup.py @@ -11,9 +11,6 @@ with open('README.rst') as f: README = f.read() -with open("requirements.txt") as f: - REQUIREMENTS = [line.strip() for line in f if line.strip()] - # get __version__, __author__, etc. with open("qusetta/_version.py") as f: exec(f.read()) @@ -31,7 +28,13 @@ license=__license__, packages=setuptools.find_packages(exclude=("tests", "docs")), test_suite="tests", - install_requires=REQUIREMENTS, + install_requires=[ + "qcware-quasar>=1.0.0" + ], + extras_require={ + "cirq": ["cirq>=0.8.0"], + "qiskit": ["qiskit>=0.19.0"] + }, include_package_data=True, classifiers=[ "Programming Language :: Python :: 3", From 2f681244b59890bd1d76679ae28c2ab74524e086 Mon Sep 17 00:00:00 2001 From: "Joseph T. Iosue" Date: Thu, 27 Aug 2020 12:35:31 -0400 Subject: [PATCH 3/6] Update __init__.py --- qusetta/__init__.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/qusetta/__init__.py b/qusetta/__init__.py index 720821b..e7e76b3 100644 --- a/qusetta/__init__.py +++ b/qusetta/__init__.py @@ -22,23 +22,22 @@ from ._gates import * from ._conversions import * -import warnings +from ._quasar import * + +__all__ = "Quasar", try: import qiskit from ._qiskit import * + __all__ += "Qiskit", except (ImportError, ModuleNotFoundError) as e: pass - try: import cirq from ._cirq import * + __all__ += "Cirq", except (ImportError, ModuleNotFoundError) as e: pass -from ._quasar import * - -__all__ = "Cirq", "Qiskit", "Quasar" - name = "qusetta" From 5aa3eaf60dff5f02d4748155a807fa2547b4592b Mon Sep 17 00:00:00 2001 From: "Joseph T. Iosue" Date: Thu, 27 Aug 2020 18:02:52 -0400 Subject: [PATCH 4/6] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bc1852e..cccc2d3 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ ], extras_require={ "cirq": ["cirq>=0.8.0"], - "qiskit": ["qiskit>=0.19.0"] + "qiskit": ["qiskit-aer>=0.6.0"] }, include_package_data=True, classifiers=[ From 94aec63c7fccd0689a5b729350123f8d80ed027e Mon Sep 17 00:00:00 2001 From: "Joseph T. Iosue" Date: Thu, 27 Aug 2020 18:03:26 -0400 Subject: [PATCH 5/6] Delete requirements.txt --- requirements.txt | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index e449434..0000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -cirq>=0.8.0 -qiskit>=0.19.0 -qcware-quasar>=1.0.0 From 71234f77f786bfcd666a7bb6b7310da0988c8137 Mon Sep 17 00:00:00 2001 From: "Joseph T. Iosue" Date: Thu, 27 Aug 2020 18:03:40 -0400 Subject: [PATCH 6/6] Update MANIFEST.in --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 645a28c..0c73842 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1 @@ -include README.rst LICENSE requirements.txt +include README.rst LICENSE