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 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 +} diff --git a/qusetta/__init__.py b/qusetta/__init__.py index fa3310e..e7e76b3 100644 --- a/qusetta/__init__.py +++ b/qusetta/__init__.py @@ -22,10 +22,22 @@ from ._gates import * from ._conversions import * -from ._cirq import * -from ._qiskit import * from ._quasar import * -__all__ = "Cirq", "Qiskit", "Quasar" +__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 name = "qusetta" 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/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 diff --git a/setup.py b/setup.py index 8a94389..cccc2d3 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-aer>=0.6.0"] + }, include_package_data=True, classifiers=[ "Programming Language :: Python :: 3",