├── README.md ├── Questions.txt ├── test_components.m ├── test.m ├── TDRA.asv └── TDRA.m /README.md: -------------------------------------------------------------------------------- 1 | # Research-Project 2 | Heuristic approach to a truck and drones model for last-mile delivery. 3 | We consider the problem of truck and drone delivery scheduling, following work done by Moshref-Javadi et al. 4 | Given a depot location and a set of customer locations, the goal is to determine which customers should be served by a 5 | truck and which should be served by drones that depart and return to the truck. Moreover, the schedule must specify the 6 | customer visitation order for the truck and the departure and rendezvous stops for the drones. We present preliminary results 7 | from a heuristic algorithm with the goal of minimizing the total customer waiting time. 8 | -------------------------------------------------------------------------------- /Questions.txt: -------------------------------------------------------------------------------- 1 | 1. When inserting our customer into the truck position and we get our s_prime, 2 | do we get another s_prime when we insert our customer into the drone 3 | route and compare those as well?? Or what do we dooooo? Right now I have 4 | it as two separate solutions. 5 | 6 | 7 | 2. When it says remove the assigned customers from the list, does it mean 8 | remove jCust from the sorted list? 9 | 10 | 11 | 3. How are we meant to terminate the while loop of he ECA heuristic? 12 | 13 | 4. 14 | 15 | 16 | NOTES 17 | - We don't have to worry about parts 3 and 4 having 0 in the vectors 18 | because they use indexing which seems to start at 1. The significance is 19 | that when looking at soln.anPart1(iReconveneIndex), we don't have to add 20 | +1 inside the parenthesis. 21 | 22 | - It is my belief that we don't need to calculate the departure time of the 23 | drone because that all depends on whether the truck gets there first or 24 | second. 25 | 26 | 27 | 28 | aafDistances = 29 | 30 | 0 8.2462 6.7082 7.6158 0 31 | 8.2462 0 2.2361 15.0333 8.2462 32 | 6.7082 2.2361 0 13.0000 6.7082 33 | 7.6158 15.0333 13.0000 0 7.6158 34 | 0 8.2462 6.7082 7.6158 0 -------------------------------------------------------------------------------- /test_components.m: -------------------------------------------------------------------------------- 1 | function[ ] = test_components() 2 | 3 | x = [1 2 3 4 5 6 7 8]; 4 | 5 | y = [0 0 1 0 0 1]; 6 | 7 | % x = x(y > 0); 8 | x = [9]; 9 | 10 | idx1 = randi([1 length(x)]); 11 | 12 | idx1 13 | 14 | 15 | 16 | 17 | 18 | 19 | end 20 | 21 | function[ bValid ] = check_flight_validity( aafDistances, nLeaving, nVisiting, nReturning) 22 | 23 | maxDistance = 10; 24 | alpha = 1.5; 25 | bValid = 1; 26 | 27 | % Calculate the distance from departure to the customer 28 | a = aafDistances(nLeaving+1, nVisiting+1); 29 | 30 | % Calculate the distance from the customer to the returning 31 | b = aafDistances(nVisiting+1, nReturning+1); 32 | 33 | % Total drone distance 34 | fDroneDistance = (a+b)/alpha; 35 | 36 | if fDroneDistance > maxDistance 37 | bValid = 0; 38 | end 39 | 40 | 41 | end 42 | 43 | 44 | function[ aafDistances ] = calculateDistances( x, y ) 45 | % calculateDistances will calculate the distances between all the points 46 | % on the graph 47 | % INPUT 48 | % x, y the x and y coordinates of the customers 49 | % OUTPUT 50 | % aafDistances An array of distances where D_ij is the distance between 51 | % house i and house j; this matrix should be symmetric 52 | 53 | % Variables 54 | % 55 | 56 | % Initialize the distances array 57 | aafDistances = zeros(length(x)); 58 | 59 | % Calculate the distances 60 | for i = 1 : length(x) 61 | for j = 1 : length(x) 62 | aafDistances( i, j ) = sqrt( (x(i) - x(j))^2 + (y(i) - y(j))^2 ); 63 | end 64 | end 65 | 66 | end 67 | 68 | function[] = iforgor() 69 | 70 | % Drone 1, Customer 11, Leaving and Returning 71 | % anDrones(1).CustomerSet( 2).aanCust = [0 10; 0 9; 0 8; 0 7; 0 6; 0 5; 0 4; 0 3; 0 2; 0 1; 0 0; 10 9; 6 0]; 72 | % anDrones(1).CustomerSet( 1).aanCust = [0 10; 0 9; 6 0]; 73 | % anDrones(2).CustomerSet(12).aanCust = [0 10; 6 0]; 74 | % 75 | % anDrones(2).CustomerSet(11).aanCust = [1 2 3 4]; 76 | % anDrones(2).CustomerSet(11).nCust = 11; 77 | % anDrones(2).CustomerSet( 4).aanCust = [12; 13; 14]; 78 | 79 | 80 | solnIn.anPart1 = [0 10 9 8 7 3 5 6 0 ]; 81 | solnIn.anPart2 = [11 1 -1 12 4 2]; 82 | solnIn.anPart3 = [1 6 -1 2 3 6]; 83 | solnIn.anPart4 = [3 7 -1 3 6 7]; 84 | 85 | C0.x = [ 0 -4 -7 -6 4 6 2 9 8 7 6 3 0 ]; 86 | C0.y = [ 0 1 -3 -8 -9 -3 10 9 -2 -8 -8 3 0]; 87 | aafDistances = calculateDistances(C0.x, C0.y); 88 | 89 | 90 | % Count the number of drones 91 | if isempty(solnIn.anPart2) 92 | nDrones = 0; 93 | else 94 | nDrones = 1; 95 | i = 1; 96 | while i < length(solnIn.anPart2) 97 | if solnIn.anPart2(i) == -1 98 | nDrones = nDrones + 1; 99 | end 100 | i = i + 1; 101 | end 102 | end 103 | 104 | solnNew = solnIn; 105 | 106 | % Remove all UAV flights from S_new 107 | % solnNew.anPart2 = []; 108 | % solnNew.anPart3 = []; 109 | % solnNew.anPart4 = []; 110 | % for iDrone = 1 : nDrones 111 | % solnNew.anPart2 = -1; 112 | % solnNew.anPart3 = -1; 113 | % solnNew.anPart4 = -1; 114 | % end 115 | iCustomer = 1; 116 | iDrone = 1; 117 | while iCustomer <= length(solnNew.anPart2) && iDrone <= nDrones 118 | if solnNew.anPart2(iCustomer) == -1 119 | iDrone = iDrone + 1; 120 | else 121 | solnNew.anPart3(iCustomer) = 0; 122 | solnNew.anPart4(iCustomer) = 0; 123 | end 124 | iCustomer = iCustomer + 1; 125 | end 126 | 127 | 128 | 129 | % Create the P_j structure 130 | n = length(solnIn.anPart1) - 1; 131 | 132 | iDroneCustomer = 1; 133 | for iDrone = 1 : nDrones 134 | while iDroneCustomer < length(solnIn.anPart2) && solnIn.anPart2(iDroneCustomer) ~= -1 135 | iRow = 1; 136 | % P_j(iDrone).Customer(solnIn.anPart2(iDroneCustomer)).aanCust = zeros(n*(n+1)/2, 2); % the number of possible permutations 137 | for iLeaving = 1 : length(solnIn.anPart1) - 1 % subtract 1 because it drone can't leave from last spot 138 | for sReturning = iLeaving + 1 : length(solnIn.anPart1) 139 | % Only add solution if it is feasible 140 | if check_flight_validity(aafDistances, solnIn.anPart1(iLeaving), solnIn.anPart2(iDroneCustomer), solnIn.anPart1(sReturning)) 141 | % P_j(iDrone).Customer(solnIn.anPart2(iDroneCustomer)).aanCust(iRow, :) = ... 142 | % [solnIn.anPart1(iLeaving), solnIn.anPart1(sReturning)]; 143 | P_j(iDrone).Customer(solnIn.anPart2(iDroneCustomer)).aanCust(iRow, :) = ... 144 | [iLeaving, sReturning]; 145 | iRow = iRow + 1; 146 | end 147 | 148 | end 149 | 150 | end 151 | iDroneCustomer = iDroneCustomer + 1; 152 | end 153 | iDroneCustomer = iDroneCustomer + 1; 154 | end 155 | 156 | 157 | 158 | 159 | 160 | % Run algorithm 161 | for iteration = 1 : 10 162 | P_jCopy = P_j; 163 | iDrone = 1; 164 | bDone = 0; 165 | iDroneCustomer = 1; 166 | while iDrone < nDrones && bDone ~= 1 167 | while iDroneCustomer < length(solnNew.anPart2) && solnNew.anPart2(iDroneCustomer) ~= -1 && bDone ~= -1 168 | % Randomly pick (i, s) from P_c (if possible) 169 | % fprintf("iDrone: %d\n", iDrone) 170 | % fprintf("Customer: %d\n", solnNew.anPart2(iDroneCustomer)) 171 | % P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust 172 | anDimensions = size(P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust); 173 | 174 | % solnNew 175 | if anDimensions(1) == 0 176 | bDone = 1; 177 | else 178 | nRows = anDimensions(1); 179 | 180 | nRandRow = randi(nRows); 181 | nRandi = P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust(nRandRow, 1); 182 | nRands = P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust(nRandRow, 2); 183 | 184 | 185 | % Assign launch i and reconvene s locations to customer j 186 | solnNew.anPart3(iDroneCustomer) = nRandi; 187 | solnNew.anPart4(iDroneCustomer) = nRands; 188 | 189 | % solnNew 190 | 191 | % P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust 192 | % P_jCopy(iDrone).Customer(1).aanCust 193 | % Update P_jCopy according to the previously assigned flights to UAV_u 194 | iTempRow = 1; 195 | for iRow = 1 : nRows 196 | i = P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust(iRow, 1); 197 | s = P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust(iRow, 2); 198 | 199 | if i < nRandi && s <= nRandi 200 | P_jCopy2(iDrone).Customer(solnIn.anPart2(iDroneCustomer)).aanCust(iTempRow, :) = ... 201 | [i, s]; 202 | iTempRow = iTempRow + 1; 203 | elseif i >= nRands && s > nRandi 204 | P_jCopy2(iDrone).Customer(solnIn.anPart2(iDroneCustomer)).aanCust(iTempRow, :) = ... 205 | [i, s]; 206 | iTempRow = iTempRow + 1; 207 | else 208 | bOk = 0; 209 | end 210 | 211 | anDimensions = size(P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust); 212 | if iRow == nRows && anDimensions(1) == 0 213 | P_jCopy2 = P_jCopy; 214 | end 215 | 216 | end 217 | 218 | % Actually update P_jCopy 219 | P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust = P_jCopy2(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust; 220 | % P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust 221 | % P_jCopy(iDrone).Customer(1).aanCust 222 | % 223 | 224 | % for each customer that drone iDrone is delivering to 225 | % for each possible set i, s values for that drone 226 | % if i < nRandi && s <= nRandi: bOk = 1; 227 | 228 | % elseif i >= nRands && s > nRandi: bOk = 1; 229 | 230 | % else bOk = 0 231 | % Iterate iDroneCustomer 232 | iDroneCustomer = iDroneCustomer + 1; 233 | end 234 | end 235 | iDrone = iDrone + 1; 236 | end 237 | end 238 | 239 | 240 | end -------------------------------------------------------------------------------- /test.m: -------------------------------------------------------------------------------- 1 | function[] = test( ) 2 | % This main function will execute the entirety of the Truck and Drone 3 | % routing algorithm. 4 | 5 | %%% 6 | % When it comes to calculating the "travel time between two customers" we will 7 | % use the distance between the two nodes for trucks. When calculating the 8 | % travel time between two customers by UAV, we will use the distance but 9 | % divided by a speed factor of 1.5. 10 | %%% 11 | 12 | % Variables 13 | % C0 Set of customer locations with depot as the starting 14 | % location where C0.x is the x location of the 15 | % customers and C0.y is the y locations of the 16 | % customers. 17 | % c Number of customers 18 | % U Set of drones 19 | % u Number of drones 20 | % aafDistancesTruck Travel times between two customers i & j by truck 21 | % soln Our representation of the solution where: 22 | % ansoln.Part1 represents the sequence of customers 23 | % visited by the truck 24 | % ansoln.Part2 represents the assignment of 25 | % customers to UAVs & also the sequence of 26 | % visits by each UAV 27 | % ansoln.Part3 represents the launch locations of 28 | % each UAV for every flight 29 | % ansoln.Part4 represents the reconvene locations of 30 | % each UAV for every flight 31 | % 32 | 33 | 34 | % Generate customer locations 35 | % x = (50 - -50)*rand(1, 4) + -50; 36 | % y = (50 - -50)*rand(1, 4) + -50; 37 | % C0.x = [ 0 -10 0 0 ]; % we treat the first slot as the depot; 38 | % C0.y = [ 0 0 -10 -5 ]; % we treat the first slot as the depot; 39 | % C0.x = [ 0 randi([-10, 10], 1, 10) 0 ]; 40 | % C0.y = [ 0 randi([-10, 10], 1, 10) 0 ]; 41 | 42 | % C0.x = [ 0 -4 -7 -6 4 6 2 9 8 7 6 0 ]; 43 | % C0.y = [ 0 1 -3 -8 -9 -3 10 9 -2 -8 -8 0]; 44 | 45 | % % 0 1 2 3 4 0 Customer ID 46 | % % 1 2 3 4 5 6 Indices 47 | % C0.x = [ 0 8 6 -7 1 0 ]; 48 | % C0.y = [ 0 -2 -3 -3 2 0 ]; 49 | 50 | % 0 1 2 3 4 5 6 0 Customer ID 51 | % 1 2 3 4 5 6 7 8 Indices 52 | C0.x = [ 0 -2 -3 2 2 7 -4 0 ]; 53 | C0.y = [ 0 4 1 4 3 1 3 0 ]; 54 | 55 | 56 | % Plot the customer locations 57 | % hold on 58 | % figure( 1 ); 59 | % plot(0,0,'b*', 'MarkerSize', 12) 60 | % plot_2D(C0.x, C0.y) 61 | % hold off 62 | 63 | 64 | % Numbers of customers (excluding the depot) 65 | c = length(C0.x) - 2; 66 | 67 | % Number of drones 68 | U = [1, 2]; 69 | u = length(U); 70 | 71 | % Initial and final temperature (Inputs for SA) 72 | T0 = 90; 73 | TFinal = 0.01; 74 | Beta_SA = 0.99; 75 | Imax = 20; 76 | 77 | % Calculate the distances between each all points 78 | % Here, our first row of the columns is how far our depot is from each 79 | % of the customers. 80 | aafDistances = calculateDistances(C0.x, C0.y); 81 | 82 | 83 | % Initialize solution data structure 84 | soln.anPart1 = zeros(1, length(C0.x)); % the Truck Route 85 | soln.anPart2 = [-1]; % Assignment of customers to UAVs 86 | soln.anPart3 = [-1]; % Launch locations of the UAVs 87 | soln.anPart4 = [-1]; % Reconvene locations of the UAVs 88 | 89 | 90 | % Generate initial solution s, to the STRPD based on SA 91 | % s = simulatedAnnealing(c, aafDistances, soln, T0, TFinal, Beta_SA, Imax, u); 92 | 93 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 94 | % Test plotting our path with drones 95 | % THis is correct btw 96 | % s.anPart1 = [ 0 4 2 3 0 ]; 97 | % s.anPart3 = [ 2 -1 ]; % These indices refer to the cell number in part 1, so this is wrong. 98 | % s.anPart2 = [ 1 -1 ]; % visits customer 1 99 | % s.anPart4 = [ 3 -1 ]; % returns to index 2 of s.anPart1 (which is customer 3) 100 | 101 | s.anPart1 = [ 0 4 3 1 2 0 ]; 102 | s.anPart3 = [ 1 -1 4 ]; 103 | s.anPart2 = [ 5 -1 6 ]; 104 | s.anPart4 = [ 3 -1 5 ]; 105 | 106 | % s.anPart1 = [ 0 4 2 0 ]; 107 | % s.anPart3 = [ 2 3 -1 ]; % These indices refer to the cell number in part 1, so this is wrong. 108 | % s.anPart2 = [ 1 3 -1 ]; % visits customer 1 109 | % s.anPart4 = [ 3 4 -1 ]; % returns to index 2 of s.anPart1 (which is customer 3) 110 | 111 | % s.anPart1 = [ 0 4 2 0 ]; 112 | % s.anPart3 = [ 2 -1 3 ]; % These indices refer to the cell number in part 1, so this is wrong. 113 | % s.anPart2 = [ 1 -1 3 ]; % visits customer 1 114 | % s.anPart4 = [ 3 -1 4 ]; % returns to index 2 of s.anPart1 (which is customer 3) 115 | 116 | % s.anPart1 = [ 0 4 2 0 ]; 117 | % s.anPart3 = [ 2 -1 3 ]; % These indices refer to the cell number in part 1, so this is wrong. 118 | % s.anPart2 = [ 1 -1 3 ]; % visits customer 1 119 | % s.anPart4 = [ 3 -1 4 ]; % returns to index 2 of s.anPart1 (which is customer 3) 120 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 121 | 122 | % Plot the truck and drone route 123 | plot_route( C0, s); 124 | 125 | 126 | % Initialize best solution, the rsult of the best solution, the 127 | % iteration number, and the temperature 128 | s_best = s; 129 | C0 130 | fs_best = f(aafDistances, s_best, u) 131 | 132 | 133 | % 134 | 135 | 136 | 137 | end 138 | 139 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 140 | function[ ] = plot_2D( x, y ) 141 | % plot_2D will plot the given x and y coordinates 142 | % INPUT 143 | % x 144 | % y 145 | % OUTPUT 146 | 147 | % Variables 148 | % 149 | 150 | % Plot the graph 151 | plot(x, y, "r.", 'MarkerSize', 12) 152 | xline(0) 153 | yline(0) 154 | 155 | end 156 | 157 | function[ ] = plot_route( C0, s) 158 | % plot_route will take in the customer locations & the initial solution 159 | % produced via the Simulated annealing method. It will then plot the route 160 | % the truck will take. 161 | % Input 162 | % C0 The locations of all the customers 163 | % s The route we are plotting 164 | % u The number of drones 165 | % Output 166 | % 167 | 168 | % Variables 169 | % a, b Placeholder variables for the coordinates 170 | % nCustInd counter to keep track of the customer indices 171 | 172 | % Plot the truck route 173 | nCustInd = 2; 174 | a = [ 0 ]; 175 | b = [ 0 ]; 176 | 177 | % Fill in these a and b arrays 178 | % for i = s.anPart1( 2 : length(C0.x) - 1 ) 179 | for i = s.anPart1( 2 : length(s.anPart1) - 1) 180 | a(nCustInd) = C0.x(i+1); 181 | b(nCustInd) = C0.y(i+1); 182 | nCustInd = nCustInd + 1; 183 | a; 184 | b; 185 | end 186 | 187 | a(end + 1) = 0; 188 | b(end + 1) = 0; 189 | 190 | 191 | 192 | % Initialize the coords visited by the drones 193 | aanDrones = zeros(length(s.anPart2)-1, 6); 194 | 195 | C0; 196 | s; 197 | % Fill in this matrix 198 | for iCustomer = 1 : length(s.anPart2) 199 | if s.anPart2(iCustomer) == -1 200 | aanDrones( iCustomer, : ) = -1*ones(1, 6); 201 | % aanDrones = [ aanDrones ; -1*ones(1, 6) ] 202 | 203 | else 204 | aanDrones(iCustomer, 1) = C0.x(s.anPart1(s.anPart3(iCustomer)) + 1); 205 | aanDrones(iCustomer, 4) = C0.y(s.anPart1(s.anPart3(iCustomer)) + 1); 206 | 207 | aanDrones(iCustomer, 3) = C0.x(s.anPart1(s.anPart4(iCustomer)) + 1); 208 | aanDrones(iCustomer, 6) = C0.y(s.anPart1(s.anPart4(iCustomer)) + 1); 209 | 210 | aanDrones(iCustomer, 2) = C0.x(s.anPart2(iCustomer) + 1); 211 | aanDrones(iCustomer, 5) = C0.y(s.anPart2(iCustomer) + 1); 212 | end 213 | end 214 | 215 | 216 | 217 | % Plot the drone route 218 | aColors = ["r--", "b--", "g--", "k--", "m--"]; 219 | 220 | % 221 | aDimensions = size(aanDrones); 222 | 223 | % Plot this stuff 224 | figure(2) 225 | hold on; 226 | plot(0,0,'b*', 'MarkerSize', 12) 227 | plot(a, b, 'k-') 228 | plot(a, b, "ro") 229 | 230 | iDrone = 1; 231 | for iRow = 1 : aDimensions 232 | x = [ 0 ]; 233 | y = [ 0 ]; 234 | 235 | if aanDrones(iRow, 1) == -1 236 | iDrone = iDrone + 1; 237 | else 238 | for iCol = 1 : 3 239 | x(iCol) = aanDrones(iRow, iCol); 240 | y(iCol) = aanDrones(iRow, iCol+3); 241 | end 242 | 243 | plot(x, y, aColors(iDrone)); 244 | end 245 | end 246 | hold off; 247 | end 248 | 249 | function[ aafDistances ] = calculateDistances( x, y ) 250 | % calculateDistances will calculate the distances between all the points 251 | % on the graph 252 | % INPUT 253 | % x, y the x and y coordinates of the customers 254 | % OUTPUT 255 | % aafDistances An array of distances where D_ij is the distance between 256 | % house i and house j; this matrix should be symmetric 257 | 258 | % Variables 259 | % 260 | 261 | % Initialize the distances array 262 | aafDistances = zeros(length(x)); 263 | 264 | % Calculate the distances 265 | for i = 1 : length(x) 266 | for j = 1 : length(x) 267 | aafDistances( i, j ) = sqrt( (x(i) - x(j))^2 + (y(i) - y(j))^2 ); 268 | end 269 | end 270 | 271 | end 272 | 273 | function[ s_best ] = ... 274 | simulatedAnnealing( c, aafDistances, soln, T0, TFinal, Beta_SA, Imax, u) 275 | % simulatedAnnealing function will take in an array of distances 276 | % and will generate a random TRP (traveling repairman problem). 277 | % INPUT 278 | % c The length of the soln 279 | % aafdistances Array of distances between customers 280 | % soln Data structure of solution 281 | % T0 The initial temperature 282 | % TFinal The final temperature we want to approach 283 | % Beta_SA The cooling schedule for our simulated annealing 284 | % Imax The maximum number of iterations 285 | % u The numebr of drones 286 | % OUTPUT 287 | % s_best Returns the best solution to the TRP 288 | 289 | 290 | % Variables 291 | % soln Data structure with solutions parts 1, 2, 3, and 4 292 | % s Solution to the TRP problem 293 | % s_best Best solution 294 | % fs_best The result of the best solution7 295 | % T The current temperature of our simulated annealing 296 | 297 | 298 | % Initialize our current and final temperatures 299 | T = T0; 300 | 301 | % Generate a ranomd TRP (Traveling Repairman Problem) 302 | % randomly insert all customers into vector 303 | soln.anPart1 = [ 0 randperm(c) 0 ]; 304 | s = soln; % create duplicate of soln for comparison 305 | 306 | % Initilaize the best solution and the best result 307 | s_best = s; 308 | fs_best = f(aafDistances, s, u); 309 | 310 | sPrime = soln; 311 | 312 | % Run the simulated annealing process 313 | while T > TFinal 314 | for i = 1 : Imax 315 | % Find neighbor to s 316 | sPrime.anPart1 = neighbor(s); 317 | 318 | 319 | 320 | % Check if neighbor is better than current solution 321 | diff = fs_best - f(aafDistances, sPrime, u); 322 | 323 | % Check if neighbor is better 324 | if f(aafDistances, sPrime, u) < f(aafDistances, s_best, u) 325 | s_best.anPart1 = sPrime.anPart1; 326 | fs_best = f(aafDistances, sPrime, u); 327 | end 328 | 329 | % Boltzmann probability time babbbyyyy 330 | if rand() < exp(-abs(diff) / T) 331 | s.anPart1 = sPrime.anPart1; 332 | end 333 | 334 | end 335 | 336 | % Cooling schedule 337 | T = Beta_SA * T; 338 | end 339 | 340 | 341 | 342 | end 343 | 344 | function[ sPrime ] = neighbor( soln ) 345 | % The neighbor function will implement the well-known 2-opt method for 346 | % creating neighbors for our current solution s. Here, we must note that 347 | % our 2-opt method will switch two customers within the vector of Parts 1 348 | % and 2. The customers are selected randomly. Our 2-opt is a local search 349 | % heuristic. 350 | 351 | % INPUT 352 | % soln Current solution data structure. 353 | % OUTPUT 354 | % sPrime A neighbor to input solution soln 355 | 356 | % Variables 357 | % sPrime The soln.anPart1 which is the neighbor to our original 358 | % soln.anPart1 359 | % randIndex1 The index of the customer that will be switched out from 360 | % part 1 of the soln data structure 361 | % randIndex2 The index of the customer that will be switched out from 362 | % part 2 of the soln data structure. 363 | % nTemp A temporary variable that holds the customer at index 364 | % randIndex1 365 | 366 | 367 | % Randomly pick two customer indices from anPart1 368 | randIndex1 = -1; 369 | randIndex2 = -1; 370 | 371 | % Make sure that we don't pick the same two indices 372 | while randIndex1 == randIndex2 373 | randIndex1 = randi([2, length(soln.anPart1)-1]); 374 | randIndex2 = randi([2, length(soln.anPart1)-1]); 375 | end 376 | 377 | % fprintf("Before switching: \n") 378 | % soln.anPart1 379 | 380 | % Switch their positions in soln.anPart1 381 | nTemp = soln.anPart1(randIndex1); 382 | soln.anPart1(randIndex1) = soln.anPart1(randIndex2); 383 | soln.anPart1(randIndex2) = nTemp; 384 | 385 | % % fprintf("after switching: \n") 386 | % soln.anPart1 387 | 388 | % Return the new neighbor to the input solution 389 | sPrime = soln.anPart1; 390 | 391 | end 392 | 393 | function[ fTotalWaitTime ] = f( aafDistances, soln, k) 394 | % This function f is our wait time function. In this case, we treat 395 | % the time = distance for our trucks and we calculate the time for the 396 | % drones using a speed factor of 1.5 397 | 398 | % INPUT 399 | % aafDistances Array of floats containing the distances between custs. 400 | % soln Data structure of solutions in parts 1, 2, 3 and 4 401 | % k Number of drones 402 | % OUTPUT 403 | % fTotalWaitTime Float of total wait time 404 | 405 | 406 | % Variables 407 | % alpha Our speed factor for the drones. 408 | % aSoln Current solution 409 | 410 | 411 | % Initialize constants 412 | alpha = 1.5; 413 | 414 | 415 | % Create temporary list of customers 416 | anCustomers = soln.anPart1; 417 | 418 | 419 | % Initialize total wait time 420 | fTotalWaitTime = 0; 421 | 422 | 423 | % Create vector arrival and departure times for drones and trucks 424 | aafDroneArrivalTime = zeros(k, length(aafDistances)); 425 | aafTruckArrivalTime = zeros(1, length(aafDistances)); 426 | aafTruckDepartureTime = zeros(1, length(aafDistances)); 427 | 428 | 429 | soln; 430 | aafDistances; 431 | 432 | % Calculate the total wait time for the truck customers 433 | for iCustomerIndex = 2 : length(soln.anPart1) % Here iCustomerIndex = 1 & = length(soln.anPart1) are the nodes 434 | iDrone = 1; % Initialize drone counter 435 | 436 | % Calculate the arrival time of the truck to this node 437 | aafTruckArrivalTime(anCustomers(iCustomerIndex) + 1) = ... 438 | aafDistances( anCustomers(iCustomerIndex - 1) + 1, anCustomers(iCustomerIndex) + 1 ); 439 | 440 | % "Put this arrival times in aafTruckDepartureTimes and vector and 441 | % then update them if they have a drone approaching that same 442 | % node. So that if there is no drone going towards customer at 443 | % iCustomerIndex, then they already have something in their truck 444 | % arrival time vector. That is, we are assuming that no customer 445 | % has a drone delivering to them. 446 | aafTruckDepartureTime(anCustomers(iCustomerIndex) + 1) = ... 447 | aafTruckArrivalTime(anCustomers(iCustomerIndex) + 1); 448 | 449 | 450 | 451 | for iReconveneIndex = 1 : length(soln.anPart4) 452 | 453 | % Check if we have an "X" there 454 | if soln.anPart4(iReconveneIndex) == -1 455 | iDrone = iDrone + 1; 456 | end 457 | 458 | % Get the arrival time of the drones 459 | if iCustomerIndex == soln.anPart4(iReconveneIndex) % This means there is a drone flying to this point 460 | a = aafDistances( anCustomers(soln.anPart3(iReconveneIndex)) + 1, soln.anPart2(iReconveneIndex) + 1); 461 | b = aafDistances( soln.anPart2(iReconveneIndex) + 1, anCustomers(soln.anPart4(iReconveneIndex)) + 1); 462 | 463 | aafDroneArrivalTime(iDrone, anCustomers(soln.anPart4(iReconveneIndex)) + 1) = (a + b)/alpha; 464 | 465 | %%%% WORKS TILL HERE %%%% 466 | 467 | % if Truck is there before the drone, it must wait 468 | if aafTruckArrivalTime(anCustomers(iCustomerIndex) + 1) < max (aafDroneArrivalTime( :, anCustomers(iCustomerIndex) + 1 ) ) 469 | aafTruckDepartureTime(anCustomers(iCustomerIndex) + 1) = ... 470 | max (aafDroneArrivalTime( :, anCustomers(iCustomerIndex) + 1) ); 471 | end 472 | 473 | % If the drone is there before the truck 474 | if aafTruckArrivalTime(anCustomers(iCustomerIndex) + 1) > max( aafDroneArrivalTime( :, iCustomerIndex) ) 475 | aafTruckDepartureTime(anCustomers(iCustomerIndex) + 1) = aafTruckArrivalTime( anCustomers(iCustomerIndex) + 1); 476 | end 477 | 478 | end 479 | 480 | end 481 | 482 | end 483 | 484 | for fDepartureTime = aafTruckDepartureTime 485 | fTotalWaitTime = fTotalWaitTime + fDepartureTime; 486 | end 487 | 488 | aafTruckArrivalTime 489 | aafTruckDepartureTime 490 | aafDroneArrivalTime 491 | 492 | 493 | 494 | end 495 | -------------------------------------------------------------------------------- /TDRA.asv: -------------------------------------------------------------------------------- 1 | function[] = TDRA( ) 2 | % This main function will execute the entirety of the Truck and Drone 3 | % routing algorithm. 4 | 5 | %%% 6 | % When it comes to calculating the "travel time between two customers" we will 7 | % use the distance between the two nodes for trucks. When calculating the 8 | % travel time between two customers by UAV, we will use the distance but 9 | % divided by a speed factor of 1.5. 10 | %%% 11 | 12 | % Variables 13 | % C0 Set of customer locations with depot as the starting 14 | % location where C0.x is the x location of the 15 | % customers and C0.y is the y locations of the 16 | % customers. 17 | % c Number of customers 18 | % U Set of drones 19 | % u Number of drones 20 | % aafDistancesTruck Travel times between two customers i & j by truck 21 | % soln Our representation of the solution where: 22 | % ansoln.Part1 represents the sequence of customers 23 | % visited by the truck 24 | % ansoln.Part2 represents the assignment of 25 | % customers to UAVs & also the sequence of 26 | % visits by each UAV 27 | % ansoln.Part3 represents the launch locations of 28 | % each UAV for every flight 29 | % ansoln.Part4 represents the reconvene locations of 30 | % each UAV for every flight 31 | % Rmax Maximum number of iterations that our heuristic 32 | % will run 33 | % WeightInfo The weight of each heuristic which serves as an 34 | % indicator of the performance of the heuristic 35 | 36 | 37 | 38 | 39 | % Generate customer locations 40 | % C0.x = (50 - -50)*rand(1, 4) + -50; 41 | % C0.y = (50 - -50)*rand(1, 4) + -50; 42 | % C0.x = [ 0 -10 0 0 ]; % we treat the first slot as the depot; 43 | % C0.y = [ 0 0 -10 -5 ]; % we treat the first slot as the depot; 44 | % C0.x = [ 0 randi([-10, 10], 1, 10) 0 ]; 45 | % C0.y = [ 0 randi([-10, 10], 1, 10) 0 ]; 46 | 47 | C0.x = [ 0 -4 -7 -6 4 6 2 9 8 7 6 0 ]; 48 | C0.y = [ 0 1 -3 -8 -9 -3 10 9 -2 -8 -8 0]; 49 | 50 | % % 0 1 2 3 4 0 Customer ID 51 | % % 1 2 3 4 5 6 Indices 52 | % C0.x = [ 0 8 6 -7 1 0 ]; 53 | % C0.y = [ 0 -2 -3 -3 2 0 ]; 54 | 55 | % 0 1 2 3 4 5 6 0 Customer ID 56 | % 1 2 3 4 5 6 7 8 Indices 57 | % C0.x = [ 0 -2 -3 2 2 7 -4 0 ]; 58 | % C0.y = [ 0 4 1 4 3 1 3 0 ]; 59 | 60 | % 61 | % C0.x = [0, 1, 2, 3, 3, 2, 1, 0 ] 62 | % C0.y = [0, 1, 1, 0.1, -1, -1, -1, 0] 63 | 64 | %%%%%%%%%%%%%%%%%%%%%%%%Testing ellipse fitting %%%%%%%%%%%%%%%%%%%%%%%% 65 | % Slanted ellipse 66 | % C0.x = [ 1, 2, 3, 4, 4.5, 5, 5.5, 6, 6, 5.7, 5.5, 5, 4, 3, 1, ... 67 | % -1, -3, -6, -7, -7.5, -7, -6, ... 68 | % -3, -2, -1, -4, -5]; 69 | % C0.y = [ 7, 6, 5, 4, 3, 2, 0.5, -1, -1.5, -3.5, -4, -5, -5.3, -5.2, -4.5,... 70 | % 8, 8, 7, 6, 4, 2, 0, ... 71 | % -2.5, -3, -3.5, -1.5, -1]; 72 | 73 | 74 | % Ellipse along x axis; This works well 75 | % C0.x = [ -3, -2.5, -2, -1.5, -1, -.5, 0, .5, 1, 1.5, 2, 2.5, 3, -3, -2.5, -2, -1.5, -1, -.5, 0, .5, 1, 1.5, 2, 2.5, 3 ]; 76 | % C0.y = [ 0.54, 1.06, 1.34, 1.52, 1.64, 1.71, 1.73, 1.71, 1.64, 1.54, 1.34, 1.06, .54, -0.54, -1.06, -1.34, -1.52, -1.64, -1.71, -1.73, -1.71, -1.64, -1.54, -1.34, -1.06, -.54 ]; 77 | 78 | % Ellipse along y axis 79 | % C0.x = [ 0 [ -2 : 0.5 : 2 ] [ -2 : 0.5 : 2 ] 0] 80 | % C0.y = [0 sqrt( 1 - (C0.x(1 : 9).^2)/4 ) [ -1*sqrt( 1 - (C0.x(10 : 18).^2)/4 ) ] 0 ] 81 | 82 | 83 | % Plot the customer locations 84 | % hold on 85 | % figure( 1 ); 86 | % plot(0,0,'b*', 'MarkerSize', 12) 87 | % plot(C0.x, C0.y, 'bo') 88 | % hold off 89 | 90 | % Numbers of customers (excluding the depot) 91 | c = length(C0.x) - 2; 92 | 93 | % Number of drones 94 | U = [1, 2]; 95 | u = length(U); 96 | 97 | % Initial and final temperature (Inputs for SA) 98 | T0 = 90; 99 | TFinal = 0.01; 100 | Beta_SA = 0.99; 101 | Imax = 20; 102 | fBoltzmannThresh = 0.8; 103 | 104 | % Calculate the distances between each all points 105 | % Here, our first row of the columns is how far our depot is from each 106 | % of the customers. 107 | aafDistances = calculateDistances(C0.x, C0.y); 108 | 109 | % Initialize solution data structure 110 | soln.anPart1 = zeros(1, length(C0.x)); % the Truck Route 111 | soln.anPart2 = [-1]; % Assignment of customers to UAVs 112 | soln.anPart3 = [-1]; % Launch locations of the UAVs 113 | soln.anPart4 = [-1]; % Reconvene locations of the UAVs 114 | 115 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 116 | % Test plotting our path with drones 117 | % THis is correct btw 118 | % s.anPart1 = [ 0 4 2 3 0 ]; 119 | % s.anPart3 = [ 2 -1 ]; % These indices refer to the cell number in part 1, so this is wrong. 120 | % s.anPart2 = [ 1 -1 ]; % visits customer 1 121 | % s.anPart4 = [ 3 -1 ]; % returns to index 2 of s.anPart1 (which is customer 3) 122 | % 123 | % s.anPart1 = [ 0 4 3 1 2 0 ]; % MOST RECENT TEST 124 | % s.anPart3 = [ 1 -1 4 ]; 125 | % s.anPart2 = [ 5 -1 6 ]; 126 | % s.anPart4 = [ 3 -1 5 ]; 127 | % 128 | % s.anPart1 = [ 0 4 2 0 ]; 129 | % s.anPart3 = [ 2 3 -1 ]; % These indices refer to the cell number in part 1, so this is wrong. 130 | % s.anPart2 = [ 1 3 -1 ]; % visits customer 1 131 | % s.anPart4 = [ 3 4 -1 ]; % returns to index 2 of s.anPart1 (which is customer 3) 132 | % 133 | % s.anPart1 = [ 0 4 2 0 ]; 134 | % s.anPart3 = [ 2 -1 3 ]; % These indices refer to the cell number in part 1, so this is wrong. 135 | % s.anPart2 = [ 1 -1 3 ]; % visits customer 1 136 | % s.anPart4 = [ 3 -1 4 ]; % returns to index 2 of s.anPart1 (which is customer 3) 137 | % 138 | % s.anPart1 = [ 0 4 2 0 ]; 139 | % s.anPart3 = [ 2 -1 3 ]; % These indices refer to the cell number in part 1, so this is wrong. 140 | % s.anPart2 = [ 1 -1 3 ]; % visits customer 1 141 | % s.anPart4 = [ 3 -1 4 ]; % returns to index 2 of s.anPart1 (which is customer 3) 142 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 143 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 144 | 145 | 146 | %% Generate Initial Solution 147 | % Generate initial solution s, to the STRPD based on SA 148 | s = simulatedAnnealing(c, aafDistances, soln, T0, TFinal, Beta_SA, Imax, u); 149 | 150 | % Run elliptical customer assignment on result of Simulated annealing 151 | s = ellipticalCustomerAssignment( s, C0, u ); 152 | 153 | %% Intialize Values 154 | % Initialize the best solution and the result of the best solution 155 | s_best = s; 156 | fs_best = f(aafDistances, s, u); 157 | T = T0; 158 | 159 | %% Plot the truck and drone route 160 | plot_route( C0, s, 1); 161 | 162 | % ALAN FIX THIS 163 | % The reason our plot isn't working too well is because for whatever 164 | % reason, the solnOut is choosing to return to a customer location that 165 | % is further away than the one it should be returning to . 166 | hold on; 167 | plot_route( C0, s_best, 4) 168 | hold off; 169 | 170 | s 171 | 172 | solnOut = apply_heuristic_4_Greedy_Assignment(s, C0, aafDistances) 173 | 174 | 175 | % Initialize the weights before running main algorithm 176 | WeightInfo = weight_init(); 177 | 178 | Rmax = 100; % should be 6000 179 | for iIter = 1 : Rmax 180 | % Select a heuristic 181 | nHeuristic = select_heuristic(WeightInfo); 182 | 183 | nHeuristic = 5; 184 | 185 | % Apply the selected heuristic to s 186 | switch nHeuristic 187 | case 1 188 | s_prime = apply_heuristic_2_opt(s, C0, aafDistances); 189 | fs_prime = f(aafDistances, s_prime, u); 190 | case 2 191 | s_prime = apply_heuristic_7_drone_planner(s, C0, aafDistances); 192 | fs_prime = f(aafDistances, s_prime, u); 193 | case 3 194 | s_prime = apply_heuristic_3_opt(s, C0, aafDistances); 195 | fs_prime = f(aafDistances, s_prime, u); 196 | case 4 197 | s_prime = apply_heuristic_4_Greedy_Assignment(s, C0, aafDistances); 198 | fs_prime = f(aafDistances, s_prime, u); 199 | case 5 200 | s_prime = apply_heuristic_5_Origin_Destination(s, C0, aafDistances, u); 201 | fs_prime = f(aafDistances, s_prime, u); 202 | 203 | otherwise 204 | fs_prime = 0; 205 | end 206 | 207 | % Check if new prime solution is better than best solution 208 | if fs_prime < fs_best 209 | s_best = s_prime; 210 | fs_best = fs_prime; 211 | end 212 | 213 | % If s_prim is accepted as new sol based on Boltzmann; Boltzmann probability time babbbyyyy 214 | diff = fs_best - f(aafDistances, s_prime, u); 215 | 216 | % if rand() < exp(-abs(diff) / T) 217 | if exp(-abs(diff) / T) < fBoltzmannThresh 218 | s = s_prime; 219 | end 220 | 221 | % Get the current value 222 | fs_Curr = f(aafDistances, s, u); 223 | 224 | % Update weights of heuristics 225 | WeightInfo = update_weights( WeightInfo, nHeuristic, fs_prime, fs_best, fs_Curr, iIter); 226 | end 227 | 228 | f(aafDistances, solnOut, u) 229 | 230 | answerBeforeHueristic2Opt = solnOut 231 | beforeResult = f(aafDistances, answerBeforeHueristic2Opt, u) 232 | 233 | % This is line 4 of Algorithm 1: Outline of the TDRA 234 | answerAfterHueristic2Opt = apply_heuristic_2_opt(solnOut, C0, aafDistances) 235 | afterResult = f(aafDistances, answerAfterHueristic2Opt, u) 236 | 237 | end 238 | 239 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 240 | function[ ] = plot_route( C0, s, fig_num ) 241 | % plot_route will take in the customer locations & the initial solution 242 | % produced via the Simulated annealing method. It will then plot the route 243 | % the truck will take. 244 | % Input 245 | % C0 The locations of all the customers 246 | % s The route we are plotting 247 | % u The number of drones 248 | % fig_num The number of the figure 249 | % Output 250 | % 251 | 252 | % Variables 253 | % a, b Placeholder variables for the coordinates 254 | % nCustInd counter to keep track of the customer indices 255 | 256 | % Plot the truck route 257 | nCustInd = 2; 258 | a = [ 0 ]; 259 | b = [ 0 ]; 260 | 261 | % Fill in these a and b arrays 262 | % for i = s.anPart1( 2 : length(C0.x) - 1 ) 263 | for i = s.anPart1( 2 : length(s.anPart1) - 1) 264 | a(nCustInd) = C0.x(i+1); 265 | b(nCustInd) = C0.y(i+1); 266 | nCustInd = nCustInd + 1; 267 | a; 268 | b; 269 | end 270 | 271 | a(end + 1) = 0; 272 | b(end + 1) = 0; 273 | 274 | 275 | 276 | % Initialize the coords visited by the drones 277 | aanDrones = zeros(length(s.anPart2)-1, 6); 278 | 279 | C0; 280 | s; 281 | % Fill in this matrix 282 | for iCustomer = 1 : length(s.anPart2) 283 | if s.anPart2(iCustomer) == -1 284 | aanDrones( iCustomer, : ) = -1*ones(1, 6); 285 | % aanDrones = [ aanDrones ; -1*ones(1, 6) ] 286 | 287 | else 288 | aanDrones(iCustomer, 1) = C0.x(s.anPart1(s.anPart3(iCustomer)) + 1); 289 | aanDrones(iCustomer, 4) = C0.y(s.anPart1(s.anPart3(iCustomer)) + 1); 290 | 291 | aanDrones(iCustomer, 3) = C0.x(s.anPart1(s.anPart4(iCustomer)) + 1); 292 | aanDrones(iCustomer, 6) = C0.y(s.anPart1(s.anPart4(iCustomer)) + 1); 293 | 294 | aanDrones(iCustomer, 2) = C0.x(s.anPart2(iCustomer) + 1); 295 | aanDrones(iCustomer, 5) = C0.y(s.anPart2(iCustomer) + 1); 296 | end 297 | end 298 | 299 | 300 | 301 | % Plot the drone route 302 | aColors = ["r--", "b--", "g--", "k--", "m--"]; 303 | 304 | % 305 | aDimensions = size(aanDrones); 306 | 307 | % Plot this stuff 308 | figure(fig_num) 309 | hold on; 310 | plot(0,0,'b*', 'MarkerSize', 12) 311 | plot(a, b, 'k-') 312 | plot(a, b, "ro") 313 | 314 | iDrone = 1; 315 | for iRow = 1 : aDimensions 316 | x = [ 0 ]; 317 | y = [ 0 ]; 318 | 319 | if aanDrones(iRow, 1) == -1 320 | iDrone = iDrone + 1; 321 | else 322 | for iCol = 1 : 3 323 | x(iCol) = aanDrones(iRow, iCol); 324 | y(iCol) = aanDrones(iRow, iCol+3); 325 | end 326 | 327 | plot(x, y, aColors(iDrone)); 328 | end 329 | end 330 | hold off; 331 | end 332 | 333 | function[ aafDistances ] = calculateDistances( x, y ) 334 | % calculateDistances will calculate the distances between all the points 335 | % on the graph 336 | % INPUT 337 | % x, y the x and y coordinates of the customers 338 | % OUTPUT 339 | % aafDistances An array of distances where D_ij is the distance between 340 | % house i and house j; this matrix should be symmetric 341 | 342 | % Variables 343 | % 344 | 345 | % Initialize the distances array 346 | aafDistances = zeros(length(x)); 347 | 348 | % Calculate the distances 349 | for i = 1 : length(x) 350 | for j = 1 : length(x) 351 | aafDistances( i, j ) = sqrt( (x(i) - x(j))^2 + (y(i) - y(j))^2 ); 352 | end 353 | end 354 | 355 | end 356 | 357 | function[ s_best ] = ... 358 | simulatedAnnealing( c, aafDistances, soln, T0, TFinal, Beta_SA, Imax, u) 359 | % simulatedAnnealing function will take in an array of distances 360 | % and will generate a random TRP (traveling repairman problem). 361 | % INPUT 362 | % c The length of the soln 363 | % aafdistances Array of distances between customers 364 | % soln Data structure of solution 365 | % T0 The initial temperature 366 | % TFinal The final temperature we want to approach 367 | % Beta_SA The cooling schedule for our simulated annealing 368 | % Imax The maximum number of iterations 369 | % u The numebr of drones 370 | % OUTPUT 371 | % s_best Returns the best solution to the TRP 372 | 373 | 374 | % Variables 375 | % soln Data structure with solutions parts 1, 2, 3, and 4 376 | % s Solution to the TRP problem 377 | % s_best Best solution 378 | % fs_best The result of the best solution7 379 | % T The current temperature of our simulated annealing 380 | 381 | 382 | % Initialize our current and final temperatures 383 | T = T0; 384 | 385 | % Initialize boltzmann threshold 386 | fBoltzmannThresh = 0.8; 387 | 388 | % Generate a ranomd TRP (Traveling Repairman Problem) 389 | % randomly insert all customers into vector 390 | soln.anPart1 = [ 0 randperm(c) 0 ]; 391 | s = soln; % create duplicate of soln for comparison 392 | 393 | % Initilaize the best solution and the best result 394 | s_best = s; 395 | fs_best = f(aafDistances, s, u); 396 | 397 | sPrime = soln; 398 | 399 | % Run the simulated annealing process 400 | while T > TFinal 401 | for i = 1 : Imax 402 | % Find neighbor to s 403 | sPrime.anPart1 = neighbor(s); 404 | 405 | % Check if neighbor is better than current solution 406 | diff = fs_best - f(aafDistances, sPrime, u); 407 | 408 | % Check if neighbor is better 409 | if f(aafDistances, sPrime, u) < f(aafDistances, s_best, u) 410 | s_best.anPart1 = sPrime.anPart1; 411 | fs_best = f(aafDistances, sPrime, u); 412 | end 413 | 414 | % Boltzmann probability time babbbyyyy 415 | if exp(-abs(diff) / T) < fBoltzmannThresh 416 | s.anPart1 = sPrime.anPart1; 417 | end 418 | end 419 | 420 | % Cooling schedule 421 | T = Beta_SA * T; 422 | end 423 | end 424 | 425 | function[ solnOut ] = ellipticalCustomerAssignment( solnIn, C0, k ) 426 | % The ellipticalCustomerAssignment heuristic function will take in a soln 427 | % and then will return another solution. 428 | 429 | % INPUT 430 | % solnIn This is the input solution 431 | % C0 The locations (coordinates) of the customers 432 | % k Number of drones 433 | % OUTPUT 434 | % solnOut This is the output solution 435 | 436 | % VARIABLES 437 | % D The design matrix 438 | % C The constraint matrix 439 | % S Scatter matrix 440 | % V Matrix with columns being the eigenvectors 441 | % D Matrix where diagonals are eigenvalues 442 | % g Anonymous function for distance from point to conic 443 | % h Anonymous function for distance between two points 444 | % aVectorScaled The parameters that create our ellipse 445 | % anSortedTruckCustomers Vector of truck customers sorted by distance 446 | % to ellipse 447 | % aafDistances Get the distances between all the points 448 | 449 | 450 | % Create matrix of distances 451 | aafDistances = calculateDistances(C0.x, C0.y); 452 | 453 | % Assign initial variables 454 | solnOut = solnIn; 455 | fSolnOut = f( aafDistances, solnIn, k); 456 | 457 | % Fit an ellipse to C0 458 | % First create our design matrix 459 | D = zeros(length(C0.x) - 1, 6); 460 | for iRow = 1 : length(C0.x) - 1 461 | D( iRow, 1 ) = (C0.x(iRow))^2; 462 | D( iRow, 2 ) = C0.x(iRow) * C0.y(iRow); 463 | D( iRow, 3 ) = (C0.y(iRow))^2; 464 | D( iRow, 4 ) = C0.x(iRow); 465 | D( iRow, 5 ) = C0.y(iRow); 466 | D( iRow, 6 ) = 1; 467 | end 468 | 469 | 470 | % Next our constraint matrix 471 | C = zeros(6); 472 | C(1, 3) = 2; 473 | C(3, 1) = 2; 474 | C(2, 2) = -1; 475 | 476 | % Create scatter matrix 477 | S = D' * D; 478 | 479 | % Find our eigenvalues and eigenvectors 480 | [ eigenvector, eigenvalue ] = eig( S, C ); 481 | 482 | % Find the smallest eigenvalue 483 | fMinDiag = inf; 484 | for iDiagonal = 1 : 6 485 | if eigenvalue(iDiagonal, iDiagonal) > 1e-6 && eigenvalue(iDiagonal, iDiagonal) < fMinDiag 486 | iMinDiag = iDiagonal; 487 | fMinDiag = eigenvalue(iDiagonal, iDiagonal); 488 | end 489 | end 490 | 491 | 492 | % Scale the parameters so that a'C*a = 1 493 | aVector = eigenvector( :, iMinDiag ); 494 | fScaling = aVector' * C * aVector; 495 | aVectorScaled = aVector / sqrt(fScaling); 496 | 497 | 498 | % This function gives us the algebraic distance of any point (x, y) 499 | % from the conic. 500 | g = @( a, x, y) (a(1)*x.^2 + a(2).*x.*y + a(3)*y.^2 + a(4)*x + a(5)*y + a(6)); 501 | 502 | 503 | % Create functions from the quadratic formulas 504 | fplus = @(a, x) ( -(a(2)*x + a(5)) + sqrt( (a(2)*x + a(5)).^2 - 4*a(3)*(a(1)*x.^2 + a(4)*x + a(6)) ) ) / (2*a(3)); 505 | fminus = @(a, x) ( -(a(2)*x + a(5)) - sqrt( (a(2)*x + a(5)).^2 - 4*a(3)*(a(1)*x.^2 + a(4)*x + a(6)) ) ) / (2*a(3)); 506 | 507 | 508 | % Create array of points 509 | afXData = [ min(C0.x) - 10 : 0.01 : max(C0.x) + 10 ]; 510 | % Check to see if the x and y are imaginary 511 | counter = 1; 512 | for x = afXData 513 | if isreal(fplus( aVectorScaled, x )) && fplus( aVectorScaled, x) ~= 0 514 | afXGoodData(counter) = x; 515 | afYDataPositive(counter) = fplus( aVectorScaled, x ); 516 | end 517 | 518 | if isreal(fminus( aVectorScaled, x )) && fminus( aVectorScaled, x) ~= 0 519 | afYDataNegative(counter) = fminus( aVectorScaled, x ); 520 | end 521 | 522 | if isreal(fminus( aVectorScaled, x )) && isreal(fplus( aVectorScaled, x )) 523 | counter = counter + 1; 524 | end 525 | 526 | 527 | end 528 | 529 | 530 | % Plot the points 531 | figure() 532 | hold on; 533 | plot(C0.x, C0.y, 'bo') 534 | plot(afXGoodData, afYDataPositive, 'r-') 535 | plot(afXGoodData, afYDataNegative, 'r-') 536 | 537 | % Create another plot to overlay 538 | figure(12345) 539 | hold on; 540 | plot(C0.x, C0.y, 'bo') 541 | plot(afXGoodData, afYDataPositive, 'r-') 542 | plot(afXGoodData, afYDataNegative, 'r-') 543 | 544 | 545 | % Given a customer point (xi, yi), find the point (x, y) on an ellipse 546 | % that minimizes the distance between (xi, yi) and (x, y) 547 | 548 | % Set the max number of iterations, the threshold 549 | % for convergence 550 | nMax = 30; 551 | fThreshold = 10^(-6); 552 | 553 | % Hard code the function and its jacobian 554 | h = @(x, y, lambda, xi, yi, a) ... 555 | [ 2*x - 2*xi + 2*a(1)*x*lambda + a(2)*lambda*y + a(4)*lambda; 556 | 2*y - 2*yi + a(2)*lambda*x + 2*a(3)*lambda*y + a(5)*lambda; 557 | a(1)*x^2 + a(2)*x*y + a(3)*y^2 + a(4)*x + a(5)*y + a(6) ]; 558 | 559 | h_jacobian = @(x, y, lambda, xi, yi, a) ... 560 | [ 2 + 2*a(1)*lambda, a(2)*lambda, 2*a(1)*x + a(2)*y + a(4); 561 | a(2)*lambda, 2 + 2*a(3)*lambda, a(2)*x + 2*a(3)*y + a(5); 562 | 2*a(1)*x + a(2)*y + a(4), a(2)*x + 2*a(3)*y + a(5), 0 ]; 563 | 564 | % Initialize ellipse points 565 | afEllipse.x = []; 566 | afEllipse.y = []; 567 | 568 | % Initialize vector of distances 569 | afDistanceFromEllipse = zeros(1, length(C0.x)-2); 570 | 571 | %Instead of iterating through the length of the arrays, iterate through 572 | for nCustomer = solnIn.anPart1(2 : end - 1) 573 | % Set initial guess 574 | % x y lambda 575 | afGuess = [ C0.x(nCustomer + 1); C0.y(nCustomer + 1); 0 ]; 576 | 577 | 578 | % Get the unknowns 579 | [ ae_vals, j, aUnknowns ] = ... 580 | newtons_method( nMax, afGuess, fThreshold, h, h_jacobian, aVectorScaled, C0, nCustomer+1); 581 | 582 | % Plot the point on the ellipse 583 | plot(aUnknowns(1, j-1), aUnknowns(2, j-1), 'ko') 584 | 585 | % Store the ellipse point 586 | afEllipse.x = aUnknowns(1, j-1); 587 | afEllipse.y = aUnknowns(2, j-1); 588 | 589 | % Calculate the distance between customers and closest pt on ellipse 590 | afDistanceFromEllipse( nCustomer ) = sqrt( (afEllipse.x(end) - C0.x(nCustomer+1))^2 + ... 591 | (afEllipse.y(end) - C0.y(nCustomer+1))^2 ); 592 | 593 | % Plot those thangs 594 | plot( [ C0.x(nCustomer+1); afEllipse.x ], [C0.y(nCustomer+1); afEllipse.y], 'b-' ) 595 | % [ C0.x(nCustomer); afEllipse.x ] 596 | % % for i = 1 : length(C0.x) 597 | % % plot( [ C0.x(nCustomer+1); afEllipse.x(nCustomer) ], [C0.y(nCustomer+1); afEllipse.y(nCustomer)], 'b-' ) 598 | % % end 599 | % % hold off; 600 | 601 | % Final Output Lines 602 | % fprintf("\n") 603 | % fprintf("Final Output Line for Part (a)\n") 604 | % fprintf("Current k | e^(k+1) | x^(k+1)\n" ); 605 | % fprintf(" %4d | %10.10f | [ %2.6f ; %2.6f; %2.6f ] \n", k-2, ae_vals(k-1), aUnknowns(1, k-1), aUnknowns(2, k-1), aUnknowns(3, k-1)) 606 | 607 | end 608 | 609 | hold off; 610 | 611 | 612 | % Sort the customer in descending order from distance to the ellipse 613 | [ afSortedDistances, anSortedTruckCustomers ] = sort( afDistanceFromEllipse, 2, "descend" ); 614 | 615 | afSortedDistances; 616 | 617 | 618 | % Get the distance matrix (the distances between all the points) 619 | aafDistances = calculateDistances(C0.x, C0.y); 620 | 621 | 622 | % Now we start looping until no feasible position is available for 623 | % customers in the list for re-insertion in UAV routes 624 | % feasible = 1; % 625 | % temp_counter = 0; % this temporary counter will eventually be removed, 626 | % it is taking place of the feasibility thing 627 | 628 | bDone = 0; 629 | 630 | 631 | 632 | % if for one iteration of the repeat loop, doesn't improve. 633 | while ~bDone 634 | % Initialize the "previous" best waiting time 635 | fTotalWaitingTimePrev = fSolnOut; 636 | 637 | for i = 1 : length(anSortedTruckCustomers) 638 | % Store the index of customer i in anSortedTruckCustomers 639 | jCust = anSortedTruckCustomers(i); % customer index at index i in anSortedTruckCustomers 640 | 641 | % Select customer c_j in the list & remove it from truck route 642 | solnRemovedCustomer = soln_remove_truck_customer(solnIn, jCust); 643 | 644 | % Check all potential positions in truck route 645 | for iTruckStop = 2 : length(solnIn.anPart1) - 1 646 | % Insert the customer in the truck route 647 | insertedTruckSoln = ... 648 | soln_insert_truck_customer(solnRemovedCustomer, jCust, iTruckStop); 649 | 650 | 651 | % Check feasibility 652 | % Totally checking feasibility, yep looks super great 653 | bFeasible = check_feasibility(insertedTruckSoln); 654 | 655 | % Calculate the total waiting time 656 | fTruckInsertionWaitingTime = f( aafDistances, insertedTruckSoln, k ); 657 | 658 | % Compare this s to our original s_out 659 | if bFeasible && (fTruckInsertionWaitingTime < fSolnOut) 660 | solnOut = insertedTruckSoln; 661 | fSolnOut = fTruckInsertionWaitingTime; 662 | end 663 | end 664 | 665 | % Intialize feasibility for inserting customer into drone 666 | % bFeasible = 1; 667 | 668 | indexindexindex = 1; 669 | % Check all potential positions in drone route 670 | for iDrone = 1 : k 671 | for iStopLeave = 1 : length(solnRemovedCustomer.anPart1) 672 | for iStopReturn = iStopLeave + 1 : length(solnRemovedCustomer.anPart1) 673 | % Insert the customer in the truck route 674 | insertedDroneSoln = ... 675 | soln_add_drone_customer(solnRemovedCustomer,... 676 | jCust, iDrone, iStopLeave, iStopReturn); 677 | 678 | % Check feasibility 679 | bFeasible = check_feasibility(insertedDroneSoln, C0, aafDistances); 680 | 681 | % Let's check what it looks like 682 | % hold on; 683 | % plot_route( C0, insertedDroneSoln, indexindexindex) 684 | % hold off; 685 | 686 | % Calculate the total waiting time 687 | % note: when calculating wait times, we are using 688 | % the CURRENT soln.anPart1 689 | fDroneInsertionWaitingTime = f( aafDistances, insertedDroneSoln, k); 690 | 691 | % Compare this to the solnOut 692 | if bFeasible && (fDroneInsertionWaitingTime < fSolnOut) 693 | solnOut = insertedDroneSoln; 694 | fSolnOut = fDroneInsertionWaitingTime; 695 | end 696 | 697 | indexindexindex = indexindexindex + 1; 698 | end 699 | end 700 | end 701 | end % here here here 702 | 703 | 704 | % Remove the customer from the list 705 | anSortedTruckCustomers(1) = []; 706 | 707 | 708 | % right here 709 | if fTotalWaitingTimePrev <= fSolnOut 710 | bDone = 1; 711 | end 712 | end 713 | 714 | end 715 | 716 | function[ ae_vals, k, aUnknowns ] = ... 717 | newtons_method( nMax, afGuess, fThreshold, h, h_jacobian, a, C0, iCustomer ) 718 | % Newton's method will approximate the roots of our function h. For this 719 | % particular problem, h is the gradient of our distance formula. 720 | % Input 721 | % nMax The maximum number of iterations 722 | % afGuess Our initial guess 723 | % fThreshold The threshold for convergence 724 | % h The function we are trying to find the root of 725 | % h_jacobian The jacobian of function h 726 | % aVectorScaled The paramters we found to create our ellipse 727 | % C0 Our set of customers 728 | % iCustomer The current customer we are at 729 | % Output 730 | % ae_vals Array of error values 731 | % k Number of iterations 732 | % aUnknowns Array of unknowns we are trying to find 733 | 734 | % Local Variables 735 | % k Number of iterations it took to converge 736 | % e The error value 737 | % xi x coordinate of the customers 738 | % yi y coordinate of the customers 739 | 740 | % Initialize k values and error value e 741 | k = 1; 742 | e = 99; % absurd error value 743 | 744 | % Create variables for the x and y coordinates of the customers 745 | xi = C0.x(iCustomer); 746 | yi = C0.y(iCustomer); 747 | 748 | % Initialize vector to hold the updating x, y, and lambda values and 749 | % vector for e values 750 | aUnknowns = zeros(3, nMax); 751 | ae_vals = zeros(1, nMax); 752 | aUnknowns(:, 1) = afGuess; 753 | ae_vals(1) = e; 754 | 755 | % Run Newton's Method for the vector case 756 | while k < nMax + 2 && ae_vals(k) > fThreshold 757 | aUnknowns(:, k+1) = aUnknowns(:, k) - ... 758 | h_jacobian(aUnknowns(1, k), aUnknowns(2, k), aUnknowns(3, k), xi, yi, a) \ ... 759 | h(aUnknowns(1, k), aUnknowns(2, k), aUnknowns(3, k), xi, yi, a ); 760 | 761 | % Calculate the error 762 | ae_vals(k+1) = norm( aUnknowns(:, k+1) - aUnknowns(:, k) ); 763 | 764 | % Print k, the error and the value 765 | % fprintf("Current k | e^(k+1) | x^(k+1)\n" ); 766 | % fprintf(" %4d | %10.10f | [ %2.6f ; %2.6f; %2.6f ] \n", k-1, ae_vals(k+1), aUnknowns(1, k+1), aUnknowns(2, k+1), aUnknowns(3, k+1)) 767 | 768 | % Update k 769 | k = k + 1; 770 | end 771 | end 772 | 773 | function[ solnOut ] = soln_remove_truck_customer(solnIn, jCust) 774 | % The soln_remove_truck_Customer function takes in a solution structure and 775 | % the customer j that we will be removing from the truck route. It will 776 | % first loop over soln.anPart1 to find jCust. Then it will "remove him" by 777 | % esentially shifting everyone in solnIn.anPart1 after him down by 1. 778 | % Input 779 | % solnIn The solution we are currently working with 780 | % jCust The customer we will be removing from part 1 of solnIn 781 | % Output 782 | % solnOut The resulting solution 783 | 784 | % Local variables 785 | % iCustomer counter variable to keep track of the customer index 786 | % 787 | 788 | % Initialize jCustIndex to end + 1 789 | jCustIndex = length(solnIn.anPart1) + 1; 790 | 791 | % Loop over soln.anPart1 to find jCust 792 | for iCustomer = 1 : length(solnIn.anPart1) 793 | if solnIn.anPart1(iCustomer) == jCust 794 | jCustIndex = iCustomer; 795 | end 796 | end 797 | 798 | % Remove jCust and shift everyone down 799 | solnIn.anPart1(jCustIndex) = []; 800 | solnIn.anPart1; 801 | 802 | % Shift things in part 3 and part 4 that are after the removal slot 803 | % down by 1 804 | nDroneCounter = 1; 805 | for nIndex = 1 : length(solnIn.anPart3) 806 | if solnIn.anPart3(nIndex) < 0 807 | nDroneCounter = nDroneCounter + 1; 808 | 809 | else 810 | if solnIn.anPart3(nIndex) > jCustIndex % "if it comes after" 811 | solnIn.anPart3(nIndex) = solnIn.anPart3(nIndex) - 1; 812 | end 813 | 814 | if solnIn.anPart4(nIndex) > jCustIndex 815 | solnIn.anPart4(nIndex) = solnIn.anPart4(nIndex) - 1; 816 | end 817 | end 818 | end 819 | 820 | % Return the solnOut 821 | solnOut = solnIn; 822 | 823 | end 824 | 825 | function[ solnOut ] = soln_insert_truck_customer(solnIn, jCust, iStop) 826 | % The soln_insert_truck_customer method will take in the solution, the 827 | % jCustomer we will be inserting, and the place where the truck will be 828 | % inserted. 829 | % Inputer 830 | % solnIn The input solution 831 | % jCust the customer that will be inserted 832 | % iStop Where the truck will be placed 833 | 834 | % Local Variables 835 | % 836 | 837 | % Initialize the out solution 838 | solnOut = solnIn; 839 | 840 | % First insert values 1 to iStop - 1 841 | solnOut.anPart1 = solnIn.anPart1(1 : iStop - 1); 842 | 843 | % Next insert jCust 844 | solnOut.anPart1(iStop) = jCust; 845 | 846 | % Next insert the remaining values iStop to the end of the array 847 | solnOut.anPart1 = [ solnOut.anPart1 solnIn.anPart1(iStop : end)]; 848 | 849 | % Update anPart3 and anPart4 850 | nDroneCounter = 1; 851 | for nIndex = 1 : length(solnIn.anPart3) 852 | if solnIn.anPart3(nIndex) < 0 853 | nDroneCounter = nDroneCounter + 1; 854 | else 855 | if solnIn.anPart3(nIndex) >= iStop 856 | solnOut.anPart3(nIndex) = solnIn.anPart3(nIndex) + 1; 857 | end 858 | 859 | if solnIn.anPart4(nIndex) >= iStop 860 | solnOut.anPart4(nIndex) = solnIn.anPart4(nIndex) + 1; 861 | end 862 | end 863 | end 864 | end 865 | 866 | function[ solnOut ] = ... 867 | soln_add_drone_customer(solnIn, jCust, iDrone, iStopLeave, iStopReturn) 868 | % soln_add_drone_customer will take in the current solution data structure, 869 | % the customer we will be including, the drone that will be delivering to 870 | % it as well as where the drone will be departing from and arriving to. 871 | % Input 872 | % solnIn Current solution 873 | % jCust Customer we are inserting 874 | % iDrone The drone that will be delivering to that customer 875 | % iStopLeave The index from which the drone will be leaving (based off 876 | % of the indices from soln.anPart1) 877 | % iStopReturn The index where the drone will be returning (also based off 878 | % of the indices from soln.anPart1) 879 | 880 | % Output 881 | % solnOut The resutling solution. 882 | 883 | % Local variable 884 | % iPart3Drone The drone we are currently looking at in part 3 885 | 886 | 887 | % Find the drone we are looking for in solnIn.anPart3 888 | iPart3Drone = 1; 889 | nDroneCounter = 1; 890 | 891 | % Initialize the solnOut 892 | solnOut = solnIn; 893 | 894 | while (nDroneCounter ~= iDrone) 895 | % If we hit a separator (-1) increment the drone 896 | if (solnIn.anPart3(iPart3Drone) == -1) 897 | nDroneCounter = nDroneCounter + 1; 898 | end 899 | iPart3Drone = iPart3Drone + 1; 900 | end 901 | 902 | 903 | % Look for where iStopLeave fits in solnIn.anPart3 (start from 904 | % iPart3Drone) 905 | bPlaced = 0; 906 | iLeavePlacement = iPart3Drone; 907 | while (~bPlaced) 908 | % In the case that we want to assign drone n a customer, but there 909 | % are currently no drone n customers 910 | if iLeavePlacement > length(solnIn.anPart3) 911 | % Insert placeholder slot 912 | solnOut.anPart3(end + 1) = 0; 913 | solnOut.anPart2(end + 1) = 0; 914 | solnOut.anPart4(end + 1) = 0; 915 | end 916 | 917 | if (solnOut.anPart3(iLeavePlacement) < iStopLeave) 918 | % Then we place it in index iLeavePlacement 919 | bPlaced = 1; 920 | 921 | else 922 | % We iterate through 923 | iLeavePlacement = iLeavePlacement + 1; 924 | 925 | end 926 | end 927 | 928 | 929 | % Shift oclumns in Part 3 up by 1 930 | % Shift the columns at & abova where it goes to the right by 1 931 | solnOut.anPart3 = solnIn.anPart3(1 : iLeavePlacement - 1); 932 | 933 | % Insert the index it will be leaving from 934 | solnOut.anPart3(iLeavePlacement) = iStopLeave; 935 | 936 | % Insert the remaining values iStopLeave to the end of the array 937 | solnOut.anPart3 = [ solnOut.anPart3 solnIn.anPart3(iLeavePlacement : end) ]; 938 | 939 | % ---- 940 | % Shift columns in Part 2 up by 1 941 | % Shift the columns at & abova where it goes to the right by 1 942 | solnOut.anPart2 = solnIn.anPart2(1 : iLeavePlacement - 1); 943 | 944 | % Insert the customer jCust 945 | solnOut.anPart2(iLeavePlacement) = jCust; 946 | 947 | % Insert the remaining values iStopLeave to the end of the array 948 | solnOut.anPart2 = [ solnOut.anPart2 solnIn.anPart2(iLeavePlacement : end) ]; 949 | 950 | 951 | % ---- 952 | % Shift the columns in Part 4 up by 1 953 | % Shift the columns at & above where it goes to the right by 1 954 | solnOut.anPart4 = solnIn.anPart4(1 : iLeavePlacement - 1); 955 | 956 | % Insert the customer return index 957 | solnOut.anPart4( iLeavePlacement ) = iStopReturn; 958 | 959 | % Insert the remaining values iStopLeave to the end of the array 960 | solnOut.anPart4 = [ solnOut.anPart4 solnIn.anPart4(iLeavePlacement : end) ]; 961 | end 962 | 963 | function[ solnOut ] = soln_remove_drone_customer(solnIn, iCustomerToRemove) 964 | % This function removes the specified drone customer from the solution 965 | % (parts 2, 3, 4) 966 | solnOut.anPart1 = solnIn.anPart1; 967 | 968 | iIndexNew = 0; 969 | for iIndexOrig = 1 : length( solnIn.anPart2 ) 970 | if solnIn.anPart2( iIndexOrig ) ~= iCustomerToRemove 971 | iIndexNew = iIndexNew + 1; 972 | solnOut.anPart2( iIndexNew ) = solnIn.anPart2( iIndexOrig ); 973 | solnOut.anPart3( iIndexNew ) = solnIn.anPart3( iIndexOrig ); 974 | solnOut.anPart4( iIndexNew ) = solnIn.anPart4( iIndexOrig ); 975 | else 976 | iRemovalIndex = iIndexOrig; 977 | end 978 | end 979 | 980 | end 981 | 982 | function[ sPrime ] = neighbor( soln ) 983 | % The neighbor function will implement the well-known 2-opt method for 984 | % creating neighbors for our current solution s. Here, we must note that 985 | % our 2-opt method will switch two customers within the vector of Parts 1 986 | % and 2. The customers are selected randomly. Our 2-opt is a local search 987 | % heuristic. 988 | 989 | % INPUT 990 | % soln Current solution data structure. 991 | % OUTPUT 992 | % sPrime A neighbor to input solution soln 993 | 994 | % Variables 995 | % sPrime The soln.anPart1 which is the neighbor to our original 996 | % soln.anPart1 997 | % randIndex1 The index of the customer that will be switched out from 998 | % part 1 of the soln data structure 999 | % randIndex2 The index of the customer that will be switched out from 1000 | % part 2 of the soln data structure. 1001 | % nTemp A temporary variable that holds the customer at index 1002 | % randIndex1 1003 | 1004 | 1005 | % Randomly pick two customer indices from anPart1 1006 | randIndex1 = -1; 1007 | randIndex2 = -1; 1008 | 1009 | % Make sure that we don't pick the same two indices 1010 | while randIndex1 == randIndex2 1011 | randIndex1 = randi([2, length(soln.anPart1)-1]); 1012 | randIndex2 = randi([2, length(soln.anPart1)-1]); 1013 | end 1014 | 1015 | % fprintf("Before switching: \n") 1016 | % soln.anPart1 1017 | 1018 | % Switch their positions in soln.anPart1 1019 | nTemp = soln.anPart1(randIndex1); 1020 | soln.anPart1(randIndex1) = soln.anPart1(randIndex2); 1021 | soln.anPart1(randIndex2) = nTemp; 1022 | 1023 | % % fprintf("after switching: \n") 1024 | % soln.anPart1 1025 | 1026 | % Return the new neighbor to the input solution 1027 | sPrime = soln.anPart1; 1028 | 1029 | end 1030 | 1031 | function[ bValid ] = check_flight_validity( aafDistances, nLeaving, nVisiting, nReturning ) 1032 | % Check flight validity will take a matrix of distances, as well as the 1033 | % leaving, visiting, and returning nodes of a drone flight path. It will 1034 | % then determine if the flight distance of the drone falls within 1035 | % parameters. 1036 | 1037 | % Input 1038 | % aafDistances Matrix of distances between customer nodes 1039 | % nLeaving The node from which the drone will be leaving 1040 | % nVisiting The customer node which the drone visits 1041 | % nReturning The cusotmer that the drone returns to 1042 | % Output 1043 | % bValid Boolean variable that determines whether the flight 1044 | % path is valid 1045 | 1046 | % Variables 1047 | % maxDistance The maximum flight distance a drone can fly 1048 | % alpha The factor by which the flight distance is divided to 1049 | % account for drone speed 1050 | % a Distance from departure to the customer 1051 | % b Distnace from the customer to the returning depot 1052 | % fDrone Distance The total "distance" flown by the drone 1053 | 1054 | 1055 | % Initialize max flight distance, alpha, and boolean valid variable 1056 | maxDistance = 10; 1057 | alpha = 1.5; 1058 | bValid = 1; 1059 | 1060 | % Calculate the distance from departure to the customer 1061 | a = aafDistances(nLeaving+1, nVisiting+1); 1062 | 1063 | % Calculate the distance from the customer to the returning 1064 | b = aafDistances(nVisiting+1, nReturning+1); 1065 | 1066 | % Total drone distance 1067 | fDroneDistance = (a+b)/alpha; 1068 | 1069 | if fDroneDistance > maxDistance 1070 | bValid = 0; 1071 | end 1072 | end 1073 | 1074 | function[ fTotalWaitTime ] = f( aafDistances, soln, k) 1075 | % This function f is our wait time function. In this case, we treat 1076 | % the time = distance for our trucks and we calculate the time for the 1077 | % drones using a speed factor of 1.5 1078 | 1079 | % INPUT 1080 | % aafDistances Array of floats containing the distances between custs. 1081 | % soln Data structure of solutions in parts 1, 2, 3 and 4 1082 | % k Number of drones 1083 | % OUTPUT 1084 | % fTotalWaitTime Float of total wait time 1085 | 1086 | 1087 | % Variables 1088 | % alpha Our speed factor for the drones. 1089 | % aSoln Current solution 1090 | 1091 | 1092 | % Initialize constants 1093 | alpha = 1.5; 1094 | 1095 | 1096 | % Create temporary list of customers 1097 | anCustomers = soln.anPart1; 1098 | 1099 | % Create vector arrival and departure times for drones and trucks 1100 | aafDroneArrivalTime = zeros(k, length(aafDistances)); 1101 | aafTruckArrivalTime = zeros(1, length(aafDistances)); 1102 | aafTruckDepartureTime = zeros(1, length(aafDistances)); 1103 | 1104 | % Calculate the total wait time for the truck customers 1105 | iPrevious = 1; 1106 | for iCustomerIndex = 2 : length(soln.anPart1) % Here iCustomerIndex = 1 & = length(soln.anPart1) are the nodes 1107 | iDrone = 1; % Initialize drone counter 1108 | 1109 | % Calculate the arrival time of the truck to this node 1110 | % aafTruckArrivalTime(anCustomers(iCustomerIndex) + 1) = ... 1111 | % aafDistances( anCustomers(iCustomerIndex - 1) + 1, anCustomers(iCustomerIndex) + 1 ) ... 1112 | % + aafTruckArrivalTime( anCustomers(iCustomerIndex - 1) + 1); 1113 | aafTruckArrivalTime(anCustomers(iCustomerIndex) + 1) = ... 1114 | aafDistances( anCustomers(iCustomerIndex - 1) + 1, anCustomers(iCustomerIndex) + 1 ) ... 1115 | + aafTruckDepartureTime( anCustomers(iCustomerIndex - 1) + 1); 1116 | 1117 | % "Put this arrival times in aafTruckDepartureTimes and vector and 1118 | % then update them if they have a drone approaching that same 1119 | % node. So that if there is no drone going towards customer at 1120 | % iCustomerIndex, then they already have something in their truck 1121 | % arrival time vector. That is, we are assuming that no customer 1122 | % has a drone delivering to them. 1123 | % NOTE: ASSUME INSTANTANEOUS ARRIVAL AND DEPARTURE 1124 | aafTruckDepartureTime(anCustomers(iCustomerIndex) + 1) = ... 1125 | aafTruckArrivalTime(anCustomers(iCustomerIndex) + 1); 1126 | 1127 | 1128 | 1129 | for iReconveneIndex = 1 : length(soln.anPart4) 1130 | 1131 | % Check if we have an "X" there 1132 | if soln.anPart4(iReconveneIndex) == -1 1133 | iDrone = iDrone + 1; 1134 | end 1135 | 1136 | % Get the arrival time of the drones 1137 | if iCustomerIndex == soln.anPart4(iReconveneIndex) % This means there is a drone flying to this point 1138 | 1139 | % calculate distance from departure node to customer node 1140 | a = aafDistances( anCustomers(soln.anPart3(iReconveneIndex)) + 1, soln.anPart2(iReconveneIndex) + 1); 1141 | 1142 | % Calculate distance from customer node to arrival node 1143 | b = aafDistances( soln.anPart2(iReconveneIndex) + 1, anCustomers(soln.anPart4(iReconveneIndex)) + 1); 1144 | % b = aafDistances( soln.anPart2(iReconveneIndex) + 1, anCustomers(soln.anPart4(iReconveneIndex) + 1) ); 1145 | 1146 | 1147 | aafDroneArrivalTime(iDrone, anCustomers(soln.anPart4(iReconveneIndex)) + 1) = (a + b)/alpha; 1148 | 1149 | %%%% WORKS TILL HERE %%%% 1150 | 1151 | % if Truck is there before the drone, it must wait 1152 | if aafTruckArrivalTime(anCustomers(iCustomerIndex) + 1) < ... 1153 | max (aafDroneArrivalTime( :, anCustomers(iCustomerIndex) + 1 ) ) ... 1154 | + aafTruckDepartureTime(anCustomers(iPrevious) + 1) % added this addition bit 1155 | 1156 | aafTruckDepartureTime(anCustomers(iCustomerIndex) + 1) = ... 1157 | max (aafDroneArrivalTime( :, anCustomers(iCustomerIndex) + 1) ) + ... 1158 | aafTruckDepartureTime(anCustomers(iPrevious) + 1); 1159 | % do we need to add everything up to this point to the 1160 | % distance traveled by the drone?? 1161 | elseif aafTruckArrivalTime(anCustomers(iCustomerIndex) + 1) > max( aafDroneArrivalTime( :, iCustomerIndex) ) 1162 | % If the drone is there before the truck 1163 | aafTruckDepartureTime(anCustomers(iCustomerIndex) + 1) = aafTruckArrivalTime( anCustomers(iCustomerIndex) + 1); 1164 | end 1165 | 1166 | end 1167 | 1168 | end 1169 | iPrevious = iPrevious + 1; 1170 | end 1171 | 1172 | 1173 | % Get the total waiting time 1174 | fTotalWaitTime = aafTruckDepartureTime( soln.anPart1(end - 1) + 1 ); 1175 | 1176 | 1177 | % %% NOTES 1178 | % % WE MIGHT HAVE TO CREATE IF STATEMENT FOR WHAT TO RETURN. For example, 1179 | % % if the last customer is visited by a drone and arrives there before 1180 | % % the truck, we are not interested in waiting for the truck to get 1181 | % % there (since our drone would have already delivered to the customer). 1182 | % % In this case we don't want to return the aafTruckDepartureTime. 1183 | % 1184 | % % Potential Fix: in the case that we visit a customer and the drone 1185 | % % gets there faster, IF there is a customer to visit afterwards, THEN 1186 | % % we choose the higher time. ELSE (last customer), we take the minimum. 1187 | % % Look at the picture titled "ECA_fig1" 1188 | 1189 | end 1190 | 1191 | function[ bFeasible ] = check_feasibility( solnIn, C0, aafDistances ) 1192 | % This function will check the feasibility of the solution that is passed 1193 | % in. It will check that several requirements are satisfied: 1194 | % FOR DRONES: 1195 | % - Travel distance for each drone <= max 1196 | % - Leaving stop of drone (part 3) < returning stop of drone (part 4)** 1197 | % - No overlapping drone trips (we don't give a drone in the air a delivery) 1198 | % - for each slot in part 4, make sure entry in slot+1 of part 3 is 1199 | % >= the part 4 value 1200 | % GENERAL: 1201 | % - Make sure each customer is delivered to 1202 | % - Battery pack availability/battery life for consecutive trips 1203 | % - No out and back trips (covered by **) (part3 == part4) 1204 | % TRUCK: 1205 | % - truck must start and end at the depot 1206 | % Input 1207 | % solnIn Input solution 1208 | % C0 The customer locations 1209 | % aafDistances The distances between customer nodes 1210 | % Output 1211 | % bFeasible Boolean value that is true (1) if the solution is feasible; 1212 | % false (0) otherwise 1213 | 1214 | % Local variables 1215 | % maxDistance The max distance a drone can fly 1216 | % alpha Drone flight multiplier constant 1217 | % fDroneDistance The distance flown by a specific drone 1218 | 1219 | % Initialize drone flight multiplier constant 1220 | alpha = 1.5; 1221 | 1222 | % Initialize maxDistance variable 1223 | maxDistance = 10; 1224 | 1225 | % Initialize bFeasible to true 1226 | bFeasible = 1; 1227 | 1228 | % Create variable for all of the customers 1229 | anCustomers = solnIn.anPart1; 1230 | 1231 | % Make sure that drone isn't traveling back and forth 1232 | iCustIndex = 1; 1233 | while iCustIndex <= length(solnIn.anPart3) && bFeasible 1234 | if (solnIn.anPart3(iCustIndex) == solnIn.anPart4(iCustIndex) && solnIn.anPart3(iCustIndex) ~= -1) 1235 | bFeasible = 0; 1236 | end 1237 | 1238 | if (solnIn.anPart3(iCustIndex) == 1 && (solnIn.anPart4(iCustIndex) == length(solnIn.anPart1))) 1239 | bFeasible = 0; 1240 | end 1241 | iCustIndex = iCustIndex + 1; 1242 | end 1243 | 1244 | % Make sure that the travel distance for each drone is not too far 1245 | i = 1; 1246 | while i <= length(solnIn.anPart3) && bFeasible 1247 | fDroneDistance = 0; 1248 | if (solnIn.anPart3(i) ~= -1) 1249 | % Here, we will do the drone flight distance calculation the 1250 | % way we did it in the f() function. 1251 | 1252 | % Calculate the distance from departure to the customer 1253 | a = aafDistances( anCustomers(solnIn.anPart3(i)) + 1, solnIn.anPart2(i) + 1); 1254 | 1255 | % Calculate the distance from the customer to the arrival 1256 | b = aafDistances( solnIn.anPart2(i) + 1, anCustomers(solnIn.anPart4(i)) + 1); 1257 | 1258 | % Total Drone distance 1259 | fDroneDistance = (a+b)/alpha; 1260 | end 1261 | 1262 | if fDroneDistance > maxDistance 1263 | bFeasible = 0; 1264 | end 1265 | i = i+1; 1266 | end 1267 | end 1268 | 1269 | % Heuristics 1270 | function[ WeightInfo ] = weight_init() 1271 | % The weight_init() function will initialize the weights of all the 1272 | % heuristics available to us and return a structure that keeps track of 1273 | % scores, times, weights, gammas, and segments. 1274 | 1275 | % Input 1276 | % 1277 | 1278 | % Output 1279 | % WeightInfo The variable WeightInfo is a data structure with 1280 | % the following attributes: 1281 | % 1282 | % WeightInfo.afScores : pi vector which represents the heuristic scores 1283 | % WeightInfo.anTimes : theta vector which represents the number of 1284 | % times each heuristic was used 1285 | % WeightInfo.aafWeights: w_q,l array which represents the weight of 1286 | % heuristic q (col) used in segment l (row) 1287 | % WeightInfo.fGamma : a coefficient between 0 and 1 used to balance 1288 | % between the value of earlier weights and the 1289 | % new normalized scores 1290 | % .nSegmentCounter: current l 1291 | 1292 | % Variables 1293 | % numHeuristics The current number of heuristics implemented in 1294 | % the code; 1295 | 1296 | % Number of heuristics 1297 | numHeuristics = 4; 1298 | 1299 | % Initialize WeightInfo 1300 | WeightInfo.afScores = zeros(1, numHeuristics); % 2 heuristics 1301 | WeightInfo.anTimes = zeros(1, numHeuristics); 1302 | WeightInfo.aafWeights(1, :) = (1/numHeuristics) * ones(1, numHeuristics); 1303 | WeightInfo.fGamma = 0.2; 1304 | WeightInfo.nSegmentCounter = 1; 1305 | 1306 | end 1307 | 1308 | function[ WeightInfo ] = update_weights( WeightInfo, nHeuristic, f_sPrime, f_sBest, f_sCurr, iIter) 1309 | % In this function we update one slot of afScores and anTimes every time 1310 | % this function is called. But we only update aafWeights every k iterations 1311 | % (50, in this case) 1312 | 1313 | % Input 1314 | % WeightInfo The variable WeightInfo is a data structure with 1315 | % the following attributes: 1316 | % 1317 | % WeightInfo.afScores : pi vector which represents the heuristic scores 1318 | % WeightInfo.anTimes : theta vector which represents the number of 1319 | % times each heuristic was used 1320 | % WeightInfo.aafWeights: w_q,l array which represents the weight of 1321 | % heuristic q (col) used in segment l (row) 1322 | % WeightInfo.fGamma : a coefficient between 0 and 1 used to balance 1323 | % between the value of earlier weights and the 1324 | % new normalized scores 1325 | % .nSegmentCounter: current l; keeps track of the number of 1326 | % iterations 1327 | % Output 1328 | % 1329 | 1330 | % Local Variables 1331 | % k The segment length 1332 | 1333 | % Initialize the segment length 1334 | k = 50; 1335 | 1336 | % Get the number of segments 1337 | nDim = size(WeightInfo.aafWeights); 1338 | l = nDim(2); 1339 | 1340 | % Update the afScores of the nHeuristic 1341 | if f_sPrime < f_sBest 1342 | WeightInfo.afScores(nHeuristic) = WeightInfo.afScores(nHeuristic) + 2; 1343 | elseif f_sPrime <= f_sCurr 1344 | WeightInfo.afScores(nHeuristic) = WeightInfo.afScores(nHeuristic) + 1; 1345 | end 1346 | 1347 | % Update the anTimes 1348 | WeightInfo.anTimes(nHeuristic) = WeightInfo.anTimes(nHeuristic) + 1; 1349 | 1350 | % Check to see if the nSegmentCounter is a multiple of k = 50 1351 | % if mod(WeightInfo.nSegmentCounter, 50) == 0 1352 | % % Update the weights of the heurisitic 1353 | % WeightInfo.aafWeights(:, l+1) = ... 1354 | % WeightInfo.aafWeights(:, l)*(1 - WeightInfo.fGamma) + ... 1355 | % WeightInfo.fGamma*(WeightInfo.afScores / WeightInfo.anTimes); 1356 | % end 1357 | if mod(iIter, 50) == 0 1358 | % Update the weights of the heurisitic 1359 | WeightInfo.aafWeights(l+1, :) = ... 1360 | WeightInfo.aafWeights(l, :)*(1 - WeightInfo.fGamma) + ... 1361 | WeightInfo.fGamma*(WeightInfo.afScores / WeightInfo.anTimes); 1362 | 1363 | % Update the segment counter 1364 | WeightInfo.nSegmentCounter = WeightInfo.nSegmentCounter + 1; 1365 | end 1366 | end 1367 | 1368 | function[ nHeuristic ] = select_heuristic( WeightInfo ) 1369 | % The select_heuristic function will take in a WeightInfo variable with a 1370 | % structure with the attributes: scores, times, weights, gamma, and segment 1371 | % counter. It will calculate the probabilities of the heuristic being 1372 | % selected using the WeightInfo.aafWeights attribute. 1373 | 1374 | % Input 1375 | % WeightInfo Structure with attributes afScores, anTimes, 1376 | % aafWeights, fGamma, and nSegmentCounter 1377 | 1378 | % Output 1379 | % nHeuristic Returns the index associated with the chosen heuristic. 1380 | % For example, 1 = 2-Opt Heuristic, 2 = 3-opt Heuristic, 1381 | % 3 = Greedy Assignment Heuristic, etc 1382 | 1383 | % Variables 1384 | % afProbabilities Vector of probabilities of each heuristic being selected 1385 | % fHeuristicWeightSum The sum of the weights from 1386 | % WeightInfo.nSegmentCounter 1387 | % anSize The dimensions of the aafWeights attribute 1388 | 1389 | % Calculate the size 1390 | anSize = size(WeightInfo.aafWeights); 1391 | 1392 | % Calculate vector of probabilities 1393 | fHeuristicWeightSum = 0; 1394 | for i = 1 : anSize(2) 1395 | fHeuristicWeightSum = fHeuristicWeightSum + ... 1396 | WeightInfo.aafWeights(WeightInfo.nSegmentCounter, i); 1397 | end 1398 | 1399 | afProbabilities = ... 1400 | WeightInfo.aafWeights(WeightInfo.nSegmentCounter, :) / fHeuristicWeightSum; 1401 | 1402 | % Split interval 1403 | afBoundaries = [0]; 1404 | 1405 | for i = 1 : length(afProbabilities) 1406 | afBoundaries(i+1) = afBoundaries(i) + afProbabilities(i); 1407 | end 1408 | 1409 | % Randomly select one 1410 | fRand = rand(); 1411 | 1412 | % Map the rand # to nHeuristic 1413 | for i = 1 : length(afBoundaries) 1414 | if fRand >= afBoundaries(i) 1415 | nHeuristic = i; 1416 | end 1417 | end 1418 | end 1419 | 1420 | % Heuristic 1 1421 | function[ solnNew ] = apply_heuristic_2_opt( solnCurr, C0, aafDistances ) 1422 | % This function will implement the 2-opt heuristic which will swap two 1423 | % customers within the vector of Parts 1 and 2. The customers are selected 1424 | % randomly for the 2-Opt method. Chainging the values in Parts 1 and 2 1425 | % while keeping the values in Parts 3 and 4 unchanged may lead to an 1426 | % infeasible solution due to the flight range constraint. In the case of 1427 | % infeasibility, we use the DRONE PLANNER HEURISTIC. 1428 | 1429 | % Input 1430 | % solnCurr The current solution 1431 | % C0 The locations (coordinates) of the customers 1432 | % aafDistances The distances between customer node 1433 | 1434 | % Output 1435 | % solnBest The best solution our algorithm was able to determine 1436 | 1437 | % Variables 1438 | % nCustA Randomly selected customer integer 1439 | % nCustB Randomly selected customer integer 1440 | % nCustomers Number of customers 1441 | % nIndA1 Index of customer A if in part 1 (-1 if not in part 1) 1442 | % nIndB1 Index of customer B if in part 1 (-1 if not in part 1) 1443 | % nIndA2 Index of customer A if in part 2 (-1 if not in part 1) 1444 | % nIndB2 Index of customer B if in part 2 (-1 if not in part 1) 1445 | 1446 | % Get the number of customers 1447 | nCustomers = length(solnCurr.anPart1) - 2; 1448 | 1449 | for i = 1 : length(solnCurr.anPart2) 1450 | if solnCurr.anPart2(i) ~= -1 1451 | nCustomers = nCustomers + 1; 1452 | end 1453 | end 1454 | 1455 | % Get customers to be swapped 1456 | nCustA = randi([1, nCustomers]); % Ignore the 2 zeros 1457 | nCustB = randi([1, nCustomers]); 1458 | 1459 | while nCustB == nCustA 1460 | nCustB = randi([1, nCustomers]); 1461 | end 1462 | 1463 | % Find which slot in either part1 or part2 each of A & B are at 1464 | % Part 1 1465 | nIndA1 = -1; 1466 | nIndB1 = -1; 1467 | for nIndex = 1 : length(solnCurr.anPart1) 1468 | if nCustA == solnCurr.anPart1(nIndex) 1469 | nIndA1 = nIndex; 1470 | end 1471 | 1472 | if nCustB == solnCurr.anPart1(nIndex) 1473 | nIndB1 = nIndex; 1474 | end 1475 | end 1476 | 1477 | % Part 2 1478 | nIndA2 = -1; 1479 | nIndB2 = -1; 1480 | for nIndex = 1 : length(solnCurr.anPart2) 1481 | if nCustA == solnCurr.anPart2(nIndex) 1482 | nIndA2 = nIndex; 1483 | end 1484 | 1485 | if nCustB == solnCurr.anPart2(nIndex) 1486 | nIndB2 = nIndex; 1487 | end 1488 | end 1489 | 1490 | % Create solnNew and swap the two variables 1491 | solnNew = solnCurr; 1492 | 1493 | % customer a in part 1 and customer b in part 2 1494 | if nIndA1 ~= -1 && nIndB2 ~= -1 1495 | tempCust = solnNew.anPart1(nIndA1); 1496 | solnNew.anPart1(nIndA1) = solnNew.anPart2(nIndB2); 1497 | solnNew.anPart2(nIndB2) = tempCust; 1498 | 1499 | % customer a in part 2 and customer b in part 1 1500 | elseif nIndA2 ~= -1 && nIndB1 ~= -1 1501 | tempCust = solnNew.anPart2(nIndA2); 1502 | solnNew.anPart2(nIndA2) = solnNew.anPart1(nIndB1); 1503 | solnNew.anPart1(nIndB1) = tempCust; 1504 | 1505 | % customer a and b are in part 1 1506 | elseif nIndA1 ~= -1 && nIndB1 ~= -1 1507 | tempCust = solnNew.anPart1(nIndA1); 1508 | solnNew.anPart1(nIndA1) = solnNew.anPart1(nIndB1); 1509 | solnNew.anPart1(nIndB1) = tempCust; 1510 | 1511 | % customer a and b are in part 2 1512 | elseif nIndA2 ~= -1 && nIndB2 ~= -1 1513 | tempCust = solnNew.anPart2(nIndA2); 1514 | solnNew.anPart2(nIndA2) = solnNew.anPart2(nIndB2); 1515 | solnNew.anPart2(nIndB2) = tempCust; 1516 | end 1517 | 1518 | 1519 | % Check the feasibility 1520 | bFeasible = check_feasibility(solnNew, C0, aafDistances); 1521 | if bFeasible == 0 1522 | solnNew = apply_heuristic_7_drone_planner(solnNew, C0, aafDistances); 1523 | 1524 | if check_feasibility(solnNew, C0, aafDistances) == 0 1525 | solnNew = solnCurr; 1526 | end 1527 | end 1528 | end 1529 | 1530 | % Heuristic 2 1531 | function[ solnNew ] = apply_heuristic_7_drone_planner(solnIn, C0, aafDistances) 1532 | % This function will apply the drone planner heuristic. 1533 | 1534 | % Input 1535 | % solnIn The input solution structure 1536 | % C0 The locations (coordinates) of the customers 1537 | % aafDistances Matrix of all distances between nodes 1538 | % Output 1539 | % solnNew The new solution created from the drone planner 1540 | % heuristic 1541 | 1542 | % Variables 1543 | % P_j Nested structure that contains every (i, s) combination 1544 | % where i, s in V (set of all nodes in the network) && 1545 | % the flight from i to j to s is in range (< L) && i and 1546 | % s are truck customers && i is served before s 1547 | % nDrones Number of drones 1548 | 1549 | solnNew = solnIn; 1550 | % Count the number of drones 1551 | if isempty(solnIn.anPart2) 1552 | nDrones = 0; 1553 | else 1554 | nDrones = 1; 1555 | i = 1; 1556 | while i < length(solnIn.anPart2) 1557 | if solnIn.anPart2(i) == -1 1558 | nDrones = nDrones + 1; 1559 | end 1560 | i = i + 1; 1561 | end 1562 | end 1563 | 1564 | % Remove all UAV flights from S_out 1565 | iCustomer = 1; 1566 | iDrone = 1; 1567 | while iCustomer <= length(solnNew.anPart2) && iDrone <= nDrones 1568 | if solnNew.anPart2(iCustomer) == -1 1569 | iDrone = iDrone + 1; 1570 | else 1571 | solnNew.anPart3(iCustomer) = 0; 1572 | solnNew.anPart4(iCustomer) = 0; 1573 | end 1574 | iCustomer = iCustomer + 1; 1575 | end 1576 | 1577 | 1578 | % Create the P_j structure 1579 | n = length(solnIn.anPart1) - 1; 1580 | 1581 | iDroneCustomer = 1; 1582 | for iDrone = 1 : nDrones 1583 | while iDroneCustomer < length(solnIn.anPart2) && solnIn.anPart2(iDroneCustomer) ~= -1 1584 | iRow = 1; 1585 | % P_j(iDrone).Customer(solnIn.anPart2(iDroneCustomer)).aanCust = zeros(n*(n+1)/2, 2); % the number of possible permutations 1586 | for iLeaving = 1 : length(solnIn.anPart1) - 1 % subtract 1 because it drone can't leave from last spot 1587 | for sReturning = iLeaving + 1 : length(solnIn.anPart1) 1588 | % Only add solution if it is feasible 1589 | if check_flight_validity(aafDistances, solnIn.anPart1(iLeaving), solnIn.anPart2(iDroneCustomer), solnIn.anPart1(sReturning)) 1590 | % P_j(iDrone).Customer(solnIn.anPart2(iDroneCustomer)).aanCust(iRow, :) = ... 1591 | % [solnIn.anPart1(iLeaving), solnIn.anPart1(sReturning)]; 1592 | P_j(iDrone).Customer(solnIn.anPart2(iDroneCustomer)).aanCust(iRow, :) = ... 1593 | [iLeaving, sReturning]; 1594 | iRow = iRow + 1; 1595 | end 1596 | 1597 | end 1598 | 1599 | end 1600 | iDroneCustomer = iDroneCustomer + 1; 1601 | end 1602 | iDroneCustomer = iDroneCustomer + 1; 1603 | end 1604 | 1605 | % Run algorithm 1606 | for iteration = 1 : 10 1607 | P_jCopy = P_j; 1608 | iDrone = 1; 1609 | bDone = 0; 1610 | iDroneCustomer = 1; 1611 | 1612 | while iDrone <= nDrones && bDone ~= 1 1613 | while iDroneCustomer < length(solnNew.anPart2) && solnNew.anPart2(iDroneCustomer) ~= -1 && bDone ~= -1 1614 | % Randomly pick (i, s) from P_c (if possible) 1615 | % fprintf("iDrone: %d\n", iDrone) 1616 | % fprintf("Customer: %d\n", solnNew.anPart2(iDroneCustomer)) 1617 | anDimensions = size(P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust); 1618 | 1619 | if anDimensions(1) == 0 1620 | bDone = 1; 1621 | else 1622 | nRows = anDimensions(1); 1623 | 1624 | nRandRow = randi(nRows); 1625 | nRandi = P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust(nRandRow, 1); 1626 | nRands = P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust(nRandRow, 2); 1627 | 1628 | 1629 | % Assign launch i and reconvene s locations to customer j 1630 | solnNew.anPart3(iDroneCustomer) = nRandi; 1631 | solnNew.anPart4(iDroneCustomer) = nRands; 1632 | 1633 | % Initialize P_jCopy2 to be the same thing as P_jCopy 1634 | P_jCopy2 = P_jCopy; 1635 | 1636 | % Update P_jCopy according to the previously assigned flights to UAV_u 1637 | iTempRow = 1; 1638 | for iRow = 1 : nRows 1639 | i = P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust(iRow, 1); 1640 | s = P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust(iRow, 2); 1641 | 1642 | if i < nRandi && s <= nRandi 1643 | P_jCopy2(iDrone).Customer(solnIn.anPart2(iDroneCustomer)).aanCust(iTempRow, :) = ... 1644 | [i, s]; 1645 | iTempRow = iTempRow + 1; 1646 | elseif i >= nRands && s > nRandi 1647 | P_jCopy2(iDrone).Customer(solnIn.anPart2(iDroneCustomer)).aanCust(iTempRow, :) = ... 1648 | [i, s]; 1649 | iTempRow = iTempRow + 1; 1650 | else 1651 | bOk = 0; 1652 | end 1653 | 1654 | anDimensions = size(P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust); 1655 | if iRow == nRows && anDimensions(1) == 0 1656 | P_jCopy2 = P_jCopy; 1657 | end 1658 | 1659 | end 1660 | 1661 | % Actually update P_jCopy 1662 | P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust = P_jCopy2(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust; 1663 | 1664 | % Iterate iDroneCustomer 1665 | iDroneCustomer = iDroneCustomer + 1; 1666 | end 1667 | end 1668 | iDrone = iDrone + 1; 1669 | end 1670 | end 1671 | 1672 | %% Potential issue for feasibility 1673 | % Our drone planner will likely return parts 3 and 4 out of order 1674 | % (making it pop up as infeasible in our check_feasibility function) 1675 | % so we can fix this by fixing our output so that our drone routes are 1676 | % in proper order 1677 | end 1678 | 1679 | % Heuristic 3 1680 | function[ solnNew ] = apply_heuristic_3_opt( solnCurr, C0, aafDistances ) 1681 | % This function will implement the 2-opt heuristic which will swap two 1682 | % customers within the vector of Parts 1 and 2. The customers are selected 1683 | % randomly for the 2-Opt method. Chainging the values in Parts 1 and 2 1684 | % while keeping the values in Parts 3 and 4 unchanged may lead to an 1685 | % infeasible solution due to the flight range constraint. In the case of 1686 | % infeasibility, we use the DRONE PLANNER HEURISTIC. 1687 | 1688 | % Input 1689 | % solnCurr The current solution 1690 | % C0 The locations (coordinates) of the customers 1691 | % aafDistances The distances between customer node 1692 | 1693 | % Output 1694 | % solnBest The best solution our algorithm was able to determine 1695 | 1696 | % Variables 1697 | % nCustA Randomly selected customer integer 1698 | % nCustB Randomly selected customer integer 1699 | % nCustC Randomly selected customer integer 1700 | % nCustomers Number of customers 1701 | % nIndA1 Index of customer A if in part 1 (-1 if not in part 1) 1702 | % nIndB1 Index of customer B if in part 1 (-1 if not in part 1) 1703 | % nIndA2 Index of customer A if in part 2 (-1 if not in part 1) 1704 | % nIndB2 Index of customer B if in part 2 (-1 if not in part 1) 1705 | 1706 | % Get the number of customers 1707 | nCustomers = length(solnCurr.anPart1) - 2; 1708 | 1709 | for i = 1 : length(solnCurr.anPart2) 1710 | if solnCurr.anPart2(i) ~= -1 1711 | nCustomers = nCustomers + 1; 1712 | end 1713 | end 1714 | 1715 | % Get customers to be swapped 1716 | nCustA = randi([1, nCustomers]); % Ignore the 2 zeros 1717 | nCustB = randi([1, nCustomers]); 1718 | nCustC = randi([1, nCustomers]); 1719 | 1720 | while nCustB == nCustA 1721 | nCustB = randi([1, nCustomers]); 1722 | end 1723 | 1724 | while nCustC == nCustA || nCustC == nCustB 1725 | nCustC = randi([1, nCustomers]); 1726 | end 1727 | 1728 | % Find which slot in either part1 or part2 each of A & B & C are at 1729 | nPartA = 2; 1730 | nIndA = -1; 1731 | nPartB = 2; 1732 | nIndB = -1; 1733 | nPartC = 2; 1734 | nIndC = -1; 1735 | 1736 | for nIndex = 1 : length(solnCurr.anPart1) 1737 | if nCustA == solnCurr.anPart1(nIndex) 1738 | nPartA = 1; 1739 | nIndA = nIndex; 1740 | end 1741 | 1742 | if nCustB == solnCurr.anPart1(nIndex) 1743 | nPartB = 1; 1744 | nIndB = nIndex; 1745 | end 1746 | 1747 | if nCustC == solnCurr.anPart1(nIndex) 1748 | nPartC = 1; 1749 | nIndC = nIndex; 1750 | end 1751 | end 1752 | 1753 | 1754 | for nIndex = 1 : length(solnCurr.anPart2) 1755 | if nCustA == solnCurr.anPart2(nIndex) 1756 | nIndA = nIndex; 1757 | end 1758 | 1759 | if nCustB == solnCurr.anPart2(nIndex) 1760 | nIndB = nIndex; 1761 | end 1762 | 1763 | if nCustC == solnCurr.anPart2(nIndex) 1764 | nIndC = nIndex; 1765 | end 1766 | end 1767 | 1768 | % customer a in part 1 and customer b in part 1 1769 | solnNew = solnCurr; 1770 | 1771 | % Move customer C into customer A 1772 | if nPartA == 1 1773 | solnNew.anPart1(nIndA) = nCustC; 1774 | 1775 | else 1776 | solnNew.anPart2(nIndA) = nCustC; 1777 | end 1778 | 1779 | % Move customer A into customer B 1780 | if nPartB == 1 1781 | solnNew.anPart1(nIndB) = nCustA; 1782 | else 1783 | solnNew.anPart2(nIndB) = nCustA; 1784 | end 1785 | 1786 | % Move customer B into customer C 1787 | if nPartC == 1 1788 | solnNew.anPart1(nIndC) = nCustB; 1789 | else 1790 | solnNew.anPart2(nIndC) = nCustB; 1791 | end 1792 | 1793 | 1794 | % Check the feasibility 1795 | bFeasible = check_feasibility(solnNew, C0, aafDistances); 1796 | if bFeasible == 0 1797 | solnNew = apply_heuristic_7_drone_planner(solnNew, C0, aafDistances); 1798 | 1799 | if check_feasibility(solnNew, C0, aafDistances) == 0 1800 | solnNew = solnCurr; 1801 | end 1802 | end 1803 | end 1804 | 1805 | % Heuristic 4 1806 | function[ solnNew ] = apply_heuristic_4_Greedy_Assignment(solnIn, C0, aafDistances) 1807 | % This function will apply the greedy assignment heuristic to the input 1808 | % solution. This will make a list of customers that are NOT at leaving OR 1809 | % returning truck stops of drone flights (customers not at spots in parts 3 1810 | % or 4). It will then pick one at random and choose the best spot for the 1811 | % chosen customer. 1812 | % Input 1813 | % solnIn The input solution 1814 | % C0 The set of customers 1815 | % aafDistances Float matrix; calculated distances between nodes 1816 | % Output 1817 | % solnNew The output solution 1818 | 1819 | % Local Variables 1820 | % bLeavingOrReturning Array of boolean vectors. 1's indicate 1821 | % that they are rendezvous locations; 1822 | % 0's indicate that they are not 1823 | % EX. [1 0 1] implies drone leaves cust 1 & goes back to cust 3 1824 | % anTruckCustomers Array of truck customers. This excludes 1825 | % the depots. 1826 | 1827 | % Count the number of drones 1828 | if isempty(solnIn.anPart2) 1829 | nDrones = 0; 1830 | else 1831 | nDrones = 1; 1832 | i = 1; 1833 | while i < length(solnIn.anPart2) 1834 | if solnIn.anPart2(i) == -1 1835 | nDrones = nDrones + 1; 1836 | end 1837 | i = i + 1; 1838 | end 1839 | end 1840 | 1841 | % Initialize values 1842 | solnNew = solnIn; 1843 | fs_best = f(aafDistances, solnIn, nDrones); 1844 | 1845 | % Create a list of customers served by truck or UAV, but not served as 1846 | % rendevous locations 1847 | bLeavingOrReturning = zeros(1, length(C0.x) - 2); % -2 for depots 1848 | 1849 | % Create array of truck customers 1850 | anTruckCustomers = solnIn.anPart1(2 : end - 1); 1851 | 1852 | % Loop through parts 3 and 4. Then mark each of those as a 1 in the boolean array 1853 | for iPart3 = 1 : length(solnIn.anPart3) 1854 | % if NOT switching to another drone and NOT a depot 1855 | if solnIn.anPart3(iPart3) ~= -1 && solnIn.anPart1(solnIn.anPart3(iPart3)) ~= 0 1856 | % Set their boolean value to true (indicating a rendezvous loc) 1857 | anTruckCustomers(solnIn.anPart3(iPart3) ) 1858 | bLeavingOrReturning(anTruckCustomers(solnIn.anPart3(iPart3) - 1)) = 1; 1859 | end 1860 | 1861 | if solnIn.anPart4(iPart3) ~= -1 && solnIn.anPart1(solnIn.anPart4(iPart3)) ~= 0 1862 | % Set their boolean value to true (indicating a rendezvous loc) 1863 | anTruckCustomers(solnIn.anPart4(iPart3) - 1) 1864 | bLeavingOrReturning(anTruckCustomers(solnIn.anPart4(iPart3) - 1)) = 1; 1865 | end 1866 | end 1867 | 1868 | % Obtain list of all potential positions 1869 | anValidCustomers = [anTruckCustomers]; 1870 | 1871 | % Add on the customers from part 2 that are not -1 1872 | for iCustomer = 1 : length(solnIn.anPart2) 1873 | if solnIn.anPart2(iCustomer) ~= -1 1874 | anValidCustomers = [anValidCustomers solnIn.anPart2(iCustomer)]; 1875 | end 1876 | end 1877 | 1878 | % Start taking away from that list if they are a rendezvous location 1879 | anValidCustomersCopy = anValidCustomers; 1880 | for nCust = anValidCustomersCopy 1881 | if bLeavingOrReturning(nCust) == 1 1882 | anValidCustomers(nCust) = []; 1883 | end 1884 | end 1885 | 1886 | anValidCust 1887 | 1888 | % Select a customer randomly remove from the list of valid customers 1889 | randIndex = randperm(length(anValidCustomers), 1); 1890 | nRandCust = anValidCustomers(randIndex); 1891 | 1892 | % Remove it from its current route 1893 | % Select customer c_j in the list & remove it from truck route 1894 | solnRemovedCustomer = soln_remove_truck_customer(solnIn, nRandCust); 1895 | 1896 | % Check all potential positions in truck route 1897 | for iTruckStop = 2 : length(solnIn.anPart1) - 1 1898 | % Insert the customer in the truck route 1899 | insertedTruckSoln = ... 1900 | soln_insert_truck_customer(solnRemovedCustomer, nRandCust, iTruckStop); 1901 | 1902 | % Check feasibility 1903 | % Totally checking feasibility, yep looks super great 1904 | bFeasible = check_feasibility(insertedTruckSoln, C0, aafDistances); 1905 | 1906 | % Calculate the total waiting time 1907 | fTruckInsertionWaitingTime = f( aafDistances, insertedTruckSoln, nDrones ); 1908 | 1909 | % Compare this s to our original s_out 1910 | if bFeasible && (fTruckInsertionWaitingTime < fs_best) 1911 | solnNew = insertedTruckSoln; 1912 | fs_best = fTruckInsertionWaitingTime; 1913 | end 1914 | end 1915 | 1916 | indexindexindex = 1; 1917 | % Check all potential positions in drone route 1918 | for iDrone = 1 : nDrones 1919 | for iStopLeave = 1 : length(solnRemovedCustomer.anPart1) 1920 | for iStopReturn = iStopLeave + 1 : length(solnRemovedCustomer.anPart1) 1921 | % Insert the customer in the truck route 1922 | insertedDroneSoln = ... 1923 | soln_add_drone_customer(solnRemovedCustomer,... 1924 | nRandCust, iDrone, iStopLeave, iStopReturn); 1925 | 1926 | % Check feasibility 1927 | bFeasible = check_feasibility(insertedDroneSoln, C0, aafDistances); 1928 | 1929 | % Let's check what it looks like 1930 | % hold on; 1931 | % plot_route( C0, insertedDroneSoln, indexindexindex) 1932 | % hold off; 1933 | 1934 | % Calculate the total waiting time 1935 | % note: when calculating wait times, we are using 1936 | % the CURRENT soln.anPart1 1937 | fDroneInsertionWaitingTime = f( aafDistances, insertedDroneSoln, nDrones); 1938 | 1939 | % Compare this to the solnOut 1940 | if bFeasible && (fDroneInsertionWaitingTime < fs_best) 1941 | solnNew = insertedDroneSoln; 1942 | fs_best = fDroneInsertionWaitingTime; 1943 | end 1944 | 1945 | indexindexindex = indexindexindex + 1; 1946 | end 1947 | end 1948 | end 1949 | end 1950 | 1951 | % Heuristic 5 1952 | function[ solnNew ] = apply_heuristic_5_Origin_Destination(solnIn, C0, aafDistances, k) 1953 | % The origin destination heuristic will take in a current solution and 1954 | % calculate the distance. It will create a list of all customers served at 1955 | % rendezvous locations between truck & UAV (opposite of greedy assignment), 1956 | % select a customer i randomly from the list and remove it from its current 1957 | % route. It will then look at every potential position in truck route to 1958 | % insert customer i and save that as s'. It will then apply drone planner 1959 | % on that solution and check if the solution is feasible. 1960 | % Input 1961 | % k The number of drones 1962 | % Output 1963 | % 1964 | 1965 | % Local Variable 1966 | % bLeavingOrReturning boolean array; 1 if customer is rendevous 1967 | % location, 0 otherwise 1968 | % 1969 | 1970 | % Calculate distance of input solution 1971 | fSolnIn = f(aafDistances, solnIn, k); 1972 | 1973 | % Store it as the best 1974 | fSolnBet = fSolnIn; 1975 | 1976 | % Create list of all non-rendezvous locations 1977 | bLeavingOrReturning = zeros(1, length(C0.x) - 2); %-2 for depots 1978 | 1979 | % Create array of truck customers 1980 | % anTruckCustomers = solnIn.anPart1(2 : end - 1); 1981 | anListTruckCust = []; 1982 | 1983 | % Loop through parts 3 and 4. Mark each of those as a 1 in bool array 1984 | nIndex = 1; 1985 | for iPart3 = 1 : length(solnIn.anPart3) 1986 | % if NOT switching to another drone and NOT a depot 1987 | if solnIn.anPart3(iPart3) ~= -1 && solnIn.anPart1(solnIn.anPart3(iPart3)) ~= 0 1988 | % Set their boolean value to true (indicating a rendezvous loc) 1989 | % anTruckCustomers(solnIn.anPart3(iPart3)) 1990 | solnIn.anPart1(solnIn.anPart3(iPart3)) 1991 | % bLeavingOrReturning(solnIn.anPart1(solnIn.anPart3(iPart3) - 1)) = 1; 1992 | bLeavingOrReturning(solnIn.anPart1(solnIn.anPart3(iPart3))) = 1; 1993 | 1994 | % Add to list of rendezvous truck locations 1995 | anListTruckCust(nIndex) = solnIn.anPart1(solnIn.anPart3(iPart3)); 1996 | 1997 | % Increment index 1998 | nIndex = nIndex + 1; 1999 | end 2000 | 2001 | % if NOT switching to another drone and NOT a depot 2002 | if solnIn.anPart4(iPart3) ~= -1 && solnIn.anPart1(solnIn.anPart4(iPart3)) ~= 0 2003 | % Set their boolean value to true (indicating a rendevous loc) 2004 | % anTruckCustomers(solnIn.anPart4(iPart3) - 1) 2005 | solnIn.anPart1(solnIn.anPart4(iPart3)) 2006 | % bLeavingOrReturning(solnIn.anPart1(solnIn.anPart4(iPart3) - 1)) = 1; 2007 | bLeavingOrReturning(solnIn.anPart1(solnIn.anPart4(iPart3))) = 1; 2008 | 2009 | % Add to list of rendezvous truck locations 2010 | anListTruckCust(nIndex) = solnIn.anPart1(solnIn.anPart4(iPart3)); 2011 | 2012 | % Increment index 2013 | nIndex = nIndex + 1; 2014 | end 2015 | end 2016 | 2017 | % Obtain list of customer rendezvous locations 2018 | anValidCustomers = [anTruckCustomers]; 2019 | 2020 | % Start taking away from that list if they are not rendezvous locations 2021 | anValidCustomersCopy = anValidCustomers; 2022 | for nCust = anValidCustomersCopy 2023 | if bLeavingOrReturning(nCust) == 1 2024 | anValidCustomers(nCust) = []; 2025 | end 2026 | end 2027 | 2028 | % Select a customer randomly from the list & remove from its route 2029 | randIndex = randperm(length(anValidCustomers), 1); 2030 | nRandCust = anValidCustomers(randIndex); 2031 | 2032 | % Select customer c_j in the list & remove it from truck route 2033 | solnRemovedCustomer = soln_remove_truck_customer(solnIn, nRandCust); 2034 | 2035 | % Check all potential positions in truck route 2036 | for iTruckStop = 2 : length(solnIn.anPart1) - 1 2037 | % Insert the customer in the truck route 2038 | insertedTruckSoln = ... 2039 | soln_insert_truck_customer(solnRemovedCustomer, nRandCust, iTruckStop); 2040 | 2041 | % Apply drone planner heuristic 2042 | 2043 | 2044 | % Check feasibility 2045 | % Totally checking feasibility, yep looks super great 2046 | bFeasible = check_feasibility(insertedTruckSoln, C0, aafDistances); 2047 | 2048 | % Calculate the total waiting time 2049 | fTruckInsertionWaitingTime = f( aafDistances, insertedTruckSoln, k ); 2050 | 2051 | % Compare this s to our original s_out 2052 | if bFeasible && (fTruckInsertionWaitingTime < fs_best) 2053 | solnNew = insertedTruckSoln; 2054 | fs_best = fTruckInsertionWaitingTime; 2055 | end 2056 | end 2057 | 2058 | end -------------------------------------------------------------------------------- /TDRA.m: -------------------------------------------------------------------------------- 1 | function[] = TDRA( ) 2 | % This main function will execute the entirety of the Truck and Drone 3 | % routing algorithm. 4 | 5 | %%% 6 | % When it comes to calculating the "travel time between two customers" we will 7 | % use the distance between the two nodes for trucks. When calculating the 8 | % travel time between two customers by UAV, we will use the distance but 9 | % divided by a speed factor of 1.5. 10 | %%% 11 | 12 | % Variables 13 | % C0 Set of customer locations with depot as the starting 14 | % location where C0.x is the x location of the 15 | % customers and C0.y is the y locations of the 16 | % customers. 17 | % c Number of customers 18 | % U Set of drones 19 | % u Number of drones 20 | % aafDistancesTruck Travel times between two customers i & j by truck 21 | % soln Our representation of the solution where: 22 | % ansoln.Part1 represents the sequence of customers 23 | % visited by the truck 24 | % ansoln.Part2 represents the assignment of 25 | % customers to UAVs & also the sequence of 26 | % visits by each UAV 27 | % ansoln.Part3 represents the launch locations of 28 | % each UAV for every flight 29 | % ansoln.Part4 represents the reconvene locations of 30 | % each UAV for every flight 31 | % Rmax Maximum number of iterations that our heuristic 32 | % will run 33 | % WeightInfo The weight of each heuristic which serves as an 34 | % indicator of the performance of the heuristic 35 | 36 | 37 | 38 | 39 | % Generate customer locations 40 | % C0.x = (50 - -50)*rand(1, 4) + -50; 41 | % C0.y = (50 - -50)*rand(1, 4) + -50; 42 | % C0.x = [ 0 -10 0 0 ]; % we treat the first slot as the depot; 43 | % C0.y = [ 0 0 -10 -5 ]; % we treat the first slot as the depot; 44 | % C0.x = [ 0 randi([-10, 10], 1, 10) 0 ]; 45 | % C0.y = [ 0 randi([-10, 10], 1, 10) 0 ]; 46 | 47 | C0.x = [ 0 -4 -7 -6 4 6 2 9 8 7 6 0 ]; 48 | C0.y = [ 0 1 -3 -8 -9 -3 10 9 -2 -8 -8 0]; 49 | 50 | % % 0 1 2 3 4 0 Customer ID 51 | % % 1 2 3 4 5 6 Indices 52 | % C0.x = [ 0 8 6 -7 1 0 ]; 53 | % C0.y = [ 0 -2 -3 -3 2 0 ]; 54 | 55 | % 0 1 2 3 4 5 6 0 Customer ID 56 | % 1 2 3 4 5 6 7 8 Indices 57 | % C0.x = [ 0 -2 -3 2 2 7 -4 0 ]; 58 | % C0.y = [ 0 4 1 4 3 1 3 0 ]; 59 | 60 | % 61 | % C0.x = [0, 1, 2, 3, 3, 2, 1, 0 ] 62 | % C0.y = [0, 1, 1, 0.1, -1, -1, -1, 0] 63 | 64 | %%%%%%%%%%%%%%%%%%%%%%%%Testing ellipse fitting %%%%%%%%%%%%%%%%%%%%%%%% 65 | % Slanted ellipse 66 | % C0.x = [ 1, 2, 3, 4, 4.5, 5, 5.5, 6, 6, 5.7, 5.5, 5, 4, 3, 1, ... 67 | % -1, -3, -6, -7, -7.5, -7, -6, ... 68 | % -3, -2, -1, -4, -5]; 69 | % C0.y = [ 7, 6, 5, 4, 3, 2, 0.5, -1, -1.5, -3.5, -4, -5, -5.3, -5.2, -4.5,... 70 | % 8, 8, 7, 6, 4, 2, 0, ... 71 | % -2.5, -3, -3.5, -1.5, -1]; 72 | 73 | 74 | % Ellipse along x axis; This works well 75 | % C0.x = [ -3, -2.5, -2, -1.5, -1, -.5, 0, .5, 1, 1.5, 2, 2.5, 3, -3, -2.5, -2, -1.5, -1, -.5, 0, .5, 1, 1.5, 2, 2.5, 3 ]; 76 | % C0.y = [ 0.54, 1.06, 1.34, 1.52, 1.64, 1.71, 1.73, 1.71, 1.64, 1.54, 1.34, 1.06, .54, -0.54, -1.06, -1.34, -1.52, -1.64, -1.71, -1.73, -1.71, -1.64, -1.54, -1.34, -1.06, -.54 ]; 77 | 78 | % Ellipse along y axis 79 | % C0.x = [ 0 [ -2 : 0.5 : 2 ] [ -2 : 0.5 : 2 ] 0] 80 | % C0.y = [0 sqrt( 1 - (C0.x(1 : 9).^2)/4 ) [ -1*sqrt( 1 - (C0.x(10 : 18).^2)/4 ) ] 0 ] 81 | 82 | 83 | % Plot the customer locations 84 | % hold on 85 | % figure( 1 ); 86 | % plot(0,0,'b*', 'MarkerSize', 12) 87 | % plot(C0.x, C0.y, 'bo') 88 | % hold off 89 | 90 | % Numbers of customers (excluding the depot) 91 | c = length(C0.x) - 2; 92 | 93 | % Number of drones 94 | U = [1, 2]; 95 | u = length(U); 96 | 97 | % Initial and final temperature (Inputs for SA) 98 | T0 = 90; 99 | TFinal = 0.01; 100 | Beta_SA = 0.99; 101 | Imax = 20; 102 | fBoltzmannThresh = 0.8; 103 | 104 | % Calculate the distances between each all points 105 | % Here, our first row of the columns is how far our depot is from each 106 | % of the customers. 107 | aafDistances = calculateDistances(C0.x, C0.y); 108 | 109 | % Initialize solution data structure 110 | soln.anPart1 = zeros(1, length(C0.x)); % the Truck Route 111 | soln.anPart2 = [-1]; % Assignment of customers to UAVs 112 | soln.anPart3 = [-1]; % Launch locations of the UAVs 113 | soln.anPart4 = [-1]; % Reconvene locations of the UAVs 114 | 115 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 116 | % Test plotting our path with drones 117 | % THis is correct btw 118 | % s.anPart1 = [ 0 4 2 3 0 ]; 119 | % s.anPart3 = [ 2 -1 ]; % These indices refer to the cell number in part 1, so this is wrong. 120 | % s.anPart2 = [ 1 -1 ]; % visits customer 1 121 | % s.anPart4 = [ 3 -1 ]; % returns to index 2 of s.anPart1 (which is customer 3) 122 | % 123 | % s.anPart1 = [ 0 4 3 1 2 0 ]; % MOST RECENT TEST 124 | % s.anPart3 = [ 1 -1 4 ]; 125 | % s.anPart2 = [ 5 -1 6 ]; 126 | % s.anPart4 = [ 3 -1 5 ]; 127 | % 128 | % s.anPart1 = [ 0 4 2 0 ]; 129 | % s.anPart3 = [ 2 3 -1 ]; % These indices refer to the cell number in part 1, so this is wrong. 130 | % s.anPart2 = [ 1 3 -1 ]; % visits customer 1 131 | % s.anPart4 = [ 3 4 -1 ]; % returns to index 2 of s.anPart1 (which is customer 3) 132 | % 133 | % s.anPart1 = [ 0 4 2 0 ]; 134 | % s.anPart3 = [ 2 -1 3 ]; % These indices refer to the cell number in part 1, so this is wrong. 135 | % s.anPart2 = [ 1 -1 3 ]; % visits customer 1 136 | % s.anPart4 = [ 3 -1 4 ]; % returns to index 2 of s.anPart1 (which is customer 3) 137 | % 138 | % s.anPart1 = [ 0 4 2 0 ]; 139 | % s.anPart3 = [ 2 -1 3 ]; % These indices refer to the cell number in part 1, so this is wrong. 140 | % s.anPart2 = [ 1 -1 3 ]; % visits customer 1 141 | % s.anPart4 = [ 3 -1 4 ]; % returns to index 2 of s.anPart1 (which is customer 3) 142 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 143 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 144 | 145 | 146 | %% Generate Initial Solution 147 | % Generate initial solution s, to the STRPD based on SA 148 | s = simulatedAnnealing(c, aafDistances, soln, T0, TFinal, Beta_SA, Imax, u); 149 | 150 | % Run elliptical customer assignment on result of Simulated annealing 151 | s = ellipticalCustomerAssignment( s, C0, u ); 152 | 153 | %% Intialize Values 154 | % Initialize the best solution and the result of the best solution 155 | s_best = s; 156 | fs_best = f(aafDistances, s, u); 157 | T = T0; 158 | 159 | %% Plot the truck and drone route 160 | plot_route( C0, s, 1); 161 | 162 | % ALAN FIX THIS 163 | % The reason our plot isn't working too well is because for whatever 164 | % reason, the solnOut is choosing to return to a customer location that 165 | % is further away than the one it should be returning to . 166 | hold on; 167 | plot_route( C0, s_best, 4) 168 | hold off; 169 | 170 | s 171 | 172 | solnOut = apply_heuristic_4_Greedy_Assignment(s, C0, aafDistances) 173 | 174 | 175 | % Initialize the weights before running main algorithm 176 | WeightInfo = weight_init(); 177 | 178 | Rmax = 100; % should be 6000 179 | for iIter = 1 : Rmax 180 | % Select a heuristic 181 | nHeuristic = select_heuristic(WeightInfo); 182 | 183 | nHeuristic = 5; 184 | 185 | % Apply the selected heuristic to s 186 | switch nHeuristic 187 | case 1 188 | s_prime = apply_heuristic_2_opt(s, C0, aafDistances); 189 | fs_prime = f(aafDistances, s_prime, u); 190 | case 2 191 | s_prime = apply_heuristic_7_drone_planner(s, C0, aafDistances); 192 | fs_prime = f(aafDistances, s_prime, u); 193 | case 3 194 | s_prime = apply_heuristic_3_opt(s, C0, aafDistances); 195 | fs_prime = f(aafDistances, s_prime, u); 196 | case 4 197 | s_prime = apply_heuristic_4_Greedy_Assignment(s, C0, aafDistances); 198 | fs_prime = f(aafDistances, s_prime, u); 199 | case 5 200 | s_prime = apply_heuristic_5_Origin_Destination(s, C0, aafDistances, u); 201 | fs_prime = f(aafDistances, s_prime, u); 202 | 203 | otherwise 204 | fs_prime = 0; 205 | end 206 | 207 | % Check if new prime solution is better than best solution 208 | if fs_prime < fs_best 209 | s_best = s_prime; 210 | fs_best = fs_prime; 211 | end 212 | 213 | % If s_prim is accepted as new sol based on Boltzmann; Boltzmann probability time babbbyyyy 214 | diff = fs_best - f(aafDistances, s_prime, u); 215 | 216 | % if rand() < exp(-abs(diff) / T) 217 | if exp(-abs(diff) / T) < fBoltzmannThresh 218 | s = s_prime; 219 | end 220 | 221 | % Get the current value 222 | fs_Curr = f(aafDistances, s, u); 223 | 224 | % Update weights of heuristics 225 | WeightInfo = update_weights( WeightInfo, nHeuristic, fs_prime, fs_best, fs_Curr, iIter); 226 | end 227 | 228 | f(aafDistances, solnOut, u) 229 | 230 | answerBeforeHueristic2Opt = solnOut 231 | beforeResult = f(aafDistances, answerBeforeHueristic2Opt, u) 232 | 233 | % This is line 4 of Algorithm 1: Outline of the TDRA 234 | answerAfterHueristic2Opt = apply_heuristic_2_opt(solnOut, C0, aafDistances) 235 | afterResult = f(aafDistances, answerAfterHueristic2Opt, u) 236 | 237 | end 238 | 239 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 240 | function[ ] = plot_route( C0, s, fig_num ) 241 | % plot_route will take in the customer locations & the initial solution 242 | % produced via the Simulated annealing method. It will then plot the route 243 | % the truck will take. 244 | % Input 245 | % C0 The locations of all the customers 246 | % s The route we are plotting 247 | % u The number of drones 248 | % fig_num The number of the figure 249 | % Output 250 | % 251 | 252 | % Variables 253 | % a, b Placeholder variables for the coordinates 254 | % nCustInd counter to keep track of the customer indices 255 | 256 | % Plot the truck route 257 | nCustInd = 2; 258 | a = [ 0 ]; 259 | b = [ 0 ]; 260 | 261 | % Fill in these a and b arrays 262 | % for i = s.anPart1( 2 : length(C0.x) - 1 ) 263 | for i = s.anPart1( 2 : length(s.anPart1) - 1) 264 | a(nCustInd) = C0.x(i+1); 265 | b(nCustInd) = C0.y(i+1); 266 | nCustInd = nCustInd + 1; 267 | a; 268 | b; 269 | end 270 | 271 | a(end + 1) = 0; 272 | b(end + 1) = 0; 273 | 274 | 275 | 276 | % Initialize the coords visited by the drones 277 | aanDrones = zeros(length(s.anPart2)-1, 6); 278 | 279 | C0; 280 | s; 281 | % Fill in this matrix 282 | for iCustomer = 1 : length(s.anPart2) 283 | if s.anPart2(iCustomer) == -1 284 | aanDrones( iCustomer, : ) = -1*ones(1, 6); 285 | % aanDrones = [ aanDrones ; -1*ones(1, 6) ] 286 | 287 | else 288 | aanDrones(iCustomer, 1) = C0.x(s.anPart1(s.anPart3(iCustomer)) + 1); 289 | aanDrones(iCustomer, 4) = C0.y(s.anPart1(s.anPart3(iCustomer)) + 1); 290 | 291 | aanDrones(iCustomer, 3) = C0.x(s.anPart1(s.anPart4(iCustomer)) + 1); 292 | aanDrones(iCustomer, 6) = C0.y(s.anPart1(s.anPart4(iCustomer)) + 1); 293 | 294 | aanDrones(iCustomer, 2) = C0.x(s.anPart2(iCustomer) + 1); 295 | aanDrones(iCustomer, 5) = C0.y(s.anPart2(iCustomer) + 1); 296 | end 297 | end 298 | 299 | 300 | 301 | % Plot the drone route 302 | aColors = ["r--", "b--", "g--", "k--", "m--"]; 303 | 304 | % 305 | aDimensions = size(aanDrones); 306 | 307 | % Plot this stuff 308 | figure(fig_num) 309 | hold on; 310 | plot(0,0,'b*', 'MarkerSize', 12) 311 | plot(a, b, 'k-') 312 | plot(a, b, "ro") 313 | 314 | iDrone = 1; 315 | for iRow = 1 : aDimensions 316 | x = [ 0 ]; 317 | y = [ 0 ]; 318 | 319 | if aanDrones(iRow, 1) == -1 320 | iDrone = iDrone + 1; 321 | else 322 | for iCol = 1 : 3 323 | x(iCol) = aanDrones(iRow, iCol); 324 | y(iCol) = aanDrones(iRow, iCol+3); 325 | end 326 | 327 | plot(x, y, aColors(iDrone)); 328 | end 329 | end 330 | hold off; 331 | end 332 | 333 | function[ aafDistances ] = calculateDistances( x, y ) 334 | % calculateDistances will calculate the distances between all the points 335 | % on the graph 336 | % INPUT 337 | % x, y the x and y coordinates of the customers 338 | % OUTPUT 339 | % aafDistances An array of distances where D_ij is the distance between 340 | % house i and house j; this matrix should be symmetric 341 | 342 | % Variables 343 | % 344 | 345 | % Initialize the distances array 346 | aafDistances = zeros(length(x)); 347 | 348 | % Calculate the distances 349 | for i = 1 : length(x) 350 | for j = 1 : length(x) 351 | aafDistances( i, j ) = sqrt( (x(i) - x(j))^2 + (y(i) - y(j))^2 ); 352 | end 353 | end 354 | 355 | end 356 | 357 | function[ s_best ] = ... 358 | simulatedAnnealing( c, aafDistances, soln, T0, TFinal, Beta_SA, Imax, u) 359 | % simulatedAnnealing function will take in an array of distances 360 | % and will generate a random TRP (traveling repairman problem). 361 | % INPUT 362 | % c The length of the soln 363 | % aafdistances Array of distances between customers 364 | % soln Data structure of solution 365 | % T0 The initial temperature 366 | % TFinal The final temperature we want to approach 367 | % Beta_SA The cooling schedule for our simulated annealing 368 | % Imax The maximum number of iterations 369 | % u The numebr of drones 370 | % OUTPUT 371 | % s_best Returns the best solution to the TRP 372 | 373 | 374 | % Variables 375 | % soln Data structure with solutions parts 1, 2, 3, and 4 376 | % s Solution to the TRP problem 377 | % s_best Best solution 378 | % fs_best The result of the best solution7 379 | % T The current temperature of our simulated annealing 380 | 381 | 382 | % Initialize our current and final temperatures 383 | T = T0; 384 | 385 | % Initialize boltzmann threshold 386 | fBoltzmannThresh = 0.8; 387 | 388 | % Generate a ranomd TRP (Traveling Repairman Problem) 389 | % randomly insert all customers into vector 390 | soln.anPart1 = [ 0 randperm(c) 0 ]; 391 | s = soln; % create duplicate of soln for comparison 392 | 393 | % Initilaize the best solution and the best result 394 | s_best = s; 395 | fs_best = f(aafDistances, s, u); 396 | 397 | sPrime = soln; 398 | 399 | % Run the simulated annealing process 400 | while T > TFinal 401 | for i = 1 : Imax 402 | % Find neighbor to s 403 | sPrime.anPart1 = neighbor(s); 404 | 405 | % Check if neighbor is better than current solution 406 | diff = fs_best - f(aafDistances, sPrime, u); 407 | 408 | % Check if neighbor is better 409 | if f(aafDistances, sPrime, u) < f(aafDistances, s_best, u) 410 | s_best.anPart1 = sPrime.anPart1; 411 | fs_best = f(aafDistances, sPrime, u); 412 | end 413 | 414 | % Boltzmann probability time babbbyyyy 415 | if exp(-abs(diff) / T) < fBoltzmannThresh 416 | s.anPart1 = sPrime.anPart1; 417 | end 418 | end 419 | 420 | % Cooling schedule 421 | T = Beta_SA * T; 422 | end 423 | end 424 | 425 | function[ solnOut ] = ellipticalCustomerAssignment( solnIn, C0, k ) 426 | % The ellipticalCustomerAssignment heuristic function will take in a soln 427 | % and then will return another solution. 428 | 429 | % INPUT 430 | % solnIn This is the input solution 431 | % C0 The locations (coordinates) of the customers 432 | % k Number of drones 433 | % OUTPUT 434 | % solnOut This is the output solution 435 | 436 | % VARIABLES 437 | % D The design matrix 438 | % C The constraint matrix 439 | % S Scatter matrix 440 | % V Matrix with columns being the eigenvectors 441 | % D Matrix where diagonals are eigenvalues 442 | % g Anonymous function for distance from point to conic 443 | % h Anonymous function for distance between two points 444 | % aVectorScaled The parameters that create our ellipse 445 | % anSortedTruckCustomers Vector of truck customers sorted by distance 446 | % to ellipse 447 | % aafDistances Get the distances between all the points 448 | 449 | 450 | % Create matrix of distances 451 | aafDistances = calculateDistances(C0.x, C0.y); 452 | 453 | % Assign initial variables 454 | solnOut = solnIn; 455 | fSolnOut = f( aafDistances, solnIn, k); 456 | 457 | % Fit an ellipse to C0 458 | % First create our design matrix 459 | D = zeros(length(C0.x) - 1, 6); 460 | for iRow = 1 : length(C0.x) - 1 461 | D( iRow, 1 ) = (C0.x(iRow))^2; 462 | D( iRow, 2 ) = C0.x(iRow) * C0.y(iRow); 463 | D( iRow, 3 ) = (C0.y(iRow))^2; 464 | D( iRow, 4 ) = C0.x(iRow); 465 | D( iRow, 5 ) = C0.y(iRow); 466 | D( iRow, 6 ) = 1; 467 | end 468 | 469 | 470 | % Next our constraint matrix 471 | C = zeros(6); 472 | C(1, 3) = 2; 473 | C(3, 1) = 2; 474 | C(2, 2) = -1; 475 | 476 | % Create scatter matrix 477 | S = D' * D; 478 | 479 | % Find our eigenvalues and eigenvectors 480 | [ eigenvector, eigenvalue ] = eig( S, C ); 481 | 482 | % Find the smallest eigenvalue 483 | fMinDiag = inf; 484 | for iDiagonal = 1 : 6 485 | if eigenvalue(iDiagonal, iDiagonal) > 1e-6 && eigenvalue(iDiagonal, iDiagonal) < fMinDiag 486 | iMinDiag = iDiagonal; 487 | fMinDiag = eigenvalue(iDiagonal, iDiagonal); 488 | end 489 | end 490 | 491 | 492 | % Scale the parameters so that a'C*a = 1 493 | aVector = eigenvector( :, iMinDiag ); 494 | fScaling = aVector' * C * aVector; 495 | aVectorScaled = aVector / sqrt(fScaling); 496 | 497 | 498 | % This function gives us the algebraic distance of any point (x, y) 499 | % from the conic. 500 | g = @( a, x, y) (a(1)*x.^2 + a(2).*x.*y + a(3)*y.^2 + a(4)*x + a(5)*y + a(6)); 501 | 502 | 503 | % Create functions from the quadratic formulas 504 | fplus = @(a, x) ( -(a(2)*x + a(5)) + sqrt( (a(2)*x + a(5)).^2 - 4*a(3)*(a(1)*x.^2 + a(4)*x + a(6)) ) ) / (2*a(3)); 505 | fminus = @(a, x) ( -(a(2)*x + a(5)) - sqrt( (a(2)*x + a(5)).^2 - 4*a(3)*(a(1)*x.^2 + a(4)*x + a(6)) ) ) / (2*a(3)); 506 | 507 | 508 | % Create array of points 509 | afXData = [ min(C0.x) - 10 : 0.01 : max(C0.x) + 10 ]; 510 | % Check to see if the x and y are imaginary 511 | counter = 1; 512 | for x = afXData 513 | if isreal(fplus( aVectorScaled, x )) && fplus( aVectorScaled, x) ~= 0 514 | afXGoodData(counter) = x; 515 | afYDataPositive(counter) = fplus( aVectorScaled, x ); 516 | end 517 | 518 | if isreal(fminus( aVectorScaled, x )) && fminus( aVectorScaled, x) ~= 0 519 | afYDataNegative(counter) = fminus( aVectorScaled, x ); 520 | end 521 | 522 | if isreal(fminus( aVectorScaled, x )) && isreal(fplus( aVectorScaled, x )) 523 | counter = counter + 1; 524 | end 525 | 526 | 527 | end 528 | 529 | 530 | % Plot the points 531 | figure() 532 | hold on; 533 | plot(C0.x, C0.y, 'bo') 534 | plot(afXGoodData, afYDataPositive, 'r-') 535 | plot(afXGoodData, afYDataNegative, 'r-') 536 | 537 | % Create another plot to overlay 538 | figure(12345) 539 | hold on; 540 | plot(C0.x, C0.y, 'bo') 541 | plot(afXGoodData, afYDataPositive, 'r-') 542 | plot(afXGoodData, afYDataNegative, 'r-') 543 | 544 | 545 | % Given a customer point (xi, yi), find the point (x, y) on an ellipse 546 | % that minimizes the distance between (xi, yi) and (x, y) 547 | 548 | % Set the max number of iterations, the threshold 549 | % for convergence 550 | nMax = 30; 551 | fThreshold = 10^(-6); 552 | 553 | % Hard code the function and its jacobian 554 | h = @(x, y, lambda, xi, yi, a) ... 555 | [ 2*x - 2*xi + 2*a(1)*x*lambda + a(2)*lambda*y + a(4)*lambda; 556 | 2*y - 2*yi + a(2)*lambda*x + 2*a(3)*lambda*y + a(5)*lambda; 557 | a(1)*x^2 + a(2)*x*y + a(3)*y^2 + a(4)*x + a(5)*y + a(6) ]; 558 | 559 | h_jacobian = @(x, y, lambda, xi, yi, a) ... 560 | [ 2 + 2*a(1)*lambda, a(2)*lambda, 2*a(1)*x + a(2)*y + a(4); 561 | a(2)*lambda, 2 + 2*a(3)*lambda, a(2)*x + 2*a(3)*y + a(5); 562 | 2*a(1)*x + a(2)*y + a(4), a(2)*x + 2*a(3)*y + a(5), 0 ]; 563 | 564 | % Initialize ellipse points 565 | afEllipse.x = []; 566 | afEllipse.y = []; 567 | 568 | % Initialize vector of distances 569 | afDistanceFromEllipse = zeros(1, length(C0.x)-2); 570 | 571 | %Instead of iterating through the length of the arrays, iterate through 572 | for nCustomer = solnIn.anPart1(2 : end - 1) 573 | % Set initial guess 574 | % x y lambda 575 | afGuess = [ C0.x(nCustomer + 1); C0.y(nCustomer + 1); 0 ]; 576 | 577 | 578 | % Get the unknowns 579 | [ ae_vals, j, aUnknowns ] = ... 580 | newtons_method( nMax, afGuess, fThreshold, h, h_jacobian, aVectorScaled, C0, nCustomer+1); 581 | 582 | % Plot the point on the ellipse 583 | plot(aUnknowns(1, j-1), aUnknowns(2, j-1), 'ko') 584 | 585 | % Store the ellipse point 586 | afEllipse.x = aUnknowns(1, j-1); 587 | afEllipse.y = aUnknowns(2, j-1); 588 | 589 | % Calculate the distance between customers and closest pt on ellipse 590 | afDistanceFromEllipse( nCustomer ) = sqrt( (afEllipse.x(end) - C0.x(nCustomer+1))^2 + ... 591 | (afEllipse.y(end) - C0.y(nCustomer+1))^2 ); 592 | 593 | % Plot those thangs 594 | plot( [ C0.x(nCustomer+1); afEllipse.x ], [C0.y(nCustomer+1); afEllipse.y], 'b-' ) 595 | % [ C0.x(nCustomer); afEllipse.x ] 596 | % % for i = 1 : length(C0.x) 597 | % % plot( [ C0.x(nCustomer+1); afEllipse.x(nCustomer) ], [C0.y(nCustomer+1); afEllipse.y(nCustomer)], 'b-' ) 598 | % % end 599 | % % hold off; 600 | 601 | % Final Output Lines 602 | % fprintf("\n") 603 | % fprintf("Final Output Line for Part (a)\n") 604 | % fprintf("Current k | e^(k+1) | x^(k+1)\n" ); 605 | % fprintf(" %4d | %10.10f | [ %2.6f ; %2.6f; %2.6f ] \n", k-2, ae_vals(k-1), aUnknowns(1, k-1), aUnknowns(2, k-1), aUnknowns(3, k-1)) 606 | 607 | end 608 | 609 | hold off; 610 | 611 | 612 | % Sort the customer in descending order from distance to the ellipse 613 | [ afSortedDistances, anSortedTruckCustomers ] = sort( afDistanceFromEllipse, 2, "descend" ); 614 | 615 | afSortedDistances; 616 | 617 | 618 | % Get the distance matrix (the distances between all the points) 619 | aafDistances = calculateDistances(C0.x, C0.y); 620 | 621 | 622 | % Now we start looping until no feasible position is available for 623 | % customers in the list for re-insertion in UAV routes 624 | % feasible = 1; % 625 | % temp_counter = 0; % this temporary counter will eventually be removed, 626 | % it is taking place of the feasibility thing 627 | 628 | bDone = 0; 629 | 630 | 631 | 632 | % if for one iteration of the repeat loop, doesn't improve. 633 | while ~bDone 634 | % Initialize the "previous" best waiting time 635 | fTotalWaitingTimePrev = fSolnOut; 636 | 637 | for i = 1 : length(anSortedTruckCustomers) 638 | % Store the index of customer i in anSortedTruckCustomers 639 | jCust = anSortedTruckCustomers(i); % customer index at index i in anSortedTruckCustomers 640 | 641 | % Select customer c_j in the list & remove it from truck route 642 | solnRemovedCustomer = soln_remove_truck_customer(solnIn, jCust); 643 | 644 | % Check all potential positions in truck route 645 | for iTruckStop = 2 : length(solnIn.anPart1) - 1 646 | % Insert the customer in the truck route 647 | insertedTruckSoln = ... 648 | soln_insert_truck_customer(solnRemovedCustomer, jCust, iTruckStop); 649 | 650 | 651 | % Check feasibility 652 | % Totally checking feasibility, yep looks super great 653 | bFeasible = check_feasibility(insertedTruckSoln); 654 | 655 | % Calculate the total waiting time 656 | fTruckInsertionWaitingTime = f( aafDistances, insertedTruckSoln, k ); 657 | 658 | % Compare this s to our original s_out 659 | if bFeasible && (fTruckInsertionWaitingTime < fSolnOut) 660 | solnOut = insertedTruckSoln; 661 | fSolnOut = fTruckInsertionWaitingTime; 662 | end 663 | end 664 | 665 | % Intialize feasibility for inserting customer into drone 666 | % bFeasible = 1; 667 | 668 | indexindexindex = 1; 669 | % Check all potential positions in drone route 670 | for iDrone = 1 : k 671 | for iStopLeave = 1 : length(solnRemovedCustomer.anPart1) 672 | for iStopReturn = iStopLeave + 1 : length(solnRemovedCustomer.anPart1) 673 | % Insert the customer in the truck route 674 | insertedDroneSoln = ... 675 | soln_add_drone_customer(solnRemovedCustomer,... 676 | jCust, iDrone, iStopLeave, iStopReturn); 677 | 678 | % Check feasibility 679 | bFeasible = check_feasibility(insertedDroneSoln, C0, aafDistances); 680 | 681 | % Let's check what it looks like 682 | % hold on; 683 | % plot_route( C0, insertedDroneSoln, indexindexindex) 684 | % hold off; 685 | 686 | % Calculate the total waiting time 687 | % note: when calculating wait times, we are using 688 | % the CURRENT soln.anPart1 689 | fDroneInsertionWaitingTime = f( aafDistances, insertedDroneSoln, k); 690 | 691 | % Compare this to the solnOut 692 | if bFeasible && (fDroneInsertionWaitingTime < fSolnOut) 693 | solnOut = insertedDroneSoln; 694 | fSolnOut = fDroneInsertionWaitingTime; 695 | end 696 | 697 | indexindexindex = indexindexindex + 1; 698 | end 699 | end 700 | end 701 | end % here here here 702 | 703 | 704 | % Remove the customer from the list 705 | anSortedTruckCustomers(1) = []; 706 | 707 | 708 | % right here 709 | if fTotalWaitingTimePrev <= fSolnOut 710 | bDone = 1; 711 | end 712 | end 713 | 714 | end 715 | 716 | function[ ae_vals, k, aUnknowns ] = ... 717 | newtons_method( nMax, afGuess, fThreshold, h, h_jacobian, a, C0, iCustomer ) 718 | % Newton's method will approximate the roots of our function h. For this 719 | % particular problem, h is the gradient of our distance formula. 720 | % Input 721 | % nMax The maximum number of iterations 722 | % afGuess Our initial guess 723 | % fThreshold The threshold for convergence 724 | % h The function we are trying to find the root of 725 | % h_jacobian The jacobian of function h 726 | % aVectorScaled The paramters we found to create our ellipse 727 | % C0 Our set of customers 728 | % iCustomer The current customer we are at 729 | % Output 730 | % ae_vals Array of error values 731 | % k Number of iterations 732 | % aUnknowns Array of unknowns we are trying to find 733 | 734 | % Local Variables 735 | % k Number of iterations it took to converge 736 | % e The error value 737 | % xi x coordinate of the customers 738 | % yi y coordinate of the customers 739 | 740 | % Initialize k values and error value e 741 | k = 1; 742 | e = 99; % absurd error value 743 | 744 | % Create variables for the x and y coordinates of the customers 745 | xi = C0.x(iCustomer); 746 | yi = C0.y(iCustomer); 747 | 748 | % Initialize vector to hold the updating x, y, and lambda values and 749 | % vector for e values 750 | aUnknowns = zeros(3, nMax); 751 | ae_vals = zeros(1, nMax); 752 | aUnknowns(:, 1) = afGuess; 753 | ae_vals(1) = e; 754 | 755 | % Run Newton's Method for the vector case 756 | while k < nMax + 2 && ae_vals(k) > fThreshold 757 | aUnknowns(:, k+1) = aUnknowns(:, k) - ... 758 | h_jacobian(aUnknowns(1, k), aUnknowns(2, k), aUnknowns(3, k), xi, yi, a) \ ... 759 | h(aUnknowns(1, k), aUnknowns(2, k), aUnknowns(3, k), xi, yi, a ); 760 | 761 | % Calculate the error 762 | ae_vals(k+1) = norm( aUnknowns(:, k+1) - aUnknowns(:, k) ); 763 | 764 | % Print k, the error and the value 765 | % fprintf("Current k | e^(k+1) | x^(k+1)\n" ); 766 | % fprintf(" %4d | %10.10f | [ %2.6f ; %2.6f; %2.6f ] \n", k-1, ae_vals(k+1), aUnknowns(1, k+1), aUnknowns(2, k+1), aUnknowns(3, k+1)) 767 | 768 | % Update k 769 | k = k + 1; 770 | end 771 | end 772 | 773 | function[ solnOut ] = soln_remove_truck_customer(solnIn, jCust) 774 | % The soln_remove_truck_Customer function takes in a solution structure and 775 | % the customer j that we will be removing from the truck route. It will 776 | % first loop over soln.anPart1 to find jCust. Then it will "remove him" by 777 | % esentially shifting everyone in solnIn.anPart1 after him down by 1. 778 | % Input 779 | % solnIn The solution we are currently working with 780 | % jCust The customer we will be removing from part 1 of solnIn 781 | % Output 782 | % solnOut The resulting solution 783 | 784 | % Local variables 785 | % iCustomer counter variable to keep track of the customer index 786 | % 787 | 788 | % Initialize jCustIndex to end + 1 789 | jCustIndex = length(solnIn.anPart1) + 1; 790 | 791 | % Loop over soln.anPart1 to find jCust 792 | for iCustomer = 1 : length(solnIn.anPart1) 793 | if solnIn.anPart1(iCustomer) == jCust 794 | jCustIndex = iCustomer; 795 | end 796 | end 797 | 798 | % Remove jCust and shift everyone down 799 | solnIn.anPart1(jCustIndex) = []; 800 | solnIn.anPart1; 801 | 802 | % Shift things in part 3 and part 4 that are after the removal slot 803 | % down by 1 804 | nDroneCounter = 1; 805 | for nIndex = 1 : length(solnIn.anPart3) 806 | if solnIn.anPart3(nIndex) < 0 807 | nDroneCounter = nDroneCounter + 1; 808 | 809 | else 810 | if solnIn.anPart3(nIndex) > jCustIndex % "if it comes after" 811 | solnIn.anPart3(nIndex) = solnIn.anPart3(nIndex) - 1; 812 | end 813 | 814 | if solnIn.anPart4(nIndex) > jCustIndex 815 | solnIn.anPart4(nIndex) = solnIn.anPart4(nIndex) - 1; 816 | end 817 | end 818 | end 819 | 820 | % Return the solnOut 821 | solnOut = solnIn; 822 | 823 | end 824 | 825 | function[ solnOut ] = soln_insert_truck_customer(solnIn, jCust, iStop) 826 | % The soln_insert_truck_customer method will take in the solution, the 827 | % jCustomer we will be inserting, and the place where the truck will be 828 | % inserted. 829 | % Inputer 830 | % solnIn The input solution 831 | % jCust the customer that will be inserted 832 | % iStop Where the truck will be placed 833 | 834 | % Local Variables 835 | % 836 | 837 | % Initialize the out solution 838 | solnOut = solnIn; 839 | 840 | % First insert values 1 to iStop - 1 841 | solnOut.anPart1 = solnIn.anPart1(1 : iStop - 1); 842 | 843 | % Next insert jCust 844 | solnOut.anPart1(iStop) = jCust; 845 | 846 | % Next insert the remaining values iStop to the end of the array 847 | solnOut.anPart1 = [ solnOut.anPart1 solnIn.anPart1(iStop : end)]; 848 | 849 | % Update anPart3 and anPart4 850 | nDroneCounter = 1; 851 | for nIndex = 1 : length(solnIn.anPart3) 852 | if solnIn.anPart3(nIndex) < 0 853 | nDroneCounter = nDroneCounter + 1; 854 | else 855 | if solnIn.anPart3(nIndex) >= iStop 856 | solnOut.anPart3(nIndex) = solnIn.anPart3(nIndex) + 1; 857 | end 858 | 859 | if solnIn.anPart4(nIndex) >= iStop 860 | solnOut.anPart4(nIndex) = solnIn.anPart4(nIndex) + 1; 861 | end 862 | end 863 | end 864 | end 865 | 866 | function[ solnOut ] = ... 867 | soln_add_drone_customer(solnIn, jCust, iDrone, iStopLeave, iStopReturn) 868 | % soln_add_drone_customer will take in the current solution data structure, 869 | % the customer we will be including, the drone that will be delivering to 870 | % it as well as where the drone will be departing from and arriving to. 871 | % Input 872 | % solnIn Current solution 873 | % jCust Customer we are inserting 874 | % iDrone The drone that will be delivering to that customer 875 | % iStopLeave The index from which the drone will be leaving (based off 876 | % of the indices from soln.anPart1) 877 | % iStopReturn The index where the drone will be returning (also based off 878 | % of the indices from soln.anPart1) 879 | 880 | % Output 881 | % solnOut The resutling solution. 882 | 883 | % Local variable 884 | % iPart3Drone The drone we are currently looking at in part 3 885 | 886 | 887 | % Find the drone we are looking for in solnIn.anPart3 888 | iPart3Drone = 1; 889 | nDroneCounter = 1; 890 | 891 | % Initialize the solnOut 892 | solnOut = solnIn; 893 | 894 | while (nDroneCounter ~= iDrone) 895 | % If we hit a separator (-1) increment the drone 896 | if (solnIn.anPart3(iPart3Drone) == -1) 897 | nDroneCounter = nDroneCounter + 1; 898 | end 899 | iPart3Drone = iPart3Drone + 1; 900 | end 901 | 902 | 903 | % Look for where iStopLeave fits in solnIn.anPart3 (start from 904 | % iPart3Drone) 905 | bPlaced = 0; 906 | iLeavePlacement = iPart3Drone; 907 | while (~bPlaced) 908 | % In the case that we want to assign drone n a customer, but there 909 | % are currently no drone n customers 910 | if iLeavePlacement > length(solnIn.anPart3) 911 | % Insert placeholder slot 912 | solnOut.anPart3(end + 1) = 0; 913 | solnOut.anPart2(end + 1) = 0; 914 | solnOut.anPart4(end + 1) = 0; 915 | end 916 | 917 | if (solnOut.anPart3(iLeavePlacement) < iStopLeave) 918 | % Then we place it in index iLeavePlacement 919 | bPlaced = 1; 920 | 921 | else 922 | % We iterate through 923 | iLeavePlacement = iLeavePlacement + 1; 924 | 925 | end 926 | end 927 | 928 | 929 | % Shift oclumns in Part 3 up by 1 930 | % Shift the columns at & abova where it goes to the right by 1 931 | solnOut.anPart3 = solnIn.anPart3(1 : iLeavePlacement - 1); 932 | 933 | % Insert the index it will be leaving from 934 | solnOut.anPart3(iLeavePlacement) = iStopLeave; 935 | 936 | % Insert the remaining values iStopLeave to the end of the array 937 | solnOut.anPart3 = [ solnOut.anPart3 solnIn.anPart3(iLeavePlacement : end) ]; 938 | 939 | % ---- 940 | % Shift columns in Part 2 up by 1 941 | % Shift the columns at & abova where it goes to the right by 1 942 | solnOut.anPart2 = solnIn.anPart2(1 : iLeavePlacement - 1); 943 | 944 | % Insert the customer jCust 945 | solnOut.anPart2(iLeavePlacement) = jCust; 946 | 947 | % Insert the remaining values iStopLeave to the end of the array 948 | solnOut.anPart2 = [ solnOut.anPart2 solnIn.anPart2(iLeavePlacement : end) ]; 949 | 950 | 951 | % ---- 952 | % Shift the columns in Part 4 up by 1 953 | % Shift the columns at & above where it goes to the right by 1 954 | solnOut.anPart4 = solnIn.anPart4(1 : iLeavePlacement - 1); 955 | 956 | % Insert the customer return index 957 | solnOut.anPart4( iLeavePlacement ) = iStopReturn; 958 | 959 | % Insert the remaining values iStopLeave to the end of the array 960 | solnOut.anPart4 = [ solnOut.anPart4 solnIn.anPart4(iLeavePlacement : end) ]; 961 | end 962 | 963 | function[ solnOut ] = soln_remove_drone_customer(solnIn, iCustomerToRemove) 964 | % This function removes the specified drone customer from the solution 965 | % (parts 2, 3, 4) 966 | solnOut.anPart1 = solnIn.anPart1; 967 | 968 | iIndexNew = 0; 969 | for iIndexOrig = 1 : length( solnIn.anPart2 ) 970 | if solnIn.anPart2( iIndexOrig ) ~= iCustomerToRemove 971 | iIndexNew = iIndexNew + 1; 972 | solnOut.anPart2( iIndexNew ) = solnIn.anPart2( iIndexOrig ); 973 | solnOut.anPart3( iIndexNew ) = solnIn.anPart3( iIndexOrig ); 974 | solnOut.anPart4( iIndexNew ) = solnIn.anPart4( iIndexOrig ); 975 | else 976 | iRemovalIndex = iIndexOrig; 977 | end 978 | end 979 | 980 | end 981 | 982 | function[ sPrime ] = neighbor( soln ) 983 | % The neighbor function will implement the well-known 2-opt method for 984 | % creating neighbors for our current solution s. Here, we must note that 985 | % our 2-opt method will switch two customers within the vector of Parts 1 986 | % and 2. The customers are selected randomly. Our 2-opt is a local search 987 | % heuristic. 988 | 989 | % INPUT 990 | % soln Current solution data structure. 991 | % OUTPUT 992 | % sPrime A neighbor to input solution soln 993 | 994 | % Variables 995 | % sPrime The soln.anPart1 which is the neighbor to our original 996 | % soln.anPart1 997 | % randIndex1 The index of the customer that will be switched out from 998 | % part 1 of the soln data structure 999 | % randIndex2 The index of the customer that will be switched out from 1000 | % part 2 of the soln data structure. 1001 | % nTemp A temporary variable that holds the customer at index 1002 | % randIndex1 1003 | 1004 | 1005 | % Randomly pick two customer indices from anPart1 1006 | randIndex1 = -1; 1007 | randIndex2 = -1; 1008 | 1009 | % Make sure that we don't pick the same two indices 1010 | while randIndex1 == randIndex2 1011 | randIndex1 = randi([2, length(soln.anPart1)-1]); 1012 | randIndex2 = randi([2, length(soln.anPart1)-1]); 1013 | end 1014 | 1015 | % fprintf("Before switching: \n") 1016 | % soln.anPart1 1017 | 1018 | % Switch their positions in soln.anPart1 1019 | nTemp = soln.anPart1(randIndex1); 1020 | soln.anPart1(randIndex1) = soln.anPart1(randIndex2); 1021 | soln.anPart1(randIndex2) = nTemp; 1022 | 1023 | % % fprintf("after switching: \n") 1024 | % soln.anPart1 1025 | 1026 | % Return the new neighbor to the input solution 1027 | sPrime = soln.anPart1; 1028 | 1029 | end 1030 | 1031 | function[ bValid ] = check_flight_validity( aafDistances, nLeaving, nVisiting, nReturning ) 1032 | % Check flight validity will take a matrix of distances, as well as the 1033 | % leaving, visiting, and returning nodes of a drone flight path. It will 1034 | % then determine if the flight distance of the drone falls within 1035 | % parameters. 1036 | 1037 | % Input 1038 | % aafDistances Matrix of distances between customer nodes 1039 | % nLeaving The node from which the drone will be leaving 1040 | % nVisiting The customer node which the drone visits 1041 | % nReturning The cusotmer that the drone returns to 1042 | % Output 1043 | % bValid Boolean variable that determines whether the flight 1044 | % path is valid 1045 | 1046 | % Variables 1047 | % maxDistance The maximum flight distance a drone can fly 1048 | % alpha The factor by which the flight distance is divided to 1049 | % account for drone speed 1050 | % a Distance from departure to the customer 1051 | % b Distnace from the customer to the returning depot 1052 | % fDrone Distance The total "distance" flown by the drone 1053 | 1054 | 1055 | % Initialize max flight distance, alpha, and boolean valid variable 1056 | maxDistance = 10; 1057 | alpha = 1.5; 1058 | bValid = 1; 1059 | 1060 | % Calculate the distance from departure to the customer 1061 | a = aafDistances(nLeaving+1, nVisiting+1); 1062 | 1063 | % Calculate the distance from the customer to the returning 1064 | b = aafDistances(nVisiting+1, nReturning+1); 1065 | 1066 | % Total drone distance 1067 | fDroneDistance = (a+b)/alpha; 1068 | 1069 | if fDroneDistance > maxDistance 1070 | bValid = 0; 1071 | end 1072 | end 1073 | 1074 | function[ fTotalWaitTime ] = f( aafDistances, soln, k) 1075 | % This function f is our wait time function. In this case, we treat 1076 | % the time = distance for our trucks and we calculate the time for the 1077 | % drones using a speed factor of 1.5 1078 | 1079 | % INPUT 1080 | % aafDistances Array of floats containing the distances between custs. 1081 | % soln Data structure of solutions in parts 1, 2, 3 and 4 1082 | % k Number of drones 1083 | % OUTPUT 1084 | % fTotalWaitTime Float of total wait time 1085 | 1086 | 1087 | % Variables 1088 | % alpha Our speed factor for the drones. 1089 | % aSoln Current solution 1090 | 1091 | 1092 | % Initialize constants 1093 | alpha = 1.5; 1094 | 1095 | 1096 | % Create temporary list of customers 1097 | anCustomers = soln.anPart1; 1098 | 1099 | % Create vector arrival and departure times for drones and trucks 1100 | aafDroneArrivalTime = zeros(k, length(aafDistances)); 1101 | aafTruckArrivalTime = zeros(1, length(aafDistances)); 1102 | aafTruckDepartureTime = zeros(1, length(aafDistances)); 1103 | 1104 | % Calculate the total wait time for the truck customers 1105 | iPrevious = 1; 1106 | for iCustomerIndex = 2 : length(soln.anPart1) % Here iCustomerIndex = 1 & = length(soln.anPart1) are the nodes 1107 | iDrone = 1; % Initialize drone counter 1108 | 1109 | % Calculate the arrival time of the truck to this node 1110 | % aafTruckArrivalTime(anCustomers(iCustomerIndex) + 1) = ... 1111 | % aafDistances( anCustomers(iCustomerIndex - 1) + 1, anCustomers(iCustomerIndex) + 1 ) ... 1112 | % + aafTruckArrivalTime( anCustomers(iCustomerIndex - 1) + 1); 1113 | aafTruckArrivalTime(anCustomers(iCustomerIndex) + 1) = ... 1114 | aafDistances( anCustomers(iCustomerIndex - 1) + 1, anCustomers(iCustomerIndex) + 1 ) ... 1115 | + aafTruckDepartureTime( anCustomers(iCustomerIndex - 1) + 1); 1116 | 1117 | % "Put this arrival times in aafTruckDepartureTimes and vector and 1118 | % then update them if they have a drone approaching that same 1119 | % node. So that if there is no drone going towards customer at 1120 | % iCustomerIndex, then they already have something in their truck 1121 | % arrival time vector. That is, we are assuming that no customer 1122 | % has a drone delivering to them. 1123 | % NOTE: ASSUME INSTANTANEOUS ARRIVAL AND DEPARTURE 1124 | aafTruckDepartureTime(anCustomers(iCustomerIndex) + 1) = ... 1125 | aafTruckArrivalTime(anCustomers(iCustomerIndex) + 1); 1126 | 1127 | 1128 | 1129 | for iReconveneIndex = 1 : length(soln.anPart4) 1130 | 1131 | % Check if we have an "X" there 1132 | if soln.anPart4(iReconveneIndex) == -1 1133 | iDrone = iDrone + 1; 1134 | end 1135 | 1136 | % Get the arrival time of the drones 1137 | if iCustomerIndex == soln.anPart4(iReconveneIndex) % This means there is a drone flying to this point 1138 | 1139 | % calculate distance from departure node to customer node 1140 | a = aafDistances( anCustomers(soln.anPart3(iReconveneIndex)) + 1, soln.anPart2(iReconveneIndex) + 1); 1141 | 1142 | % Calculate distance from customer node to arrival node 1143 | b = aafDistances( soln.anPart2(iReconveneIndex) + 1, anCustomers(soln.anPart4(iReconveneIndex)) + 1); 1144 | % b = aafDistances( soln.anPart2(iReconveneIndex) + 1, anCustomers(soln.anPart4(iReconveneIndex) + 1) ); 1145 | 1146 | 1147 | aafDroneArrivalTime(iDrone, anCustomers(soln.anPart4(iReconveneIndex)) + 1) = (a + b)/alpha; 1148 | 1149 | %%%% WORKS TILL HERE %%%% 1150 | 1151 | % if Truck is there before the drone, it must wait 1152 | if aafTruckArrivalTime(anCustomers(iCustomerIndex) + 1) < ... 1153 | max (aafDroneArrivalTime( :, anCustomers(iCustomerIndex) + 1 ) ) ... 1154 | + aafTruckDepartureTime(anCustomers(iPrevious) + 1) % added this addition bit 1155 | 1156 | aafTruckDepartureTime(anCustomers(iCustomerIndex) + 1) = ... 1157 | max (aafDroneArrivalTime( :, anCustomers(iCustomerIndex) + 1) ) + ... 1158 | aafTruckDepartureTime(anCustomers(iPrevious) + 1); 1159 | % do we need to add everything up to this point to the 1160 | % distance traveled by the drone?? 1161 | elseif aafTruckArrivalTime(anCustomers(iCustomerIndex) + 1) > max( aafDroneArrivalTime( :, iCustomerIndex) ) 1162 | % If the drone is there before the truck 1163 | aafTruckDepartureTime(anCustomers(iCustomerIndex) + 1) = aafTruckArrivalTime( anCustomers(iCustomerIndex) + 1); 1164 | end 1165 | 1166 | end 1167 | 1168 | end 1169 | iPrevious = iPrevious + 1; 1170 | end 1171 | 1172 | 1173 | % Get the total waiting time 1174 | fTotalWaitTime = aafTruckDepartureTime( soln.anPart1(end - 1) + 1 ); 1175 | 1176 | 1177 | % %% NOTES 1178 | % % WE MIGHT HAVE TO CREATE IF STATEMENT FOR WHAT TO RETURN. For example, 1179 | % % if the last customer is visited by a drone and arrives there before 1180 | % % the truck, we are not interested in waiting for the truck to get 1181 | % % there (since our drone would have already delivered to the customer). 1182 | % % In this case we don't want to return the aafTruckDepartureTime. 1183 | % 1184 | % % Potential Fix: in the case that we visit a customer and the drone 1185 | % % gets there faster, IF there is a customer to visit afterwards, THEN 1186 | % % we choose the higher time. ELSE (last customer), we take the minimum. 1187 | % % Look at the picture titled "ECA_fig1" 1188 | 1189 | end 1190 | 1191 | function[ bFeasible ] = check_feasibility( solnIn, C0, aafDistances ) 1192 | % This function will check the feasibility of the solution that is passed 1193 | % in. It will check that several requirements are satisfied: 1194 | % FOR DRONES: 1195 | % - Travel distance for each drone <= max 1196 | % - Leaving stop of drone (part 3) < returning stop of drone (part 4)** 1197 | % - No overlapping drone trips (we don't give a drone in the air a delivery) 1198 | % - for each slot in part 4, make sure entry in slot+1 of part 3 is 1199 | % >= the part 4 value 1200 | % GENERAL: 1201 | % - Make sure each customer is delivered to 1202 | % - Battery pack availability/battery life for consecutive trips 1203 | % - No out and back trips (covered by **) (part3 == part4) 1204 | % TRUCK: 1205 | % - truck must start and end at the depot 1206 | % Input 1207 | % solnIn Input solution 1208 | % C0 The customer locations 1209 | % aafDistances The distances between customer nodes 1210 | % Output 1211 | % bFeasible Boolean value that is true (1) if the solution is feasible; 1212 | % false (0) otherwise 1213 | 1214 | % Local variables 1215 | % maxDistance The max distance a drone can fly 1216 | % alpha Drone flight multiplier constant 1217 | % fDroneDistance The distance flown by a specific drone 1218 | 1219 | % Initialize drone flight multiplier constant 1220 | alpha = 1.5; 1221 | 1222 | % Initialize maxDistance variable 1223 | maxDistance = 10; 1224 | 1225 | % Initialize bFeasible to true 1226 | bFeasible = 1; 1227 | 1228 | % Create variable for all of the customers 1229 | anCustomers = solnIn.anPart1; 1230 | 1231 | % Make sure that drone isn't traveling back and forth 1232 | iCustIndex = 1; 1233 | while iCustIndex <= length(solnIn.anPart3) && bFeasible 1234 | if (solnIn.anPart3(iCustIndex) == solnIn.anPart4(iCustIndex) && solnIn.anPart3(iCustIndex) ~= -1) 1235 | bFeasible = 0; 1236 | end 1237 | 1238 | if (solnIn.anPart3(iCustIndex) == 1 && (solnIn.anPart4(iCustIndex) == length(solnIn.anPart1))) 1239 | bFeasible = 0; 1240 | end 1241 | iCustIndex = iCustIndex + 1; 1242 | end 1243 | 1244 | % Make sure that the travel distance for each drone is not too far 1245 | i = 1; 1246 | while i <= length(solnIn.anPart3) && bFeasible 1247 | fDroneDistance = 0; 1248 | if (solnIn.anPart3(i) ~= -1) 1249 | % Here, we will do the drone flight distance calculation the 1250 | % way we did it in the f() function. 1251 | 1252 | % Calculate the distance from departure to the customer 1253 | a = aafDistances( anCustomers(solnIn.anPart3(i)) + 1, solnIn.anPart2(i) + 1); 1254 | 1255 | % Calculate the distance from the customer to the arrival 1256 | b = aafDistances( solnIn.anPart2(i) + 1, anCustomers(solnIn.anPart4(i)) + 1); 1257 | 1258 | % Total Drone distance 1259 | fDroneDistance = (a+b)/alpha; 1260 | end 1261 | 1262 | if fDroneDistance > maxDistance 1263 | bFeasible = 0; 1264 | end 1265 | i = i+1; 1266 | end 1267 | end 1268 | 1269 | % Heuristics 1270 | function[ WeightInfo ] = weight_init() 1271 | % The weight_init() function will initialize the weights of all the 1272 | % heuristics available to us and return a structure that keeps track of 1273 | % scores, times, weights, gammas, and segments. 1274 | 1275 | % Input 1276 | % 1277 | 1278 | % Output 1279 | % WeightInfo The variable WeightInfo is a data structure with 1280 | % the following attributes: 1281 | % 1282 | % WeightInfo.afScores : pi vector which represents the heuristic scores 1283 | % WeightInfo.anTimes : theta vector which represents the number of 1284 | % times each heuristic was used 1285 | % WeightInfo.aafWeights: w_q,l array which represents the weight of 1286 | % heuristic q (col) used in segment l (row) 1287 | % WeightInfo.fGamma : a coefficient between 0 and 1 used to balance 1288 | % between the value of earlier weights and the 1289 | % new normalized scores 1290 | % .nSegmentCounter: current l 1291 | 1292 | % Variables 1293 | % numHeuristics The current number of heuristics implemented in 1294 | % the code; 1295 | 1296 | % Number of heuristics 1297 | numHeuristics = 4; 1298 | 1299 | % Initialize WeightInfo 1300 | WeightInfo.afScores = zeros(1, numHeuristics); % 2 heuristics 1301 | WeightInfo.anTimes = zeros(1, numHeuristics); 1302 | WeightInfo.aafWeights(1, :) = (1/numHeuristics) * ones(1, numHeuristics); 1303 | WeightInfo.fGamma = 0.2; 1304 | WeightInfo.nSegmentCounter = 1; 1305 | 1306 | end 1307 | 1308 | function[ WeightInfo ] = update_weights( WeightInfo, nHeuristic, f_sPrime, f_sBest, f_sCurr, iIter) 1309 | % In this function we update one slot of afScores and anTimes every time 1310 | % this function is called. But we only update aafWeights every k iterations 1311 | % (50, in this case) 1312 | 1313 | % Input 1314 | % WeightInfo The variable WeightInfo is a data structure with 1315 | % the following attributes: 1316 | % 1317 | % WeightInfo.afScores : pi vector which represents the heuristic scores 1318 | % WeightInfo.anTimes : theta vector which represents the number of 1319 | % times each heuristic was used 1320 | % WeightInfo.aafWeights: w_q,l array which represents the weight of 1321 | % heuristic q (col) used in segment l (row) 1322 | % WeightInfo.fGamma : a coefficient between 0 and 1 used to balance 1323 | % between the value of earlier weights and the 1324 | % new normalized scores 1325 | % .nSegmentCounter: current l; keeps track of the number of 1326 | % iterations 1327 | % Output 1328 | % 1329 | 1330 | % Local Variables 1331 | % k The segment length 1332 | 1333 | % Initialize the segment length 1334 | k = 50; 1335 | 1336 | % Get the number of segments 1337 | nDim = size(WeightInfo.aafWeights); 1338 | l = nDim(2); 1339 | 1340 | % Update the afScores of the nHeuristic 1341 | if f_sPrime < f_sBest 1342 | WeightInfo.afScores(nHeuristic) = WeightInfo.afScores(nHeuristic) + 2; 1343 | elseif f_sPrime <= f_sCurr 1344 | WeightInfo.afScores(nHeuristic) = WeightInfo.afScores(nHeuristic) + 1; 1345 | end 1346 | 1347 | % Update the anTimes 1348 | WeightInfo.anTimes(nHeuristic) = WeightInfo.anTimes(nHeuristic) + 1; 1349 | 1350 | % Check to see if the nSegmentCounter is a multiple of k = 50 1351 | % if mod(WeightInfo.nSegmentCounter, 50) == 0 1352 | % % Update the weights of the heurisitic 1353 | % WeightInfo.aafWeights(:, l+1) = ... 1354 | % WeightInfo.aafWeights(:, l)*(1 - WeightInfo.fGamma) + ... 1355 | % WeightInfo.fGamma*(WeightInfo.afScores / WeightInfo.anTimes); 1356 | % end 1357 | if mod(iIter, 50) == 0 1358 | % Update the weights of the heurisitic 1359 | WeightInfo.aafWeights(l+1, :) = ... 1360 | WeightInfo.aafWeights(l, :)*(1 - WeightInfo.fGamma) + ... 1361 | WeightInfo.fGamma*(WeightInfo.afScores / WeightInfo.anTimes); 1362 | 1363 | % Update the segment counter 1364 | WeightInfo.nSegmentCounter = WeightInfo.nSegmentCounter + 1; 1365 | end 1366 | end 1367 | 1368 | function[ nHeuristic ] = select_heuristic( WeightInfo ) 1369 | % The select_heuristic function will take in a WeightInfo variable with a 1370 | % structure with the attributes: scores, times, weights, gamma, and segment 1371 | % counter. It will calculate the probabilities of the heuristic being 1372 | % selected using the WeightInfo.aafWeights attribute. 1373 | 1374 | % Input 1375 | % WeightInfo Structure with attributes afScores, anTimes, 1376 | % aafWeights, fGamma, and nSegmentCounter 1377 | 1378 | % Output 1379 | % nHeuristic Returns the index associated with the chosen heuristic. 1380 | % For example, 1 = 2-Opt Heuristic, 2 = 3-opt Heuristic, 1381 | % 3 = Greedy Assignment Heuristic, etc 1382 | 1383 | % Variables 1384 | % afProbabilities Vector of probabilities of each heuristic being selected 1385 | % fHeuristicWeightSum The sum of the weights from 1386 | % WeightInfo.nSegmentCounter 1387 | % anSize The dimensions of the aafWeights attribute 1388 | 1389 | % Calculate the size 1390 | anSize = size(WeightInfo.aafWeights); 1391 | 1392 | % Calculate vector of probabilities 1393 | fHeuristicWeightSum = 0; 1394 | for i = 1 : anSize(2) 1395 | fHeuristicWeightSum = fHeuristicWeightSum + ... 1396 | WeightInfo.aafWeights(WeightInfo.nSegmentCounter, i); 1397 | end 1398 | 1399 | afProbabilities = ... 1400 | WeightInfo.aafWeights(WeightInfo.nSegmentCounter, :) / fHeuristicWeightSum; 1401 | 1402 | % Split interval 1403 | afBoundaries = [0]; 1404 | 1405 | for i = 1 : length(afProbabilities) 1406 | afBoundaries(i+1) = afBoundaries(i) + afProbabilities(i); 1407 | end 1408 | 1409 | % Randomly select one 1410 | fRand = rand(); 1411 | 1412 | % Map the rand # to nHeuristic 1413 | for i = 1 : length(afBoundaries) 1414 | if fRand >= afBoundaries(i) 1415 | nHeuristic = i; 1416 | end 1417 | end 1418 | end 1419 | 1420 | % Heuristic 1 1421 | function[ solnNew ] = apply_heuristic_2_opt( solnCurr, C0, aafDistances ) 1422 | % This function will implement the 2-opt heuristic which will swap two 1423 | % customers within the vector of Parts 1 and 2. The customers are selected 1424 | % randomly for the 2-Opt method. Chainging the values in Parts 1 and 2 1425 | % while keeping the values in Parts 3 and 4 unchanged may lead to an 1426 | % infeasible solution due to the flight range constraint. In the case of 1427 | % infeasibility, we use the DRONE PLANNER HEURISTIC. 1428 | 1429 | % Input 1430 | % solnCurr The current solution 1431 | % C0 The locations (coordinates) of the customers 1432 | % aafDistances The distances between customer node 1433 | 1434 | % Output 1435 | % solnBest The best solution our algorithm was able to determine 1436 | 1437 | % Variables 1438 | % nCustA Randomly selected customer integer 1439 | % nCustB Randomly selected customer integer 1440 | % nCustomers Number of customers 1441 | % nIndA1 Index of customer A if in part 1 (-1 if not in part 1) 1442 | % nIndB1 Index of customer B if in part 1 (-1 if not in part 1) 1443 | % nIndA2 Index of customer A if in part 2 (-1 if not in part 1) 1444 | % nIndB2 Index of customer B if in part 2 (-1 if not in part 1) 1445 | 1446 | % Get the number of customers 1447 | nCustomers = length(solnCurr.anPart1) - 2; 1448 | 1449 | for i = 1 : length(solnCurr.anPart2) 1450 | if solnCurr.anPart2(i) ~= -1 1451 | nCustomers = nCustomers + 1; 1452 | end 1453 | end 1454 | 1455 | % Get customers to be swapped 1456 | nCustA = randi([1, nCustomers]); % Ignore the 2 zeros 1457 | nCustB = randi([1, nCustomers]); 1458 | 1459 | while nCustB == nCustA 1460 | nCustB = randi([1, nCustomers]); 1461 | end 1462 | 1463 | % Find which slot in either part1 or part2 each of A & B are at 1464 | % Part 1 1465 | nIndA1 = -1; 1466 | nIndB1 = -1; 1467 | for nIndex = 1 : length(solnCurr.anPart1) 1468 | if nCustA == solnCurr.anPart1(nIndex) 1469 | nIndA1 = nIndex; 1470 | end 1471 | 1472 | if nCustB == solnCurr.anPart1(nIndex) 1473 | nIndB1 = nIndex; 1474 | end 1475 | end 1476 | 1477 | % Part 2 1478 | nIndA2 = -1; 1479 | nIndB2 = -1; 1480 | for nIndex = 1 : length(solnCurr.anPart2) 1481 | if nCustA == solnCurr.anPart2(nIndex) 1482 | nIndA2 = nIndex; 1483 | end 1484 | 1485 | if nCustB == solnCurr.anPart2(nIndex) 1486 | nIndB2 = nIndex; 1487 | end 1488 | end 1489 | 1490 | % Create solnNew and swap the two variables 1491 | solnNew = solnCurr; 1492 | 1493 | % customer a in part 1 and customer b in part 2 1494 | if nIndA1 ~= -1 && nIndB2 ~= -1 1495 | tempCust = solnNew.anPart1(nIndA1); 1496 | solnNew.anPart1(nIndA1) = solnNew.anPart2(nIndB2); 1497 | solnNew.anPart2(nIndB2) = tempCust; 1498 | 1499 | % customer a in part 2 and customer b in part 1 1500 | elseif nIndA2 ~= -1 && nIndB1 ~= -1 1501 | tempCust = solnNew.anPart2(nIndA2); 1502 | solnNew.anPart2(nIndA2) = solnNew.anPart1(nIndB1); 1503 | solnNew.anPart1(nIndB1) = tempCust; 1504 | 1505 | % customer a and b are in part 1 1506 | elseif nIndA1 ~= -1 && nIndB1 ~= -1 1507 | tempCust = solnNew.anPart1(nIndA1); 1508 | solnNew.anPart1(nIndA1) = solnNew.anPart1(nIndB1); 1509 | solnNew.anPart1(nIndB1) = tempCust; 1510 | 1511 | % customer a and b are in part 2 1512 | elseif nIndA2 ~= -1 && nIndB2 ~= -1 1513 | tempCust = solnNew.anPart2(nIndA2); 1514 | solnNew.anPart2(nIndA2) = solnNew.anPart2(nIndB2); 1515 | solnNew.anPart2(nIndB2) = tempCust; 1516 | end 1517 | 1518 | 1519 | % Check the feasibility 1520 | bFeasible = check_feasibility(solnNew, C0, aafDistances); 1521 | if bFeasible == 0 1522 | solnNew = apply_heuristic_7_drone_planner(solnNew, C0, aafDistances); 1523 | 1524 | if check_feasibility(solnNew, C0, aafDistances) == 0 1525 | solnNew = solnCurr; 1526 | end 1527 | end 1528 | end 1529 | 1530 | % Heuristic 2 1531 | function[ solnNew ] = apply_heuristic_7_drone_planner(solnIn, C0, aafDistances) 1532 | % This function will apply the drone planner heuristic. 1533 | 1534 | % Input 1535 | % solnIn The input solution structure 1536 | % C0 The locations (coordinates) of the customers 1537 | % aafDistances Matrix of all distances between nodes 1538 | % Output 1539 | % solnNew The new solution created from the drone planner 1540 | % heuristic 1541 | 1542 | % Variables 1543 | % P_j Nested structure that contains every (i, s) combination 1544 | % where i, s in V (set of all nodes in the network) && 1545 | % the flight from i to j to s is in range (< L) && i and 1546 | % s are truck customers && i is served before s 1547 | % nDrones Number of drones 1548 | 1549 | solnNew = solnIn; 1550 | % Count the number of drones 1551 | if isempty(solnIn.anPart2) 1552 | nDrones = 0; 1553 | else 1554 | nDrones = 1; 1555 | i = 1; 1556 | while i < length(solnIn.anPart2) 1557 | if solnIn.anPart2(i) == -1 1558 | nDrones = nDrones + 1; 1559 | end 1560 | i = i + 1; 1561 | end 1562 | end 1563 | 1564 | % Remove all UAV flights from S_out 1565 | iCustomer = 1; 1566 | iDrone = 1; 1567 | while iCustomer <= length(solnNew.anPart2) && iDrone <= nDrones 1568 | if solnNew.anPart2(iCustomer) == -1 1569 | iDrone = iDrone + 1; 1570 | else 1571 | solnNew.anPart3(iCustomer) = 0; 1572 | solnNew.anPart4(iCustomer) = 0; 1573 | end 1574 | iCustomer = iCustomer + 1; 1575 | end 1576 | 1577 | 1578 | % Create the P_j structure 1579 | n = length(solnIn.anPart1) - 1; 1580 | 1581 | iDroneCustomer = 1; 1582 | for iDrone = 1 : nDrones 1583 | while iDroneCustomer < length(solnIn.anPart2) && solnIn.anPart2(iDroneCustomer) ~= -1 1584 | iRow = 1; 1585 | % P_j(iDrone).Customer(solnIn.anPart2(iDroneCustomer)).aanCust = zeros(n*(n+1)/2, 2); % the number of possible permutations 1586 | for iLeaving = 1 : length(solnIn.anPart1) - 1 % subtract 1 because it drone can't leave from last spot 1587 | for sReturning = iLeaving + 1 : length(solnIn.anPart1) 1588 | % Only add solution if it is feasible 1589 | if check_flight_validity(aafDistances, solnIn.anPart1(iLeaving), solnIn.anPart2(iDroneCustomer), solnIn.anPart1(sReturning)) 1590 | % P_j(iDrone).Customer(solnIn.anPart2(iDroneCustomer)).aanCust(iRow, :) = ... 1591 | % [solnIn.anPart1(iLeaving), solnIn.anPart1(sReturning)]; 1592 | P_j(iDrone).Customer(solnIn.anPart2(iDroneCustomer)).aanCust(iRow, :) = ... 1593 | [iLeaving, sReturning]; 1594 | iRow = iRow + 1; 1595 | end 1596 | 1597 | end 1598 | 1599 | end 1600 | iDroneCustomer = iDroneCustomer + 1; 1601 | end 1602 | iDroneCustomer = iDroneCustomer + 1; 1603 | end 1604 | 1605 | % Run algorithm 1606 | for iteration = 1 : 10 1607 | P_jCopy = P_j; 1608 | iDrone = 1; 1609 | bDone = 0; 1610 | iDroneCustomer = 1; 1611 | 1612 | while iDrone <= nDrones && bDone ~= 1 1613 | while iDroneCustomer < length(solnNew.anPart2) && solnNew.anPart2(iDroneCustomer) ~= -1 && bDone ~= -1 1614 | % Randomly pick (i, s) from P_c (if possible) 1615 | % fprintf("iDrone: %d\n", iDrone) 1616 | % fprintf("Customer: %d\n", solnNew.anPart2(iDroneCustomer)) 1617 | anDimensions = size(P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust); 1618 | 1619 | if anDimensions(1) == 0 1620 | bDone = 1; 1621 | else 1622 | nRows = anDimensions(1); 1623 | 1624 | nRandRow = randi(nRows); 1625 | nRandi = P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust(nRandRow, 1); 1626 | nRands = P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust(nRandRow, 2); 1627 | 1628 | 1629 | % Assign launch i and reconvene s locations to customer j 1630 | solnNew.anPart3(iDroneCustomer) = nRandi; 1631 | solnNew.anPart4(iDroneCustomer) = nRands; 1632 | 1633 | % Initialize P_jCopy2 to be the same thing as P_jCopy 1634 | P_jCopy2 = P_jCopy; 1635 | 1636 | % Update P_jCopy according to the previously assigned flights to UAV_u 1637 | iTempRow = 1; 1638 | for iRow = 1 : nRows 1639 | i = P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust(iRow, 1); 1640 | s = P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust(iRow, 2); 1641 | 1642 | if i < nRandi && s <= nRandi 1643 | P_jCopy2(iDrone).Customer(solnIn.anPart2(iDroneCustomer)).aanCust(iTempRow, :) = ... 1644 | [i, s]; 1645 | iTempRow = iTempRow + 1; 1646 | elseif i >= nRands && s > nRandi 1647 | P_jCopy2(iDrone).Customer(solnIn.anPart2(iDroneCustomer)).aanCust(iTempRow, :) = ... 1648 | [i, s]; 1649 | iTempRow = iTempRow + 1; 1650 | else 1651 | bOk = 0; 1652 | end 1653 | 1654 | anDimensions = size(P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust); 1655 | if iRow == nRows && anDimensions(1) == 0 1656 | P_jCopy2 = P_jCopy; 1657 | end 1658 | 1659 | end 1660 | 1661 | % Actually update P_jCopy 1662 | P_jCopy(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust = P_jCopy2(iDrone).Customer(solnNew.anPart2(iDroneCustomer)).aanCust; 1663 | 1664 | % Iterate iDroneCustomer 1665 | iDroneCustomer = iDroneCustomer + 1; 1666 | end 1667 | end 1668 | iDrone = iDrone + 1; 1669 | end 1670 | end 1671 | 1672 | %% Potential issue for feasibility 1673 | % Our drone planner will likely return parts 3 and 4 out of order 1674 | % (making it pop up as infeasible in our check_feasibility function) 1675 | % so we can fix this by fixing our output so that our drone routes are 1676 | % in proper order 1677 | end 1678 | 1679 | % Heuristic 3 1680 | function[ solnNew ] = apply_heuristic_3_opt( solnCurr, C0, aafDistances ) 1681 | % This function will implement the 2-opt heuristic which will swap two 1682 | % customers within the vector of Parts 1 and 2. The customers are selected 1683 | % randomly for the 2-Opt method. Chainging the values in Parts 1 and 2 1684 | % while keeping the values in Parts 3 and 4 unchanged may lead to an 1685 | % infeasible solution due to the flight range constraint. In the case of 1686 | % infeasibility, we use the DRONE PLANNER HEURISTIC. 1687 | 1688 | % Input 1689 | % solnCurr The current solution 1690 | % C0 The locations (coordinates) of the customers 1691 | % aafDistances The distances between customer node 1692 | 1693 | % Output 1694 | % solnBest The best solution our algorithm was able to determine 1695 | 1696 | % Variables 1697 | % nCustA Randomly selected customer integer 1698 | % nCustB Randomly selected customer integer 1699 | % nCustC Randomly selected customer integer 1700 | % nCustomers Number of customers 1701 | % nIndA1 Index of customer A if in part 1 (-1 if not in part 1) 1702 | % nIndB1 Index of customer B if in part 1 (-1 if not in part 1) 1703 | % nIndA2 Index of customer A if in part 2 (-1 if not in part 1) 1704 | % nIndB2 Index of customer B if in part 2 (-1 if not in part 1) 1705 | 1706 | % Get the number of customers 1707 | nCustomers = length(solnCurr.anPart1) - 2; 1708 | 1709 | for i = 1 : length(solnCurr.anPart2) 1710 | if solnCurr.anPart2(i) ~= -1 1711 | nCustomers = nCustomers + 1; 1712 | end 1713 | end 1714 | 1715 | % Get customers to be swapped 1716 | nCustA = randi([1, nCustomers]); % Ignore the 2 zeros 1717 | nCustB = randi([1, nCustomers]); 1718 | nCustC = randi([1, nCustomers]); 1719 | 1720 | while nCustB == nCustA 1721 | nCustB = randi([1, nCustomers]); 1722 | end 1723 | 1724 | while nCustC == nCustA || nCustC == nCustB 1725 | nCustC = randi([1, nCustomers]); 1726 | end 1727 | 1728 | % Find which slot in either part1 or part2 each of A & B & C are at 1729 | nPartA = 2; 1730 | nIndA = -1; 1731 | nPartB = 2; 1732 | nIndB = -1; 1733 | nPartC = 2; 1734 | nIndC = -1; 1735 | 1736 | for nIndex = 1 : length(solnCurr.anPart1) 1737 | if nCustA == solnCurr.anPart1(nIndex) 1738 | nPartA = 1; 1739 | nIndA = nIndex; 1740 | end 1741 | 1742 | if nCustB == solnCurr.anPart1(nIndex) 1743 | nPartB = 1; 1744 | nIndB = nIndex; 1745 | end 1746 | 1747 | if nCustC == solnCurr.anPart1(nIndex) 1748 | nPartC = 1; 1749 | nIndC = nIndex; 1750 | end 1751 | end 1752 | 1753 | 1754 | for nIndex = 1 : length(solnCurr.anPart2) 1755 | if nCustA == solnCurr.anPart2(nIndex) 1756 | nIndA = nIndex; 1757 | end 1758 | 1759 | if nCustB == solnCurr.anPart2(nIndex) 1760 | nIndB = nIndex; 1761 | end 1762 | 1763 | if nCustC == solnCurr.anPart2(nIndex) 1764 | nIndC = nIndex; 1765 | end 1766 | end 1767 | 1768 | % customer a in part 1 and customer b in part 1 1769 | solnNew = solnCurr; 1770 | 1771 | % Move customer C into customer A 1772 | if nPartA == 1 1773 | solnNew.anPart1(nIndA) = nCustC; 1774 | 1775 | else 1776 | solnNew.anPart2(nIndA) = nCustC; 1777 | end 1778 | 1779 | % Move customer A into customer B 1780 | if nPartB == 1 1781 | solnNew.anPart1(nIndB) = nCustA; 1782 | else 1783 | solnNew.anPart2(nIndB) = nCustA; 1784 | end 1785 | 1786 | % Move customer B into customer C 1787 | if nPartC == 1 1788 | solnNew.anPart1(nIndC) = nCustB; 1789 | else 1790 | solnNew.anPart2(nIndC) = nCustB; 1791 | end 1792 | 1793 | 1794 | % Check the feasibility 1795 | bFeasible = check_feasibility(solnNew, C0, aafDistances); 1796 | if bFeasible == 0 1797 | solnNew = apply_heuristic_7_drone_planner(solnNew, C0, aafDistances); 1798 | 1799 | if check_feasibility(solnNew, C0, aafDistances) == 0 1800 | solnNew = solnCurr; 1801 | end 1802 | end 1803 | end 1804 | 1805 | % Heuristic 4 1806 | function[ solnNew ] = apply_heuristic_4_Greedy_Assignment(solnIn, C0, aafDistances) 1807 | % This function will apply the greedy assignment heuristic to the input 1808 | % solution. This will make a list of customers that are NOT at leaving OR 1809 | % returning truck stops of drone flights (customers not at spots in parts 3 1810 | % or 4). It will then pick one at random and choose the best spot for the 1811 | % chosen customer. 1812 | % Input 1813 | % solnIn The input solution 1814 | % C0 The set of customers 1815 | % aafDistances Float matrix; calculated distances between nodes 1816 | % Output 1817 | % solnNew The output solution 1818 | 1819 | % Local Variables 1820 | % bLeavingOrReturning Array of boolean vectors. 1's indicate 1821 | % that they are rendezvous locations; 1822 | % 0's indicate that they are not 1823 | % EX. [1 0 1] implies drone leaves cust 1 & goes back to cust 3 1824 | % anTruckCustomers Array of truck customers. This excludes 1825 | % the depots. 1826 | 1827 | % Count the number of drones 1828 | if isempty(solnIn.anPart2) 1829 | nDrones = 0; 1830 | else 1831 | nDrones = 1; 1832 | i = 1; 1833 | while i < length(solnIn.anPart2) 1834 | if solnIn.anPart2(i) == -1 1835 | nDrones = nDrones + 1; 1836 | end 1837 | i = i + 1; 1838 | end 1839 | end 1840 | 1841 | % Initialize values 1842 | solnNew = solnIn; 1843 | fs_best = f(aafDistances, solnIn, nDrones); 1844 | 1845 | % Create a list of customers served by truck or UAV, but not served as 1846 | % rendevous locations 1847 | bLeavingOrReturning = zeros(1, length(C0.x) - 2); % -2 for depots 1848 | 1849 | % Create array of truck customers 1850 | anTruckCustomers = solnIn.anPart1(2 : end - 1); 1851 | 1852 | % Loop through parts 3 and 4. Then mark each of those as a 1 in the boolean array 1853 | for iPart3 = 1 : length(solnIn.anPart3) 1854 | % if NOT switching to another drone and NOT a depot 1855 | if solnIn.anPart3(iPart3) ~= -1 && solnIn.anPart1(solnIn.anPart3(iPart3)) ~= 0 1856 | % Set their boolean value to true (indicating a rendezvous loc) 1857 | anTruckCustomers(solnIn.anPart3(iPart3) ) 1858 | bLeavingOrReturning(anTruckCustomers(solnIn.anPart3(iPart3) - 1)) = 1; 1859 | end 1860 | 1861 | if solnIn.anPart4(iPart3) ~= -1 && solnIn.anPart1(solnIn.anPart4(iPart3)) ~= 0 1862 | % Set their boolean value to true (indicating a rendezvous loc) 1863 | anTruckCustomers(solnIn.anPart4(iPart3) - 1) 1864 | bLeavingOrReturning(anTruckCustomers(solnIn.anPart4(iPart3) - 1)) = 1; 1865 | end 1866 | end 1867 | 1868 | % Obtain list of all potential positions 1869 | anValidCustomers = [anTruckCustomers]; 1870 | 1871 | % Add on the customers from part 2 that are not -1 1872 | for iCustomer = 1 : length(solnIn.anPart2) 1873 | if solnIn.anPart2(iCustomer) ~= -1 1874 | anValidCustomers = [anValidCustomers solnIn.anPart2(iCustomer)]; 1875 | end 1876 | end 1877 | 1878 | % Start taking away from that list if they are a rendezvous location 1879 | % anValidCustomersCopy = anValidCustomers; 1880 | % for nCust = anValidCustomersCopy 1881 | % if bLeavingOrReturning(nCust) == 1 1882 | % anValidCustomers(nCust) = []; 1883 | % end 1884 | % end 1885 | 1886 | anValidCustomers = anValidCustomers(bLeavingOrReturning > 0); 1887 | 1888 | % Select a customer randomly remove from the list of valid customers 1889 | randIndex = randperm(length(anValidCustomers), 1); 1890 | nRandCust = anValidCustomers(randIndex); 1891 | 1892 | % Remove it from its current route 1893 | % Select customer c_j in the list & remove it from truck route 1894 | solnRemovedCustomer = soln_remove_truck_customer(solnIn, nRandCust); 1895 | 1896 | % Check all potential positions in truck route 1897 | for iTruckStop = 2 : length(solnIn.anPart1) - 1 1898 | % Insert the customer in the truck route 1899 | insertedTruckSoln = ... 1900 | soln_insert_truck_customer(solnRemovedCustomer, nRandCust, iTruckStop); 1901 | 1902 | % Check feasibility 1903 | % Totally checking feasibility, yep looks super great 1904 | bFeasible = check_feasibility(insertedTruckSoln, C0, aafDistances); 1905 | 1906 | % Calculate the total waiting time 1907 | fTruckInsertionWaitingTime = f( aafDistances, insertedTruckSoln, nDrones ); 1908 | 1909 | % Compare this s to our original s_out 1910 | if bFeasible && (fTruckInsertionWaitingTime < fs_best) 1911 | solnNew = insertedTruckSoln; 1912 | fs_best = fTruckInsertionWaitingTime; 1913 | end 1914 | end 1915 | 1916 | indexindexindex = 1; 1917 | % Check all potential positions in drone route 1918 | for iDrone = 1 : nDrones 1919 | for iStopLeave = 1 : length(solnRemovedCustomer.anPart1) 1920 | for iStopReturn = iStopLeave + 1 : length(solnRemovedCustomer.anPart1) 1921 | % Insert the customer in the truck route 1922 | insertedDroneSoln = ... 1923 | soln_add_drone_customer(solnRemovedCustomer,... 1924 | nRandCust, iDrone, iStopLeave, iStopReturn); 1925 | 1926 | % Check feasibility 1927 | bFeasible = check_feasibility(insertedDroneSoln, C0, aafDistances); 1928 | 1929 | % Let's check what it looks like 1930 | % hold on; 1931 | % plot_route( C0, insertedDroneSoln, indexindexindex) 1932 | % hold off; 1933 | 1934 | % Calculate the total waiting time 1935 | % note: when calculating wait times, we are using 1936 | % the CURRENT soln.anPart1 1937 | fDroneInsertionWaitingTime = f( aafDistances, insertedDroneSoln, nDrones); 1938 | 1939 | % Compare this to the solnOut 1940 | if bFeasible && (fDroneInsertionWaitingTime < fs_best) 1941 | solnNew = insertedDroneSoln; 1942 | fs_best = fDroneInsertionWaitingTime; 1943 | end 1944 | 1945 | indexindexindex = indexindexindex + 1; 1946 | end 1947 | end 1948 | end 1949 | end 1950 | 1951 | % Heuristic 5 1952 | function[ solnNew ] = apply_heuristic_5_Origin_Destination(solnIn, C0, aafDistances, k) 1953 | % The origin destination heuristic will take in a current solution and 1954 | % calculate the distance. It will create a list of all customers served at 1955 | % rendezvous locations between truck & UAV (opposite of greedy assignment), 1956 | % select a customer i randomly from the list and remove it from its current 1957 | % route. It will then look at every potential position in truck route to 1958 | % insert customer i and save that as s'. It will then apply drone planner 1959 | % on that solution and check if the solution is feasible. 1960 | % Input 1961 | % k The number of drones 1962 | % Output 1963 | % 1964 | 1965 | % Local Variable 1966 | % bLeavingOrReturning boolean array; 1 if customer is rendevous 1967 | % location, 0 otherwise 1968 | % 1969 | 1970 | % Calculate distance of input solution 1971 | fSolnIn = f(aafDistances, solnIn, k); 1972 | 1973 | % Store it as the best 1974 | fSolnBet = fSolnIn; 1975 | 1976 | % Create list of all non-rendezvous locations 1977 | bLeavingOrReturning = zeros(1, length(C0.x) - 2); %-2 for depots 1978 | 1979 | % Create array of truck customers 1980 | % anTruckCustomers = solnIn.anPart1(2 : end - 1); 1981 | anListTruckCust = []; 1982 | 1983 | % Loop through parts 3 and 4. Mark each of those as a 1 in bool array 1984 | nIndex = 1; 1985 | for iPart3 = 1 : length(solnIn.anPart3) 1986 | % if NOT switching to another drone and NOT a depot 1987 | if solnIn.anPart3(iPart3) ~= -1 && solnIn.anPart1(solnIn.anPart3(iPart3)) ~= 0 1988 | % Set their boolean value to true (indicating a rendezvous loc) 1989 | % anTruckCustomers(solnIn.anPart3(iPart3)) 1990 | solnIn.anPart1(solnIn.anPart3(iPart3)) 1991 | % bLeavingOrReturning(solnIn.anPart1(solnIn.anPart3(iPart3) - 1)) = 1; 1992 | bLeavingOrReturning(solnIn.anPart1(solnIn.anPart3(iPart3))) = 1; 1993 | 1994 | % Add to list of rendezvous truck locations 1995 | anListTruckCust(nIndex) = solnIn.anPart1(solnIn.anPart3(iPart3)); 1996 | 1997 | % Increment index 1998 | nIndex = nIndex + 1; 1999 | end 2000 | 2001 | % if NOT switching to another drone and NOT a depot 2002 | if solnIn.anPart4(iPart3) ~= -1 && solnIn.anPart1(solnIn.anPart4(iPart3)) ~= 0 2003 | % Set their boolean value to true (indicating a rendevous loc) 2004 | % anTruckCustomers(solnIn.anPart4(iPart3) - 1) 2005 | solnIn.anPart1(solnIn.anPart4(iPart3)) 2006 | % bLeavingOrReturning(solnIn.anPart1(solnIn.anPart4(iPart3) - 1)) = 1; 2007 | bLeavingOrReturning(solnIn.anPart1(solnIn.anPart4(iPart3))) = 1; 2008 | 2009 | % Add to list of rendezvous truck locations 2010 | anListTruckCust(nIndex) = solnIn.anPart1(solnIn.anPart4(iPart3)); 2011 | 2012 | % Increment index 2013 | nIndex = nIndex + 1; 2014 | end 2015 | end 2016 | 2017 | % % Obtain list of customer rendezvous locations 2018 | % anValidCustomers = [anTruckCustomers]; 2019 | % 2020 | % % Start taking away from that list if they are not rendezvous locations 2021 | % anValidCustomersCopy = anValidCustomers; 2022 | % for nCust = anValidCustomersCopy 2023 | % if bLeavingOrReturning(nCust) == 1 2024 | % anValidCustomers(nCust) = []; 2025 | % end 2026 | % end 2027 | 2028 | % Select a customer randomly from the list & remove from its route 2029 | randIndex = randi([1 length(anListTruckCust)]); 2030 | nRandCust = anListTruckCust(randIndex); 2031 | % randIndex = randperm(length(anListTruckCust), 1); 2032 | % nRandCust = anValidCustomers(randIndex); 2033 | 2034 | % Select customer c_j in the list & remove it from truck route 2035 | solnRemovedCustomer = soln_remove_truck_customer(solnIn, nRandCust); 2036 | 2037 | % Check all potential positions in truck route 2038 | for iTruckStop = 2 : length(solnIn.anPart1) - 1 2039 | % Insert the customer in the truck route 2040 | insertedTruckSoln = ... 2041 | soln_insert_truck_customer(solnRemovedCustomer, nRandCust, iTruckStop); 2042 | 2043 | % Apply drone planner heuristic 2044 | 2045 | 2046 | % Check feasibility 2047 | % Totally checking feasibility, yep looks super great 2048 | bFeasible = check_feasibility(insertedTruckSoln, C0, aafDistances); 2049 | 2050 | % Calculate the total waiting time 2051 | fTruckInsertionWaitingTime = f( aafDistances, insertedTruckSoln, k ); 2052 | 2053 | % Compare this s to our original s_out 2054 | if bFeasible && (fTruckInsertionWaitingTime < fs_best) 2055 | solnNew = insertedTruckSoln; 2056 | fs_best = fTruckInsertionWaitingTime; 2057 | end 2058 | end 2059 | 2060 | end --------------------------------------------------------------------------------