Register
Login
Resources
Docs Blog Datasets Glossary Case Studies Tutorials & Webinars
Product
Data Engine LLMs Platform Enterprise
Pricing Explore
Connect to our Discord channel

pctsp_gurobi.py 4.7 KB

You have to be logged in to leave a comment. Sign In
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
  1. #!/usr/bin/python
  2. # Copyright 2017, Gurobi Optimization, Inc.
  3. # Solve a traveling salesman problem on a set of
  4. # points using lazy constraints. The base MIP model only includes
  5. # 'degree-2' constraints, requiring each node to have exactly
  6. # two incident edges. Solutions to this model may contain subtours -
  7. # tours that don't visit every city. The lazy constraint callback
  8. # adds new constraints to cut them off.
  9. from gurobipy import *
  10. def solve_euclidian_pctsp(depot, loc, penalty, prize, min_prize, threads=0, timeout=None, gap=None):
  11. """
  12. Solves the Euclidan pctsp problem to pctsptimality using the MIP formulation
  13. with lazy subtour elimination constraint generation.
  14. :param points: list of (x, y) coordinate
  15. :return:
  16. """
  17. points = [depot] + loc
  18. n = len(points)
  19. # Callback - use lazy constraints to eliminate sub-tours
  20. def subtourelim(model, where):
  21. if where == GRB.Callback.MIPSOL:
  22. # make a list of edges selected in the solution
  23. vals = model.cbGetSolution(model._vars)
  24. selected = tuplelist((i, j) for i, j in model._vars.keys() if vals[i, j] > 0.5)
  25. # find the shortest cycle in the selected edge list
  26. tour = subtour(selected)
  27. if tour is not None:
  28. # add subtour elimination constraint for every pair of cities in tour
  29. # model.cbLazy(quicksum(model._vars[i, j]
  30. # for i, j in itertools.combinations(tour, 2))
  31. # <= len(tour) - 1)
  32. model.cbLazy(quicksum(model._vars[i, j]
  33. for i, j in itertools.combinations(tour, 2))
  34. <= quicksum(model._dvars[i] for i in tour) * (len(tour) - 1) / float(len(tour)))
  35. # Given a tuplelist of edges, find the shortest subtour
  36. def subtour(edges, exclude_depot=True):
  37. unvisited = list(range(n))
  38. #cycle = range(n + 1) # initial length has 1 more city
  39. cycle = None
  40. while unvisited: # true if list is non-empty
  41. thiscycle = []
  42. neighbors = unvisited
  43. while neighbors:
  44. current = neighbors[0]
  45. thiscycle.append(current)
  46. unvisited.remove(current)
  47. neighbors = [j for i, j in edges.select(current, '*') if j in unvisited]
  48. # If we do not yet have a cycle or this is the shorter cycle, keep this cycle
  49. # Unless it contains the depot while we do not want the depot
  50. if (
  51. (cycle is None or len(cycle) > len(thiscycle))
  52. and len(thiscycle) > 1 and not (0 in thiscycle and exclude_depot)
  53. ):
  54. cycle = thiscycle
  55. return cycle
  56. # Dictionary of Euclidean distance between each pair of points
  57. dist = {(i,j) :
  58. math.sqrt(sum((points[i][k]-points[j][k])**2 for k in range(2)))
  59. for i in range(n) for j in range(i)}
  60. m = Model()
  61. m.Params.outputFlag = False
  62. # Create variables
  63. vars = m.addVars(dist.keys(), obj=dist, vtype=GRB.BINARY, name='e')
  64. for i,j in vars.keys():
  65. vars[j,i] = vars[i,j] # edge in pctspposite direction
  66. # Depot vars can be 2
  67. for i,j in vars.keys():
  68. if i == 0 or j == 0:
  69. vars[i,j].vtype = GRB.INTEGER
  70. vars[i,j].ub = 2
  71. penalty_dict = {
  72. i + 1: -p # We get penalties for the nodes not visited so we 'save the penalties' for the nodes visited
  73. for i, p in enumerate(penalty)
  74. }
  75. delta = m.addVars(range(1, n), obj=penalty_dict, vtype=GRB.BINARY, name='delta')
  76. # Add degree-2 constraint (2 * delta for nodes which are not the depot)
  77. m.addConstrs(vars.sum(i,'*') == (2 if i == 0 else 2 * delta[i]) for i in range(n))
  78. # Minimum prize constraint
  79. assert min_prize <= sum(prize)
  80. # Subtract 1 from i since prizes are 0 indexed while delta vars start with 1 (0 is depot)
  81. m.addConstr(quicksum(var * prize[i - 1] for i, var in delta.items()) >= min_prize)
  82. # optimize model
  83. m._vars = vars
  84. m._dvars = delta
  85. m.Params.lazyConstraints = 1
  86. m.Params.threads = threads
  87. if timeout:
  88. m.Params.timeLimit = timeout
  89. if gap:
  90. m.Params.mipGap = gap * 0.01 # Percentage
  91. # For the correct objective, we need to add the sum of the penalties, which are subtracted when nodes are visited
  92. # this is important for the relative gap termination criterion
  93. m.objcon = sum(penalty)
  94. m.optimize(subtourelim)
  95. vals = m.getAttr('x', vars)
  96. selected = tuplelist((i,j) for i,j in vals.keys() if vals[i,j] > 0.5)
  97. tour = subtour(selected, exclude_depot=False)
  98. assert tour[0] == 0, "Tour should start with depot"
  99. return m.objVal, tour
Tip!

Press p or to see the previous file or, n or to see the next file

Comments

Loading...