├── .gitattributes ├── .gitignore ├── GA-Mutation-Descriptions.pdf ├── LICENSE ├── README.md ├── figstatus.m ├── get_config.m ├── mtsp_ga.m ├── mtsp_ga_combo.m ├── mtsp_ga_max.m ├── mtsp_ga_minmax.m ├── mtsp_ga_minsum.m ├── mtsp_ga_turbo.m ├── mtsp_rs.m ├── mtspf_ga.m ├── mtspf_ga_bases.m ├── mtspf_ga_minmax.m ├── mtspo_ga.m ├── mtspof_ga.m ├── mtspof_ga_bases.m ├── mtspofs_ga.m ├── mtspofs_ga_bases.m ├── mtspofs_ga_depots.m ├── mtspv_ga.m ├── mtspvf_ga.m ├── mtspvo_ga.m ├── mtspvof_ga.m ├── mtspvofs_ga.m ├── seed_tsp.m ├── seed_tsp_turbo.m ├── tsp_fn.m ├── tsp_ga.m ├── tsp_ga_clusters.m ├── tsp_ga_co.m ├── tsp_ga_hybrid.m ├── tsp_ga_max.m ├── tsp_ga_turbo.m ├── tsp_nn.m ├── tsp_rs.m ├── tspo_ga.m ├── tspo_ga_turbo.m ├── tspo_nn.m ├── tspof_ga.m └── tspofs_ga.m /.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # Always use LF by default 3 | # 4 | * text eol=lf 5 | 6 | # 7 | # These files are binary and should be left untouched 8 | # 9 | *.pdf binary 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ############## 2 | ### MATLAB ### 3 | ############## 4 | 5 | # Autosave extensions 6 | *.asv 7 | *.m~ 8 | 9 | # MAT Files 10 | *.mat 11 | 12 | # FIG Files 13 | *.fig 14 | 15 | # P Files 16 | *.p 17 | 18 | ############### 19 | ### Windows ### 20 | ############### 21 | 22 | # Windows image file caches 23 | Thumbs.db 24 | 25 | # Folder config file 26 | Desktop.ini 27 | 28 | # Windows shortcuts 29 | *.lnk 30 | -------------------------------------------------------------------------------- /GA-Mutation-Descriptions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LenKerr/matlab-tsp-ga/3be93cb3130526fe91d4ee7f6190877318db8bfe/GA-Mutation-Descriptions.pdf -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Joseph Kirk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MATLAB Traveling Salesman Problem (TSP) Genetic Algorithm Toolbox 2 | 3 | ## Purpose 4 | This toolbox contains MATLAB functions to solve the Traveling Salesman Problem (TSP), Multiple Traveling Salesman Problem (M-TSP) and other variations using a custom Genetic Algorithm (GA) 5 | 6 | [![View Traveling Salesman Problem (TSP) Genetic Algorithm Toolbox on the MATLAB File Exchange](https://www.mathworks.com/matlabcentral/images/matlab-file-exchange.svg)](https://www.mathworks.com/matlabcentral/fileexchange/75525-traveling-salesman-problem-tsp-genetic-algorithm-toolbox) 7 | 8 | ## TSP / M-TSP and Variations 9 | Functions in this toolbox are listed below with short descriptions 10 | 11 | * `tsp_ga` - Solves the classic Traveling Salesman Problem (TSP) 12 | * `tspo_ga` - Solves an open variation of the TSP with no start/end constraints 13 | * `tspof_ga` - Solves an open variation of the TSP with fixed start & end constraints 14 | * `tspofs_ga` - Solves an open variation of the TSP with only a fixed start constraint 15 | * `tsp_ga_max` - Solves a variation of the TSP that *maximizes* the total distance 16 | * `tsp_nn` - Nearest Neighbor (NN) solution to the TSP 17 | * `tspo_nn` - Nearest Neighbor (NN) solution to the open variation of the TSP (no start/end constraints) 18 | * `tsp_fn` - Farthest Neighbor (FN) solution to the TSP 19 | * `tsp_rs` - Random Search (RS) solution to the TSP (provides interesting performance comparisons) 20 | * `tsp_ga_co` - Solves the TSP with a variation of the GA that uses a cross-over operator 21 | * `tsp_ga_hybrid` - Solves the TSP with a variation of the GA that uses a hybrid set of operators 22 | * `tsp_ga_clusters` - Variation of the TSP to find the shortest tour through clusters of points 23 | * `tsp_ga_turbo` - Solves the TSP with a variation of the GA that increases the mutation rate on the best route 24 | * `tspo_ga_turbo` - Solves the Open TSP with a variation of the GA that increases the mutation rate on the best route 25 | * `seed_tsp` - Script to compare `tsp_ga` performance with an approach that seeds the algorithm with the NN solution 26 | * `seed_tsp_turbo` - Script to compare `tsp_ga_turbo` performance with an approach that seeds the algorithm with the NN solution 27 | * `mtsp_ga` - Solves the classic Multiple Traveling Salesmen Problem (M-TSP) 28 | * `mtsp_rs` - Random Search (RS) solution to the M-TSP (provides interesting performance comparisons) 29 | * `mtspf_ga` - Solves a variation of the M-TSP where all salesmen start and end at the first city 30 | * `mtspo_ga` - Solves an open variation of the M-TSP with no start/end constraints 31 | * `mtspof_ga` - Solves an open variation of the M-TSP where all salesmen start at the first city and end at the last 32 | * `mtspofs_ga` - Solves an open variation of the M-TSP where all salesmen start at the first city 33 | * `mtspf_ga_bases` - Solves a variation of the M-TSP where all salesmen are assigned starting cities (bases) 34 | * `mtspof_ga_bases` - Solves an open variation of the M-TSP where all salesmen are assigned separate starting and ending cities (bases) 35 | * `mtspofs_ga_bases` - Solves an open variation of the M-TSP where all salesmen are assigned separate starting cities (bases) 36 | * `mtspofs_ga_depots` - Solves an open variation of the M-TSP where all salesmen are assigned separate starting cities and must travel to a specified end city (depot) that is not assigned in advance 37 | * `mtsp_ga_max` - Solves a variation of the M-TSP that *maximizes* the total distance 38 | * `mtsp_ga_minsum` - Same as `mtsp_ga` (just named such that objective function is explicit) 39 | * `mtsp_ga_minmax` - Solves the classic M-TSP except the objective function minimizes the maximum tour (which tends to make tour lengths more equitable) 40 | * `mtsp_ga_combo` - Solves the classic M-TSP except the objective function is a combination of *minmax* and *minsum* 41 | * `mtspf_ga_minmax` - Solves the `mtspf_ga` variation, but with the *minmax* objective function 42 | * `mtsp_ga_turbo` - Solves the M-TSP with a variation of the GA that increases the mutation rate on the best route 43 | * `mtspv_ga` - Solves a variation of `mtsp_ga` that also optimizes the number of salesmen 44 | * `mtspvf_ga` - Solves a variation of `mtspf_ga` that also optimizes the number of salesmen 45 | * `mtspvo_ga` - Solves a variation of `mtspo_ga` that also optimizes the number of salesmen 46 | * `mtspvof_ga` - Solves a variation of `mtspof_ga` that also optimizes the number of salesmen 47 | * `mtspvofs_ga` - Solves a variation of `mtspofs_ga` that also optimizes the number of salesmen 48 | * `figstatus` - Helper function to display a progress status bar along the bottom of the figure with the option to stop early 49 | * `get_config` - Helper function to support a variety of input options (structure or name/value pairs) 50 | 51 | ## Notes about Custom GA 52 | The custom Genetic Algorithm used by most of the functions in this toolbox does not use crossover and mutation operators in the traditional sense, because the crossover operator tends to be a highly destructive operator and rarely improves the best solution. Instead, three different mutation operators (*flip*, *swap*, and *slide*) are used -- see `TSPGA-Mutation-Descriptions.pdf` for more details. 53 | 54 | ## Features 55 | The solvers in this toolbox have several (hopefully useful) features: 56 | 57 | 1. The inputs can either be a structure with zero or more expected fields, or a set of name/value pairs 58 | 2. The output structure from running any of the GA solvers can be passed back as an input into the solver to continue where it left off 59 | 3. If the plot showing the current best solution is displayed, a status bar is shown across the bottom of the figure with an option to exit early 60 | 4. If the waitbar is used, it also utilizes an option that lets the user exit early if desired 61 | 5. The output structures from the solvers contain fields (`plotPoints`, `plotResult`, `plotMatrix`, and `plotHistory`) that hold anonymous functions which can be used as a simple way to view the results (see examples) 62 | 63 | ## Examples 64 | Below are examples demonstrating some of the toolbox features 65 | 66 | Solve the classic TSP with a randomly generated set of city locations and then inject the result structure back into the solver as an input to continue where it left off 67 | 68 | cities = 10*rand(100,2); 69 | tsp = tsp_ga('xy',cities,'numIter',1000); 70 | tsp = tsp_ga(tsp); 71 | 72 | Solve the classic M-TSP without any plots/status and then use the anonymous function fields to show the results 73 | 74 | cities = 10*rand(30,2); 75 | mtsp = mtsp_ga('xy',cities,'showProg',false,'showResult',false); 76 | figure; mtsp.plotPoints(mtsp); title('Points') 77 | figure; mtsp.plotMatrix(mtsp); title('Sorted Distance Matrix') 78 | figure; hold('on'); mtsp.plotResult(mtsp); title('Solution') 79 | figure; mtsp.plotHistory(mtsp); title('Distance History') 80 | 81 | -------------------------------------------------------------------------------- /figstatus.m: -------------------------------------------------------------------------------- 1 | %FIGSTATUS Creates a status bar in a figure to show loop progress 2 | % 3 | % Description: Displays loop progress as a status bar in a figure window 4 | % 5 | % Inputs: 6 | % i - (numeric) current loop index 7 | % n - (numeric) total number of loop iterations 8 | % hStatus - (optional) handle to status object 9 | % hFig - (optional) handle to figure 10 | % 11 | % Outputs: 12 | % hStatus - handle to status object 13 | % isCancelled - scalar logical indicating user has clicked "cancel" button 14 | % 15 | % Usage: 16 | % [hStatus,isCancelled] = figstatus(i,n); 17 | % -or- 18 | % hStatus = figstatus(i,n); 19 | % -or- 20 | % figstatus(i,n,hStatus); 21 | % -or- 22 | % figstatus(i,n,hStatus,hFig); 23 | % 24 | % Example: 25 | % n = 1e3; 26 | % hStatus = figstatus(0,n); 27 | % for i = 1:n 28 | % % Computations go here ... 29 | % figstatus(i,n,hStatus); 30 | % end 31 | % 32 | % Example: 33 | % n = 1e7; 34 | % hStatus = figstatus(0,n); 35 | % for i = 1:n 36 | % % Computations go here ... 37 | % if ~mod(i,ceil(n/100)) 38 | % figstatus(i,n,hStatus); 39 | % end 40 | % end 41 | % 42 | % Example: 43 | % n = 1e7; 44 | % figstatus(0,n); 45 | % for i = 1:n 46 | % % Computations go here ... 47 | % if ~mod(i,ceil(n/100)) 48 | % figstatus(i,n); 49 | % end 50 | % end 51 | % 52 | % Example: 53 | % % Create a button to quit early if desired 54 | % n = 1e3; 55 | % [hStatus,isCancelled] = figstatus(0,n); 56 | % for i = 1:n 57 | % % Computations go here ... 58 | % [hStatus,isCancelled] = figstatus(i,n); 59 | % if isCancelled 60 | % % Normal use in a function would be to break out of the loop, but 61 | % % if this example is run in the Command Window, just throw error 62 | % % break 63 | % error('??? User killed processing'); 64 | % end 65 | % end 66 | % 67 | % See also: progress, uipanel, uicontrol, patch 68 | % 69 | function varargout = figstatus(i,n,hStatus,hFig) 70 | 71 | 72 | % 73 | % Check for between 2 and 4 inputs 74 | % 75 | narginchk(2,4); 76 | 77 | 78 | % 79 | % Set persistent quit trigger 80 | % 81 | persistent IS_CANCELLED; 82 | 83 | 84 | % 85 | % Initialize cancel button trigger 86 | % 87 | if isempty(IS_CANCELLED) 88 | IS_CANCELLED = false; 89 | end 90 | 91 | 92 | % 93 | % Send quit signal if triggered 94 | % 95 | if IS_CANCELLED 96 | IS_CANCELLED = false; 97 | varargout = {[],true}; 98 | return 99 | end 100 | 101 | 102 | % 103 | % Check for status handle 104 | % 105 | if (nargin < 3) || isempty(hStatus) || ~ishandle(hStatus) 106 | 107 | % 108 | % Get current figure if not provided 109 | % 110 | if (nargin < 4) || isempty(hFig) || ~ishandle(hFig) 111 | hFig = gcf; 112 | end 113 | 114 | % 115 | % Look for handle to status 116 | % 117 | hStatus = findobj(hFig,'Tag','_figstatus_'); 118 | if isempty(hStatus) && (i ~= n) 119 | 120 | % 121 | % Create status panel and bar 122 | % 123 | figColor = get(hFig,'Color'); 124 | hStatusPanel = uipanel(hFig, ... 125 | 'Units', 'normalized', ... 126 | 'Position', [0 0 1 0.05], ... 127 | 'BackgroundColor', figColor, ... 128 | 'Tag', '_figstatuspanel_', ... 129 | 'BorderType', 'etchedin'); 130 | hStatusAx = axes( ... 131 | 'Color', 'w', ... 132 | 'Units', 'normalized', ... 133 | 'Position', [0 0 1 1], ... 134 | 'XLim', [0 1], ... 135 | 'YLim', [0 1], ... 136 | 'Visible', 'off', ... 137 | 'Parent', hStatusPanel); 138 | if ~verLessThan('matlab','9.5') % R2018b 139 | set(hStatusAx.Toolbar,'Visible','off'); 140 | % disableDefaultInteractivity(hStatusAx); 141 | end 142 | if (nargout > 1) 143 | set(hStatusAx,'Position',[0 0 0.95 1]); 144 | uicontrol(hStatusPanel, ... 145 | 'Units', 'normalized', ... 146 | 'Position', [0.95 0 0.05 1], ... 147 | 'String', 'X', ... 148 | 'Callback', @trigger_cancel); 149 | end 150 | % cmw = 0.1 * repmat([7 10 10 7],3,1)'; 151 | % patch([0 0 1 1],[0 1 1 0],[0.5 0.5 0.5], ... 152 | % 'FaceVertexCData',cmw, ... 153 | % 'FaceColor','interp', ... 154 | % 'EdgeColor','none', ... 155 | % 'Parent',hStatusAx); 156 | grn = 0.1 * [2 6 2; 8 10 8]; 157 | cmg = grn([1 2 2 1],:); 158 | hStatus = patch([0 0 1 1],[0 1 1 0],[0.5 0.5 0.5], ... 159 | 'FaceVertexCData',cmg, ... 160 | 'FaceColor','interp', ... 161 | 'EdgeColor','none', ... 162 | 'Tag','_figstatus_', ... 163 | 'Parent',hStatusAx); 164 | 165 | end 166 | end 167 | 168 | 169 | % 170 | % Set status bar length 171 | % 172 | frac = (i / n); 173 | set(hStatus,'XData',[0 0 frac frac]); 174 | drawnow(); 175 | 176 | 177 | % 178 | % If complete, cleanup by deleting entire status panel 179 | % 180 | if (i == n) 181 | hStatusPanel = findobj(hFig,'Tag','_figstatuspanel_'); 182 | delete(hStatusPanel); 183 | end 184 | 185 | 186 | % 187 | % Pass output handle if requested 188 | % 189 | if nargout 190 | varargout = {hStatus,IS_CANCELLED}; 191 | end 192 | 193 | 194 | % 195 | % Nested function to trigger quit 196 | % 197 | function trigger_cancel(varargin) 198 | IS_CANCELLED = true; 199 | hStatusPanel = findobj(hFig,'Tag','_figstatuspanel_'); 200 | delete(hStatusPanel); 201 | end 202 | 203 | end 204 | 205 | -------------------------------------------------------------------------------- /get_config.m: -------------------------------------------------------------------------------- 1 | % PARAM_OVERRIDE 2 | % 3 | % Filename: get_config.m 4 | % 5 | % Description: Takes a default parameter structure along with user-defined 6 | % parameters and merges them by overriding the defaults with user values 7 | % 8 | % Date: 04/30/14 9 | % 10 | % Author: 11 | % Joseph Kirk 12 | % jdkirk630@gmail.com 13 | % 14 | % Inputs: 15 | % defaultConfig - structure containing default parameters 16 | % userConfig - structure containing user-defined parameters 17 | % 18 | % Outputs: 19 | % config - structure containing the full merged parameter set 20 | % 21 | % Usage: 22 | % config = get_config(defaultConfig,userConfig); 23 | % 24 | % See also: fieldnames 25 | % 26 | function config = get_config(defaultConfig,userConfig) 27 | 28 | 29 | % 30 | % Initialize the configuration structure as the default 31 | % 32 | config = defaultConfig; 33 | 34 | 35 | % 36 | % Extract the field names of the default configuration structure 37 | % 38 | defaultFields = fieldnames(defaultConfig); 39 | 40 | 41 | % 42 | % Extract the field names of the user configuration structure 43 | % 44 | userFields = fieldnames(userConfig); 45 | nUserFields = length(userFields); 46 | 47 | 48 | % 49 | % Override any default configuration fields with user values 50 | % 51 | for i = 1:nUserFields 52 | userField = userFields{i}; 53 | isField = strcmpi(defaultFields,userField); 54 | if nnz(isField) == 1 55 | thisField = defaultFields{isField}; 56 | config.(thisField) = userConfig.(userField); 57 | end 58 | end 59 | 60 | end 61 | 62 | -------------------------------------------------------------------------------- /mtsp_rs.m: -------------------------------------------------------------------------------- 1 | % MTSP_RS Multiple Traveling Salesmen Problem (M-TSP) Random Search (RS) Algorithm 2 | % The Random Search algorithm generates random solutions and evaluates 3 | % their fitness 4 | % 5 | % Summary: 6 | % 1. Each salesman travels to a unique set of cities and completes the 7 | % route by returning to the city he started from 8 | % 2. Each city is visited by exactly one salesman 9 | % 10 | % Input: 11 | % USERCONFIG (structure) with zero or more of the following fields: 12 | % - XY (float) is an Nx2 matrix of city locations, where N is the number of cities 13 | % - DMAT (float) is an NxN matrix of city-to-city distances or costs 14 | % - NSALESMEN (scalar integer) is the number of salesmen to visit the cities 15 | % - MINTOUR (scalar integer) is the minimum tour length for any of the salesmen 16 | % - POPSIZE (scalar integer) is the size of the population (should be <= N) 17 | % - NUMITER (scalar integer) is the number of desired iterations for the algorithm to run 18 | % - SHOWPROG (scalar logical) shows the GA progress if true 19 | % - SHOWRESULT (scalar logical) shows the GA results if true 20 | % - SHOWWAITBAR (scalar logical) shows a waitbar if true 21 | % 22 | % Input Notes: 23 | % 1. Rather than passing in a structure containing these fields, any/all of 24 | % these inputs can be passed in as parameter/value pairs in any order instead. 25 | % 2. Field/parameter names are case insensitive but must match exactly otherwise. 26 | % 27 | % Output: 28 | % RESULTSTRUCT (structure) with the following fields: 29 | % (in addition to a record of the algorithm configuration) 30 | % - OPTROUTE (integer array) is the best route found by the algorithm 31 | % - OPTBREAK (integer array) is the list of route break points (these specify the indices 32 | % into the route used to obtain the individual salesman routes) 33 | % - MINDIST (scalar float) is the total distance traveled by the salesmen 34 | % 35 | % Route/Breakpoint Details: 36 | % If there are 10 cities and 3 salesmen, a possible route/break 37 | % combination might be: rte = [5 6 9 1 4 2 8 10 3 7], brks = [3 7] 38 | % Taken together, these represent the solution [5 6 9][1 4 2 8][10 3 7], 39 | % which designates the routes for the 3 salesmen as follows: 40 | % . Salesman 1 travels from city 5 to 6 to 9 and back to 5 41 | % . Salesman 2 travels from city 1 to 4 to 2 to 8 and back to 1 42 | % . Salesman 3 travels from city 10 to 3 to 7 and back to 10 43 | % 44 | % Usage: 45 | % mtsp_rs 46 | % -or- 47 | % mtsp_rs(userConfig) 48 | % -or- 49 | % resultStruct = mtsp_rs; 50 | % -or- 51 | % resultStruct = mtsp_rs(userConfig); 52 | % -or- 53 | % [...] = mtsp_rs('Param1',Value1,'Param2',Value2, ...); 54 | % 55 | % Example: 56 | % % Let the function create an example problem to solve 57 | % mtsp_rs; 58 | % 59 | % Example: 60 | % % Request the output structure from the solver 61 | % resultStruct = mtsp_rs; 62 | % 63 | % Example: 64 | % % Pass a random set of user-defined XY points to the solver 65 | % userConfig = struct('xy',10*rand(35,2)); 66 | % resultStruct = mtsp_rs(userConfig); 67 | % 68 | % Example: 69 | % % Pass a more interesting set of XY points to the solver 70 | % n = 50; 71 | % phi = (sqrt(5)-1)/2; 72 | % theta = 2*pi*phi*(0:n-1); 73 | % rho = (1:n).^phi; 74 | % [x,y] = pol2cart(theta(:),rho(:)); 75 | % xy = 10*([x y]-min([x;y]))/(max([x;y])-min([x;y])); 76 | % userConfig = struct('xy',xy); 77 | % resultStruct = mtsp_rs(userConfig); 78 | % 79 | % Example: 80 | % % Pass a random set of 3D (XYZ) points to the solver 81 | % xyz = 10*rand(35,3); 82 | % userConfig = struct('xy',xyz); 83 | % resultStruct = mtsp_rs(userConfig); 84 | % 85 | % Example: 86 | % % Turn off the plots but show a waitbar 87 | % userConfig = struct('showProg',false,'showResult',false,'showWaitbar',true); 88 | % resultStruct = mtsp_rs(userConfig); 89 | % 90 | % See also: mtsp_ga, tsp_ga, tsp_rs 91 | % 92 | % Author: Joseph Kirk 93 | % Email: jdkirk630@gmail.com 94 | % 95 | function varargout = mtsp_rs(varargin) 96 | 97 | 98 | % 99 | % Initialize default configuration 100 | % 101 | defaultConfig.xy = 10*rand(30,2); 102 | defaultConfig.dmat = []; 103 | defaultConfig.nSalesmen = 5; 104 | defaultConfig.minTour = 3; 105 | defaultConfig.popSize = Inf; 106 | defaultConfig.numIter = 5e3; 107 | defaultConfig.showProg = true; 108 | defaultConfig.showStatus = true; 109 | defaultConfig.showResult = true; 110 | defaultConfig.showWaitbar = false; 111 | 112 | 113 | % 114 | % Interpret user configuration inputs 115 | % 116 | if ~nargin 117 | userConfig = struct(); 118 | elseif isstruct(varargin{1}) 119 | userConfig = varargin{1}; 120 | else 121 | try 122 | userConfig = struct(varargin{:}); 123 | catch 124 | error('??? Expected inputs are either a structure or parameter/value pairs'); 125 | end 126 | end 127 | 128 | 129 | % 130 | % Override default configuration with user inputs 131 | % 132 | configStruct = get_config(defaultConfig,userConfig); 133 | 134 | 135 | % 136 | % Extract configuration 137 | % 138 | xy = configStruct.xy; 139 | dmat = configStruct.dmat; 140 | nSalesmen = configStruct.nSalesmen; 141 | minTour = configStruct.minTour; 142 | popSize = configStruct.popSize; 143 | numIter = configStruct.numIter; 144 | showProg = configStruct.showProg; 145 | showStatus = configStruct.showStatus; 146 | showResult = configStruct.showResult; 147 | showWaitbar = configStruct.showWaitbar; 148 | if isempty(dmat) 149 | nPoints = size(xy,1); 150 | a = meshgrid(1:nPoints); 151 | dmat = reshape(sqrt(sum((xy(a,:)-xy(a',:)).^2,2)),nPoints,nPoints); 152 | end 153 | 154 | 155 | % 156 | % Verify inputs 157 | % 158 | [N,dims] = size(xy); 159 | [nr,nc] = size(dmat); 160 | if (N ~= nr) || (N ~= nc) 161 | error('??? Invalid XY or DMAT inputs') 162 | end 163 | n = N; 164 | 165 | 166 | % 167 | % Sanity checks 168 | % 169 | nSalesmen = max(1,min(n,round(real(nSalesmen(1))))); 170 | minTour = max(1,min(floor(n/nSalesmen),round(real(minTour(1))))); 171 | popSize = max(1,min(n,round(real(popSize(1))))); 172 | numIter = max(1,round(real(numIter(1)))); 173 | showProg = logical(showProg(1)); 174 | showStatus = logical(showStatus(1)); 175 | showResult = logical(showResult(1)); 176 | showWaitbar = logical(showWaitbar(1)); 177 | 178 | 179 | % 180 | % Initializations for route break point selection 181 | % 182 | nBreaks = nSalesmen-1; 183 | dof = n - minTour*nSalesmen; % degrees of freedom 184 | addto = ones(1,dof+1); 185 | for k = 2:nBreaks 186 | addto = cumsum(addto); 187 | end 188 | cumProb = cumsum(addto)/sum(addto); 189 | 190 | 191 | % 192 | % Initialize the populations 193 | % 194 | popRoute = zeros(popSize,n); % population of routes 195 | popBreak = zeros(popSize,nBreaks); % population of breaks 196 | popRoute(1,:) = (1:n); 197 | popBreak(1,:) = rand_breaks(); 198 | for k = 2:popSize 199 | popRoute(k,:) = randperm(n); 200 | popBreak(k,:) = rand_breaks(); 201 | end 202 | 203 | 204 | % 205 | % Seed the algorithm with a previous result if available 206 | % 207 | if all(isfield(userConfig,{'optRoute','optBreak'})) 208 | optRoute = userConfig.optRoute; 209 | optBreak = userConfig.optBreak; 210 | isValidRoute = isequal(popRoute(1,:),sort(optRoute)); 211 | isValidBreak = all(optBreak > 0) && all(optBreak <= n) && ... 212 | (length(optBreak) == nBreaks) && ~any(mod(optBreak,1)); 213 | if isValidRoute && isValidBreak 214 | popRoute(1,:) = optRoute; 215 | popBreak(1,:) = optBreak; 216 | end 217 | end 218 | 219 | 220 | % 221 | % Select the colors for the plotted routes 222 | % 223 | pclr = ~get(0,'DefaultAxesColor'); 224 | clr = [1 0 0; 0 0 1; 0.67 0 1; 0 1 0; 1 0.5 0]; 225 | if (nSalesmen > 5) 226 | clr = hsv(nSalesmen); 227 | end 228 | 229 | 230 | % 231 | % Run the Random Search (RS) 232 | % 233 | globalMin = Inf; 234 | distHistory = NaN(1,numIter); 235 | [isClosed,isStopped,isCancelled] = deal(false); 236 | if showProg 237 | hFig = figure('Name','MTSP_RS | Current Best Solution', ... 238 | 'Numbertitle','off','CloseRequestFcn',@close_request); 239 | hAx = gca; 240 | if showStatus 241 | [hStatus,isCancelled] = figstatus(0,numIter,[],hFig); 242 | end 243 | end 244 | if showWaitbar 245 | hWait = waitbar(0,'Searching for near-optimal solution ...', ... 246 | 'CreateCancelBtn',@cancel_search); 247 | end 248 | isRunning = true; 249 | for iter = 1:numIter 250 | 251 | % 252 | % EVALUATE SOLUTIONS 253 | % This section of code computes the total cost of each solution 254 | % in the population. The actual code that gets executed uses a 255 | % much faster (vectorized) method to calculate the route lengths 256 | % compared to the triple for-loop below (provided for reference) 257 | % but gives the same result. 258 | % 259 | % totalDist = zeros(popSize,1); 260 | % for p = 1:popSize 261 | % d = 0; 262 | % pRoute = popRoute(p,:); 263 | % pBreak = popBreak(p,:); 264 | % rng = [[1 pBreak+1];[pBreak n]]'; 265 | % for s = 1:nSalesmen 266 | % d = d + dmat(pRoute(rng(s,2)),pRoute(rng(s,1))); 267 | % for k = rng(s,1):rng(s,2)-1 268 | % d = d + dmat(pRoute(k),pRoute(k+1)); 269 | % end 270 | % end 271 | % totalDist(p) = d; 272 | % end 273 | % 274 | row = popRoute; 275 | col = popRoute(:,[2:n 1]); 276 | for p = 1:popSize 277 | brk = popBreak(p,:); 278 | col(p,[brk n]) = row(p,[1 brk+1]); 279 | end 280 | ind = N*(col-1) + row; 281 | totalDist = sum(dmat(ind),2); 282 | 283 | 284 | % 285 | % SELECT THE BEST 286 | % This section of code finds the best solution in the current 287 | % population and stores it if it is better than the previous best. 288 | % 289 | [minDist,index] = min(totalDist); 290 | distHistory(iter) = minDist; 291 | if (minDist < globalMin) 292 | globalMin = minDist; 293 | optRoute = popRoute(index,:); 294 | optBreak = popBreak(index,:); 295 | rng = [[1 optBreak+1];[optBreak n]]'; 296 | if showProg 297 | 298 | % 299 | % Plot the best route 300 | % 301 | for s = 1:nSalesmen 302 | rte = optRoute([rng(s,1):rng(s,2) rng(s,1)]); 303 | if (dims > 2), plot3(hAx,xy(rte,1),xy(rte,2),xy(rte,3),'.-','Color',clr(s,:)); 304 | else, plot(hAx,xy(rte,1),xy(rte,2),'.-','Color',clr(s,:)); end 305 | hold(hAx,'on'); 306 | end 307 | title(hAx,sprintf('Total Distance = %1.4f, Iteration = %d',minDist,iter)); 308 | hold(hAx,'off'); 309 | drawnow; 310 | end 311 | end 312 | 313 | 314 | % 315 | % Update the status bar and check cancellation status 316 | % 317 | if showProg && showStatus && ~mod(iter,ceil(numIter/100)) 318 | [hStatus,isCancelled] = figstatus(iter,numIter,hStatus,hFig); 319 | end 320 | if (isStopped || isCancelled) 321 | break 322 | end 323 | 324 | 325 | % 326 | % MODIFY THE POPULATION 327 | % This section of code randomly searches for new solutions 328 | % 329 | popRoute(1,:) = optRoute; 330 | popBreak(1,:) = optBreak; 331 | for k = 2:popSize 332 | popRoute(k,:) = randperm(n); 333 | popBreak(k,:) = rand_breaks(); 334 | end 335 | 336 | 337 | % 338 | % Update the waitbar 339 | % 340 | if showWaitbar && ~mod(iter,ceil(numIter/325)) 341 | waitbar(iter/numIter,hWait); 342 | end 343 | 344 | end 345 | if showProg && showStatus 346 | figstatus(numIter,numIter,hStatus,hFig); 347 | end 348 | if showWaitbar 349 | delete(hWait); 350 | end 351 | isRunning = false; 352 | if isClosed 353 | delete(hFig); 354 | end 355 | 356 | 357 | % 358 | % Append prior distance history if present 359 | % 360 | if isfield(userConfig,'distHistory') 361 | priorHistory = userConfig.distHistory; 362 | isNan = isnan(priorHistory); 363 | distHistory = [priorHistory(~isNan) distHistory]; 364 | end 365 | 366 | 367 | % 368 | % Format the optimal solution 369 | % 370 | optSolution = cell(nSalesmen,1); 371 | rng = [[1 optBreak+1];[optBreak n]]'; 372 | for s = 1:nSalesmen 373 | optSolution{s} = optRoute([rng(s,1):rng(s,2) rng(s,1)]); 374 | end 375 | 376 | 377 | % 378 | % Show the final results 379 | % 380 | if showResult 381 | 382 | % 383 | % Plot the GA results 384 | % 385 | figure('Name','MTSP_RS | Results','Numbertitle','off'); 386 | subplot(2,2,1); 387 | if (dims > 2), plot3(xy(:,1),xy(:,2),xy(:,3),'.','Color',pclr); 388 | else, plot(xy(:,1),xy(:,2),'.','Color',pclr); end 389 | title('City Locations'); 390 | subplot(2,2,2); 391 | imagesc(dmat(optRoute,optRoute)); 392 | title('Distance Matrix'); 393 | subplot(2,2,3); 394 | for s = 1:nSalesmen 395 | rte = optSolution{s}; 396 | if (dims > 2), plot3(xy(rte,1),xy(rte,2),xy(rte,3),'.-','Color',clr(s,:)); 397 | else, plot(xy(rte,1),xy(rte,2),'.-','Color',clr(s,:)); end 398 | title(sprintf('Total Distance = %1.4f',minDist)); 399 | hold on; 400 | end 401 | subplot(2,2,4); 402 | plot(distHistory,'b','LineWidth',2); 403 | title('Best Solution History'); 404 | set(gca,'XLim',[1 length(distHistory)],'YLim',[0 1.1*max([1 distHistory])]); 405 | end 406 | 407 | 408 | % 409 | % Return output 410 | % 411 | if nargout 412 | 413 | % 414 | % Create anonymous functions for plot generation 415 | % 416 | plotPoints = @(s)plot(s.xy(:,1),s.xy(:,2),'.','Color',~get(gca,'Color')); 417 | plotResult = @(s)cellfun(@(s,i)plot(s.xy(i,1),s.xy(i,2),'.-', ... 418 | 'Color',rand(1,3)),repmat({s},size(s.optSolution)),s.optSolution); 419 | plotHistory = @(s)plot(s.distHistory,'b-','LineWidth',2); 420 | plotMatrix = @(s)imagesc(s.dmat(cat(2,s.optSolution{:}),cat(2,s.optSolution{:}))); 421 | 422 | 423 | % 424 | % Save results in output structure 425 | % 426 | resultStruct = struct( ... 427 | 'xy', xy, ... 428 | 'dmat', dmat, ... 429 | 'nSalesmen', nSalesmen, ... 430 | 'minTour', minTour, ... 431 | 'popSize', popSize, ... 432 | 'numIter', numIter, ... 433 | 'showProg', showProg, ... 434 | 'showResult', showResult, ... 435 | 'showWaitbar', showWaitbar, ... 436 | 'optRoute', optRoute, ... 437 | 'optBreak', optBreak, ... 438 | 'optSolution', {optSolution}, ... 439 | 'plotPoints', plotPoints, ... 440 | 'plotResult', plotResult, ... 441 | 'plotHistory', plotHistory, ... 442 | 'plotMatrix', plotMatrix, ... 443 | 'distHistory', distHistory, ... 444 | 'minDist', minDist); 445 | 446 | varargout = {resultStruct}; 447 | 448 | end 449 | 450 | 451 | % 452 | % Generate random set of break points 453 | % 454 | function breaks = rand_breaks() 455 | if (minTour == 1) % No constraints on breaks 456 | breaks = sort(randperm(n-1,nBreaks)); 457 | else % Force breaks to be at least the minimum tour length 458 | nAdjust = find(rand < cumProb,1)-1; 459 | spaces = randi(nBreaks,1,nAdjust); 460 | adjust = zeros(1,nBreaks); 461 | for kk = 1:nBreaks 462 | adjust(kk) = sum(spaces == kk); 463 | end 464 | breaks = minTour*(1:nBreaks) + cumsum(adjust); 465 | end 466 | end 467 | 468 | 469 | % 470 | % Nested function to cancel search 471 | % 472 | function cancel_search(varargin) 473 | isStopped = true; 474 | end 475 | 476 | 477 | % 478 | % Nested function to close the figure window 479 | % 480 | function close_request(varargin) 481 | if isRunning 482 | [isClosed,isStopped] = deal(true); 483 | isRunning = false; 484 | else 485 | delete(hFig); 486 | end 487 | end 488 | 489 | end 490 | 491 | -------------------------------------------------------------------------------- /seed_tsp.m: -------------------------------------------------------------------------------- 1 | 2 | 3 | % 4 | % Create XY points 5 | % 6 | xy = 10*rand(200,2); 7 | 8 | 9 | % 10 | % Solve the TSP using NN 11 | % 12 | nn = tsp_nn('xy',xy); 13 | 14 | 15 | % 16 | % Solve the TSP starting with the NN solution 17 | % 18 | ga = tsp_ga(nn); 19 | 20 | 21 | % 22 | % Compare against running the TSP from scratch 23 | % 24 | ga0 = tsp_ga('xy',xy); 25 | 26 | -------------------------------------------------------------------------------- /seed_tsp_turbo.m: -------------------------------------------------------------------------------- 1 | 2 | 3 | % 4 | % Create XY points 5 | % 6 | xy = 10*rand(200,2); 7 | 8 | 9 | % 10 | % Solve the TSP using NN 11 | % 12 | nn = tsp_nn('xy',xy); 13 | 14 | 15 | % 16 | % Solve the TSP starting with the NN solution 17 | % 18 | ga = tsp_ga_turbo(nn); 19 | 20 | 21 | % 22 | % Compare against running the TSP from scratch 23 | % 24 | ga0 = tsp_ga_turbo('xy',xy); 25 | 26 | -------------------------------------------------------------------------------- /tsp_fn.m: -------------------------------------------------------------------------------- 1 | %TSP_FN Traveling Salesman Problem (TSP) Farthest Neighbor (FN) Algorithm 2 | % The Farthest Neighbor algorithm produces different results depending on 3 | % which city is selected as the starting point. This function determines 4 | % the Farthest Neighbor routes for multiple starting points and returns 5 | % the best of those routes 6 | % 7 | % Summary: 8 | % 1. A single salesman travels to each of the cities and completes the 9 | % route by returning to the city he started from 10 | % 2. Each city is visited by the salesman exactly once 11 | % 12 | % Input: 13 | % USERCONFIG (structure) with zero or more of the following fields: 14 | % - XY (float) is an Nx2 matrix of city locations, where N is the number of cities 15 | % - DMAT (float) is an NxN matrix of point to point distances/costs 16 | % - POPSIZE (scalar integer) is the size of the population (should be <= N) 17 | % - SHOWPROG (scalar logical) shows the GA progress if true 18 | % - SHOWRESULT (scalar logical) shows the GA results if true 19 | % - SHOWWAITBAR (scalar logical) shows a waitbar if true 20 | % 21 | % Input Notes: 22 | % 1. Rather than passing in a structure containing these fields, any/all of 23 | % these inputs can be passed in as parameter/value pairs in any order instead. 24 | % 2. Field/parameter names are case insensitive but must match exactly otherwise. 25 | % 26 | % Output: 27 | % RESULTSTRUCT (structure) with the following fields: 28 | % (in addition to a record of the algorithm configuration) 29 | % - OPTROUTE (integer array) is the best route found by the algorithm 30 | % - MINDIST (scalar float) is the cost of the best route 31 | % 32 | % Usage: 33 | % tsp_fn 34 | % -or- 35 | % tsp_fn(userConfig) 36 | % -or- 37 | % resultStruct = tsp_fn; 38 | % -or- 39 | % resultStruct = tsp_fn(userConfig); 40 | % -or- 41 | % [...] = tsp_fn('Param1',Value1,'Param2',Value2, ...); 42 | % 43 | % Example: 44 | % % Let the function create an example problem to solve 45 | % tsp_fn; 46 | % 47 | % Example: 48 | % % Request the output structure from the solver 49 | % resultStruct = tsp_fn; 50 | % 51 | % Example: 52 | % % Pass a random set of user-defined XY points to the solver 53 | % userConfig = struct('xy',10*rand(50,2)); 54 | % resultStruct = tsp_fn(userConfig); 55 | % 56 | % Example: 57 | % % Pass a more interesting set of XY points to the solver 58 | % n = 100; 59 | % phi = (sqrt(5)-1)/2; 60 | % theta = 2*pi*phi*(0:n-1); 61 | % rho = (1:n).^phi; 62 | % [x,y] = pol2cart(theta(:),rho(:)); 63 | % xy = 10*([x y]-min([x;y]))/(max([x;y])-min([x;y])); 64 | % userConfig = struct('xy',xy); 65 | % resultStruct = tsp_fn(userConfig); 66 | % 67 | % Example: 68 | % % Pass a random set of 3D (XYZ) points to the solver 69 | % xyz = 10*rand(50,3); 70 | % userConfig = struct('xy',xyz); 71 | % resultStruct = tsp_fn(userConfig); 72 | % 73 | % Example: 74 | % % Turn off the plots but show a waitbar 75 | % userConfig = struct('showProg',false,'showResult',false,'showWaitbar',true); 76 | % resultStruct = tsp_fn(userConfig); 77 | % 78 | % See also: tsp_ga, tspo_ga, tspof_ga, tspofs_ga 79 | % 80 | % Author: Joseph Kirk 81 | % Email: jdkirk630@gmail.com 82 | % 83 | function varargout = tsp_fn(varargin) 84 | 85 | 86 | % 87 | % Initialize default configuration 88 | % 89 | defaultConfig.xy = 10*rand(100,2); 90 | defaultConfig.dmat = []; 91 | defaultConfig.popSize = Inf; 92 | defaultConfig.showProg = true; 93 | defaultConfig.showStatus = true; 94 | defaultConfig.showResult = true; 95 | defaultConfig.showWaitbar = false; 96 | 97 | 98 | % 99 | % Interpret user configuration inputs 100 | % 101 | if ~nargin 102 | userConfig = struct(); 103 | elseif isstruct(varargin{1}) 104 | userConfig = varargin{1}; 105 | else 106 | try 107 | userConfig = struct(varargin{:}); 108 | catch 109 | error('??? Expected inputs are either a structure or parameter/value pairs'); 110 | end 111 | end 112 | 113 | 114 | % 115 | % Override default configuration with user inputs 116 | % 117 | configStruct = get_config(defaultConfig,userConfig); 118 | 119 | 120 | % 121 | % Extract configuration 122 | % 123 | xy = configStruct.xy; 124 | dmat = configStruct.dmat; 125 | popSize = configStruct.popSize; 126 | showProg = configStruct.showProg; 127 | showStatus = configStruct.showStatus; 128 | showResult = configStruct.showResult; 129 | showWaitbar = configStruct.showWaitbar; 130 | if isempty(dmat) 131 | nPoints = size(xy,1); 132 | a = meshgrid(1:nPoints); 133 | dmat = reshape(sqrt(sum((xy(a,:)-xy(a',:)).^2,2)),nPoints,nPoints); 134 | end 135 | 136 | 137 | % 138 | % Verify inputs 139 | % 140 | [N,dims] = size(xy); 141 | [nr,nc] = size(dmat); 142 | if (N ~= nr) || (N ~= nc) 143 | error('??? Invalid XY or DMAT inputs') 144 | end 145 | n = N; 146 | 147 | 148 | % 149 | % Sanity checks 150 | % 151 | popSize = max(1,min(n,round(real(popSize(1))))); 152 | showProg = logical(showProg(1)); 153 | showStatus = logical(showStatus(1)); 154 | showResult = logical(showResult(1)); 155 | showWaitbar = logical(showWaitbar(1)); 156 | 157 | 158 | % 159 | % Initialize the population 160 | % 161 | pop = zeros(popSize,n); 162 | 163 | 164 | % 165 | % Run the FN 166 | % 167 | distHistory = NaN(1,popSize); 168 | [isClosed,isStopped,isCancelled] = deal(false); 169 | if showProg 170 | hFig = figure('Name','TSP_FN | Current Solution', ... 171 | 'Numbertitle','off','CloseRequestFcn',@close_request); 172 | hAx = gca; 173 | if showStatus 174 | [hStatus,isCancelled] = figstatus(0,popSize,[],hFig); 175 | end 176 | end 177 | if showWaitbar 178 | hWait = waitbar(0,'Searching for near-optimal solution ...', ... 179 | 'CreateCancelBtn',@cancel_search); 180 | end 181 | isRunning = true; 182 | for p = 1:popSize 183 | d = 0; 184 | thisRte = zeros(1,n); 185 | isVisited = false(1,n); 186 | I = p; 187 | isVisited(I) = true; 188 | thisRte(1) = I; 189 | for k = 2:n 190 | dists = dmat(I,:); 191 | dists(logical(isVisited)) = NaN; 192 | dMax = max(dists(~isVisited)); 193 | J = find(dists == dMax,1); 194 | isVisited(J) = true; 195 | thisRte(k) = J; 196 | d = d + dmat(I,J); 197 | I = J; 198 | end 199 | d = d + dmat(I,p); 200 | pop(p,:) = thisRte; 201 | distHistory(p) = d; 202 | 203 | 204 | % 205 | % Plot the current route 206 | % 207 | if showProg 208 | rte = thisRte([1:n 1]); 209 | if (dims > 2), plot3(hAx,xy(rte,1),xy(rte,2),xy(rte,3),'r.-'); 210 | else, plot(hAx,xy(rte,1),xy(rte,2),'r.-'); end 211 | title(hAx,sprintf('Total Distance = %1.4f, Iteration = %d', ... 212 | distHistory(p),p)); 213 | drawnow; 214 | end 215 | 216 | 217 | % 218 | % Update the status bar and check cancellation status 219 | % 220 | if showProg && showStatus && ~mod(p,ceil(popSize/100)) 221 | [hStatus,isCancelled] = figstatus(p,popSize,hStatus,hFig); 222 | end 223 | if (isStopped || isCancelled) 224 | break 225 | end 226 | 227 | 228 | % 229 | % Update the waitbar 230 | % 231 | if showWaitbar && ~mod(p,ceil(popSize/325)) 232 | waitbar(p/popSize,hWait); 233 | end 234 | 235 | end 236 | if showProg && showStatus 237 | figstatus(popSize,popSize,hStatus,hFig); 238 | end 239 | if showWaitbar 240 | delete(hWait); 241 | end 242 | isRunning = false; 243 | if isClosed 244 | delete(hFig); 245 | end 246 | 247 | 248 | % 249 | % Find the maximum distance route 250 | % 251 | [maxDist,index] = max(distHistory); 252 | optRoute = pop(index,:); 253 | 254 | 255 | % 256 | % Format the optimal solution 257 | % 258 | index = find(optRoute == 1,1); 259 | optSolution = [optRoute([index:n 1:index-1]) 1]; 260 | 261 | 262 | % 263 | % Show the final results 264 | % 265 | if showResult 266 | 267 | % 268 | % Plot the best route 269 | % 270 | if showProg && ~isClosed 271 | rte = optRoute([1:n 1]); 272 | if (dims > 2), plot3(hAx,xy(rte,1),xy(rte,2),xy(rte,3),'r.-'); 273 | else, plot(hAx,xy(rte,1),xy(rte,2),'r.-'); end 274 | title(hAx,sprintf('Total Distance = %1.4f',maxDist)); 275 | end 276 | 277 | % 278 | % Plot the FN results 279 | % 280 | figure('Name','TSP_FN | Results','Numbertitle','off'); 281 | subplot(2,2,1); 282 | pclr = ~get(0,'DefaultAxesColor'); 283 | if (dims > 2), plot3(xy(:,1),xy(:,2),xy(:,3),'.','Color',pclr); 284 | else, plot(xy(:,1),xy(:,2),'.','Color',pclr); end 285 | title('City Locations'); 286 | subplot(2,2,2); 287 | imagesc(dmat(optRoute,optRoute)); 288 | title('Distance Matrix'); 289 | subplot(2,2,3); 290 | rte = optRoute([1:n 1]); 291 | if (dims > 2), plot3(xy(rte,1),xy(rte,2),xy(rte,3),'r.-'); 292 | else, plot(xy(rte,1),xy(rte,2),'r.-'); end 293 | title(sprintf('Total Distance = %1.4f',maxDist)); 294 | subplot(2,2,4); 295 | plot(sort(distHistory,'descend'),'b','LineWidth',2); 296 | title('Distances'); 297 | set(gca,'XLim',[0 popSize+1],'YLim',[0 1.1*max([1 distHistory])]); 298 | end 299 | 300 | 301 | % 302 | % Return output 303 | % 304 | if nargout 305 | 306 | % 307 | % Create anonymous functions for plot generation 308 | % 309 | plotPoints = @(s)plot(s.xy(:,1),s.xy(:,2),'.','Color',~get(gca,'Color')); 310 | plotResult = @(s)plot(s.xy(s.optSolution,1),s.xy(s.optSolution,2),'r.-'); 311 | plotHistory = @(s)plot(s.distHistory,'b-','LineWidth',2); 312 | plotMatrix = @(s)imagesc(s.dmat(s.optSolution,s.optSolution)); 313 | 314 | 315 | % 316 | % Save results in output structure 317 | % 318 | resultStruct = struct( ... 319 | 'xy', xy, ... 320 | 'dmat', dmat, ... 321 | 'popSize', popSize, ... 322 | 'showProg', showProg, ... 323 | 'showResult', showResult, ... 324 | 'showWaitbar', showWaitbar, ... 325 | 'optRoute', optRoute, ... 326 | 'optSolution', optSolution, ... 327 | 'plotPoints', plotPoints, ... 328 | 'plotResult', plotResult, ... 329 | 'plotHistory', plotHistory, ... 330 | 'plotMatrix', plotMatrix, ... 331 | 'maxDist', maxDist, ... 332 | 'distHistory', distHistory, ... 333 | 'pop', pop); 334 | 335 | varargout = {resultStruct}; 336 | 337 | end 338 | 339 | 340 | % 341 | % Nested function to cancel search 342 | % 343 | function cancel_search(varargin) 344 | isStopped = true; 345 | end 346 | 347 | 348 | % 349 | % Nested function to close the figure window 350 | % 351 | function close_request(varargin) 352 | if isRunning 353 | [isClosed,isStopped] = deal(true); 354 | isRunning = false; 355 | else 356 | delete(hFig); 357 | end 358 | end 359 | 360 | end 361 | 362 | -------------------------------------------------------------------------------- /tsp_ga.m: -------------------------------------------------------------------------------- 1 | %TSP_GA Traveling Salesman Problem (TSP) Genetic Algorithm (GA) 2 | % Finds a (near) optimal solution to the TSP by setting up a GA to search 3 | % for the shortest route (least distance for the salesman to travel to 4 | % each city exactly once and return to the starting city) 5 | % 6 | % Summary: 7 | % 1. A single salesman travels to each of the cities and completes the 8 | % route by returning to the city he started from 9 | % 2. Each city is visited by the salesman exactly once 10 | % 11 | % Input: 12 | % USERCONFIG (structure) with zero or more of the following fields: 13 | % - XY (float) is an Nx2 matrix of city locations, where N is the number of cities 14 | % - DMAT (float) is an NxN matrix of point to point distances/costs 15 | % - POPSIZE (scalar integer) is the size of the population (should be divisible by 4) 16 | % - NUMITER (scalar integer) is the number of desired iterations for the algorithm to run 17 | % - SHOWPROG (scalar logical) shows the GA progress if true 18 | % - SHOWRESULT (scalar logical) shows the GA results if true 19 | % - SHOWWAITBAR (scalar logical) shows a waitbar if true 20 | % 21 | % Input Notes: 22 | % 1. Rather than passing in a structure containing these fields, any/all of 23 | % these inputs can be passed in as parameter/value pairs in any order instead. 24 | % 2. Field/parameter names are case insensitive but must match exactly otherwise. 25 | % 26 | % Output: 27 | % RESULTSTRUCT (structure) with the following fields: 28 | % (in addition to a record of the algorithm configuration) 29 | % - OPTROUTE (integer array) is the best route found by the algorithm 30 | % - MINDIST (scalar float) is the cost of the best route 31 | % 32 | % Usage: 33 | % tsp_ga 34 | % -or- 35 | % tsp_ga(userConfig) 36 | % -or- 37 | % resultStruct = tsp_ga; 38 | % -or- 39 | % resultStruct = tsp_ga(userConfig); 40 | % -or- 41 | % [...] = tsp_ga('Param1',Value1,'Param2',Value2, ...); 42 | % 43 | % Example: 44 | % % Let the function create an example problem to solve 45 | % tsp_ga; 46 | % 47 | % Example: 48 | % % Request the output structure from the solver 49 | % resultStruct = tsp_ga; 50 | % 51 | % Example: 52 | % % Pass a random set of user-defined XY points to the solver 53 | % userConfig = struct('xy',10*rand(50,2)); 54 | % resultStruct = tsp_ga(userConfig); 55 | % 56 | % Example: 57 | % % Pass a more interesting set of XY points to the solver 58 | % n = 100; 59 | % phi = (sqrt(5)-1)/2; 60 | % theta = 2*pi*phi*(0:n-1); 61 | % rho = (1:n).^phi; 62 | % [x,y] = pol2cart(theta(:),rho(:)); 63 | % xy = 10*([x y]-min([x;y]))/(max([x;y])-min([x;y])); 64 | % userConfig = struct('xy',xy); 65 | % resultStruct = tsp_ga(userConfig); 66 | % 67 | % Example: 68 | % % Pass a random set of 3D (XYZ) points to the solver 69 | % xyz = 10*rand(50,3); 70 | % userConfig = struct('xy',xyz); 71 | % resultStruct = tsp_ga(userConfig); 72 | % 73 | % Example: 74 | % % Change the defaults for GA population size and number of iterations 75 | % userConfig = struct('popSize',200,'numIter',1e4); 76 | % resultStruct = tsp_ga(userConfig); 77 | % 78 | % Example: 79 | % % Turn off the plots but show a waitbar 80 | % userConfig = struct('showProg',false,'showResult',false,'showWaitbar',true); 81 | % resultStruct = tsp_ga(userConfig); 82 | % 83 | % See also: mtsp_ga, tsp_nn, tspo_ga, tspof_ga, tspofs_ga 84 | % 85 | % Author: Joseph Kirk 86 | % Email: jdkirk630@gmail.com 87 | % 88 | function varargout = tsp_ga(varargin) 89 | 90 | 91 | % 92 | % Initialize default configuration 93 | % 94 | defaultConfig.xy = 10*rand(50,2); 95 | defaultConfig.dmat = []; 96 | defaultConfig.popSize = 100; 97 | defaultConfig.numIter = 1e4; 98 | defaultConfig.showProg = true; 99 | defaultConfig.showStatus = true; 100 | defaultConfig.showResult = true; 101 | defaultConfig.showWaitbar = false; 102 | 103 | 104 | % 105 | % Interpret user configuration inputs 106 | % 107 | if ~nargin 108 | userConfig = struct(); 109 | elseif isstruct(varargin{1}) 110 | userConfig = varargin{1}; 111 | else 112 | try 113 | userConfig = struct(varargin{:}); 114 | catch 115 | error('??? Expected inputs are either a structure or parameter/value pairs'); 116 | end 117 | end 118 | 119 | 120 | % 121 | % Override default configuration with user inputs 122 | % 123 | configStruct = get_config(defaultConfig,userConfig); 124 | 125 | 126 | % 127 | % Extract configuration 128 | % 129 | xy = configStruct.xy; 130 | dmat = configStruct.dmat; 131 | popSize = configStruct.popSize; 132 | numIter = configStruct.numIter; 133 | showProg = configStruct.showProg; 134 | showStatus = configStruct.showStatus; 135 | showResult = configStruct.showResult; 136 | showWaitbar = configStruct.showWaitbar; 137 | if isempty(dmat) 138 | nPoints = size(xy,1); 139 | a = meshgrid(1:nPoints); 140 | dmat = reshape(sqrt(sum((xy(a,:)-xy(a',:)).^2,2)),nPoints,nPoints); 141 | end 142 | 143 | 144 | % 145 | % Verify inputs 146 | % 147 | [N,dims] = size(xy); 148 | [nr,nc] = size(dmat); 149 | if (N ~= nr) || (N ~= nc) 150 | error('??? Invalid XY or DMAT inputs') 151 | end 152 | n = N; 153 | 154 | 155 | % 156 | % Sanity checks 157 | % 158 | popSize = 4*ceil(popSize/4); 159 | numIter = max(1,round(real(numIter(1)))); 160 | showProg = logical(showProg(1)); 161 | showStatus = logical(showStatus(1)); 162 | showResult = logical(showResult(1)); 163 | showWaitbar = logical(showWaitbar(1)); 164 | 165 | 166 | % 167 | % Initialize the population 168 | % 169 | pop = zeros(popSize,n); 170 | pop(1,:) = (1:n); 171 | for k = 2:popSize 172 | pop(k,:) = randperm(n); 173 | end 174 | 175 | 176 | % 177 | % Seed the algorithm with a previous result if available 178 | % 179 | if isfield(userConfig,'optRoute') 180 | optRoute = userConfig.optRoute; 181 | isValid = isequal(pop(1,:),sort(optRoute)); 182 | if isValid 183 | pop(1,:) = optRoute; 184 | end 185 | end 186 | 187 | 188 | % 189 | % Run the GA 190 | % 191 | globalMin = Inf; 192 | distHistory = NaN(1,numIter); 193 | tmpPop = zeros(4,n); 194 | newPop = zeros(popSize,n); 195 | [isClosed,isStopped,isCancelled] = deal(false); 196 | if showProg 197 | hFig = figure('Name','TSP_GA | Current Best Solution', ... 198 | 'Numbertitle','off','CloseRequestFcn',@close_request); 199 | hAx = gca; 200 | if showStatus 201 | [hStatus,isCancelled] = figstatus(0,numIter,[],hFig); 202 | end 203 | end 204 | if showWaitbar 205 | hWait = waitbar(0,'Searching for near-optimal solution ...', ... 206 | 'CreateCancelBtn',@cancel_search); 207 | end 208 | isRunning = true; 209 | for iter = 1:numIter 210 | 211 | % 212 | % EVALUATE SOLUTIONS 213 | % This section of code computes the total cost of each solution 214 | % in the population. The actual code that gets executed uses a 215 | % much faster (vectorized) method to calculate the route lengths 216 | % compared to the double for-loop below (provided for reference) 217 | % but gives the same result. 218 | % 219 | % totalDist = zeros(popSize,1); 220 | % for p = 1:popSize 221 | % d = dmat(pop(p,n),pop(p,1)); 222 | % for k = 2:n 223 | % d = d + dmat(pop(p,k-1),pop(p,k)); 224 | % end 225 | % totalDist(p) = d; 226 | % end 227 | % 228 | row = pop; 229 | col = pop(:,[2:n 1]); 230 | ind = N*(col-1) + row; 231 | totalDist = sum(dmat(ind),2); 232 | 233 | 234 | % 235 | % SELECT THE BEST 236 | % This section of code finds the best solution in the current 237 | % population and stores it if it is better than the previous best. 238 | % 239 | [minDist,index] = min(totalDist); 240 | distHistory(iter) = minDist; 241 | if (minDist < globalMin) 242 | globalMin = minDist; 243 | optRoute = pop(index,:); 244 | if showProg 245 | 246 | % 247 | % Plot the best route 248 | % 249 | rte = optRoute([1:n 1]); 250 | if (dims > 2), plot3(hAx,xy(rte,1),xy(rte,2),xy(rte,3),'r.-'); 251 | else, plot(hAx,xy(rte,1),xy(rte,2),'r.-'); end 252 | title(hAx,sprintf('Total Distance = %1.4f, Iteration = %d',minDist,iter)); 253 | drawnow; 254 | 255 | end 256 | end 257 | 258 | 259 | % 260 | % Update the status bar and check cancellation status 261 | % 262 | if showProg && showStatus && ~mod(iter,ceil(numIter/100)) 263 | [hStatus,isCancelled] = figstatus(iter,numIter,hStatus,hFig); 264 | end 265 | if (isStopped || isCancelled) 266 | break 267 | end 268 | 269 | 270 | % 271 | % MODIFY THE POPULATION 272 | % This section of code invokes the genetic algorithm operators. 273 | % In this implementation, solutions are randomly assigned to groups 274 | % of four and the best solution is kept (tournament selection). 275 | % The best-of-four solution is then mutated 3 different ways 276 | % (flip, swap, and slide). There is no crossover operator because 277 | % it tends to be highly destructive and rarely improves a decent 278 | % solution. 279 | % 280 | randomOrder = randperm(popSize); 281 | for p = 4:4:popSize 282 | rtes = pop(randomOrder(p-3:p),:); 283 | dists = totalDist(randomOrder(p-3:p)); 284 | [ignore,idx] = min(dists); %#ok 285 | bestOf4Route = rtes(idx,:); 286 | routeInsertionPoints = sort(randperm(n,2)); 287 | I = routeInsertionPoints(1); 288 | J = routeInsertionPoints(2); 289 | for k = 1:4 % Mutate the best to get three new routes 290 | tmpPop(k,:) = bestOf4Route; 291 | switch k 292 | case 2 % Flip 293 | tmpPop(k,I:J) = tmpPop(k,J:-1:I); 294 | case 3 % Swap 295 | tmpPop(k,[I J]) = tmpPop(k,[J I]); 296 | case 4 % Slide 297 | tmpPop(k,I:J) = tmpPop(k,[I+1:J I]); 298 | otherwise % Do nothing 299 | end 300 | end 301 | newPop(p-3:p,:) = tmpPop; 302 | end 303 | pop = newPop; 304 | 305 | 306 | % 307 | % Update the waitbar 308 | % 309 | if showWaitbar && ~mod(iter,ceil(numIter/325)) 310 | waitbar(iter/numIter,hWait); 311 | end 312 | 313 | end 314 | if showProg && showStatus 315 | figstatus(numIter,numIter,hStatus,hFig); 316 | end 317 | if showWaitbar 318 | delete(hWait); 319 | end 320 | isRunning = false; 321 | if isClosed 322 | delete(hFig); 323 | end 324 | 325 | 326 | % 327 | % Append prior distance history if present 328 | % 329 | if isfield(userConfig,'distHistory') 330 | priorHistory = userConfig.distHistory; 331 | isNan = isnan(priorHistory); 332 | distHistory = [priorHistory(~isNan) distHistory]; 333 | end 334 | 335 | 336 | % 337 | % Format the optimal solution 338 | % 339 | index = find(optRoute == 1,1); 340 | optSolution = [optRoute([index:n 1:index-1]) 1]; 341 | 342 | 343 | % 344 | % Show the final results 345 | % 346 | if showResult 347 | 348 | % 349 | % Plot the GA results 350 | % 351 | figure('Name','TSP_GA | Results','Numbertitle','off'); 352 | subplot(2,2,1); 353 | pclr = ~get(0,'DefaultAxesColor'); 354 | if (dims > 2), plot3(xy(:,1),xy(:,2),xy(:,3),'.','Color',pclr); 355 | else, plot(xy(:,1),xy(:,2),'.','Color',pclr); end 356 | title('City Locations'); 357 | subplot(2,2,2); 358 | imagesc(dmat(optRoute,optRoute)); 359 | title('Distance Matrix'); 360 | subplot(2,2,3); 361 | rte = optSolution; 362 | if (dims > 2), plot3(xy(rte,1),xy(rte,2),xy(rte,3),'r.-'); 363 | else, plot(xy(rte,1),xy(rte,2),'r.-'); end 364 | title(sprintf('Total Distance = %1.4f',minDist)); 365 | subplot(2,2,4); 366 | plot(distHistory,'b','LineWidth',2); 367 | title('Best Solution History'); 368 | set(gca,'XLim',[1 length(distHistory)],'YLim',[0 1.1*max([1 distHistory])]); 369 | end 370 | 371 | 372 | % 373 | % Return output 374 | % 375 | if nargout 376 | 377 | % 378 | % Create anonymous functions for plot generation 379 | % 380 | plotPoints = @(s)plot(s.xy(:,1),s.xy(:,2),'.','Color',~get(gca,'Color')); 381 | plotResult = @(s)plot(s.xy(s.optSolution,1),s.xy(s.optSolution,2),'r.-'); 382 | plotHistory = @(s)plot(s.distHistory,'b-','LineWidth',2); 383 | plotMatrix = @(s)imagesc(s.dmat(s.optSolution,s.optSolution)); 384 | 385 | 386 | % 387 | % Save results in output structure 388 | % 389 | resultStruct = struct( ... 390 | 'xy', xy, ... 391 | 'dmat', dmat, ... 392 | 'popSize', popSize, ... 393 | 'numIter', numIter, ... 394 | 'showProg', showProg, ... 395 | 'showResult', showResult, ... 396 | 'showWaitbar', showWaitbar, ... 397 | 'optRoute', optRoute, ... 398 | 'optSolution', optSolution, ... 399 | 'plotPoints', plotPoints, ... 400 | 'plotResult', plotResult, ... 401 | 'plotHistory', plotHistory, ... 402 | 'plotMatrix', plotMatrix, ... 403 | 'distHistory', distHistory, ... 404 | 'minDist', minDist); 405 | 406 | varargout = {resultStruct}; 407 | 408 | end 409 | 410 | 411 | % 412 | % Nested function to cancel search 413 | % 414 | function cancel_search(varargin) 415 | isStopped = true; 416 | end 417 | 418 | 419 | % 420 | % Nested function to close the figure window 421 | % 422 | function close_request(varargin) 423 | if isRunning 424 | [isClosed,isStopped] = deal(true); 425 | isRunning = false; 426 | else 427 | delete(hFig); 428 | end 429 | end 430 | 431 | end 432 | 433 | -------------------------------------------------------------------------------- /tsp_ga_co.m: -------------------------------------------------------------------------------- 1 | %TSP_GA_CO Traveling Salesman Problem (TSP) Genetic Algorithm (GA) 2 | % Finds a (near) optimal solution to the TSP by setting up a GA to search 3 | % for the shortest route (least distance for the salesman to travel to 4 | % each city exactly once and return to the starting city) 5 | % 6 | % Summary: 7 | % 1. A single salesman travels to each of the cities and completes the 8 | % route by returning to the city he started from 9 | % 2. Each city is visited by the salesman exactly once 10 | % 11 | % Input: 12 | % USERCONFIG (structure) with zero or more of the following fields: 13 | % - XY (float) is an Nx2 matrix of city locations, where N is the number of cities 14 | % - DMAT (float) is an NxN matrix of point to point distances/costs 15 | % - POPSIZE (scalar integer) is the size of the population (should be divisible by 4) 16 | % - NUMITER (scalar integer) is the number of desired iterations for the algorithm to run 17 | % - SHOWPROG (scalar logical) shows the GA progress if true 18 | % - SHOWRESULT (scalar logical) shows the GA results if true 19 | % - SHOWWAITBAR (scalar logical) shows a waitbar if true 20 | % 21 | % Input Notes: 22 | % 1. Rather than passing in a structure containing these fields, any/all of 23 | % these inputs can be passed in as parameter/value pairs in any order instead. 24 | % 2. Field/parameter names are case insensitive but must match exactly otherwise. 25 | % 26 | % Output: 27 | % RESULTSTRUCT (structure) with the following fields: 28 | % (in addition to a record of the algorithm configuration) 29 | % - OPTROUTE (integer array) is the best route found by the algorithm 30 | % - MINDIST (scalar float) is the cost of the best route 31 | % 32 | % Usage: 33 | % tsp_ga_co 34 | % -or- 35 | % tsp_ga_co(userConfig) 36 | % -or- 37 | % resultStruct = tsp_ga_co; 38 | % -or- 39 | % resultStruct = tsp_ga_co(userConfig); 40 | % -or- 41 | % [...] = tsp_ga_co('Param1',Value1,'Param2',Value2, ...); 42 | % 43 | % Example: 44 | % % Let the function create an example problem to solve 45 | % tsp_ga_co; 46 | % 47 | % Example: 48 | % % Request the output structure from the solver 49 | % resultStruct = tsp_ga_co; 50 | % 51 | % Example: 52 | % % Pass a random set of user-defined XY points to the solver 53 | % userConfig = struct('xy',10*rand(50,2)); 54 | % resultStruct = tsp_ga_co(userConfig); 55 | % 56 | % Example: 57 | % % Pass a more interesting set of XY points to the solver 58 | % n = 100; 59 | % phi = (sqrt(5)-1)/2; 60 | % theta = 2*pi*phi*(0:n-1); 61 | % rho = (1:n).^phi; 62 | % [x,y] = pol2cart(theta(:),rho(:)); 63 | % xy = 10*([x y]-min([x;y]))/(max([x;y])-min([x;y])); 64 | % userConfig = struct('xy',xy); 65 | % resultStruct = tsp_ga_co(userConfig); 66 | % 67 | % Example: 68 | % % Pass a random set of 3D (XYZ) points to the solver 69 | % xyz = 10*rand(50,3); 70 | % userConfig = struct('xy',xyz); 71 | % resultStruct = tsp_ga_co(userConfig); 72 | % 73 | % Example: 74 | % % Change the defaults for GA population size and number of iterations 75 | % userConfig = struct('popSize',200,'numIter',1e4); 76 | % resultStruct = tsp_ga_co(userConfig); 77 | % 78 | % Example: 79 | % % Turn off the plots but show a waitbar 80 | % userConfig = struct('showProg',false,'showResult',false,'showWaitbar',true); 81 | % resultStruct = tsp_ga_co(userConfig); 82 | % 83 | % See also: mtsp_ga, tsp_nn, tspo_ga, tspof_ga, tspofs_ga 84 | % 85 | % Author: Joseph Kirk 86 | % Email: jdkirk630@gmail.com 87 | % 88 | function varargout = tsp_ga_co(varargin) 89 | 90 | 91 | % 92 | % Initialize default configuration 93 | % 94 | defaultConfig.xy = 10*rand(50,2); 95 | defaultConfig.dmat = []; 96 | defaultConfig.popSize = 100; 97 | defaultConfig.numIter = 1e4; 98 | defaultConfig.showProg = true; 99 | defaultConfig.showStatus = true; 100 | defaultConfig.showResult = true; 101 | defaultConfig.showWaitbar = false; 102 | 103 | 104 | % 105 | % Interpret user configuration inputs 106 | % 107 | if ~nargin 108 | userConfig = struct(); 109 | elseif isstruct(varargin{1}) 110 | userConfig = varargin{1}; 111 | else 112 | try 113 | userConfig = struct(varargin{:}); 114 | catch 115 | error('??? Expected inputs are either a structure or parameter/value pairs'); 116 | end 117 | end 118 | 119 | 120 | % 121 | % Override default configuration with user inputs 122 | % 123 | configStruct = get_config(defaultConfig,userConfig); 124 | 125 | 126 | % 127 | % Extract configuration 128 | % 129 | xy = configStruct.xy; 130 | dmat = configStruct.dmat; 131 | popSize = configStruct.popSize; 132 | numIter = configStruct.numIter; 133 | showProg = configStruct.showProg; 134 | showStatus = configStruct.showStatus; 135 | showResult = configStruct.showResult; 136 | showWaitbar = configStruct.showWaitbar; 137 | if isempty(dmat) 138 | nPoints = size(xy,1); 139 | a = meshgrid(1:nPoints); 140 | dmat = reshape(sqrt(sum((xy(a,:)-xy(a',:)).^2,2)),nPoints,nPoints); 141 | end 142 | 143 | 144 | % 145 | % Verify inputs 146 | % 147 | [N,dims] = size(xy); 148 | [nr,nc] = size(dmat); 149 | if (N ~= nr) || (N ~= nc) 150 | error('??? Invalid XY or DMAT inputs') 151 | end 152 | n = N; 153 | 154 | 155 | % 156 | % Sanity checks 157 | % 158 | popSize = 4*ceil(popSize/4); 159 | numIter = max(1,round(real(numIter(1)))); 160 | showProg = logical(showProg(1)); 161 | showStatus = logical(showStatus(1)); 162 | showResult = logical(showResult(1)); 163 | showWaitbar = logical(showWaitbar(1)); 164 | 165 | 166 | % 167 | % Initialize the population 168 | % 169 | pop = zeros(popSize,n); 170 | pop(1,:) = (1:n); 171 | for k = 2:popSize 172 | pop(k,:) = randperm(n); 173 | end 174 | 175 | 176 | % 177 | % Seed the algorithm with a previous result if available 178 | % 179 | if isfield(userConfig,'optRoute') 180 | optRoute = userConfig.optRoute; 181 | isValid = isequal(pop(1,:),sort(optRoute)); 182 | if isValid 183 | pop(1,:) = optRoute; 184 | end 185 | end 186 | 187 | 188 | % 189 | % Run the GA 190 | % 191 | globalMin = Inf; 192 | distHistory = NaN(1,numIter); 193 | newPop = zeros(popSize,n); 194 | [isClosed,isStopped,isCancelled] = deal(false); 195 | if showProg 196 | hFig = figure('Name','TSP_GA_CO | Current Best Solution', ... 197 | 'Numbertitle','off','CloseRequestFcn',@close_request); 198 | hAx = gca; 199 | if showStatus 200 | [hStatus,isCancelled] = figstatus(0,numIter,[],hFig); 201 | end 202 | end 203 | if showWaitbar 204 | hWait = waitbar(0,'Searching for near-optimal solution ...', ... 205 | 'CreateCancelBtn',@cancel_search); 206 | end 207 | isRunning = true; 208 | for iter = 1:numIter 209 | 210 | % 211 | % EVALUATE SOLUTIONS 212 | % This section of code computes the total cost of each solution 213 | % in the population. The actual code that gets executed uses a 214 | % much faster (vectorized) method to calculate the route lengths 215 | % compared to the double for-loop below (provided for reference) 216 | % but gives the same result. 217 | % 218 | % totalDist = zeros(popSize,1); 219 | % for p = 1:popSize 220 | % d = dmat(pop(p,n),pop(p,1)); 221 | % for k = 2:n 222 | % d = d + dmat(pop(p,k-1),pop(p,k)); 223 | % end 224 | % totalDist(p) = d; 225 | % end 226 | % 227 | row = pop; 228 | col = pop(:,[2:n 1]); 229 | ind = N*(col-1) + row; 230 | totalDist = sum(dmat(ind),2); 231 | 232 | 233 | % 234 | % SELECT THE BEST 235 | % This section of code finds the best solution in the current 236 | % population and stores it if it is better than the previous best. 237 | % 238 | [minDist,index] = min(totalDist); 239 | distHistory(iter) = minDist; 240 | if (minDist < globalMin) 241 | globalMin = minDist; 242 | optRoute = pop(index,:); 243 | if showProg 244 | 245 | % 246 | % Plot the best route 247 | % 248 | rte = optRoute([1:n 1]); 249 | if (dims > 2), plot3(hAx,xy(rte,1),xy(rte,2),xy(rte,3),'r.-'); 250 | else, plot(hAx,xy(rte,1),xy(rte,2),'r.-'); end 251 | title(hAx,sprintf('Total Distance = %1.4f, Iteration = %d',minDist,iter)); 252 | drawnow; 253 | end 254 | end 255 | 256 | 257 | % 258 | % Update the status bar and check cancellation status 259 | % 260 | if showProg && showStatus && ~mod(iter,ceil(numIter/100)) 261 | [hStatus,isCancelled] = figstatus(iter,numIter,hStatus,hFig); 262 | end 263 | if (isStopped || isCancelled) 264 | break 265 | end 266 | 267 | 268 | % 269 | % MODIFY THE POPULATION 270 | % This section of code invokes the genetic algorithm operators. 271 | % In this implementation, solutions are randomly assigned to groups 272 | % of four and the best solution is kept (tournament selection). 273 | % The top two solutions are then used to create two new solutions. 274 | % One of the new solutions is also mutated and passed along to the 275 | % next iteration of the algorithm. 276 | % 277 | randomOrder = randperm(popSize); 278 | for p = 4:4:popSize 279 | 280 | % 281 | % Find the parents 282 | % 283 | rtes = pop(randomOrder(p-3:p),:); 284 | dists = totalDist(randomOrder(p-3:p)); 285 | [ignore,ord] = sort(dists); %#ok 286 | father = rtes(ord(1),:); 287 | mother = rtes(ord(2),:); 288 | routeInsertionPoints = sort(randperm(n,2)); 289 | I = routeInsertionPoints(1); 290 | J = routeInsertionPoints(2); 291 | len = J - I + 1; 292 | f = father(I:J)'; 293 | m = mother(I:J)'; 294 | 295 | 296 | % 297 | % Create Two Children From Two Best Parents 298 | % 299 | childOne = father; 300 | childOne(any(m(:,ones(n,1)) == father(ones(len,1),:),1)) = []; 301 | childOne = [childOne(1:I-1) mother(I:J) childOne(I:n-len)]; 302 | childTwo = mother; 303 | childTwo(any(f(:,ones(n,1)) == mother(ones(len,1),:),1)) = []; 304 | childTwo = [childTwo(1:I-1) father(I:J) childTwo(I:n-len)]; 305 | 306 | 307 | % 308 | % Mutate One of the Children 309 | % 310 | mutant = childOne; 311 | routeInsertionPoints = sort(randperm(n,2)); 312 | I = routeInsertionPoints(1); 313 | J = routeInsertionPoints(2); 314 | mutant(I:J) = mutant(J:-1:I); 315 | 316 | 317 | % 318 | % Pass the Best Parent, the Children, and the Mutant to the Next Generation 319 | % 320 | newPop(p-3:p,:) = [father; childOne; childTwo; mutant]; 321 | end 322 | pop = newPop; 323 | 324 | 325 | % 326 | % Update the waitbar 327 | % 328 | if showWaitbar && ~mod(iter,ceil(numIter/325)) 329 | waitbar(iter/numIter,hWait); 330 | end 331 | 332 | end 333 | if showProg && showStatus 334 | figstatus(numIter,numIter,hStatus,hFig); 335 | end 336 | if showWaitbar 337 | delete(hWait); 338 | end 339 | isRunning = false; 340 | if isClosed 341 | delete(hFig); 342 | end 343 | 344 | 345 | % 346 | % Append prior distance history if present 347 | % 348 | if isfield(userConfig,'distHistory') 349 | priorHistory = userConfig.distHistory; 350 | isNan = isnan(priorHistory); 351 | distHistory = [priorHistory(~isNan) distHistory]; 352 | end 353 | 354 | 355 | % 356 | % Format the optimal solution 357 | % 358 | index = find(optRoute == 1,1); 359 | optSolution = [optRoute([index:n 1:index-1]) 1]; 360 | 361 | 362 | % 363 | % Show the final results 364 | % 365 | if showResult 366 | 367 | % 368 | % Plot the GA results 369 | % 370 | figure('Name','TSP_GA_CO | Results','Numbertitle','off'); 371 | subplot(2,2,1); 372 | pclr = ~get(0,'DefaultAxesColor'); 373 | if (dims > 2), plot3(xy(:,1),xy(:,2),xy(:,3),'.','Color',pclr); 374 | else, plot(xy(:,1),xy(:,2),'.','Color',pclr); end 375 | title('City Locations'); 376 | subplot(2,2,2); 377 | imagesc(dmat(optRoute,optRoute)); 378 | title('Distance Matrix'); 379 | subplot(2,2,3); 380 | rte = optSolution; 381 | if (dims > 2), plot3(xy(rte,1),xy(rte,2),xy(rte,3),'r.-'); 382 | else, plot(xy(rte,1),xy(rte,2),'r.-'); end 383 | title(sprintf('Total Distance = %1.4f',minDist)); 384 | subplot(2,2,4); 385 | plot(distHistory,'b','LineWidth',2); 386 | title('Best Solution History'); 387 | set(gca,'XLim',[1 length(distHistory)],'YLim',[0 1.1*max([1 distHistory])]); 388 | end 389 | 390 | 391 | % 392 | % Return output 393 | % 394 | if nargout 395 | 396 | % 397 | % Create anonymous functions for plot generation 398 | % 399 | plotPoints = @(s)plot(s.xy(:,1),s.xy(:,2),'.','Color',~get(gca,'Color')); 400 | plotResult = @(s)plot(s.xy(s.optSolution,1),s.xy(s.optSolution,2),'r.-'); 401 | plotHistory = @(s)plot(s.distHistory,'b-','LineWidth',2); 402 | plotMatrix = @(s)imagesc(s.dmat(s.optSolution,s.optSolution)); 403 | 404 | 405 | % 406 | % Save results in output structure 407 | % 408 | resultStruct = struct( ... 409 | 'xy', xy, ... 410 | 'dmat', dmat, ... 411 | 'popSize', popSize, ... 412 | 'numIter', numIter, ... 413 | 'showProg', showProg, ... 414 | 'showResult', showResult, ... 415 | 'showWaitbar', showWaitbar, ... 416 | 'optRoute', optRoute, ... 417 | 'optSolution', optSolution, ... 418 | 'plotPoints', plotPoints, ... 419 | 'plotResult', plotResult, ... 420 | 'plotHistory', plotHistory, ... 421 | 'plotMatrix', plotMatrix, ... 422 | 'distHistory', distHistory, ... 423 | 'minDist', minDist); 424 | 425 | varargout = {resultStruct}; 426 | 427 | end 428 | 429 | 430 | % 431 | % Nested function to cancel search 432 | % 433 | function cancel_search(varargin) 434 | isStopped = true; 435 | end 436 | 437 | 438 | % 439 | % Nested function to close the figure window 440 | % 441 | function close_request(varargin) 442 | if isRunning 443 | [isClosed,isStopped] = deal(true); 444 | isRunning = false; 445 | else 446 | delete(hFig); 447 | end 448 | end 449 | 450 | end 451 | 452 | -------------------------------------------------------------------------------- /tsp_ga_hybrid.m: -------------------------------------------------------------------------------- 1 | %TSP_GA_HYBRID Traveling Salesman Problem (TSP) Genetic Algorithm (GA) 2 | % Finds a (near) optimal solution to the TSP by setting up a GA to search 3 | % for the shortest route (least distance for the salesman to travel to 4 | % each city exactly once and return to the starting city) 5 | % 6 | % Summary: 7 | % 1. A single salesman travels to each of the cities and completes the 8 | % route by returning to the city he started from 9 | % 2. Each city is visited by the salesman exactly once 10 | % 11 | % Input: 12 | % USERCONFIG (structure) with zero or more of the following fields: 13 | % - XY (float) is an Nx2 matrix of city locations, where N is the number of cities 14 | % - DMAT (float) is an NxN matrix of point to point distances/costs 15 | % - POPSIZE (scalar integer) is the size of the population (should be divisible by 4) 16 | % - NUMITER (scalar integer) is the number of desired iterations for the algorithm to run 17 | % - SHOWPROG (scalar logical) shows the GA progress if true 18 | % - SHOWRESULT (scalar logical) shows the GA results if true 19 | % - SHOWWAITBAR (scalar logical) shows a waitbar if true 20 | % 21 | % Input Notes: 22 | % 1. Rather than passing in a structure containing these fields, any/all of 23 | % these inputs can be passed in as parameter/value pairs in any order instead. 24 | % 2. Field/parameter names are case insensitive but must match exactly otherwise. 25 | % 26 | % Output: 27 | % RESULTSTRUCT (structure) with the following fields: 28 | % (in addition to a record of the algorithm configuration) 29 | % - OPTROUTE (integer array) is the best route found by the algorithm 30 | % - MINDIST (scalar float) is the cost of the best route 31 | % 32 | % Usage: 33 | % tsp_ga_hybrid 34 | % -or- 35 | % tsp_ga_hybrid(userConfig) 36 | % -or- 37 | % resultStruct = tsp_ga_hybrid; 38 | % -or- 39 | % resultStruct = tsp_ga_hybrid(userConfig); 40 | % -or- 41 | % [...] = tsp_ga_hybrid('Param1',Value1,'Param2',Value2, ...); 42 | % 43 | % Example: 44 | % % Let the function create an example problem to solve 45 | % tsp_ga_hybrid; 46 | % 47 | % Example: 48 | % % Request the output structure from the solver 49 | % resultStruct = tsp_ga_hybrid; 50 | % 51 | % Example: 52 | % % Pass a random set of user-defined XY points to the solver 53 | % userConfig = struct('xy',10*rand(50,2)); 54 | % resultStruct = tsp_ga_hybrid(userConfig); 55 | % 56 | % Example: 57 | % % Pass a more interesting set of XY points to the solver 58 | % n = 100; 59 | % phi = (sqrt(5)-1)/2; 60 | % theta = 2*pi*phi*(0:n-1); 61 | % rho = (1:n).^phi; 62 | % [x,y] = pol2cart(theta(:),rho(:)); 63 | % xy = 10*([x y]-min([x;y]))/(max([x;y])-min([x;y])); 64 | % userConfig = struct('xy',xy); 65 | % resultStruct = tsp_ga_hybrid(userConfig); 66 | % 67 | % Example: 68 | % % Pass a random set of 3D (XYZ) points to the solver 69 | % xyz = 10*rand(50,3); 70 | % userConfig = struct('xy',xyz); 71 | % resultStruct = tsp_ga_hybrid(userConfig); 72 | % 73 | % Example: 74 | % % Change the defaults for GA population size and number of iterations 75 | % userConfig = struct('popSize',200,'numIter',1e4); 76 | % resultStruct = tsp_ga_hybrid(userConfig); 77 | % 78 | % Example: 79 | % % Turn off the plots but show a waitbar 80 | % userConfig = struct('showProg',false,'showResult',false,'showWaitbar',true); 81 | % resultStruct = tsp_ga_hybrid(userConfig); 82 | % 83 | % See also: mtsp_ga, tsp_nn, tspo_ga, tspof_ga, tspofs_ga 84 | % 85 | % Author: Joseph Kirk 86 | % Email: jdkirk630@gmail.com 87 | % 88 | function varargout = tsp_ga_hybrid(varargin) 89 | 90 | 91 | % 92 | % Initialize default configuration 93 | % 94 | defaultConfig.xy = 10*rand(50,2); 95 | defaultConfig.dmat = []; 96 | defaultConfig.popSize = 100; 97 | defaultConfig.numIter = 1e4; 98 | defaultConfig.showProg = true; 99 | defaultConfig.showStatus = true; 100 | defaultConfig.showResult = true; 101 | defaultConfig.showWaitbar = false; 102 | 103 | 104 | % 105 | % Interpret user configuration inputs 106 | % 107 | if ~nargin 108 | userConfig = struct(); 109 | elseif isstruct(varargin{1}) 110 | userConfig = varargin{1}; 111 | else 112 | try 113 | userConfig = struct(varargin{:}); 114 | catch 115 | error('??? Expected inputs are either a structure or parameter/value pairs'); 116 | end 117 | end 118 | 119 | 120 | % 121 | % Override default configuration with user inputs 122 | % 123 | configStruct = get_config(defaultConfig,userConfig); 124 | 125 | 126 | % 127 | % Extract configuration 128 | % 129 | xy = configStruct.xy; 130 | dmat = configStruct.dmat; 131 | popSize = configStruct.popSize; 132 | numIter = configStruct.numIter; 133 | showProg = configStruct.showProg; 134 | showStatus = configStruct.showStatus; 135 | showResult = configStruct.showResult; 136 | showWaitbar = configStruct.showWaitbar; 137 | if isempty(dmat) 138 | nPoints = size(xy,1); 139 | a = meshgrid(1:nPoints); 140 | dmat = reshape(sqrt(sum((xy(a,:)-xy(a',:)).^2,2)),nPoints,nPoints); 141 | end 142 | 143 | 144 | % 145 | % Verify inputs 146 | % 147 | [N,dims] = size(xy); 148 | [nr,nc] = size(dmat); 149 | if (N ~= nr) || (N ~= nc) 150 | error('??? Invalid XY or DMAT inputs') 151 | end 152 | n = N; 153 | 154 | 155 | % 156 | % Sanity checks 157 | % 158 | popSize = 4*ceil(popSize/4); 159 | numIter = max(1,round(real(numIter(1)))); 160 | showProg = logical(showProg(1)); 161 | showStatus = logical(showStatus(1)); 162 | showResult = logical(showResult(1)); 163 | showWaitbar = logical(showWaitbar(1)); 164 | 165 | 166 | % 167 | % Initialize the population 168 | % 169 | pop = zeros(popSize,n); 170 | pop(1,:) = (1:n); 171 | for k = 2:popSize 172 | pop(k,:) = randperm(n); 173 | end 174 | 175 | 176 | % 177 | % Seed the algorithm with a previous result if available 178 | % 179 | if isfield(userConfig,'optRoute') 180 | optRoute = userConfig.optRoute; 181 | isValid = isequal(pop(1,:),sort(optRoute)); 182 | if isValid 183 | pop(1,:) = optRoute; 184 | end 185 | end 186 | 187 | 188 | % 189 | % Run the GA 190 | % 191 | globalMin = Inf; 192 | distHistory = NaN(1,numIter); 193 | tmpPop = zeros(4,n); 194 | newPop = zeros(popSize,n); 195 | [isClosed,isStopped,isCancelled] = deal(false); 196 | if showProg 197 | hFig = figure('Name','TSP_GA_HYBRID | Current Best Solution', ... 198 | 'Numbertitle','off','CloseRequestFcn',@close_request); 199 | hAx = gca; 200 | if showStatus 201 | [hStatus,isCancelled] = figstatus(0,numIter,[],hFig); 202 | end 203 | end 204 | if showWaitbar 205 | hWait = waitbar(0,'Searching for near-optimal solution ...', ... 206 | 'CreateCancelBtn',@cancel_search); 207 | end 208 | isRunning = true; 209 | for iter = 1:numIter 210 | 211 | % 212 | % EVALUATE SOLUTIONS 213 | % This section of code computes the total cost of each solution 214 | % in the population. The actual code that gets executed uses a 215 | % much faster (vectorized) method to calculate the route lengths 216 | % compared to the double for-loop below (provided for reference) 217 | % but gives the same result. 218 | % 219 | % totalDist = zeros(popSize,1); 220 | % for p = 1:popSize 221 | % d = dmat(pop(p,n),pop(p,1)); 222 | % for k = 2:n 223 | % d = d + dmat(pop(p,k-1),pop(p,k)); 224 | % end 225 | % totalDist(p) = d; 226 | % end 227 | % 228 | row = pop; 229 | col = pop(:,[2:n 1]); 230 | ind = N*(col-1) + row; 231 | totalDist = sum(dmat(ind),2); 232 | 233 | 234 | % 235 | % SELECT THE BEST 236 | % This section of code finds the best solution in the current 237 | % population and stores it if it is better than the previous best. 238 | % 239 | [minDist,index] = min(totalDist); 240 | distHistory(iter) = minDist; 241 | if (minDist < globalMin) 242 | globalMin = minDist; 243 | optRoute = pop(index,:); 244 | if showProg 245 | 246 | % 247 | % Plot the best route 248 | % 249 | rte = optRoute([1:n 1]); 250 | if (dims > 2), plot3(hAx,xy(rte,1),xy(rte,2),xy(rte,3),'r.-'); 251 | else, plot(hAx,xy(rte,1),xy(rte,2),'r.-'); end 252 | title(hAx,sprintf('Total Distance = %1.4f, Iteration = %d',minDist,iter)); 253 | drawnow; 254 | end 255 | end 256 | 257 | 258 | % 259 | % Update the status bar and check cancellation status 260 | % 261 | if showProg && showStatus && ~mod(iter,ceil(numIter/100)) 262 | [hStatus,isCancelled] = figstatus(iter,numIter,hStatus,hFig); 263 | end 264 | if (isStopped || isCancelled) 265 | break 266 | end 267 | 268 | 269 | % 270 | % MODIFY THE POPULATION 271 | % This section of code invokes the genetic algorithm operators. 272 | % In this implementation, solutions are randomly assigned to groups 273 | % of four and the best solution is kept (tournament selection). 274 | % A crossover operator is used for the first half of the iterations 275 | % to help facilitate major route changes. For the remainder of the 276 | % iterations, the best-of-four solution is mutated 3 different ways 277 | % (flip, swap, and slide). 278 | % 279 | randomOrder = randperm(popSize); 280 | for p = 4:4:popSize 281 | rtes = pop(randomOrder(p-3:p),:); 282 | dists = totalDist(randomOrder(p-3:p)); 283 | if (iter < numIter/2) % Use Cross Over and Mutation 284 | 285 | % 286 | % Find the parents 287 | % 288 | [ignore,ord] = sort(dists); %#ok 289 | father = rtes(ord(1),:); 290 | mother = rtes(ord(2),:); 291 | routeInsertionPoints = sort(randperm(n,2)); 292 | I = routeInsertionPoints(1); 293 | J = routeInsertionPoints(2); 294 | len = J - I + 1; 295 | f = father(I:J)'; 296 | m = mother(I:J)'; 297 | 298 | 299 | % 300 | % Create Two Children From Two Best Parents 301 | % 302 | childOne = father; 303 | childOne(any(m(:,ones(n,1)) == father(ones(len,1),:),1)) = []; 304 | childOne = [childOne(1:I-1) mother(I:J) childOne(I:n-len)]; 305 | childTwo = mother; 306 | childTwo(any(f(:,ones(n,1)) == mother(ones(len,1),:),1)) = []; 307 | childTwo = [childTwo(1:I-1) father(I:J) childTwo(I:n-len)]; 308 | 309 | 310 | % 311 | % Mutate One of the Children 312 | % 313 | mutant = childOne; 314 | routeInsertionPoints = sort(randperm(n,2)); 315 | I = routeInsertionPoints(1); 316 | J = routeInsertionPoints(2); 317 | mutant(I:J) = mutant(J:-1:I); 318 | 319 | 320 | % 321 | % Pass the Best Parent, the Children, and the Mutant to the Next Generation 322 | % 323 | newPop(p-3:p,:) = [father; childOne; childTwo; mutant]; 324 | 325 | else % Use Mutation Only 326 | 327 | [ignore,idx] = min(dists); %#ok 328 | bestOf4Route = rtes(idx,:); 329 | routeInsertionPoints = sort(randperm(n,2)); 330 | I = routeInsertionPoints(1); 331 | J = routeInsertionPoints(2); 332 | for k = 1:4 % Mutate the best to get three new routes 333 | tmpPop(k,:) = bestOf4Route; 334 | switch k 335 | case 2 % Flip 336 | tmpPop(k,I:J) = tmpPop(k,J:-1:I); 337 | case 3 % Swap 338 | tmpPop(k,[I J]) = tmpPop(k,[J I]); 339 | case 4 % Slide 340 | tmpPop(k,I:J) = tmpPop(k,[I+1:J I]); 341 | otherwise % Do nothing 342 | end 343 | end 344 | newPop(p-3:p,:) = tmpPop; 345 | end 346 | end 347 | pop = newPop; 348 | 349 | 350 | % 351 | % Update the waitbar 352 | % 353 | if showWaitbar && ~mod(iter,ceil(numIter/325)) 354 | waitbar(iter/numIter,hWait); 355 | end 356 | 357 | end 358 | if showProg && showStatus 359 | figstatus(numIter,numIter,hStatus,hFig); 360 | end 361 | if showWaitbar 362 | delete(hWait); 363 | end 364 | isRunning = false; 365 | if isClosed 366 | delete(hFig); 367 | end 368 | 369 | 370 | % 371 | % Append prior distance history if present 372 | % 373 | if isfield(userConfig,'distHistory') 374 | priorHistory = userConfig.distHistory; 375 | isNan = isnan(priorHistory); 376 | distHistory = [priorHistory(~isNan) distHistory]; 377 | end 378 | 379 | 380 | % 381 | % Format the optimal solution 382 | % 383 | index = find(optRoute == 1,1); 384 | optSolution = [optRoute([index:n 1:index-1]) 1]; 385 | 386 | 387 | % 388 | % Show the final results 389 | % 390 | if showResult 391 | 392 | % 393 | % Plot the GA results 394 | % 395 | figure('Name','TSP_GA_HYBRID | Results','Numbertitle','off'); 396 | subplot(2,2,1); 397 | pclr = ~get(0,'DefaultAxesColor'); 398 | if (dims > 2), plot3(xy(:,1),xy(:,2),xy(:,3),'.','Color',pclr); 399 | else, plot(xy(:,1),xy(:,2),'.','Color',pclr); end 400 | title('City Locations'); 401 | subplot(2,2,2); 402 | imagesc(dmat(optRoute,optRoute)); 403 | title('Distance Matrix'); 404 | subplot(2,2,3); 405 | rte = optSolution; 406 | if (dims > 2), plot3(xy(rte,1),xy(rte,2),xy(rte,3),'r.-'); 407 | else, plot(xy(rte,1),xy(rte,2),'r.-'); end 408 | title(sprintf('Total Distance = %1.4f',minDist)); 409 | subplot(2,2,4); 410 | plot(distHistory,'b','LineWidth',2); 411 | title('Best Solution History'); 412 | set(gca,'XLim',[1 length(distHistory)],'YLim',[0 1.1*max([1 distHistory])]); 413 | end 414 | 415 | 416 | % 417 | % Return output 418 | % 419 | if nargout 420 | 421 | % 422 | % Create anonymous functions for plot generation 423 | % 424 | plotPoints = @(s)plot(s.xy(:,1),s.xy(:,2),'.','Color',~get(gca,'Color')); 425 | plotResult = @(s)plot(s.xy(s.optSolution,1),s.xy(s.optSolution,2),'r.-'); 426 | plotHistory = @(s)plot(s.distHistory,'b-','LineWidth',2); 427 | plotMatrix = @(s)imagesc(s.dmat(s.optSolution,s.optSolution)); 428 | 429 | 430 | % 431 | % Save results in output structure 432 | % 433 | resultStruct = struct( ... 434 | 'xy', xy, ... 435 | 'dmat', dmat, ... 436 | 'popSize', popSize, ... 437 | 'numIter', numIter, ... 438 | 'showProg', showProg, ... 439 | 'showResult', showResult, ... 440 | 'showWaitbar', showWaitbar, ... 441 | 'optRoute', optRoute, ... 442 | 'optSolution', optSolution, ... 443 | 'plotPoints', plotPoints, ... 444 | 'plotResult', plotResult, ... 445 | 'plotHistory', plotHistory, ... 446 | 'plotMatrix', plotMatrix, ... 447 | 'distHistory', distHistory, ... 448 | 'minDist', minDist); 449 | 450 | varargout = {resultStruct}; 451 | 452 | end 453 | 454 | 455 | % 456 | % Nested function to cancel search 457 | % 458 | function cancel_search(varargin) 459 | isStopped = true; 460 | end 461 | 462 | 463 | % 464 | % Nested function to close the figure window 465 | % 466 | function close_request(varargin) 467 | if isRunning 468 | [isClosed,isStopped] = deal(true); 469 | isRunning = false; 470 | else 471 | delete(hFig); 472 | end 473 | end 474 | 475 | end 476 | 477 | -------------------------------------------------------------------------------- /tsp_ga_max.m: -------------------------------------------------------------------------------- 1 | %TSP_GA_MAX Traveling Salesman Problem (TSP) Genetic Algorithm (GA) 2 | % Finds a (near) optimal solution to the TSP by setting up a GA to search 3 | % for the LONGEST route (GREATEST distance for the salesman to travel to 4 | % each city exactly once and return to the starting city) 5 | % 6 | % Summary: 7 | % 1. A single salesman travels to each of the cities and completes the 8 | % route by returning to the city he started from 9 | % 2. Each city is visited by the salesman exactly once 10 | % 11 | % Input: 12 | % USERCONFIG (structure) with zero or more of the following fields: 13 | % - XY (float) is an Nx2 matrix of city locations, where N is the number of cities 14 | % - DMAT (float) is an NxN matrix of point to point distances/costs 15 | % - POPSIZE (scalar integer) is the size of the population (should be divisible by 4) 16 | % - NUMITER (scalar integer) is the number of desired iterations for the algorithm to run 17 | % - SHOWPROG (scalar logical) shows the GA progress if true 18 | % - SHOWRESULT (scalar logical) shows the GA results if true 19 | % - SHOWWAITBAR (scalar logical) shows a waitbar if true 20 | % 21 | % Input Notes: 22 | % 1. Rather than passing in a structure containing these fields, any/all of 23 | % these inputs can be passed in as parameter/value pairs in any order instead. 24 | % 2. Field/parameter names are case insensitive but must match exactly otherwise. 25 | % 26 | % Output: 27 | % RESULTSTRUCT (structure) with the following fields: 28 | % (in addition to a record of the algorithm configuration) 29 | % - OPTROUTE (integer array) is the best route found by the algorithm 30 | % - MAXDIST (scalar float) is the cost of the best route 31 | % 32 | % Usage: 33 | % tsp_ga_max 34 | % -or- 35 | % tsp_ga_max(userConfig) 36 | % -or- 37 | % resultStruct = tsp_ga_max; 38 | % -or- 39 | % resultStruct = tsp_ga_max(userConfig); 40 | % -or- 41 | % [...] = tsp_ga_max('Param1',Value1,'Param2',Value2, ...); 42 | % 43 | % Example: 44 | % % Let the function create an example problem to solve 45 | % tsp_ga_max; 46 | % 47 | % Example: 48 | % % Request the output structure from the solver 49 | % resultStruct = tsp_ga_max; 50 | % 51 | % Example: 52 | % % Pass a random set of user-defined XY points to the solver 53 | % userConfig = struct('xy',10*rand(50,2)); 54 | % resultStruct = tsp_ga_max(userConfig); 55 | % 56 | % Example: 57 | % % Pass a more interesting set of XY points to the solver 58 | % n = 100; 59 | % phi = (sqrt(5)-1)/2; 60 | % theta = 2*pi*phi*(0:n-1); 61 | % rho = (1:n).^phi; 62 | % [x,y] = pol2cart(theta(:),rho(:)); 63 | % xy = 10*([x y]-min([x;y]))/(max([x;y])-min([x;y])); 64 | % userConfig = struct('xy',xy); 65 | % resultStruct = tsp_ga_max(userConfig); 66 | % 67 | % Example: 68 | % % Pass a random set of 3D (XYZ) points to the solver 69 | % xyz = 10*rand(50,3); 70 | % userConfig = struct('xy',xyz); 71 | % resultStruct = tsp_ga_max(userConfig); 72 | % 73 | % Example: 74 | % % Change the defaults for GA population size and number of iterations 75 | % userConfig = struct('popSize',200,'numIter',1e4); 76 | % resultStruct = tsp_ga_max(userConfig); 77 | % 78 | % Example: 79 | % % Turn off the plots but show a waitbar 80 | % userConfig = struct('showProg',false,'showResult',false,'showWaitbar',true); 81 | % resultStruct = tsp_ga_max(userConfig); 82 | % 83 | % See also: mtsp_ga, tsp_nn, tspo_ga, tspof_ga, tspofs_ga 84 | % 85 | % Author: Joseph Kirk 86 | % Email: jdkirk630@gmail.com 87 | % 88 | function varargout = tsp_ga_max(varargin) 89 | 90 | 91 | % 92 | % Initialize default configuration 93 | % 94 | defaultConfig.xy = 10*rand(50,2); 95 | defaultConfig.dmat = []; 96 | defaultConfig.popSize = 100; 97 | defaultConfig.numIter = 1e4; 98 | defaultConfig.showProg = true; 99 | defaultConfig.showStatus = true; 100 | defaultConfig.showResult = true; 101 | defaultConfig.showWaitbar = false; 102 | 103 | 104 | % 105 | % Interpret user configuration inputs 106 | % 107 | if ~nargin 108 | userConfig = struct(); 109 | elseif isstruct(varargin{1}) 110 | userConfig = varargin{1}; 111 | else 112 | try 113 | userConfig = struct(varargin{:}); 114 | catch 115 | error('??? Expected inputs are either a structure or parameter/value pairs'); 116 | end 117 | end 118 | 119 | 120 | % 121 | % Override default configuration with user inputs 122 | % 123 | configStruct = get_config(defaultConfig,userConfig); 124 | 125 | 126 | % 127 | % Extract configuration 128 | % 129 | xy = configStruct.xy; 130 | dmat = configStruct.dmat; 131 | popSize = configStruct.popSize; 132 | numIter = configStruct.numIter; 133 | showProg = configStruct.showProg; 134 | showStatus = configStruct.showStatus; 135 | showResult = configStruct.showResult; 136 | showWaitbar = configStruct.showWaitbar; 137 | if isempty(dmat) 138 | nPoints = size(xy,1); 139 | a = meshgrid(1:nPoints); 140 | dmat = reshape(sqrt(sum((xy(a,:)-xy(a',:)).^2,2)),nPoints,nPoints); 141 | end 142 | 143 | 144 | % 145 | % Verify inputs 146 | % 147 | [N,dims] = size(xy); 148 | [nr,nc] = size(dmat); 149 | if (N ~= nr) || (N ~= nc) 150 | error('??? Invalid XY or DMAT inputs') 151 | end 152 | n = N; 153 | 154 | 155 | % 156 | % Sanity checks 157 | % 158 | popSize = 4*ceil(popSize/4); 159 | numIter = max(1,round(real(numIter(1)))); 160 | showProg = logical(showProg(1)); 161 | showStatus = logical(showStatus(1)); 162 | showResult = logical(showResult(1)); 163 | showWaitbar = logical(showWaitbar(1)); 164 | 165 | 166 | % 167 | % Initialize the population 168 | % 169 | pop = zeros(popSize,n); 170 | pop(1,:) = (1:n); 171 | for k = 2:popSize 172 | pop(k,:) = randperm(n); 173 | end 174 | 175 | 176 | % 177 | % Seed the algorithm with a previous result if available 178 | % 179 | if isfield(userConfig,'optRoute') 180 | optRoute = userConfig.optRoute; 181 | isValid = isequal(pop(1,:),sort(optRoute)); 182 | if isValid 183 | pop(1,:) = optRoute; 184 | end 185 | end 186 | 187 | 188 | % 189 | % Run the GA 190 | % 191 | globalMax = 0; 192 | distHistory = NaN(1,numIter); 193 | tmpPop = zeros(4,n); 194 | newPop = zeros(popSize,n); 195 | [isClosed,isStopped,isCancelled] = deal(false); 196 | if showProg 197 | hFig = figure('Name','TSP_GA_MAX | Current Best Solution', ... 198 | 'Numbertitle','off','CloseRequestFcn',@close_request); 199 | hAx = gca; 200 | if showStatus 201 | [hStatus,isCancelled] = figstatus(0,numIter,[],hFig); 202 | end 203 | end 204 | if showWaitbar 205 | hWait = waitbar(0,'Searching for near-optimal solution ...', ... 206 | 'CreateCancelBtn',@cancel_search); 207 | end 208 | isRunning = true; 209 | for iter = 1:numIter 210 | 211 | % 212 | % EVALUATE SOLUTIONS 213 | % This section of code computes the total cost of each solution 214 | % in the population. The actual code that gets executed uses a 215 | % much faster (vectorized) method to calculate the route lengths 216 | % compared to the double for-loop below (provided for reference) 217 | % but gives the same result. 218 | % 219 | % totalDist = zeros(popSize,1); 220 | % for p = 1:popSize 221 | % d = dmat(pop(p,n),pop(p,1)); 222 | % for k = 2:n 223 | % d = d + dmat(pop(p,k-1),pop(p,k)); 224 | % end 225 | % totalDist(p) = d; 226 | % end 227 | % 228 | row = pop; 229 | col = pop(:,[2:n 1]); 230 | ind = N*(col-1) + row; 231 | totalDist = sum(dmat(ind),2); 232 | 233 | 234 | % 235 | % SELECT THE BEST 236 | % This section of code finds the best solution in the current 237 | % population and stores it if it is better than the previous best. 238 | % 239 | [maxDist,index] = max(totalDist); 240 | distHistory(iter) = maxDist; 241 | if (maxDist > globalMax) 242 | globalMax = maxDist; 243 | optRoute = pop(index,:); 244 | if showProg 245 | 246 | % 247 | % Plot the best route 248 | % 249 | rte = optRoute([1:n 1]); 250 | if (dims > 2), plot3(hAx,xy(rte,1),xy(rte,2),xy(rte,3),'r.-'); 251 | else, plot(hAx,xy(rte,1),xy(rte,2),'r.-'); end 252 | title(hAx,sprintf('Total Distance = %1.4f, Iteration = %d',maxDist,iter)); 253 | drawnow; 254 | end 255 | end 256 | 257 | 258 | % 259 | % Update the status bar and check cancellation status 260 | % 261 | if showProg && showStatus && ~mod(iter,ceil(numIter/100)) 262 | [hStatus,isCancelled] = figstatus(iter,numIter,hStatus,hFig); 263 | end 264 | if (isStopped || isCancelled) 265 | break 266 | end 267 | 268 | 269 | % 270 | % MODIFY THE POPULATION 271 | % This section of code invokes the genetic algorithm operators. 272 | % In this implementation, solutions are randomly assigned to groups 273 | % of four and the best solution is kept (tournament selection). 274 | % The best-of-four solution is then mutated 3 different ways 275 | % (flip, swap, and slide). There is no crossover operator because 276 | % it tends to be highly destructive and rarely improves a decent 277 | % solution. 278 | % 279 | randomOrder = randperm(popSize); 280 | for p = 4:4:popSize 281 | rtes = pop(randomOrder(p-3:p),:); 282 | dists = totalDist(randomOrder(p-3:p)); 283 | [ignore,idx] = max(dists); %#ok 284 | bestOf4Route = rtes(idx,:); 285 | routeInsertionPoints = sort(randperm(n,2)); 286 | I = routeInsertionPoints(1); 287 | J = routeInsertionPoints(2); 288 | for k = 1:4 % Mutate the best to get three new routes 289 | tmpPop(k,:) = bestOf4Route; 290 | switch k 291 | case 2 % Flip 292 | tmpPop(k,I:J) = tmpPop(k,J:-1:I); 293 | case 3 % Swap 294 | tmpPop(k,[I J]) = tmpPop(k,[J I]); 295 | case 4 % Slide 296 | tmpPop(k,I:J) = tmpPop(k,[I+1:J I]); 297 | otherwise % Do nothing 298 | end 299 | end 300 | newPop(p-3:p,:) = tmpPop; 301 | end 302 | pop = newPop; 303 | 304 | 305 | % 306 | % Update the waitbar 307 | % 308 | if showWaitbar && ~mod(iter,ceil(numIter/325)) 309 | waitbar(iter/numIter,hWait); 310 | end 311 | 312 | end 313 | if showProg && showStatus 314 | figstatus(numIter,numIter,hStatus,hFig); 315 | end 316 | if showWaitbar 317 | delete(hWait); 318 | end 319 | isRunning = false; 320 | if isClosed 321 | delete(hFig); 322 | end 323 | 324 | 325 | % 326 | % Append prior distance history if present 327 | % 328 | if isfield(userConfig,'distHistory') 329 | priorHistory = userConfig.distHistory; 330 | isNan = isnan(priorHistory); 331 | distHistory = [priorHistory(~isNan) distHistory]; 332 | end 333 | 334 | 335 | % 336 | % Format the optimal solution 337 | % 338 | index = find(optRoute == 1,1); 339 | optSolution = [optRoute([index:n 1:index-1]) 1]; 340 | 341 | 342 | % 343 | % Show the final results 344 | % 345 | if showResult 346 | 347 | % 348 | % Plot the GA results 349 | % 350 | figure('Name','TSP_GA_MAX | Results','Numbertitle','off'); 351 | subplot(2,2,1); 352 | pclr = ~get(0,'DefaultAxesColor'); 353 | if (dims > 2), plot3(xy(:,1),xy(:,2),xy(:,3),'.','Color',pclr); 354 | else, plot(xy(:,1),xy(:,2),'.','Color',pclr); end 355 | title('City Locations'); 356 | subplot(2,2,2); 357 | imagesc(dmat(optRoute,optRoute)); 358 | title('Distance Matrix'); 359 | subplot(2,2,3); 360 | rte = optRoute([1:n 1]); 361 | if (dims > 2), plot3(xy(rte,1),xy(rte,2),xy(rte,3),'r.-'); 362 | else, plot(xy(rte,1),xy(rte,2),'r.-'); end 363 | title(sprintf('Total Distance = %1.4f',maxDist)); 364 | subplot(2,2,4); 365 | plot(distHistory,'b','LineWidth',2); 366 | title('Best Solution History'); 367 | set(gca,'XLim',[1 length(distHistory)],'YLim',[0 1.1*max([1 distHistory])]); 368 | end 369 | 370 | 371 | % 372 | % Return output 373 | % 374 | if nargout 375 | 376 | % 377 | % Create anonymous functions for plot generation 378 | % 379 | plotPoints = @(s)plot(s.xy(:,1),s.xy(:,2),'.','Color',~get(gca,'Color')); 380 | plotResult = @(s)plot(s.xy(s.optSolution,1),s.xy(s.optSolution,2),'r.-'); 381 | plotHistory = @(s)plot(s.distHistory,'b-','LineWidth',2); 382 | plotMatrix = @(s)imagesc(s.dmat(s.optSolution,s.optSolution)); 383 | 384 | 385 | % 386 | % Save results in output structure 387 | % 388 | resultStruct = struct( ... 389 | 'xy', xy, ... 390 | 'dmat', dmat, ... 391 | 'popSize', popSize, ... 392 | 'numIter', numIter, ... 393 | 'showProg', showProg, ... 394 | 'showResult', showResult, ... 395 | 'showWaitbar', showWaitbar, ... 396 | 'optRoute', optRoute, ... 397 | 'optSolution', optSolution, ... 398 | 'plotPoints', plotPoints, ... 399 | 'plotResult', plotResult, ... 400 | 'plotHistory', plotHistory, ... 401 | 'plotMatrix', plotMatrix, ... 402 | 'distHistory', distHistory, ... 403 | 'maxDist', maxDist); 404 | 405 | varargout = {resultStruct}; 406 | 407 | end 408 | 409 | 410 | % 411 | % Nested function to cancel search 412 | % 413 | function cancel_search(varargin) 414 | isStopped = true; 415 | end 416 | 417 | 418 | % 419 | % Nested function to close the figure window 420 | % 421 | function close_request(varargin) 422 | if isRunning 423 | [isClosed,isStopped] = deal(true); 424 | isRunning = false; 425 | else 426 | delete(hFig); 427 | end 428 | end 429 | 430 | end 431 | 432 | -------------------------------------------------------------------------------- /tsp_ga_turbo.m: -------------------------------------------------------------------------------- 1 | %TSP_GA_TURBO Traveling Salesman Problem (TSP) Genetic Algorithm (GA) 2 | % Finds a (near) optimal solution to the TSP by setting up a GA to search 3 | % for the shortest route (least distance for the salesman to travel to 4 | % each city exactly once and return to the starting city) 5 | % 6 | % Summary: 7 | % 1. A single salesman travels to each of the cities and completes the 8 | % route by returning to the city he started from 9 | % 2. Each city is visited by the salesman exactly once 10 | % 11 | % Input: 12 | % USERCONFIG (structure) with zero or more of the following fields: 13 | % - XY (float) is an Nx2 matrix of city locations, where N is the number of cities 14 | % - DMAT (float) is an NxN matrix of point to point distances/costs 15 | % - POPSIZE (scalar integer) is the size of the population (should be divisible by 4) 16 | % - NUMITER (scalar integer) is the number of desired iterations for the algorithm to run 17 | % - SHOWPROG (scalar logical) shows the GA progress if true 18 | % - SHOWRESULT (scalar logical) shows the GA results if true 19 | % - SHOWWAITBAR (scalar logical) shows a waitbar if true 20 | % 21 | % Input Notes: 22 | % 1. Rather than passing in a structure containing these fields, any/all of 23 | % these inputs can be passed in as parameter/value pairs in any order instead. 24 | % 2. Field/parameter names are case insensitive but must match exactly otherwise. 25 | % 26 | % Output: 27 | % RESULTSTRUCT (structure) with the following fields: 28 | % (in addition to a record of the algorithm configuration) 29 | % - OPTROUTE (integer array) is the best route found by the algorithm 30 | % - MINDIST (scalar float) is the cost of the best route 31 | % 32 | % Usage: 33 | % tsp_ga_turbo 34 | % -or- 35 | % tsp_ga_turbo(userConfig) 36 | % -or- 37 | % resultStruct = tsp_ga_turbo; 38 | % -or- 39 | % resultStruct = tsp_ga_turbo(userConfig); 40 | % -or- 41 | % [...] = tsp_ga_turbo('Param1',Value1,'Param2',Value2, ...); 42 | % 43 | % Example: 44 | % % Let the function create an example problem to solve 45 | % tsp_ga_turbo; 46 | % 47 | % Example: 48 | % % Request the output structure from the solver 49 | % resultStruct = tsp_ga_turbo; 50 | % 51 | % Example: 52 | % % Pass a random set of user-defined XY points to the solver 53 | % userConfig = struct('xy',10*rand(50,2)); 54 | % resultStruct = tsp_ga_turbo(userConfig); 55 | % 56 | % Example: 57 | % % Pass a more interesting set of XY points to the solver 58 | % n = 100; 59 | % phi = (sqrt(5)-1)/2; 60 | % theta = 2*pi*phi*(0:n-1); 61 | % rho = (1:n).^phi; 62 | % [x,y] = pol2cart(theta(:),rho(:)); 63 | % xy = 10*([x y]-min([x;y]))/(max([x;y])-min([x;y])); 64 | % userConfig = struct('xy',xy); 65 | % resultStruct = tsp_ga_turbo(userConfig); 66 | % 67 | % Example: 68 | % % Pass a random set of 3D (XYZ) points to the solver 69 | % xyz = 10*rand(50,3); 70 | % userConfig = struct('xy',xyz); 71 | % resultStruct = tsp_ga_turbo(userConfig); 72 | % 73 | % Example: 74 | % % Change the defaults for GA population size and number of iterations 75 | % userConfig = struct('popSize',200,'numIter',1e4); 76 | % resultStruct = tsp_ga_turbo(userConfig); 77 | % 78 | % Example: 79 | % % Turn off the plots but show a waitbar 80 | % userConfig = struct('showProg',false,'showResult',false,'showWaitbar',true); 81 | % resultStruct = tsp_ga_turbo(userConfig); 82 | % 83 | % See also: mtsp_ga, tsp_nn, tspo_ga, tspof_ga, tspofs_ga 84 | % 85 | % Author: Joseph Kirk 86 | % Email: jdkirk630@gmail.com 87 | % 88 | function varargout = tsp_ga_turbo(varargin) 89 | 90 | 91 | % 92 | % Initialize default configuration 93 | % 94 | defaultConfig.xy = 10*rand(100,2); 95 | defaultConfig.dmat = []; 96 | defaultConfig.popSize = 100; 97 | defaultConfig.numIter = 1e4; 98 | defaultConfig.showProg = true; 99 | defaultConfig.showStatus = true; 100 | defaultConfig.showResult = true; 101 | defaultConfig.showWaitbar = false; 102 | 103 | 104 | % 105 | % Interpret user configuration inputs 106 | % 107 | if ~nargin 108 | userConfig = struct(); 109 | elseif isstruct(varargin{1}) 110 | userConfig = varargin{1}; 111 | else 112 | try 113 | userConfig = struct(varargin{:}); 114 | catch 115 | error('??? Expected inputs are either a structure or parameter/value pairs'); 116 | end 117 | end 118 | 119 | 120 | % 121 | % Override default configuration with user inputs 122 | % 123 | configStruct = get_config(defaultConfig,userConfig); 124 | 125 | 126 | % 127 | % Extract configuration 128 | % 129 | xy = configStruct.xy; 130 | dmat = configStruct.dmat; 131 | popSize = configStruct.popSize; 132 | numIter = configStruct.numIter; 133 | showProg = configStruct.showProg; 134 | showStatus = configStruct.showStatus; 135 | showResult = configStruct.showResult; 136 | showWaitbar = configStruct.showWaitbar; 137 | if isempty(dmat) 138 | nPoints = size(xy,1); 139 | a = meshgrid(1:nPoints); 140 | dmat = reshape(sqrt(sum((xy(a,:)-xy(a',:)).^2,2)),nPoints,nPoints); 141 | end 142 | 143 | 144 | % 145 | % Verify inputs 146 | % 147 | [N,dims] = size(xy); 148 | [nr,nc] = size(dmat); 149 | if (N ~= nr) || (N ~= nc) 150 | error('??? Invalid XY or DMAT inputs') 151 | end 152 | n = N; 153 | 154 | 155 | % 156 | % Sanity checks 157 | % 158 | popSize = 1+3*ceil((popSize-1)/3); 159 | numIter = max(1,round(real(numIter(1)))); 160 | showProg = logical(showProg(1)); 161 | showStatus = logical(showStatus(1)); 162 | showResult = logical(showResult(1)); 163 | showWaitbar = logical(showWaitbar(1)); 164 | 165 | 166 | % 167 | % Initialize the population 168 | % 169 | pop = zeros(popSize,n); 170 | pop(1,:) = (1:n); 171 | for k = 2:popSize 172 | pop(k,:) = randperm(n); 173 | end 174 | 175 | 176 | % 177 | % Seed the algorithm with a previous result if available 178 | % 179 | if isfield(userConfig,'optRoute') 180 | optRoute = userConfig.optRoute; 181 | isValid = isequal(pop(1,:),sort(optRoute)); 182 | if isValid 183 | pop(1,:) = optRoute; 184 | end 185 | end 186 | 187 | 188 | % 189 | % Run the GA 190 | % 191 | globalMin = Inf; 192 | localMin = Inf; 193 | distHistory = NaN(1,numIter); 194 | fullHistory = zeros(popSize,numIter); 195 | tmpPop = zeros(3,n); 196 | newPop = zeros(popSize,n); 197 | [isClosed,isStopped,isCancelled] = deal(false); 198 | if showProg 199 | hFig = figure('Name','TSP_GA_TURBO | Current Best Solution', ... 200 | 'Numbertitle','off','CloseRequestFcn',@close_request); 201 | hAx = gca; 202 | if showStatus 203 | [hStatus,isCancelled] = figstatus(0,numIter,[],hFig); 204 | end 205 | end 206 | if showWaitbar 207 | hWait = waitbar(0,'Searching for near-optimal solution ...', ... 208 | 'CreateCancelBtn',@cancel_search); 209 | end 210 | nSame = 0; 211 | isRunning = true; 212 | for iter = 1:numIter 213 | 214 | % 215 | % EVALUATE SOLUTIONS 216 | % This section of code computes the total cost of each solution 217 | % in the population. The actual code that gets executed uses a 218 | % much faster (vectorized) method to calculate the route lengths 219 | % compared to the double for-loop below (provided for reference) 220 | % but gives the same result. 221 | % 222 | % totalDist = zeros(popSize,1); 223 | % for p = 1:popSize 224 | % d = dmat(pop(p,n),pop(p,1)); 225 | % for k = 2:n 226 | % d = d + dmat(pop(p,k-1),pop(p,k)); 227 | % end 228 | % totalDist(p) = d; 229 | % end 230 | % 231 | row = pop; 232 | col = pop(:,[2:n 1]); 233 | ind = N*(col-1) + row; 234 | totalDist = sum(dmat(ind),2); 235 | 236 | 237 | % 238 | % SELECT THE BEST 239 | % This section of code finds the best solution in the current 240 | % population and stores it if it is better than the previous best. 241 | % 242 | [minDist,index] = min(totalDist); 243 | fullHistory(:,iter) = sort(totalDist); 244 | if (minDist < globalMin) 245 | globalMin = minDist; 246 | optRoute = pop(index,:); 247 | if showProg 248 | 249 | % 250 | % Plot the best route 251 | % 252 | rte = optRoute([1:n 1]); 253 | if (dims > 2), plot3(hAx,xy(rte,1),xy(rte,2),xy(rte,3),'r.-'); 254 | else, plot(hAx,xy(rte,1),xy(rte,2),'r.-'); end 255 | title(hAx,sprintf('Total Distance = %1.4f, Iteration = %d',minDist,iter)); 256 | drawnow; 257 | end 258 | end 259 | distHistory(iter) = globalMin; 260 | 261 | 262 | % 263 | % Update the status bar and check cancellation status 264 | % 265 | if showProg && showStatus && ~mod(iter,ceil(numIter/100)) 266 | [hStatus,isCancelled] = figstatus(iter,numIter,hStatus,hFig); 267 | end 268 | if (isStopped || isCancelled) 269 | break 270 | end 271 | 272 | 273 | % 274 | % Determine whether to restart a stalled search 275 | % 276 | if (minDist < localMin) 277 | localMin = minDist; 278 | nSame = 0; 279 | else 280 | nSame = nSame + 1; 281 | end 282 | if (nSame >= 500) 283 | for p = 1:popSize 284 | pop(p,:) = randperm(n); 285 | end 286 | localMin = Inf; 287 | else 288 | 289 | % 290 | % Genetic Algorithm operators 291 | % 292 | bestRoute = pop(index,:); 293 | for p = 3:3:popSize 294 | routeInsertionPoints = sort(randperm(n,2)); 295 | I = routeInsertionPoints(1); 296 | J = routeInsertionPoints(2); 297 | for k = 1:3 % Mutate the Best to get Three New Routes 298 | tmpPop(k,:) = bestRoute; 299 | switch k 300 | case 1 % Flip 301 | tmpPop(k,I:J) = tmpPop(k,J:-1:I); 302 | case 2 % Swap 303 | tmpPop(k,[I J]) = tmpPop(k,[J I]); 304 | case 3 % Slide 305 | tmpPop(k,I:J) = tmpPop(k,[I+1:J I]); 306 | otherwise % Do Nothing 307 | end 308 | end 309 | newPop(p-2:p,:) = tmpPop; 310 | end 311 | pop = newPop; 312 | pop(popSize,:) = bestRoute; 313 | end 314 | 315 | 316 | % 317 | % Update the waitbar 318 | % 319 | if showWaitbar && ~mod(iter,ceil(numIter/325)) 320 | waitbar(iter/numIter,hWait); 321 | end 322 | 323 | end 324 | if showProg && showStatus 325 | figstatus(numIter,numIter,hStatus,hFig); 326 | end 327 | if showWaitbar 328 | delete(hWait); 329 | end 330 | isRunning = false; 331 | if isClosed 332 | delete(hFig); 333 | end 334 | 335 | 336 | % 337 | % Append prior distance history if present 338 | % 339 | if isfield(userConfig,'distHistory') 340 | priorHistory = userConfig.distHistory; 341 | isNan = isnan(priorHistory); 342 | distHistory = [priorHistory(~isNan) distHistory]; 343 | end 344 | 345 | 346 | % 347 | % Format the optimal solution 348 | % 349 | index = find(optRoute == 1,1); 350 | optSolution = [optRoute([index:n 1:index-1]) 1]; 351 | 352 | 353 | % 354 | % Show the final results 355 | % 356 | if showResult 357 | 358 | % 359 | % Plot the GA results 360 | % 361 | figure('Name','TSP_GA_TURBO | Results','Numbertitle','off'); 362 | subplot(2,2,1); 363 | pclr = ~get(0,'DefaultAxesColor'); 364 | if (dims > 2), plot3(xy(:,1),xy(:,2),xy(:,3),'.','Color',pclr); 365 | else, plot(xy(:,1),xy(:,2),'.','Color',pclr); end 366 | title('City Locations'); 367 | subplot(2,2,2); 368 | imagesc(dmat(optRoute,optRoute)); 369 | title('Distance Matrix'); 370 | subplot(2,2,3); 371 | rte = optSolution; 372 | if (dims > 2), plot3(xy(rte,1),xy(rte,2),xy(rte,3),'r.-'); 373 | else, plot(xy(rte,1),xy(rte,2),'r.-'); end 374 | title(sprintf('Total Distance = %1.4f',globalMin)); 375 | subplot(2,2,4); 376 | plot(distHistory,'b','LineWidth',2); 377 | title('Best Solution History'); 378 | set(gca,'XLim',[1 length(distHistory)],'YLim',[0 1.1*max([1 distHistory])]); 379 | end 380 | 381 | 382 | % 383 | % Return output 384 | % 385 | if nargout 386 | 387 | % 388 | % Create anonymous functions for plot generation 389 | % 390 | plotPoints = @(s)plot(s.xy(:,1),s.xy(:,2),'.','Color',~get(gca,'Color')); 391 | plotResult = @(s)plot(s.xy(s.optSolution,1),s.xy(s.optSolution,2),'r.-'); 392 | plotHistory = @(s)plot(s.distHistory,'b-','LineWidth',2); 393 | plotMatrix = @(s)imagesc(s.dmat(s.optSolution,s.optSolution)); 394 | 395 | 396 | % 397 | % Save results in output structure 398 | % 399 | resultStruct = struct( ... 400 | 'xy', xy, ... 401 | 'dmat', dmat, ... 402 | 'popSize', popSize, ... 403 | 'numIter', numIter, ... 404 | 'showProg', showProg, ... 405 | 'showResult', showResult, ... 406 | 'showWaitbar', showWaitbar, ... 407 | 'optRoute', optRoute, ... 408 | 'optSolution', optSolution, ... 409 | 'plotPoints', plotPoints, ... 410 | 'plotResult', plotResult, ... 411 | 'plotHistory', plotHistory, ... 412 | 'plotMatrix', plotMatrix, ... 413 | 'distHistory', distHistory, ... 414 | 'minDist', globalMin); 415 | 416 | varargout = {resultStruct}; 417 | 418 | end 419 | 420 | 421 | % 422 | % Nested function to cancel search 423 | % 424 | function cancel_search(varargin) 425 | isStopped = true; 426 | end 427 | 428 | 429 | % 430 | % Nested function to close the figure window 431 | % 432 | function close_request(varargin) 433 | if isRunning 434 | [isClosed,isStopped] = deal(true); 435 | isRunning = false; 436 | else 437 | delete(hFig); 438 | end 439 | end 440 | 441 | end 442 | 443 | -------------------------------------------------------------------------------- /tsp_nn.m: -------------------------------------------------------------------------------- 1 | %TSP_NN Traveling Salesman Problem (TSP) Nearest Neighbor (NN) Algorithm 2 | % The Nearest Neighbor algorithm produces different results depending on 3 | % which city is selected as the starting point. This function determines 4 | % the Nearest Neighbor routes for multiple starting points and returns 5 | % the best of those routes 6 | % 7 | % Summary: 8 | % 1. A single salesman travels to each of the cities and completes the 9 | % route by returning to the city he started from 10 | % 2. Each city is visited by the salesman exactly once 11 | % 12 | % Input: 13 | % USERCONFIG (structure) with zero or more of the following fields: 14 | % - XY (float) is an Nx2 matrix of city locations, where N is the number of cities 15 | % - DMAT (float) is an NxN matrix of point to point distances/costs 16 | % - POPSIZE (scalar integer) is the size of the population (should be <= N) 17 | % - SHOWPROG (scalar logical) shows the GA progress if true 18 | % - SHOWRESULT (scalar logical) shows the GA results if true 19 | % - SHOWWAITBAR (scalar logical) shows a waitbar if true 20 | % 21 | % Input Notes: 22 | % 1. Rather than passing in a structure containing these fields, any/all of 23 | % these inputs can be passed in as parameter/value pairs in any order instead. 24 | % 2. Field/parameter names are case insensitive but must match exactly otherwise. 25 | % 26 | % Output: 27 | % RESULTSTRUCT (structure) with the following fields: 28 | % (in addition to a record of the algorithm configuration) 29 | % - OPTROUTE (integer array) is the best route found by the algorithm 30 | % - MINDIST (scalar float) is the cost of the best route 31 | % 32 | % Usage: 33 | % tsp_nn 34 | % -or- 35 | % tsp_nn(userConfig) 36 | % -or- 37 | % resultStruct = tsp_nn; 38 | % -or- 39 | % resultStruct = tsp_nn(userConfig); 40 | % -or- 41 | % [...] = tsp_nn('Param1',Value1,'Param2',Value2, ...); 42 | % 43 | % Example: 44 | % % Let the function create an example problem to solve 45 | % tsp_nn; 46 | % 47 | % Example: 48 | % % Request the output structure from the solver 49 | % resultStruct = tsp_nn; 50 | % 51 | % Example: 52 | % % Pass a random set of user-defined XY points to the solver 53 | % userConfig = struct('xy',10*rand(50,2)); 54 | % resultStruct = tsp_nn(userConfig); 55 | % 56 | % Example: 57 | % % Pass a more interesting set of XY points to the solver 58 | % n = 100; 59 | % phi = (sqrt(5)-1)/2; 60 | % theta = 2*pi*phi*(0:n-1); 61 | % rho = (1:n).^phi; 62 | % [x,y] = pol2cart(theta(:),rho(:)); 63 | % xy = 10*([x y]-min([x;y]))/(max([x;y])-min([x;y])); 64 | % userConfig = struct('xy',xy); 65 | % resultStruct = tsp_nn(userConfig); 66 | % 67 | % Example: 68 | % % Pass a random set of 3D (XYZ) points to the solver 69 | % xyz = 10*rand(50,3); 70 | % userConfig = struct('xy',xyz); 71 | % resultStruct = tsp_nn(userConfig); 72 | % 73 | % Example: 74 | % % Turn off the plots but show a waitbar 75 | % userConfig = struct('showProg',false,'showResult',false,'showWaitbar',true); 76 | % resultStruct = tsp_nn(userConfig); 77 | % 78 | % See also: tsp_ga, tspo_ga, tspof_ga, tspofs_ga 79 | % 80 | % Author: Joseph Kirk 81 | % Email: jdkirk630@gmail.com 82 | % 83 | function varargout = tsp_nn(varargin) 84 | 85 | 86 | % 87 | % Initialize default configuration 88 | % 89 | defaultConfig.xy = 10*rand(100,2); 90 | defaultConfig.dmat = []; 91 | defaultConfig.popSize = Inf; 92 | defaultConfig.showProg = true; 93 | defaultConfig.showStatus = true; 94 | defaultConfig.showResult = true; 95 | defaultConfig.showWaitbar = false; 96 | 97 | 98 | % 99 | % Interpret user configuration inputs 100 | % 101 | if ~nargin 102 | userConfig = struct(); 103 | elseif isstruct(varargin{1}) 104 | userConfig = varargin{1}; 105 | else 106 | try 107 | userConfig = struct(varargin{:}); 108 | catch 109 | error('??? Expected inputs are either a structure or parameter/value pairs'); 110 | end 111 | end 112 | 113 | 114 | % 115 | % Override default configuration with user inputs 116 | % 117 | configStruct = get_config(defaultConfig,userConfig); 118 | 119 | 120 | % 121 | % Extract configuration 122 | % 123 | xy = configStruct.xy; 124 | dmat = configStruct.dmat; 125 | popSize = configStruct.popSize; 126 | showProg = configStruct.showProg; 127 | showStatus = configStruct.showStatus; 128 | showResult = configStruct.showResult; 129 | showWaitbar = configStruct.showWaitbar; 130 | if isempty(dmat) 131 | nPoints = size(xy,1); 132 | a = meshgrid(1:nPoints); 133 | dmat = reshape(sqrt(sum((xy(a,:)-xy(a',:)).^2,2)),nPoints,nPoints); 134 | end 135 | 136 | 137 | % 138 | % Verify inputs 139 | % 140 | [N,dims] = size(xy); 141 | [nr,nc] = size(dmat); 142 | if (N ~= nr) || (N ~= nc) 143 | error('??? Invalid XY or DMAT inputs') 144 | end 145 | n = N; 146 | 147 | 148 | % 149 | % Sanity checks 150 | % 151 | popSize = max(1,min(n,round(real(popSize(1))))); 152 | showProg = logical(showProg(1)); 153 | showStatus = logical(showStatus(1)); 154 | showResult = logical(showResult(1)); 155 | showWaitbar = logical(showWaitbar(1)); 156 | 157 | 158 | % 159 | % Initialize the population 160 | % 161 | pop = zeros(popSize,n); 162 | 163 | 164 | % 165 | % Run the NN 166 | % 167 | distHistory = NaN(1,popSize); 168 | [isClosed,isStopped,isCancelled] = deal(false); 169 | if showProg 170 | hFig = figure('Name','TSP_NN | Current Solution', ... 171 | 'Numbertitle','off','CloseRequestFcn',@close_request); 172 | hAx = gca; 173 | if showStatus 174 | [hStatus,isCancelled] = figstatus(0,popSize,[],hFig); 175 | end 176 | end 177 | if showWaitbar 178 | hWait = waitbar(0,'Searching for near-optimal solution ...', ... 179 | 'CreateCancelBtn',@cancel_search); 180 | end 181 | isRunning = true; 182 | for p = 1:popSize 183 | d = 0; 184 | thisRte = zeros(1,n); 185 | isVisited = false(1,n); 186 | I = p; 187 | isVisited(I) = true; 188 | thisRte(1) = I; 189 | for k = 2:n 190 | dists = dmat(I,:); 191 | dists(logical(isVisited)) = NaN; 192 | dMin = min(dists(~isVisited)); 193 | J = find(dists == dMin,1); 194 | isVisited(J) = true; 195 | thisRte(k) = J; 196 | d = d + dmat(I,J); 197 | I = J; 198 | end 199 | d = d + dmat(I,p); 200 | pop(p,:) = thisRte; 201 | distHistory(p) = d; 202 | 203 | 204 | % 205 | % Plot the current route 206 | % 207 | if showProg 208 | rte = thisRte([1:n 1]); 209 | if (dims > 2), plot3(hAx,xy(rte,1),xy(rte,2),xy(rte,3),'r.-'); 210 | else, plot(hAx,xy(rte,1),xy(rte,2),'r.-'); end 211 | title(hAx,sprintf('Total Distance = %1.4f, Iteration = %d', ... 212 | distHistory(p),p)); 213 | drawnow; 214 | end 215 | 216 | 217 | % 218 | % Update the status bar and check cancellation status 219 | % 220 | if showProg && showStatus && ~mod(p,ceil(popSize/100)) 221 | [hStatus,isCancelled] = figstatus(p,popSize,hStatus,hFig); 222 | end 223 | if (isStopped || isCancelled) 224 | break 225 | end 226 | 227 | 228 | % 229 | % Update the waitbar 230 | % 231 | if showWaitbar && ~mod(p,ceil(popSize/325)) 232 | waitbar(p/popSize,hWait); 233 | end 234 | 235 | end 236 | if showProg && showStatus 237 | figstatus(popSize,popSize,hStatus,hFig); 238 | end 239 | if showWaitbar 240 | delete(hWait); 241 | end 242 | isRunning = false; 243 | if isClosed 244 | delete(hFig); 245 | end 246 | 247 | 248 | % 249 | % Find the minimum distance route 250 | % 251 | [minDist,index] = min(distHistory); 252 | optRoute = pop(index,:); 253 | 254 | 255 | % 256 | % Format the optimal solution 257 | % 258 | index = find(optRoute == 1,1); 259 | optSolution = [optRoute([index:n 1:index-1]) 1]; 260 | 261 | 262 | % 263 | % Show the final results 264 | % 265 | if showResult 266 | 267 | % 268 | % Plot the best route 269 | % 270 | if showProg && ~isClosed 271 | rte = optRoute([1:n 1]); 272 | if (dims > 2), plot3(hAx,xy(rte,1),xy(rte,2),xy(rte,3),'r.-'); 273 | else, plot(hAx,xy(rte,1),xy(rte,2),'r.-'); end 274 | title(hAx,sprintf('Total Distance = %1.4f',minDist)); 275 | end 276 | 277 | % 278 | % Plot the NN results 279 | % 280 | figure('Name','TSP_NN | Results','Numbertitle','off'); 281 | subplot(2,2,1); 282 | pclr = ~get(0,'DefaultAxesColor'); 283 | if (dims > 2), plot3(xy(:,1),xy(:,2),xy(:,3),'.','Color',pclr); 284 | else, plot(xy(:,1),xy(:,2),'.','Color',pclr); end 285 | title('City Locations'); 286 | subplot(2,2,2); 287 | imagesc(dmat(optRoute,optRoute)); 288 | title('Distance Matrix'); 289 | subplot(2,2,3); 290 | rte = optRoute([1:n 1]); 291 | if (dims > 2), plot3(xy(rte,1),xy(rte,2),xy(rte,3),'r.-'); 292 | else, plot(xy(rte,1),xy(rte,2),'r.-'); end 293 | title(sprintf('Total Distance = %1.4f',minDist)); 294 | subplot(2,2,4); 295 | plot(sort(distHistory,'descend'),'b','LineWidth',2); 296 | title('Distances'); 297 | set(gca,'XLim',[0 popSize+1],'YLim',[0 1.1*max([1 distHistory])]); 298 | end 299 | 300 | 301 | % 302 | % Return output 303 | % 304 | if nargout 305 | 306 | % 307 | % Create anonymous functions for plot generation 308 | % 309 | plotPoints = @(s)plot(s.xy(:,1),s.xy(:,2),'.','Color',~get(gca,'Color')); 310 | plotResult = @(s)plot(s.xy(s.optSolution,1),s.xy(s.optSolution,2),'r.-'); 311 | plotHistory = @(s)plot(s.distHistory,'b-','LineWidth',2); 312 | plotMatrix = @(s)imagesc(s.dmat(s.optSolution,s.optSolution)); 313 | 314 | 315 | % 316 | % Save results in output structure 317 | % 318 | resultStruct = struct( ... 319 | 'xy', xy, ... 320 | 'dmat', dmat, ... 321 | 'popSize', popSize, ... 322 | 'showProg', showProg, ... 323 | 'showResult', showResult, ... 324 | 'showWaitbar', showWaitbar, ... 325 | 'optRoute', optRoute, ... 326 | 'optSolution', optSolution, ... 327 | 'plotPoints', plotPoints, ... 328 | 'plotResult', plotResult, ... 329 | 'plotHistory', plotHistory, ... 330 | 'plotMatrix', plotMatrix, ... 331 | 'distHistory', distHistory, ... 332 | 'minDist', minDist, ... 333 | 'pop', pop); 334 | 335 | varargout = {resultStruct}; 336 | 337 | end 338 | 339 | 340 | % 341 | % Nested function to cancel search 342 | % 343 | function cancel_search(varargin) 344 | isStopped = true; 345 | end 346 | 347 | 348 | % 349 | % Nested function to close the figure window 350 | % 351 | function close_request(varargin) 352 | if isRunning 353 | [isClosed,isStopped] = deal(true); 354 | isRunning = false; 355 | else 356 | delete(hFig); 357 | end 358 | end 359 | 360 | end 361 | 362 | -------------------------------------------------------------------------------- /tsp_rs.m: -------------------------------------------------------------------------------- 1 | %TSP_RS Traveling Salesman Problem (TSP) Random Search (RS) Algorithm 2 | % The Random Search algorithm generates random solutions and evaluates 3 | % their fitness 4 | % 5 | % Summary: 6 | % 1. A single salesman travels to each of the cities and completes the 7 | % route by returning to the city he started from 8 | % 2. Each city is visited by the salesman exactly once 9 | % 10 | % Input: 11 | % USERCONFIG (structure) with zero or more of the following fields: 12 | % - XY (float) is an Nx2 matrix of city locations, where N is the number of cities 13 | % - DMAT (float) is an NxN matrix of point to point distances/costs 14 | % - POPSIZE (scalar integer) is the size of the population (should be <= N) 15 | % - NUMITER (scalar integer) is the number of desired iterations for the algorithm to run 16 | % - SHOWPROG (scalar logical) shows the GA progress if true 17 | % - SHOWRESULT (scalar logical) shows the GA results if true 18 | % - SHOWWAITBAR (scalar logical) shows a waitbar if true 19 | % 20 | % Input Notes: 21 | % 1. Rather than passing in a structure containing these fields, any/all of 22 | % these inputs can be passed in as parameter/value pairs in any order instead. 23 | % 2. Field/parameter names are case insensitive but must match exactly otherwise. 24 | % 25 | % Output: 26 | % RESULTSTRUCT (structure) with the following fields: 27 | % (in addition to a record of the algorithm configuration) 28 | % - OPTROUTE (integer array) is the best route found by the algorithm 29 | % - MINDIST (scalar float) is the cost of the best route 30 | % 31 | % Usage: 32 | % tsp_rs 33 | % -or- 34 | % tsp_rs(userConfig) 35 | % -or- 36 | % resultStruct = tsp_rs; 37 | % -or- 38 | % resultStruct = tsp_rs(userConfig); 39 | % -or- 40 | % [...] = tsp_rs('Param1',Value1,'Param2',Value2, ...); 41 | % 42 | % Example: 43 | % % Let the function create an example problem to solve 44 | % tsp_rs; 45 | % 46 | % Example: 47 | % % Request the output structure from the solver 48 | % resultStruct = tsp_rs; 49 | % 50 | % Example: 51 | % % Pass a random set of user-defined XY points to the solver 52 | % userConfig = struct('xy',10*rand(50,2)); 53 | % resultStruct = tsp_rs(userConfig); 54 | % 55 | % Example: 56 | % % Pass a more interesting set of XY points to the solver 57 | % n = 100; 58 | % phi = (sqrt(5)-1)/2; 59 | % theta = 2*pi*phi*(0:n-1); 60 | % rho = (1:n).^phi; 61 | % [x,y] = pol2cart(theta(:),rho(:)); 62 | % xy = 10*([x y]-min([x;y]))/(max([x;y])-min([x;y])); 63 | % userConfig = struct('xy',xy); 64 | % resultStruct = tsp_rs(userConfig); 65 | % 66 | % Example: 67 | % % Pass a random set of 3D (XYZ) points to the solver 68 | % xyz = 10*rand(50,3); 69 | % userConfig = struct('xy',xyz); 70 | % resultStruct = tsp_rs(userConfig); 71 | % 72 | % Example: 73 | % % Turn off the plots but show a waitbar 74 | % userConfig = struct('showProg',false,'showResult',false,'showWaitbar',true); 75 | % resultStruct = tsp_rs(userConfig); 76 | % 77 | % See also: tsp_ga, tspo_ga, tspof_ga, tspofs_ga 78 | % 79 | % Author: Joseph Kirk 80 | % Email: jdkirk630@gmail.com 81 | % 82 | function varargout = tsp_rs(varargin) 83 | 84 | 85 | % 86 | % Initialize default configuration 87 | % 88 | defaultConfig.xy = 10*rand(100,2); 89 | defaultConfig.dmat = []; 90 | defaultConfig.popSize = Inf; 91 | defaultConfig.numIter = 1e4; 92 | defaultConfig.showProg = true; 93 | defaultConfig.showStatus = true; 94 | defaultConfig.showResult = true; 95 | defaultConfig.showWaitbar = false; 96 | 97 | 98 | % 99 | % Interpret user configuration inputs 100 | % 101 | if ~nargin 102 | userConfig = struct(); 103 | elseif isstruct(varargin{1}) 104 | userConfig = varargin{1}; 105 | else 106 | try 107 | userConfig = struct(varargin{:}); 108 | catch 109 | error('??? Expected inputs are either a structure or parameter/value pairs'); 110 | end 111 | end 112 | 113 | 114 | % 115 | % Override default configuration with user inputs 116 | % 117 | configStruct = get_config(defaultConfig,userConfig); 118 | 119 | 120 | % 121 | % Extract configuration 122 | % 123 | xy = configStruct.xy; 124 | dmat = configStruct.dmat; 125 | popSize = configStruct.popSize; 126 | numIter = configStruct.numIter; 127 | showProg = configStruct.showProg; 128 | showStatus = configStruct.showStatus; 129 | showResult = configStruct.showResult; 130 | showWaitbar = configStruct.showWaitbar; 131 | if isempty(dmat) 132 | nPoints = size(xy,1); 133 | a = meshgrid(1:nPoints); 134 | dmat = reshape(sqrt(sum((xy(a,:)-xy(a',:)).^2,2)),nPoints,nPoints); 135 | end 136 | 137 | 138 | % 139 | % Verify inputs 140 | % 141 | [N,dims] = size(xy); 142 | [nr,nc] = size(dmat); 143 | if (N ~= nr) || (N ~= nc) 144 | error('??? Invalid XY or DMAT inputs') 145 | end 146 | n = N; 147 | 148 | 149 | % 150 | % Sanity checks 151 | % 152 | popSize = max(1,min(n,round(real(popSize(1))))); 153 | numIter = max(1,round(real(numIter(1)))); 154 | showProg = logical(showProg(1)); 155 | showStatus = logical(showStatus(1)); 156 | showResult = logical(showResult(1)); 157 | showWaitbar = logical(showWaitbar(1)); 158 | 159 | 160 | % 161 | % Initialize the population 162 | % 163 | pop = zeros(popSize,n); 164 | pop(1,:) = (1:n); 165 | for k = 2:popSize 166 | pop(k,:) = randperm(n); 167 | end 168 | 169 | 170 | % 171 | % Seed the algorithm with a previous result if available 172 | % 173 | if isfield(userConfig,'optRoute') 174 | optRoute = userConfig.optRoute; 175 | isValid = isequal(pop(1,:),sort(optRoute)); 176 | if isValid 177 | pop(1,:) = optRoute; 178 | end 179 | end 180 | 181 | 182 | % 183 | % Run the Random Search (RS) 184 | % 185 | globalMin = Inf; 186 | distHistory = NaN(1,numIter); 187 | [isClosed,isStopped,isCancelled] = deal(false); 188 | if showProg 189 | hFig = figure('Name','TSP_RS | Current Best Solution', ... 190 | 'Numbertitle','off','CloseRequestFcn',@close_request); 191 | hAx = gca; 192 | if showStatus 193 | [hStatus,isCancelled] = figstatus(0,numIter,[],hFig); 194 | end 195 | end 196 | if showWaitbar 197 | hWait = waitbar(0,'Searching for near-optimal solution ...', ... 198 | 'CreateCancelBtn',@cancel_search); 199 | end 200 | isRunning = true; 201 | for iter = 1:numIter 202 | 203 | % 204 | % EVALUATE SOLUTIONS 205 | % This section of code computes the total cost of each solution 206 | % in the population. The actual code that gets executed uses a 207 | % much faster (vectorized) method to calculate the route lengths 208 | % compared to the double for-loop below (provided for reference) 209 | % but gives the same result. 210 | % 211 | % totalDist = zeros(popSize,1); 212 | % for p = 1:popSize 213 | % d = dmat(pop(p,n),pop(p,1)); 214 | % for k = 2:n 215 | % d = d + dmat(pop(p,k-1),pop(p,k)); 216 | % end 217 | % totalDist(p) = d; 218 | % end 219 | % 220 | row = pop; 221 | col = pop(:,[2:n 1]); 222 | ind = N*(col-1) + row; 223 | totalDist = sum(dmat(ind),2); 224 | 225 | 226 | % 227 | % SELECT THE BEST 228 | % This section of code finds the best solution in the current 229 | % population and stores it if it is better than the previous best. 230 | % 231 | [minDist,index] = min(totalDist); 232 | distHistory(iter) = minDist; 233 | if (minDist < globalMin) 234 | globalMin = minDist; 235 | optRoute = pop(index,:); 236 | if showProg 237 | 238 | % 239 | % Plot the best route 240 | % 241 | rte = optRoute([1:n 1]); 242 | if (dims > 2), plot3(hAx,xy(rte,1),xy(rte,2),xy(rte,3),'r.-'); 243 | else, plot(hAx,xy(rte,1),xy(rte,2),'r.-'); end 244 | title(hAx,sprintf('Total Distance = %1.4f, Iteration = %d',minDist,iter)); 245 | drawnow; 246 | end 247 | end 248 | 249 | 250 | % 251 | % Update the status bar and check cancellation status 252 | % 253 | if showProg && showStatus && ~mod(iter,ceil(numIter/100)) 254 | [hStatus,isCancelled] = figstatus(iter,numIter,hStatus,hFig); 255 | end 256 | if (isStopped || isCancelled) 257 | break 258 | end 259 | 260 | 261 | % 262 | % MODIFY THE POPULATION 263 | % This section of code randomly searches for new solutions 264 | % 265 | pop(1,:) = optRoute; 266 | for k = 2:popSize 267 | pop(k,:) = randperm(n); 268 | end 269 | 270 | 271 | % 272 | % Update the waitbar 273 | % 274 | if showWaitbar && ~mod(iter,ceil(numIter/325)) 275 | waitbar(iter/numIter,hWait); 276 | end 277 | 278 | end 279 | if showProg && showStatus 280 | figstatus(numIter,numIter,hStatus,hFig); 281 | end 282 | if showWaitbar 283 | delete(hWait); 284 | end 285 | isRunning = false; 286 | if isClosed 287 | delete(hFig); 288 | end 289 | 290 | 291 | % 292 | % Append prior distance history if present 293 | % 294 | if isfield(userConfig,'distHistory') 295 | priorHistory = userConfig.distHistory; 296 | isNan = isnan(priorHistory); 297 | distHistory = [priorHistory(~isNan) distHistory]; 298 | end 299 | 300 | 301 | % 302 | % Format the optimal solution 303 | % 304 | index = find(optRoute == 1,1); 305 | optSolution = [optRoute([index:n 1:index-1]) 1]; 306 | 307 | 308 | % 309 | % Show the final results 310 | % 311 | if showResult 312 | 313 | % 314 | % Plot the RS results 315 | % 316 | figure('Name','TSP_RS | Results','Numbertitle','off'); 317 | subplot(2,2,1); 318 | pclr = ~get(0,'DefaultAxesColor'); 319 | if (dims > 2), plot3(xy(:,1),xy(:,2),xy(:,3),'.','Color',pclr); 320 | else, plot(xy(:,1),xy(:,2),'.','Color',pclr); end 321 | title('City Locations'); 322 | subplot(2,2,2); 323 | imagesc(dmat(optRoute,optRoute)); 324 | title('Distance Matrix'); 325 | subplot(2,2,3); 326 | rte = optSolution; 327 | if (dims > 2), plot3(xy(rte,1),xy(rte,2),xy(rte,3),'r.-'); 328 | else, plot(xy(rte,1),xy(rte,2),'r.-'); end 329 | title(sprintf('Total Distance = %1.4f',minDist)); 330 | subplot(2,2,4); 331 | plot(distHistory,'b','LineWidth',2); 332 | title('Best Solution History'); 333 | set(gca,'XLim',[1 length(distHistory)],'YLim',[0 1.1*max([1 distHistory])]); 334 | end 335 | 336 | 337 | % 338 | % Return output 339 | % 340 | if nargout 341 | 342 | % 343 | % Create anonymous functions for plot generation 344 | % 345 | plotPoints = @(s)plot(s.xy(:,1),s.xy(:,2),'.','Color',~get(gca,'Color')); 346 | plotResult = @(s)plot(s.xy(s.optSolution,1),s.xy(s.optSolution,2),'r.-'); 347 | plotHistory = @(s)plot(s.distHistory,'b-','LineWidth',2); 348 | plotMatrix = @(s)imagesc(s.dmat(s.optSolution,s.optSolution)); 349 | 350 | 351 | % 352 | % Save results in output structure 353 | % 354 | resultStruct = struct( ... 355 | 'xy', xy, ... 356 | 'dmat', dmat, ... 357 | 'popSize', popSize, ... 358 | 'showProg', showProg, ... 359 | 'showResult', showResult, ... 360 | 'showWaitbar', showWaitbar, ... 361 | 'optRoute', optRoute, ... 362 | 'optSolution', optSolution, ... 363 | 'plotPoints', plotPoints, ... 364 | 'plotResult', plotResult, ... 365 | 'plotHistory', plotHistory, ... 366 | 'plotMatrix', plotMatrix, ... 367 | 'distHistory', distHistory, ... 368 | 'minDist', minDist, ... 369 | 'pop', pop); 370 | 371 | varargout = {resultStruct}; 372 | 373 | end 374 | 375 | 376 | % 377 | % Nested function to cancel search 378 | % 379 | function cancel_search(varargin) 380 | isStopped = true; 381 | end 382 | 383 | 384 | % 385 | % Nested function to close the figure window 386 | % 387 | function close_request(varargin) 388 | if isRunning 389 | [isClosed,isStopped] = deal(true); 390 | isRunning = false; 391 | else 392 | delete(hFig); 393 | end 394 | end 395 | 396 | end 397 | 398 | -------------------------------------------------------------------------------- /tspo_ga.m: -------------------------------------------------------------------------------- 1 | %TSPO_GA Open Traveling Salesman Problem (TSP) Genetic Algorithm (GA) 2 | % Finds a (near) optimal solution to a variation of the TSP by setting up 3 | % a GA to search for the shortest route (least distance for the salesman 4 | % to travel to each city exactly once without returning to the starting city) 5 | % 6 | % Summary: 7 | % 1. A single salesman travels to each of the cities but does not close 8 | % the loop by returning to the city he started from 9 | % 2. Each city is visited by the salesman exactly once 10 | % 11 | % Input: 12 | % USERCONFIG (structure) with zero or more of the following fields: 13 | % - XY (float) is an Nx2 matrix of city locations, where N is the number of cities 14 | % - DMAT (float) is an NxN matrix of point to point distances/costs 15 | % - POPSIZE (scalar integer) is the size of the population (should be divisible by 4) 16 | % - NUMITER (scalar integer) is the number of desired iterations for the algorithm to run 17 | % - SHOWPROG (scalar logical) shows the GA progress if true 18 | % - SHOWRESULT (scalar logical) shows the GA results if true 19 | % - SHOWWAITBAR (scalar logical) shows a waitbar if true 20 | % 21 | % Input Notes: 22 | % 1. Rather than passing in a structure containing these fields, any/all of 23 | % these inputs can be passed in as parameter/value pairs in any order instead. 24 | % 2. Field/parameter names are case insensitive but must match exactly otherwise. 25 | % 26 | % Output: 27 | % RESULTSTRUCT (structure) with the following fields: 28 | % (in addition to a record of the algorithm configuration) 29 | % - OPTROUTE (integer array) is the best route found by the algorithm 30 | % - MINDIST (scalar float) is the cost of the best route 31 | % 32 | % Usage: 33 | % tspo_ga 34 | % -or- 35 | % tspo_ga(userConfig) 36 | % -or- 37 | % resultStruct = tspo_ga; 38 | % -or- 39 | % resultStruct = tspo_ga(userConfig); 40 | % -or- 41 | % [...] = tspo_ga('Param1',Value1,'Param2',Value2, ...); 42 | % 43 | % Example: 44 | % % Let the function create an example problem to solve 45 | % tspo_ga; 46 | % 47 | % Example: 48 | % % Request the output structure from the solver 49 | % resultStruct = tspo_ga; 50 | % 51 | % Example: 52 | % % Pass a random set of user-defined XY points to the solver 53 | % userConfig = struct('xy',10*rand(50,2)); 54 | % resultStruct = tspo_ga(userConfig); 55 | % 56 | % Example: 57 | % % Pass a more interesting set of XY points to the solver 58 | % n = 50; 59 | % phi = (sqrt(5)-1)/2; 60 | % theta = 2*pi*phi*(0:n-1); 61 | % rho = (1:n).^phi; 62 | % [x,y] = pol2cart(theta(:),rho(:)); 63 | % xy = 10*([x y]-min([x;y]))/(max([x;y])-min([x;y])); 64 | % userConfig = struct('xy',xy); 65 | % resultStruct = tspo_ga(userConfig); 66 | % 67 | % Example: 68 | % % Pass a random set of 3D (XYZ) points to the solver 69 | % xyz = 10*rand(50,3); 70 | % userConfig = struct('xy',xyz); 71 | % resultStruct = tspo_ga(userConfig); 72 | % 73 | % Example: 74 | % % Change the defaults for GA population size and number of iterations 75 | % userConfig = struct('popSize',200,'numIter',1e4); 76 | % resultStruct = tspo_ga(userConfig); 77 | % 78 | % Example: 79 | % % Turn off the plots but show a waitbar 80 | % userConfig = struct('showProg',false,'showResult',false,'showWaitbar',true); 81 | % resultStruct = tspo_ga(userConfig); 82 | % 83 | % See also: tsp_ga, tsp_nn, tspof_ga, tspofs_ga 84 | % 85 | % Author: Joseph Kirk 86 | % Email: jdkirk630@gmail.com 87 | % 88 | function varargout = tspo_ga(varargin) 89 | 90 | 91 | % 92 | % Initialize default configuration 93 | % 94 | defaultConfig.xy = 10*rand(50,2); 95 | defaultConfig.dmat = []; 96 | defaultConfig.popSize = 100; 97 | defaultConfig.numIter = 1e4; 98 | defaultConfig.showProg = true; 99 | defaultConfig.showStatus = true; 100 | defaultConfig.showResult = true; 101 | defaultConfig.showWaitbar = false; 102 | 103 | 104 | % 105 | % Interpret user configuration inputs 106 | % 107 | if ~nargin 108 | userConfig = struct(); 109 | elseif isstruct(varargin{1}) 110 | userConfig = varargin{1}; 111 | else 112 | try 113 | userConfig = struct(varargin{:}); 114 | catch 115 | error('??? Expected inputs are either a structure or parameter/value pairs'); 116 | end 117 | end 118 | 119 | 120 | % 121 | % Override default configuration with user inputs 122 | % 123 | configStruct = get_config(defaultConfig,userConfig); 124 | 125 | 126 | % 127 | % Extract configuration 128 | % 129 | xy = configStruct.xy; 130 | dmat = configStruct.dmat; 131 | popSize = configStruct.popSize; 132 | numIter = configStruct.numIter; 133 | showProg = configStruct.showProg; 134 | showStatus = configStruct.showStatus; 135 | showResult = configStruct.showResult; 136 | showWaitbar = configStruct.showWaitbar; 137 | if isempty(dmat) 138 | nPoints = size(xy,1); 139 | a = meshgrid(1:nPoints); 140 | dmat = reshape(sqrt(sum((xy(a,:)-xy(a',:)).^2,2)),nPoints,nPoints); 141 | end 142 | 143 | 144 | % 145 | % Verify inputs 146 | % 147 | [N,dims] = size(xy); 148 | [nr,nc] = size(dmat); 149 | if (N ~= nr) || (N ~= nc) 150 | error('??? Invalid XY or DMAT inputs') 151 | end 152 | n = N; 153 | 154 | 155 | % 156 | % Sanity checks 157 | % 158 | popSize = 4*ceil(popSize/4); 159 | numIter = max(1,round(real(numIter(1)))); 160 | showProg = logical(showProg(1)); 161 | showStatus = logical(showStatus(1)); 162 | showResult = logical(showResult(1)); 163 | showWaitbar = logical(showWaitbar(1)); 164 | 165 | 166 | % 167 | % Initialize the population 168 | % 169 | pop = zeros(popSize,n); 170 | pop(1,:) = (1:n); 171 | for k = 2:popSize 172 | pop(k,:) = randperm(n); 173 | end 174 | 175 | 176 | % 177 | % Seed the algorithm with a previous result if available 178 | % 179 | if isfield(userConfig,'optRoute') 180 | optRoute = userConfig.optRoute; 181 | isValid = isequal(pop(1,:),sort(optRoute)); 182 | if isValid 183 | pop(1,:) = optRoute; 184 | end 185 | end 186 | 187 | 188 | % 189 | % Run the GA 190 | % 191 | globalMin = Inf; 192 | distHistory = NaN(1,numIter); 193 | tmpPop = zeros(4,n); 194 | newPop = zeros(popSize,n); 195 | [isClosed,isStopped,isCancelled] = deal(false); 196 | if showProg 197 | hFig = figure('Name','TSPO_GA | Current Best Solution', ... 198 | 'Numbertitle','off','CloseRequestFcn',@close_request); 199 | hAx = gca; 200 | if showStatus 201 | [hStatus,isCancelled] = figstatus(0,numIter,[],hFig); 202 | end 203 | end 204 | if showWaitbar 205 | hWait = waitbar(0,'Searching for near-optimal solution ...', ... 206 | 'CreateCancelBtn',@cancel_search); 207 | end 208 | isRunning = true; 209 | for iter = 1:numIter 210 | 211 | % 212 | % EVALUATE SOLUTIONS 213 | % This section of code computes the total cost of each solution 214 | % in the population. The actual code that gets executed uses a 215 | % much faster (vectorized) method to calculate the route lengths 216 | % compared to the double for-loop below (provided for reference) 217 | % but gives the same result. 218 | % 219 | % totalDist = zeros(popSize,1); 220 | % for p = 1:popSize 221 | % d = 0; 222 | % for k = 2:n 223 | % d = d + dmat(pop(p,k-1),pop(p,k)); 224 | % end 225 | % totalDist(p) = d; 226 | % end 227 | % 228 | row = pop(:,1:n-1); 229 | col = pop(:,2:n); 230 | ind = N*(col-1) + row; 231 | totalDist = sum(dmat(ind),2); 232 | 233 | 234 | % 235 | % SELECT THE BEST 236 | % This section of code finds the best solution in the current 237 | % population and stores it if it is better than the previous best. 238 | % 239 | [minDist,index] = min(totalDist); 240 | distHistory(iter) = minDist; 241 | if (minDist < globalMin) 242 | globalMin = minDist; 243 | optRoute = pop(index,:); 244 | if showProg 245 | 246 | % 247 | % Plot the best route 248 | % 249 | if (dims > 2), plot3(hAx,xy(optRoute,1),xy(optRoute,2),xy(optRoute,3),'r.-'); 250 | else, plot(hAx,xy(optRoute,1),xy(optRoute,2),'r.-'); end 251 | title(hAx,sprintf('Total Distance = %1.4f, Iteration = %d',minDist,iter)); 252 | drawnow; 253 | end 254 | end 255 | 256 | 257 | % 258 | % Update the status bar and check cancellation status 259 | % 260 | if showProg && showStatus && ~mod(iter,ceil(numIter/100)) 261 | [hStatus,isCancelled] = figstatus(iter,numIter,hStatus,hFig); 262 | end 263 | if (isStopped || isCancelled) 264 | break 265 | end 266 | 267 | 268 | % 269 | % MODIFY THE POPULATION 270 | % This section of code invokes the genetic algorithm operators. 271 | % In this implementation, solutions are randomly assigned to groups 272 | % of four and the best solution is kept (tournament selection). 273 | % The best-of-four solution is then mutated 3 different ways 274 | % (flip, swap, and slide). There is no crossover operator because 275 | % it tends to be highly destructive and rarely improves a decent 276 | % solution. 277 | % 278 | randomOrder = randperm(popSize); 279 | for p = 4:4:popSize 280 | rtes = pop(randomOrder(p-3:p),:); 281 | dists = totalDist(randomOrder(p-3:p)); 282 | [ignore,idx] = min(dists); %#ok 283 | bestOf4Route = rtes(idx,:); 284 | routeInsertionPoints = sort(randperm(n,2)); 285 | I = routeInsertionPoints(1); 286 | J = routeInsertionPoints(2); 287 | for k = 1:4 % Mutate the best to get three new routes 288 | tmpPop(k,:) = bestOf4Route; 289 | switch k 290 | case 2 % Flip 291 | tmpPop(k,I:J) = tmpPop(k,J:-1:I); 292 | case 3 % Swap 293 | tmpPop(k,[I J]) = tmpPop(k,[J I]); 294 | case 4 % Slide 295 | tmpPop(k,I:J) = tmpPop(k,[I+1:J I]); 296 | otherwise % Do nothing 297 | end 298 | end 299 | newPop(p-3:p,:) = tmpPop; 300 | end 301 | pop = newPop; 302 | 303 | 304 | % 305 | % Update the waitbar 306 | % 307 | if showWaitbar && ~mod(iter,ceil(numIter/325)) 308 | waitbar(iter/numIter,hWait); 309 | end 310 | 311 | end 312 | if showProg && showStatus 313 | figstatus(numIter,numIter,hStatus,hFig); 314 | end 315 | if showWaitbar 316 | delete(hWait); 317 | end 318 | isRunning = false; 319 | if isClosed 320 | delete(hFig); 321 | end 322 | 323 | 324 | % 325 | % Append prior distance history if present 326 | % 327 | if isfield(userConfig,'distHistory') 328 | priorHistory = userConfig.distHistory; 329 | isNan = isnan(priorHistory); 330 | distHistory = [priorHistory(~isNan) distHistory]; 331 | end 332 | 333 | 334 | % 335 | % Show the final results 336 | % 337 | if showResult 338 | 339 | % 340 | % Plot the GA results 341 | % 342 | figure('Name','TSPO_GA | Results','Numbertitle','off'); 343 | subplot(2,2,1); 344 | pclr = ~get(0,'DefaultAxesColor'); 345 | if (dims > 2), plot3(xy(:,1),xy(:,2),xy(:,3),'.','Color',pclr); 346 | else, plot(xy(:,1),xy(:,2),'.','Color',pclr); end 347 | title('City Locations'); 348 | subplot(2,2,2); 349 | imagesc(dmat(optRoute,optRoute)); 350 | title('Distance Matrix'); 351 | subplot(2,2,3); 352 | if (dims > 2), plot3(xy(optRoute,1),xy(optRoute,2),xy(optRoute,3),'r.-'); 353 | else, plot(xy(optRoute,1),xy(optRoute,2),'r.-'); end 354 | title(sprintf('Total Distance = %1.4f',minDist)); 355 | subplot(2,2,4); 356 | plot(distHistory,'b','LineWidth',2); 357 | title('Best Solution History'); 358 | set(gca,'XLim',[1 length(distHistory)],'YLim',[0 1.1*max([1 distHistory])]); 359 | end 360 | 361 | 362 | % 363 | % Return output 364 | % 365 | if nargout 366 | 367 | % 368 | % Create anonymous functions for plot generation 369 | % 370 | plotPoints = @(s)plot(s.xy(:,1),s.xy(:,2),'.','Color',~get(gca,'Color')); 371 | plotResult = @(s)plot(s.xy(s.optSolution,1),s.xy(s.optSolution,2),'r.-'); 372 | plotHistory = @(s)plot(s.distHistory,'b-','LineWidth',2); 373 | plotMatrix = @(s)imagesc(s.dmat(s.optSolution,s.optSolution)); 374 | 375 | 376 | % 377 | % Save results in output structure 378 | % 379 | resultStruct = struct( ... 380 | 'xy', xy, ... 381 | 'dmat', dmat, ... 382 | 'popSize', popSize, ... 383 | 'numIter', numIter, ... 384 | 'showProg', showProg, ... 385 | 'showResult', showResult, ... 386 | 'showWaitbar', showWaitbar, ... 387 | 'optRoute', optRoute, ... 388 | 'optSolution', optRoute, ... 389 | 'plotPoints', plotPoints, ... 390 | 'plotResult', plotResult, ... 391 | 'plotHistory', plotHistory, ... 392 | 'plotMatrix', plotMatrix, ... 393 | 'distHistory', distHistory, ... 394 | 'minDist', minDist); 395 | 396 | varargout = {resultStruct}; 397 | 398 | end 399 | 400 | 401 | % 402 | % Nested function to cancel search 403 | % 404 | function cancel_search(varargin) 405 | isStopped = true; 406 | end 407 | 408 | 409 | % 410 | % Nested function to close the figure window 411 | % 412 | function close_request(varargin) 413 | if isRunning 414 | [isClosed,isStopped] = deal(true); 415 | isRunning = false; 416 | else 417 | delete(hFig); 418 | end 419 | end 420 | 421 | end 422 | 423 | -------------------------------------------------------------------------------- /tspo_ga_turbo.m: -------------------------------------------------------------------------------- 1 | %TSPO_GA_TURBO Open Traveling Salesman Problem (TSP) Genetic Algorithm (GA) 2 | % Finds a (near) optimal solution to a variation of the TSP by setting up 3 | % a GA to search for the shortest route (least distance for the salesman 4 | % to travel to each city exactly once without returning to the starting city) 5 | % 6 | % Summary: 7 | % 1. A single salesman travels to each of the cities but does not close 8 | % the loop by returning to the city he started from 9 | % 2. Each city is visited by the salesman exactly once 10 | % 11 | % Input: 12 | % USERCONFIG (structure) with zero or more of the following fields: 13 | % - XY (float) is an Nx2 matrix of city locations, where N is the number of cities 14 | % - DMAT (float) is an NxN matrix of point to point distances/costs 15 | % - POPSIZE (scalar integer) is the size of the population (should be divisible by 4) 16 | % - NUMITER (scalar integer) is the number of desired iterations for the algorithm to run 17 | % - SHOWPROG (scalar logical) shows the GA progress if true 18 | % - SHOWRESULT (scalar logical) shows the GA results if true 19 | % - SHOWWAITBAR (scalar logical) shows a waitbar if true 20 | % 21 | % Input Notes: 22 | % 1. Rather than passing in a structure containing these fields, any/all of 23 | % these inputs can be passed in as parameter/value pairs in any order instead. 24 | % 2. Field/parameter names are case insensitive but must match exactly otherwise. 25 | % 26 | % Output: 27 | % RESULTSTRUCT (structure) with the following fields: 28 | % (in addition to a record of the algorithm configuration) 29 | % - OPTROUTE (integer array) is the best route found by the algorithm 30 | % - MINDIST (scalar float) is the cost of the best route 31 | % 32 | % Usage: 33 | % tspo_ga_turbo 34 | % -or- 35 | % tspo_ga_turbo(userConfig) 36 | % -or- 37 | % resultStruct = tspo_ga_turbo; 38 | % -or- 39 | % resultStruct = tspo_ga_turbo(userConfig); 40 | % -or- 41 | % [...] = tspo_ga_turbo('Param1',Value1,'Param2',Value2, ...); 42 | % 43 | % Example: 44 | % % Let the function create an example problem to solve 45 | % tspo_ga_turbo; 46 | % 47 | % Example: 48 | % % Request the output structure from the solver 49 | % resultStruct = tspo_ga_turbo; 50 | % 51 | % Example: 52 | % % Pass a random set of user-defined XY points to the solver 53 | % userConfig = struct('xy',10*rand(50,2)); 54 | % resultStruct = tspo_ga_turbo(userConfig); 55 | % 56 | % Example: 57 | % % Pass a more interesting set of XY points to the solver 58 | % n = 50; 59 | % phi = (sqrt(5)-1)/2; 60 | % theta = 2*pi*phi*(0:n-1); 61 | % rho = (1:n).^phi; 62 | % [x,y] = pol2cart(theta(:),rho(:)); 63 | % xy = 10*([x y]-min([x;y]))/(max([x;y])-min([x;y])); 64 | % userConfig = struct('xy',xy); 65 | % resultStruct = tspo_ga_turbo(userConfig); 66 | % 67 | % Example: 68 | % % Pass a random set of 3D (XYZ) points to the solver 69 | % xyz = 10*rand(50,3); 70 | % userConfig = struct('xy',xyz); 71 | % resultStruct = tspo_ga_turbo(userConfig); 72 | % 73 | % Example: 74 | % % Change the defaults for GA population size and number of iterations 75 | % userConfig = struct('popSize',200,'numIter',1e4); 76 | % resultStruct = tspo_ga_turbo(userConfig); 77 | % 78 | % Example: 79 | % % Turn off the plots but show a waitbar 80 | % userConfig = struct('showProg',false,'showResult',false,'showWaitbar',true); 81 | % resultStruct = tspo_ga_turbo(userConfig); 82 | % 83 | % See also: tsp_ga, tsp_nn, tspof_ga, tspofs_ga 84 | % 85 | % Author: Joseph Kirk 86 | % Email: jdkirk630@gmail.com 87 | % 88 | function varargout = tspo_ga_turbo(varargin) 89 | 90 | 91 | % 92 | % Initialize default configuration 93 | % 94 | defaultConfig.xy = 10*rand(200,2); 95 | defaultConfig.dmat = []; 96 | defaultConfig.popSize = 100; 97 | defaultConfig.numIter = 1e4; 98 | defaultConfig.showProg = true; 99 | defaultConfig.showStatus = true; 100 | defaultConfig.showResult = true; 101 | defaultConfig.showWaitbar = false; 102 | 103 | 104 | % 105 | % Interpret user configuration inputs 106 | % 107 | if ~nargin 108 | userConfig = struct(); 109 | elseif isstruct(varargin{1}) 110 | userConfig = varargin{1}; 111 | else 112 | try 113 | userConfig = struct(varargin{:}); 114 | catch 115 | error('??? Expected inputs are either a structure or parameter/value pairs'); 116 | end 117 | end 118 | 119 | 120 | % 121 | % Override default configuration with user inputs 122 | % 123 | configStruct = get_config(defaultConfig,userConfig); 124 | 125 | 126 | % 127 | % Extract configuration 128 | % 129 | xy = configStruct.xy; 130 | dmat = configStruct.dmat; 131 | popSize = configStruct.popSize; 132 | numIter = configStruct.numIter; 133 | showProg = configStruct.showProg; 134 | showStatus = configStruct.showStatus; 135 | showResult = configStruct.showResult; 136 | showWaitbar = configStruct.showWaitbar; 137 | if isempty(dmat) 138 | nPoints = size(xy,1); 139 | a = meshgrid(1:nPoints); 140 | dmat = reshape(sqrt(sum((xy(a,:)-xy(a',:)).^2,2)),nPoints,nPoints); 141 | end 142 | 143 | 144 | % 145 | % Verify inputs 146 | % 147 | [N,dims] = size(xy); 148 | [nr,nc] = size(dmat); 149 | if (N ~= nr) || (N ~= nc) 150 | error('??? Invalid XY or DMAT inputs') 151 | end 152 | n = N; 153 | 154 | 155 | % 156 | % Sanity checks 157 | % 158 | popSize = 1+3*ceil((popSize-1)/3); 159 | numIter = max(1,round(real(numIter(1)))); 160 | showProg = logical(showProg(1)); 161 | showStatus = logical(showStatus(1)); 162 | showResult = logical(showResult(1)); 163 | showWaitbar = logical(showWaitbar(1)); 164 | 165 | 166 | % 167 | % Initialize the population 168 | % 169 | pop = zeros(popSize,n); 170 | pop(1,:) = (1:n); 171 | for k = 2:popSize 172 | pop(k,:) = randperm(n); 173 | end 174 | 175 | 176 | % 177 | % Seed the algorithm with a previous result if available 178 | % 179 | if isfield(userConfig,'optRoute') 180 | optRoute = userConfig.optRoute; 181 | isValid = isequal(pop(1,:),sort(optRoute)); 182 | if isValid 183 | pop(1,:) = optRoute; 184 | end 185 | end 186 | 187 | 188 | % 189 | % Run the GA 190 | % 191 | globalMin = Inf; 192 | localMin = Inf; 193 | distHistory = NaN(1,numIter); 194 | fullHistory = zeros(popSize,numIter); 195 | tmpPop = zeros(3,n); 196 | newPop = zeros(popSize,n); 197 | [isClosed,isStopped,isCancelled] = deal(false); 198 | if showProg 199 | hFig = figure('Name','TSPO_GA_TURBO | Current Best Solution', ... 200 | 'Numbertitle','off','CloseRequestFcn',@close_request); 201 | hAx = gca; 202 | if showStatus 203 | [hStatus,isCancelled] = figstatus(0,numIter,[],hFig); 204 | end 205 | end 206 | if showWaitbar 207 | hWait = waitbar(0,'Searching for near-optimal solution ...', ... 208 | 'CreateCancelBtn',@cancel_search); 209 | end 210 | nSame = 0; 211 | isRunning = true; 212 | for iter = 1:numIter 213 | 214 | % 215 | % EVALUATE SOLUTIONS 216 | % This section of code computes the total cost of each solution 217 | % in the population. The actual code that gets executed uses a 218 | % much faster (vectorized) method to calculate the route lengths 219 | % compared to the double for-loop below (provided for reference) 220 | % but gives the same result. 221 | % 222 | % totalDist = zeros(popSize,1); 223 | % for p = 1:popSize 224 | % d = 0; 225 | % for k = 2:n 226 | % d = d + dmat(pop(p,k-1),pop(p,k)); 227 | % end 228 | % totalDist(p) = d; 229 | % end 230 | % 231 | row = pop(:,1:n-1); 232 | col = pop(:,2:n); 233 | ind = N*(col-1) + row; 234 | totalDist = sum(dmat(ind),2); 235 | 236 | 237 | % 238 | % SELECT THE BEST 239 | % This section of code finds the best solution in the current 240 | % population and stores it if it is better than the previous best. 241 | % 242 | [minDist,index] = min(totalDist); 243 | fullHistory(:,iter) = sort(totalDist); 244 | if (minDist < globalMin) 245 | globalMin = minDist; 246 | optRoute = pop(index,:); 247 | if showProg 248 | 249 | % 250 | % Plot the best route 251 | % 252 | if (dims > 2), plot3(hAx,xy(optRoute,1),xy(optRoute,2),xy(optRoute,3),'r.-'); 253 | else, plot(hAx,xy(optRoute,1),xy(optRoute,2),'r.-'); end 254 | title(hAx,sprintf('Total Distance = %1.4f, Iteration = %d',minDist,iter)); 255 | drawnow; 256 | end 257 | end 258 | distHistory(iter) = globalMin; 259 | 260 | 261 | % 262 | % Update the status bar and check cancellation status 263 | % 264 | if showProg && showStatus && ~mod(iter,ceil(numIter/100)) 265 | [hStatus,isCancelled] = figstatus(iter,numIter,hStatus,hFig); 266 | end 267 | if (isStopped || isCancelled) 268 | break 269 | end 270 | 271 | 272 | % 273 | % Determine whether to restart a stalled search 274 | % 275 | if (minDist < localMin) 276 | localMin = minDist; 277 | nSame = 0; 278 | else 279 | nSame = nSame + 1; 280 | end 281 | if (nSame >= 500) 282 | for p = 1:popSize 283 | pop(p,:) = randperm(n); 284 | end 285 | localMin = Inf; 286 | else 287 | 288 | % 289 | % Genetic Algorithm operators 290 | % 291 | bestRoute = pop(index,:); 292 | for p = 3:3:popSize 293 | routeInsertionPoints = sort(randperm(n,2)); 294 | I = routeInsertionPoints(1); 295 | J = routeInsertionPoints(2); 296 | for k = 1:3 % Mutate the Best to get Three New Routes 297 | tmpPop(k,:) = bestRoute; 298 | switch k 299 | case 1 % Flip 300 | tmpPop(k,I:J) = tmpPop(k,J:-1:I); 301 | case 2 % Swap 302 | tmpPop(k,[I J]) = tmpPop(k,[J I]); 303 | case 3 % Slide 304 | tmpPop(k,I:J) = tmpPop(k,[I+1:J I]); 305 | otherwise % Do Nothing 306 | end 307 | end 308 | newPop(p-2:p,:) = tmpPop; 309 | end 310 | pop = newPop; 311 | pop(popSize,:) = bestRoute; 312 | end 313 | 314 | 315 | % 316 | % Update the waitbar 317 | % 318 | if showWaitbar && ~mod(iter,ceil(numIter/325)) 319 | waitbar(iter/numIter,hWait); 320 | end 321 | 322 | end 323 | if showProg && showStatus 324 | figstatus(numIter,numIter,hStatus,hFig); 325 | end 326 | if showWaitbar 327 | delete(hWait); 328 | end 329 | isRunning = false; 330 | if isClosed 331 | delete(hFig); 332 | end 333 | 334 | 335 | % 336 | % Append prior distance history if present 337 | % 338 | if isfield(userConfig,'distHistory') 339 | priorHistory = userConfig.distHistory; 340 | isNan = isnan(priorHistory); 341 | distHistory = [priorHistory(~isNan) distHistory]; 342 | end 343 | 344 | 345 | % 346 | % Show the final results 347 | % 348 | if showResult 349 | 350 | % 351 | % Plot the GA results 352 | % 353 | figure('Name','TSPO_GA_TURBO | Results','Numbertitle','off'); 354 | subplot(2,2,1); 355 | pclr = ~get(0,'DefaultAxesColor'); 356 | if (dims > 2), plot3(xy(:,1),xy(:,2),xy(:,3),'.','Color',pclr); 357 | else, plot(xy(:,1),xy(:,2),'.','Color',pclr); end 358 | title('City Locations'); 359 | subplot(2,2,2); 360 | imagesc(dmat(optRoute,optRoute)); 361 | title('Distance Matrix'); 362 | subplot(2,2,3); 363 | if (dims > 2), plot3(xy(optRoute,1),xy(optRoute,2),xy(optRoute,3),'r.-'); 364 | else, plot(xy(optRoute,1),xy(optRoute,2),'r.-'); end 365 | title(sprintf('Total Distance = %1.4f',globalMin)); 366 | subplot(2,2,4); 367 | plot(distHistory,'b','LineWidth',2); 368 | title('Best Solution History'); 369 | set(gca,'XLim',[1 length(distHistory)],'YLim',[0 1.1*max([1 distHistory])]); 370 | end 371 | 372 | 373 | % 374 | % Return output 375 | % 376 | if nargout 377 | 378 | % 379 | % Create anonymous functions for plot generation 380 | % 381 | plotPoints = @(s)plot(s.xy(:,1),s.xy(:,2),'.','Color',~get(gca,'Color')); 382 | plotResult = @(s)plot(s.xy(s.optSolution,1),s.xy(s.optSolution,2),'r.-'); 383 | plotHistory = @(s)plot(s.distHistory,'b-','LineWidth',2); 384 | plotMatrix = @(s)imagesc(s.dmat(s.optSolution,s.optSolution)); 385 | 386 | 387 | % 388 | % Save results in output structure 389 | % 390 | resultStruct = struct( ... 391 | 'xy', xy, ... 392 | 'dmat', dmat, ... 393 | 'popSize', popSize, ... 394 | 'numIter', numIter, ... 395 | 'showProg', showProg, ... 396 | 'showResult', showResult, ... 397 | 'showWaitbar', showWaitbar, ... 398 | 'optRoute', optRoute, ... 399 | 'optSolution', optRoute, ... 400 | 'plotPoints', plotPoints, ... 401 | 'plotResult', plotResult, ... 402 | 'plotHistory', plotHistory, ... 403 | 'plotMatrix', plotMatrix, ... 404 | 'distHistory', distHistory, ... 405 | 'minDist', globalMin); 406 | 407 | varargout = {resultStruct}; 408 | 409 | end 410 | 411 | 412 | % 413 | % Nested function to cancel search 414 | % 415 | function cancel_search(varargin) 416 | isStopped = true; 417 | end 418 | 419 | 420 | % 421 | % Nested function to close the figure window 422 | % 423 | function close_request(varargin) 424 | if isRunning 425 | [isClosed,isStopped] = deal(true); 426 | isRunning = false; 427 | else 428 | delete(hFig); 429 | end 430 | end 431 | 432 | end 433 | 434 | -------------------------------------------------------------------------------- /tspo_nn.m: -------------------------------------------------------------------------------- 1 | %TSPO_NN Open Traveling Salesman Problem (TSP) Nearest Neighbor (NN) Algorithm 2 | % The Nearest Neighbor algorithm produces different results depending on 3 | % which city is selected as the starting point. This function determines 4 | % the Nearest Neighbor routes for multiple starting points and returns 5 | % the best of those routes 6 | % 7 | % Summary: 8 | % 1. A single salesman travels to each of the cities but does not close 9 | % the loop by returning to the city he started from 10 | % 2. Each city is visited by the salesman exactly once 11 | % 12 | % Input: 13 | % USERCONFIG (structure) with zero or more of the following fields: 14 | % - XY (float) is an Nx2 matrix of city locations, where N is the number of cities 15 | % - DMAT (float) is an NxN matrix of point to point distances/costs 16 | % - POPSIZE (scalar integer) is the size of the population (should be <= N) 17 | % - SHOWPROG (scalar logical) shows the GA progress if true 18 | % - SHOWRESULT (scalar logical) shows the GA results if true 19 | % - SHOWWAITBAR (scalar logical) shows a waitbar if true 20 | % 21 | % Input Notes: 22 | % 1. Rather than passing in a structure containing these fields, any/all of 23 | % these inputs can be passed in as parameter/value pairs in any order instead. 24 | % 2. Field/parameter names are case insensitive but must match exactly otherwise. 25 | % 26 | % Output: 27 | % RESULTSTRUCT (structure) with the following fields: 28 | % (in addition to a record of the algorithm configuration) 29 | % - OPTROUTE (integer array) is the best route found by the algorithm 30 | % - MINDIST (scalar float) is the cost of the best route 31 | % 32 | % Usage: 33 | % tspo_nn 34 | % -or- 35 | % tspo_nn(userConfig) 36 | % -or- 37 | % resultStruct = tspo_nn; 38 | % -or- 39 | % resultStruct = tspo_nn(userConfig); 40 | % -or- 41 | % [...] = tspo_nn('Param1',Value1,'Param2',Value2, ...); 42 | % 43 | % Example: 44 | % % Let the function create an example problem to solve 45 | % tspo_nn; 46 | % 47 | % Example: 48 | % % Request the output structure from the solver 49 | % resultStruct = tspo_nn; 50 | % 51 | % Example: 52 | % % Pass a random set of user-defined XY points to the solver 53 | % userConfig = struct('xy',10*rand(50,2)); 54 | % resultStruct = tspo_nn(userConfig); 55 | % 56 | % Example: 57 | % % Pass a more interesting set of XY points to the solver 58 | % n = 100; 59 | % phi = (sqrt(5)-1)/2; 60 | % theta = 2*pi*phi*(0:n-1); 61 | % rho = (1:n).^phi; 62 | % [x,y] = pol2cart(theta(:),rho(:)); 63 | % xy = 10*([x y]-min([x;y]))/(max([x;y])-min([x;y])); 64 | % userConfig = struct('xy',xy); 65 | % resultStruct = tspo_nn(userConfig); 66 | % 67 | % Example: 68 | % % Pass a random set of 3D (XYZ) points to the solver 69 | % xyz = 10*rand(50,3); 70 | % userConfig = struct('xy',xyz); 71 | % resultStruct = tspo_nn(userConfig); 72 | % 73 | % Example: 74 | % % Turn off the plots but show a waitbar 75 | % userConfig = struct('showProg',false,'showResult',false,'showWaitbar',true); 76 | % resultStruct = tspo_nn(userConfig); 77 | % 78 | % See also: tsp_ga, tspo_ga, tspof_ga, tspofs_ga 79 | % 80 | % Author: Joseph Kirk 81 | % Email: jdkirk630@gmail.com 82 | % 83 | function varargout = tspo_nn(varargin) 84 | 85 | 86 | % 87 | % Initialize default configuration 88 | % 89 | defaultConfig.xy = 10*rand(100,2); 90 | defaultConfig.dmat = []; 91 | defaultConfig.popSize = Inf; 92 | defaultConfig.showProg = true; 93 | defaultConfig.showStatus = true; 94 | defaultConfig.showResult = true; 95 | defaultConfig.showWaitbar = false; 96 | 97 | 98 | % 99 | % Interpret user configuration inputs 100 | % 101 | if ~nargin 102 | userConfig = struct(); 103 | elseif isstruct(varargin{1}) 104 | userConfig = varargin{1}; 105 | else 106 | try 107 | userConfig = struct(varargin{:}); 108 | catch 109 | error('??? Expected inputs are either a structure or parameter/value pairs'); 110 | end 111 | end 112 | 113 | 114 | % 115 | % Override default configuration with user inputs 116 | % 117 | configStruct = get_config(defaultConfig,userConfig); 118 | 119 | 120 | % 121 | % Extract configuration 122 | % 123 | xy = configStruct.xy; 124 | dmat = configStruct.dmat; 125 | popSize = configStruct.popSize; 126 | showProg = configStruct.showProg; 127 | showStatus = configStruct.showStatus; 128 | showResult = configStruct.showResult; 129 | showWaitbar = configStruct.showWaitbar; 130 | if isempty(dmat) 131 | nPoints = size(xy,1); 132 | a = meshgrid(1:nPoints); 133 | dmat = reshape(sqrt(sum((xy(a,:)-xy(a',:)).^2,2)),nPoints,nPoints); 134 | end 135 | 136 | 137 | % 138 | % Verify inputs 139 | % 140 | [N,dims] = size(xy); 141 | [nr,nc] = size(dmat); 142 | if (N ~= nr) || (N ~= nc) 143 | error('??? Invalid XY or DMAT inputs') 144 | end 145 | n = N; 146 | 147 | 148 | % 149 | % Sanity checks 150 | % 151 | popSize = max(1,min(n,round(real(popSize(1))))); 152 | showProg = logical(showProg(1)); 153 | showStatus = logical(showStatus(1)); 154 | showResult = logical(showResult(1)); 155 | showWaitbar = logical(showWaitbar(1)); 156 | 157 | 158 | % 159 | % Initialize the population 160 | % 161 | pop = zeros(popSize,n); 162 | 163 | 164 | % 165 | % Run the NN 166 | % 167 | distHistory = NaN(1,popSize); 168 | [isClosed,isStopped,isCancelled] = deal(false); 169 | if showProg 170 | hFig = figure('Name','TSPO_NN | Current Solution', ... 171 | 'Numbertitle','off','CloseRequestFcn',@close_request); 172 | hAx = gca; 173 | if showStatus 174 | [hStatus,isCancelled] = figstatus(0,popSize,[],hFig); 175 | end 176 | end 177 | if showWaitbar 178 | hWait = waitbar(0,'Searching for near-optimal solution ...', ... 179 | 'CreateCancelBtn',@cancel_search); 180 | end 181 | isRunning = true; 182 | for p = 1:popSize 183 | d = 0; 184 | thisRte = zeros(1,n); 185 | isVisited = false(1,n); 186 | I = p; 187 | isVisited(I) = true; 188 | thisRte(1) = I; 189 | for k = 2:n 190 | dists = dmat(I,:); 191 | dMin = min(dists(~isVisited)); 192 | J = find(dists == dMin,1); 193 | isVisited(J) = true; 194 | thisRte(k) = J; 195 | d = d + dmat(I,J); 196 | I = J; 197 | end 198 | pop(p,:) = thisRte; 199 | distHistory(p) = d; 200 | 201 | 202 | % 203 | % Plot the current route 204 | % 205 | if showProg 206 | if (dims > 2), plot3(hAx,xy(thisRte,1),xy(thisRte,2),xy(thisRte,3),'r.-'); 207 | else, plot(hAx,xy(thisRte,1),xy(thisRte,2),'r.-'); end 208 | title(hAx,sprintf('Total Distance = %1.4f, Iteration = %d', ... 209 | distHistory(p),p)); 210 | drawnow; 211 | end 212 | 213 | 214 | % 215 | % Update the status bar and check cancellation status 216 | % 217 | if showProg && showStatus && ~mod(p,ceil(popSize/100)) 218 | [hStatus,isCancelled] = figstatus(p,popSize,hStatus,hFig); 219 | end 220 | if (isStopped || isCancelled) 221 | break 222 | end 223 | 224 | 225 | % 226 | % Update the waitbar 227 | % 228 | if showWaitbar && ~mod(p,ceil(popSize/325)) 229 | waitbar(p/popSize,hWait); 230 | end 231 | 232 | end 233 | if showProg && showStatus 234 | figstatus(popSize,popSize,hStatus,hFig); 235 | end 236 | if showWaitbar 237 | delete(hWait); 238 | end 239 | isRunning = false; 240 | if isClosed 241 | delete(hFig); 242 | end 243 | 244 | 245 | % 246 | % Find the minimum distance route 247 | % 248 | [minDist,index] = min(distHistory); 249 | optRoute = pop(index,:); 250 | 251 | 252 | % 253 | % Show the final results 254 | % 255 | if showResult 256 | 257 | % 258 | % Plot the best route 259 | % 260 | if showProg && ~isClosed 261 | if (dims > 2), plot3(hAx,xy(optRoute,1),xy(optRoute,2),xy(optRoute,3),'r.-'); 262 | else, plot(hAx,xy(optRoute,1),xy(optRoute,2),'r.-'); end 263 | title(hAx,sprintf('Total Distance = %1.4f',minDist)); 264 | end 265 | 266 | % 267 | % Plot the NN results 268 | % 269 | figure('Name','TSPO_NN | Results','Numbertitle','off'); 270 | subplot(2,2,1); 271 | pclr = ~get(0,'DefaultAxesColor'); 272 | if (dims > 2), plot3(xy(:,1),xy(:,2),xy(:,3),'.','Color',pclr); 273 | else, plot(xy(:,1),xy(:,2),'.','Color',pclr); end 274 | title('City Locations'); 275 | subplot(2,2,2); 276 | imagesc(dmat(optRoute,optRoute)); 277 | title('Distance Matrix'); 278 | subplot(2,2,3); 279 | if (dims > 2), plot3(xy(optRoute,1),xy(optRoute,2),xy(optRoute,3),'r.-'); 280 | else, plot(xy(optRoute,1),xy(optRoute,2),'r.-'); end 281 | title(sprintf('Total Distance = %1.4f',minDist)); 282 | subplot(2,2,4); 283 | plot(sort(distHistory,'descend'),'b','LineWidth',2); 284 | title('Distances'); 285 | set(gca,'XLim',[0 popSize+1],'YLim',[0 1.1*max([1 distHistory])]); 286 | end 287 | 288 | 289 | % 290 | % Return output 291 | % 292 | if nargout 293 | 294 | % 295 | % Create anonymous functions for plot generation 296 | % 297 | plotPoints = @(s)plot(s.xy(:,1),s.xy(:,2),'.','Color',~get(gca,'Color')); 298 | plotResult = @(s)plot(s.xy(s.optSolution,1),s.xy(s.optSolution,2),'r.-'); 299 | plotHistory = @(s)plot(s.distHistory,'b-','LineWidth',2); 300 | plotMatrix = @(s)imagesc(s.dmat(s.optSolution,s.optSolution)); 301 | 302 | 303 | % 304 | % Save results in output structure 305 | % 306 | resultStruct = struct( ... 307 | 'xy', xy, ... 308 | 'dmat', dmat, ... 309 | 'popSize', popSize, ... 310 | 'showProg', showProg, ... 311 | 'showResult', showResult, ... 312 | 'showWaitbar', showWaitbar, ... 313 | 'optRoute', optRoute, ... 314 | 'optSolution', optRoute, ... 315 | 'plotPoints', plotPoints, ... 316 | 'plotResult', plotResult, ... 317 | 'plotHistory', plotHistory, ... 318 | 'plotMatrix', plotMatrix, ... 319 | 'distHistory', distHistory, ... 320 | 'minDist', minDist, ... 321 | 'pop', pop); 322 | 323 | varargout = {resultStruct}; 324 | 325 | end 326 | 327 | 328 | % 329 | % Nested function to cancel search 330 | % 331 | function cancel_search(varargin) 332 | isStopped = true; 333 | end 334 | 335 | 336 | % 337 | % Nested function to close the figure window 338 | % 339 | function close_request(varargin) 340 | if isRunning 341 | [isClosed,isStopped] = deal(true); 342 | isRunning = false; 343 | else 344 | delete(hFig); 345 | end 346 | end 347 | 348 | end 349 | 350 | -------------------------------------------------------------------------------- /tspof_ga.m: -------------------------------------------------------------------------------- 1 | %TSPOF_GA Fixed Open Traveling Salesman Problem (TSP) Genetic Algorithm (GA) 2 | % Finds a (near) optimal solution to a variation of the TSP by setting up 3 | % a GA to search for the shortest route (least distance for the salesman 4 | % to travel from a FIXED START to a FIXED END while visiting the other 5 | % cities exactly once) 6 | % 7 | % Summary: 8 | % 1. A single salesman starts at the first point, ends at the last 9 | % point, and travels to each of the remaining cities in between, but 10 | % does not close the loop by returning to the city he started from 11 | % 2. Each city is visited by the salesman exactly once 12 | % 13 | % Note: The Fixed Start is taken to be the first XY point, and the Fixed 14 | % End is taken to be the last XY point 15 | % 16 | % Input: 17 | % USERCONFIG (structure) with zero or more of the following fields: 18 | % - XY (float) is an Nx2 matrix of city locations, where N is the number of cities 19 | % - DMAT (float) is an NxN matrix of point to point distances/costs 20 | % - POPSIZE (scalar integer) is the size of the population (should be divisible by 4) 21 | % - NUMITER (scalar integer) is the number of desired iterations for the algorithm to run 22 | % - SHOWPROG (scalar logical) shows the GA progress if true 23 | % - SHOWRESULT (scalar logical) shows the GA results if true 24 | % - SHOWWAITBAR (scalar logical) shows a waitbar if true 25 | % 26 | % Input Notes: 27 | % 1. Rather than passing in a structure containing these fields, any/all of 28 | % these inputs can be passed in as parameter/value pairs in any order instead. 29 | % 2. Field/parameter names are case insensitive but must match exactly otherwise. 30 | % 31 | % Output: 32 | % RESULTSTRUCT (structure) with the following fields: 33 | % (in addition to a record of the algorithm configuration) 34 | % - OPTROUTE (integer array) is the best route found by the algorithm 35 | % - MINDIST (scalar float) is the cost of the best route 36 | % 37 | % Usage: 38 | % tspof_ga 39 | % -or- 40 | % tspof_ga(userConfig) 41 | % -or- 42 | % resultStruct = tspof_ga; 43 | % -or- 44 | % resultStruct = tspof_ga(userConfig); 45 | % -or- 46 | % [...] = tspof_ga('Param1',Value1,'Param2',Value2, ...); 47 | % 48 | % Example: 49 | % % Let the function create an example problem to solve 50 | % tspof_ga; 51 | % 52 | % Example: 53 | % % Request the output structure from the solver 54 | % resultStruct = tspof_ga; 55 | % 56 | % Example: 57 | % % Pass a random set of user-defined XY points to the solver 58 | % userConfig = struct('xy',10*rand(50,2)); 59 | % resultStruct = tspof_ga(userConfig); 60 | % 61 | % Example: 62 | % % Pass a more interesting set of XY points to the solver 63 | % n = 50; 64 | % phi = (sqrt(5)-1)/2; 65 | % theta = 2*pi*phi*(0:n-1); 66 | % rho = (1:n).^phi; 67 | % [x,y] = pol2cart(theta(:),rho(:)); 68 | % xy = 10*([x y]-min([x;y]))/(max([x;y])-min([x;y])); 69 | % userConfig = struct('xy',xy); 70 | % resultStruct = tspof_ga(userConfig); 71 | % 72 | % Example: 73 | % % Pass a random set of 3D (XYZ) points to the solver 74 | % xyz = 10*rand(50,3); 75 | % userConfig = struct('xy',xyz); 76 | % resultStruct = tspof_ga(userConfig); 77 | % 78 | % Example: 79 | % % Change the defaults for GA population size and number of iterations 80 | % userConfig = struct('popSize',200,'numIter',1e4); 81 | % resultStruct = tspof_ga(userConfig); 82 | % 83 | % Example: 84 | % % Turn off the plots but show a waitbar 85 | % userConfig = struct('showProg',false,'showResult',false,'showWaitbar',true); 86 | % resultStruct = tspof_ga(userConfig); 87 | % 88 | % See also: tsp_ga, tsp_nn, tspo_ga, tspofs_ga 89 | % 90 | % Author: Joseph Kirk 91 | % Email: jdkirk630@gmail.com 92 | % 93 | function varargout = tspof_ga(varargin) 94 | 95 | 96 | % 97 | % Initialize default configuration 98 | % 99 | defaultConfig.xy = 10*rand(50,2); 100 | defaultConfig.dmat = []; 101 | defaultConfig.popSize = 100; 102 | defaultConfig.numIter = 1e4; 103 | defaultConfig.showProg = true; 104 | defaultConfig.showStatus = true; 105 | defaultConfig.showResult = true; 106 | defaultConfig.showWaitbar = false; 107 | 108 | 109 | % 110 | % Interpret user configuration inputs 111 | % 112 | if ~nargin 113 | userConfig = struct(); 114 | elseif isstruct(varargin{1}) 115 | userConfig = varargin{1}; 116 | else 117 | try 118 | userConfig = struct(varargin{:}); 119 | catch 120 | error('??? Expected inputs are either a structure or parameter/value pairs'); 121 | end 122 | end 123 | 124 | 125 | % 126 | % Override default configuration with user inputs 127 | % 128 | configStruct = get_config(defaultConfig,userConfig); 129 | 130 | 131 | % 132 | % Extract configuration 133 | % 134 | xy = configStruct.xy; 135 | dmat = configStruct.dmat; 136 | popSize = configStruct.popSize; 137 | numIter = configStruct.numIter; 138 | showProg = configStruct.showProg; 139 | showStatus = configStruct.showStatus; 140 | showResult = configStruct.showResult; 141 | showWaitbar = configStruct.showWaitbar; 142 | if isempty(dmat) 143 | nPoints = size(xy,1); 144 | a = meshgrid(1:nPoints); 145 | dmat = reshape(sqrt(sum((xy(a,:)-xy(a',:)).^2,2)),nPoints,nPoints); 146 | end 147 | 148 | 149 | % 150 | % Verify inputs 151 | % 152 | [N,dims] = size(xy); 153 | [nr,nc] = size(dmat); 154 | if (N ~= nr) || (N ~= nc) 155 | error('??? Invalid XY or DMAT inputs') 156 | end 157 | n = N - 2; % Separate start and end cities 158 | 159 | 160 | % 161 | % Sanity checks 162 | % 163 | popSize = 4*ceil(popSize/4); 164 | numIter = max(1,round(real(numIter(1)))); 165 | showProg = logical(showProg(1)); 166 | showStatus = logical(showStatus(1)); 167 | showResult = logical(showResult(1)); 168 | showWaitbar = logical(showWaitbar(1)); 169 | 170 | 171 | % 172 | % Initialize the population 173 | % 174 | pop = zeros(popSize,n); 175 | pop(1,:) = (1:n) + 1; 176 | for k = 2:popSize 177 | pop(k,:) = randperm(n) + 1; 178 | end 179 | 180 | 181 | % 182 | % Seed the algorithm with a previous result if available 183 | % 184 | if isfield(userConfig,'optRoute') 185 | optRoute = userConfig.optRoute; 186 | isValid = isequal(pop(1,:),sort(optRoute)); 187 | if isValid 188 | pop(1,:) = optRoute; 189 | end 190 | end 191 | 192 | 193 | % 194 | % Run the GA 195 | % 196 | globalMin = Inf; 197 | distHistory = NaN(1,numIter); 198 | tmpPop = zeros(4,n); 199 | newPop = zeros(popSize,n); 200 | [isClosed,isStopped,isCancelled] = deal(false); 201 | if showProg 202 | hFig = figure('Name','TSPOF_GA | Current Best Solution', ... 203 | 'Numbertitle','off','CloseRequestFcn',@close_request); 204 | hAx = gca; 205 | if showStatus 206 | [hStatus,isCancelled] = figstatus(0,numIter,[],hFig); 207 | end 208 | end 209 | if showWaitbar 210 | hWait = waitbar(0,'Searching for near-optimal solution ...', ... 211 | 'CreateCancelBtn',@cancel_search); 212 | end 213 | isRunning = true; 214 | for iter = 1:numIter 215 | 216 | % 217 | % EVALUATE SOLUTIONS 218 | % This section of code computes the total cost of each solution 219 | % in the population. The actual code that gets executed uses a 220 | % much faster (vectorized) method to calculate the route lengths 221 | % compared to the double for-loop below (provided for reference) 222 | % but gives the same result. 223 | % 224 | % totalDist = zeros(popSize,1); 225 | % for p = 1:popSize 226 | % d = dmat(1,pop(p,1)); 227 | % for k = 2:n 228 | % d = d + dmat(pop(p,k-1),pop(p,k)); 229 | % end 230 | % d = d + dmat(pop(p,n),N); 231 | % totalDist(p) = d; 232 | % end 233 | % 234 | row = [ones(popSize,1) pop]; 235 | col = [pop N(ones(popSize,1))]; 236 | ind = N*(col-1) + row; 237 | totalDist = sum(dmat(ind),2); 238 | 239 | 240 | % 241 | % SELECT THE BEST 242 | % This section of code finds the best solution in the current 243 | % population and stores it if it is better than the previous best. 244 | % 245 | [minDist,index] = min(totalDist); 246 | distHistory(iter) = minDist; 247 | if (minDist < globalMin) 248 | globalMin = minDist; 249 | optRoute = pop(index,:); 250 | if showProg 251 | 252 | % 253 | % Plot the best route 254 | % 255 | rte = [1 optRoute N]; 256 | if (dims > 2) 257 | plot3(hAx,xy(rte,1),xy(rte,2),xy(rte,3),'r.-',... 258 | xy([1 N],1),xy([1 N],2),xy([1 N],3),'ro'); 259 | else 260 | plot(hAx,xy(rte,1),xy(rte,2),'r.-',xy([1 N],1),xy([1 N],2),'ro'); 261 | end 262 | title(hAx,sprintf('Total Distance = %1.4f, Iteration = %d',minDist,iter)); 263 | drawnow; 264 | end 265 | end 266 | 267 | 268 | % 269 | % Update the status bar and check cancellation status 270 | % 271 | if showProg && showStatus && ~mod(iter,ceil(numIter/100)) 272 | [hStatus,isCancelled] = figstatus(iter,numIter,hStatus,hFig); 273 | end 274 | if (isStopped || isCancelled) 275 | break 276 | end 277 | 278 | 279 | % 280 | % MODIFY THE POPULATION 281 | % This section of code invokes the genetic algorithm operators. 282 | % In this implementation, solutions are randomly assigned to groups 283 | % of four and the best solution is kept (tournament selection). 284 | % The best-of-four solution is then mutated 3 different ways 285 | % (flip, swap, and slide). There is no crossover operator because 286 | % it tends to be highly destructive and rarely improves a decent 287 | % solution. 288 | % 289 | randomOrder = randperm(popSize); 290 | for p = 4:4:popSize 291 | rtes = pop(randomOrder(p-3:p),:); 292 | dists = totalDist(randomOrder(p-3:p)); 293 | [ignore,idx] = min(dists); %#ok 294 | bestOf4Route = rtes(idx,:); 295 | routeInsertionPoints = sort(randperm(n,2)); 296 | I = routeInsertionPoints(1); 297 | J = routeInsertionPoints(2); 298 | for k = 1:4 % Mutate the best to get three new routes 299 | tmpPop(k,:) = bestOf4Route; 300 | switch k 301 | case 2 % Flip 302 | tmpPop(k,I:J) = tmpPop(k,J:-1:I); 303 | case 3 % Swap 304 | tmpPop(k,[I J]) = tmpPop(k,[J I]); 305 | case 4 % Slide 306 | tmpPop(k,I:J) = tmpPop(k,[I+1:J I]); 307 | otherwise % Do nothing 308 | end 309 | end 310 | newPop(p-3:p,:) = tmpPop; 311 | end 312 | pop = newPop; 313 | 314 | 315 | % 316 | % Update the waitbar 317 | % 318 | if showWaitbar && ~mod(iter,ceil(numIter/325)) 319 | waitbar(iter/numIter,hWait); 320 | end 321 | 322 | end 323 | if showProg && showStatus 324 | figstatus(numIter,numIter,hStatus,hFig); 325 | end 326 | if showWaitbar 327 | delete(hWait); 328 | end 329 | isRunning = false; 330 | if isClosed 331 | delete(hFig); 332 | end 333 | 334 | 335 | % 336 | % Append prior distance history if present 337 | % 338 | if isfield(userConfig,'distHistory') 339 | priorHistory = userConfig.distHistory; 340 | isNan = isnan(priorHistory); 341 | distHistory = [priorHistory(~isNan) distHistory]; 342 | end 343 | 344 | 345 | % 346 | % Show the final results 347 | % 348 | if showResult 349 | 350 | % 351 | % Plot the GA results 352 | % 353 | figure('Name','TSPOF_GA | Results','Numbertitle','off'); 354 | subplot(2,2,1); 355 | pclr = ~get(0,'DefaultAxesColor'); 356 | if (dims > 2), plot3(xy(:,1),xy(:,2),xy(:,3),'.','Color',pclr); 357 | else, plot(xy(:,1),xy(:,2),'.','Color',pclr); end 358 | title('City Locations'); 359 | subplot(2,2,2); 360 | imagesc(dmat([1 optRoute N],[1 optRoute N])); 361 | title('Distance Matrix'); 362 | subplot(2,2,3); 363 | rte = [1 optRoute N]; 364 | if (dims > 2) 365 | plot3(xy(rte,1),xy(rte,2),xy(rte,3),'r.-',... 366 | xy([1 N],1),xy([1 N],2),xy([1 N],3),'ro'); 367 | else 368 | plot(xy(rte,1),xy(rte,2),'r.-',xy([1 N],1),xy([1 N],2),'ro'); 369 | end 370 | title(sprintf('Total Distance = %1.4f',minDist)); 371 | subplot(2,2,4); 372 | plot(distHistory,'b','LineWidth',2); 373 | title('Best Solution History'); 374 | set(gca,'XLim',[1 length(distHistory)],'YLim',[0 1.1*max([1 distHistory])]); 375 | end 376 | 377 | 378 | % 379 | % Return output 380 | % 381 | if nargout 382 | 383 | % 384 | % Create anonymous functions for plot generation 385 | % 386 | plotPoints = @(s)plot(s.xy(:,1),s.xy(:,2),'.','Color',~get(gca,'Color')); 387 | plotResult = @(s)plot(s.xy(s.optSolution,1),s.xy(s.optSolution,2),'r.-'); 388 | plotHistory = @(s)plot(s.distHistory,'b-','LineWidth',2); 389 | plotMatrix = @(s)imagesc(s.dmat(s.optSolution,s.optSolution)); 390 | 391 | 392 | % 393 | % Save results in output structure 394 | % 395 | resultStruct = struct( ... 396 | 'xy', xy, ... 397 | 'dmat', dmat, ... 398 | 'popSize', popSize, ... 399 | 'numIter', numIter, ... 400 | 'showProg', showProg, ... 401 | 'showResult', showResult, ... 402 | 'showWaitbar', showWaitbar, ... 403 | 'optRoute', optRoute, ... 404 | 'optSolution', [1 optRoute N], ... 405 | 'plotPoints', plotPoints, ... 406 | 'plotResult', plotResult, ... 407 | 'plotHistory', plotHistory, ... 408 | 'plotMatrix', plotMatrix, ... 409 | 'distHistory', distHistory, ... 410 | 'minDist', minDist); 411 | 412 | varargout = {resultStruct}; 413 | 414 | end 415 | 416 | 417 | % 418 | % Nested function to cancel search 419 | % 420 | function cancel_search(varargin) 421 | isStopped = true; 422 | end 423 | 424 | 425 | % 426 | % Nested function to close the figure window 427 | % 428 | function close_request(varargin) 429 | if isRunning 430 | [isClosed,isStopped] = deal(true); 431 | isRunning = false; 432 | else 433 | delete(hFig); 434 | end 435 | end 436 | 437 | end 438 | 439 | -------------------------------------------------------------------------------- /tspofs_ga.m: -------------------------------------------------------------------------------- 1 | %TSPOFS_GA Fixed Start Open Traveling Salesman Problem (TSP) Genetic Algorithm (GA) 2 | % Finds a (near) optimal solution to a variation of the TSP by setting up 3 | % a GA to search for the shortest route (least distance for the salesman 4 | % to travel from a FIXED START to the other cities exactly once without 5 | % returning to the starting city) 6 | % 7 | % Summary: 8 | % 1. A single salesman starts at the first point and travels to each of 9 | % the remaining cities but does not close the loop by returning to 10 | % the city he started from 11 | % 2. Each city is visited by the salesman exactly once 12 | % 13 | % Note: The Fixed Start is taken to be the first XY point 14 | % 15 | % Input: 16 | % USERCONFIG (structure) with zero or more of the following fields: 17 | % - XY (float) is an Nx2 matrix of city locations, where N is the number of cities 18 | % - DMAT (float) is an NxN matrix of point to point distances/costs 19 | % - POPSIZE (scalar integer) is the size of the population (should be divisible by 4) 20 | % - NUMITER (scalar integer) is the number of desired iterations for the algorithm to run 21 | % - SHOWPROG (scalar logical) shows the GA progress if true 22 | % - SHOWRESULT (scalar logical) shows the GA results if true 23 | % - SHOWWAITBAR (scalar logical) shows a waitbar if true 24 | % 25 | % Input Notes: 26 | % 1. Rather than passing in a structure containing these fields, any/all of 27 | % these inputs can be passed in as parameter/value pairs in any order instead. 28 | % 2. Field/parameter names are case insensitive but must match exactly otherwise. 29 | % 30 | % Output: 31 | % RESULTSTRUCT (structure) with the following fields: 32 | % (in addition to a record of the algorithm configuration) 33 | % - OPTROUTE (integer array) is the best route found by the algorithm 34 | % - MINDIST (scalar float) is the cost of the best route 35 | % 36 | % Usage: 37 | % tspofs_ga 38 | % -or- 39 | % tspofs_ga(userConfig) 40 | % -or- 41 | % resultStruct = tspofs_ga; 42 | % -or- 43 | % resultStruct = tspofs_ga(userConfig); 44 | % -or- 45 | % [...] = tspofs_ga('Param1',Value1,'Param2',Value2, ...); 46 | % 47 | % Example: 48 | % % Let the function create an example problem to solve 49 | % tspofs_ga; 50 | % 51 | % Example: 52 | % % Request the output structure from the solver 53 | % resultStruct = tspofs_ga; 54 | % 55 | % Example: 56 | % % Pass a random set of user-defined XY points to the solver 57 | % userConfig = struct('xy',10*rand(50,2)); 58 | % resultStruct = tspofs_ga(userConfig); 59 | % 60 | % Example: 61 | % % Pass a more interesting set of XY points to the solver 62 | % n = 50; 63 | % phi = (sqrt(5)-1)/2; 64 | % theta = 2*pi*phi*(0:n-1); 65 | % rho = (1:n).^phi; 66 | % [x,y] = pol2cart(theta(:),rho(:)); 67 | % xy = 10*([x y]-min([x;y]))/(max([x;y])-min([x;y])); 68 | % userConfig = struct('xy',xy); 69 | % resultStruct = tspofs_ga(userConfig); 70 | % 71 | % Example: 72 | % % Pass a random set of 3D (XYZ) points to the solver 73 | % xyz = 10*rand(50,3); 74 | % userConfig = struct('xy',xyz); 75 | % resultStruct = tspofs_ga(userConfig); 76 | % 77 | % Example: 78 | % % Change the defaults for GA population size and number of iterations 79 | % userConfig = struct('popSize',200,'numIter',1e4); 80 | % resultStruct = tspofs_ga(userConfig); 81 | % 82 | % Example: 83 | % % Turn off the plots but show a waitbar 84 | % userConfig = struct('showProg',false,'showResult',false,'showWaitbar',true); 85 | % resultStruct = tspofs_ga(userConfig); 86 | % 87 | % See also: tsp_ga, tsp_nn, tspo_ga, tspof_ga 88 | % 89 | % Author: Joseph Kirk 90 | % Email: jdkirk630@gmail.com 91 | % 92 | function varargout = tspofs_ga(varargin) 93 | 94 | 95 | % 96 | % Initialize default configuration 97 | % 98 | defaultConfig.xy = 10*rand(50,2); 99 | defaultConfig.dmat = []; 100 | defaultConfig.popSize = 100; 101 | defaultConfig.numIter = 1e4; 102 | defaultConfig.showProg = true; 103 | defaultConfig.showStatus = true; 104 | defaultConfig.showResult = true; 105 | defaultConfig.showWaitbar = false; 106 | 107 | 108 | % 109 | % Interpret user configuration inputs 110 | % 111 | if ~nargin 112 | userConfig = struct(); 113 | elseif isstruct(varargin{1}) 114 | userConfig = varargin{1}; 115 | else 116 | try 117 | userConfig = struct(varargin{:}); 118 | catch 119 | error('??? Expected inputs are either a structure or parameter/value pairs'); 120 | end 121 | end 122 | 123 | 124 | % 125 | % Override default configuration with user inputs 126 | % 127 | configStruct = get_config(defaultConfig,userConfig); 128 | 129 | 130 | % 131 | % Extract configuration 132 | % 133 | xy = configStruct.xy; 134 | dmat = configStruct.dmat; 135 | popSize = configStruct.popSize; 136 | numIter = configStruct.numIter; 137 | showProg = configStruct.showProg; 138 | showStatus = configStruct.showStatus; 139 | showResult = configStruct.showResult; 140 | showWaitbar = configStruct.showWaitbar; 141 | if isempty(dmat) 142 | nPoints = size(xy,1); 143 | a = meshgrid(1:nPoints); 144 | dmat = reshape(sqrt(sum((xy(a,:)-xy(a',:)).^2,2)),nPoints,nPoints); 145 | end 146 | 147 | 148 | % 149 | % Verify inputs 150 | % 151 | [N,dims] = size(xy); 152 | [nr,nc] = size(dmat); 153 | if (N ~= nr) || (N ~= nc) 154 | error('??? Invalid XY or DMAT inputs') 155 | end 156 | n = N - 1; % Separate start city 157 | 158 | 159 | % 160 | % Sanity checks 161 | % 162 | popSize = 4*ceil(popSize/4); 163 | numIter = max(1,round(real(numIter(1)))); 164 | showProg = logical(showProg(1)); 165 | showStatus = logical(showStatus(1)); 166 | showResult = logical(showResult(1)); 167 | showWaitbar = logical(showWaitbar(1)); 168 | 169 | 170 | % 171 | % Initialize the population 172 | % 173 | pop = zeros(popSize,n); 174 | pop(1,:) = (1:n) + 1; 175 | for k = 2:popSize 176 | pop(k,:) = randperm(n) + 1; 177 | end 178 | 179 | 180 | % 181 | % Seed the algorithm with a previous result if available 182 | % 183 | if isfield(userConfig,'optRoute') 184 | optRoute = userConfig.optRoute; 185 | isValid = isequal(pop(1,:),sort(optRoute)); 186 | if isValid 187 | pop(1,:) = optRoute; 188 | end 189 | end 190 | 191 | 192 | % 193 | % Run the GA 194 | % 195 | globalMin = Inf; 196 | distHistory = NaN(1,numIter); 197 | tmpPop = zeros(4,n); 198 | newPop = zeros(popSize,n); 199 | [isClosed,isStopped,isCancelled] = deal(false); 200 | if showProg 201 | hFig = figure('Name','TSPOFS_GA | Current Best Solution', ... 202 | 'Numbertitle','off','CloseRequestFcn',@close_request); 203 | hAx = gca; 204 | if showStatus 205 | [hStatus,isCancelled] = figstatus(0,numIter,[],hFig); 206 | end 207 | end 208 | if showWaitbar 209 | hWait = waitbar(0,'Searching for near-optimal solution ...', ... 210 | 'CreateCancelBtn',@cancel_search); 211 | end 212 | isRunning = true; 213 | for iter = 1:numIter 214 | 215 | % 216 | % EVALUATE SOLUTIONS 217 | % This section of code computes the total cost of each solution 218 | % in the population. The actual code that gets executed uses a 219 | % much faster (vectorized) method to calculate the route lengths 220 | % compared to the double for-loop below (provided for reference) 221 | % but gives the same result. 222 | % 223 | % totalDist = zeros(popSize,1); 224 | % for p = 1:popSize 225 | % d = dmat(1,pop(p,1)); 226 | % for k = 2:n 227 | % d = d + dmat(pop(p,k-1),pop(p,k)); 228 | % end 229 | % totalDist(p) = d; 230 | % end 231 | % 232 | row = [ones(popSize,1) pop(:,1:n-1)]; 233 | col = pop; 234 | ind = N*(col-1) + row; 235 | totalDist = sum(dmat(ind),2); 236 | 237 | 238 | % 239 | % SELECT THE BEST 240 | % This section of code finds the best solution in the current 241 | % population and stores it if it is better than the previous best. 242 | % 243 | [minDist,index] = min(totalDist); 244 | distHistory(iter) = minDist; 245 | if (minDist < globalMin) 246 | globalMin = minDist; 247 | optRoute = pop(index,:); 248 | if showProg 249 | 250 | % 251 | % Plot the best route 252 | % 253 | rte = [1 optRoute]; 254 | if (dims > 2) 255 | plot3(hAx,xy(rte,1),xy(rte,2),xy(rte,3),'r.-', ... 256 | xy(1,1),xy(1,2),xy(1,3),'ro'); 257 | else 258 | plot(hAx,xy(rte,1),xy(rte,2),'r.-',xy(1,1),xy(1,2),'ro'); 259 | end 260 | title(hAx,sprintf('Total Distance = %1.4f, Iteration = %d',minDist,iter)); 261 | drawnow; 262 | end 263 | end 264 | 265 | 266 | % 267 | % Update the status bar and check cancellation status 268 | % 269 | if showProg && showStatus && ~mod(iter,ceil(numIter/100)) 270 | [hStatus,isCancelled] = figstatus(iter,numIter,hStatus,hFig); 271 | end 272 | if (isStopped || isCancelled) 273 | break 274 | end 275 | 276 | 277 | % 278 | % MODIFY THE POPULATION 279 | % This section of code invokes the genetic algorithm operators. 280 | % In this implementation, solutions are randomly assigned to groups 281 | % of four and the best solution is kept (tournament selection). 282 | % The best-of-four solution is then mutated 3 different ways 283 | % (flip, swap, and slide). There is no crossover operator because 284 | % it tends to be highly destructive and rarely improves a decent 285 | % solution. 286 | % 287 | randomOrder = randperm(popSize); 288 | for p = 4:4:popSize 289 | rtes = pop(randomOrder(p-3:p),:); 290 | dists = totalDist(randomOrder(p-3:p)); 291 | [ignore,idx] = min(dists); %#ok 292 | bestOf4Route = rtes(idx,:); 293 | routeInsertionPoints = sort(randperm(n,2)); 294 | I = routeInsertionPoints(1); 295 | J = routeInsertionPoints(2); 296 | for k = 1:4 % Mutate the best to get three new routes 297 | tmpPop(k,:) = bestOf4Route; 298 | switch k 299 | case 2 % Flip 300 | tmpPop(k,I:J) = tmpPop(k,J:-1:I); 301 | case 3 % Swap 302 | tmpPop(k,[I J]) = tmpPop(k,[J I]); 303 | case 4 % Slide 304 | tmpPop(k,I:J) = tmpPop(k,[I+1:J I]); 305 | otherwise % Do nothing 306 | end 307 | end 308 | newPop(p-3:p,:) = tmpPop; 309 | end 310 | pop = newPop; 311 | 312 | 313 | % 314 | % Update the waitbar 315 | % 316 | if showWaitbar && ~mod(iter,ceil(numIter/325)) 317 | waitbar(iter/numIter,hWait); 318 | end 319 | 320 | end 321 | if showProg && showStatus 322 | figstatus(numIter,numIter,hStatus,hFig); 323 | end 324 | if showWaitbar 325 | delete(hWait); 326 | end 327 | isRunning = false; 328 | if isClosed 329 | delete(hFig); 330 | end 331 | 332 | 333 | % 334 | % Append prior distance history if present 335 | % 336 | if isfield(userConfig,'distHistory') 337 | priorHistory = userConfig.distHistory; 338 | isNan = isnan(priorHistory); 339 | distHistory = [priorHistory(~isNan) distHistory]; 340 | end 341 | 342 | 343 | % 344 | % Show the final results 345 | % 346 | if showResult 347 | 348 | % 349 | % Plot the GA results 350 | % 351 | figure('Name','TSPOFS_GA | Results','Numbertitle','off'); 352 | subplot(2,2,1); 353 | pclr = ~get(0,'DefaultAxesColor'); 354 | if (dims > 2), plot3(xy(:,1),xy(:,2),xy(:,3),'.','Color',pclr); 355 | else, plot(xy(:,1),xy(:,2),'.','Color',pclr); end 356 | title('City Locations'); 357 | subplot(2,2,2); 358 | imagesc(dmat([1 optRoute],[1 optRoute])); 359 | title('Distance Matrix'); 360 | subplot(2,2,3); 361 | rte = [1 optRoute]; 362 | if (dims > 2) 363 | plot3(xy(rte,1),xy(rte,2),xy(rte,3),'r.-', ... 364 | xy(1,1),xy(1,2),xy(1,3),'ro'); 365 | else 366 | plot(xy(rte,1),xy(rte,2),'r.-',xy(1,1),xy(1,2),'ro'); 367 | end 368 | title(sprintf('Total Distance = %1.4f',minDist)); 369 | subplot(2,2,4); 370 | plot(distHistory,'b','LineWidth',2); 371 | title('Best Solution History'); 372 | set(gca,'XLim',[1 length(distHistory)],'YLim',[0 1.1*max([1 distHistory])]); 373 | end 374 | 375 | 376 | % 377 | % Return output 378 | % 379 | if nargout 380 | 381 | % 382 | % Create anonymous functions for plot generation 383 | % 384 | plotPoints = @(s)plot(s.xy(:,1),s.xy(:,2),'.','Color',~get(gca,'Color')); 385 | plotResult = @(s)plot(s.xy(s.optSolution,1),s.xy(s.optSolution,2),'r.-'); 386 | plotHistory = @(s)plot(s.distHistory,'b-','LineWidth',2); 387 | plotMatrix = @(s)imagesc(s.dmat(s.optSolution,s.optSolution)); 388 | 389 | 390 | % 391 | % Save results in output structure 392 | % 393 | resultStruct = struct( ... 394 | 'xy', xy, ... 395 | 'dmat', dmat, ... 396 | 'popSize', popSize, ... 397 | 'numIter', numIter, ... 398 | 'showProg', showProg, ... 399 | 'showResult', showResult, ... 400 | 'showWaitbar', showWaitbar, ... 401 | 'optRoute', optRoute, ... 402 | 'optSolution', [1 optRoute], ... 403 | 'plotPoints', plotPoints, ... 404 | 'plotResult', plotResult, ... 405 | 'plotHistory', plotHistory, ... 406 | 'plotMatrix', plotMatrix, ... 407 | 'distHistory', distHistory, ... 408 | 'minDist', minDist); 409 | 410 | varargout = {resultStruct}; 411 | 412 | end 413 | 414 | 415 | % 416 | % Nested function to cancel search 417 | % 418 | function cancel_search(varargin) 419 | isStopped = true; 420 | end 421 | 422 | 423 | % 424 | % Nested function to close the figure window 425 | % 426 | function close_request(varargin) 427 | if isRunning 428 | [isClosed,isStopped] = deal(true); 429 | isRunning = false; 430 | else 431 | delete(hFig); 432 | end 433 | end 434 | 435 | end 436 | 437 | --------------------------------------------------------------------------------