Contributor: PIERRE TOURIGNY
program citytour;
{modified anneal.pas in nrpas13.zip from the book Numerical Recipes}
{95-06-23 by Pierre Tourigny, pierre@panpan.synapse.net}
{solves the traveling salesman's problem by simulated annealing}
uses graph;
const
title = 'Tour of 48 US Capitals';
maxcity = 99; {maximum city index}
maxstep = 100; {maximum number of temperature steps}
tfactor = 0.9; {temperature step is 90% of previous step}
radius = 3956.67; {average radius of the earth in miles}
bgidir = 'c:\bp\bgi'; {directory of bgi drivers; change at need}
type
city = record name: string[80]; lat,long: real; gx,gy: integer end;
tdistances = array[0..maxcity,0..maxcity] of real;
var
cities: array[0..maxcity] of city;
tour: array[0..maxcity] of integer;
path,delta,temperature,lowerbound: real;
count,success,maxpath,maxsuccess,offcut,offlast,offsplice,
first,last,cutafter,cutbefore,spliceafter,splicebefore,
mx,my,grdriver,grmode: integer;
distance: ^tdistances;
procedure usage;
begin
writeln;
writeln('Usage: citytour ');
writeln;
writeln('Example: citytour us_cptl.dat us_cptl.sol');
writeln;
writeln('Data File Format:');
writeln(' at most 100 cities, one city per line;');
writeln(' line format: ,,'+
',');
writeln(' latitude and longitude: degrees, minutes and hemisphere');
writeln(' Example: Salt Lake City,40 45 n,111 58 w,Utah,US');
halt;
end;
function dmc2r(degree,minute: real; hemi: string): real;
{degrees, minutes, hemisphere to radians}
begin
if hemi[1] in ['s','S','w','W'] then
dmc2r := -(degree + minute/60.0)*pi/180.0
else dmc2r := (degree + minute/60.0)*pi/180.0;
end;
function gcd(i1,i2: integer): real;
{approximate great-circle distance in miles}
var
dlat,dlong: real;
begin
with cities[i1] do begin
dlat := abs(lat-cities[i2].lat);
if dlat > pi then dlat := 2*pi-dlat;
dlong := abs(long-cities[i2].long);
if dlong > pi then dlong := 2*pi-dlong;
gcd := radius*sqrt(sqr(dlat)+
abs(sqr(dlong)*cos(lat)*cos(cities[i2].lat)));
end;
end;
function split(var s : string; d : string) : string;
var
i,minpos,dpos : byte;
maxs : byte absolute s; maxd : byte absolute d;
begin
minpos := succ(maxs);
for i := 1 to maxd do begin
dpos := pos(d[i],s);
if (dpos > 0) and (dpos < minpos) then minpos := dpos;
end;
split := copy(s,1,pred(minpos));
s := copy(s,minpos+1,255);
end;
function fval(s: string): integer;
var i,code: integer;
begin val(s,i,code); fval := i end;
procedure findlow; {find lower bound for optimal solution}
{note: the lower bound is not the optimal length; it only
means that the best tour cannot be shorter}
var
i,j: integer; mind1,mind2: real;
begin
lowerbound := 0.0;
for i := 0 to count-1 do begin
mind1 := 1.0e38; mind2 := mind1;
for j := 0 to count-1 do begin
if i = j then continue;
if distance^[i,j] < mind1 then begin
mind2 := mind1;
mind1 := distance^[i,j];
end
else if distance^[i,j] < mind2
then mind2 := distance^[i,j];
end;
lowerbound := lowerbound+mind1+mind2;
end;
lowerbound := lowerbound/2;
end;
procedure drawtour;
var
i,j: integer; s,sn: string;
begin
cleardevice;
outtext(title);
setcolor(white);
with cities[0] do moveto(gx,gy);
j := 0;
for i := 0 to count-1 do begin
j := tour[j];
with cities[j] do lineto(gx,gy);
end;
setcolor(red); setfillstyle(solidfill,red);
j := 0;
for i := 0 to count-1 do begin
with cities[j] do fillellipse(gx,gy,2,2);
j := tour[j];
end;
setcolor(white);
str(path:8:0,sn); s := 'Path Length:'+sn;
str(temperature:8:0,sn); s := s+' Temperature:'+sn;
str(success:6,sn); s := s+' Successful Moves:'+sn;
outtextxy(10,my-15,s);
end;
procedure init;
var
i,j: integer; data: text; s: string;
gxscale,gxmax,gyscale,gymax: real;
begin
if paramcount <> 2 then usage;
randomize;
fillchar(cities,sizeof(cities),0);
fillchar(tour,sizeof(tour),0);
assign(data,paramstr(1));
{$I-} reset(data); {$I+}
if ioresult <> 0 then begin
writeln('Data file not found'); halt; end;
grdriver := detect;
initgraph(grdriver,grmode,bgidir);
mx := getmaxx; my := getmaxy;
gyscale := my*180.0/(25.0*pi); {map is 25 degrees tall}
gxscale := mx*180.0/(60.0*pi); {map is 60 degrees wide}
gymax := 50.0*pi/180.0; gxmax := 125.0*pi/180.0; {graph point 0,0}
count := 0;
while not eof(data) do begin
readln(data,s);
with cities[count] do begin
name := split(s,',');
lat := dmc2r(fval(split(s,' ')),fval(split(s,' ')),split(s,','));
long := dmc2r(fval(split(s,' ')),fval(split(s,' ')),split(s,','));
gx := trunc((gxmax+long)*gxscale);
gy := trunc((gymax-lat)*gyscale);
end;
inc(count);
end;
close(data);
if (count = 0) or (count > 100) then usage;
for i := 0 to count-1 do tour[i] := (i+1) mod count; {following city}
maxpath := 100*count; maxsuccess := 10*count; path := 0.0;
temperature := 1500.0 {miles};
new(distance); fillchar(distance^,sizeof(distance^),0);
for i := 0 to count-2 do for j := i+1 to count-1 do begin
distance^[i,j] := gcd(i,j); distance^[j,i] := distance^[i,j];
end;
findlow;
for i := 0 to (count-1) do path := path+distance^[i,(i+1) mod count];
drawtour;
end;
procedure findedges;
begin
offcut := random(count); {offset from 0}
offlast := random(count-2); {offset from first}
offsplice := random(count-offlast-2); {offset from cutbefore}
cutafter := 0;
while offcut > 0 do begin
cutafter := tour[cutafter];
dec(offcut);
end;
first := tour[cutafter]; {first city of segment}
last := first; {last city of segment}
while offlast > 0 do begin
last := tour[last];
dec(offlast);
end;
cutbefore := tour[last];
end;
procedure revcost; {difference in path length if segment reversed}
begin
delta := -distance^[cutafter,first]-distance^[last,cutbefore]
+distance^[cutafter,last]+distance^[first,cutbefore];
end;
procedure reverse;
var
i,next,after: integer;
begin
tour[cutafter] := last;
i := first; after := cutbefore;
repeat
next := tour[i];
tour[i] := after;
after := i;
i := next;
until i = cutbefore;
end;
procedure transcost; {difference due to transporting segment}
begin
delta :=
-distance^[cutafter,first]-distance^[last,cutbefore]
-distance^[spliceafter,splicebefore]+distance^[cutafter,cutbefore]
+distance^[spliceafter,first]+distance^[last,splicebefore];
end;
procedure transport;
begin
tour[cutafter] := cutbefore;
tour[spliceafter] := first;
tour[last] := splicebefore;
end;
function metropolis: boolean;
begin
if delta < 0 then metropolis := true
else metropolis := random < exp(-delta/temperature);
end;
procedure anneal;
var
i,j,k: integer;
begin
for j := 1 to maxstep do begin
success := 0;
for k := 1 to maxpath do begin
findedges;
if random(2) = 1 then begin {reverse segment}
revcost;
if metropolis then begin
inc(success); path := path+delta; reverse;
end
end
else begin {transport segment somewhere else}
spliceafter := cutbefore;
while offsplice > 0 do begin
spliceafter := tour[spliceafter];
dec(offsplice);
end;
splicebefore := tour[spliceafter];
transcost;
if metropolis then begin
inc(success); path := path+delta; transport;
end;
end;
if success >= maxsuccess then break;
end;
drawtour;
temperature := temperature*tfactor;
if success = 0 then break;
end;
end;
procedure report;
var
i: integer; d,p: real; solution: text;
begin
assign(solution,paramstr(2));
rewrite(solution);
i := 0; p := 0.0;
writeln('City':20,'Distance (miles)':20,'Cumulative':20);
writeln(solution,'City':20,'Distance (miles)':20,'Cumulative':20);
writeln(solution);
repeat
d := distance^[i,tour[i]]; p := p + d;
writeln(cities[i].name:20,d:20:0,p:20:0);
writeln(solution,cities[i].name:20,d:20:0,p:20:0);
i := tour[i];
until i = 0;
writeln('Lower bound on optimal solution: ',lowerbound:8:0);
writeln(solution,'Lower bound on optimal solution: ',lowerbound:8:0);
close(solution);
end;
begin
init;
anneal;
closegraph;
report;
dispose(distance);
end.