Description
The model writes an AWK script on the fly to process the input file format defined by the maintainers of the TSPLib. More input instances are available from https://github.com/rhgrant10/tsplib95/tree/master/archives/problems Keywords: mixed integer linear programming, traveling salesman problem, iterative subtour elimination, TSPLib, awk script
Small Model of Type : MIP
Category : GAMS Model library
Main file : awktsp.gms includes : p43.inc
$title Traveling Salesman Problem Instance prepared with AWK (AWKTSP,SEQ=298)
$onText
The model writes an AWK script on the fly to process the input file
format defined by the maintainers of the TSPLib. More input instances
are available from
https://github.com/rhgrant10/tsplib95/tree/master/archives/problems
Keywords: mixed integer linear programming, traveling salesman problem,
iterative subtour elimination, TSPLib, awk script
$offText
$set fn p43.inc
$if not exist %fn% $abort %fn% ist not present
$echoN "$setGlobal n " > %gams.scrdir%n.%gams.scrext%
$call grep DIMENSION %fn% | cut -d: -f2 >> "%gams.scrdir%n.%gams.scrext%"
$include %gams.scrdir%n.%gams.scrext%
$onEcho > %gams.scrdir%x.%gams.scrext%
BEGIN { i=1 }
/^EDGE_WEIGHT_TYPE/ { if ( $2 != "EXPLICIT" ) error("Wrong type") }
/^EDGE_WEIGHT_FORMAT/ { if ( $2 != "FULL_MATRIX" ) error("Wrong format") }
/^EDGE_WEIGHT_SECTION/ { readM = 1; next }
/^EOF/ { exit }
readM == 1 { for (k=1; k <= NF; k++) print "i" i,"i" j+k,$k;
if (j+NF < %n%) j += NF; else { i++; j=0 } }
function error(s) { print("--- " s ". Exiting."); exit; }
$offEcho
$call awk -f "%gams.scrdir%x.%gams.scrext%" %fn% > "%gams.scrdir%data.%gams.scrext%"
Set
ii 'cities' / i1*i%n% /
i(ii) 'subset of cities to fit GAMS demo model size' / i1*i7 /;
Alias (ii,jj), (i,j,k);
Parameter c(ii,jj) /
$onDelim
$include %gams.scrdir%data.%gams.scrext%
$offDelim
/;
Variable
x(ii,jj) 'decision variables - leg of trip'
z 'objective variable';
Binary Variable x;
Equation
objective 'total cost'
rowsum(ii) 'leave each city only once'
colsum(jj) 'arrive at each city only once';
* The assignment problem is a relaxation of the TSP
objective.. z =e= sum((i,j), c(i,j)*x(i,j));
rowsum(i).. sum(j, x(i,j)) =e= 1;
colsum(j).. sum(i, x(i,j)) =e= 1;
* exclude diagonal
x.fx(ii,ii) = 0;
Set ij(ii,jj) 'exclude first row and column';
ij(ii,jj) = ord(ii) > 1 and ord(jj) > 1;
option optCr = 0, optCa = 0.99;
Model assign / objective, rowsum, colsum /;
solve assign using mip minimizing z;
* find and display tours
Set t 'tours' / t1*t17 /;
abort$(card(t) < card(i)) 'Set t is possibly too small';
Set
tour(i,j,t) 'subtours'
from(i) 'contains always one element: the from city'
next(j) 'contains always one element: the to city'
visited(i) 'flag whether a city is already visited'
tt(t) 'contains always one element: the current subtour';
Alias (i,ix);
$eolCom //
* initialize
from(i)$(ord(i) = 1) = yes; // turn first element on
tt(t)$(ord(t) = 1) = yes; // turn first element on
* Subtour elimination by adding cuts
Set cc / c1*c1000 /;
Alias (cc,ccc);
Set
curcut(cc) 'current cut always one element'
allcuts(cc) 'total cuts';
Parameter
cutcoeff(cc,i,j)
rhs(cc)
nosubtours 'number of subtours';
Equation cut(cc) 'dynamic cuts';
cut(allcuts).. sum((i,j), cutcoeff(allcuts,i,j)*x(i,j)) =l= rhs(allcuts);
Model tspcut / objective, rowsum, colsum, cut /;
curcut(cc)$(ord(cc) = 1) = yes;
Scalar ok;
loop(ccc,
* initialize
from(i) = no;
tt(t) = no;
from(i)$(ord(i) = 1) = yes; // turn first element on
tt(t)$(ord(t) = 1) = yes; // turn first element on
tour(i,j,t) = no;
visited(i) = no;
loop(i,
next(j) = no; // find city visited after city 'from'
loop((from,j),next(j)$(x.l(from,j) > 0.5) = yes);
// check x.l(from,j) = 1 would be dangerous
tour(from,next,tt) = yes; // store in table
visited(from) = yes; // mark city 'from' as visited
from(j) = next(j);
if(sum(visited(next),1) > 0, // if already visited...
tt(t) = tt(t-1);
loop(ix$(not visited(ix)), // find starting point of new subtour
from(j) = no; // make sure we only have one element turned on
from(ix) = yes;
);
);
);
display tour;
nosubtours = sum(t, max(0,smax(tour(i,j,t),1)));
display nosubtours;
break$(nosubtours = 1); // done: no subtours
// else: introduce cut
loop(t$(ord(t) <= nosubtours),
rhs(curcut) = -1;
loop(tour(i,j,t),
cutcoeff(curcut,i,j)$(x.l(i,j) > 0.5) = 1;
* not needed due to nature of assignment constraints
* cutcoeff(curcut,i,j)$(x.l(i,j) < 0.5) = -1;
rhs(curcut) = rhs(curcut) + 1;
);
allcuts(curcut) = yes; // include this cut in set
curcut(cc) = curcut(cc-1);
);
solve tspcut using mip minimizing z;
tspcut.solPrint = %solPrint.quiet%;
tspcut.limRow = 0;
tspcut.limCol = 0;
);
display x.l;
abort$(nosubtours <> 1) "Too many cuts needed";