Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UF: Adding the traveling salesman problem (TSP) linear programming version #230

Merged
merged 14 commits into from
Jul 24, 2023
2 changes: 1 addition & 1 deletion src/openqaoa-core/problems/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from .minimumvertexcover import MinimumVertexCover
from .numberpartition import NumberPartition
from .shortestpath import ShortestPath
from .tsp import TSP
from .tsp import TSP, TSP_LP
from .portfoliooptimization import PortfolioOptimization
from .maximalindependentset import MIS
from .binpacking import BinPacking
Expand Down
3 changes: 2 additions & 1 deletion src/openqaoa-core/problems/helper_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from .minimumvertexcover import MinimumVertexCover
from .numberpartition import NumberPartition
from .shortestpath import ShortestPath
from .tsp import TSP
from .tsp import TSP, TSP_LP
from .vehiclerouting import VRP
from .maximalindependentset import MIS
from .binpacking import BinPacking
Expand Down Expand Up @@ -36,6 +36,7 @@ def create_problem_from_dict(problem_instance: dict) -> Problem:
problem_mapper = {
"generic_qubo": QUBO,
"tsp": TSP,
"tsp_lp": TSP_LP,
"number_partition": NumberPartition,
"maximum_cut": MaximumCut,
"knapsack": Knapsack,
Expand Down
109 changes: 109 additions & 0 deletions src/openqaoa-core/problems/tsp.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from ..utilities import check_kwargs
from .problem import Problem
from .vehiclerouting import VRP
from .qubo import QUBO


Expand Down Expand Up @@ -350,3 +351,111 @@ def qubo(self):
# Convert to Ising equivalent since variables are in {0, 1} rather than {-1, 1}
ising_terms, ising_weights = QUBO.convert_qubo_to_ising(n, terms, weights)
return QUBO(n, ising_terms, ising_weights, self.problem_instance)

class TSP_LP(VRP):
"""
Creates an instance of the traveling salesman problem (TSP) based on the
integer linear programming formulation.
https://en.wikipedia.org/wiki/Travelling_salesman_problem

Parameters
----------
G: networkx.Graph
Networkx graph of the problem
pos: list[list]
position x, y of each node
subtours: list[list]
if -1 (Default value): All the possible subtours are added to the constraints. Avoid it for large instances.
if there are subtours that want be avoided in the solution, e.g, a 8 cities
TSP with an optimal solution showing subtour between nodes 4, 5, and 8. It can be
avoided introducing the constraint subtours=[[4,5,8]]. Additional information
about subtours refer to https://de.wikipedia.org/wiki/Datei:TSP_short_cycles.png
penalty: float
Constraints penalty factor. If the method is 'unbalanced' three values are needed,
one for the equality constraints and two for the inequality constraints.
method: str
Two available methods for the inequality constraints ["slack", "unbalanced"]
For 'unblanced' see https://arxiv.org/abs/2211.13914
Returns
-------
An instance of the TSP problem for the linear programming formulation.
"""
__name__ = "tsp_lp"

def __init__(self, G, pos, subtours, penalty, method):
super().__init__(G, pos, n_vehicles=1, subtours=subtours,
method=method, penalty=penalty)
@staticmethod
def random_instance(**kwargs):
"""
Creates a random instance of the traveling salesman problem, whose graph is
random.

Parameters
----------
**kwargs:
Required keyword arguments are:
n_nodes: int
The number of nodes (vertices) in the graph.
method: str
method for the inequality constraints ['slack', 'unbalanced'].
For the unbalaced method refer to https://arxiv.org/abs/2211.13914
For the slack method: https://en.wikipedia.org/wiki/Slack_variable
subtours: list[list]
Manually add the subtours to be avoided
seed: int
Seed for the random problems.

Returns
-------
A random instance of the vehicle routing problem.
"""
n_nodes = kwargs.get("n_nodes", 6)
seed = kwargs.get("seed", None)
method = kwargs.get("method", "slack")
if method == "slack":
penalty = kwargs.get("penalty", 4)
elif method == "unbalanced":
penalty = kwargs.get("penalty", [4, 1, 1])
else:
raise ValueError(f"The method '{method}' is not valid.")
subtours = kwargs.get("subtours", -1)
np.random.seed(seed)
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)])

return TSP_LP(G, pos, subtours=subtours, method=method, penalty=penalty)

def get_distance(self, sol):
"""
Get the TSP solution distance

Parameters
----------
sol : str, dict
Solution of the TSP problem.

Returns
-------
total_distance : float
Total distance of the current solution.

"""
cities = self.G.number_of_nodes()
if isinstance(sol, str):
solution = {}
for n, var in enumerate(self.docplex_model.iter_binary_vars()):
solution[var.name] = int(sol[n])
sol = solution
total_distance = 0
for i in range(cities):
for j in range(i+1, cities):
if sol[f"x_{i}_{j}"]:
total_distance += self.G.edges[i, j]["weight"]
return total_distance
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
NumberPartition,
QUBO,
TSP,
TSP_LP,
Knapsack,
ShortestPath,
SlackFreeKnapsack,
Expand Down
Loading