Loading...
Searching...
No Matches
tsp.m
1function tsp(varargin)
2
3 % check workspace info from arguments
4 if nargin > 0
5 wsInfo = gams.control.WorkspaceInfo();
6 wsInfo.systemDirectory = varargin{1};
7 ws = gams.control.Workspace(wsInfo);
8 else
9 ws = gams.control.Workspace();
10 end
11
12 model = {
13 '$Title Traveling Salesman Problem '
14 '$Ontext '
15 ' '
16 'The sub_tour elimination constraints are generated by a Python '
17 'script. The MIP is solved over and over, but GAMS have to '
18 'generate the model only after n cuts have been added. '
19 ' '
20 '$Offtext '
21 ' '
22 '$if not set tspdata $abort ''tspdata not set'' '
23 ' '
24 'set ii cities '
25 ' i(ii) subset of cities '
26 'alias (ii,jj),(i,j,k); '
27 ' '
28 'parameter c(ii,jj) distance matrix; '
29 ' '
30 '$gdxin %tspdata% '
31 '$load ii c '
32 ' '
33 '$if not set nrCities $set nrCities 20 '
34 'i(ii)$(ord(ii) < %nrCities%) = yes; '
35 ' '
36 'variables x(ii,jj) decision variables - leg of trip '
37 ' z objective variable; '
38 'binary variable x; x.fx(ii,ii) = 0; '
39 ' '
40 'equations objective total cost '
41 ' rowsum(ii) leave each city only once '
42 ' colsum(jj) arrive at each city only once; '
43 ' '
44 '* the assignment problem is a relaxation of the TSP '
45 'objective.. z =e= sum((i,j), c(i,j)*x(i,j)); '
46 'rowsum(i).. sum(j, x(i,j)) =e= 1; '
47 'colsum(j).. sum(i, x(i,j)) =e= 1; '
48 ' '
49 '$if not set cmax $set cmax 2 '
50 'set cut /c0*c%cmax%/; '
51 'parameter '
52 ' acut(cut,ii,jj) cut constraint matrix '
53 ' rhscut(cut) cut constraint rhs; '
54 ' '
55 'equation sscut(cut) sub_tour elimination cut; '
56 'sscut(cut).. sum((i,j), Acut(cut,i,j)*x(i,j)) =l= RHScut(cut); '
57 ' '
58 'set cc(cut) previous cuts; cc(cut) = no; '
59 '$if set cutdata execute_load ''%cutdata%'', cc, Acut, RHScut; '
60 ' '
61 'Acut(cut,i,j)$(not cc(cut)) = eps; '
62 'RHScut(cut)$(not cc(cut)) = card(ii); '
63 ' '
64 'model assign /all/; '
65 ' '
66 'option optcr=0; '};
67 model = sprintf('%s\n', model{:});
68
69 % number of cuts that can be added to a model instance
70 cutsPerRound = 10;
71 % current cut
72 curCut = 0;
73 % cut limit for current model instance (cmax = curCut + cutsPerRound)
74 cMax = 0;
75
76 % database used to collect all generated cuts
77 cutData = ws.addDatabase();
78 cc = cutData.addSet('cc', 1, '');
79 acut = cutData.addParameter('acut', 3, '');
80 rhscut = cutData.addParameter('rhscut', 1, '');
81
82 % list of cities (i1, i2, i3, ...)
83 n = {};
84 subTour = {};
85
86 while true
87 % create a new model instance when the cut limit is reached
88 if curCut >= cMax
89 fprintf(',');
90 cMax = curCut + cutsPerRound;
91 cutData.export();
92
93 % create the checkpoint
94 tspJob = ws.addJobFromString(model);
95 cp = ws.addCheckpoint();
96 opt = ws.addOptions();
97 opt.defines('nrcities', '20');
98 opt.defines('cmax', int2str(cMax - 1));
99 opt.defines('cutdata', cutData.name);
100
101 % read input data from 'tsp.gdx'
102 opt.defines('tspdata', ['"', fullfile(ws.systemDirectory, 'apifiles', 'Data', 'tsp.gdx'), '"']);
103 tspJob.run(opt, cp);
104
105 % fill the n list only once
106 if numel(n) == 0
107 for i = tspJob.outDB.getSet('i').records
108 n{end+1} = i{1}.key(1);
109 end
110 end
111
112 % instantiate the model instance with modifiers miAcut and miRhscut
113 mi = cp.addModelInstance();
114 miAcut = mi.syncDB.addParameter('acut', 3, '');
115 miRhscut = mi.syncDB.addParameter('rhscut', 1, '');
116 modifiers = {gams.control.Modifier(miAcut), gams.control.Modifier(miRhscut)};
117 mi.instantiate('assign use mip min z', modifiers);
118 else
119 fprintf('.');
120 end
121
122 % solve model instance using update type accumulate and clear acut and rhscut afterwards
123 mi.solve(gams.control.globals.SymbolUpdateType.ACCUMULATE);
124 mi.syncDB.getParameter('acut').clear();
125 mi.syncDB.getParameter('rhscut').clear();
126
127 % collect graph information from the solution
128 graph = containers.Map();
129 notVisited = n;
130 for i = n
131 for j = n
132 if mi.syncDB.getVariable('x').findRecord({i{1},j{1}}).level > 0.5
133 graph(i{1}) = j{1};
134 end
135 end
136 end
137
138 % find all subtours and add the required cuts by modifying acut and rhscut
139 while numel(notVisited) ~= 0
140 ii = notVisited{1};
141 subTour = {ii};
142 while ~strcmp(graph(ii), notVisited{1})
143 ii = graph(ii);
144 subTour{end+1} = ii;
145 end
146
147 for i = numel(notVisited):-1:1
148 for j = 1:numel(subTour)
149 if strcmp(notVisited{i}, subTour{j})
150 notVisited(i) = [];
151 break
152 end
153 end
154 end
155
156 % add the cuts to both databases (cutData, mi.SyncDB)
157 for i = subTour
158 for j = subTour
159 keys = {['c', int2str(curCut)], i{1}, j{1}};
160 rec = acut.addRecord(keys);
161 rec.value = 1;
162 rec = miAcut.addRecord(keys);
163 rec.value = 1;
164 end
165 end
166
167 key = ['c', int2str(curCut)];
168 rec = rhscut.addRecord(key);
169 rec.value = numel(subTour) - 0.5;
170 rec = miRhscut.addRecord(key);
171 rec.value = numel(subTour) - 0.5;
172 cc.addRecord(key);
173 curCut = curCut + 1;
174 end
175
176 if numel(subTour) >= numel(n)
177 break
178 end
179 end
180
181 fprintf('\nz=%g\n', mi.syncDB.getVariable('z').record.level);
182 fprintf('sub_tour: ');
183 for i = subTour
184 fprintf('%s -> ', i{1});
185 end
186 fprintf('%s\n', subTour{1});
187end