From 185371584c00351b6a231830b0a63e85bb4c394c Mon Sep 17 00:00:00 2001 From: pangara Date: Fri, 23 Feb 2024 10:27:59 -0800 Subject: [PATCH 1/3] Extend qtensor functionality for vertex cover problems --- qtensor/CircuitComposer.py | 178 +++++++++++++++++++++++-------------- qtensor/QAOASimulator.py | 162 ++++++++++++--------------------- qtensor/__init__.py | 43 +++++---- 3 files changed, 196 insertions(+), 187 deletions(-) diff --git a/qtensor/CircuitComposer.py b/qtensor/CircuitComposer.py index 08f3ea13..a3861184 100644 --- a/qtensor/CircuitComposer.py +++ b/qtensor/CircuitComposer.py @@ -1,7 +1,6 @@ from loguru import logger as log +from qtensor.utils import get_edge_subgraph import networkx as nx -import qtensor -from qtensor import utils from .OpFactory import CircuitBuilder class CircuitComposer(): @@ -51,41 +50,6 @@ def layer_of_Hadamards(self): for q in self.qubits: self.apply_gate(self.operators.H, q) - def expectation(self, operator, *qubits, simplify='auto', **params): - """ - Args: - operator: an element from OpFactory - qubits: qubits to apply the gate to - params: params to pass on application gate - - Returns: - a circuit, 0th amplitude of which evaluates to expectation value - """ - - # TODO: should return a tensor network, - # no circuit returns expectations - first_part = self.builder.copy() - first_part.apply_gate(operator, *qubits, **params) - second_part = self.builder.copy() - second_part.inverse() - - circ = first_part.circuit + second_part.circuit - do_simplify = len(circ) < 100 - if simplify is True: - do_simplify = True - if simplify is False: - do_simplify = False - - if do_simplify: - try: - circ = qtensor.simplify_circuit.simplify_qtree_circuit(circ) - except Exception as e: - - print('failed to simplify:', type(e), e) - raise - - return circ - class OldQAOAComposer(CircuitComposer): """ Abstract base class for QAOA Director """ @@ -94,7 +58,6 @@ def __init__(self, graph, *args, **kwargs): super().__init__(*args, **kwargs) self.graph = graph - self.qubit_map = {n: i for i, n in enumerate(graph.nodes())} def _get_builder(self): return self._get_builder_class()(self.n_qubits) @@ -120,7 +83,7 @@ def energy_expectation_lightcone(self, edge): G = self.graph gamma, beta = self.params['gamma'], self.params['beta'] i,j = edge - graph = utils.get_edge_subgraph(G, edge, len(gamma)) + graph = get_edge_subgraph(G, edge, len(gamma)) log.debug('Subgraph nodes: {}, edges: {}', graph.number_of_nodes(), graph.number_of_edges()) self.n_qubits = graph.number_of_nodes() mapping = {v:i for i, v in enumerate(graph.nodes())} @@ -137,21 +100,20 @@ def x_term(self, u, beta): #self.circuit.append(self.operators.H(u)) self.apply_gate(self.operators.XPhase, u, alpha=2*beta) #self.circuit.append(self.operators.H(u)) + + def z_term(self, u, gamma): + self.apply_gate(self.operators.ZPhase, u, alpha=2*gamma) + def mixer_operator(self, beta, nodes=None): if nodes is None: nodes = self.graph.nodes() for n in nodes: - qubit = self.qubit_map[n] + qubit = self.qubits[n] self.x_term(qubit, beta) 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 ansatz_state(self): @@ -166,7 +128,7 @@ def ansatz_state(self): self.mixer_operator(beta[i]) def energy_edge(self, i, j): - u, v = self.qubit_map[i], self.qubit_map[j] + u, v = self.qubits[i], self.qubits[j] self.apply_gate(self.operators.Z, u) self.apply_gate(self.operators.Z, v) @@ -182,9 +144,8 @@ def cone_ansatz(self, edge): cone_base = self.graph for i, g, b in zip(range(p, 0, -1), gamma, beta): - self.graph = utils.get_edge_subgraph(cone_base, edge, i) + self.graph = get_edge_subgraph(cone_base, edge, i) self.cost_operator_circuit(g) - self.graph = utils.get_edge_subgraph(cone_base, edge, i-1) self.mixer_operator(b) self.graph = cone_base @@ -201,32 +162,119 @@ def energy_expectation(self, i, j): second_part = self.builder.circuit self.circuit = first_part + second_part + +class MaxCutComposer(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() + for i, j in edges: + u, v = self.qubits[i], self.qubits[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 QAOAComposerChords(ZZQAOAComposer): - def cone_ansatz(self, edge): - beta, gamma = self.params['beta'], self.params['gamma'] +class MaxCutZZComposer(MaxCutComposer, ZZQAOAComposer): + pass + +class WeightedZZQAOAComposer(ZZQAOAComposer): - assert(len(beta) == len(gamma)) - p = len(beta) # infering number of QAOA steps from the parameters passed - self.layer_of_Hadamards() - # second, apply p alternating operators - cone_base = self.graph + def cost_operator_circuit(self, gamma, edges=None): + for i, j, w in self.graph.edges.data('weight', default=1): + u, v = self.qubits[i], self.qubits[j] + self.append_zz_term(u, v, gamma*w) - for i, g, b in zip(range(p, 0, -1), gamma, beta): - self.graph = utils.get_edge_subgraph_old(cone_base, edge, i) - self.cost_operator_circuit(g) - self.mixer_operator(b) - self.graph = cone_base +class VCQAOAComposer(QAOAComposer): + + 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 -class WeightedZZQAOAComposer(ZZQAOAComposer): + 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.qubits[i], self.qubits[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): - 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) + if edges is None: edges = self.graph.edges() + nodes = self.graph.nodes() + for i, j in edges: + u, v = self.qubits[i], self.qubits[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): + # 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 + second_part + +class PCZZQAOAComposer(PCQAOAComposer, ZZQAOAComposer): + 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 + second_part \ No newline at end of file diff --git a/qtensor/QAOASimulator.py b/qtensor/QAOASimulator.py index 4e9b9fc3..2828249d 100644 --- a/qtensor/QAOASimulator.py +++ b/qtensor/QAOASimulator.py @@ -1,63 +1,31 @@ from qtensor.Simulate import Simulator, QtreeSimulator, CirqSimulator from qtensor.utils import get_edge_subgraph -from qtensor.optimisation import QtreeTensorNet import numpy as np import networkx as nx from tqdm.auto import tqdm from multiprocessing import Pool from loguru import logger as log -import warnings from qtensor import tools -from qtensor.tools.lazy_import import pynauty -from qtensor.tools.lightcone_orbits import get_edge_orbits_lightcones -#debt: this is effectively only qtree simulator class QAOASimulator(Simulator): def __init__(self, composer, profile=False, *args, **kwargs): super().__init__(*args, **kwargs) self.composer = composer self.profile = profile - self._edge_cache = {} def _get_edge_energy(self, G, gamma, beta, edge): circuit = self._edge_energy_circuit(G, gamma, beta, edge) - cdata = self._edge_cache.get(edge) - if cdata is not None: - peo, width = cdata - # debt: incompatible with feynman simulator - return self.simulate_batch(circuit, batch_vars=0, peo=peo) - else: - return self.simulate(circuit) + return self.simulate(circuit) def _edge_energy_circuit(self, G, gamma, beta, edge): composer = self.composer(G, gamma=gamma, beta=beta) composer.energy_expectation_lightcone(edge) - return composer.circuit - - def _iterate_edges(self, G): - return G.edges() - - def optimize_lightcone(self, G, p, edge): - """ - Builds a circuit for corresponding edge, optimizes it and caches the result - """ - gamma, beta = [.1]*p, [.2]*p - circuit = self._edge_energy_circuit(G, gamma, beta, edge=edge) - - tn = QtreeTensorNet.from_qtree_gates(circuit, backend=self.backend) - peo, tn = self.optimizer.optimize(tn) - width = self.optimizer.treewidth - self._edge_cache[edge] = (peo, width) - # debt: proper opt data needed - return peo, width - def optimize_lightcones(self, G, p): - for e in self._iterate_edges(G): - self.optimize_lightcone(G, p, e) + return composer.circuit def _post_process_energy(self, G, E): - if np.imag(E).any()>1e-6: + if np.imag(E)>1e-6: print(f"Warning: Energy result imaginary part was: {np.imag(E)}") """ @@ -73,7 +41,6 @@ def _post_process_energy(self, G, E): return (Ed - E)/2 - def energy_expectation(self, G, gamma, beta): """ Arguments: @@ -86,15 +53,13 @@ def energy_expectation(self, G, gamma, beta): total_E = 0 with tqdm(total=G.number_of_edges(), desc='Edge iteration', ) as pbar: - for i, edge in enumerate(G.edges()): + for edge in 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) @@ -112,26 +77,7 @@ def energy_expectation_parallel(self, G, gamma, beta, n_processes=4): Returns: MaxCut energy expectation """ - widths = [] - edges = list(self._iterate_edges(G)) - optcache = list(self._edge_cache.keys()) - - for e in edges: - cdata = self._edge_cache.get(e) - if cdata is not None: - peo, width = cdata - else: - peo, width = self.optimize_lightcone(G, len(gamma), e) - widths.append(width) - - - sort_ixs = sorted(range(len(edges)), key=lambda x: widths[x]) - sorted_edges = [edges[x] for x in sort_ixs] - print('widths', widths) - print('indices', sort_ixs) - print('edges', sorted_edges) - - args = [(G, gamma, beta, edge) for edge in sorted_edges] + args = [(G, gamma, beta, edge) for edge in G.edges()] with Pool(n_processes) as p: @@ -161,56 +107,12 @@ def energy_expectation_mpi(self, G, gamma, beta, n_processes=4, print_perf=False tools.mpi.print_stats() return C -class QAOASimulatorSymmetryAccelerated(QAOASimulator): - def _iterate_edges(self, G, p, nprocs): - # debt: code duplication with energy calc - eorbits = get_edge_orbits_lightcones(G, p, nprocs) - if len(eorbits) == G.number_of_edges(): - warnings.warn(f"There is no speedup from leveraging the symmetries in lightcone structure\n Use QAOASimulator instead", RuntimeWarning) - edges = [orb_edges[0] for orb_edges in eorbits.values()] - return edges - - def energy_expectation(self, G, gamma, beta, nprocs=None): - """ - Arguments: - G: MaxCut graph, Networkx - gamma, beta: list[float] - nprocs: int (number of processes to use for lightcone orbit computation) - - Returns: MaxCut energy expectation - """ - - p = len(gamma) - assert(len(beta) == p) - - eorbits = get_edge_orbits_lightcones(G, p, nprocs) - if len(eorbits) == G.number_of_edges(): - warnings.warn(f"There is no speedup from leveraging the symmetries in lightcone structure\n Use QAOASimulator instead", RuntimeWarning) - - total_E = 0 - - with tqdm(total=len(eorbits), desc='Lightcone class of equivalence iteration', ) as pbar: - for orb_idx, orb_edges in eorbits.items(): - E = self._get_edge_energy(G, gamma, beta, orb_edges[0]) - pbar.set_postfix(Treewidth=self.optimizer.treewidth) - pbar.update(1) - total_E += len(orb_edges) * E - - if self.profile: - print(self.backend.gen_report()) - - C = self._post_process_energy(G, total_E) - return C class QAOAQtreeSimulator(QAOASimulator, QtreeSimulator): pass -class QAOAQtreeSimulatorSymmetryAccelerated(QAOASimulatorSymmetryAccelerated, QtreeSimulator): - pass - - class WeightedQAOASimulator(QAOASimulator, QtreeSimulator): def _get_edge_energy(self, G, gamma, beta, edge): circuit = self._edge_energy_circuit(G, gamma, beta, edge) @@ -228,3 +130,57 @@ 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)>1e-6: + print(f"Warning: Energy result imaginary part was: {np.imag(E)}") + + 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): + 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..2498fe1c 100644 --- a/qtensor/__init__.py +++ b/qtensor/__init__.py @@ -1,30 +1,25 @@ # -- configure logging import sys +from functools import lru_cache from loguru import logger as log log.remove() log.add(sys.stderr, level='INFO') # -- -from qtensor import utils from qtensor.utils import get_edge_subgraph import networkx as nx -from .CircuitComposer import QAOAComposer, OldQAOAComposer, ZZQAOAComposer, WeightedZZQAOAComposer, CircuitComposer -from .OpFactory import CirqBuilder, QtreeBuilder, QiskitBuilder, TorchBuilder +from .CircuitComposer import QAOAComposer, OldQAOAComposer, ZZQAOAComposer, WeightedZZQAOAComposer, VCQAOAComposer, PCQAOAComposer, VCZZQAOAComposer, PCZZQAOAComposer, MaxCutComposer +from .OpFactory import CirqBuilder, QtreeBuilder, QiskitBuilder 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.FeynmanSimulator import FeynmanSimulator, FeynmanMergedSimulator -from qtensor import contraction_backends -from qtensor.contraction_backends import PerfNumpyBackend, NumpyBackend +from qtensor.QAOASimulator import VCQAOASimulator +from qtensor.FeynmanSimulator import FeynmanSimulator +from qtensor.ProcessingFrameworks import PerfNumpyBackend, NumpyBackend from qtensor import simplify_circuit from qtensor.simplify_circuit import simplify_qtree_circuit from qtensor import optimisation -from qtensor import merged_indices -from qtensor import problems -from qtensor import MergedSimulator -from qtensor import tools class CirqQAOAComposer(QAOAComposer): def _get_builder_class(self): @@ -34,7 +29,23 @@ 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 @@ -66,15 +77,9 @@ def circuit(self): def circuit(self, circuit): self.builder.circuit = circuit -class TorchQAOAComposer(ZZQtreeQAOAComposer): - def _get_builder_class(self): - return TorchBuilder - -#DefaultQAOAComposer = SimpZZQtreeComposer -DefaultQAOAComposer = ZZQtreeQAOAComposer +DefaultQAOAComposer = SimpZZQtreeComposer WeightedQAOAComposer = WeightedZZQtreeQAOAComposer - # deprecated CCQtreeQAOAComposer = ZZQtreeQAOAComposer From f6bd40c710d446cf468aceb40d36a43d13a957a9 Mon Sep 17 00:00:00 2001 From: pangara Date: Fri, 23 Feb 2024 12:10:35 -0800 Subject: [PATCH 2/3] Make consistent with dev branch changes --- qtensor/CircuitComposer.py | 119 +++++++++++++++++++++------------- qtensor/QAOASimulator.py | 128 +++++++++++++++++++++++++++++++++---- qtensor/__init__.py | 34 ++++++---- 3 files changed, 211 insertions(+), 70 deletions(-) diff --git a/qtensor/CircuitComposer.py b/qtensor/CircuitComposer.py index a3861184..c3162660 100644 --- a/qtensor/CircuitComposer.py +++ b/qtensor/CircuitComposer.py @@ -1,6 +1,7 @@ from loguru import logger as log -from qtensor.utils import get_edge_subgraph import networkx as nx +import qtensor +from qtensor import utils from .OpFactory import CircuitBuilder class CircuitComposer(): @@ -50,6 +51,41 @@ def layer_of_Hadamards(self): for q in self.qubits: self.apply_gate(self.operators.H, q) + def expectation(self, operator, *qubits, simplify='auto', **params): + """ + Args: + operator: an element from OpFactory + qubits: qubits to apply the gate to + params: params to pass on application gate + + Returns: + a circuit, 0th amplitude of which evaluates to expectation value + """ + + # TODO: should return a tensor network, + # no circuit returns expectations + first_part = self.builder.copy() + first_part.apply_gate(operator, *qubits, **params) + second_part = self.builder.copy() + second_part.inverse() + + circ = first_part.circuit + second_part.circuit + do_simplify = len(circ) < 100 + if simplify is True: + do_simplify = True + if simplify is False: + do_simplify = False + + if do_simplify: + try: + circ = qtensor.simplify_circuit.simplify_qtree_circuit(circ) + except Exception as e: + + print('failed to simplify:', type(e), e) + raise + + return circ + class OldQAOAComposer(CircuitComposer): """ Abstract base class for QAOA Director """ @@ -58,6 +94,7 @@ def __init__(self, graph, *args, **kwargs): super().__init__(*args, **kwargs) self.graph = graph + self.qubit_map = {n: i for i, n in enumerate(graph.nodes())} def _get_builder(self): return self._get_builder_class()(self.n_qubits) @@ -83,7 +120,7 @@ def energy_expectation_lightcone(self, edge): G = self.graph gamma, beta = self.params['gamma'], self.params['beta'] i,j = edge - graph = get_edge_subgraph(G, edge, len(gamma)) + graph = utils.get_edge_subgraph(G, edge, len(gamma)) log.debug('Subgraph nodes: {}, edges: {}', graph.number_of_nodes(), graph.number_of_edges()) self.n_qubits = graph.number_of_nodes() mapping = {v:i for i, v in enumerate(graph.nodes())} @@ -100,22 +137,20 @@ def x_term(self, u, beta): #self.circuit.append(self.operators.H(u)) self.apply_gate(self.operators.XPhase, u, alpha=2*beta) #self.circuit.append(self.operators.H(u)) - - def z_term(self, u, gamma): - self.apply_gate(self.operators.ZPhase, u, alpha=2*gamma) - def mixer_operator(self, beta, nodes=None): if nodes is None: nodes = self.graph.nodes() for n in nodes: - qubit = self.qubits[n] + qubit = self.qubit_map[n] self.x_term(qubit, beta) 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 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'] @@ -128,7 +163,7 @@ def ansatz_state(self): self.mixer_operator(beta[i]) def energy_edge(self, i, j): - u, v = self.qubits[i], self.qubits[j] + u, v = self.qubit_map[i], self.qubit_map[j] self.apply_gate(self.operators.Z, u) self.apply_gate(self.operators.Z, v) @@ -144,8 +179,9 @@ def cone_ansatz(self, edge): cone_base = self.graph for i, g, b in zip(range(p, 0, -1), gamma, beta): - self.graph = get_edge_subgraph(cone_base, edge, i) + self.graph = utils.get_edge_subgraph(cone_base, edge, i) self.cost_operator_circuit(g) + self.graph = utils.get_edge_subgraph(cone_base, edge, i-1) self.mixer_operator(b) self.graph = cone_base @@ -162,27 +198,14 @@ def energy_expectation(self, i, j): second_part = self.builder.circuit self.circuit = first_part + second_part - -class MaxCutComposer(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) - +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.qubits[i], self.qubits[j] + 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) @@ -190,36 +213,50 @@ def append_zz_term(self, q1, q2, gamma): class MaxCutZZComposer(MaxCutComposer, ZZQAOAComposer): pass +class QAOAComposerChords(ZZQAOAComposer): + def cone_ansatz(self, edge): + beta, gamma = self.params['beta'], self.params['gamma'] + + assert(len(beta) == len(gamma)) + p = len(beta) # infering number of QAOA steps from the parameters passed + self.layer_of_Hadamards() + # second, apply p alternating operators + cone_base = self.graph + + for i, g, b in zip(range(p, 0, -1), gamma, beta): + self.graph = utils.get_edge_subgraph_old(cone_base, edge, i) + self.cost_operator_circuit(g) + self.mixer_operator(b) + self.graph = cone_base + + class WeightedZZQAOAComposer(ZZQAOAComposer): def cost_operator_circuit(self, gamma, edges=None): for i, j, w in self.graph.edges.data('weight', default=1): - u, v = self.qubits[i], self.qubits[j] + 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.qubits[i], self.qubits[j] + 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)) @@ -231,50 +268,42 @@ def energy_expectation(self, 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.qubits[i], self.qubits[j] + 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): - # 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 + second_part class PCZZQAOAComposer(PCQAOAComposer, ZZQAOAComposer): 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 + second_part \ No newline at end of file diff --git a/qtensor/QAOASimulator.py b/qtensor/QAOASimulator.py index 2828249d..b4e97372 100644 --- a/qtensor/QAOASimulator.py +++ b/qtensor/QAOASimulator.py @@ -1,31 +1,63 @@ from qtensor.Simulate import Simulator, QtreeSimulator, CirqSimulator from qtensor.utils import get_edge_subgraph +from qtensor.optimisation import QtreeTensorNet import numpy as np import networkx as nx from tqdm.auto import tqdm from multiprocessing import Pool from loguru import logger as log +import warnings from qtensor import tools +from qtensor.tools.lazy_import import pynauty +from qtensor.tools.lightcone_orbits import get_edge_orbits_lightcones +#debt: this is effectively only qtree simulator class QAOASimulator(Simulator): def __init__(self, composer, profile=False, *args, **kwargs): super().__init__(*args, **kwargs) self.composer = composer self.profile = profile + self._edge_cache = {} def _get_edge_energy(self, G, gamma, beta, edge): circuit = self._edge_energy_circuit(G, gamma, beta, edge) - return self.simulate(circuit) + cdata = self._edge_cache.get(edge) + if cdata is not None: + peo, width = cdata + # debt: incompatible with feynman simulator + return self.simulate_batch(circuit, batch_vars=0, peo=peo) + else: + return self.simulate(circuit) def _edge_energy_circuit(self, G, gamma, beta, edge): composer = self.composer(G, gamma=gamma, beta=beta) composer.energy_expectation_lightcone(edge) - return composer.circuit + def _iterate_edges(self, G): + return G.edges() + + def optimize_lightcone(self, G, p, edge): + """ + Builds a circuit for corresponding edge, optimizes it and caches the result + """ + gamma, beta = [.1]*p, [.2]*p + circuit = self._edge_energy_circuit(G, gamma, beta, edge=edge) + + tn = QtreeTensorNet.from_qtree_gates(circuit, backend=self.backend) + peo, tn = self.optimizer.optimize(tn) + width = self.optimizer.treewidth + self._edge_cache[edge] = (peo, width) + # debt: proper opt data needed + return peo, width + + def optimize_lightcones(self, G, p): + for e in self._iterate_edges(G): + self.optimize_lightcone(G, p, e) + def _post_process_energy(self, G, E): - if np.imag(E)>1e-6: + if np.imag(E).any()>1e-6: print(f"Warning: Energy result imaginary part was: {np.imag(E)}") """ @@ -41,6 +73,7 @@ def _post_process_energy(self, G, E): return (Ed - E)/2 + def energy_expectation(self, G, gamma, beta): """ Arguments: @@ -53,13 +86,15 @@ def energy_expectation(self, G, gamma, beta): total_E = 0 with tqdm(total=G.number_of_edges(), desc='Edge iteration', ) as pbar: - for edge in G.edges(): + 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) @@ -77,7 +112,26 @@ def energy_expectation_parallel(self, G, gamma, beta, n_processes=4): Returns: MaxCut energy expectation """ - args = [(G, gamma, beta, edge) for edge in G.edges()] + widths = [] + edges = list(self._iterate_edges(G)) + optcache = list(self._edge_cache.keys()) + + for e in edges: + cdata = self._edge_cache.get(e) + if cdata is not None: + peo, width = cdata + else: + peo, width = self.optimize_lightcone(G, len(gamma), e) + widths.append(width) + + + sort_ixs = sorted(range(len(edges)), key=lambda x: widths[x]) + sorted_edges = [edges[x] for x in sort_ixs] + print('widths', widths) + print('indices', sort_ixs) + print('edges', sorted_edges) + + args = [(G, gamma, beta, edge) for edge in sorted_edges] with Pool(n_processes) as p: @@ -107,12 +161,56 @@ def energy_expectation_mpi(self, G, gamma, beta, n_processes=4, print_perf=False tools.mpi.print_stats() return C +class QAOASimulatorSymmetryAccelerated(QAOASimulator): + def _iterate_edges(self, G, p, nprocs): + # debt: code duplication with energy calc + eorbits = get_edge_orbits_lightcones(G, p, nprocs) + if len(eorbits) == G.number_of_edges(): + warnings.warn(f"There is no speedup from leveraging the symmetries in lightcone structure\n Use QAOASimulator instead", RuntimeWarning) + edges = [orb_edges[0] for orb_edges in eorbits.values()] + return edges + + def energy_expectation(self, G, gamma, beta, nprocs=None): + """ + Arguments: + G: MaxCut graph, Networkx + gamma, beta: list[float] + nprocs: int (number of processes to use for lightcone orbit computation) + + Returns: MaxCut energy expectation + """ + + p = len(gamma) + assert(len(beta) == p) + + eorbits = get_edge_orbits_lightcones(G, p, nprocs) + if len(eorbits) == G.number_of_edges(): + warnings.warn(f"There is no speedup from leveraging the symmetries in lightcone structure\n Use QAOASimulator instead", RuntimeWarning) + + total_E = 0 + + with tqdm(total=len(eorbits), desc='Lightcone class of equivalence iteration', ) as pbar: + for orb_idx, orb_edges in eorbits.items(): + E = self._get_edge_energy(G, gamma, beta, orb_edges[0]) + pbar.set_postfix(Treewidth=self.optimizer.treewidth) + pbar.update(1) + total_E += len(orb_edges) * E + + if self.profile: + print(self.backend.gen_report()) + + C = self._post_process_energy(G, total_E) + return C class QAOAQtreeSimulator(QAOASimulator, QtreeSimulator): pass +class QAOAQtreeSimulatorSymmetryAccelerated(QAOASimulatorSymmetryAccelerated, QtreeSimulator): + pass + + class WeightedQAOASimulator(QAOASimulator, QtreeSimulator): def _get_edge_energy(self, G, gamma, beta, edge): circuit = self._edge_energy_circuit(G, gamma, beta, edge) @@ -137,12 +235,9 @@ 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) @@ -150,17 +245,18 @@ def energy_expectation(self, G, gamma, beta): 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)>1e-6: + 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() @@ -169,8 +265,13 @@ def _post_process_energy(self, G, E): 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) @@ -178,7 +279,6 @@ def energy_expectation(self, G, gamma, beta): pbar.set_postfix(Treewidth=self.optimizer.treewidth) pbar.update(1) total_E += E - if self.profile: # debt print(self.backend.gen_report()) diff --git a/qtensor/__init__.py b/qtensor/__init__.py index 2498fe1c..8643649a 100644 --- a/qtensor/__init__.py +++ b/qtensor/__init__.py @@ -1,25 +1,31 @@ # -- configure logging import sys -from functools import lru_cache from loguru import logger as log log.remove() log.add(sys.stderr, level='INFO') # -- +from qtensor import utils from qtensor.utils import get_edge_subgraph import networkx as nx -from .CircuitComposer import QAOAComposer, OldQAOAComposer, ZZQAOAComposer, WeightedZZQAOAComposer, VCQAOAComposer, PCQAOAComposer, VCZZQAOAComposer, PCZZQAOAComposer, MaxCutComposer -from .OpFactory import CirqBuilder, QtreeBuilder, QiskitBuilder +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 -from qtensor.ProcessingFrameworks import PerfNumpyBackend, NumpyBackend +from qtensor.FeynmanSimulator import FeynmanSimulator, FeynmanMergedSimulator +from qtensor import contraction_backends +from qtensor.contraction_backends import PerfNumpyBackend, NumpyBackend from qtensor import simplify_circuit from qtensor.simplify_circuit import simplify_qtree_circuit from qtensor import optimisation +from qtensor import merged_indices +from qtensor import problems +from qtensor import MergedSimulator +from qtensor import tools class CirqQAOAComposer(QAOAComposer): def _get_builder_class(self): @@ -28,11 +34,11 @@ def _get_builder_class(self): class QiskitQAOAComposer(QAOAComposer): def _get_builder_class(self): return QiskitBuilder - + class VCQiskitQAOAComposer(VCQAOAComposer): def _get_builder_class(self): return QiskitBuilder - + class PCQiskitQAOAComposer(PCQAOAComposer): def _get_builder_class(self): return QiskitBuilder @@ -40,10 +46,10 @@ def _get_builder_class(self): class VCQtreeQAOAComposer(VCZZQAOAComposer): def _get_builder_class(self): return QtreeBuilder - + class PCQtreeQAOAComposer(PCZZQAOAComposer): def _get_builder_class(self): - return QtreeBuilder + return QtreeBuilder class QtreeQAOAComposer(MaxCutComposer): def _get_builder_class(self): @@ -77,9 +83,15 @@ def circuit(self): def circuit(self, circuit): self.builder.circuit = circuit -DefaultQAOAComposer = SimpZZQtreeComposer +class TorchQAOAComposer(ZZQtreeQAOAComposer): + def _get_builder_class(self): + return TorchBuilder + +#DefaultQAOAComposer = SimpZZQtreeComposer +DefaultQAOAComposer = ZZQtreeQAOAComposer WeightedQAOAComposer = WeightedZZQtreeQAOAComposer + # deprecated CCQtreeQAOAComposer = ZZQtreeQAOAComposer @@ -94,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 From 7f24b7a3e863354a35223ad53ee959e1c2bf88bb Mon Sep 17 00:00:00 2001 From: pangara Date: Fri, 23 Feb 2024 12:11:41 -0800 Subject: [PATCH 3/3] add example notebook for vertex cover and qaoa --- examples/qtensor-vertex-cover.ipynb | 332 ++++++++++++++++++++++++++++ 1 file changed, 332 insertions(+) create mode 100644 examples/qtensor-vertex-cover.ipynb 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 +}