Source code for qrisp.algorithms.qaoa.qaoa_problem

# ********************************************************************************
# * Copyright (c) 2026 the Qrisp authors
# *
# * This program and the accompanying materials are made available under the
# * terms of the Eclipse Public License 2.0 which is available at
# * http://www.eclipse.org/legal/epl-2.0.
# *
# * This Source Code may also be made available under the following Secondary
# * Licenses when the conditions for such availability set forth in the Eclipse
# * Public License, v. 2.0 are satisfied: GNU General Public License, version 2
# * with the GNU Classpath Exception which is
# * available at https://www.gnu.org/software/classpath/license.html.
# *
# * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
# ********************************************************************************

"""Defines the QAOAProblem class encapsulating the cost operator, mixer, and optimization routine."""

import time

import jax
import jax.numpy as jnp
import numpy as np
from scipy.optimize import minimize
from sympy import Symbol

from qrisp import QuantumArray, h, x
from qrisp.algorithms.qaoa.qaoa_benchmark_data import QAOABenchmark
from qrisp.jasp import QuantumVariableTemplate, check_for_tracing_mode, jrange, sample
from qrisp.jasp.optimization_tools.optimize import minimize as jasp_minimize


[docs] class QAOAProblem: r"""Central structure to facilitate treatment of QAOA problems. This class encapsulates the cost operator, mixer operator, and classical cost function for a specific QAOA problem instance. It also provides methods to set the initial state preparation function, classical cost post-processing function, and optimizer for the problem. For a quick demonstration, we import the relevant functions from already implemented problem instances: :: from networkx import Graph G = Graph() G.add_edges_from([[0,3],[0,4],[1,3],[1,4],[2,3],[2,4]]) from qrisp.qaoa import (QAOAProblem, create_maxcut_cost_operator, create_maxcut_cl_cost_function, RX_mixer) maxcut_instance = QAOAProblem(cost_operator = create_maxcut_cost_operator(G), mixer = RX_mixer, cl_cost_function = create_maxcut_cl_cost_function(G)) from qrisp import QuantumVariable res = maxcut_instance.run(QuantumVariable(5), depth = 4, max_iter = 25) print(res) #Yields: {'11100': 0.2847, '00011': 0.2847, '10000': 0.0219, '01000': 0.0219, '00100': 0.0219, '11011': 0.0219, '10111': 0.0219, '01111': 0.0219, '00010': 0.02, '11110': 0.02, '00001': 0.02, '11101': 0.02, '00000': 0.0173, '11111': 0.0173, '10010': 0.0143, '01010': 0.0143, '11010': 0.0143, '00110': 0.0143, '10110': 0.0143, '01110': 0.0143, '10001': 0.0143, '01001': 0.0143, '11001': 0.0143, '00101': 0.0143, '10101': 0.0143, '01101': 0.0143, '11000': 0.0021, '10100': 0.0021, '01100': 0.0021, '10011': 0.0021, '01011': 0.0021, '00111': 0.0021} For an in-depth tutorial, make sure to check out :ref:`MaxCutQAOA`! Parameters ---------- cost_operator : callable A function receiving a :ref:`QuantumVariable` or :ref:`QuantumArray` and parameter $\gamma$. This function performs the application of the cost operator. mixer : callable A function receiving a :ref:`QuantumVariable` or :ref:`QuantumArray` and parameter $\beta$. This function performs the application mixing operator. cl_cost_function : callable The classical cost function for the specific QAOA problem instance, which takes a dictionary of measurement results as input. init_function : callable, optional A function receiving a :ref:`QuantumVariable` or :ref:`QuantumArray` for preparing the inital state. By default, the uniform superposition state $\ket{+}^n$ is prepared. callback : bool, optional If ``True``, intermediate results are stored. The default is ``False``. """ def __init__(self, cost_operator, mixer, cl_cost_function, init_function=None, callback=False): self.cost_operator = cost_operator self.mixer = mixer self.cl_cost_function = cl_cost_function self.init_function = init_function # parameters for callback self.callback = callback self.optimization_params = [] self.optimization_costs = [] def set_callback(self): """Sets ``callback=True`` for saving intermediate results.""" self.callback = True
[docs] def set_init_function(self, init_function): """Set the initial state preparation function for the QAOA problem. Parameters ---------- init_function : function The initial state preparation function for the specific QAOA problem instance. """ self.init_function = init_function
def computeParams(self, p, dt): """Compute the angle parameters gamma and beta based on the given inputs. Used for the TQA warm starting the initial parameters for QAOA. Parameters ---------- p : int The number of partitions for the time interval. dt : float The time step. Returns ------- np.array A concatenated numpy array of gamma and beta values. """ if check_for_tracing_mode(): import jax.numpy as jnp else: import numpy as jnp t = (jnp.arange(1, p + 1) - 0.5) / p gamma = t * dt beta = (1 - t) * dt return jnp.concatenate((gamma, beta))
[docs] def compile_circuit(self, qarg, depth, init_type="random"): r"""Compiles the circuit that is evaluated by the :meth:`run <qrisp.qaoa.QAOAProblem.run>` method. Parameters ---------- qarg : :ref:`QuantumVariable` or :ref:`QuantumArray` The argument to which the QAOA circuit is applied. depth : int The amount of QAOA layers. init_type : string, optional Specifies the way the initial optimization parameters are chosen. Available are ``random`` and ``tqa``. The default is ``random``: The parameters are initialized uniformly at random in the interval $[0,\pi/2]$. For ``tqa``, the parameters are chosen based on the `Trotterized Quantum Annealing <https://quantum-journal.org/papers/q-2021-07-01-491/>`_ protocol. If ``tqa`` is chosen, and no ``init_function`` for the :ref:`QAOAProblem` is specified, the $\ket{-}^n$ state is prepared (the ground state for the X mixer). Returns ------- compiled_qc : :ref:`QuantumCircuit` The parametrized, compiled quantum circuit without measurements. list[sympy.Symbol] A list of the parameters that appear in ``compiled_qc``. Examples -------- We create a MaxCut instance and compile the circuit: >>> from networkx import Graph >>> G = Graph([(0,1),(1,2),(2,0)]) >>> from qrisp.qaoa import maxcut_problem >>> from qrisp import QuantumVariable >>> p = 5 >>> qaoa_instance = maxcut_problem(G) >>> qarg = QuantumVariable(len(G)) >>> qrisp_qc, symbols = qaoa_instance.compile_circuit(qarg, p) >>> print(qrisp_qc) β”Œβ”€β”€β”€β”β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” Β» qarg_dupl.0: ─ H β”œβ”€ gphase β”œβ”€ gphase β”œβ”€β”€β– β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β– β”€β”€Β» β”œβ”€β”€β”€β”€β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”Œβ”€β”΄β”€β”β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Β» qarg_dupl.1: ─ H β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ X β”œβ”€ P(2*gamma_0) β”œβ”€β”€β”Όβ”€β”€Β» β”œβ”€β”€β”€β”€ β””β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”Œβ”€β”΄β”€β”Β» qarg_dupl.2: ─ H β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ X β”œΒ» β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜Β» Β« β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” Β» Β«qarg_dupl.0: ───────■────────────────────■─── Rx(2*beta_0) β”œβ”€β”€β”€β”€ gphase β”œβ”€β”€β”€Β» Β« β”Œβ”€β”΄β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Β» Β«qarg_dupl.1: ────── X β”œβ”€β”€β”€β”€β”€β”€β”€ gphase β”œβ”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β– β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€Β» Β« β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”Œβ”€β”΄β”€β” β”Œβ”€β”΄β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Β» Β«qarg_dupl.2: ─ P(2*gamma_0) β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ X β”œβ”€β”€β”€β”€β”€β”€ X β”œβ”€β”€β”€β”€β”€β”€β”€ P(2*gamma_0) β”œΒ» Β« β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜Β» Β« β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” Β» Β«qarg_dupl.0: ─ gphase β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β– β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β– β”€β”€Β» Β« β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”Œβ”€β”΄β”€β”β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Β» Β«qarg_dupl.1: ────■────── Rx(2*beta_0) β”œβ”€ X β”œβ”€ P(2*gamma_1) β”œβ”€β”€β”Όβ”€β”€Β» Β« β”Œβ”€β”΄β”€β” β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β””β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”Œβ”€β”΄β”€β”Β» Β«qarg_dupl.2: ─── X β”œβ”€β”€β”€β”€ Rx(2*beta_0) β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ X β”œΒ» Β« β””β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”˜Β» Β« β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” Β» Β«qarg_dupl.0: ───────■────────────────────■─── Rx(2*beta_1) β”œβ”€β”€β”€β”€ gphase β”œβ”€β”€β”€Β» Β« β”Œβ”€β”΄β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Β» Β«qarg_dupl.1: ────── X β”œβ”€β”€β”€β”€β”€β”€β”€ gphase β”œβ”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β– β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€Β» Β« β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”Œβ”€β”΄β”€β” β”Œβ”€β”΄β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Β» Β«qarg_dupl.2: ─ P(2*gamma_1) β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ X β”œβ”€β”€β”€β”€β”€β”€ X β”œβ”€β”€β”€β”€β”€β”€β”€ P(2*gamma_1) β”œΒ» Β« β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜Β» Β« β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” Β» Β«qarg_dupl.0: ─ gphase β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β– β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β– β”€β”€Β» Β« β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”Œβ”€β”΄β”€β”β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Β» Β«qarg_dupl.1: ────■────── Rx(2*beta_1) β”œβ”€ X β”œβ”€ P(2*gamma_2) β”œβ”€β”€β”Όβ”€β”€Β» Β« β”Œβ”€β”΄β”€β” β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β””β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”Œβ”€β”΄β”€β”Β» Β«qarg_dupl.2: ─── X β”œβ”€β”€β”€β”€ Rx(2*beta_1) β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ X β”œΒ» Β« β””β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”˜Β» Β« β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” Β» Β«qarg_dupl.0: ───────■────────────────────■─── Rx(2*beta_2) β”œβ”€β”€β”€β”€ gphase β”œβ”€β”€β”€Β» Β« β”Œβ”€β”΄β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Β» Β«qarg_dupl.1: ────── X β”œβ”€β”€β”€β”€β”€β”€β”€ gphase β”œβ”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β– β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€Β» Β« β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”Œβ”€β”΄β”€β” β”Œβ”€β”΄β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Β» Β«qarg_dupl.2: ─ P(2*gamma_2) β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ X β”œβ”€β”€β”€β”€β”€β”€ X β”œβ”€β”€β”€β”€β”€β”€β”€ P(2*gamma_2) β”œΒ» Β« β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜Β» Β« β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” Β» Β«qarg_dupl.0: ─ gphase β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β– β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β– β”€β”€Β» Β« β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”Œβ”€β”΄β”€β”β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Β» Β«qarg_dupl.1: ────■────── Rx(2*beta_2) β”œβ”€ X β”œβ”€ P(2*gamma_3) β”œβ”€β”€β”Όβ”€β”€Β» Β« β”Œβ”€β”΄β”€β” β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β””β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”Œβ”€β”΄β”€β”Β» Β«qarg_dupl.2: ─── X β”œβ”€β”€β”€β”€ Rx(2*beta_2) β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ X β”œΒ» Β« β””β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”˜Β» Β« β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” Β» Β«qarg_dupl.0: ───────■────────────────────■─── Rx(2*beta_3) β”œβ”€β”€β”€β”€ gphase β”œβ”€β”€β”€Β» Β« β”Œβ”€β”΄β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Β» Β«qarg_dupl.1: ────── X β”œβ”€β”€β”€β”€β”€β”€β”€ gphase β”œβ”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β– β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€Β» Β« β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”Œβ”€β”΄β”€β” β”Œβ”€β”΄β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Β» Β«qarg_dupl.2: ─ P(2*gamma_3) β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ X β”œβ”€β”€β”€β”€β”€β”€ X β”œβ”€β”€β”€β”€β”€β”€β”€ P(2*gamma_3) β”œΒ» Β« β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜Β» Β« β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” Β» Β«qarg_dupl.0: ─ gphase β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β– β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β– β”€β”€Β» Β« β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”Œβ”€β”΄β”€β”β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Β» Β«qarg_dupl.1: ────■────── Rx(2*beta_3) β”œβ”€ X β”œβ”€ P(2*gamma_4) β”œβ”€β”€β”Όβ”€β”€Β» Β« β”Œβ”€β”΄β”€β” β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β””β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”Œβ”€β”΄β”€β”Β» Β«qarg_dupl.2: ─── X β”œβ”€β”€β”€β”€ Rx(2*beta_3) β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ X β”œΒ» Β« β””β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”˜Β» Β« β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” Β» Β«qarg_dupl.0: ───────■────────────────────■─── Rx(2*beta_4) β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€Β» Β« β”Œβ”€β”΄β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Β» Β«qarg_dupl.1: ────── X β”œβ”€β”€β”€β”€β”€β”€β”€ gphase β”œβ”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β– β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€Β» Β« β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”Œβ”€β”΄β”€β” β”Œβ”€β”΄β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Β» Β«qarg_dupl.2: ─ P(2*gamma_4) β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ X β”œβ”€β”€β”€β”€β”€β”€ X β”œβ”€β”€β”€β”€β”€β”€β”€ P(2*gamma_4) β”œΒ» Β« β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜Β» Β« Β«qarg_dupl.0: ───────────────────── Β« β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” Β«qarg_dupl.1: ──■─── Rx(2*beta_4) β”œ Β« β”Œβ”€β”΄β”€β”β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ Β«qarg_dupl.2: ─ X β”œβ”€ Rx(2*beta_4) β”œ Β« β””β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ """ temp = list(qarg.qs.data) # Define QAOA angle parameters gamma and beta for QAOA circuit gamma = [Symbol("gamma_" + str(i)) for i in range(depth)] beta = [Symbol("beta_" + str(i)) for i in range(depth)] # Prepare initial state - if no init_function is specified, prepare uniform superposition if self.init_function is not None: self.init_function(qarg) elif init_type == "tqa": # Prepare the ground state (eigenvalue -1) of the X mixer x(qarg) h(qarg) else: h(qarg) # Apply p layers of phase separators and mixers for i in range(depth): self.cost_operator(qarg, gamma[i]) self.mixer(qarg, beta[i]) # Compile quantum circuit with intended measurements if isinstance(qarg, QuantumArray): intended_measurement_qubits = sum([list(qv) for qv in qarg.flatten()], []) else: intended_measurement_qubits = list(qarg) compiled_qc = qarg.qs.compile(intended_measurements=intended_measurement_qubits) qarg.qs.data = temp return compiled_qc, gamma + beta
def optimization_routine(self, qarg_prep, depth, mes_kwargs, init_type, init_point, optimizer, options): """Wrapper subroutine for the optimization method used in QAOA. The initial values are set and the optimization via ``COBYLA`` is conducted here. Parameters ---------- qarg_prep : callable A function returning a :ref:`QuantumVariable` or :ref:`QuantumArray` to which the QAOA circuit is applied. depth : int The amont of QAOA layers. symbols : list The list of symbols used in the quantum circuit. mes_kwargs : dict, optional The keyword arguments for the measurement function. Default is an empty dictionary, as defined in previous functions. init_type : string, optional Specifies the way the initial optimization parameters are chosen. Available are ``random`` and ``tqa``. The default is ``random``. init_point : ndarray, shape (n,), optional Specifies the initial optimization parameters. optimizer : str, optional Specifies the `SciPy optimization routine <https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html>`_. Available are, e.g., ``COBYLA``, ``COBYQA``, ``Nelder-Mead``. The Default is ``COBYLA``. In tracing mode (i.e. Jasp) Jax-traceable :ref:`optimization routines <optimization_tools>` must be utilized. Available are ``SPSA``. The Default is ``SPSA``. options : dict A dictionary of solver options. Returns ------- ndarray, shape (n,) The optimized parameters of the problem instance. float or jax.Array The expectation value of the classical cost function for the optimized parameters. """ if check_for_tracing_mode(): # Define optimization wrapper function to be minimized using QAOA def optimization_wrapper(theta, state_prep, mes_kwargs): """Wrapper function for the optimization method used in QAOA. This function calculates the value of the classical cost function. Parameters ---------- theta : jax.Array The array of angle parameters gamma and beta for the QAOA circuit. state_prep : callable A function returning a :ref:`QuantumVariable` or :ref:`QuantumArray`. The expectation of the classical cost function from the state of this QuantumVariable will be measured. The state preparation function can only take classical values as arguments. This is because a quantum value would need to be copied for each sampling iteration, which is prohibited by the no-cloning theorem. mes_kwargs : dict The keyword arguments for the sample function. Returns ------- float The expected value of the classical cost function. """ res_sample = sample(state_prep, shots=mes_kwargs["shots"])(theta) cl_cost = self.cl_cost_function(res_sample) return cl_cost def tqa_angles(p, state_prep, mes_kwargs, steps=10): """Compute the optimal parameters for the Trotterized Quantum Annealing (`TQA <https://quantum-journal.org/papers/q-2021-07-01-491/>`_) algorithm. The function first creates a linspace array `dt` from 0.1 to 1 with `steps` steps. Then for each `dt_` in `dt`, it computes the parameters `x` using the `computeParams` function and calculates the energy `energy_` using the `optimization_wrapper` function. The energy values are stored in the `energy` list. The `dt_max` corresponding to the minimum energy is found and used to compute the optimal parameters which are returned. Parameters ---------- p : int The number of partitions for the time interval. state_prep : callable A function returning a :ref:`QuantumVariable` or :ref:`QuantumArray`. mes_kwargs : dict The measurement keyword arguments. steps : int, optional The number of steps for the linspace function, default is 10. Returns ------- jax.Array A concatenated jax.numpy array of optimal gamma and beta values. """ dt = jnp.linspace(0.1, 1, steps) energy = jnp.array([0.0] * steps) for i in range(steps): theta = self.computeParams(p, dt[i]) energy_ = optimization_wrapper(theta, state_prep, mes_kwargs) energy = energy.at[i].set(energy_) idx = jnp.argmin(energy) dt_max = dt[idx] return self.computeParams(p, dt_max) else: # Define optimization wrapper function to be minimized using QAOA def optimization_wrapper(theta, qarg, qc, symbols, mes_kwargs): """Wrapper function for the optimization method used in QAOA. This function calculates the value of the classical cost function. Parameters ---------- theta : list The list of angle parameters gamma and beta for the QAOA circuit. qarg : :ref:`QuantumVariable` or :ref:`QuantumArray` The quantum argument to which the quantum circuit is applied. qc : :ref:`QuantumCircuit` The compiled quantum circuit. symbols : list The list of symbols used in the quantum circuit. mes_kwargs : dict The keyword arguments for the measurement function. Returns ------- float The expected value of the classical cost function. """ subs_dic = {symbols[i]: theta[i] for i in range(len(symbols))} res_dic = qarg.get_measurement(subs_dic=subs_dic, precompiled_qc=qc, **mes_kwargs) cl_cost = self.cl_cost_function(res_dic) if self.callback: self.optimization_costs.append(cl_cost) return cl_cost def tqa_angles(p, qarg, qc, symbols, mes_kwargs, steps=10): """Compute the optimal parameters for the Trotterized Quantum Annealing (`TQA <https://quantum-journal.org/papers/q-2021-07-01-491/>`_) algorithm. The function first creates a linspace array `dt` from 0.1 to 1 with `steps` steps. Then for each `dt_` in `dt`, it computes the parameters `x` using the `computeParams` function and calculates the energy `energy_` using the `optimization_wrapper` function. The energy values are stored in the `energy` list. The `dt_max` corresponding to the minimum energy is found and used to compute the optimal parameters which are returned. Parameters ---------- p : int The number of partitions for the time interval. qarg : :ref:`QuantumVariable` or :ref:`QuantumArray` The quantum argument to which the quantum circuit is applied. qc : :ref:`QuantumCircuit` The quantum circuit for the specific problem instance. symbols : list The list of symbols in the quantum circuit. mes_kwargs : dict The measurement keyword arguments. steps : int, optional The number of steps for the linspace function, default is 10. Returns ------- np.array A concatenated numpy array of optimal gamma and beta values. """ dt = np.linspace(0.1, 1, steps) energy = [] for dt_ in dt: theta = self.computeParams(p, dt_) energy_ = optimization_wrapper(theta, qarg, qc, symbols, mes_kwargs) energy.append(energy_) idx = np.argmin(energy) dt_max = dt[idx] return self.computeParams(p, dt_max) if check_for_tracing_mode(): def state_prep(theta): qarg = qarg_prep() # Prepare initial state - if no init_function is specified, prepare uniform superposition if self.init_function is not None: self.init_function(qarg) elif init_type == "tqa": # Prepare the ground state (eigenvalue -1) of the X mixer x(qarg) h(qarg) else: h(qarg) # Apply p layers of phase separators and mixers for i in jrange(depth): self.cost_operator(qarg, theta[i]) self.mixer(qarg, theta[i + depth]) return qarg else: qarg = qarg_prep() compiled_qc, symbols = self.compile_circuit(qarg, depth, init_type) # Initialization for optimization parameters if init_point is None: if init_type == "random": # Random initialization if check_for_tracing_mode(): key = jax.random.key(11) init_point = jax.random.uniform(key=key, shape=(2 * depth,)) * jnp.pi / 2 else: init_point = np.random.rand(2 * depth) * np.pi / 2 elif init_type == "tqa": # TQA initialization if check_for_tracing_mode(): init_point = tqa_angles(depth, state_prep, mes_kwargs) else: init_point = tqa_angles(depth, qarg, compiled_qc, symbols, mes_kwargs) else: raise Exception(f"Parameter initialization method {init_type} is not available.") if check_for_tracing_mode(): res_sample = jasp_minimize( optimization_wrapper, init_point, method=optimizer, options=options, args=( state_prep, mes_kwargs, ), ) return res_sample.x, res_sample.fun else: res_sample = minimize( optimization_wrapper, init_point, method=optimizer, options=options, args=(qarg, compiled_qc, symbols, mes_kwargs), ) return res_sample.x, res_sample.fun
[docs] def run( self, qarg, depth, mes_kwargs={}, max_iter=50, init_type="random", init_point=None, optimizer="COBYLA", options={}, ): r"""Run the specific QAOA problem instance. Takes the quantum argument, the depth of the QAOA circuit, the measurement keyword arguments (mes_kwargs) and the maximum number of iterations for the optimization (max_iter). Parameters ---------- qarg : :ref:`QuantumVariable` or :ref:`QuantumArray` or QuantumVariableTemplate or callable The argument to which the QAOA circuit is applied, or a QuantumVariableTemplate or function returning a :ref:`QuantumVariable` or :ref:`QuantumArray` to which the QAOA circuit is applied. depth : int The amount of QAOA layers. mes_kwargs : dict, optional The keyword arguments for the measurement function. Default is an empty dictionary. max_iter : int, optional The maximum number of iterations for the optimization method. Default is 50. init_type : string, optional Specifies the way the initial optimization parameters are chosen. Available are ``random`` and ``tqa``. The default is ``random``: The parameters are initialized uniformly at random in the interval $[0,\pi/2]$. For ``tqa``, the parameters are chosen based on the `Trotterized Quantum Annealing <https://quantum-journal.org/papers/q-2021-07-01-491/>`_ protocol. If ``tqa`` is chosen, and no ``init_function`` for the :ref:`QAOAProblem` is specified, the $\ket{-}^n$ state is prepared (the ground state for the X mixer). init_point : ndarray, shape (n,), optional Specifies the initial optimization parameters. optimizer : str, optional Specifies the `SciPy optimization routine <https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html>`_. Available are, e.g., ``COBYLA``, ``COBYQA``, ``Nelder-Mead``. The Default is ``COBYLA``. In tracing mode (i.e. Jasp) Jax-traceable :ref:`optimization routines <optimization_tools>` must be utilized. Available are ``COBYLA``, ``SPSA``. options : dict A dictionary of solver options. Returns ------- dict or jax.Array The optimal result after running QAOA problem for a specific problem instance. It contains the measurement results after applying the optimal QAOA circuit to the quantum argument. """ if callable(qarg): qarg_prep = qarg elif isinstance(qarg, QuantumArray): template = qarg.qtype.template() shape = qarg.shape def qarg_prep(): return QuantumArray(qtype=template.construct(), shape=shape) elif isinstance(qarg, QuantumVariableTemplate): def qarg_prep(): return qarg.construct() else: template = qarg.template() def qarg_prep(): return template.construct() # Set default options options["maxiter"] = max_iter if "shots" not in mes_kwargs: mes_kwargs["shots"] = 5000 # Delete callback self.optimization_params = [] self.optimization_costs = [] opt_theta, opt_res = self.optimization_routine( qarg_prep, depth, mes_kwargs, init_type, init_point, optimizer, options ) def state_prep(theta): qarg = qarg_prep() # Prepare initial state - if no init_function is specified, prepare uniform superposition if self.init_function is not None: self.init_function(qarg) elif init_type == "tqa": # Prepare the ground state (eigenvalue -1) of the X mixer x(qarg) h(qarg) else: h(qarg) # Apply p layers of phase separators and mixers for i in jrange(depth): self.cost_operator(qarg, theta[i]) self.mixer(qarg, theta[i + depth]) return qarg if check_for_tracing_mode(): res_sample = sample(state_prep, shots=mes_kwargs["shots"])(opt_theta) else: qarg = state_prep(opt_theta) res_sample = qarg.get_measurement(**mes_kwargs) return res_sample
[docs] def train_function( self, qarg, depth, mes_kwargs={}, max_iter=50, init_type="random", init_point=None, optimizer="COBYLA", options={}, ): r"""Train a circuit with a given ``QAOAProblem`` instance. Returns a function that can be applied to a ``QuantumVariable``, such that it represents a solution to the problem instance. When applied to a ``QuantumVariable``, the function therefore prepares the state .. math:: \ket{\psi_p}=U_M(B,\beta_p)U_P(C,\gamma_p)\dotsb U_M(B,\beta_1)U_P(C,\gamma_1)\ket{\psi_0} with optimized parameters $\gamma, \beta$. Parameters ---------- qarg : :ref:`QuantumVariable` or :ref:`QuantumArray` or QuantumVariableTemplate or callable The argument to which the QAOA circuit is applied, or a QuantumVariableTemplate or function returning a :ref:`QuantumVariable` or :ref:`QuantumArray` to which the QAOA circuit is applied. depth : int The amount of QAOA layers. mes_kwargs : dict, optional The keyword arguments for the measurement function. Default is an empty dictionary. max_iter : int, optional The maximum number of iterations for the optimization method. Default is 50. init_type : string, optional Specifies the way the initial optimization parameters are chosen. Available are ``random`` and ``tqa``. The default is ``random``: The parameters are initialized uniformly at random in the interval $[0,\pi/2]$. For ``tqa``, the parameters are chosen based on the `Trotterized Quantum Annealing <https://quantum-journal.org/papers/q-2021-07-01-491/>`_ protocol. If ``tqa`` is chosen, and no ``init_function`` for the :ref:`QAOAProblem` is specified, the $\ket{-}^n$ state is prepared (the ground state for the X mixer). init_point : ndarray, shape (n,), optional Specifies the initial optimization parameters. optimizer : str, optional Specifies the `SciPy optimization routine <https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html>`_. Available are, e.g., ``COBYLA``, ``COBYQA``, ``Nelder-Mead``. The Default is ``COBYLA``. In tracing mode (i.e. Jasp) Jax-traceable :ref:`optimization routines <optimization_tools>` must be utilized. Available are ``COBYLA``, ``SPSA``. options : dict A dictionary of solver options. Returns ------- circuit_generator : function A function that can be applied to a ``QuantumVariable`` , with optimized parameters for the problem instance. The ``QuantumVariable`` then represents a solution of the problem. Examples -------- We create a :ref:`MaxIndepSet <maxIndepSetQAOA>` instance and train a ciruit with the :ref:`QAOAProblem` instance. :: from qrisp import QuantumVariable from qrisp.qaoa import QAOAProblem, RZ_mixer, create_max_indep_set_cl_cost_function, create_max_indep_set_mixer, max_indep_set_init_function import networkx as nx import matplotlib.pyplot as plt G = nx.erdos_renyi_graph(9, 0.5, seed = 133) qaoa_instance = QAOAProblem(cost_operator=RZ_mixer, mixer=create_max_indep_set_mixer(G), cl_cost_function=create_max_indep_set_cl_cost_function(G), init_function=max_indep_set_init_function) # create a blueprint-qv to train the circuit with the problem instance qarg = QuantumVariable(G.number_of_nodes()) training_func = qaoa_instance.train_function(qarg, depth=5) # apply the trained function to a new qv qarg_trained = QuantumVariable(G.number_of_nodes()) training_func(qarg_trained) # get the measurement results opt_res = qarg_trained.get_measurement() cl_cost = create_max_indep_set_cl_cost_function(G) print("5 most likely solutions") max_five = sorted(opt_res.items(), key=lambda item: item[1], reverse=True)[:5] for res, prob in max_five: print([index for index, value in enumerate(res) if value == '1'], prob, cl_cost({res : 1})) """ if callable(qarg): qarg_prep = qarg elif isinstance(qarg, QuantumArray): template = qarg.qtype.template() shape = qarg.shape def qarg_prep(): return QuantumArray(qtype=template.construct(), shape=shape) elif isinstance(qarg, QuantumVariableTemplate): def qarg_prep(): return qarg.construct() else: template = qarg.template() def qarg_prep(): return template.construct() # Set default options options["maxiter"] = max_iter if "shots" not in mes_kwargs: mes_kwargs["shots"] = 5000 opt_theta, opt_res = self.optimization_routine( qarg_prep, depth, mes_kwargs, init_type, init_point, optimizer, options ) def circuit_generator(qarg_gen): # Prepare initial state - if no init_function is specified, prepare uniform superposition if self.init_function is not None: self.init_function(qarg_gen) elif init_type == "tqa": # Prepare the ground state (eigenvalue -1) of the X mixer x(qarg_gen) h(qarg_gen) else: h(qarg_gen) for i in jrange(depth): self.cost_operator(qarg_gen, opt_theta[i]) self.mixer(qarg_gen, opt_theta[i + depth]) return circuit_generator
[docs] def benchmark( self, qarg, depth_range, shot_range, iter_range, optimal_solution, repetitions=1, mes_kwargs={}, init_type="random", optimizer="COBYLA", options={}, ): r"""This method enables convenient data collection regarding performance of the implementation. Parameters ---------- qarg : :ref:`QuantumVariable` or :ref:`QuantumArray` or QuantumVariableTemplate or callable The argument to which the QAOA circuit is applied, or a QuantumVariableTemplate or function returning a :ref:`QuantumVariable` or :ref:`QuantumArray` to which the QAOA circuit is applied. Compare to the :meth:`.run <qrisp.qaoa.QAOAProblem.run>` method. depth_range : list[int] A list of integers indicating, which depth parameters should be explored. Depth means the amount of QAOA layers. shot_range : list[int] A list of integers indicating, which shots parameters should be explored. Shots means the amount of repetitions, the backend performs per iteration. iter_range : list[int] A list of integers indicating, what iterations parameter should be explored. Iterations means the amount of backend calls, the optimizer is allowed to do. optimal_solution : - The optimal solution to the problem. Should have the same type as the keys of the result of ``qarg.get_measurement()``. repetitions : int, optional The amount of repetitions, each parameter constellation should go though. Can be used to get a better statistical significance. The default is 1. mes_kwargs : dict, optional The keyword arguments, that are used for the ``qarg.get_measurement``. The default is {}. init_type : string, optional Specifies the way the initial optimization parameters are chosen. Available are ``random`` and ``tqa``. The default is ``random``: The parameters are initialized uniformly at random in the interval $[0,\pi/2]$. For ``tqa``, the parameters are chosen based on the `Trotterized Quantum Annealing <https://quantum-journal.org/papers/q-2021-07-01-491/>`_ protocol. If ``tqa`` is chosen, and no ``init_function`` for the :ref:`QAOAProblem` is specified, the $\ket{-}^n$ state is prepared (the ground state for the X mixer). optimizer : str, optional Specifies the `SciPy optimization routine <https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html>`_. Available are, e.g., ``COBYLA``, ``COBYQA``, ``Nelder-Mead``. The Default is ``COBYLA``. options : dict A dictionary of solver options. Returns ------- :ref:`QAOABenchmark` The results of the benchmark. Examples -------- We create a MaxCut instance and benchmark several parameters :: from qrisp import * from networkx import Graph G = Graph() G.add_edges_from([[0,3],[0,4],[1,3],[1,4],[2,3],[2,4]]) from qrisp.qaoa import maxcut_problem max_cut_instance = maxcut_problem(G) benchmark_data = max_cut_instance.benchmark(QuantumVariable(5), depth_range = [3,4,5], shot_range = [5000, 10000], iter_range = [25, 50], optimal_solution = "11100", repetitions = 2 ) We can investigate the data by calling ``visualize``: :: benchmark_data.visualize() .. image:: benchmark_plot.png The :ref:`QAOABenchmark` class contains a variety of methods to help you drawing conclusions from the collected data. Make sure to check them out! """ if callable(qarg): qarg_prep = qarg elif isinstance(qarg, QuantumArray): template = qarg.qtype.template() shape = qarg.shape def qarg_prep(): return QuantumArray(qtype=template.construct(), shape=shape) elif isinstance(qarg, QuantumVariableTemplate): def qarg_prep(): return qarg.construct() else: template = qarg.template() def qarg_prep(): return template.construct() data_dict = { "layer_depth": [], "circuit_depth": [], "qubit_amount": [], "shots": [], "iterations": [], "counts": [], "runtime": [], "cl_cost": [], } for p in depth_range: for s in shot_range: for it in iter_range: for k in range(repetitions): start_time = time.time() temp_mes_kwargs = dict(mes_kwargs) temp_mes_kwargs["shots"] = s counts = self.run( qarg_prep, depth=p, max_iter=it, mes_kwargs=temp_mes_kwargs, init_type=init_type, optimizer=optimizer, options=options, ) final_time = time.time() - start_time compiled_qc, _ = self.compile_circuit(qarg_prep(), depth=p) data_dict["layer_depth"].append(p) data_dict["circuit_depth"].append(compiled_qc.depth()) data_dict["qubit_amount"].append(compiled_qc.num_qubits()) data_dict["shots"].append(s) data_dict["iterations"].append(it) data_dict["counts"].append(counts) data_dict["runtime"].append(final_time) return QAOABenchmark(data_dict, optimal_solution, self.cl_cost_function)
[docs] def visualize_cost(self): """Visualizes the cost during the optimization process. Can only be used if ``callback=True``.""" import matplotlib.pyplot as plt if not self.callback: raise Exception("Visualization can only be performed for a QAOA instance with callback=True") x = list(range(len(self.optimization_costs))) y = self.optimization_costs plt.scatter( x, y, color="#20306f", marker="o", linestyle="solid", linewidth=1, label="QAOA cost", ) plt.xlabel("Iterations", fontsize=15, color="#444444") plt.ylabel("Cost", fontsize=15, color="#444444") plt.tick_params(axis="both", labelsize=12) plt.legend(fontsize=12, labelcolor="#444444") plt.grid() plt.show()