|
1 | 1 | # This code is part of a Qiskit project.
|
2 | 2 | #
|
3 |
| -# (C) Copyright IBM 2018, 2023. |
| 3 | +# (C) Copyright IBM 2018, 2024. |
4 | 4 | #
|
5 | 5 | # This code is licensed under the Apache License, Version 2.0. You may
|
6 | 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory
|
@@ -111,5 +111,142 @@ def test_parse_tsplib_format(self):
|
111 | 111 | self.assertEqual(graph.number_of_edges(), 51 * 50 / 2) # fully connected graph
|
112 | 112 |
|
113 | 113 |
|
| 114 | +class TestTspCustomGraph(QiskitOptimizationTestCase): |
| 115 | + """Test Tsp class with a custom non-geometric graph""" |
| 116 | + |
| 117 | + def setUp(self): |
| 118 | + """Set up test cases.""" |
| 119 | + super().setUp() |
| 120 | + self.graph = nx.Graph() |
| 121 | + self.edges_with_weights = [ |
| 122 | + (0, 1, 5), |
| 123 | + (1, 2, 5), |
| 124 | + (1, 3, 15), |
| 125 | + (2, 3, 15), |
| 126 | + (2, 4, 5), |
| 127 | + (3, 4, 5), |
| 128 | + (3, 0, 5), |
| 129 | + ] |
| 130 | + |
| 131 | + self.graph.add_nodes_from(range(5)) |
| 132 | + for source, target, weight in self.edges_with_weights: |
| 133 | + self.graph.add_edge(source, target, weight=weight) |
| 134 | + |
| 135 | + op = QuadraticProgram() |
| 136 | + for _ in range(25): |
| 137 | + op.binary_var() |
| 138 | + |
| 139 | + result_vector = np.zeros(25) |
| 140 | + result_vector[0] = 1 |
| 141 | + result_vector[6] = 1 |
| 142 | + result_vector[12] = 1 |
| 143 | + result_vector[23] = 1 |
| 144 | + result_vector[19] = 1 |
| 145 | + |
| 146 | + self.optimal_path = [0, 1, 2, 4, 3] |
| 147 | + self.optimal_edges = [(0, 1), (1, 2), (2, 4), (4, 3), (3, 0)] |
| 148 | + self.optimal_cost = 25 |
| 149 | + |
| 150 | + self.result = OptimizationResult( |
| 151 | + x=result_vector, |
| 152 | + fval=self.optimal_cost, |
| 153 | + variables=op.variables, |
| 154 | + status=OptimizationResultStatus.SUCCESS, |
| 155 | + ) |
| 156 | + |
| 157 | + def test_to_quadratic_program(self): |
| 158 | + """Test to_quadratic_program with custom graph""" |
| 159 | + tsp = Tsp(self.graph) |
| 160 | + quadratic_program = tsp.to_quadratic_program() |
| 161 | + |
| 162 | + self.assertEqual(quadratic_program.name, "TSP") |
| 163 | + self.assertEqual(quadratic_program.get_num_vars(), 25) |
| 164 | + |
| 165 | + for variable in quadratic_program.variables: |
| 166 | + self.assertEqual(variable.vartype, VarType.BINARY) |
| 167 | + |
| 168 | + objective = quadratic_program.objective |
| 169 | + self.assertEqual(objective.constant, 0) |
| 170 | + self.assertEqual(objective.sense, QuadraticObjective.Sense.MINIMIZE) |
| 171 | + |
| 172 | + # Test objective quadratic terms |
| 173 | + quadratic_terms = objective.quadratic.to_dict() |
| 174 | + for source, target, weight in self.edges_with_weights: |
| 175 | + for position in range(5): |
| 176 | + next_position = (position + 1) % 5 |
| 177 | + key = ( |
| 178 | + min(source * 5 + position, target * 5 + next_position), |
| 179 | + max(source * 5 + position, target * 5 + next_position), |
| 180 | + ) |
| 181 | + self.assertIn(key, quadratic_terms) |
| 182 | + self.assertEqual(quadratic_terms[key], weight) |
| 183 | + |
| 184 | + linear_constraints = quadratic_program.linear_constraints |
| 185 | + |
| 186 | + # Test node constraints (each node appears once) |
| 187 | + for node in range(5): |
| 188 | + self.assertEqual(linear_constraints[node].sense, Constraint.Sense.EQ) |
| 189 | + self.assertEqual(linear_constraints[node].rhs, 1) |
| 190 | + self.assertEqual( |
| 191 | + linear_constraints[node].linear.to_dict(), |
| 192 | + {5 * node + pos: 1 for pos in range(5)}, |
| 193 | + ) |
| 194 | + |
| 195 | + # Test position constraints (each position filled once) |
| 196 | + for position in range(5): |
| 197 | + self.assertEqual(linear_constraints[5 + position].sense, Constraint.Sense.EQ) |
| 198 | + self.assertEqual(linear_constraints[5 + position].rhs, 1) |
| 199 | + self.assertEqual( |
| 200 | + linear_constraints[5 + position].linear.to_dict(), |
| 201 | + {5 * node + position: 1 for node in range(5)}, |
| 202 | + ) |
| 203 | + |
| 204 | + # Test non-edge constraints |
| 205 | + non_edges = list(nx.non_edges(self.graph)) |
| 206 | + constraint_idx = 10 # Start after node and position constraints |
| 207 | + |
| 208 | + for i, j in non_edges: |
| 209 | + for k in range(5): |
| 210 | + next_k = (k + 1) % 5 |
| 211 | + |
| 212 | + # Check forward constraint: x[i,k] + x[j,(k+1)%n] <= 1 |
| 213 | + constraint = linear_constraints[constraint_idx] |
| 214 | + self.assertEqual(constraint.sense, Constraint.Sense.LE) |
| 215 | + self.assertEqual(constraint.rhs, 1) |
| 216 | + linear_dict = constraint.linear.to_dict() |
| 217 | + self.assertEqual(len(linear_dict), 2) |
| 218 | + self.assertEqual(linear_dict[i * 5 + k], 1) |
| 219 | + self.assertEqual(linear_dict[j * 5 + next_k], 1) |
| 220 | + constraint_idx += 1 |
| 221 | + |
| 222 | + # Check backward constraint: x[j,k] + x[i,(k+1)%n] <= 1 |
| 223 | + constraint = linear_constraints[constraint_idx] |
| 224 | + self.assertEqual(constraint.sense, Constraint.Sense.LE) |
| 225 | + self.assertEqual(constraint.rhs, 1) |
| 226 | + linear_dict = constraint.linear.to_dict() |
| 227 | + self.assertEqual(len(linear_dict), 2) |
| 228 | + self.assertEqual(linear_dict[j * 5 + k], 1) |
| 229 | + self.assertEqual(linear_dict[i * 5 + next_k], 1) |
| 230 | + constraint_idx += 1 |
| 231 | + |
| 232 | + # Verify total number of constraints |
| 233 | + expected_constraints = ( |
| 234 | + 5 # node constraints |
| 235 | + + 5 # position constraints |
| 236 | + + len(non_edges) * 2 * 5 # non-edge constraints (2 per non-edge per position) |
| 237 | + ) |
| 238 | + self.assertEqual(len(linear_constraints), expected_constraints) |
| 239 | + |
| 240 | + def test_interpret(self): |
| 241 | + """Test interpret with custom graph""" |
| 242 | + tsp = Tsp(self.graph) |
| 243 | + self.assertEqual(tsp.interpret(self.result), self.optimal_path) |
| 244 | + |
| 245 | + def test_edgelist(self): |
| 246 | + """Test _edgelist with custom graph""" |
| 247 | + tsp = Tsp(self.graph) |
| 248 | + self.assertEqual(tsp._edgelist(self.result), self.optimal_edges) |
| 249 | + |
| 250 | + |
114 | 251 | if __name__ == "__main__":
|
115 | 252 | unittest.main()
|
0 commit comments