Loading...
Searching...
No Matches
tsp.py
Go to the documentation of this file.
1
12
13from itertools import product
14import os
15import sys
16from gams import GamsWorkspace, GamsModifier, SymbolUpdateType
17
18GAMS_MODEL = """
19$if not set tspdata $abort 'tspdata not set'
20
21Set
22 ii 'cities'
23 i(ii) 'subset of cities';
24
25Alias (ii,jj), (i,j,k);
26
27Parameter c(ii,jj) 'distance matrix';
28
29$gdxIn %tspdata%
30$load ii c
31$gdxIn
32
33$if not set nrCities $set nrCities 20
34i(ii)$(ord(ii) < %nrCities%) = yes;
35
36Variable
37 x(ii,jj) 'decision variables - leg of trip'
38 z 'objective variable';
39
40Binary Variable x;
41x.fx(ii,ii) = 0;
42
43Equation
44 objective 'total cost'
45 rowsum(ii) 'leave each city only once'
46 colsum(jj) 'arrive at each city only once';
47
48* the assignment problem is a relaxation of the TSP
49objective.. z =e= sum((i,j), c(i,j)*x(i,j));
50
51rowsum(i).. sum(j, x(i,j)) =e= 1;
52
53colsum(j).. sum(i, x(i,j)) =e= 1;
54
55$if not set cmax $set cmax 2
56set cut /c0*c%cmax%/;
57
58Parameter
59 acut(cut,ii,jj) 'cut constraint matrix'
60 rhscut(cut) 'cut constraint rhs';
61
62Equation sscut(cut) 'sub_tour elimination cut';
63sscut(cut).. sum((i,j), acut(cut,i,j)*x(i,j)) =l= rhscut(cut);
64
65set cc(cut) 'previous cuts';
66cc(cut) = no;
67
68$if set cutdata execute_load '%cutdata%', cc, acut, rhscut;
69
70acut(cut,i,j)$(not cc(cut)) = eps;
71
72rhscut(cut)$(not cc(cut)) = card(ii);
73
74Model assign /all/;
75
76option optcr=0;
77"""
78
79if __name__ == "__main__":
80 sys_dir = sys.argv[1] if len(sys.argv) > 1 else None
81 work_dir = sys.argv[2] if len(sys.argv) > 2 else None
82 ws = GamsWorkspace(system_directory=sys_dir, working_directory=work_dir)
83
84 # number of cuts that can be added to a model instance
85 cuts_per_round = 10
86
87 # current cut
88 cur_cut = 0
89
90 # cut limit for current model instance (cmax = cur_cut + cuts_per_round)
91 cmax = 0
92
93 # database used to collect all generated cuts
94 cut_data = ws.add_database()
95 cc = cut_data.add_set("cc", 1)
96 acut = cut_data.add_parameter("acut", 3)
97 rhscut = cut_data.add_parameter("rhscut", 1)
98
99 n = [] # list of cities (i1, i2, i3, ...)
100 while True:
101 # create a new model instance if the cut limit is reached
102 if cur_cut >= cmax:
103 cmax = cur_cut + cuts_per_round
104 cut_data.export()
105
106 # create the checkpoint
107 job = ws.add_job_from_string(GAMS_MODEL)
108 cp = ws.add_checkpoint()
109 opt = ws.add_options()
110 opt.defines["nrcities"] = "20"
111 opt.defines["cmax"] = str(cmax - 1)
112 opt.defines["cutdata"] = cut_data.name
113 opt.defines["tspdata"] = (
114 '"'
115 + os.path.join(ws.system_directory, "apifiles", "Data", "tsp.gdx")
116 + '"'
117 )
118 job.run(gams_options=opt, checkpoint=cp)
119
120 # fill the cities list only once
121 if not n:
122 for i in job.out_db["i"]:
123 n += i.keys
124
125 # instantiate the model instance with modifiers mi_acut and mi_rhscut
126 mi = cp.add_modelinstance()
127 mi_acut = mi.sync_db.add_parameter("acut", 3)
128 mi_rhscut = mi.sync_db.add_parameter("rhscut", 1)
129 mi.instantiate(
130 "assign use mip min z", [GamsModifier(mi_acut), GamsModifier(mi_rhscut)]
131 )
132
133 # solve model instance using update type accumulate and clear acut and rhscut afterwards
134 mi.solve(SymbolUpdateType.Accumulate)
135 mi.sync_db["acut"].clear()
136 mi.sync_db["rhscut"].clear()
137
138 # collect graph information from the solution
139 graph = {}
140 for i, j in product(n, n):
141 if mi.sync_db["x"].find_record([i, j]).level > 0.5:
142 graph[i] = j
143
144 # find all sub tours and add the required cuts by modifying acut and rhscut
145 not_visited = list(n) # make copy
146 while not_visited:
147 i = not_visited[0]
148 sub_tour = [i]
149 while graph[i] != not_visited[0]:
150 i = graph[i]
151 sub_tour.append(i)
152 not_visited = list(filter(lambda x: x not in sub_tour, not_visited))
153
154 # add the cuts to both databases (cut_data, mi.sync_db)
155 for i, j in product(sub_tour, sub_tour):
156 acut.add_record([f"c{cur_cut}", i, j]).value = 1
157 mi_acut.add_record([f"c{cur_cut}", i, j]).value = 1
158 rhscut.add_record(f"c{cur_cut}").value = len(sub_tour) - 0.5
159 mi_rhscut.add_record([f"c{cur_cut}"]).value = len(sub_tour) - 0.5
160 cc.add_record(f"c{cur_cut}")
161 cur_cut += 1
162
163 # quit the loop if a sub tour contains all cities and print the solution
164 if len(sub_tour) == len(n):
165 print(f"z={mi.sync_db['z'].first_record().level}")
166 print("sub_tour: ")
167 for i in sub_tour:
168 print(f"{i} -> ", end="")
169 print(sub_tour[0])
170 break