function [opt_rte,opt_brk,min_dist] = mtsp_ga(xy,dmat,salesmen,...
pop_size,num_iter,singles,show_plots,show_waitbar)
% MTSP_GA Multiple Traveling Salesman Problem (M-TSP) Genetic Algorithm (GA)
% Finds a (near) optimal solution to the M-TSP by setting up a GA to search
% for the shortest route (least distance needed for the salesmen to travel
% to each city exactly once and return to their starting locations)
%
% Input:
% XY (float) is an Nx2 matrix of city locations, where N is the number of cities
% DMAT (float) is an NxN matrix of city-to-city distances or costs
% SALESMEN (scalar integer) is the number of salesmen to visit the cities
% POP_SIZE (scalar integer) is the size of the population (should be divisible by 8)
% NUM_ITER (scalar integer) is the number of desired iterations for the algorithm to run
% SINGLES (scalar logical) is a flag that, if zero, forces the salesmen to
% visit at least 2 cities
% SHOW_PLOTS (scalar logical) is a flag that, if non-zero, generates a couple plots
% SHOW_WAITBAR (scalar logical) is a flag that, if non-zero, displays a waitbar
%
% Output:
% OPT_RTE (integer array) is the best route found by the algorithm
% OPT_BRK (integer array) is the list of route break points (these specify the indices
% into the route used to obtain the individual salesman routes)
% MIN_DIST (scalar float) is the total distance traveled by the salesmen
%
% Route/Breakpoint Details:
% If there are 10 cities and 3 salesmen, a possible route/break
% combination might be: rte = [5 6 9 1 4 2 8 10 3 7], brks = [3 7]
% Taken together, these represent the solution [5 6 9][1 4 2 8][10 3 7],
% which designates the routes for the 3 salesmen as follows:
% . Salesman 1 travels from city 5 to 6 to 9 and back to 5
% . Salesman 2 travels from city 1 to 4 to 2 to 8 and back to 1
% . Salesman 3 travels from city 10 to 3 to 7 and back to 10
%
% Example:
% n = 35; dims = 2;
% xy = 10*rand(n,dims);
% salesmen = 5;
% pop_size = 80;
% num_iter = 5e3;
% singles = 0;
% show_plots = 1;
% show_waitbar = 1;
% a = reshape(xy,1,n,dims);
% b = reshape(xy,n,1,dims);
% dmat = sqrt(sum((a(ones(n,1),:,:)-b(:,ones(n,1),:)).^2,3));
% [opt_rte,opt_brk,min_dist] = mtsp_ga(xy,dmat,salesmen,pop_size,...
% num_iter,singles,show_plots,show_waitbar);
%
% Author: Joseph Kirk
% Email: jdkirk630 at gmail dot com
% Release: 1.0
% Release Date: 3/4/08
if ~nargin % Initialize Example Inputs
n = 50; dims = 2;
xy = 10*rand(n,dims);
salesmen = 7;
pop_size = 80;
num_iter = 1e4;
singles = 0;
show_plots = 1;
show_waitbar = 1;
try % Compute the Distance Matrix
dmat = distmat(xy); % File Exchange Contribution #15145
catch
a = reshape(xy,1,n,dims);
b = reshape(xy,n,1,dims);
dmat = sqrt(sum((a(ones(n,1),:,:)-b(:,ones(n,1),:)).^2,3));
end
else % Process the Given Inputs
n = size(xy,1);
[nr,nc] = size(dmat);
if nr ~= n || nc ~= n
error('Invalid inputs XY and/or DMAT');
end
end
% Verify Inputs
salesmen = max(1,min(n,round(real(salesmen(1)))));
if ~singles
salesmen = min(floor(n/2),salesmen);
end
pop_size = max(8,8*ceil(pop_size(1)/8));
num_iter = max(2,round(real(num_iter(1))));
singles = logical(singles(1));
show_plots = logical(show_plots(1));
show_waitbar = logical(show_waitbar(1));
% Plot the Cities and the Distance Matrix
if show_plots
hfig = figure('Name','MTSPGA','Numbertitle','off');
subplot(2,2,1);
plot(xy(:,1),xy(:,2),'.');
for k = 1:n
text(xy(k,1),xy(k,2),[' ' num2str(k)],'FontWeight','b');
end
title('City Locations')
subplot(2,2,2);
imagesc(dmat);
colormap(flipud(gray));
title('Distance Matrix');
end
% Initializations for Route Break Point Selection
num_brks = salesmen-1;
addto = ones(1,n-2*num_brks-1);
for k = 2:num_brks
addto = cumsum(addto);
end
cum_prob = cumsum(addto)/sum(addto);
% Initialize the Populations
pop_rte = zeros(pop_size,n); % population of routes
pop_brk = zeros(pop_size,num_brks); % population of breaks
for k = 1:pop_size
pop_rte(k,:) = randperm(n);
pop_brk(k,:) = randbreaks();
end
tmp_pop_rte = zeros(8,n);
tmp_pop_brk = zeros(8,num_brks);
new_pop_rte = zeros(pop_size,n);
new_pop_brk = zeros(pop_size,num_brks);
% Select the Colors for the Plotted Routes
clr = [1 0 0; 0 0 1; 0.67 0 1; 0 1 0; 1 0.5 0];
if salesmen > 5
clr = hsv(salesmen);
end
% Run the GA
global_min = Inf;
total_dist = zeros(1,pop_size);
dist_history = zeros(1,num_iter);
if show_plots
pfig = figure('Name','Current Best Solution','Numbertitle','off');
end
if show_waitbar
wb = waitbar(0,'Searching ... Please Wait');
end
for iter = 1:num_iter
% Evaluate Members of the Population
for p = 1:pop_size
d = 0;
p_rte = pop_rte(p,:);
p_brk = pop_brk(p,:);
rng = [[1 p_brk+1];[p_brk n]]';
for m = 1:salesmen
d = d + dmat(p_rte(rng(m,2)),p_rte(rng(m,1)));
for k = rng(m,1):rng(m,2)-1
d = d + dmat(p_rte(k),p_rte(k+1));
end
end
total_dist(p) = d;
end
% Find the Best Route in the Population
[min_dist,index] = min(total_dist);
dist_history(iter) = min_dist;
if min_dist < global_min
global_min = min_dist;
opt_rte = pop_rte(index,:);
opt_brk = pop_brk(index,:);
rng = [[1 opt_brk+1];[opt_brk n]]';
% Plot the Best Route
if show_plots
figure(pfig); hold off;
for m = 1:salesmen
rte = [opt_rte(rng(m,1):rng(m,2)) opt_rte(rng(m,1))];
plot(xy(rte,1),xy(rte,2),'.-','Color',clr(m,:));
title(sprintf('Total Distance = %1.4f',min_dist));
hold on;
end
end
end
% Genetic Algorithm Operators
rand_grouping = randperm(pop_size);
for p = 8:8:pop_size
rtes = pop_rte(rand_grouping(p-7:p),:);
brks = pop_brk(rand_grouping(p-7:p),:);
dists = total_dist(rand_grouping(p-7:p));
[ignore,idx] = min(dists);
best_of_8_rte = rtes(idx,:);
best_of_8_brks = brks(idx,:);
rte_ins_pts = sort(ceil(n*rand(1,2)));
I = rte_ins_pts(1);
J = rte_ins_pts(2);
for k = 1:8 % Generate New Solutions
tmp_pop_rte(k,:) = best_of_8_rte;
tmp_pop_brk(k,:) = best_of_8_brks;
switch k
case 2 % Flip
tmp_pop_rte(k,I:J) = fliplr(tmp_pop_rte(k,I:J));
case 3 % Swap
tmp_pop_rte(k,[I J]) = tmp_pop_rte(k,[J I]);
case 4 % Slide
tmp_pop_rte(k,I:J) = tmp_pop_rte(k,[I+1:J I]);
case 5 % Modify Breaks
tmp_pop_brk(k,:) = randbreaks();
case 6 % Flip, Modify Breaks
tmp_pop_rte(k,I:J) = fliplr(tmp_pop_rte(k,I:J));
tmp_pop_brk(k,:) = randbreaks();
case 7 % Swap, Modify Breaks
tmp_pop_rte(k,[I J]) = tmp_pop_rte(k,[J I]);
tmp_pop_brk(k,:) = randbreaks();
case 8 % Slide, Modify Breaks
tmp_pop_rte(k,I:J) = tmp_pop_rte(k,[I+1:J I]);
tmp_pop_brk(k,:) = randbreaks();
otherwise % Do Nothing
end
end
new_pop_rte(p-7:p,:) = tmp_pop_rte;
new_pop_brk(p-7:p,:) = tmp_pop_brk;
end
pop_rte = new_pop_rte;
pop_brk = new_pop_brk;
if show_waitbar, waitbar(iter/num_iter,wb); end
end
if show_waitbar, close(wb); end
% Plot the Results
if show_plots
rng = [[1 opt_brk+1];[opt_brk n]]';
figure(hfig); subplot(2,2,3); hold off;
for m = 1:salesmen
rte = [opt_rte(rng(m,1):rng(m,2)) opt_rte(rng(m,1))];
plot(xy(rte,1),xy(rte,2),'.-','Color',clr(m,:));
title(sprintf('Total Distance = %1.4f',min_dist));
hold on;
end
subplot(2,2,4);
plot(dist_history,'r','LineWidth',2);
title('Best Solution History');
set(gca,'XLim',[1 num_iter],'YLim',[0 1.1*max(dist_history)]);
end
% Generate Random Set of Break Points
function breaks = randbreaks()
if singles % No Constraints on Breaks
tmp_brks = randperm(n-1);
breaks = sort(tmp_brks(1:num_brks));
else % Force Breaks to be At Least Two Apart
num_adjust = find(rand < cum_prob,1)-1;
spaces = ceil(num_brks*rand(1,num_adjust));
adjust = zeros(1,num_brks);
for kk = 1:num_brks
adjust(kk) = sum(spaces == kk);
end
breaks = 2*(1:num_brks) + cumsum(adjust);
end
end
end