diff --git a/docs/index.rst b/docs/index.rst index 3aa5e38..c85fe46 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,6 +23,7 @@ of which has its own self-contained documentation. installation vqe qaoa + ising_qaoa qft phaseestimation tomography diff --git a/docs/ising_qaoa.rst b/docs/ising_qaoa.rst new file mode 100644 index 0000000..a8af8c0 --- /dev/null +++ b/docs/ising_qaoa.rst @@ -0,0 +1,269 @@ +Ising Wrapper for QAOA +====================== + +Overview +-------- + +Ising QAOA is a wrapper for the +`quantum approximate optimization algorithm `_ +that makes it easy to work within the framework of Ising-type Hamiltonians +with binary variables \\( \\sigma_{k} \\in \\{+1, -1\\} \\). + +This wrapper is particulary useful for people that have been working with quantum annealers +in the past. However, in comparison to current quantum annealers the Ising QAOA wrapper +supports not only 2-local but also k-local interaction terms and the driver (mixer) +Hamiltonian is not dictated by the hardware but can be defined by the user. + +For each Ising instance the user specifies the bias terms \\( h_{i} \\), the +interaction tensor \\( J_{i,j,..,k} \\) and the approximation order of the algorithm. +``ising_qaoa.py`` contains routines for approximating the ground state of +the specified Ising-type Hamiltonian through the use of QAOA which itself +finds the optimal rotation angles via Grove's +`variational-quantum-eigensolver method `_. + +.. _quickstart-example: + +Quickstart Example +------------------- + +To test your installation and get going we can run Ising QAOA for +a very simple Ising Hamiltonian: the 2D checkerboard. +(for a detailed explanation see :ref:`2d-checkerboard`) +In your python script import the packages and connect to your QVM: + +.. code-block:: python + + import pyquil.api as api + from grove.ising.ising_qaoa import ising_qaoa + qvm_connection = api.QVMConnection() + +Next we define the appropriate bias and interaction terms of the Ising Hamiltonian +for the checkerboard problem: + +.. code-block:: python + + J = {(0, 1): 1, (0, 2): 1, (1, 3): 1, (2, 3): 1} + h = {0: -1, 1: 1, 2: 1, 3: -1} + +There are plenty of optional configuration parameters for the algorithm but the two +most important are the number of steps to use for the trotterization (roughly corresponds to +the accuracy of the optimization) and the driver Hamiltonian (which determines how the +state space is searched). We instantiate the algorithm and run the optimization routine on our QVM: + +.. code-block:: python + + steps = 2 + solution_string, ising_energy, _ = ising_qaoa(h=h, J=J, num_steps=steps) + print(f'The algorithm returned {solution_string} with an energy of {ising_energy}') + +When running this routine you should observe the expectation value converging towards -8.0 +and the solution with the highest probability should be \\( [1, -1, -1, 1] \\) with an energy of \\( -8.0 \\). + +We can verify this by running the Ising QAOA multiple times and collecting statistics. +To do this, replace the last line of the last code block with: + +.. code-block:: python + + runs = 10 + stats = dict() + for _ in range(runs): + solution_string, ising_energy, _ = ising_qaoa(h=h, J=J, num_steps=steps) + if tuple(solution_string) in stats.keys(): + stats[tuple(solution_string)] += 1 + else: + stats[tuple(solution_string)] = 1 + print(f'Solution statistics: {stats}') + +You should see that the algorithm returns the aforementioned solution with ~99% probability (unless the classical minimizer got stuck in a local minima). + + +Algorithm and Details +--------------------- + +Introduction +~~~~~~~~~~~~ + +The Ising model is a very famous mathematical model by physicist Ernst Ising that was originally developed +to model ferromagnetism in statistical mechanics. The Ising model can be written as a +quadratic function of a set of spins \\( \\sigma_{i} = \\pm\, 1 \\): + +$$\\mathbf{H}(\\sigma_{i},...,\\sigma_{N}) = - \\sum_{i`_]: + +$$\\mathbf{H}(\\sigma_{i},...,\\sigma_{N}) = - \\sum_{i`_] +we know that finding the ground state of a two-dimensional Ising model without magnetic field lies +in the complexity class \\( P \\). However, as soon as you turn on that external magnetic field +you find yourself in the complexity class \\( NP \\) when trying to find the ground state and that's why +we are in desperate need for quantum computers to help us explore the energy landscape more efficiently! But +keep in mind that there is no proof that quantum computers can solve these types of problems in polynomial time... + +One of the reasons for the Ising model's popularity is the fact that many NP problems, such as number and graph partitioning or 3SAT, can be mapped to +it as discussed in e.g. [`3 `_]. + +This Ising QAOA wrapper automatically generates the cost Hamiltonian for the QAOA based on the provided +biases \\( h_{i} \\) and interaction terms \\( J_{i,j} \\). It operates with binary variables from the +set \\( \\{-1, +1\\} \\) and calculates the energy of the solution string. +Using either the QVM or the QPU, it can be used as a stand alone optimizer or a plugin +optimization routine in a larger environment. The usage pipeline is as follows: +1) formulate your problem in terms of an Ising model, +2) instantiate Ising QAOA with \\( h \\) and \\( J \\), +3) retrieve ground state solution by sampling. + +.. figure:: ising_qaoa/generalplan.png + :align: center + :figwidth: 100% + +The following sections give three concrete examples of how to use the +Ising QAOA wrapper to approximate ground states of various Hamiltonians. + +.. _2d-checkerboard: + +2-local Checkerboard Example +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Before we try to create a checkerboard pattern, let's quickly think about the interaction or coupling term. +Consider two spins \\( \\sigma_{i} \\) and \\( \\sigma_{j} \\) and their 2-local interaction term \\( J_{i,j} \\): + +.. image:: ising_qaoa/simple_coupling.png + :align: center + :scale: 75% + +Suppose there are no biases on spins \\( i \\) and \\( j \\) and the coupling is \\( J_{i,j} = -1 \\). +Which values of \\( \\sigma_{i} \\) and \\( \\sigma_{j} \\) minimize the energy? Both spins should have the same +value, either +1 or -1, in order to get an overall energy of -1. Hence, a negative coupling term *correlates* spins! + +If the coupling is \\( J_{i,j} = +1 \\) the energy is minimized when the two spins have opposite values. +Thus, a positive coupling *anti-correlates* spins! + +Now consider the following two-dimensional graph with spins \\( \\sigma_{0}, \\sigma_{1}, \\sigma_{2}, \\sigma_{3}\\): + +.. image:: ising_qaoa/2d_checkerboard.png + :align: center + :scale: 75% + +Let's define that we colour vertex \\( i \\) black if \\( \\sigma_{i} = -1 \\) and white if \\( \\sigma_{i} = +1 \\). +The goal is to create a checkerboard pattern with the four vertices in the graph. There is various ways of defining +\\( h \\) and \\( J \\) to achieve this result. There are two possible solutions to this problem: + +.. image:: ising_qaoa/2d_checkerboard_solutions.png + :align: center + :scale: 75% + +The example used in the :ref:`quickstart-example` is one way of doing it. However, it involved bias terms which +strongly biased for solution nr. 2. This time we want don't care which solution we get as long as it is a valid +solution. Hence, we don't use any bias terms and only anticorrelate each pair of neighbouring spins: + +.. code-block:: python + + import pyquil.api as api + from grove.ising.ising_qaoa import ising_qaoa + qvm_connection = api.QVMConnection() + + J = {(0, 1): 1, (0, 2): 1, (1, 3): 1, (2, 3): 1} + h = {} + + +Given this Ising problem, we run the QAOA algorithm with two \\( \\beta \\) and two \\( \\gamma \\) parameters (``steps=2``). +We run it ten times in order to collect some statistics: + +.. code-block:: python + + steps = 2 + runs = 10 + stats = dict() + for _ in range(runs): + solution_string, ising_energy, _ = ising_qaoa(h=h, J=J, num_steps=steps) + if tuple(solution_string) in stats.keys(): + stats[tuple(solution_string)] += 1 + else: + stats[tuple(solution_string)] = 1 + print(f'Solution statistics: {stats}') + +You should get the two possible solution strings \\( [-1, 1, 1, -1] \\) and \\( [1, -1, -1, 1] \\) +with roughly equal probabilities and an energy value of \\( -4.0 \\) each. + +3-local Triangle Example +~~~~~~~~~~~~~~~~~~~~~~~~ + +To illustrate the use of Ising QAOA with k-local interaction terms we will now consider a triangular graph. +This time we are dealing with 3 spins \\( \\sigma_{0}, \\sigma_{1}, \\sigma_{2}\\): + +.. image:: ising_qaoa/triangle.png + :align: center + :scale: 75% + +where the orange plane represents the 3-local interactions \\( J_{0,1,2} \\). The goal is to colour the graph +such that it looks like this: + +.. image:: ising_qaoa/triangle_desired.png + :align: center + :scale: 75% + +Let's again define that we colour vertex \\( i \\) black if \\( \\sigma_{i} = -1 \\) and white if \\( \\sigma_{i} = +1 \\). +To get the desired colouring we define a strongly negative 3-local interaction term \\( J_{0,1,2} \\). This ensures that +either all vertices are coloured white or two vertices are coloured black. In order to incentivize the latter, +we set the following biases on \\( \\sigma_{0} \\) and \\( \\sigma_{1}\\): + +.. code-block:: python + + import pyquil.api as api + from grove.ising.ising_qaoa import ising_qaoa + + qvm_connection = api.QVMConnection() + + J = {(0, 1, 2): -3} + h = {0: 1, 1: -1} + +We can now run the algorithm ten times with step size 2 to collect statistics (this might take a couple of minutes): + +.. code-block:: python + + steps = 2 + runs = 10 + stats = dict() + for _ in range(runs): + solution_string, ising_energy, _ = ising_qaoa(h=h, J=J, num_steps=steps) + if tuple(solution_string) in stats.keys(): + stats[tuple(solution_string)] += 1 + else: + stats[tuple(solution_string)] = 1 + print(f'Solution statistics: {stats}') + +The majority of the results should be the correct solution string \\( [-1, 1, -1 ] \\) (with energy -5.0). + +Source Code Docs +---------------- + +Here you can find documentation for the different functions of Ising QAOA. + +grove.ising.ising_qaoa.ising_qaoa +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: grove.ising.ising_qaoa.ising_qaoa + +grove.ising.ising_qaoa.ising_trans +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: grove.ising.ising_qaoa.ising_trans + +grove.ising.ising_qaoa.energy_value +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: grove.ising.ising_qaoa.energy_value diff --git a/docs/ising_qaoa/2d_checkerboard.png b/docs/ising_qaoa/2d_checkerboard.png new file mode 100644 index 0000000..04eb90e Binary files /dev/null and b/docs/ising_qaoa/2d_checkerboard.png differ diff --git a/docs/ising_qaoa/2d_checkerboard_solutions.png b/docs/ising_qaoa/2d_checkerboard_solutions.png new file mode 100644 index 0000000..83bde03 Binary files /dev/null and b/docs/ising_qaoa/2d_checkerboard_solutions.png differ diff --git a/docs/ising_qaoa/generalplan.png b/docs/ising_qaoa/generalplan.png new file mode 100644 index 0000000..7b6a17c Binary files /dev/null and b/docs/ising_qaoa/generalplan.png differ diff --git a/docs/ising_qaoa/simple_coupling.png b/docs/ising_qaoa/simple_coupling.png new file mode 100644 index 0000000..412875c Binary files /dev/null and b/docs/ising_qaoa/simple_coupling.png differ diff --git a/docs/ising_qaoa/triangle.png b/docs/ising_qaoa/triangle.png new file mode 100644 index 0000000..8eabf93 Binary files /dev/null and b/docs/ising_qaoa/triangle.png differ diff --git a/docs/ising_qaoa/triangle_desired.png b/docs/ising_qaoa/triangle_desired.png new file mode 100644 index 0000000..0933881 Binary files /dev/null and b/docs/ising_qaoa/triangle_desired.png differ diff --git a/examples/IsingSolver.ipynb b/examples/IsingSolver.ipynb index 4a43a7a..4d17f8f 100644 --- a/examples/IsingSolver.ipynb +++ b/examples/IsingSolver.ipynb @@ -20,7 +20,7 @@ "metadata": {}, "outputs": [], "source": [ - "from grove.ising.ising_qaoa import ising\n", + "from grove.ising.ising_qaoa import ising_qaoa\n", "from mock import patch" ] }, @@ -68,9 +68,9 @@ "outputs": [], "source": [ "J = {(0, 1): -2, (2, 3): 3}\n", - "h = [1, 1, -1, 1]\n", + "h = {0: 1, 1: 1, 2: -1, 3: 1}\n", "\n", - "solution, min_energy, circuit = ising(h, J, connection=cxn)" + "solution, min_energy, circuit = ising_qaoa(h, J, connection=cxn)" ] }, { @@ -86,7 +86,7 @@ "metadata": {}, "outputs": [], "source": [ - "solution_2, min_energy_2, circuit_2 = ising(h, J, num_steps=9, verbose=False, connection=cxn)" + "solution_2, min_energy_2, circuit_2 = ising_qaoa(h, J, num_steps=9, verbose=False, connection=cxn)" ] }, { diff --git a/grove/ising/ising_qaoa.py b/grove/ising/ising_qaoa.py index 3f6ba28..d2b8b91 100644 --- a/grove/ising/ising_qaoa.py +++ b/grove/ising/ising_qaoa.py @@ -1,4 +1,3 @@ - """ Finding the minimum energy for an Ising problem by QAOA. """ @@ -15,20 +14,27 @@ def energy_value(h, J, sol): """ Obtain energy of an Ising solution for a given Ising problem (h,J). - :param h: External magnectic term of the Ising problem. List. - :param J: Interaction term of the Ising problem. Dictionary. - :param sol: Ising solution. List. + :param dict h: External magnetic term of the Ising problem. + :param dict J: Interaction terms of the Ising problem (may be k-local). + :param list sol: Ising solution as returned from QVM/QPU. :return: Energy of the Ising string. :rtype: Integer or float. """ + # Solution string is reversed when obtained from qvm/qpu + sol = list(reversed(sol)) + ener_ising = 0 for elm in J.keys(): - if elm[0] == elm[1]: - raise TypeError("""Interaction term must connect two different variables""") + if len(elm) != len(set(elm)): + raise TypeError("Interaction term must connect different variables. " + "The term {} contains a duplicate.".format(elm)) else: - ener_ising += J[elm] * int(sol[elm[0]]) * int(sol[elm[1]]) - for i in range(len(h)): + multipliers = 1 + for idx in elm: + multipliers *= sol[idx] + ener_ising += J[elm] * multipliers + for i in h.keys(): ener_ising += h[i] * int(sol[i]) return ener_ising @@ -38,23 +44,34 @@ def print_fun(x): def ising_trans(x): - # Transformation to Ising notation + """ + Transformation to Ising notation. + + :param int x: Value of a single binary bit from {0, 1}. + :return: Transformed bit value from {-1, 1}. + :rtype: Integer. + """ if x == 1: return -1 else: return 1 -def ising(h, J, num_steps=0, verbose=True, rand_seed=None, connection=None, samples=None, - initial_beta=None, initial_gamma=None, minimizer_kwargs=None, - vqe_option=None): +def ising_qaoa(h, J, num_steps=0, driver_operators=None, verbose=True, + rand_seed=None, connection=None, samples=None, + initial_beta=None, initial_gamma=None, minimizer_kwargs=None, + vqe_option=None): """ - Ising set up method + Ising set up method for QAOA. Supports 2-local as well as k-local interaction terms. - :param h: External magnectic term of the Ising problem. List. - :param J: Interaction term of the Ising problem. Dictionary. + :param dict h: External magnectic term of the Ising problem. + :param dict J: Interaction terms of the Ising problem (may be k-local). :param num_steps: (Optional.Default=2 * len(h)) Trotterization order for the QAOA algorithm. + :param list driver_operators: (Optional. Default: X on all qubits.) The mixer/driver + Hamiltonian used in QAOA. Can be used to enforce hard constraints + and ensure that solution stays in feasible subspace. + Must be list of PauliSum objects. :param verbose: (Optional.Default=True) Verbosity of the code. :param rand_seed: (Optional. Default=None) random seed when beta and gamma angles are not provided. @@ -68,11 +85,9 @@ def ising(h, J, num_steps=0, verbose=True, rand_seed=None, connection=None, samp parameters. :param minimizer_kwargs: (Optional. Default=None). Minimizer optional arguments. If None set to - {'method': 'Nelder-Mead', - 'options': {'ftol': 1.0e-2, 'xtol': 1.0e-2, - 'disp': False} - :param vqe_option: (Optional. Default=None). VQE optional - arguments. If None set to + {'method': 'Nelder-Mead', 'options': {'ftol': 1.0e-2, + 'xtol': 1.0e-2, disp': False} + :param vqe_option: (Optional. Default=None). VQE optional arguments. If None set to vqe_option = {'disp': print_fun, 'return_all': True, 'samples': samples} :return: Most frequent Ising string, Energy of the Ising string, Circuit used to obtain result. @@ -82,18 +97,30 @@ def ising(h, J, num_steps=0, verbose=True, rand_seed=None, connection=None, samp if num_steps == 0: num_steps = 2 * len(h) - n_nodes = len(h) + qubit_indices = set([index for tuple_ in list(J.keys()) for index in tuple_] + + list(h.keys())) + n_nodes = len(qubit_indices) cost_operators = [] - driver_operators = [] - for i, j in J.keys(): - cost_operators.append(PauliSum([PauliTerm("Z", i, J[(i, j)]) * PauliTerm("Z", j)])) + for key in J.keys(): + # first PauliTerm is multiplied with coefficient obtained from J + pauli_product = PauliTerm("Z", key[0], J[key]) + + for i in range(1,len(key)): + # multiply with additional Z PauliTerms depending + # on the locality of the interaction terms + pauli_product *= PauliTerm("Z", key[i]) + + cost_operators.append(PauliSum([pauli_product])) - for i in range(n_nodes): + for i in h.keys(): cost_operators.append(PauliSum([PauliTerm("Z", i, h[i])])) - for i in range(n_nodes): - driver_operators.append(PauliSum([PauliTerm("X", i, -1.0)])) + if driver_operators is None: + driver_operators = [] + # default to X mixer + for i in qubit_indices: + driver_operators.append(PauliSum([PauliTerm("X", i, 1.0)])) if connection is None: connection = CXN @@ -119,8 +146,7 @@ def ising(h, J, num_steps=0, verbose=True, rand_seed=None, connection=None, samp vqe_options=vqe_option) betas, gammas = qaoa_inst.get_angles() - most_freq_string, sampling_results = qaoa_inst.get_string( - betas, gammas) + most_freq_string, sampling_results = qaoa_inst.get_string(betas, gammas) most_freq_string_ising = [ising_trans(it) for it in most_freq_string] energy_ising = energy_value(h, J, most_freq_string_ising) param_prog = qaoa_inst.get_parameterized_program() diff --git a/grove/pyqaoa/qaoa.py b/grove/pyqaoa/qaoa.py index 8103aab..612f48d 100644 --- a/grove/pyqaoa/qaoa.py +++ b/grove/pyqaoa/qaoa.py @@ -139,7 +139,7 @@ def get_parameterized_program(self): cost_para_programs = [] driver_para_programs = [] - for idx in range(self.steps): + for _ in range(self.steps): cost_list = [] driver_list = [] for cost_pauli_sum in self.cost_ham: diff --git a/grove/tests/ising/test_ising.py b/grove/tests/ising/test_ising.py index b76d8d5..de1c585 100644 --- a/grove/tests/ising/test_ising.py +++ b/grove/tests/ising/test_ising.py @@ -1,17 +1,29 @@ -from grove.ising.ising_qaoa import ising -from grove.ising.ising_qaoa import energy_value import numpy as np from mock import patch +from pyquil.paulis import PauliSum, PauliTerm +from grove.ising.ising_qaoa import energy_value, ising_trans, ising_qaoa + def test_energy_value(): J = {(0, 1): 2.3} - h = [-2.4, 5.2] + h = {0: -2.4, 1: 5.2} sol = [1, -1] ener_ising = energy_value(h, J, sol) - assert(np.isclose(ener_ising, -9.9)) + assert(np.isclose(ener_ising, 5.3)) + + J = {(0, 1, 2): 1.2, (0, 1, 2, 3): 2.5, (0, 2, 3): 0.5, (1, 3): 3.1} + h = {0: -2.4, 1: 5.2, 3: -0.3} + sol = [1, -1, -1, 1] + ener_ising = energy_value(h, J, sol) + assert(np.isclose(ener_ising, -7.8)) + +def test_ising_trans(): + sol = [0, 1, 1, 0] + ising_sol = [ising_trans(bit) for bit in sol] + assert ising_sol == [1, -1, -1, 1] def test_ising_mock(): with patch("pyquil.api.QVMConnection") as cxn: @@ -20,9 +32,83 @@ def test_ising_mock(): cxn.expectation.return_value = [-0.4893891813015294, 0.8876822987380573, -0.4893891813015292, -0.9333372094534063, -0.9859245403423198, 0.9333372094534065] J = {(0, 1): -2, (2, 3): 3} - h = [1, 1, -1, 1] + h = {0: 1, 1: 1, 2: -1, 3: 1} p = 1 - most_freq_string_ising, energy_ising, circuit = ising(h, J, num_steps=p, vqe_option=None, connection=cxn) + most_freq_string_ising, energy_ising, circuit = ising_qaoa(h, J, num_steps=p, vqe_option=None, connection=cxn) assert most_freq_string_ising == [-1, -1, 1, -1] - assert energy_ising == -9 + assert energy_ising == 5 + + with patch("pyquil.api.QVMConnection") as cxn: + # Mock the response + cxn.run_and_measure.return_value = [[1, 0, 1, 0]] + cxn.expectation.return_value = [0, 0, 0, 0] # dummy + + # checkerboard with couplings + J = {(0, 1): 1, (0, 2): 1, (1, 3): 1, (2, 3): 1} + h = {} + p = 1 + most_freq_string_ising, energy_ising, circuit = ising_qaoa(h, J, num_steps=p, vqe_option=None, connection=cxn) + + assert most_freq_string_ising == [-1, 1, -1, 1] + assert energy_ising == 0 + + with patch("pyquil.api.QVMConnection") as cxn: + # Mock the response + cxn.run_and_measure.return_value = [[1, 0, 1, 0]] + cxn.expectation.return_value = [0, 0, 0, 0] # dummy + + # checkerboard with biases + J = {} + h = {0: 1, 1: -1, 2: 1, 3: -1} + p = 1 + most_freq_string_ising, energy_ising, circuit = ising_qaoa(h, J, num_steps=p, vqe_option=None, connection=cxn) + + assert most_freq_string_ising == [-1, 1, -1, 1] + assert energy_ising == 4 + + with patch("pyquil.api.QVMConnection") as cxn: + # Mock the response + cxn.run_and_measure.return_value = [[1, 0, 1, 0, 1]] + cxn.expectation.return_value = [0, 0, 0, 0, 0] # dummy + + J = {(0, 4): -1} + h = {0: 1, 1: -1, 2: 1, 3: -1} + p = 1 + most_freq_string_ising, energy_ising, circuit = ising_qaoa(h, J, num_steps=p, vqe_option=None, connection=cxn) + + assert most_freq_string_ising == [-1, 1, -1, 1, -1] + assert energy_ising == -5 + + with patch("pyquil.api.QVMConnection") as cxn: + # Mock the response + cxn.run_and_measure.return_value = [[0, 1, 1, 0]] + cxn.expectation.return_value = [0, 0, 0, 0, 0, 0, 0] # dummy + + J = {(0, 1, 2): 1.2, (0, 1, 2, 3): 2.5, (0, 2, 3): 0.5, (1, 3): 3.1} + h = {0: -2.4, 1: 5.2, 3: -0.3} + p = 1 + most_freq_string_ising, energy_ising, circuit = ising_qaoa(h, J, num_steps=p, vqe_option=None, connection=cxn) + + assert most_freq_string_ising == [1, -1, -1, 1] + assert energy_ising == -7.8 + + with patch("pyquil.api.QVMConnection") as cxn: + # Mock the response + cxn.run_and_measure.return_value = [[0, 1, 1, 0]] + cxn.expectation.return_value = [0, 0, 0, 0, 0, 0, 0] # dummy + + swap_mixer = [] + for i in range(4): + for j in range(4): + if j != i: + swap_mixer.append(PauliSum([PauliTerm("X", i, 0.5) * PauliTerm("X", j, 1.0)])) + swap_mixer.append(PauliSum([PauliTerm("Y", i, 0.5) * PauliTerm("Y", j, 1.0)])) + + J = {(0, 1, 2): 1.2, (0, 1, 2, 3): 2.5, (0, 2, 3): 0.5, (1, 3): 3.1} + h = {0: -2.4, 1: 5.2, 3: -0.3} + p = 1 + most_freq_string_ising, energy_ising, circuit = ising_qaoa(h, J, driver_operators=swap_mixer, num_steps=p, vqe_option=None, connection=cxn) + + assert most_freq_string_ising == [1, -1, -1, 1] + assert energy_ising == -7.8