Repository: entropicalabs/openqaoa (language: python)

Issue:
updated the structure of device creation, including removing qpu_auth…
This pull request will bring MAJOR changes to the following:
- The qpu_auth is gone, and so is the access_object structure along with it! Users will now have to `create_device` for both local and cloud machines (providing credentials during initialisation for cloud ones)
- Simulator backends have updates to their names
- Workflows, some functions in RQAOA, factory methods like `get_qaoa_backend` have been updated to reflect this change
- The unit-tests and all notebooks have been updated to reflect these changes 
- A few bugs have also been fixed along the way


Discussion / hints:

Fix patch:
diff --git a/src/openqaoa-core/problems/__init__.py b/src/openqaoa-core/problems/__init__.py
index 6b65c1e66..f366d199c 100644
--- a/src/openqaoa-core/problems/__init__.py
+++ b/src/openqaoa-core/problems/__init__.py
@@ -4,6 +4,7 @@
 from .numberpartition import NumberPartition
 from .shortestpath import ShortestPath
 from .tsp import TSP
+from .portfoliooptimization import PortfolioOptimization
 from .maximalindependentset import MIS
 from .binpacking import BinPacking
 from .converters import FromDocplex2IsingModel
diff --git a/src/openqaoa-core/problems/portfoliooptimization.py b/src/openqaoa-core/problems/portfoliooptimization.py
new file mode 100644
index 000000000..886ebdab8
--- /dev/null
+++ b/src/openqaoa-core/problems/portfoliooptimization.py
@@ -0,0 +1,224 @@
+import numpy as np
+from docplex.mp.model import Model
+import networkx as nx
+import matplotlib.pyplot as plt
+
+from .problem import Problem
+from .converters import FromDocplex2IsingModel
+from .qubo import QUBO
+
+
+class PortfolioOptimization(Problem):
+    """
+    Creates an instance of the portfolio optimization problem.
+    https://en.wikipedia.org/wiki/Portfolio_optimization
+
+    Parameters
+    ----------
+    mu: List[int]
+        The expected return
+    sigma: List[List[int]]
+        The covariance matrix.
+    risk_factor: float
+        desired risk factor exposures
+    budget: int
+        maximum number of assets to buy
+    penalty: float
+        Penalty for the maximum budget.
+
+    Returns
+    -------
+        An instance of the portfolio optimization problem.
+    """
+
+    __name__ = "portfolio_optimization"
+
+    def __init__(self, mu, sigma, risk_factor, budget, penalty):
+        # Check whether the input is valid. Number of values should match the number of weights.
+        for s_i in sigma:
+            if len(mu) != len(s_i):
+                raise ValueError("Number of assets does not match sigma")
+        self.mu = mu
+        self.sigma = sigma
+        self.risk_factor = risk_factor
+        self.penalty = penalty
+        self.num_assets = len(mu)
+        self.budget = budget
+
+    @staticmethod
+    def random_instance(**kwargs):
+        """
+        Creates a random instance of the portfolio optimization problem.
+
+        Parameters
+        ----------
+        num_assets: int
+            The number of assets.
+        mu_bounds: tuple
+            bounds of the expected return
+        sigma_bounds: tuple
+            bound of the covariance matrix
+        risk_factor: float
+            desired risk factor exposures
+        seed: int
+            Seed for the random number selection.
+
+        Returns
+        -------
+            A random instance of the portfolio optimization problem.
+        """
+        num_assets = kwargs.get("num_assets", 8)
+        seed = kwargs.get("seed", 1234)
+        mu_bounds = kwargs.get("mu_bounds", (-1, 1))
+        sigma_bounds = kwargs.get("sigma_bounds", (-0.1, 0.1))
+        budget = kwargs.get("budget", 5)
+        risk_factor = kwargs.get("risk_factor", 0.1)
+        penalty = kwargs.get("penalty", 2)
+
+        np.random.seed(seed)
+
+        mu = [
+            (mu_bounds[1] - mu_bounds[0]) * np.random.rand() + mu_bounds[0]
+            for _ in range(num_assets)
+        ]
+        sigma = [[0 for i in range(num_assets)] for j in range(num_assets)]
+        for i in range(num_assets):
+            for j in range(num_assets):
+                sigma[i][j] = (
+                    sigma_bounds[1] - sigma_bounds[0]
+                ) * np.random.rand() + sigma_bounds[0]
+
+        return PortfolioOptimization(mu, sigma, risk_factor, budget, penalty)
+
+    @property
+    def docplex_model(self):
+        num_assets = self.num_assets
+
+        # Start the docplex model with Model("name of the model")
+        mdl = Model("Portfolio_Optimization")
+
+        # Consider the number of variables as num_assets,
+        # and binary set of variables that represent the stocks
+        # x vector in numpy array for matrix multiplication
+        x = np.array(mdl.binary_var_list(num_assets, name="asset"))
+
+        # Specific the objective of the
+        # portfolio optimization function
+        objective_function = -np.array(self.mu) @ x + x.T @ np.array(self.sigma) @ x
+
+        # For this problem it aims to maximize the profit
+        # of those assets minimizing the risk of the investment
+        mdl.minimize(objective_function)
+
+        # Budget constraint
+        mdl.add_constraint(mdl.sum(x) == self.budget, ctname="budget")
+
+        return mdl
+
+    @property
+    def qubo(self):
+        """
+        Returns the QUBO encoding of this problem.
+        Returns
+        -------
+            The QUBO encoding of this problem.
+        """
+        cplex_model = self.docplex_model
+        n_vars = cplex_model.number_of_binary_variables
+        qubo_docplex = FromDocplex2IsingModel(cplex_model, multipliers=self.penalty)
+        ising_model = qubo_docplex.ising_model
+        return QUBO(
+            n_vars,
+            ising_model.terms + [[]],
+            ising_model.weights + [ising_model.constant],
+            self.problem_instance,
+        )
+
+    def classical_solution(self, string: bool = False):
+        """
+        Return the classical solution of the portfolio optimization problem
+        Parameters
+        ----------
+        string : bool, optional
+            If the solution is returned as a string. The default is False.
+        Raises
+        ------
+        ValueError
+            A flag if docplex does not find a valid solution.
+        Returns
+        -------
+        solution : Union[str, dict]
+            The classical solution of the specific problem as a string or a dict.
+        """
+        cplex_model = self.docplex_model
+        docplex_sol = cplex_model.solve()
+
+        if docplex_sol is None:
+            raise ValueError(f"solution not found: {cplex_model.solve_details.status}")
+
+        if string:
+            solution = ""
+        else:
+            solution = {}
+        for var in cplex_model.iter_binary_vars():
+            if string:
+                # round is used because sometimes docplex gives the solution close to 1 but not 1,
+                #if we want to store this as an integer and use int(value) and the value is 0.999999
+                #it will store a zero.
+                solution += str(round(docplex_sol.get_value(var), 1))
+            else:
+                solution[var.name] = round(docplex_sol.get_value(var), 1)
+        return solution
+
+    def plot_solution(self, solution, ax=None):
+        G = nx.Graph()
+        G.add_nodes_from(range(self.num_assets))
+        edge_list = []
+        width = []
+        for i in range(self.num_assets - 1):
+            for j in range(i + 1, self.num_assets):
+                G.add_edge(i, j, weight=self.sigma[i][j])
+                edge_list.append((i, j))
+                width.append(self.sigma[i][j])
+        pos = nx.circular_layout(G)
+        if ax is None:
+            fig, ax = plt.subplots()
+        else:
+            fig = None
+        graph_colors_sol = [
+            "#F29D55" if int(i) else "#28C9C9" for i in solution.values()
+        ]
+
+        nx.draw(
+            G,
+            pos=pos,
+            labels={i: k for i, k in enumerate(solution.keys())},
+            node_size=900,
+            node_color=graph_colors_sol,
+            ax=ax,
+            edgecolors="grey",
+        )
+        nx.draw_networkx_edges(G, pos, edgelist=edge_list, width=width, ax=ax)
+        ax.plot(
+            [],
+            [],
+            marker="o",
+            linewidth=0,
+            markeredgecolor="grey",
+            color="#F29D55",
+            label="selected",
+            markersize=10,
+        )
+        ax.plot(
+            [],
+            [],
+            marker="o",
+            linewidth=0,
+            markeredgecolor="grey",
+            color="#28C9C9",
+            label="non-selected",
+            markersize=10,
+        )
+
+        ax.legend(loc="upper center", bbox_to_anchor=(0.5, 1.08), ncol=2)
+        return fig


Test patch:
diff --git a/tests/test_problems.py b/tests/test_problems.py
index c8cc7c954..b921ca339 100644
--- a/tests/test_problems.py
+++ b/tests/test_problems.py
@@ -11,6 +11,7 @@
     SlackFreeKnapsack,
     MaximumCut,
     MinimumVertexCover,
+    PortfolioOptimization,
     MIS,
     BinPacking,
 )
@@ -31,6 +32,7 @@ def terms_list_equality(terms_list1, terms_list2):
 
     return bool
 
+
 def terms_list_isclose(terms_list1, terms_list2):
     """
     Check if the distance between two terms list
@@ -40,13 +42,16 @@ def terms_list_isclose(terms_list1, terms_list2):
         bool = False
     else:
         for term1, term2 in zip(terms_list1, terms_list2):
-            bool = True if np.isclose(term1, term2) or np.isclose(term1, term2[::-1]) else False
+            bool = (
+                True
+                if np.isclose(term1, term2) or np.isclose(term1, term2[::-1])
+                else False
+            )
 
     return bool
 
 
 class TestProblem(unittest.TestCase):
-
     # TESTING QUBO CLASS METHODS
     def test_qubo_terms_and_weight_same_size(self):
         """
@@ -903,7 +908,7 @@ def test_tsp_type_checking(self):
 
         # Graph type-check
         G = nx.complete_graph(5)
-        for (u, v) in G.edges():
+        for u, v in G.edges():
             G[u][v]["weight"] = "a"
 
         with self.assertRaises(TypeError) as e:
@@ -912,7 +917,7 @@ def test_tsp_type_checking(self):
             "The edge weights must be of type float or int", str(e.exception)
         )
 
-        for (u, v) in G.edges():
+        for u, v in G.edges():
             G[u][v]["weight"] = -2.0
 
         with self.assertRaises(ValueError) as e:
@@ -981,7 +986,7 @@ def test_shortestpath_terms_weights_constant(self):
         sp_qubo_constant = 2.5
 
         gr = nx.generators.fast_gnp_random_graph(3, 1, seed=1234)
-        for (u, v) in gr.edges():
+        for u, v in gr.edges():
             gr.edges[u, v]["weight"] = 1
         for w in gr.nodes():
             gr.nodes[w]["weight"] = 1
@@ -1007,7 +1012,7 @@ def test_shortestpath_random_instance(self):
         sp_rand_constant = 2.5
 
         gr = nx.generators.fast_gnp_random_graph(3, 1, seed=1234)
-        for (u, v) in gr.edges():
+        for u, v in gr.edges():
             gr.edges[u, v]["weight"] = 1.0
         for w in gr.nodes():
             gr.nodes[w]["weight"] = 1.0
@@ -1046,13 +1051,124 @@ def test_assertion_fn():
 
         self.assertRaises(Exception, test_assertion_fn)
 
+    # TESTING PORTFOLIO OPTIMIZATION PROBLEM CLASS
+
+    def test_portfoliooptimization_terms_weights_constant(self):
+        """Test terms,weights,constant of QUBO generated by Portfolio Optimization class"""
+
+        po_terms = [[0, 1], [0, 2], [1, 2], [0], [1], [2]]
+        po_weights = [0.505, 0.505, 0.505, 0.535, 0.535, 0.535]
+        po_constant = 0.8799999999999999
+
+        mu = [0.1, 0.1, 0.1]
+        sigma = [[0.01, 0.01, 0.01], [0.01, 0.01, 0.01], [0.01, 0.01, 0.01]]
+        risk_factor = 0.1
+        budget = 2
+        penalty = 1
+
+        qubo = PortfolioOptimization(mu, sigma, risk_factor, budget, penalty).qubo
+        terms, weights = qubo.terms, qubo.weights
+        constant = qubo.constant
+
+        self.assertEqual(weights, po_weights)
+        self.assertEqual(terms, po_terms)
+        self.assertEqual(po_constant, constant)
+
+    def test_portfoliooptimization_random_instance(self):
+        """Test random instance method of Portfolio Optimization problem class"""
+        seed = 1234
+        np.random.seed(seed)
+        num_assets = 3
+        risk_factor = 0.1
+        budget = 2
+        penalty = 1
+        mu_bounds = [-0.1, 0.1]
+        sigma_bounds = [-0.01, 0.01]
+        mu = [
+            (mu_bounds[1] - mu_bounds[0]) * np.random.rand() + mu_bounds[0]
+            for _ in range(num_assets)
+        ]
+        sigma = [[0 for i in range(num_assets)] for j in range(num_assets)]
+        for i in range(num_assets):
+            for j in range(num_assets):
+                sigma[i][j] = (
+                    sigma_bounds[1] - sigma_bounds[0]
+                ) * np.random.rand() + sigma_bounds[0]
+
+        qubo = PortfolioOptimization(mu, sigma, risk_factor, budget, penalty).qubo
+
+        qubo_random = PortfolioOptimization.random_instance(
+            mu_bounds=mu_bounds,
+            sigma_bounds=sigma_bounds,
+            risk_factor=risk_factor,
+            budget=budget,
+            seed=seed,
+            penalty=penalty,
+            num_assets=num_assets,
+        ).qubo
+        self.assertEqual(qubo.weights, qubo_random.weights)
+        self.assertEqual(qubo.terms, qubo_random.terms)
+        self.assertEqual(qubo.constant, qubo_random.constant)
+
+    def test_portfoliooptimization_classical_sol(self):
+        """Test the portfolio optimization set random instance method classical solution"""
+
+        seed = 1234
+        np.random.seed(seed)
+        po_sol = PortfolioOptimization.random_instance(
+            num_assets=10, seed=seed
+        ).classical_solution()
+
+        sol = {
+            "asset_0": 0,
+            "asset_1": 0,
+            "asset_2": 0,
+            "asset_3": 1,
+            "asset_4": 1,
+            "asset_5": 0,
+            "asset_6": 0,
+            "asset_7": 1,
+            "asset_8": 1,
+            "asset_9": 1,
+        }
+
+        self.assertEqual(po_sol, sol)
+
+    def test_portfoliooptimization_plot(self):
+        """Test portfolio optmization random instance method"""
+        import matplotlib.pyplot as plt
+
+        seed = 1234
+        porfolitooptimization_random_prob = PortfolioOptimization.random_instance(
+            num_assets=12, budget=8, seed=seed
+        )
+        sol = porfolitooptimization_random_prob.classical_solution()
+        figure = porfolitooptimization_random_prob.plot_solution(sol)
+        self.assertTrue(isinstance(figure, plt.Figure))
+        fig, ax = plt.subplots(figsize=(5, 5))
+        self.assertTrue(
+            porfolitooptimization_random_prob.plot_solution(sol, ax=ax) == None
+        )
+
     # TESTING MAXIMAL INDEPENDENT SET PROBLEM
 
     def test_mis_terms_weights_constant(self):
         """Test that MaximumCut creates a correct QUBO from the provided graph"""
 
         gr = nx.generators.random_graphs.fast_gnp_random_graph(n=5, p=0.8, seed=1234)
-        gr_edges = [[0, 3], [0, 4], [1, 2], [1, 3], [2, 4], [3, 4], [0], [1], [2], [3], [4]]
+        gr_edges = [
+            [0, 3],
+            [0, 4],
+            [1, 2],
+            [1, 3],
+            [2, 4],
+            [3, 4],
+            [0],
+            [1],
+            [2],
+            [3],
+            [4],
+        ]
         gr_weights = [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, -0.5, -1.0, -1.0]
 
         mis_prob_qubo = MIS(gr).qubo
@@ -1100,42 +1216,46 @@ def test_mis_classical_sol(self):
         seed = 1234
         np.random.seed(seed)
         mis_sol = MIS.random_instance(
-            n_nodes=10, edge_probability = 0.7, seed=seed).classical_solution()
-        
-        sol = {'x_0': 1,
-         'x_1': 1,
-         'x_2': 0,
-         'x_3': 0,
-         'x_4': 0,
-         'x_5': 0,
-         'x_6': 0,
-         'x_7': 0,
-         'x_8': 1,
-         'x_9': 0}
+            n_nodes=10, edge_probability=0.7, seed=seed
+        ).classical_solution()
+
+        sol = {
+            "x_0": 1,
+            "x_1": 1,
+            "x_2": 0,
+            "x_3": 0,
+            "x_4": 0,
+            "x_5": 0,
+            "x_6": 0,
+            "x_7": 0,
+            "x_8": 1,
+            "x_9": 0,
+        }
 
         self.assertEqual(mis_sol, sol)
 
     def test_mis_plot(self):
         """Test maximal independent set random instance method"""
         from matplotlib.pyplot import Figure
+
         seed = 1234
         mis_random_prob = MIS.random_instance(
-            n_nodes=10, edge_probability = 0.7, seed=seed
+            n_nodes=10, edge_probability=0.7, seed=seed
         )
-        sol = {'x_0': 1,
-         'x_1': 1,
-         'x_2': 0,
-         'x_3': 0,
-         'x_4': 0,
-         'x_5': 0,
-         'x_6': 0,
-         'x_7': 0,
-         'x_8': 1,
-         'x_9': 0}
+        sol = {
+            "x_0": 1,
+            "x_1": 1,
+            "x_2": 0,
+            "x_3": 0,
+            "x_4": 0,
+            "x_5": 0,
+            "x_6": 0,
+            "x_7": 0,
+            "x_8": 1,
+            "x_9": 0,
+        }
         fig = mis_random_prob.plot_solution(sol)
-        self.assertTrue(
-            isinstance(fig, Figure)
-        )
+        self.assertTrue(isinstance(fig, Figure))
 
     def __generate_random_problems(self):
         problems_random_instances = {
@@ -1159,8 +1279,7 @@ def __generate_random_problems(self):
             "maximal_independent_set": MIS.random_instance(
                 n_nodes=randint(3, 15), edge_probability=random()
             ),
-            "bin_packing": BinPacking.random_instance(
-            ),
+            "bin_packing": BinPacking.random_instance(),
         }
         qubo_random_instances = {
             k: v.qubo for k, v in problems_random_instances.items()
@@ -1171,7 +1290,10 @@ def __generate_random_problems(self):
     def test_problem_instance(self):
         """
         Test problem instance method of the QUBO class.
-        From the random instance of all the different problems, we generate the QUBO problem out of it and then we check if the problem instance attribute is correct, by comparing the keys of the problem instance with the expected keys.
+        From the random instance of all the different problems, we generate the
+        QUBO problem out of it and then we check if the problem instance
+        attribute is correct, by comparing the keys of the problem instance
+        with the expected keys.
         """
 
         _, qubos = self.__generate_random_problems()
@@ -1199,9 +1321,18 @@ def test_problem_instance(self):
             "minimum_vertex_cover": ["problem_type", "G", "field", "penalty"],
             "shortest_path": ["problem_type", "G", "source", "dest"],
             "maximal_independent_set": ["problem_type", "G", "penalty"],
-            "bin_packing":["problem_type", "weights", "weight_capacity","penalty",
-                           "n_items", "method", "simplifications", "n_bins",
-                           'min_bins', 'solution'],
+            "bin_packing": [
+                "problem_type",
+                "weights",
+                "weight_capacity",
+                "penalty",
+                "n_items",
+                "method",
+                "simplifications",
+                "n_bins",
+                "min_bins",
+                "solution",
+            ],
             "generic_qubo": ["problem_type"],
         }
 
@@ -1261,7 +1392,6 @@ def test_qubo_from_dict(self):
 
         _, qubos = self.__generate_random_problems()
         for _, qubo in qubos.items():
-
             qubo_dict = qubo.asdict()
 
             new_qubo = QUBO.from_dict(qubo_dict)
@@ -1290,95 +1420,130 @@ def test_qubo_from_dict(self):
     # TESTING BINPACKING CLASS
     def test_binpacking_terms_weights_constant(self):
         """Test that BinPacking creates a correct QUBO from the provided weights and terms"""
-        
-        terms = [[2, 3],
-                 [4, 5],
-                 [0, 2],
-                 [0, 4],
-                 [0, 6],
-                 [2, 4],
-                 [2, 6],
-                 [4, 6],
-                 [1, 3],
-                 [1, 5],
-                 [1, 7],
-                 [3, 5],
-                 [3, 7],
-                 [5, 7],
-                 [0],
-                 [1],
-                 [2],
-                 [3],
-                 [4],
-                 [5],
-                 [6],
-                 [7]]
-
-        weights = [1.5,
-         1.5,
-         -0.75,
-         -1.0,
-         -1.5,
-         0.5,
-         0.75,
-         1.0,
-         -0.75,
-         -1.0,
-         -1.5,
-         0.5,
-         0.75,
-         1.0,
-         1.25,
-         1.25,
-         -0.875,
-         -0.875,
-         -1.1666666666666665,
-         -1.1666666666666665,
-         -1.75,
-         -1.75]
+
+        terms = [
+            [2, 3],
+            [4, 5],
+            [0, 2],
+            [0, 4],
+            [0, 6],
+            [2, 4],
+            [2, 6],
+            [4, 6],
+            [1, 3],
+            [1, 5],
+            [1, 7],
+            [3, 5],
+            [3, 7],
+            [5, 7],
+            [0],
+            [1],
+            [2],
+            [3],
+            [4],
+            [5],
+            [6],
+            [7],
+        ]
+
+        weights = [
+            1.5,
+            1.5,
+            -0.75,
+            -1.0,
+            -1.5,
+            0.5,
+            0.75,
+            1.0,
+            -0.75,
+            -1.0,
+            -1.5,
+            0.5,
+            0.75,
+            1.0,
+            1.25,
+            1.25,
+            -0.875,
+            -0.875,
+            -1.1666666666666665,
+            -1.1666666666666665,
+            -1.75,
+            -1.75,
+        ]
         constant = 10.083333333333332
         weights_list = [3, 4]
         weight_capacity = 6
-        binpacking_prob_qubo = BinPacking(weights_list, weight_capacity, simplifications=False).qubo
+        binpacking_prob_qubo = BinPacking(
+            weights_list, weight_capacity, simplifications=False
+        ).qubo
         self.assertTrue(terms_list_equality(terms, binpacking_prob_qubo.terms))
         self.assertTrue(terms_list_isclose(weights, binpacking_prob_qubo.weights))
         self.assertTrue(np.isclose(constant, binpacking_prob_qubo.constant))
 
     def test_binpacking_terms_weights_constant_simplified(self):
         """Test that BinPacking creates a correct QUBO from the provided weights and terms"""
-        
-        terms = [[1, 2],
-                 [1, 3],
-                 [2, 3],
-                 [4, 5],
-                 [4, 6],
-                 [5, 6],
-                 [1, 4],
-                 [1, 7],
-                 [4, 7],
-                 [2, 5],
-                 [8, 2],
-                 [8, 5],
-                 [0, 3],
-                 [0, 6],
-                 [0, 9],
-                 [3, 6],
-                 [9, 3],
-                 [9, 6],
-                 [0],
-                 [1],
-                 [2],
-                 [3],
-                 [4],
-                 [5],
-                 [6],
-                 [7],
-                 [8],
-                 [9]]
-
-        weights = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.2, 0.28, 0.35, 0.2, 0.4, 0.5, -0.4,
-                    -0.5, -1.0, 0.2, 0.4, 0.5, 0.4, -1.08, -0.96, -1.36, -1.1, -0.95,
-                    -1.45, -0.14, 0.1, -0.9]
+
+        terms = [
+            [1, 2],
+            [1, 3],
+            [2, 3],
+            [4, 5],
+            [4, 6],
+            [5, 6],
+            [1, 4],
+            [1, 7],
+            [4, 7],
+            [2, 5],
+            [8, 2],
+            [8, 5],
+            [0, 3],
+            [0, 6],
+            [0, 9],
+            [3, 6],
+            [9, 3],
+            [9, 6],
+            [0],
+            [1],
+            [2],
+            [3],
+            [4],
+            [5],
+            [6],
+            [7],
+            [8],
+            [9],
+        ]
+
+        weights = [
+            1.0,
+            1.0,
+            1.0,
+            1.0,
+            1.0,
+            1.0,
+            0.2,
+            0.28,
+            0.35,
+            0.2,
+            0.4,
+            0.5,
+            -0.4,
+            -0.5,
+            -1.0,
+            0.2,
+            0.4,
+            0.5,
+            0.4,
+            -1.08,
+            -0.96,
+            -1.36,
+            -1.1,
+            -0.95,
+            -1.45,
+            -0.14,
+            0.1,
+            -0.9,
+        ]
         constant = 9.29
         weights_list = [3, 4, 5]
         weight_capacity = 10
@@ -1387,74 +1552,119 @@ def test_binpacking_terms_weights_constant_simplified(self):
         self.assertTrue(terms_list_equality(terms, binpacking_prob_qubo.terms))
         self.assertTrue(terms_list_isclose(weights, binpacking_prob_qubo.weights))
         self.assertTrue(np.isclose(constant, binpacking_prob_qubo.constant))
-        
+
     def test_binpacking_terms_weights_constant_unbalanced(self):
-            """Test that BinPacking creates a correct QUBO from the provided weights and terms
-            using the unbalanced penalization encoding"""
-            terms = [[1, 2],
-                     [1, 3],
-                     [2, 3],
-                     [4, 5],
-                     [4, 6],
-                     [5, 6],
-                     [1, 4],
-                     [2, 5],
-                     [0, 3],
-                     [0, 6],
-                     [3, 6],
-                     [0],
-                     [1],
-                     [2],
-                     [3],
-                     [4],
-                     [5],
-                     [6]]
-
-            weights = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.05, 0.05, -0.1, -0.125,
-                       0.05, -0.475, -0.97, -0.91, -1.01, -0.9625, -0.8875, -1.0125]
-            constant = 6.8775
-            
-            weights_list = [3, 4, 5]
-            weight_capacity = 10
-            binpacking_prob_qubo = BinPacking(weights_list, weight_capacity, method="unbalanced").qubo
-
-            self.assertTrue(terms_list_equality(terms, binpacking_prob_qubo.terms))
-            self.assertTrue(terms_list_isclose(weights, binpacking_prob_qubo.weights))
-            self.assertTrue(np.isclose(constant, binpacking_prob_qubo.constant))
+        """Test that BinPacking creates a correct QUBO from the provided weights and terms
+        using the unbalanced penalization encoding"""
+        terms = [
+            [1, 2],
+            [1, 3],
+            [2, 3],
+            [4, 5],
+            [4, 6],
+            [5, 6],
+            [1, 4],
+            [2, 5],
+            [0, 3],
+            [0, 6],
+            [3, 6],
+            [0],
+            [1],
+            [2],
+            [3],
+            [4],
+            [5],
+            [6],
+        ]
+
+        weights = [
+            1.0,
+            1.0,
+            1.0,
+            1.0,
+            1.0,
+            1.0,
+            0.05,
+            0.05,
+            -0.1,
+            -0.125,
+            0.05,
+            -0.475,
+            -0.97,
+            -0.91,
+            -1.01,
+            -0.9625,
+            -0.8875,
+            -1.0125,
+        ]
+        constant = 6.8775
+
+        weights_list = [3, 4, 5]
+        weight_capacity = 10
+        binpacking_prob_qubo = BinPacking(
+            weights_list, weight_capacity, method="unbalanced"
+        ).qubo
+
+        self.assertTrue(terms_list_equality(terms, binpacking_prob_qubo.terms))
+        self.assertTrue(terms_list_isclose(weights, binpacking_prob_qubo.weights))
+        self.assertTrue(np.isclose(constant, binpacking_prob_qubo.constant))
 
     def test_binpacking_terms_penalizations_terms_unbalanced(self):
-            """Test that BinPacking creates a correct QUBO from the provided weights and terms
-            using the unbalanced penalization encoding given the penalization terms"""
-            terms = [[1, 2], [0, 2], [0], [1], [2]]
-
-            weights = [0.5, -0.25, -0.25, -0.0625, -0.125]
-            constant = 1.953125
-            
-            weights_list = [3, 4]
-            weight_capacity = 8
-            penalty = [1, 1, 1]
-            binpacking_prob_qubo = BinPacking(weights_list, weight_capacity, penalty=penalty, method="unbalanced", simplifications=True).qubo
-
-            self.assertTrue(terms_list_equality(terms, binpacking_prob_qubo.terms))
-            self.assertTrue(terms_list_isclose(weights, binpacking_prob_qubo.weights))
-            self.assertTrue(np.isclose(constant, binpacking_prob_qubo.constant))
+        """Test that BinPacking creates a correct QUBO from the provided weights and terms
+        using the unbalanced penalization encoding given the penalization terms"""
+        terms = [[1, 2], [0, 2], [0], [1], [2]]
+
+        weights = [0.5, -0.25, -0.25, -0.0625, -0.125]
+        constant = 1.953125
+
+        weights_list = [3, 4]
+        weight_capacity = 8
+        penalty = [1, 1, 1]
+        binpacking_prob_qubo = BinPacking(
+            weights_list,
+            weight_capacity,
+            penalty=penalty,
+            method="unbalanced",
+            simplifications=True,
+        ).qubo
+
+        self.assertTrue(terms_list_equality(terms, binpacking_prob_qubo.terms))
+        self.assertTrue(terms_list_isclose(weights, binpacking_prob_qubo.weights))
+        self.assertTrue(np.isclose(constant, binpacking_prob_qubo.constant))
 
     def test_binpacking_terms_penalizations_terms_slack(self):
-            """Test that BinPacking creates a correct QUBO from the provided weights and terms
-            using the unbalanced penalization encoding"""
-            terms = [[1, 2], [1, 3], [0, 2], [0, 4], [2, 4], [0], [1], [2], [3], [4]]
-
-            weights = [0.5, 0.15625, -0.25, -0.5, 0.25, -0.25, 0.03125, -0.125, 0.0390625, -0.25]
-            constant = 2.7890625
-            
-            weights_list = [3, 4]
-            weight_capacity = 8
-            penalty = [1]
-            binpacking_prob_qubo = BinPacking(weights_list, weight_capacity, penalty=penalty, method="slack", simplifications=True).qubo
-
-            self.assertTrue(terms_list_equality(terms, binpacking_prob_qubo.terms))
-            self.assertTrue(terms_list_isclose(weights, binpacking_prob_qubo.weights))
-            self.assertTrue(np.isclose(constant, binpacking_prob_qubo.constant))
+        """Test that BinPacking creates a correct QUBO from the provided weights and terms
+        using the unbalanced penalization encoding"""
+        terms = [[1, 2], [1, 3], [0, 2], [0, 4], [2, 4], [0], [1], [2], [3], [4]]
+
+        weights = [
+            0.5,
+            0.15625,
+            -0.25,
+            -0.5,
+            0.25,
+            -0.25,
+            0.03125,
+            -0.125,
+            0.0390625,
+            -0.25,
+        ]
+        constant = 2.7890625
+
+        weights_list = [3, 4]
+        weight_capacity = 8
+        penalty = [1]
+        binpacking_prob_qubo = BinPacking(
+            weights_list,
+            weight_capacity,
+            penalty=penalty,
+            method="slack",
+            simplifications=True,
+        ).qubo
+
+        self.assertTrue(terms_list_equality(terms, binpacking_prob_qubo.terms))
+        self.assertTrue(terms_list_isclose(weights, binpacking_prob_qubo.weights))
+        self.assertTrue(np.isclose(constant, binpacking_prob_qubo.constant))
 
     def test_binpacking_random_problem(self):
         """Test Bin Packing random instance method"""
@@ -1473,10 +1683,14 @@ def test_binpacking_random_problem(self):
         ).qubo
 
         self.assertTrue(
-            terms_list_equality(binpacking_manual_prob.terms, binpacking_random_prob.terms)
+            terms_list_equality(
+                binpacking_manual_prob.terms, binpacking_random_prob.terms
+            )
         )
         self.assertEqual(binpacking_manual_prob.weights, binpacking_random_prob.weights)
-        self.assertEqual(binpacking_manual_prob.constant, binpacking_random_prob.constant)
+        self.assertEqual(
+            binpacking_manual_prob.constant, binpacking_random_prob.constant
+        )
 
     def test_binpacking_classical_sol(self):
         """Test the Bin Packing random instance method classical solution"""
@@ -1484,46 +1698,48 @@ def test_binpacking_classical_sol(self):
         seed = 1234
         np.random.seed(seed)
         binpacking_sol = BinPacking.random_instance(
-            n_items=3, seed=seed).classical_solution()
-        
-        sol = {'y_0': 1,
-             'y_1': 0,
-             'y_2': 0,
-             'x_0_0': 1,
-             'x_0_1': 0,
-             'x_0_2': 0,
-             'x_1_0': 1,
-             'x_1_1': 0,
-             'x_1_2': 0,
-             'x_2_0': 1,
-             'x_2_1': 0,
-             'x_2_2': 0}
+            n_items=3, seed=seed
+        ).classical_solution()
+
+        sol = {
+            "y_0": 1,
+            "y_1": 0,
+            "y_2": 0,
+            "x_0_0": 1,
+            "x_0_1": 0,
+            "x_0_2": 0,
+            "x_1_0": 1,
+            "x_1_1": 0,
+            "x_1_2": 0,
+            "x_2_0": 1,
+            "x_2_1": 0,
+            "x_2_2": 0,
+        }
 
         self.assertEqual(binpacking_sol, sol)
 
     def test_binpacking_plot(self):
         """Test Bin Packing random instance method"""
         from matplotlib.pyplot import Figure
+
         seed = 1234
-        binpacking_random_prob = BinPacking.random_instance(
-            n_items=3, seed=seed
-        )
-        sol = {'y_0': 1,
-         'y_1': 0,
-         'y_2': 0,
-         'x_0_0': 1,
-         'x_0_1': 0,
-         'x_0_2': 0,
-         'x_1_0': 1,
-         'x_1_1': 0,
-         'x_1_2': 0,
-         'x_2_0': 1,
-         'x_2_1': 0,
-         'x_2_2': 0}
+        binpacking_random_prob = BinPacking.random_instance(n_items=3, seed=seed)
+        sol = {
+            "y_0": 1,
+            "y_1": 0,
+            "y_2": 0,
+            "x_0_0": 1,
+            "x_0_1": 0,
+            "x_0_2": 0,
+            "x_1_0": 1,
+            "x_1_1": 0,
+            "x_1_2": 0,
+            "x_2_0": 1,
+            "x_2_1": 0,
+            "x_2_2": 0,
+        }
         fig = binpacking_random_prob.plot_solution(sol)
-        self.assertTrue(
-            isinstance(fig, Figure)
-        )
+        self.assertTrue(isinstance(fig, Figure))
 
     def test_binpacking_method_checking(self):
         """
@@ -1535,7 +1751,8 @@ def test_binpacking_method_checking(self):
         with self.assertRaises(ValueError) as e:
             BinPacking(weights, weight_capacity, method=method)
         self.assertEqual(
-            f"The method '{method}' is not a valid method. Choose between 'slack' and 'unbalanced'", str(e.exception)
+            f"The method '{method}' is not a valid method. Choose between 'slack' and 'unbalanced'",
+            str(e.exception),
         )
 
     def test_binpacking_random_problem_checking(self):
@@ -1547,8 +1764,9 @@ def test_binpacking_random_problem_checking(self):
         with self.assertRaises(ValueError) as e:
             BinPacking.random_instance(min_weight=min_weight, max_weight=max_weight)
         self.assertEqual(
-            f"min_weight: {min_weight} must be < max_weight:{max_weight}", str(e.exception)
-        )  
+            f"min_weight: {min_weight} must be < max_weight:{max_weight}",
+            str(e.exception),
+        )
 
     def test_binpacking_classical_sol_checking(self):
         """
@@ -1557,10 +1775,10 @@ def test_binpacking_classical_sol_checking(self):
         weights = [10, 10]
         weight_capacity = 8
         with self.assertRaises(ValueError) as e:
-            BinPacking(weights=weights, weight_capacity=weight_capacity).classical_solution()
-        self.assertEqual(
-            'solution not found: integer infeasible', str(e.exception)
-        )
+            BinPacking(
+                weights=weights, weight_capacity=weight_capacity
+            ).classical_solution()
+        self.assertEqual("solution not found: integer infeasible", str(e.exception))
 
     def test_binpacking_input_weights(self):
         """
@@ -1569,9 +1787,12 @@ def test_binpacking_input_weights(self):
         weights = [10.1, 10]
         weight_capacity = 8
         with self.assertRaises(TypeError) as e:
-            BinPacking(weights=weights, weight_capacity=weight_capacity).classical_solution()
+            BinPacking(
+                weights=weights, weight_capacity=weight_capacity
+            ).classical_solution()
         self.assertEqual(
-            f"The weights must be integer numbers. Format {type(weights[0])} found.", str(e.exception)
+            f"The weights must be integer numbers. Format {type(weights[0])} found.",
+            str(e.exception),
         )
 
     def test_binpacking_input_weight_capacity(self):
@@ -1581,10 +1802,14 @@ def test_binpacking_input_weight_capacity(self):
         weights = [10, 10]
         weight_capacity = 8.1
         with self.assertRaises(TypeError) as e:
-            BinPacking(weights=weights, weight_capacity=weight_capacity).classical_solution()
+            BinPacking(
+                weights=weights, weight_capacity=weight_capacity
+            ).classical_solution()
         self.assertEqual(
-            f"The weight_capacity must be integer. Format {type(weight_capacity)} found.", str(e.exception)
+            f"The weight_capacity must be integer. Format {type(weight_capacity)} found.",
+            str(e.exception),
         )
 
+
 if __name__ == "__main__":
     unittest.main()