Source code for quantum_launcher.problems.problem_formulations.hamiltonians.tsp
1from typing import Literal
2
3import numpy as np
4import networkx as nx
5
6from quantum_launcher.problems.problem_initialization import TSP
7
8from quantum_launcher.hampy import Equation
9import quantum_launcher.hampy as hampy
10from quantum_launcher.hampy.utils import shift_affected_qubits
11
12
[docs]
13def make_non_collision_hamiltonian(node_count: int, quadratic=False):
14 """
15 Creates a Hamiltonian representing constraints for the TSP problem. (Each node visited only once, one node per timestep)
16 Qubit mapping: [step1:[node1, node2,... noden]],[step2],...[stepn]
17
18 Args:
19 node_count: Number of nodes in the TSP problem
20 quadratic: Whether to encode as a QUBO problem
21
22 Returns:
23 np.ndarray: Hamiltonian representing the constraints
24 """
25
26 n = node_count**2
27 eq = Equation(n)
28
29 # hampy.one_in_n() takes a long time to execute with large qubit counts.
30 # We can exploit the nature of tsp constraints and create the operator for the first timestep and then shift it for the rest of the timesteps
31 # Same with nodes below.
32 # I'm pretty sure this works as intended...
33
34 # Ensure that at each timestep only one node is visited
35 t0_op = hampy.one_in_n(
36 [i for i in range(node_count)],
37 eq.size,
38 quadratic=quadratic
39 )
40
41 for timestep in range(node_count):
42 shift = shift_affected_qubits(t0_op, timestep * node_count)
43 eq += shift
44
45 # Ensure that each node is visited only once
46 n0_op = hampy.one_in_n(
47 [timestep * node_count for timestep in range(node_count)],
48 eq.size,
49 quadratic=quadratic
50 )
51
52 for node in range(node_count):
53 shift = shift_affected_qubits(n0_op, node)
54 eq += shift
55
56 return -1 * eq.hamiltonian
57
58
[docs]
59def make_connection_hamiltonian(edge_costs: np.ndarray, return_to_start: bool = True) -> np.ndarray:
60 """
61 Creates a Hamiltonian that represents the costs of picking each path.
62
63 Args:
64 tsp_matrix: Edge cost matrix of the TSP problem
65
66 Returns:
67 np.ndarray: Optimal chain of nodes to visit
68 """
69 node_count = edge_costs.shape[0]
70 if len(edge_costs.shape) != 2 or edge_costs.shape[1] != node_count:
71 raise ValueError("edge_costs must be a square matrix")
72
73 n = node_count**2
74 eq = Equation(n)
75
76 for timestep in range(node_count - 1):
77 for node in range(node_count):
78 for next_node in range(node_count):
79 if node == next_node:
80 continue
81 and_hamiltonian = (
82 eq[node + timestep * node_count]
83 & eq[next_node + (timestep + 1) * node_count]
84 )
85 eq += edge_costs[node, next_node] * and_hamiltonian
86
87 if not return_to_start:
88 return eq.hamiltonian
89
90 # Add cost of returning to the first node
91 for node in range(node_count):
92 for node2 in range(node_count):
93 and_hamiltonian = (
94 eq[node + (node_count - 1) * node_count] & eq[node2]
95 )
96 eq += edge_costs[node, node2] * and_hamiltonian
97
98 return eq.hamiltonian
99
100
[docs]
101def problem_to_hamiltonian(problem: TSP, constraints_weight: int = 5, costs_weight: int = 1, return_to_start: bool = True, onehot: Literal['exact', 'quadratic'] = 'exact') -> np.ndarray:
102 """
103 Creates a Hamiltonian for the TSP problem.
104
105 Args:
106 problem: TSP problem instance
107 quadratic: Whether to encode as a quadratic Hamiltonian
108 constraints_weight: Weight of the constraints in the Hamiltonian
109 costs_weight: Weight of the costs in the Hamiltonian
110
111 Returns:
112 np.ndarray: Hamiltonian representing the TSP problem
113 """
114 instance_graph = problem.instance
115
116 edge_costs = nx.to_numpy_array(instance_graph)
117 # discourage breaking the constraints
118 edge_costs += np.eye(len(edge_costs)) * np.max(edge_costs)
119 scaled_edge_costs = edge_costs.astype(np.float32) / np.max(edge_costs)
120
121 node_count = len(instance_graph.nodes)
122
123 constraints = make_non_collision_hamiltonian(node_count, quadratic=(onehot == 'quadratic'))
124 costs = make_connection_hamiltonian(scaled_edge_costs, return_to_start=return_to_start)
125
126 return constraints * constraints_weight + costs * costs_weight