diff --git a/src/openqaoa-core/openqaoa/problems/helper_functions.py b/src/openqaoa-core/openqaoa/problems/helper_functions.py index 137c29021..6bb70326b 100644 --- a/src/openqaoa-core/openqaoa/problems/helper_functions.py +++ b/src/openqaoa-core/openqaoa/problems/helper_functions.py @@ -11,6 +11,7 @@ from .vehiclerouting import VRP from .maximalindependentset import MIS from .binpacking import BinPacking +from .portfoliooptimization import PortfolioOptimization from .qubo import QUBO @@ -45,6 +46,7 @@ def create_problem_from_dict(problem_instance: dict) -> Problem: "vehicle_routing": VRP, "maximal_independent_set": MIS, "bin_packing": BinPacking, + "portfolio_optimization": PortfolioOptimization, } # check if the problem type is in the mapper diff --git a/src/openqaoa-core/tests/test_bin_packing.py b/src/openqaoa-core/tests/test_bin_packing.py new file mode 100644 index 000000000..db2c8220e --- /dev/null +++ b/src/openqaoa-core/tests/test_bin_packing.py @@ -0,0 +1,434 @@ +import unittest +import numpy as np +from openqaoa.problems import BinPacking + + +def terms_list_equality(terms_list1, terms_list2): + """ + Check the terms equality between two terms list + where the order of edges do not matter. + """ + if len(terms_list1) != len(terms_list2): + bool = False + else: + for term1, term2 in zip(terms_list1, terms_list2): + bool = True if (term1 == term2 or term1 == term2[::-1]) else False + + return bool + + +def terms_list_isclose(terms_list1, terms_list2): + """ + Check if the distance between two terms list + where the order of edges do not matter. + """ + if len(terms_list1) != len(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 + ) + + return bool + + +class TestBinPacking(unittest.TestCase): + """Tests for the 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, + ] + constant = 10.083333333333332 + weights_list = [3, 4] + weight_capacity = 6 + 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, + ] + constant = 9.29 + weights_list = [3, 4, 5] + weight_capacity = 10 + binpacking_prob_qubo = BinPacking(weights_list, weight_capacity).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_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)) + + 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)) + + 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)) + + def test_binpacking_random_problem(self): + """Test Bin Packing random instance method""" + + seed = 1234 + np.random.seed(seed) + min_weight = 1 + max_weight = 7 + n_items = 3 + weight_capacity = 15 + weights = list(np.random.randint(min_weight, max_weight, n_items)) + binpacking_manual_prob = BinPacking(weights, weight_capacity).qubo + + binpacking_random_prob = BinPacking.random_instance( + n_items=3, seed=seed, weight_capacity=weight_capacity + ).qubo + + self.assertTrue( + 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 + ) + + def test_binpacking_classical_sol(self): + """Test the Bin Packing random instance method classical solution""" + + 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, + } + + 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, + } + fig = binpacking_random_prob.plot_solution(sol) + self.assertTrue(isinstance(fig, Figure)) + + def test_binpacking_method_checking(self): + """ + Checks if the method-checking returns the right error. + """ + weights = [3, 5, 7] + weight_capacity = 15 + method = "random" + 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), + ) + + def test_binpacking_random_problem_checking(self): + """ + Checks if the random min_weight equal to max_weight returns the right error. + """ + min_weight = 5 + max_weight = 5 + 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), + ) + + def test_binpacking_classical_sol_checking(self): + """ + Checks if the unfeasible classical solution returns the right error. + """ + 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)) + + def test_binpacking_input_weights(self): + """ + Checks if the unfeasible classical solution returns the right error. + """ + weights = [10.1, 10] + weight_capacity = 8 + with self.assertRaises(TypeError) as e: + 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), + ) + + def test_binpacking_input_weight_capacity(self): + """ + Checks if the unfeasible classical solution returns the right error. + """ + weights = [10, 10] + weight_capacity = 8.1 + with self.assertRaises(TypeError) as e: + 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), + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/openqaoa-core/tests/test_knapsack.py b/src/openqaoa-core/tests/test_knapsack.py new file mode 100644 index 000000000..71e216726 --- /dev/null +++ b/src/openqaoa-core/tests/test_knapsack.py @@ -0,0 +1,335 @@ +import unittest +import numpy as np +from openqaoa.problems import Knapsack, SlackFreeKnapsack + + +def terms_list_equality(terms_list1, terms_list2): + """ + Check the terms equality between two terms list + where the order of edges do not matter. + """ + if len(terms_list1) != len(terms_list2): + bool = False + else: + for term1, term2 in zip(terms_list1, terms_list2): + bool = True if (term1 == term2 or term1 == term2[::-1]) else False + + return bool + + +def terms_list_isclose(terms_list1, terms_list2): + """ + Check if the distance between two terms list + where the order of edges do not matter. + """ + if len(terms_list1) != len(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 + ) + + return bool + +class TestKnapsack(unittest.TestCase): + """Tests for Knapsack class""" + def test_knapsack_terms_weights_constant(self): + """Test that Knapsack creates the correct QUBO problem""" + + values = [2, 4, 3, 5] + weights = [3, 6, 9, 1] + weight_capacity = 15 + n_qubits = len(values) + int(np.ceil(np.log2(weight_capacity))) + penalty = 2 * max(values) + knap_terms = [ + [0, 1], + [0, 2], + [0, 3], + [1, 2], + [1, 3], + [2, 3], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7], + [0, 4], + [0, 5], + [0, 6], + [0, 7], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [0], + [1], + [2], + [3], + [4], + [5], + [6], + [7], + ] + knap_weights = [ + 10.0, + 20.0, + 40.0, + 40.0, + 80.0, + 160.0, + 90.0, + 135.0, + 15.0, + 270.0, + 30.0, + 45.0, + 15.0, + 30.0, + 45.0, + 5.0, + 30.0, + 60.0, + 90.0, + 10.0, + 60.0, + 120.0, + 180.0, + 20.0, + 120.0, + 240.0, + 360.0, + 40.0, + -20.0, + -40.0, + -80.0, + -160.0, + -59.0, + -118.0, + -178.5, + -17.5, + ] + knap_constant = 563.0 + + knapsack_prob_qubo = Knapsack(values, weights, weight_capacity, penalty).qubo + + self.assertTrue(terms_list_equality(knap_terms, knapsack_prob_qubo.terms)) + self.assertEqual(knap_weights, knapsack_prob_qubo.weights) + self.assertEqual(knap_constant, knapsack_prob_qubo.constant) + self.assertEqual(n_qubits, knapsack_prob_qubo.n) + + def test_knapsack_random_problem(self): + """Test random instance method of Knapsack problem class""" + + rng = np.random.default_rng(1234) + n_items = 5 + values = list(map(int, rng.integers(1, n_items, size=n_items))) + weights = list(map(int, rng.integers(1, n_items, size=n_items))) + weight_capacity = int( + rng.integers(np.min(weights) * n_items, np.max(weights) * n_items) + ) + penalty = 2 * np.max(values) + + knap_manual = Knapsack(values, weights, weight_capacity, int(penalty)).qubo + + knap_random_instance = Knapsack.random_instance(n_items=n_items, seed=1234).qubo + + self.assertTrue( + terms_list_equality(knap_manual.terms, knap_random_instance.terms) + ) + self.assertEqual(knap_manual.weights, knap_random_instance.weights) + self.assertEqual(knap_manual.constant, knap_random_instance.constant) + self.assertEqual(knap_manual.n, knap_random_instance.n) + + def test_knapsack_random_problem_smallsize(self): + """Test random instance method of Knapsack problem class""" + + rng = np.random.default_rng(1234) + n_items = 3 + values = list(map(int, rng.integers(1, n_items, size=n_items))) + weights = list(map(int, rng.integers(1, n_items, size=n_items))) + weight_capacity = int( + rng.integers(np.min(weights) * n_items, np.max(weights) * n_items) + ) + penalty = 2 * np.max(values) + + knap_manual = Knapsack(values, weights, weight_capacity, int(penalty)).qubo + + knap_random_instance = Knapsack.random_instance(n_items=n_items, seed=1234).qubo + + self.assertTrue( + terms_list_equality(knap_manual.terms, knap_random_instance.terms) + ) + self.assertEqual(knap_manual.weights, knap_random_instance.weights) + self.assertEqual(knap_manual.constant, knap_random_instance.constant) + self.assertEqual(knap_manual.n, knap_random_instance.n) + + def test_knapsack_type_checking(self): + """ + Checks if the type-checking returns the right error. + """ + + # values type-check + weights = [1, 2] + weight_capacity = 5 + penalty = 0.1 + values_list = [(1, 2), {"test": "oh", "test1": "oh"}, np.array([1, 2])] + + for each_values in values_list: + with self.assertRaises(TypeError) as e: + Knapsack(each_values, weights, weight_capacity, penalty) + self.assertEqual( + "The input parameter, values, has to be a list", str(e.exception) + ) + + values_list = [["test", "oh"], [np.array(1), np.array(2)], [0.1, 0.5]] + for each_values in values_list: + with self.assertRaises(TypeError) as e: + Knapsack(each_values, weights, weight_capacity, penalty) + self.assertEqual( + "The elements in values list must be of type int.", str(e.exception) + ) + + # weights type-check + values = [1, 2] + weight_capacity = 5 + penalty = 0.1 + weights_list = [(1, 2), {"test": "oh", "test1": "oh"}, np.array([1, 2])] + + for each_weights in weights_list: + with self.assertRaises(TypeError) as e: + Knapsack(values, each_weights, weight_capacity, penalty) + self.assertEqual( + "The input parameter, weights, has to be a list", str(e.exception) + ) + + weights_list = [["test", "oh"], [np.array(1), np.array(2)], [0.1, 0.5]] + for each_weights in weights_list: + with self.assertRaises(TypeError) as e: + Knapsack(values, each_weights, weight_capacity, penalty) + self.assertEqual( + "The elements in weights list must be of type int.", str(e.exception) + ) + + # weight capacity type-check + values = [1, 2] + weights = [1, 2] + weight_capacity_list = [0.5, np.array(1), np.array(0.5), "oh"] + penalty = 0.1 + + for each_weight_capacity in weight_capacity_list: + with self.assertRaises(TypeError) as e: + Knapsack(values, weights, each_weight_capacity, penalty) + self.assertEqual( + "The input parameter, weight_capacity, has to be of type int", + str(e.exception), + ) + + with self.assertRaises(TypeError) as e: + Knapsack(values, weights, -1, penalty) + self.assertEqual( + "The input parameter, weight_capacity, must be a positive integer greater than 0", + str(e.exception), + ) + + # penalty capacity type-check + values = [1, 2] + weights = [1, 2] + penalty_list = [np.array(1), np.array(0.5), "oh"] + weight_capacity = 5 + + for each_penalty in penalty_list: + with self.assertRaises(TypeError) as e: + Knapsack(values, weights, weight_capacity, each_penalty) + self.assertEqual( + "The input parameter, penalty, has to be of type float or int", + str(e.exception), + ) + + +class TestSlackFreeKnapsack(unittest.TestCase): + """Tests for SlackFreeKnapsack class""" + def test_slackfreeknapsack_terms_weights_constant(self): + """Test that SlackFreeKnapsack creates the correct QUBO problem""" + + values = [2, 4, 3, 5] + weights = [3, 6, 9, 1] + weight_capacity = 15 + n_qubits = len(values) + penalty = 2 * max(values) + slknap_terms = [ + [0, 1], + [0, 2], + [0, 3], + [1, 2], + [1, 3], + [2, 3], + [0], + [1], + [2], + [3], + ] + slknap_weights = [ + 90.0, + 135.0, + 15.0, + 270.0, + 30.0, + 45.0, + 166.0, + 332.0, + 496.5, + 57.5, + ] + slknap_constant = 613.0 + + slknapsack_prob_qubo = SlackFreeKnapsack( + values, weights, weight_capacity, penalty + ).qubo + + self.assertTrue(terms_list_equality(slknap_terms, slknapsack_prob_qubo.terms)) + self.assertEqual(slknap_weights, slknapsack_prob_qubo.weights) + self.assertEqual(slknap_constant, slknapsack_prob_qubo.constant) + self.assertEqual(n_qubits, slknapsack_prob_qubo.n) + + def test_slackfreeknapsack_random_problem(self): + """Test random instance method of SlackFreeKnapsack problem class""" + + rng = np.random.default_rng(1234) + n_items = 5 + values = list(map(int, rng.integers(1, n_items, size=n_items))) + weights = list(map(int, rng.integers(1, n_items, size=n_items))) + weight_capacity = int( + rng.integers(np.min(weights) * n_items, np.max(weights) * n_items) + ) + penalty = 2 * np.max(values) + + slknap_manual = SlackFreeKnapsack( + values, weights, weight_capacity, int(penalty) + ).qubo + + slknap_random_instance = SlackFreeKnapsack.random_instance( + n_items=n_items, seed=1234 + ).qubo + + self.assertTrue( + terms_list_equality(slknap_manual.terms, slknap_random_instance.terms) + ) + self.assertEqual(slknap_manual.weights, slknap_random_instance.weights) + self.assertEqual(slknap_manual.constant, slknap_random_instance.constant) + self.assertEqual(slknap_manual.n, slknap_random_instance.n) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/openqaoa-core/tests/test_maximum_cut.py b/src/openqaoa-core/tests/test_maximum_cut.py new file mode 100644 index 000000000..aaaaf2c32 --- /dev/null +++ b/src/openqaoa-core/tests/test_maximum_cut.py @@ -0,0 +1,88 @@ +import unittest +import networkx as nx +import numpy as np +from openqaoa.problems import MaximumCut + + +def terms_list_equality(terms_list1, terms_list2): + """ + Check the terms equality between two terms list + where the order of edges do not matter. + """ + if len(terms_list1) != len(terms_list2): + bool = False + else: + for term1, term2 in zip(terms_list1, terms_list2): + bool = True if (term1 == term2 or term1 == term2[::-1]) else False + + return bool + + +def terms_list_isclose(terms_list1, terms_list2): + """ + Check if the distance between two terms list + where the order of edges do not matter. + """ + if len(terms_list1) != len(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 + ) + + return bool + +class TestMaximumCut(unittest.TestCase): + """Tests for MaximumCut class""" + def test_maximumcut_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=10, p=0.8) + gr_edges = [list(edge) for edge in gr.edges()] + gr_weights = [1] * len(gr_edges) + + maxcut_prob_qubo = MaximumCut(gr).qubo + + self.assertTrue(terms_list_equality(gr_edges, maxcut_prob_qubo.terms)) + self.assertEqual(gr_weights, maxcut_prob_qubo.weights) + self.assertEqual(0, maxcut_prob_qubo.constant) + + def test_maximumcut_random_problem(self): + """Test MaximumCut random instance method""" + + seed = 1234 + gr = nx.generators.random_graphs.fast_gnp_random_graph(n=10, p=0.8, seed=seed) + maxcut_manual_prob = MaximumCut(gr).qubo + + np.random.seed(1234) + maxcut_random_prob = MaximumCut.random_instance( + n_nodes=10, edge_probability=0.8, seed=seed + ).qubo + + self.assertTrue( + terms_list_equality(maxcut_manual_prob.terms, maxcut_random_prob.terms) + ) + self.assertEqual(maxcut_manual_prob.weights, maxcut_random_prob.weights) + self.assertEqual(maxcut_manual_prob.constant, maxcut_random_prob.constant) + + def test_maximumcut_type_checking(self): + """ + Checks if the type-checking returns the right error. + """ + + # graph type-check + graph_list = [(1, 2), {"node1": 1, "node2": 2}, np.array([1, 2])] + + for each_graph in graph_list: + with self.assertRaises(TypeError) as e: + MaximumCut(G=each_graph) + self.assertEqual( + "Input problem graph must be a networkx Graph.", str(e.exception) + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/openqaoa-core/tests/test_minimum_vertex_cover.py b/src/openqaoa-core/tests/test_minimum_vertex_cover.py new file mode 100644 index 000000000..307e9471e --- /dev/null +++ b/src/openqaoa-core/tests/test_minimum_vertex_cover.py @@ -0,0 +1,137 @@ +import unittest +import networkx as nx +import numpy as np +from openqaoa.problems import MinimumVertexCover + + +def terms_list_equality(terms_list1, terms_list2): + """ + Check the terms equality between two terms list + where the order of edges do not matter. + """ + if len(terms_list1) != len(terms_list2): + bool = False + else: + for term1, term2 in zip(terms_list1, terms_list2): + bool = True if (term1 == term2 or term1 == term2[::-1]) else False + + return bool + + +def terms_list_isclose(terms_list1, terms_list2): + """ + Check if the distance between two terms list + where the order of edges do not matter. + """ + if len(terms_list1) != len(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 + ) + + return bool + +class TestMinimumVertexCover(unittest.TestCase): + """Tests for MinimumVertexCover class""" + def test_mvc_terms_weights_constant(self): + """Test terms,weights,constant of QUBO generated by MVC class""" + + mvc_terms = [ + [0, 3], + [0, 4], + [1, 2], + [1, 3], + [2, 4], + [3, 4], + [0], + [1], + [2], + [3], + [4], + ] + mvc_weights = [1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 2.0, 2.0, 2.0, 3.25, 3.25] + mvc_constant = 10.0 + + gr = nx.generators.fast_gnp_random_graph(5, 0.8, seed=1234) + mvc_prob = MinimumVertexCover(gr, field=1.0, penalty=5).qubo + + self.assertTrue(terms_list_equality(mvc_terms, mvc_prob.terms)) + self.assertEqual(mvc_weights, mvc_prob.weights) + self.assertEqual(mvc_constant, mvc_prob.constant) + + def test_mvc_random_problem(self): + """Test the random_instance method of MVC class""" + mvc_terms = [ + [0, 3], + [0, 4], + [1, 2], + [1, 3], + [2, 4], + [3, 4], + [0], + [1], + [2], + [3], + [4], + ] + mvc_weights = [2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 4.5, 4.5, 4.5, 7.0, 7.0] + mvc_constant = 17.5 + + mvc_prob_random = MinimumVertexCover.random_instance( + n_nodes=5, edge_probability=0.8, seed=1234 + ).qubo + + self.assertTrue(terms_list_equality(mvc_terms, mvc_prob_random.terms)) + self.assertEqual(mvc_weights, mvc_prob_random.weights) + self.assertEqual(mvc_constant, mvc_prob_random.constant) + + def test_mvc_type_checking(self): + """ + Checks if the type-checking returns the right error. + """ + + # graph type-check + graph_list = [(1, 2), {"node1": 1, "node2": 2}, np.array([1, 2])] + field = 0.1 + penalty = 0.1 + + for each_graph in graph_list: + with self.assertRaises(TypeError) as e: + MinimumVertexCover(each_graph, field, penalty) + self.assertEqual( + "Input problem graph must be a networkx Graph.", str(e.exception) + ) + + # field capacity type-check + graph = nx.circulant_graph(6, [1]) + field_list = [np.array(1), np.array(0.5), "oh"] + penalty = 0.1 + + for each_field in field_list: + with self.assertRaises(TypeError) as e: + MinimumVertexCover(graph, each_field, penalty) + self.assertEqual( + "The input parameter, field, has to be of type float or int", + str(e.exception), + ) + + # penalty capacity type-check + graph = nx.circulant_graph(6, [1]) + field = 0.1 + penalty_list = [np.array(1), np.array(0.5), "oh"] + + for each_penalty in penalty_list: + with self.assertRaises(TypeError) as e: + MinimumVertexCover(graph, field, each_penalty) + self.assertEqual( + "The input parameter, penalty, has to be of type float or int", + str(e.exception), + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/openqaoa-core/tests/test_mis.py b/src/openqaoa-core/tests/test_mis.py new file mode 100644 index 000000000..fbaa5997d --- /dev/null +++ b/src/openqaoa-core/tests/test_mis.py @@ -0,0 +1,147 @@ +import unittest +import networkx as nx +import numpy as np +from openqaoa.problems import MIS + + +def terms_list_equality(terms_list1, terms_list2): + """ + Check the terms equality between two terms list + where the order of edges do not matter. + """ + if len(terms_list1) != len(terms_list2): + bool = False + else: + for term1, term2 in zip(terms_list1, terms_list2): + bool = True if (term1 == term2 or term1 == term2[::-1]) else False + + return bool + + +def terms_list_isclose(terms_list1, terms_list2): + """ + Check if the distance between two terms list + where the order of edges do not matter. + """ + if len(terms_list1) != len(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 + ) + + return bool + +class TestMIS(unittest.TestCase): + """Tests for the MIS class""" + def test_mis_terms_weights_constant(self): + """Test that MIS 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_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 + + self.assertTrue(terms_list_equality(gr_edges, mis_prob_qubo.terms)) + self.assertEqual(gr_weights, mis_prob_qubo.weights) + self.assertEqual(0.5, mis_prob_qubo.constant) + + def test_mis_random_problem(self): + """Test MIS random instance method""" + + seed = 1234 + gr = nx.generators.random_graphs.fast_gnp_random_graph(n=10, p=0.8, seed=seed) + mis_manual_prob = MIS(gr).qubo + + np.random.seed(1234) + mis_random_prob = MIS.random_instance( + n_nodes=10, edge_probability=0.8, seed=seed + ).qubo + + self.assertTrue( + terms_list_equality(mis_manual_prob.terms, mis_random_prob.terms) + ) + self.assertEqual(mis_manual_prob.weights, mis_random_prob.weights) + self.assertEqual(mis_manual_prob.constant, mis_random_prob.constant) + + def test_mis_type_checking(self): + """ + Checks if the type-checking returns the right error. + """ + + # graph type-check + graph_list = [(1, 2), {"node1": 1, "node2": 2}, np.array([1, 2])] + + for each_graph in graph_list: + with self.assertRaises(TypeError) as e: + MIS(G=each_graph) + self.assertEqual( + "Input problem graph must be a networkx Graph.", str(e.exception) + ) + + def test_mis_classical_sol(self): + """Test the maximal independent set random instance method classical solution""" + + 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, + } + + 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 + ) + 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)) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/src/openqaoa-core/tests/test_number_partition.py b/src/openqaoa-core/tests/test_number_partition.py new file mode 100644 index 000000000..30bc638ff --- /dev/null +++ b/src/openqaoa-core/tests/test_number_partition.py @@ -0,0 +1,92 @@ +import unittest +import numpy as np +from openqaoa.problems import NumberPartition + + +def terms_list_equality(terms_list1, terms_list2): + """ + Check the terms equality between two terms list + where the order of edges do not matter. + """ + if len(terms_list1) != len(terms_list2): + bool = False + else: + for term1, term2 in zip(terms_list1, terms_list2): + bool = True if (term1 == term2 or term1 == term2[::-1]) else False + + return bool + + +def terms_list_isclose(terms_list1, terms_list2): + """ + Check if the distance between two terms list + where the order of edges do not matter. + """ + if len(terms_list1) != len(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 + ) + + return bool + +class TestNumberPartition(unittest.TestCase): + """Tests for NumberPartition class""" + def test_number_partitioning_terms_weights_constant(self): + """Test that Number Partitioning creates the correct terms, weights, constant""" + list_numbers = [1, 2, 3] + expected_terms = [[0, 1], [0, 2], [1, 2]] + expected_weights = [4, 6, 12] + expected_constant = 14 + + np_problem = NumberPartition(list_numbers) + qubo_problem = np_problem.qubo + + self.assertTrue(terms_list_equality(qubo_problem.terms, expected_terms)) + self.assertEqual(qubo_problem.weights, expected_weights) + self.assertEqual(qubo_problem.constant, expected_constant) + + def test_number_partitioning_random_problem(self): + """Test randomly generated NumberPartition problem""" + # regenerate the same numbers randomly + rng = np.random.default_rng(1234) + random_numbers_list = list(map(int, rng.integers(1, 10, size=5))) + manual_np_prob = NumberPartition(random_numbers_list).qubo + + np_prob_random = NumberPartition.random_instance(n_numbers=5, seed=1234).qubo + + self.assertTrue(terms_list_equality(np_prob_random.terms, manual_np_prob.terms)) + self.assertEqual(np_prob_random.weights, manual_np_prob.weights) + self.assertEqual(np_prob_random.constant, manual_np_prob.constant) + + def test_num_part_type_checking(self): + """ + Checks if the type-checking returns the right error. + """ + + # numbers type-check + numbers_list = [(1, 2), {"test": 1, "test1": 2}, np.array([1, 2])] + + for each_number in numbers_list: + with self.assertRaises(TypeError) as e: + NumberPartition(numbers=each_number) + self.assertEqual( + "The input parameter, numbers, has to be a list", str(e.exception) + ) + + numbers_list = [[0.1, 1], [np.array(1), np.array(2)]] + + for each_number in numbers_list: + with self.assertRaises(TypeError) as e: + NumberPartition(numbers=each_number) + self.assertEqual( + "The elements in numbers list must be of type int.", str(e.exception) + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/openqaoa-core/tests/test_portfolio_optimization.py b/src/openqaoa-core/tests/test_portfolio_optimization.py new file mode 100644 index 000000000..546c8f175 --- /dev/null +++ b/src/openqaoa-core/tests/test_portfolio_optimization.py @@ -0,0 +1,138 @@ +import unittest +import numpy as np +from openqaoa.problems import PortfolioOptimization + + +def terms_list_equality(terms_list1, terms_list2): + """ + Check the terms equality between two terms list + where the order of edges do not matter. + """ + if len(terms_list1) != len(terms_list2): + bool = False + else: + for term1, term2 in zip(terms_list1, terms_list2): + bool = True if (term1 == term2 or term1 == term2[::-1]) else False + + return bool + + +def terms_list_isclose(terms_list1, terms_list2): + """ + Check if the distance between two terms list + where the order of edges do not matter. + """ + if len(terms_list1) != len(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 + ) + + return bool + +class TestPortfolioOptimization(unittest.TestCase): + """Tests for PortfoliaOptimization class""" + def test_portfoliooptimization_terms_weights_constant(self): + """Test terms,weights,constant of QUBO generated by PortfolioOptimization 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 PortfolioOptimization 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 optimization random instance method""" + import matplotlib.pyplot as plt + + seed = 1234 + portfoliooptimization_random_prob = PortfolioOptimization.random_instance( + num_assets=12, budget=8, seed=seed + ) + sol = portfoliooptimization_random_prob.classical_solution() + figure = portfoliooptimization_random_prob.plot_solution(sol) + self.assertTrue(isinstance(figure, plt.Figure)) + fig, ax = plt.subplots(figsize=(5, 5)) + self.assertTrue( + portfoliooptimization_random_prob.plot_solution(sol, ax=ax) == None + ) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/src/openqaoa-core/tests/test_problems.py b/src/openqaoa-core/tests/test_problems.py deleted file mode 100644 index baa2ba688..000000000 --- a/src/openqaoa-core/tests/test_problems.py +++ /dev/null @@ -1,2002 +0,0 @@ -import unittest -import networkx as nx -import numpy as np -from random import randint, random -from openqaoa.problems import ( - NumberPartition, - QUBO, - TSP, - Knapsack, - ShortestPath, - SlackFreeKnapsack, - MaximumCut, - MinimumVertexCover, - VRP, - PortfolioOptimization, - MIS, - BinPacking, -) -from openqaoa.utilities import convert2serialize -from openqaoa.problems.helper_functions import create_problem_from_dict - - -def terms_list_equality(terms_list1, terms_list2): - """ - Check the terms equality between two terms list - where the order of edges do not matter. - """ - if len(terms_list1) != len(terms_list2): - bool = False - else: - for term1, term2 in zip(terms_list1, terms_list2): - bool = True if (term1 == term2 or term1 == term2[::-1]) else False - - return bool - - -def terms_list_isclose(terms_list1, terms_list2): - """ - Check if the distance between two terms list - where the order of edges do not matter. - """ - if len(terms_list1) != len(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 - ) - - return bool - - -class TestProblem(unittest.TestCase): - # TESTING QUBO CLASS METHODS - def test_qubo_terms_and_weight_same_size(self): - """ - Test that creating a QUBO problem with invalid terms and weights - sizes raises an exception and check the constant is detected correctly - """ - n = 2 - terms_wrong = [[0], [1]] - terms_correct = [[0], [1], []] - weights = [1, 2, 3] - - with self.assertRaises(ValueError): - qubo_problem = QUBO(n, terms_wrong, weights) - - qubo_problem = QUBO(n, terms_correct, weights) - self.assertEqual(qubo_problem.constant, 3) - - def test_qubo_cleaning_terms(self): - """Test that cleaning terms works for a QUBO problem""" - terms = [[1, 2], [0], [2, 3], [2, 1], [0]] - weights = [3, 4, -3, -2, -1] - - cleaned_terms = [[1, 2], [0], [2, 3]] - self.assertEqual(QUBO.clean_terms_and_weights(terms, weights)[0], cleaned_terms) - - def test_qubo_cleaning_weights(self): - """Test that cleaning weights works for a QUBO problem""" - terms = [[1, 2], [0], [2, 3], [2, 1], [0]] - weights = [3, 4, -3, -2, -1] - - cleaned_weights = [1, 3, -3] - self.assertEqual( - QUBO.clean_terms_and_weights(terms, weights)[1], cleaned_weights - ) - - def test_qubo_ising_conversion(self): - """Test that conversion to Ising formulation works for a QUBO problem""" - # Small instance - n = 2 - terms = [[1, 1], [0, 0]] - weights = [3, 4] - - expected_ising_terms = [[0], [1], []] - expected_ising_weights = [-2, -1.5, 3.5] - - ising_terms, ising_weights = QUBO.convert_qubo_to_ising(n, terms, weights) - self.assertEqual(expected_ising_terms, ising_terms) - self.assertEqual(expected_ising_weights, ising_weights) - - # Larger instance - n = 4 - terms = [[1, 2], [0], [2, 3], [2, 1], [0]] - weights = [3, 4, -3, -2, -1] - - expected_ising_terms = [[1, 2], [2, 3], [2, 1], [0], [1], [2], [3], []] - expected_ising_weights = [0.75, -0.75, -0.5, -1.5, -0.25, 0.5, 0.75, 1.0] - - ising_terms, ising_weights = QUBO.convert_qubo_to_ising(n, terms, weights) - self.assertEqual(expected_ising_terms, ising_terms) - self.assertEqual(expected_ising_weights, ising_weights) - - def test_qubo_type_checking(self): - """ - Checks if the type-checking returns the right error. - """ - - # n type-check - n_list = [1.5, "test", [], (), {}, np.array(1)] - terms = [[0], []] - weights = [1, 2] - - with self.assertRaises(TypeError) as e: - for each_n in n_list: - QUBO(each_n, terms, weights) - self.assertEqual( - "The input parameter, n, has to be of type int", str(e.exception) - ) - - n_list = [-1, 0] - with self.assertRaises(TypeError) as e: - for each_n in n_list: - QUBO(each_n, terms, weights) - self.assertEqual( - "The input parameter, n, must be a positive integer greater than 0", - str(e.exception), - ) - - # weights type-check - n = 1 - terms = [[0], []] - weights_list = [{"test": "oh", "test1": "oh"}, np.array([1, 2])] - - for each_weights in weights_list: - with self.assertRaises(TypeError) as e: - QUBO(n, terms, each_weights) - self.assertEqual( - "The input parameter weights must be of type of list or tuple", - str(e.exception), - ) - - weights_list = [["test", "oh"], [np.array(1), np.array(2)]] - for each_weights in weights_list: - with self.assertRaises(TypeError) as e: - QUBO(n, terms, each_weights) - self.assertEqual( - "The elements in weights list must be of type float or int.", - str(e.exception), - ) - - # terms type-check - n = 1 - terms_list = [{"test": [0], "test1": []}] - weights = [1, 2] - for each_terms in terms_list: - with self.assertRaises(TypeError) as e: - QUBO(n, each_terms, weights) - self.assertEqual( - "The input parameter terms must be of type of list or tuple", - str(e.exception), - ) - - def test_qubo_metadata(self): - """Test that metadata is correctly stored""" - qubo_problem = QUBO.random_instance(3) - qubo_problem.set_metadata({"tag1": "value1", "tag2": "value2"}) - qubo_problem.set_metadata({"tag2": "value2.0"}) - - assert ( - qubo_problem.metadata["tag1"] == "value1" - ), "qubo metadata is not well set" - assert ( - qubo_problem.metadata["tag2"] == "value2.0" - ), "qubo metadata is not well set, should have overwritten previous value" - - error = False - try: - qubo_problem.set_metadata({"tag10": complex(1, 2)}) - except: - error = True - assert ( - error - ), "Should have thrown an error when setting metadata that is not json serializable" - - error = False - try: - qubo_problem.set_metadata({(1, 2): "value"}) - except: - error = True - assert ( - error - ), "Should have thrown an error when setting key metadata that is not json serializable" - - def test_qubo_problem_instance_serializable(self): - """test that when problem instance is not serializable, it throws an error""" - - qubo = QUBO.random_instance(3) - - error = False - try: - qubo.problem_instance = {"tag10": complex(1, 2)} - except: - error = True - assert ( - error - ), "Should have thrown an error when setting qubo problem instance that is not json serializable" - - error = False - try: - qubo.problem_instance = {(1, 2): "value"} - except: - error = True - assert ( - error - ), "Should have thrown an error when setting key qubo problem instance that is not json serializable" - - # TESTING NUMBER PARITION CLASS - - def test_number_partitioning_terms_weights_constant(self): - """Test that Number Partitioning creates the correct terms, weights, constant""" - list_numbers = [1, 2, 3] - expected_terms = [[0, 1], [0, 2], [1, 2]] - expected_weights = [4, 6, 12] - expected_constant = 14 - - np_problem = NumberPartition(list_numbers) - qubo_problem = np_problem.qubo - - self.assertTrue(terms_list_equality(qubo_problem.terms, expected_terms)) - self.assertEqual(qubo_problem.weights, expected_weights) - self.assertEqual(qubo_problem.constant, expected_constant) - - def test_number_partitioning_random_problem(self): - """Test randomly generated NumberPartition problem""" - # regenerate the same numbers randomly - rng = np.random.default_rng(1234) - random_numbers_list = list(map(int, rng.integers(1, 10, size=5))) - manual_np_prob = NumberPartition(random_numbers_list).qubo - - np_prob_random = NumberPartition.random_instance(n_numbers=5, seed=1234).qubo - - self.assertTrue(terms_list_equality(np_prob_random.terms, manual_np_prob.terms)) - self.assertEqual(np_prob_random.weights, manual_np_prob.weights) - self.assertEqual(np_prob_random.constant, manual_np_prob.constant) - - def test_num_part_type_checking(self): - """ - Checks if the type-checking returns the right error. - """ - - # numbers type-check - numbers_list = [(1, 2), {"test": 1, "test1": 2}, np.array([1, 2])] - - for each_number in numbers_list: - with self.assertRaises(TypeError) as e: - NumberPartition(numbers=each_number) - self.assertEqual( - "The input parameter, numbers, has to be a list", str(e.exception) - ) - - numbers_list = [[0.1, 1], [np.array(1), np.array(2)]] - - for each_number in numbers_list: - with self.assertRaises(TypeError) as e: - NumberPartition(numbers=each_number) - self.assertEqual( - "The elements in numbers list must be of type int.", str(e.exception) - ) - - # TESTING MAXIMUMCUT CLASS - - def test_maximumcut_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=10, p=0.8) - gr_edges = [list(edge) for edge in gr.edges()] - gr_weights = [1] * len(gr_edges) - - maxcut_prob_qubo = MaximumCut(gr).qubo - - self.assertTrue(terms_list_equality(gr_edges, maxcut_prob_qubo.terms)) - self.assertEqual(gr_weights, maxcut_prob_qubo.weights) - self.assertEqual(0, maxcut_prob_qubo.constant) - - def test_maximumcut_random_problem(self): - """Test MaximumCut random instance method""" - - seed = 1234 - gr = nx.generators.random_graphs.fast_gnp_random_graph(n=10, p=0.8, seed=seed) - maxcut_manual_prob = MaximumCut(gr).qubo - - np.random.seed(1234) - maxcut_random_prob = MaximumCut.random_instance( - n_nodes=10, edge_probability=0.8, seed=seed - ).qubo - - self.assertTrue( - terms_list_equality(maxcut_manual_prob.terms, maxcut_random_prob.terms) - ) - self.assertEqual(maxcut_manual_prob.weights, maxcut_random_prob.weights) - self.assertEqual(maxcut_manual_prob.constant, maxcut_random_prob.constant) - - def test_maximumcut_type_checking(self): - """ - Checks if the type-checking returns the right error. - """ - - # graph type-check - graph_list = [(1, 2), {"node1": 1, "node2": 2}, np.array([1, 2])] - - for each_graph in graph_list: - with self.assertRaises(TypeError) as e: - MaximumCut(G=each_graph) - self.assertEqual( - "Input problem graph must be a networkx Graph.", str(e.exception) - ) - - # TESTING KNAPSACK CLASS - - def test_knapsack_terms_weights_constant(self): - """Test that Knapsack creates the correct QUBO problem""" - - values = [2, 4, 3, 5] - weights = [3, 6, 9, 1] - weight_capacity = 15 - n_qubits = len(values) + int(np.ceil(np.log2(weight_capacity))) - penalty = 2 * max(values) - knap_terms = [ - [0, 1], - [0, 2], - [0, 3], - [1, 2], - [1, 3], - [2, 3], - [4, 5], - [4, 6], - [4, 7], - [5, 6], - [5, 7], - [6, 7], - [0, 4], - [0, 5], - [0, 6], - [0, 7], - [1, 4], - [1, 5], - [1, 6], - [1, 7], - [2, 4], - [2, 5], - [2, 6], - [2, 7], - [3, 4], - [3, 5], - [3, 6], - [3, 7], - [0], - [1], - [2], - [3], - [4], - [5], - [6], - [7], - ] - knap_weights = [ - 10.0, - 20.0, - 40.0, - 40.0, - 80.0, - 160.0, - 90.0, - 135.0, - 15.0, - 270.0, - 30.0, - 45.0, - 15.0, - 30.0, - 45.0, - 5.0, - 30.0, - 60.0, - 90.0, - 10.0, - 60.0, - 120.0, - 180.0, - 20.0, - 120.0, - 240.0, - 360.0, - 40.0, - -20.0, - -40.0, - -80.0, - -160.0, - -59.0, - -118.0, - -178.5, - -17.5, - ] - knap_constant = 563.0 - - knapsack_prob_qubo = Knapsack(values, weights, weight_capacity, penalty).qubo - - self.assertTrue(terms_list_equality(knap_terms, knapsack_prob_qubo.terms)) - self.assertEqual(knap_weights, knapsack_prob_qubo.weights) - self.assertEqual(knap_constant, knapsack_prob_qubo.constant) - self.assertEqual(n_qubits, knapsack_prob_qubo.n) - - def test_knapsack_random_problem(self): - """Test random instance method of Knapsack problem class""" - - rng = np.random.default_rng(1234) - n_items = 5 - values = list(map(int, rng.integers(1, n_items, size=n_items))) - weights = list(map(int, rng.integers(1, n_items, size=n_items))) - weight_capacity = int( - rng.integers(np.min(weights) * n_items, np.max(weights) * n_items) - ) - penalty = 2 * np.max(values) - - knap_manual = Knapsack(values, weights, weight_capacity, int(penalty)).qubo - - knap_random_instance = Knapsack.random_instance(n_items=n_items, seed=1234).qubo - - self.assertTrue( - terms_list_equality(knap_manual.terms, knap_random_instance.terms) - ) - self.assertEqual(knap_manual.weights, knap_random_instance.weights) - self.assertEqual(knap_manual.constant, knap_random_instance.constant) - self.assertEqual(knap_manual.n, knap_random_instance.n) - - def test_knapsack_random_problem_smallsize(self): - """Test random instance method of Knapsack problem class""" - - rng = np.random.default_rng(1234) - n_items = 3 - values = list(map(int, rng.integers(1, n_items, size=n_items))) - weights = list(map(int, rng.integers(1, n_items, size=n_items))) - weight_capacity = int( - rng.integers(np.min(weights) * n_items, np.max(weights) * n_items) - ) - penalty = 2 * np.max(values) - - knap_manual = Knapsack(values, weights, weight_capacity, int(penalty)).qubo - - knap_random_instance = Knapsack.random_instance(n_items=n_items, seed=1234).qubo - - self.assertTrue( - terms_list_equality(knap_manual.terms, knap_random_instance.terms) - ) - self.assertEqual(knap_manual.weights, knap_random_instance.weights) - self.assertEqual(knap_manual.constant, knap_random_instance.constant) - self.assertEqual(knap_manual.n, knap_random_instance.n) - - def test_knapsack_type_checking(self): - """ - Checks if the type-checking returns the right error. - """ - - # values type-check - weights = [1, 2] - weight_capacity = 5 - penalty = 0.1 - values_list = [(1, 2), {"test": "oh", "test1": "oh"}, np.array([1, 2])] - - for each_values in values_list: - with self.assertRaises(TypeError) as e: - Knapsack(each_values, weights, weight_capacity, penalty) - self.assertEqual( - "The input parameter, values, has to be a list", str(e.exception) - ) - - values_list = [["test", "oh"], [np.array(1), np.array(2)], [0.1, 0.5]] - for each_values in values_list: - with self.assertRaises(TypeError) as e: - Knapsack(each_values, weights, weight_capacity, penalty) - self.assertEqual( - "The elements in values list must be of type int.", str(e.exception) - ) - - # weights type-check - values = [1, 2] - weight_capacity = 5 - penalty = 0.1 - weights_list = [(1, 2), {"test": "oh", "test1": "oh"}, np.array([1, 2])] - - for each_weights in weights_list: - with self.assertRaises(TypeError) as e: - Knapsack(values, each_weights, weight_capacity, penalty) - self.assertEqual( - "The input parameter, weights, has to be a list", str(e.exception) - ) - - weights_list = [["test", "oh"], [np.array(1), np.array(2)], [0.1, 0.5]] - for each_weights in weights_list: - with self.assertRaises(TypeError) as e: - Knapsack(values, each_weights, weight_capacity, penalty) - self.assertEqual( - "The elements in weights list must be of type int.", str(e.exception) - ) - - # weight capacity type-check - values = [1, 2] - weights = [1, 2] - weight_capacity_list = [0.5, np.array(1), np.array(0.5), "oh"] - penalty = 0.1 - - for each_weight_capacity in weight_capacity_list: - with self.assertRaises(TypeError) as e: - Knapsack(values, weights, each_weight_capacity, penalty) - self.assertEqual( - "The input parameter, weight_capacity, has to be of type int", - str(e.exception), - ) - - with self.assertRaises(TypeError) as e: - Knapsack(values, weights, -1, penalty) - self.assertEqual( - "The input parameter, weight_capacity, must be a positive integer greater than 0", - str(e.exception), - ) - - # penalty capacity type-check - values = [1, 2] - weights = [1, 2] - penalty_list = [np.array(1), np.array(0.5), "oh"] - weight_capacity = 5 - - for each_penalty in penalty_list: - with self.assertRaises(TypeError) as e: - Knapsack(values, weights, weight_capacity, each_penalty) - self.assertEqual( - "The input parameter, penalty, has to be of type float or int", - str(e.exception), - ) - - # TESTING SLACKFREEKNAPSACK CLASS - - def test_slackfreeknapsack_terms_weights_constant(self): - """Test that SlackFree Knapsack creates the correct QUBO problem""" - - values = [2, 4, 3, 5] - weights = [3, 6, 9, 1] - weight_capacity = 15 - n_qubits = len(values) - penalty = 2 * max(values) - slknap_terms = [ - [0, 1], - [0, 2], - [0, 3], - [1, 2], - [1, 3], - [2, 3], - [0], - [1], - [2], - [3], - ] - slknap_weights = [ - 90.0, - 135.0, - 15.0, - 270.0, - 30.0, - 45.0, - 166.0, - 332.0, - 496.5, - 57.5, - ] - slknap_constant = 613.0 - - slknapsack_prob_qubo = SlackFreeKnapsack( - values, weights, weight_capacity, penalty - ).qubo - - self.assertTrue(terms_list_equality(slknap_terms, slknapsack_prob_qubo.terms)) - self.assertEqual(slknap_weights, slknapsack_prob_qubo.weights) - self.assertEqual(slknap_constant, slknapsack_prob_qubo.constant) - self.assertEqual(n_qubits, slknapsack_prob_qubo.n) - - def test_slackfreeknapsack_random_problem(self): - """Test random instance method of SlackFree Knapsack problem class""" - - rng = np.random.default_rng(1234) - n_items = 5 - values = list(map(int, rng.integers(1, n_items, size=n_items))) - weights = list(map(int, rng.integers(1, n_items, size=n_items))) - weight_capacity = int( - rng.integers(np.min(weights) * n_items, np.max(weights) * n_items) - ) - penalty = 2 * np.max(values) - - slknap_manual = SlackFreeKnapsack( - values, weights, weight_capacity, int(penalty) - ).qubo - - slknap_random_instance = SlackFreeKnapsack.random_instance( - n_items=n_items, seed=1234 - ).qubo - - self.assertTrue( - terms_list_equality(slknap_manual.terms, slknap_random_instance.terms) - ) - self.assertEqual(slknap_manual.weights, slknap_random_instance.weights) - self.assertEqual(slknap_manual.constant, slknap_random_instance.constant) - self.assertEqual(slknap_manual.n, slknap_random_instance.n) - - # TESTING MINIMUMVERTEXCOVER CLASS - - def test_mvc_terms_weights_constant(self): - """Test terms,weights,constant of QUBO generated by MVC class""" - - mvc_terms = [ - [0, 3], - [0, 4], - [1, 2], - [1, 3], - [2, 4], - [3, 4], - [0], - [1], - [2], - [3], - [4], - ] - mvc_weights = [1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 2.0, 2.0, 2.0, 3.25, 3.25] - mvc_constant = 10.0 - - gr = nx.generators.fast_gnp_random_graph(5, 0.8, seed=1234) - mvc_prob = MinimumVertexCover(gr, field=1.0, penalty=5).qubo - - self.assertTrue(terms_list_equality(mvc_terms, mvc_prob.terms)) - self.assertEqual(mvc_weights, mvc_prob.weights) - self.assertEqual(mvc_constant, mvc_prob.constant) - - def test_mvc_random_problem(self): - """Test the random_instance method of MVC class""" - mvc_terms = [ - [0, 3], - [0, 4], - [1, 2], - [1, 3], - [2, 4], - [3, 4], - [0], - [1], - [2], - [3], - [4], - ] - mvc_weights = [2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 4.5, 4.5, 4.5, 7.0, 7.0] - mvc_constant = 17.5 - - mvc_prob_random = MinimumVertexCover.random_instance( - n_nodes=5, edge_probability=0.8, seed=1234 - ).qubo - - self.assertTrue(terms_list_equality(mvc_terms, mvc_prob_random.terms)) - self.assertEqual(mvc_weights, mvc_prob_random.weights) - self.assertEqual(mvc_constant, mvc_prob_random.constant) - - def test_mvc_type_checking(self): - """ - Checks if the type-checking returns the right error. - """ - - # graph type-check - graph_list = [(1, 2), {"node1": 1, "node2": 2}, np.array([1, 2])] - field = 0.1 - penalty = 0.1 - - for each_graph in graph_list: - with self.assertRaises(TypeError) as e: - MinimumVertexCover(each_graph, field, penalty) - self.assertEqual( - "Input problem graph must be a networkx Graph.", str(e.exception) - ) - - # field capacity type-check - graph = nx.circulant_graph(6, [1]) - field_list = [np.array(1), np.array(0.5), "oh"] - penalty = 0.1 - - for each_field in field_list: - with self.assertRaises(TypeError) as e: - MinimumVertexCover(graph, each_field, penalty) - self.assertEqual( - "The input parameter, field, has to be of type float or int", - str(e.exception), - ) - - # penalty capacity type-check - graph = nx.circulant_graph(6, [1]) - field = 0.1 - penalty_list = [np.array(1), np.array(0.5), "oh"] - - for each_penalty in penalty_list: - with self.assertRaises(TypeError) as e: - MinimumVertexCover(graph, field, each_penalty) - self.assertEqual( - "The input parameter, penalty, has to be of type float or int", - str(e.exception), - ) - - # TESTING TSP PROBLEM CLASS - - def test_tsp_terms_weights_constant(self): - """Testing TSP problem creation""" - city_coordinates = [(4, 1), (4, 4), (3, 3), (1, 3.5)] - expected_terms = [ - [0, 3], - [1, 4], - [2, 5], - [0, 6], - [1, 7], - [8, 2], - [3, 6], - [4, 7], - [8, 5], - [0, 1], - [0, 2], - [1, 2], - [3, 4], - [3, 5], - [4, 5], - [6, 7], - [8, 6], - [8, 7], - [0, 4], - [1, 3], - [3, 7], - [4, 6], - [0, 5], - [2, 3], - [8, 3], - [5, 6], - [1, 5], - [2, 4], - [8, 4], - [5, 7], - [0], - [1], - [2], - [3], - [4], - [5], - [6], - [7], - [8], - ] - expected_weights = [ - 3.905124837953327, - 3.905124837953327, - 3.905124837953327, - 3.905124837953327, - 3.905124837953327, - 3.905124837953327, - 3.905124837953327, - 3.905124837953327, - 3.905124837953327, - 3.905124837953327, - 3.905124837953327, - 3.905124837953327, - 3.905124837953327, - 3.905124837953327, - 3.905124837953327, - 3.905124837953327, - 3.905124837953327, - 3.905124837953327, - 0.3535533905932738, - 0.3535533905932738, - 0.7603453162872774, - 0.7603453162872774, - 0.3535533905932738, - 0.3535533905932738, - 0.5153882032022076, - 0.5153882032022076, - 0.7603453162872774, - 0.7603453162872774, - 0.5153882032022076, - 0.5153882032022076, - -10.424148382787205, - -9.797225258452029, - -11.038545614372802, - -10.038047089667756, - -9.548132863497614, - -10.361716714885624, - -10.424148382787205, - -9.797225258452029, - -11.038545614372802, - ] - expected_constant = 62.51983851122417 - tsp_qubo = TSP(city_coordinates).qubo - self.assertTrue(terms_list_equality(expected_terms, tsp_qubo.terms)) - self.assertEqual(expected_weights, tsp_qubo.weights) - self.assertEqual(expected_constant, tsp_qubo.constant) - - def test_tsp_random_instance(self): - """Testing the random_instance method of the TSP problem class""" - rng = np.random.default_rng(1234) - n_cities = 4 - - box_size = np.sqrt(n_cities) - city_coordinates = list(map(tuple, box_size * rng.random(size=(n_cities, 2)))) - - tsp_prob = TSP(city_coordinates).qubo - - tsp_prob_random = TSP.random_instance(n_cities=n_cities, seed=1234).qubo - - self.assertTrue(terms_list_equality(tsp_prob_random.terms, tsp_prob.terms)) - self.assertEqual(tsp_prob_random.weights, tsp_prob.weights) - self.assertEqual(tsp_prob_random.constant, tsp_prob.constant) - - def test_tsp_type_checking(self): - """ - Checks if the type-checking returns the right error. - """ - # If nothing is given, must return a ValueError - with self.assertRaises(ValueError) as e: - TSP() - self.assertEqual( - "Input missing: city coordinates, distance matrix or (weighted graph) required", - str(e.exception), - ) - - # coordinates type-check - coordinates_list = [(1, 2), {"test": "oh", "test1": "oh"}, np.array([1, 2])] - - for each_coordinates in coordinates_list: - with self.assertRaises(TypeError) as e: - TSP(each_coordinates) - self.assertEqual("The coordinates should be a list", str(e.exception)) - - coordinates_list = [[[1, 2], [2, 1]], [np.array([1, 2]), np.array([2, 1])]] - for each_coordinates in coordinates_list: - with self.assertRaises(TypeError) as e: - TSP(each_coordinates) - self.assertEqual( - "The coordinates should be contained in a tuple", str(e.exception) - ) - - coordinates_list = [ - [("oh", "num"), ("num", "oh")], - [(np.array(1), np.array(2)), (np.array(2), np.array(1))], - ] - for each_coordinates in coordinates_list: - with self.assertRaises(TypeError) as e: - TSP(each_coordinates) - self.assertEqual( - "The coordinates must be of type float or int", str(e.exception) - ) - - # coordinates type-check - distance_matrices = [ - (1, 2), - np.array([[1, 2], [3, 4]]), - {"test": "oh", "test1": "oh"}, - ] - - for distance_matrix in distance_matrices: - with self.assertRaises(TypeError) as e: - TSP(distance_matrix=distance_matrix) - self.assertEqual("The distance matrix should be a list", str(e.exception)) - - # Distance matrix type-check - distance_matrices = [[(1, 2), (2, 1)], [np.array([1, 2]), np.array([2, 1])]] - for distance_matrix in distance_matrices: - with self.assertRaises(TypeError) as e: - TSP(distance_matrix=distance_matrix) - self.assertEqual( - "Each row in the distance matrix should be a list", str(e.exception) - ) - - distance_matrices = [ - [["oh", "num"], ["num", "oh"]], - [[np.array(1), np.array(2)], [np.array(2), np.array(1)]], - ] - for distance_matrix in distance_matrices: - with self.assertRaises(TypeError) as e: - TSP(distance_matrix=distance_matrix) - self.assertEqual( - "The distance matrix entries must be of type float or int", - str(e.exception), - ) - - distance_matrix = [[1, 2.3], [-2, 3]] - with self.assertRaises(ValueError) as e: - TSP(distance_matrix=distance_matrix) - self.assertEqual("Distances should be positive", str(e.exception)) - - # Graph type-check - G = nx.complete_graph(5) - for u, v in G.edges(): - G[u][v]["weight"] = "a" - - with self.assertRaises(TypeError) as e: - TSP(G=G) - self.assertEqual( - "The edge weights must be of type float or int", str(e.exception) - ) - - for u, v in G.edges(): - G[u][v]["weight"] = -2.0 - - with self.assertRaises(ValueError) as e: - TSP(G=G) - self.assertEqual("Edge weights should be positive", str(e.exception)) - - # TESTING SHORTESTPATH PROBLEM CLASS - - def test_shortestpath_terms_weights_constant(self): - """Test terms,weights,constant of QUBO generated by Shortest Path class""" - - sp_terms = [ - [0], - [1], - [2], - [3], - [1], - [1, 2], - [2, 1], - [2], - [2], - [2, 3], - [3, 2], - [3], - [0], - [0, 1], - [1], - [1, 3], - [0, 3], - [3, 1], - [3], - ] - sp_weights = [1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 4, -4, 1, 1, -4, 1, 1] - conv_sp_terms = [ - [1, 2], - [2, 1], - [2, 3], - [3, 2], - [0, 1], - [1, 3], - [0, 3], - [3, 1], - [0], - [1], - [2], - [3], - [], - ] - conv_sp_weights = [ - 0.25, - 0.25, - 0.25, - 0.25, - -1.0, - 0.25, - -1.0, - 0.25, - -0.5, - -0.5, - -0.5, - -0.5, - 2.5, - ] - sp_qubo_terms = [[1, 2], [2, 3], [0, 1], [1, 3], [0, 3], [0], [1], [2], [3]] - sp_qubo_weights = [0.5, 0.5, -1.0, 0.5, -1.0, -0.5, -0.5, -0.5, -0.5] - sp_qubo_constant = 2.5 - - gr = nx.generators.fast_gnp_random_graph(3, 1, seed=1234) - for u, v in gr.edges(): - gr.edges[u, v]["weight"] = 1 - for w in gr.nodes(): - gr.nodes[w]["weight"] = 1 - source, dest = 0, 2 - sp = ShortestPath(gr, source, dest) - n_variables = sp.G.number_of_nodes() + sp.G.number_of_edges() - 2 - bin_terms, bin_weights = sp.terms_and_weights() - terms, weights = QUBO.convert_qubo_to_ising(n_variables, bin_terms, bin_weights) - qubo = sp.qubo - - self.assertTrue(terms_list_equality(bin_terms, sp_terms)) - self.assertEqual(list(bin_weights), sp_weights) - self.assertTrue(terms_list_equality(terms, conv_sp_terms)) - self.assertEqual(list(weights), conv_sp_weights) - self.assertTrue(terms_list_equality(sp_qubo_terms, qubo.terms)) - self.assertEqual(sp_qubo_weights, qubo.weights) - self.assertEqual(sp_qubo_constant, qubo.constant) - - def test_shortestpath_random_instance(self): - """Test random instance method of Shortest Path problem class""" - sp_rand_terms = [[1, 2], [2, 3], [0, 1], [1, 3], [0, 3], [0], [1], [2], [3]] - sp_rand_weights = [0.5, 0.5, -1.0, 0.5, -1.0, -0.5, -0.5, -0.5, -0.5] - sp_rand_constant = 2.5 - - gr = nx.generators.fast_gnp_random_graph(3, 1, seed=1234) - for u, v in gr.edges(): - gr.edges[u, v]["weight"] = 1.0 - for w in gr.nodes(): - gr.nodes[w]["weight"] = 1.0 - sp_prob = ShortestPath.random_instance( - n_nodes=3, edge_probability=1, seed=1234, source=0, dest=2 - ).qubo - - self.assertTrue(terms_list_equality(sp_rand_terms, sp_prob.terms)) - self.assertEqual(sp_rand_weights, sp_prob.weights) - self.assertEqual(sp_rand_constant, sp_prob.constant) - - self.assertEqual(sp_prob.terms, ShortestPath(gr, 0, 2).qubo.terms) - self.assertEqual(sp_prob.weights, ShortestPath(gr, 0, 2).qubo.weights) - self.assertEqual(sp_prob.constant, ShortestPath(gr, 0, 2).qubo.constant) - - def test_assertion_error(self): - def test_assertion_fn(): - n_row = 1 - n_col = 1 - - G = nx.triangular_lattice_graph(n_row, n_col) - G = nx.convert_node_labels_to_integers(G) - G.remove_edges_from(nx.selfloop_edges(G)) - - node_weights = np.round(np.random.rand(len(G.nodes())), 3) - edge_weights = np.round(np.random.rand(len(G.edges())), 3) - - node_dict = dict(zip(list(G.nodes()), node_weights)) - edge_dict = dict(zip(list(G.edges()), edge_weights)) - - nx.set_edge_attributes(G, values=edge_dict, name="weight") - nx.set_node_attributes(G, values=node_dict, name="weight") - - shortest_path_problem = ShortestPath(G, 0, -1) - shortest_path_qubo = shortest_path_problem.qubo - - self.assertRaises(Exception, test_assertion_fn) - - # TESTING VRP PROBLEM CLASS - - def test_vrp_terms_weights_constant(self): - """Testing VRP problem creation""" - pos = [[4, 1], [4, 4], [3, 3]] # nodes position x, y - n_vehicles = 1 - n_nodes = len(pos) - G = nx.Graph() - G.add_nodes_from(range(n_nodes)) - for i in range(n_nodes - 1): - for j in range(i + 1, n_nodes): - r = np.sqrt((pos[i][0] - pos[j][0]) ** 2 + (pos[i][1] - pos[j][1]) ** 2) - G.add_weighted_edges_from([(i, j, r)]) - vrp_qubo = VRP(G, pos, n_vehicles).qubo - expected_terms = [[0, 1], [0, 2], [1, 2], [0], [1], [2]] - expected_weights = [2.0, 2.0, 2.0, 6.5, 6.881966011250105, 7.292893218813452] - expected_constant = 21.32514076993644 - - self.assertTrue(terms_list_equality(expected_terms, vrp_qubo.terms)) - self.assertEqual(expected_weights, vrp_qubo.weights) - self.assertEqual(expected_constant, vrp_qubo.constant) - - def test_vrp_random_instance(self): - """Testing the random_instance method of the VRP problem class""" - seed = 1234 - np.random.seed(seed) - n_nodes = 3 - n_vehicles = 1 - G = nx.Graph() - G.add_nodes_from(range(n_nodes)) - pos = [[0, 0]] - pos += [list(2 * np.random.rand(2) - 1) for _ in range(n_nodes - 1)] - for i in range(n_nodes - 1): - for j in range(i + 1, n_nodes): - r = np.sqrt((pos[i][0] - pos[j][0]) ** 2 + (pos[i][1] - pos[j][1]) ** 2) - G.add_weighted_edges_from([(i, j, r)]) - - vrp_prob = VRP(G, pos, n_vehicles).qubo - vrp_prob_random = VRP.random_instance( - n_nodes=n_nodes, n_vehicles=n_vehicles, seed=seed - ).qubo - - self.assertTrue(terms_list_equality(vrp_prob_random.terms, vrp_prob.terms)) - self.assertEqual(vrp_prob_random.weights, vrp_prob.weights) - self.assertEqual(vrp_prob_random.constant, vrp_prob.constant) - - def test_vrp_random_instance_unbalanced(self): - """ - Testing the random_instance method of the VRP problem class using the - unbalanced penalization method - """ - seed = 1234 - np.random.seed(seed) - n_nodes = 8 - n_vehicles = 2 - n_vars = n_nodes * (n_nodes - 1) // 2 - - vrp_prob_random = VRP.random_instance( - n_nodes=n_nodes, - n_vehicles=n_vehicles, - seed=seed, - method="unbalanced", - penalty=3 * [0.1], - ).qubo - - self.assertTrue(vrp_prob_random.n == n_vars) - self.assertTrue(vrp_prob_random.weights[0] == 0.05) - self.assertTrue(vrp_prob_random.weights[-1] == -0.688413325464513) - self.assertTrue(vrp_prob_random.terms[0] == [0, 1]) - self.assertTrue(vrp_prob_random.terms[-1] == [27]) - - def test_vrp_plot(self): - """ - Testing the random_instance method of the VRP problem class using the - unbalanced penalization method - """ - import matplotlib.pyplot as plt - - seed = 1234 - np.random.seed(seed) - n_nodes = 8 - n_vehicles = 2 - - vrp_prob_random = VRP.random_instance( - n_nodes=n_nodes, n_vehicles=n_vehicles, seed=seed - ) - vrp_sol = vrp_prob_random.classical_solution() - vrp_sol_str = vrp_prob_random.classical_solution(string=True) - - self.assertTrue(isinstance(vrp_prob_random.plot_solution(vrp_sol), plt.Figure)) - self.assertTrue( - isinstance(vrp_prob_random.plot_solution(vrp_sol_str), plt.Figure) - ) - - def test_vrp_type_checking(self): - """ - Checks if the type-checking returns the right error. - """ - # Wrong method is called - with self.assertRaises(ValueError) as e: - VRP.random_instance(n_nodes=6, n_vehicles=2, method="method") - self.assertEqual( - "The method 'method' is not valid.", - str(e.exception), - ) - # Penalization terms in unblanced method is not equal to three - with self.assertRaises(ValueError) as e: - VRP.random_instance( - n_nodes=6, n_vehicles=2, method="unbalanced", penalty=[0.1] - ) - self.assertEqual( - "The penalty must have 3 parameters [lambda_0, lambda_1, lambda_2]", - str(e.exception), - ) - - # paths with an unfeasible solution - with self.assertRaises(ValueError) as e: - vrp = VRP.random_instance(n_nodes=6, n_vehicles=2, seed=1234) - sol = vrp.classical_solution() - sol["x_0_1"] = ( - sol["x_0_1"] + 1 - ) % 2 # Changing one value in the solution to make it an unfeasible solution - vrp.paths_subtours(sol) - self.assertEqual( - "Solution provided does not fulfill all the path conditions.", - str(e.exception), - ) - # subtours with an unfeasible direction (broke subtour) - with self.assertRaises(ValueError) as e: - vrp = VRP.random_instance( - n_nodes=10, n_vehicles=2, subtours=[[]], seed=1234 - ) - sol = vrp.classical_solution() - sol["x_1_2"] = ( - sol["x_1_2"] + 1 - ) % 2 # Changing one value in the solution to make it an unfeasible solution - vrp.paths_subtours(sol) - self.assertEqual( - "The subtours in the solution are broken.", - str(e.exception), - ) - # Test unfeasible problem does not return classical solution - with self.assertRaises(ValueError) as e: - vrp = VRP.random_instance(n_nodes=3, n_vehicles=3, seed=1234) - sol = vrp.classical_solution() - vrp.paths_subtours(sol) - self.assertEqual( - "Solution not found: integer infeasible.", - str(e.exception), - ) - # Test wrong graph input - with self.assertRaises(TypeError) as e: - G = [[0, 1, 2], [1, 2, 3]] - vrp = VRP(G, pos=[[0, 1], [1, 2]], n_vehicles=2) - self.assertEqual( - "Input problem graph must be a networkx Graph.", - str(e.exception), - ) - # Test different size between graph nodes and x, y positions in pos - with self.assertRaises(ValueError) as e: - G = nx.Graph() - G.add_nodes_from(range(3)) - vrp = VRP(G, pos=[[0, 1], [1, 2]], n_vehicles=2) - self.assertEqual( - "The number of nodes in G is 3 while the x, y coordinates in pos is 2", - str(e.exception), - ) - # Test different size between colors and number of vehicles - with self.assertRaises(ValueError) as e: - seed = 1234 - vrp = VRP.random_instance(n_nodes=6, n_vehicles=2, seed=seed) - vrp.plot_solution(vrp.classical_solution(), colors=["tab:blue"]) - self.assertEqual( - "The length of colors 1 and the number of vehicles 2 do not match", - str(e.exception), - ) - - # 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_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 - - self.assertTrue(terms_list_equality(gr_edges, mis_prob_qubo.terms)) - self.assertEqual(gr_weights, mis_prob_qubo.weights) - self.assertEqual(0.5, mis_prob_qubo.constant) - - def test_mis_random_problem(self): - """Test MaximumCut random instance method""" - - seed = 1234 - gr = nx.generators.random_graphs.fast_gnp_random_graph(n=10, p=0.8, seed=seed) - mis_manual_prob = MIS(gr).qubo - - np.random.seed(1234) - mis_random_prob = MIS.random_instance( - n_nodes=10, edge_probability=0.8, seed=seed - ).qubo - - self.assertTrue( - terms_list_equality(mis_manual_prob.terms, mis_random_prob.terms) - ) - self.assertEqual(mis_manual_prob.weights, mis_random_prob.weights) - self.assertEqual(mis_manual_prob.constant, mis_random_prob.constant) - - def test_mis_type_checking(self): - """ - Checks if the type-checking returns the right error. - """ - - # graph type-check - graph_list = [(1, 2), {"node1": 1, "node2": 2}, np.array([1, 2])] - - for each_graph in graph_list: - with self.assertRaises(TypeError) as e: - MIS(G=each_graph) - self.assertEqual( - "Input problem graph must be a networkx Graph.", str(e.exception) - ) - - def test_mis_classical_sol(self): - """Test the maximal independent set random instance method classical solution""" - - 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, - } - - 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 - ) - 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)) - - def __generate_random_problems(self): - problems_random_instances = { - "tsp": TSP.random_instance(n_cities=randint(2, 15)), - "number_partition": NumberPartition.random_instance( - n_numbers=randint(2, 15) - ), - "maximum_cut": MaximumCut.random_instance( - n_nodes=randint(2, 15), edge_probability=random() - ), - "knapsack": Knapsack.random_instance(n_items=randint(2, 15)), - "slack_free_knapsack": SlackFreeKnapsack.random_instance( - n_items=randint(2, 15) - ), - "minimum_vertex_cover": MinimumVertexCover.random_instance( - n_nodes=randint(2, 15), edge_probability=random() - ), - "shortest_path": ShortestPath.random_instance( - n_nodes=randint(3, 15), edge_probability=random() - ), - "vehicle_routing": VRP.random_instance(), - "maximal_independent_set": MIS.random_instance( - n_nodes=randint(3, 15), edge_probability=random() - ), - "bin_packing": BinPacking.random_instance(), - } - qubo_random_instances = { - k: v.qubo for k, v in problems_random_instances.items() - } - qubo_random_instances["generic_qubo"] = QUBO.random_instance(randint(2, 15)) - return problems_random_instances, qubo_random_instances - - 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. - """ - - _, qubos = self.__generate_random_problems() - - expected_keys = { - "tsp": ["problem_type", "n_cities", "G", "A", "B"], - "number_partition": ["problem_type", "numbers", "n_numbers"], - "maximum_cut": ["problem_type", "G"], - "knapsack": [ - "problem_type", - "values", - "weights", - "weight_capacity", - "penalty", - "n_items", - ], - "slack_free_knapsack": [ - "problem_type", - "values", - "weights", - "weight_capacity", - "penalty", - "n_items", - ], - "minimum_vertex_cover": ["problem_type", "G", "field", "penalty"], - "shortest_path": ["problem_type", "G", "source", "dest"], - "vehicle_routing": [ - "problem_type", - "G", - "pos", - "n_vehicles", - "depot", - "subtours", - "method", - "penalty", - ], - "maximal_independent_set": ["problem_type", "G", "penalty"], - "bin_packing": [ - "problem_type", - "weights", - "weight_capacity", - "penalty", - "n_items", - "method", - "simplifications", - "n_bins", - "min_bins", - "solution", - ], - "generic_qubo": ["problem_type"], - } - - for k, v in qubos.items(): - assert ( - list(v.problem_instance.keys()) == expected_keys[k] - ), "Problem instance keys are not correct for problem type {}".format(k) - assert ( - k == v.problem_instance["problem_type"] - ), "Problem type is not correct for problem type {}".format(k) - - def test_problem_from_instance_dict(self): - """ - Test problem from instance method of the problem class. - """ - problem_mapper = { - "generic_qubo": QUBO, - "tsp": TSP, - "number_partition": NumberPartition, - "maximum_cut": MaximumCut, - "knapsack": Knapsack, - "slack_free_knapsack": SlackFreeKnapsack, - "minimum_vertex_cover": MinimumVertexCover, - "shortest_path": ShortestPath, - "maximal_independent_set": MIS, - "bin_packing": BinPacking, - "vehicle_routing": VRP, - } - - problems, qubos = self.__generate_random_problems() - - for type in qubos: - if type == "generic_qubo": - continue - - problem_instance = qubos[type].problem_instance.copy() - - problem = create_problem_from_dict(problem_instance) - - assert ( - problem.problem_instance == problems[type].problem_instance - ), "Problem from instance method is not correct for problem type {}".format( - type - ) - assert convert2serialize(problem) == convert2serialize( - problems[type] - ), "Problem from instance method is not correct for problem type {}".format( - type - ) - - def test_qubo_from_dict(self): - """ - Test qubo from dict method of the QUBO class. - """ - - _, qubos = self.__generate_random_problems() - for _, qubo in qubos.items(): - qubo_dict = qubo.asdict() - - new_qubo = QUBO.from_dict(qubo_dict) - - for term, new_term in zip(qubo.terms, new_qubo.terms): - assert set(term) == set( - new_term - ), "QUBO from dict method is not correct for problem type {}, terms compared: {}, {}".format( - qubo.problem_instance["problem_type"], term, new_term - ) - - assert set(qubo.weights) == set( - new_qubo.weights - ), "QUBO from dict method is not correct for problem type {}".format( - qubo.problem_instance["problem_type"] - ) - - for key in qubo.__dict__: - if key != "terms" and key != "weights": - assert ( - qubo.__dict__[key] == new_qubo.__dict__[key] - ), "QUBO from dict method is not correct for problem type {}".format( - qubo.problem_instance["problem_type"] - ) - - # 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, - ] - constant = 10.083333333333332 - weights_list = [3, 4] - weight_capacity = 6 - 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, - ] - constant = 9.29 - weights_list = [3, 4, 5] - weight_capacity = 10 - binpacking_prob_qubo = BinPacking(weights_list, weight_capacity).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_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)) - - 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)) - - 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)) - - def test_binpacking_random_problem(self): - """Test Bin Packing random instance method""" - - seed = 1234 - np.random.seed(seed) - min_weight = 1 - max_weight = 7 - n_items = 3 - weight_capacity = 15 - weights = list(np.random.randint(min_weight, max_weight, n_items)) - binpacking_manual_prob = BinPacking(weights, weight_capacity).qubo - - binpacking_random_prob = BinPacking.random_instance( - n_items=3, seed=seed, weight_capacity=weight_capacity - ).qubo - - self.assertTrue( - 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 - ) - - def test_binpacking_classical_sol(self): - """Test the Bin Packing random instance method classical solution""" - - 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, - } - - 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, - } - fig = binpacking_random_prob.plot_solution(sol) - self.assertTrue(isinstance(fig, Figure)) - - def test_binpacking_method_checking(self): - """ - Checks if the method-checking returns the right error. - """ - weights = [3, 5, 7] - weight_capacity = 15 - method = "random" - 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), - ) - - def test_binpacking_random_problem_checking(self): - """ - Checks if the random min_weight equal to max_weight returns the right error. - """ - min_weight = 5 - max_weight = 5 - 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), - ) - - def test_binpacking_classical_sol_checking(self): - """ - Checks if the unfeasible classical solution returns the right error. - """ - 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)) - - def test_binpacking_input_weights(self): - """ - Checks if the unfeasible classical solution returns the right error. - """ - weights = [10.1, 10] - weight_capacity = 8 - with self.assertRaises(TypeError) as e: - 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), - ) - - def test_binpacking_input_weight_capacity(self): - """ - Checks if the unfeasible classical solution returns the right error. - """ - weights = [10, 10] - weight_capacity = 8.1 - with self.assertRaises(TypeError) as e: - 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), - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/src/openqaoa-core/tests/test_qubo.py b/src/openqaoa-core/tests/test_qubo.py new file mode 100644 index 000000000..b2daffacc --- /dev/null +++ b/src/openqaoa-core/tests/test_qubo.py @@ -0,0 +1,364 @@ +import unittest +from random import randint, random +import numpy as np +from openqaoa.problems import ( + NumberPartition, + QUBO, + TSP, + Knapsack, + ShortestPath, + SlackFreeKnapsack, + MaximumCut, + MinimumVertexCover, + VRP, + PortfolioOptimization, + MIS, + BinPacking, +) +from openqaoa.utilities import convert2serialize +from openqaoa.problems.helper_functions import create_problem_from_dict + + +class TestQUBO(unittest.TestCase): + """Tests for the QUBO class""" + def __generate_random_problems(self): + problems_random_instances = { + "tsp": TSP.random_instance(n_cities=randint(2, 15)), + "number_partition": NumberPartition.random_instance( + n_numbers=randint(2, 15) + ), + "maximum_cut": MaximumCut.random_instance( + n_nodes=randint(2, 15), edge_probability=random() + ), + "knapsack": Knapsack.random_instance(n_items=randint(2, 15)), + "slack_free_knapsack": SlackFreeKnapsack.random_instance( + n_items=randint(2, 15) + ), + "minimum_vertex_cover": MinimumVertexCover.random_instance( + n_nodes=randint(2, 15), edge_probability=random() + ), + "shortest_path": ShortestPath.random_instance( + n_nodes=randint(3, 15), edge_probability=random() + ), + "vehicle_routing": VRP.random_instance(), + "maximal_independent_set": MIS.random_instance( + n_nodes=randint(3, 15), edge_probability=random() + ), + "bin_packing": BinPacking.random_instance(), + "portfolio_optimization": PortfolioOptimization.random_instance(), + } + qubo_random_instances = { + k: v.qubo for k, v in problems_random_instances.items() + } + qubo_random_instances["generic_qubo"] = QUBO.random_instance(randint(2, 15)) + return problems_random_instances, qubo_random_instances + + 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. + """ + + _, qubos = self.__generate_random_problems() + + expected_keys = { + "tsp": ["problem_type", "n_cities", "G", "A", "B"], + "number_partition": ["problem_type", "numbers", "n_numbers"], + "maximum_cut": ["problem_type", "G"], + "knapsack": [ + "problem_type", + "values", + "weights", + "weight_capacity", + "penalty", + "n_items", + ], + "slack_free_knapsack": [ + "problem_type", + "values", + "weights", + "weight_capacity", + "penalty", + "n_items", + ], + "minimum_vertex_cover": ["problem_type", "G", "field", "penalty"], + "shortest_path": ["problem_type", "G", "source", "dest"], + "vehicle_routing": [ + "problem_type", + "G", + "pos", + "n_vehicles", + "depot", + "subtours", + "method", + "penalty", + ], + "maximal_independent_set": ["problem_type", "G", "penalty"], + "bin_packing": [ + "problem_type", + "weights", + "weight_capacity", + "penalty", + "n_items", + "method", + "simplifications", + "n_bins", + "min_bins", + "solution", + ], + "portfolio_optimization": [ + "problem_type", + "mu", + "sigma", + "risk_factor", + "penalty", + "num_assets", + "budget", + ], + "generic_qubo": ["problem_type"], + } + + for k, v in qubos.items(): + assert ( + list(v.problem_instance.keys()) == expected_keys[k] + ), "Problem instance keys are not correct for problem type {}".format(k) + assert ( + k == v.problem_instance["problem_type"] + ), "Problem type is not correct for problem type {}".format(k) + + def test_problem_from_instance_dict(self): + """ + Test problem from instance method of the problem class. + """ + problems, qubos = self.__generate_random_problems() + + for type in qubos: + if type == "generic_qubo": + continue + + problem_instance = qubos[type].problem_instance.copy() + + problem = create_problem_from_dict(problem_instance) + + assert ( + problem.problem_instance == problems[type].problem_instance + ), "Problem from instance method is not correct for problem type {}".format( + type + ) + assert convert2serialize(problem) == convert2serialize( + problems[type] + ), "Problem from instance method is not correct for problem type {}".format( + type + ) + + def test_qubo_from_dict(self): + """ + Test qubo from dict method of the QUBO class. + """ + + _, qubos = self.__generate_random_problems() + for _, qubo in qubos.items(): + qubo_dict = qubo.asdict() + + new_qubo = QUBO.from_dict(qubo_dict) + + for term, new_term in zip(qubo.terms, new_qubo.terms): + assert set(term) == set( + new_term + ), "QUBO from dict method is not correct for problem type {}, terms compared: {}, {}".format( + qubo.problem_instance["problem_type"], term, new_term + ) + + assert set(qubo.weights) == set( + new_qubo.weights + ), "QUBO from dict method is not correct for problem type {}".format( + qubo.problem_instance["problem_type"] + ) + + for key in qubo.__dict__: + if key != "terms" and key != "weights": + assert ( + qubo.__dict__[key] == new_qubo.__dict__[key] + ), "QUBO from dict method is not correct for problem type {}".format( + qubo.problem_instance["problem_type"] + ) + def test_qubo_terms_and_weight_same_size(self): + """ + Test that creating a QUBO problem with invalid terms and weights + sizes raises an exception and check the constant is detected correctly + """ + n = 2 + terms_wrong = [[0], [1]] + terms_correct = [[0], [1], []] + weights = [1, 2, 3] + + with self.assertRaises(ValueError): + qubo_problem = QUBO(n, terms_wrong, weights) + + qubo_problem = QUBO(n, terms_correct, weights) + self.assertEqual(qubo_problem.constant, 3) + + def test_qubo_cleaning_terms(self): + """Test that cleaning terms works for a QUBO problem""" + terms = [[1, 2], [0], [2, 3], [2, 1], [0]] + weights = [3, 4, -3, -2, -1] + + cleaned_terms = [[1, 2], [0], [2, 3]] + self.assertEqual(QUBO.clean_terms_and_weights(terms, weights)[0], cleaned_terms) + + def test_qubo_cleaning_weights(self): + """Test that cleaning weights works for a QUBO problem""" + terms = [[1, 2], [0], [2, 3], [2, 1], [0]] + weights = [3, 4, -3, -2, -1] + + cleaned_weights = [1, 3, -3] + self.assertEqual( + QUBO.clean_terms_and_weights(terms, weights)[1], cleaned_weights + ) + + def test_qubo_ising_conversion(self): + """Test that conversion to Ising formulation works for a QUBO problem""" + # Small instance + n = 2 + terms = [[1, 1], [0, 0]] + weights = [3, 4] + + expected_ising_terms = [[0], [1], []] + expected_ising_weights = [-2, -1.5, 3.5] + + ising_terms, ising_weights = QUBO.convert_qubo_to_ising(n, terms, weights) + self.assertEqual(expected_ising_terms, ising_terms) + self.assertEqual(expected_ising_weights, ising_weights) + + # Larger instance + n = 4 + terms = [[1, 2], [0], [2, 3], [2, 1], [0]] + weights = [3, 4, -3, -2, -1] + + expected_ising_terms = [[1, 2], [2, 3], [2, 1], [0], [1], [2], [3], []] + expected_ising_weights = [0.75, -0.75, -0.5, -1.5, -0.25, 0.5, 0.75, 1.0] + + ising_terms, ising_weights = QUBO.convert_qubo_to_ising(n, terms, weights) + self.assertEqual(expected_ising_terms, ising_terms) + self.assertEqual(expected_ising_weights, ising_weights) + + def test_qubo_type_checking(self): + """ + Checks if the type-checking returns the right error. + """ + + # n type-check + n_list = [1.5, "test", [], (), {}, np.array(1)] + terms = [[0], []] + weights = [1, 2] + + with self.assertRaises(TypeError) as e: + for each_n in n_list: + QUBO(each_n, terms, weights) + self.assertEqual( + "The input parameter, n, has to be of type int", str(e.exception) + ) + + n_list = [-1, 0] + with self.assertRaises(TypeError) as e: + for each_n in n_list: + QUBO(each_n, terms, weights) + self.assertEqual( + "The input parameter, n, must be a positive integer greater than 0", + str(e.exception), + ) + + # weights type-check + n = 1 + terms = [[0], []] + weights_list = [{"test": "oh", "test1": "oh"}, np.array([1, 2])] + + for each_weights in weights_list: + with self.assertRaises(TypeError) as e: + QUBO(n, terms, each_weights) + self.assertEqual( + "The input parameter weights must be of type of list or tuple", + str(e.exception), + ) + + weights_list = [["test", "oh"], [np.array(1), np.array(2)]] + for each_weights in weights_list: + with self.assertRaises(TypeError) as e: + QUBO(n, terms, each_weights) + self.assertEqual( + "The elements in weights list must be of type float or int.", + str(e.exception), + ) + + # terms type-check + n = 1 + terms_list = [{"test": [0], "test1": []}] + weights = [1, 2] + for each_terms in terms_list: + with self.assertRaises(TypeError) as e: + QUBO(n, each_terms, weights) + self.assertEqual( + "The input parameter terms must be of type of list or tuple", + str(e.exception), + ) + + def test_qubo_metadata(self): + """Test that metadata is correctly stored""" + qubo_problem = QUBO.random_instance(3) + qubo_problem.set_metadata({"tag1": "value1", "tag2": "value2"}) + qubo_problem.set_metadata({"tag2": "value2.0"}) + + assert ( + qubo_problem.metadata["tag1"] == "value1" + ), "qubo metadata is not well set" + assert ( + qubo_problem.metadata["tag2"] == "value2.0" + ), "qubo metadata is not well set, should have overwritten previous value" + + error = False + try: + qubo_problem.set_metadata({"tag10": complex(1, 2)}) + except: + error = True + assert ( + error + ), "Should have thrown an error when setting metadata that is not json serializable" + + error = False + try: + qubo_problem.set_metadata({(1, 2): "value"}) + except: + error = True + assert ( + error + ), "Should have thrown an error when setting key metadata that is not json serializable" + + def test_qubo_problem_instance_serializable(self): + """test that when problem instance is not serializable, it throws an error""" + + qubo = QUBO.random_instance(3) + + error = False + try: + qubo.problem_instance = {"tag10": complex(1, 2)} + except: + error = True + assert ( + error + ), "Should have thrown an error when setting qubo problem instance that is not json serializable" + + error = False + try: + qubo.problem_instance = {(1, 2): "value"} + except: + error = True + assert ( + error + ), "Should have thrown an error when setting key qubo problem instance that is not json serializable" + + +if __name__ == "__main__": + unittest.main() diff --git a/src/openqaoa-core/tests/test_shortest_path.py b/src/openqaoa-core/tests/test_shortest_path.py new file mode 100644 index 000000000..34bda93ad --- /dev/null +++ b/src/openqaoa-core/tests/test_shortest_path.py @@ -0,0 +1,168 @@ +import unittest +import networkx as nx +import numpy as np +from openqaoa.problems import ShortestPath, QUBO + + +def terms_list_equality(terms_list1, terms_list2): + """ + Check the terms equality between two terms list + where the order of edges do not matter. + """ + if len(terms_list1) != len(terms_list2): + bool = False + else: + for term1, term2 in zip(terms_list1, terms_list2): + bool = True if (term1 == term2 or term1 == term2[::-1]) else False + + return bool + + +def terms_list_isclose(terms_list1, terms_list2): + """ + Check if the distance between two terms list + where the order of edges do not matter. + """ + if len(terms_list1) != len(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 + ) + + return bool + +class TestShortestPath(unittest.TestCase): + """Tests for ShortestPath class""" + def test_shortestpath_terms_weights_constant(self): + """Test terms,weights,constant of QUBO generated by Shortest Path class""" + + sp_terms = [ + [0], + [1], + [2], + [3], + [1], + [1, 2], + [2, 1], + [2], + [2], + [2, 3], + [3, 2], + [3], + [0], + [0, 1], + [1], + [1, 3], + [0, 3], + [3, 1], + [3], + ] + sp_weights = [1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 4, -4, 1, 1, -4, 1, 1] + conv_sp_terms = [ + [1, 2], + [2, 1], + [2, 3], + [3, 2], + [0, 1], + [1, 3], + [0, 3], + [3, 1], + [0], + [1], + [2], + [3], + [], + ] + conv_sp_weights = [ + 0.25, + 0.25, + 0.25, + 0.25, + -1.0, + 0.25, + -1.0, + 0.25, + -0.5, + -0.5, + -0.5, + -0.5, + 2.5, + ] + sp_qubo_terms = [[1, 2], [2, 3], [0, 1], [1, 3], [0, 3], [0], [1], [2], [3]] + sp_qubo_weights = [0.5, 0.5, -1.0, 0.5, -1.0, -0.5, -0.5, -0.5, -0.5] + sp_qubo_constant = 2.5 + + gr = nx.generators.fast_gnp_random_graph(3, 1, seed=1234) + for u, v in gr.edges(): + gr.edges[u, v]["weight"] = 1 + for w in gr.nodes(): + gr.nodes[w]["weight"] = 1 + source, dest = 0, 2 + sp = ShortestPath(gr, source, dest) + n_variables = sp.G.number_of_nodes() + sp.G.number_of_edges() - 2 + bin_terms, bin_weights = sp.terms_and_weights() + terms, weights = QUBO.convert_qubo_to_ising(n_variables, bin_terms, bin_weights) + qubo = sp.qubo + + self.assertTrue(terms_list_equality(bin_terms, sp_terms)) + self.assertEqual(list(bin_weights), sp_weights) + self.assertTrue(terms_list_equality(terms, conv_sp_terms)) + self.assertEqual(list(weights), conv_sp_weights) + self.assertTrue(terms_list_equality(sp_qubo_terms, qubo.terms)) + self.assertEqual(sp_qubo_weights, qubo.weights) + self.assertEqual(sp_qubo_constant, qubo.constant) + + def test_shortestpath_random_instance(self): + """Test random instance method of Shortest Path problem class""" + sp_rand_terms = [[1, 2], [2, 3], [0, 1], [1, 3], [0, 3], [0], [1], [2], [3]] + sp_rand_weights = [0.5, 0.5, -1.0, 0.5, -1.0, -0.5, -0.5, -0.5, -0.5] + sp_rand_constant = 2.5 + + gr = nx.generators.fast_gnp_random_graph(3, 1, seed=1234) + for u, v in gr.edges(): + gr.edges[u, v]["weight"] = 1.0 + for w in gr.nodes(): + gr.nodes[w]["weight"] = 1.0 + sp_prob = ShortestPath.random_instance( + n_nodes=3, edge_probability=1, seed=1234, source=0, dest=2 + ).qubo + + self.assertTrue(terms_list_equality(sp_rand_terms, sp_prob.terms)) + self.assertEqual(sp_rand_weights, sp_prob.weights) + self.assertEqual(sp_rand_constant, sp_prob.constant) + + self.assertEqual(sp_prob.terms, ShortestPath(gr, 0, 2).qubo.terms) + self.assertEqual(sp_prob.weights, ShortestPath(gr, 0, 2).qubo.weights) + self.assertEqual(sp_prob.constant, ShortestPath(gr, 0, 2).qubo.constant) + + def test_assertion_error(self): + def test_assertion_fn(): + n_row = 1 + n_col = 1 + + G = nx.triangular_lattice_graph(n_row, n_col) + G = nx.convert_node_labels_to_integers(G) + G.remove_edges_from(nx.selfloop_edges(G)) + + node_weights = np.round(np.random.rand(len(G.nodes())), 3) + edge_weights = np.round(np.random.rand(len(G.edges())), 3) + + node_dict = dict(zip(list(G.nodes()), node_weights)) + edge_dict = dict(zip(list(G.edges()), edge_weights)) + + nx.set_edge_attributes(G, values=edge_dict, name="weight") + nx.set_node_attributes(G, values=node_dict, name="weight") + + shortest_path_problem = ShortestPath(G, 0, -1) + shortest_path_qubo = shortest_path_problem.qubo + + self.assertRaises(Exception, test_assertion_fn) + + + +if __name__ == "__main__": + unittest.main() diff --git a/src/openqaoa-core/tests/test_tsp.py b/src/openqaoa-core/tests/test_tsp.py new file mode 100644 index 000000000..9ceebe04f --- /dev/null +++ b/src/openqaoa-core/tests/test_tsp.py @@ -0,0 +1,244 @@ +import unittest +import networkx as nx +import numpy as np +from openqaoa.problems import TSP + + +def terms_list_equality(terms_list1, terms_list2): + """ + Check the terms equality between two terms list + where the order of edges do not matter. + """ + if len(terms_list1) != len(terms_list2): + bool = False + else: + for term1, term2 in zip(terms_list1, terms_list2): + bool = True if (term1 == term2 or term1 == term2[::-1]) else False + + return bool + + +def terms_list_isclose(terms_list1, terms_list2): + """ + Check if the distance between two terms list + where the order of edges do not matter. + """ + if len(terms_list1) != len(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 + ) + + return bool + +class TestTSP(unittest.TestCase): + """Tests for TSP class""" + def test_tsp_terms_weights_constant(self): + """Testing TSP problem creation""" + city_coordinates = [(4, 1), (4, 4), (3, 3), (1, 3.5)] + expected_terms = [ + [0, 3], + [1, 4], + [2, 5], + [0, 6], + [1, 7], + [8, 2], + [3, 6], + [4, 7], + [8, 5], + [0, 1], + [0, 2], + [1, 2], + [3, 4], + [3, 5], + [4, 5], + [6, 7], + [8, 6], + [8, 7], + [0, 4], + [1, 3], + [3, 7], + [4, 6], + [0, 5], + [2, 3], + [8, 3], + [5, 6], + [1, 5], + [2, 4], + [8, 4], + [5, 7], + [0], + [1], + [2], + [3], + [4], + [5], + [6], + [7], + [8], + ] + expected_weights = [ + 3.905124837953327, + 3.905124837953327, + 3.905124837953327, + 3.905124837953327, + 3.905124837953327, + 3.905124837953327, + 3.905124837953327, + 3.905124837953327, + 3.905124837953327, + 3.905124837953327, + 3.905124837953327, + 3.905124837953327, + 3.905124837953327, + 3.905124837953327, + 3.905124837953327, + 3.905124837953327, + 3.905124837953327, + 3.905124837953327, + 0.3535533905932738, + 0.3535533905932738, + 0.7603453162872774, + 0.7603453162872774, + 0.3535533905932738, + 0.3535533905932738, + 0.5153882032022076, + 0.5153882032022076, + 0.7603453162872774, + 0.7603453162872774, + 0.5153882032022076, + 0.5153882032022076, + -10.424148382787205, + -9.797225258452029, + -11.038545614372802, + -10.038047089667756, + -9.548132863497614, + -10.361716714885624, + -10.424148382787205, + -9.797225258452029, + -11.038545614372802, + ] + expected_constant = 62.51983851122417 + tsp_qubo = TSP(city_coordinates).qubo + self.assertTrue(terms_list_equality(expected_terms, tsp_qubo.terms)) + self.assertEqual(expected_weights, tsp_qubo.weights) + self.assertEqual(expected_constant, tsp_qubo.constant) + + def test_tsp_random_instance(self): + """Testing the random_instance method of the TSP problem class""" + rng = np.random.default_rng(1234) + n_cities = 4 + + box_size = np.sqrt(n_cities) + city_coordinates = list(map(tuple, box_size * rng.random(size=(n_cities, 2)))) + + tsp_prob = TSP(city_coordinates).qubo + + tsp_prob_random = TSP.random_instance(n_cities=n_cities, seed=1234).qubo + + self.assertTrue(terms_list_equality(tsp_prob_random.terms, tsp_prob.terms)) + self.assertEqual(tsp_prob_random.weights, tsp_prob.weights) + self.assertEqual(tsp_prob_random.constant, tsp_prob.constant) + + def test_tsp_type_checking(self): + """ + Checks if the type-checking returns the right error. + """ + # If nothing is given, must return a ValueError + with self.assertRaises(ValueError) as e: + TSP() + self.assertEqual( + "Input missing: city coordinates, distance matrix or (weighted graph) required", + str(e.exception), + ) + + # coordinates type-check + coordinates_list = [(1, 2), {"test": "oh", "test1": "oh"}, np.array([1, 2])] + + for each_coordinates in coordinates_list: + with self.assertRaises(TypeError) as e: + TSP(each_coordinates) + self.assertEqual("The coordinates should be a list", str(e.exception)) + + coordinates_list = [[[1, 2], [2, 1]], [np.array([1, 2]), np.array([2, 1])]] + for each_coordinates in coordinates_list: + with self.assertRaises(TypeError) as e: + TSP(each_coordinates) + self.assertEqual( + "The coordinates should be contained in a tuple", str(e.exception) + ) + + coordinates_list = [ + [("oh", "num"), ("num", "oh")], + [(np.array(1), np.array(2)), (np.array(2), np.array(1))], + ] + for each_coordinates in coordinates_list: + with self.assertRaises(TypeError) as e: + TSP(each_coordinates) + self.assertEqual( + "The coordinates must be of type float or int", str(e.exception) + ) + + # coordinates type-check + distance_matrices = [ + (1, 2), + np.array([[1, 2], [3, 4]]), + {"test": "oh", "test1": "oh"}, + ] + + for distance_matrix in distance_matrices: + with self.assertRaises(TypeError) as e: + TSP(distance_matrix=distance_matrix) + self.assertEqual("The distance matrix should be a list", str(e.exception)) + + # Distance matrix type-check + distance_matrices = [[(1, 2), (2, 1)], [np.array([1, 2]), np.array([2, 1])]] + for distance_matrix in distance_matrices: + with self.assertRaises(TypeError) as e: + TSP(distance_matrix=distance_matrix) + self.assertEqual( + "Each row in the distance matrix should be a list", str(e.exception) + ) + + distance_matrices = [ + [["oh", "num"], ["num", "oh"]], + [[np.array(1), np.array(2)], [np.array(2), np.array(1)]], + ] + for distance_matrix in distance_matrices: + with self.assertRaises(TypeError) as e: + TSP(distance_matrix=distance_matrix) + self.assertEqual( + "The distance matrix entries must be of type float or int", + str(e.exception), + ) + + distance_matrix = [[1, 2.3], [-2, 3]] + with self.assertRaises(ValueError) as e: + TSP(distance_matrix=distance_matrix) + self.assertEqual("Distances should be positive", str(e.exception)) + + # Graph type-check + G = nx.complete_graph(5) + for u, v in G.edges(): + G[u][v]["weight"] = "a" + + with self.assertRaises(TypeError) as e: + TSP(G=G) + self.assertEqual( + "The edge weights must be of type float or int", str(e.exception) + ) + + for u, v in G.edges(): + G[u][v]["weight"] = -2.0 + + with self.assertRaises(ValueError) as e: + TSP(G=G) + self.assertEqual("Edge weights should be positive", str(e.exception)) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/src/openqaoa-core/tests/test_vrp.py b/src/openqaoa-core/tests/test_vrp.py new file mode 100644 index 000000000..490c155ea --- /dev/null +++ b/src/openqaoa-core/tests/test_vrp.py @@ -0,0 +1,217 @@ +import unittest +import networkx as nx +import numpy as np +from openqaoa.problems import VRP + + +def terms_list_equality(terms_list1, terms_list2): + """ + Check the terms equality between two terms list + where the order of edges do not matter. + """ + if len(terms_list1) != len(terms_list2): + bool = False + else: + for term1, term2 in zip(terms_list1, terms_list2): + bool = True if (term1 == term2 or term1 == term2[::-1]) else False + + return bool + + +def terms_list_isclose(terms_list1, terms_list2): + """ + Check if the distance between two terms list + where the order of edges do not matter. + """ + if len(terms_list1) != len(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 + ) + + return bool + +class TestQUBO(unittest.TestCase): + """Tests for VRP class""" + def test_vrp_terms_weights_constant(self): + """Testing VRP problem creation""" + pos = [[4, 1], [4, 4], [3, 3]] # nodes position x, y + n_vehicles = 1 + n_nodes = len(pos) + G = nx.Graph() + G.add_nodes_from(range(n_nodes)) + for i in range(n_nodes - 1): + for j in range(i + 1, n_nodes): + r = np.sqrt((pos[i][0] - pos[j][0]) ** 2 + (pos[i][1] - pos[j][1]) ** 2) + G.add_weighted_edges_from([(i, j, r)]) + vrp_qubo = VRP(G, pos, n_vehicles).qubo + expected_terms = [[0, 1], [0, 2], [1, 2], [0], [1], [2]] + expected_weights = [2.0, 2.0, 2.0, 6.5, 6.881966011250105, 7.292893218813452] + expected_constant = 21.32514076993644 + + self.assertTrue(terms_list_equality(expected_terms, vrp_qubo.terms)) + self.assertEqual(expected_weights, vrp_qubo.weights) + self.assertEqual(expected_constant, vrp_qubo.constant) + + def test_vrp_random_instance(self): + """Testing the random_instance method of the VRP problem class""" + seed = 1234 + np.random.seed(seed) + n_nodes = 3 + n_vehicles = 1 + G = nx.Graph() + G.add_nodes_from(range(n_nodes)) + pos = [[0, 0]] + pos += [list(2 * np.random.rand(2) - 1) for _ in range(n_nodes - 1)] + for i in range(n_nodes - 1): + for j in range(i + 1, n_nodes): + r = np.sqrt((pos[i][0] - pos[j][0]) ** 2 + (pos[i][1] - pos[j][1]) ** 2) + G.add_weighted_edges_from([(i, j, r)]) + + vrp_prob = VRP(G, pos, n_vehicles).qubo + vrp_prob_random = VRP.random_instance( + n_nodes=n_nodes, n_vehicles=n_vehicles, seed=seed + ).qubo + + self.assertTrue(terms_list_equality(vrp_prob_random.terms, vrp_prob.terms)) + self.assertEqual(vrp_prob_random.weights, vrp_prob.weights) + self.assertEqual(vrp_prob_random.constant, vrp_prob.constant) + + def test_vrp_random_instance_unbalanced(self): + """ + Testing the random_instance method of the VRP problem class using the + unbalanced penalization method + """ + seed = 1234 + np.random.seed(seed) + n_nodes = 8 + n_vehicles = 2 + n_vars = n_nodes * (n_nodes - 1) // 2 + + vrp_prob_random = VRP.random_instance( + n_nodes=n_nodes, + n_vehicles=n_vehicles, + seed=seed, + method="unbalanced", + penalty=3 * [0.1], + ).qubo + + self.assertTrue(vrp_prob_random.n == n_vars) + self.assertTrue(vrp_prob_random.weights[0] == 0.05) + self.assertTrue(vrp_prob_random.weights[-1] == -0.688413325464513) + self.assertTrue(vrp_prob_random.terms[0] == [0, 1]) + self.assertTrue(vrp_prob_random.terms[-1] == [27]) + + def test_vrp_plot(self): + """ + Testing the random_instance method of the VRP problem class using the + unbalanced penalization method + """ + import matplotlib.pyplot as plt + + seed = 1234 + np.random.seed(seed) + n_nodes = 8 + n_vehicles = 2 + + vrp_prob_random = VRP.random_instance( + n_nodes=n_nodes, n_vehicles=n_vehicles, seed=seed + ) + vrp_sol = vrp_prob_random.classical_solution() + vrp_sol_str = vrp_prob_random.classical_solution(string=True) + + self.assertTrue(isinstance(vrp_prob_random.plot_solution(vrp_sol), plt.Figure)) + self.assertTrue( + isinstance(vrp_prob_random.plot_solution(vrp_sol_str), plt.Figure) + ) + + def test_vrp_type_checking(self): + """ + Checks if the type-checking returns the right error. + """ + # Wrong method is called + with self.assertRaises(ValueError) as e: + VRP.random_instance(n_nodes=6, n_vehicles=2, method="method") + self.assertEqual( + "The method 'method' is not valid.", + str(e.exception), + ) + # Penalization terms in unblanced method is not equal to three + with self.assertRaises(ValueError) as e: + VRP.random_instance( + n_nodes=6, n_vehicles=2, method="unbalanced", penalty=[0.1] + ) + self.assertEqual( + "The penalty must have 3 parameters [lambda_0, lambda_1, lambda_2]", + str(e.exception), + ) + + # paths with an unfeasible solution + with self.assertRaises(ValueError) as e: + vrp = VRP.random_instance(n_nodes=6, n_vehicles=2, seed=1234) + sol = vrp.classical_solution() + sol["x_0_1"] = ( + sol["x_0_1"] + 1 + ) % 2 # Changing one value in the solution to make it an unfeasible solution + vrp.paths_subtours(sol) + self.assertEqual( + "Solution provided does not fulfill all the path conditions.", + str(e.exception), + ) + # subtours with an unfeasible direction (broke subtour) + with self.assertRaises(ValueError) as e: + vrp = VRP.random_instance( + n_nodes=10, n_vehicles=2, subtours=[[]], seed=1234 + ) + sol = vrp.classical_solution() + sol["x_1_2"] = ( + sol["x_1_2"] + 1 + ) % 2 # Changing one value in the solution to make it an unfeasible solution + vrp.paths_subtours(sol) + self.assertEqual( + "The subtours in the solution are broken.", + str(e.exception), + ) + # Test unfeasible problem does not return classical solution + with self.assertRaises(ValueError) as e: + vrp = VRP.random_instance(n_nodes=3, n_vehicles=3, seed=1234) + sol = vrp.classical_solution() + vrp.paths_subtours(sol) + self.assertEqual( + "Solution not found: integer infeasible.", + str(e.exception), + ) + # Test wrong graph input + with self.assertRaises(TypeError) as e: + G = [[0, 1, 2], [1, 2, 3]] + vrp = VRP(G, pos=[[0, 1], [1, 2]], n_vehicles=2) + self.assertEqual( + "Input problem graph must be a networkx Graph.", + str(e.exception), + ) + # Test different size between graph nodes and x, y positions in pos + with self.assertRaises(ValueError) as e: + G = nx.Graph() + G.add_nodes_from(range(3)) + vrp = VRP(G, pos=[[0, 1], [1, 2]], n_vehicles=2) + self.assertEqual( + "The number of nodes in G is 3 while the x, y coordinates in pos is 2", + str(e.exception), + ) + # Test different size between colors and number of vehicles + with self.assertRaises(ValueError) as e: + seed = 1234 + vrp = VRP.random_instance(n_nodes=6, n_vehicles=2, seed=seed) + vrp.plot_solution(vrp.classical_solution(), colors=["tab:blue"]) + self.assertEqual( + "The length of colors 1 and the number of vehicles 2 do not match", + str(e.exception), + ) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file