diff --git a/examples/qtensor-vertex-cover.ipynb b/examples/qtensor-vertex-cover.ipynb new file mode 100644 index 00000000..3152242e --- /dev/null +++ b/examples/qtensor-vertex-cover.ipynb @@ -0,0 +1,332 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "e54d32e8-4528-4c68-9064-bf60c74673fe", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "import networkx as nx\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0a211498-211b-4a87-9762-d6c9f6652c4e", + "metadata": {}, + "outputs": [], + "source": [ + "sys.path.append(\"../\")" + ] + }, + { + "cell_type": "markdown", + "id": "89452af5-c62b-4e46-bd3a-840adc78ab7c", + "metadata": {}, + "source": [ + "## Sanity check" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3b757515-99d8-44e5-8c53-c370a3b1a331", + "metadata": {}, + "outputs": [], + "source": [ + "from qtensor import QAOA_energy\n", + "\n", + "G = nx.random_regular_graph(3, 10)\n", + "gamma, beta = [np.pi/3], [np.pi/2]\n", + "\n", + "E = QAOA_energy(G, gamma, beta)" + ] + }, + { + "cell_type": "markdown", + "id": "5c466f06-511a-483b-8f15-e0aad686bcec", + "metadata": {}, + "source": [ + "## Vertex Cover: Qiskit" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "508eb5b9-782b-4213-a797-5116d847d6b8", + "metadata": {}, + "outputs": [], + "source": [ + "import qiskit\n", + "qiskit.__qiskit_version__" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c3f64065-7a1f-4183-a887-813a235958ee", + "metadata": {}, + "outputs": [], + "source": [ + "# Qtensor branch: dev\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import networkx as nx\n", + "from qtensor import QAOAComposer\n", + "from qtensor import CirqQAOAComposer, QtreeQAOAComposer\n", + "from qtensor import VCQiskitQAOAComposer, PCQiskitQAOAComposer\n", + "from qtensor import VCQtreeQAOAComposer, PCQtreeQAOAComposer" + ] + }, + { + "cell_type": "markdown", + "id": "f8b97479-45a4-4b8c-aa64-adda17f1d7eb", + "metadata": {}, + "source": [ + "### QAOA problem graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5dbf5964-25d8-43d8-b11d-7e205dc4cd45", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "G = nx.erdos_renyi_graph(4, 2/(5-1))\n", + "nx.draw_kamada_kawai(G, with_labels=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0ce1f7b9-49f9-4fe1-8706-495fbad08a09", + "metadata": {}, + "outputs": [], + "source": [ + "p = 1\n", + "qiskit_qaoa = VCQiskitQAOAComposer(G, gamma=[.1]*p, beta=[.2]*p)\n", + "qiskit_qaoa.ansatz_state()\n", + "qiskit_qaoa.circuit.draw('mpl')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7d06acf1-f0a7-4b3d-b3e8-2044b9e662b8", + "metadata": {}, + "outputs": [], + "source": [ + "p = 1\n", + "qiskit_qaoa = PCQiskitQAOAComposer(G, gamma=[.1]*p, beta=[.2]*p)\n", + "qiskit_qaoa.ansatz_state()\n", + "qiskit_qaoa.circuit.draw('mpl')" + ] + }, + { + "cell_type": "markdown", + "id": "17285e25-e4a8-48f3-830f-819003df454e", + "metadata": {}, + "source": [ + "## Simulate circuits\n", + "## Use lightcone optimisaiton\n", + "\n", + "Suppose we are interested in an expectation value of particular operator in a state $|\\psi\\rangle = \\hat U | 0\\rangle$. \n", + "We can use the fact that in the expression\n", + "$$\\langle \\psi | \\hat E | \\psi \\rangle = \\langle 0 | \\hat U^\\dagger \\hat E \\hat U |0\\rangle$$\n", + "a lot of operators from $\\hat U$ cancel out." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bacfda45-45de-48c6-84b2-700595abff81", + "metadata": {}, + "outputs": [], + "source": [ + "p = 2\n", + "qiskit_qaoa = VCQiskitQAOAComposer(G, gamma=[.1]*p, beta=[.2]*p)\n", + "qiskit_qaoa.energy_expectation_lightcone((0,1))\n", + "qiskit_qaoa.circuit.draw('mpl')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a96e8f63-c3ee-4918-bc38-bd60f511d305", + "metadata": {}, + "outputs": [], + "source": [ + "p = 2\n", + "qiskit_qaoa = PCQiskitQAOAComposer(G, gamma=[.1]*p, beta=[.2]*p)\n", + "qiskit_qaoa.energy_expectation_lightcone((0,1))\n", + "qiskit_qaoa.circuit.draw('mpl')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e0500a31-2cc0-4cba-9169-0c47fc74a969", + "metadata": {}, + "outputs": [], + "source": [ + "com = VCQtreeQAOAComposer(G, gamma=[.1]*p, beta=[.2]*p)\n", + "com.energy_expectation_lightcone(list(G.edges())[0])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0473fced-0447-4ecf-ae0d-e964b7511cdb", + "metadata": {}, + "outputs": [], + "source": [ + "com = PCQtreeQAOAComposer(G, gamma=[.1]*p, beta=[.2]*p)\n", + "com.energy_expectation_lightcone(list(G.edges())[0])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1da40689-a891-4599-9d75-2ab1bcd6c861", + "metadata": {}, + "outputs": [], + "source": [ + "from qtensor.QAOASimulator import QAOAQtreeSimulator\n", + "qaoa_sim = QAOAQtreeSimulator(VCQtreeQAOAComposer)\n", + "\n", + "qaoa_sim.energy_expectation(G, gamma=[.1]*p, beta=[.2]*p)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "79f55af2-a9aa-44f5-beb4-a7180545c6f8", + "metadata": {}, + "outputs": [], + "source": [ + "from qtensor.QAOASimulator import QAOAQtreeSimulator\n", + "qaoa_sim = QAOAQtreeSimulator(PCQtreeQAOAComposer)\n", + "\n", + "p=3\n", + "qaoa_sim.energy_expectation(G, gamma=[.1]*p, beta=[.2]*p)" + ] + }, + { + "cell_type": "markdown", + "id": "b158dcfe-404a-4e4b-9654-2d5ee32cde01", + "metadata": {}, + "source": [ + "## QAOA" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "166e32f4-ddce-45f8-96e5-1bc12ef0a8c8", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from scipy.optimize import minimize" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4f860402-2c18-463a-8136-8f13e47cc7d7", + "metadata": {}, + "outputs": [], + "source": [ + "edges = [(0, 1), (1, 2), (2, 0), (2, 3)]\n", + "graph = nx.Graph(edges)\n", + "\n", + "nx.draw(graph, with_labels=True)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0d1495c2-fe6b-4b98-89fb-33c1fe8fb196", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "p=3\n", + "initial_params = np.array([.1]*2*p)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ca2cf6ef-7f52-453c-beb6-906fb863afe8", + "metadata": {}, + "outputs": [], + "source": [ + "def cost_function(params):\n", + " p = len(params) // 2\n", + "\n", + " # Split the single list into two lists\n", + " gammas = params[:p]\n", + " betas = params[p:]\n", + " expectation = qaoa_sim.energy_expectation(graph, gammas, betas)\n", + " return expectation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16de68dc-291a-4bf5-a358-845392b8f9d8", + "metadata": {}, + "outputs": [], + "source": [ + "# Minimize the function\n", + "result = minimize(cost_function, initial_params)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5716d24f-9e74-4e07-afe5-c56ee70f667f", + "metadata": {}, + "outputs": [], + "source": [ + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7bb1e7c3-285c-4440-8255-7722db98be57", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qtensor/CircuitComposer.py b/qtensor/CircuitComposer.py index 08f3ea13..c3162660 100644 --- a/qtensor/CircuitComposer.py +++ b/qtensor/CircuitComposer.py @@ -147,13 +147,10 @@ def append_zz_term(self, q1, q2, gamma): self.apply_gate(self.operators.cX, q1, q2) self.apply_gate(self.operators.ZPhase, q2, alpha=2*gamma) self.apply_gate(self.operators.cX, q1, q2) - def cost_operator_circuit(self, gamma, edges=None): - if edges is None: edges = self.graph.edges() - for i, j in edges: - u, v = self.qubit_map[i], self.qubit_map[j] - self.append_zz_term(u, v, gamma) - - + + def z_term(self, u, gamma): + self.apply_gate(self.operators.ZPhase, u, alpha=2*gamma) + def ansatz_state(self): beta, gamma = self.params['beta'], self.params['gamma'] @@ -202,11 +199,20 @@ def energy_expectation(self, i, j): self.circuit = first_part + second_part - +class MaxCutComposer(QAOAComposer): + def cost_operator_circuit(self, gamma, edges=None): + if edges is None: edges = self.graph.edges() + for i, j in edges: + u, v = self.qubit_map[i], self.qubit_map[j] + self.append_zz_term(u, v, gamma) + class ZZQAOAComposer(QAOAComposer): def append_zz_term(self, q1, q2, gamma): self.apply_gate(self.operators.ZZ, q1, q2, alpha=2*gamma) +class MaxCutZZComposer(MaxCutComposer, ZZQAOAComposer): + pass + class QAOAComposerChords(ZZQAOAComposer): def cone_ansatz(self, edge): beta, gamma = self.params['beta'], self.params['gamma'] @@ -230,3 +236,74 @@ def cost_operator_circuit(self, gamma, edges=None): for i, j, w in self.graph.edges.data('weight', default=1): u, v = self.qubit_map[i], self.qubit_map[j] self.append_zz_term(u, v, gamma*w) + +class VCQAOAComposer(QAOAComposer): + def energy_expectation(self, i, j): + # Will need to deprecate stateful API and return the circuit + self.cone_ansatz(edge=(i, j)) + self.energy_edge(i, j) + first_part = self.builder.circuit + self.builder.reset() + self.cone_ansatz(edge=(i, j)) + self.builder.inverse() + second_part = self.builder.circuit + self.circuit = first_part.compose(second_part) + + def cost_operator_circuit(self, gamma, edges=None): + if edges is None: edges = self.graph.edges() + nodes = self.graph.nodes() + for i, j in edges: + u, v = self.qubit_map[i], self.qubit_map[j] + self.append_zz_term(u, v, gamma*3) + self.z_term(u, gamma*3) + self.z_term(v, gamma*3) + for i in nodes: + u = self.qubits[i] + self.z_term(u, gamma * (-1)) + +class PCQAOAComposer(QAOAComposer): + def energy_expectation(self, i, j): + # Will need to deprecate stateful API and return the circuit + self.cone_ansatz(edge=(i, j)) + self.energy_edge(i, j) + first_part = self.builder.circuit + self.builder.reset() + self.cone_ansatz(edge=(i, j)) + self.builder.inverse() + second_part = self.builder.circuit + self.circuit = first_part.compose(second_part) + + + def cost_operator_circuit(self, gamma, edges=None): + if edges is None: edges = self.graph.edges() + nodes = self.graph.nodes() + for i, j in edges: + u, v = self.qubit_map[i], self.qubit_map[j] + self.append_zz_term(u, v, gamma*(-1)) + self.z_term(u, gamma) + self.z_term(v, gamma) + for i in nodes: + u = self.qubits[i] + self.z_term(u, gamma * (-1)) + +class VCZZQAOAComposer(VCQAOAComposer, ZZQAOAComposer): + def energy_expectation(self, i, j): + self.cone_ansatz(edge=(i, j)) + self.energy_edge(i, j) + first_part = self.builder.circuit + self.builder.reset() + self.cone_ansatz(edge=(i, j)) + self.builder.inverse() + second_part = self.builder.circuit + self.circuit = first_part + second_part + +class PCZZQAOAComposer(PCQAOAComposer, ZZQAOAComposer): + def energy_expectation(self, i, j): + self.cone_ansatz(edge=(i, j)) + self.energy_edge(i, j) + first_part = self.builder.circuit + self.builder.reset() + self.cone_ansatz(edge=(i, j)) + self.builder.inverse() + second_part = self.builder.circuit + self.circuit = first_part + second_part \ No newline at end of file diff --git a/qtensor/QAOASimulator.py b/qtensor/QAOASimulator.py index 4e9b9fc3..b4e97372 100644 --- a/qtensor/QAOASimulator.py +++ b/qtensor/QAOASimulator.py @@ -228,3 +228,59 @@ def _get_edge_energy(self, G, gamma, beta, edge): trial_result = self.simulate(circuit) return np.sum(trial_result.state_vector()) pass + +class VCQAOASimulator(QtreeSimulator): + def energy_expectation(self, G, gamma, beta): + """ + Arguments: + G: VertexCover graph, Networkx + gamma, beta: list[float] + Returns: VertexCover energy expectation + """ + total_E = 0 + with tqdm(total=G.number_of_edges(), desc='Edge iteration', ) as pbar: + for i, edge in enumerate(G.edges()): + E = self._get_edge_energy(G, gamma, beta, edge) + # debt + pbar.set_postfix(Treewidth=self.optimizer.treewidth) + pbar.update(1) + total_E += E + if self.profile: + # debt + print(self.backend.gen_report()) + return total_E + +class PCQAOASimulator(QtreeSimulator): + def _post_process_energy(self, G, E): + if np.imag(E).any()>1e-6: + print(f"Warning: Energy result imaginary part was: {np.imag(E)}") + """ + Calculate final energy of Profit Cover by adding the offsets + """ + E = np.real(E) + + Ed = G.number_of_edges() + V = G.number_of_nodes() + + return E - Ed/4 + V/2 + + def energy_expectation(self, G, gamma, beta): + """ + Arguments: + G: ProfitCover graph, Networkx + gamma, beta: list[float] + Returns: ProfitCover energy expectation + """ + total_E = 0 + with tqdm(total=G.number_of_edges(), desc='Edge iteration', ) as pbar: + for i, edge in enumerate(G.edges()): + E = self._get_edge_energy(G, gamma, beta, edge) + # debt + pbar.set_postfix(Treewidth=self.optimizer.treewidth) + pbar.update(1) + total_E += E + if self.profile: + # debt + print(self.backend.gen_report()) + C = self._post_process_energy(G, total_E) + return C diff --git a/qtensor/__init__.py b/qtensor/__init__.py index f30a7f7d..8643649a 100644 --- a/qtensor/__init__.py +++ b/qtensor/__init__.py @@ -8,13 +8,14 @@ from qtensor.utils import get_edge_subgraph import networkx as nx -from .CircuitComposer import QAOAComposer, OldQAOAComposer, ZZQAOAComposer, WeightedZZQAOAComposer, CircuitComposer +from .CircuitComposer import QAOAComposer, OldQAOAComposer, ZZQAOAComposer, WeightedZZQAOAComposer, CircuitComposer, VCQAOAComposer, PCQAOAComposer, VCZZQAOAComposer, PCZZQAOAComposer, MaxCutComposer from .OpFactory import CirqBuilder, QtreeBuilder, QiskitBuilder, TorchBuilder from .OpFactory import QtreeFullBuilder from qtensor.Simulate import CirqSimulator, QtreeSimulator from qtensor.QAOASimulator import QAOAQtreeSimulator from qtensor.QAOASimulator import QAOACirqSimulator from qtensor.QAOASimulator import QAOAQtreeSimulatorSymmetryAccelerated +from qtensor.QAOASimulator import VCQAOASimulator from qtensor.FeynmanSimulator import FeynmanSimulator, FeynmanMergedSimulator from qtensor import contraction_backends from qtensor.contraction_backends import PerfNumpyBackend, NumpyBackend @@ -33,8 +34,24 @@ def _get_builder_class(self): class QiskitQAOAComposer(QAOAComposer): def _get_builder_class(self): return QiskitBuilder - -class QtreeQAOAComposer(QAOAComposer): + +class VCQiskitQAOAComposer(VCQAOAComposer): + def _get_builder_class(self): + return QiskitBuilder + +class PCQiskitQAOAComposer(PCQAOAComposer): + def _get_builder_class(self): + return QiskitBuilder + +class VCQtreeQAOAComposer(VCZZQAOAComposer): + def _get_builder_class(self): + return QtreeBuilder + +class PCQtreeQAOAComposer(PCZZQAOAComposer): + def _get_builder_class(self): + return QtreeBuilder + +class QtreeQAOAComposer(MaxCutComposer): def _get_builder_class(self): return QtreeBuilder @@ -89,4 +106,4 @@ def QAOA_energy(G, gamma, beta, n_processes=0): return res -from . import toolbox +from . import toolbox \ No newline at end of file