-
Notifications
You must be signed in to change notification settings - Fork 2
/
mheuristics.h
622 lines (474 loc) · 22.9 KB
/
mheuristics.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
#ifndef MHEURISTICS_H
#define MHEURISTICS_H
/* ------------------ NEIGHBORHOODS ------------------ */
// Exchange two cities from the route randomly
std::pair<int, int> opt2(int id) {
int a = rand() % (gang[id].route.size() - 1) + 1;
int b = rand() % (gang[id].route.size() - 1) + 1;
std::swap(gang[id].route[a], gang[id].route[b]);
return std::make_pair(a, b);
}
// Exchange four cities from the route randomly
std::pair<std::pair<int, int>, std::pair<int , int> > opt4(int id) {
int a = rand() % (gang[id].route.size() - 1) + 1;
int b = rand() % (gang[id].route.size() - 1) + 1;
int c = rand() % (gang[id].route.size() - 1) + 1;
int d = rand() % (gang[id].route.size() - 1) + 1;
std::swap(gang[id].route[a], gang[id].route[b]);
std::swap(gang[id].route[c], gang[id].route[d]);
auto p1 = std::make_pair(a, b);
auto p2 = std::make_pair(c, d);
return std::make_pair(p1,p2);
}
// Removes a city from the Thiefs route
std::pair<int, Node> removeCity(int id){
// Pick a random city
int cityId = rand() % (gang[id].route.size() - 1) + 1;
auto p = std::make_pair(cityId, gang[id].route[cityId]); //Salva o indice no vetor de rotas e os dados do nó
// Return the items taken from that city - if there is any
for(int i = 0; i < gang[id].route[cityId].items.size(); i++){
// Item index
int pos = gang[id].route[cityId].items[i];
// The item removed is free to be taken by other thief now.
// Also, the weight it had on the @globalCapacity is now removed - since we returned it.
gangCapacity -= items[pos].weight;
gang[id].capacity -= items[pos].weight;
items[pos].thief = -1;
}
// Remove it from the route
gang[id].route.erase(gang[id].route.begin() + cityId);
// Returns index and node
return p;
}
// Removes an item from the Thiefs route
std::pair<int, int> removeItem(int id) {
int cont = 0;
int cityId;
// Draw a city with more than 1 item, trying at most 100 times to do so.
do {
cityId = rand() % (gang[id].route.size() - 1) + 1;
cont++;
if(cont > 100) return std::make_pair(-1, -1); // Flag indicating it wasn't possible to draw a city with at least 1 item ( pair.first = -1 )
} while(gang[id].route[cityId].items.size() <= 1);
// Pick a random item from the chosen city
int itemPos = rand() % gang[id].route[cityId].items.size();
int itemId = gang[id].route[cityId].items[itemPos];
// Remove it from the Thiefs route
gang[id].route[cityId].items.erase(gang[id].route[cityId].items.begin() + itemPos);
// Update current capacity and global capacity
gang[id].route[cityId].capacity -= items[itemId].weight;
gang[id].capacity -= items[itemId].weight;
gangCapacity -= items[itemId].weight;
// Mark the item as not taken
items[itemId].thief = -1;
return std::make_pair(cityId, itemId);
}
// Adds an item to a Thief route
std::pair<int, int> addItem(int id) {
int cont = 0;
int itemId;
// Pick an item that doens't overflow current @globalCapacity and that hasn't been taken. Try that at most 100 times
do {
itemId = rand() % items.size();
} while((items[itemId].thief != -1 || items[itemId].weight + gangCapacity > W) && cont++ < 100);
// If we couldn't pick an item randomly after 100 trials, go through the @items vector and get the first avaliable.
if(cont >= 100){
itemId = -1;
for(int i = 0; i < items.size(); i++){
if(items[i].thief == -1 && items[i].weight + gangCapacity <= W){
itemId = i;
break;
}
}
// If everything is already taken, return fail flag ( pair.first = -1 )
if(itemId == -1) return std::make_pair(-1, -1);
}
// Updating capacities after getting the item
gangCapacity += items[itemId].weight;
gang[id].capacity += items[itemId].weight;
items[itemId].thief = id;
// If the item we took is in a city from our route, just add it to the Node item list
for(int i = 0; i < gang[id].route.size(); i++){
if(items[itemId].city == gang[id].route[i].id){
gang[id].route[i].items.push_back(itemId);
gang[id].route[i].capacity += items[itemId].weight;
// Flag indicating that we took the item from a city within our route ( pair.second = -idCity )
return std::make_pair(itemId, -1 * i);
}
}
// Look for the best place to fit the new city - if the item comes from a not visited city
int bestPlace = closestCity(id, items[itemId].city);
Node newItem = Node(items[itemId].city, {itemId}, items[itemId].weight); // Creating the new city node to go in route
// If the best option isn't the origin
if(bestPlace)
gang[id].route.insert(gang[id].route.begin() + bestPlace, newItem); // Add right before the best option
else
gang[id].route.push_back(newItem); // Add in the end of the route. Making it be "before" the origin - remmember the thief has to return in the end, hence the circulatiry
return std::make_pair(itemId, bestPlace);
}
/* ------------------ COST FUNCTION ------------------ */
long double cost(long double vMax, long double vMin, int W, long double R) {
long double total = 0.0;
long double currPenalty = 0.0; // Backpack fee cost
long double v = (vMax - vMin)/W; // Defined in the problem description
for(int i=0; i < gang.size(); i++){
int capacity = 0; // Total weight carried by thief @i from at a certain route
auto first = gang[i].route.front();
auto end = gang[i].route.back();
// Go until the penultimate city (Avoids seg. fault)
for(int j = 0; j < gang[i].route.size() - 1; j++){
capacity += gang[i].route[j].capacity;
int a = gang[i].route[j].id; // Current city
int b = gang[i].route[j + 1].id; // Next city
// Sum profit from items in the next city - first city has no item by definition and we go until the penultimate city.
for(int k = 0; k < gang[i].route[j + 1].items.size(); k++)
total += items[gang[i].route[j + 1].items[k]].profit;
currPenalty += adj[a][b]/(vMax - v * capacity); // Adds the cost of going from @a to @b in the current fee
}
// Coming back home, adding weight carried from last city and final backpack fee
capacity += end.capacity;
currPenalty += adj[end.id][first.id]/(vMax - v * capacity);
}
total -= R * currPenalty;
return total;
}
bool localSearchFirst(int type, int size) {
long double bestCost = cost(vMax, vMin, W, R);
bool isBetter = false;
// For a given neighborhood size
for(int i = 0; i < size; i++){
// Define a random order of neighborhood exploration, aiming to diversify the solutions.
std::vector<int>stepsOrder(gang.size());
std::iota(stepsOrder.begin(), stepsOrder.end(), 0);
random_shuffle(stepsOrder.begin(), stepsOrder.end());
for(int j = 0; j < gang.size(); j++) {
// Current thief index
int pos = stepsOrder[j];
/*
NEIGHBORHOODS
0 = opt2
1 = opt4
2 = remove city
3 = remove item
4 = add item
5 = exchange item
*/
// Explore a given neighborhood
if(type == 0 && gang[pos].route.size() > 1){ // Only swaps if there is more than one city in the route
std::pair<int, int> swaped = opt2(stepsOrder[j]);
// Checks if the cost improved and updates
long double newCost = cost(vMax, vMin, W, R);
if(bestCost < newCost){
bestCost = newCost;
isBetter = true;
}
// If things weren't good, undo changes
else std::swap(gang[pos].route[swaped.first], gang[pos].route[swaped.second]);
}
else if(type == 1 && gang[pos].route.size() > 1){
// Swap
std::pair<std::pair<int, int>, std::pair<int, int>> swaped = opt4(pos);
std::pair<int, int> swaped1 = swaped.first;
std::pair<int, int> swaped2 = swaped.second;
long double newCost = cost(vMax, vMin, W, R);
if(bestCost < newCost){
bestCost = newCost;
isBetter = true;
}
// If things weren't good, undo changes
else {
std::swap(gang[pos].route[swaped2.first], gang[pos].route[swaped2.second]);
std::swap(gang[pos].route[swaped1.first], gang[pos].route[swaped1.second]);
}
}
else if(type == 2 && gang[pos].route.size() > 1){
std::pair<int, Node> removed = removeCity(pos);
long double newCost = cost(vMax, vMin, W, R);
if(bestCost < newCost){
bestCost = newCost;
isBetter = true;
}
// Take the items back and re-update the capacities
else {
for(int k = 0; k < removed.second.items.size(); k++){
gangCapacity += items[removed.second.items[k]].weight;
gang[pos].capacity += items[removed.second.items[k]].weight;
items[removed.second.items[k]].thief = pos;
}
// Putting the city back in the route
gang[pos].route.insert(gang[pos].route.begin() + removed.first, removed.second);
}
}
else if(type == 3 && gang[pos].route.size() > 1){
std::pair<int, int> removed = removeItem(pos);
// Couldn't remove
if(removed.first == -1) continue;
long double newCost = cost(vMax, vMin, W, R);
if(bestCost < newCost){
bestCost = newCost;
isBetter = true;
}
// Undo changes
else {
gang[pos].route[removed.first].items.push_back(removed.second);
gang[pos].route[removed.first].capacity += items[removed.second].weight;
gangCapacity += items[removed.second].weight;
gang[pos].capacity += items[removed.second].weight;
items[removed.second].thief = pos;
}
}
else if(type == 4){
std::pair<int, int> added = addItem(pos);
// No valid items to add
if(added.first == -1) continue;
long double newCost = cost(vMax, vMin, W, R);
if(bestCost < newCost){
bestCost = newCost;
isBetter = true;
}
else{
// Updates capacities
gangCapacity -= items[added.first].weight;
gang[pos].capacity -= items[added.first].weight;
items[added.first].thief = -1;
// If the city was already in the route (flag: pair.second < 0 )
if(added.second < 0){
added.second *= -1;
gang[pos].route[added.second].items.pop_back();
gang[pos].route[added.second].capacity -= items[added.first].weight;
}
// Otherwise, remove the whole city - added because of 1 item - from the route
else {
if(added.second)
gang[pos].route.erase(gang[pos].route.begin() + added.second);
else
gang[pos].route.pop_back();
}
}
}
else if(type == 5 && gang[pos].route.size() > 1){
// Removes a random item and adds a new one right after.
std::pair<int, int> removed = removeItem(pos);
std::pair<int, int> added = addItem(pos);
long double newCost = cost(vMax, vMin, W, R);
if(bestCost < newCost){
bestCost = newCost;
isBetter = true;
}
// Undoing changes
else{
// If it was possible to add an item
if(added.first != -1){
// Update statuses
gangCapacity -= items[added.first].weight;
gang[pos].capacity -= items[added.first].weight;
items[added.first].thief = -1;
// If the city is already in the current route, you just need to remove the item.
if(added.second < 0){
added.second *= -1;
gang[pos].route[added.second].items.pop_back();
gang[pos].route[added.second].capacity -= items[added.first].weight;
}
// Otherwise remove the city
else {
if(added.second)
gang[pos].route.erase(gang[pos].route.begin() + added.second);
else
gang[pos].route.pop_back();
}
}
// If something was removed in the attempt for a better solution
if(removed.first != -1){
// Updates capacities and take back the removed item
gang[pos].route[removed.first].items.push_back(removed.second);
gang[pos].route[removed.first].capacity += items[removed.second].weight;
gangCapacity += items[removed.second].weight;
gang[pos].capacity += items[removed.second].weight;
items[removed.second].thief = pos;
}
}
}
}
}
return isBetter;
}
/* ------------------ METAHEURISTICS ------------------ */
// Variable Neighborhood Descent -- Using 6 neighborhoods of the same size. By default the initial one is 0 (opt2)
bool VND(int size) {
bool isBetter = false;
int i = 0;
while(i <= 5 && !timeLimit(600)){
// Iterate in current neighborhood. Improving means going back to first neighborhood
if(localSearchFirst(i, size)){
i = 0; //
isBetter = true;
}
// No improvements asks for diversity. Therefore we go to different neighborhoods
else
i++;
}
return isBetter;
}
void greedyInitialSolution(int numThiefs, bool safe = false, int numMoves = 1, int randomChance = 0){
std::vector<int>actualPos(numThiefs, 0);
std::vector<bool>ended(numThiefs, false);
long double v = (vMax - vMin)/W;
clearSolution();
double bestCost = 0.0;
std::vector<Item>bestItems = items;
std::vector<Thief>bestGang = gang;
int bestGangCap = 0;
bool hasOption = true;
while(hasOption) {
hasOption = false;
for(int i = 0; i < numThiefs; i++){
// If the thief @i returned, go to the next one
if(ended[i]) continue;
// Max moves for each thief per iteration
int availableMoves = numMoves;
while(availableMoves--){
// Initializes with -INFINITY to allow a lot of exploration.
// If no @bestItem was found to put in the current thief route, returns the flag -1
long double bestValue = std::numeric_limits<long double>::lowest();
int bestItem = -1;
for(int j = 0; j < M; j++){
// Item taken or too heavy? Keep going...
if(items[j].weight + gangCapacity > W || items[j].thief != -1) continue;
// Cost of taking the item from its respective city and coming back to the beginning
long double goingCost = R * adj[actualPos[i]][items[j].city]/(vMax - v * gang[i].capacity);
long double returnCost = R * adj[items[j].city][0]/(vMax - v * (gang[i].capacity + items[j].weight));
// Unsafe mode means we ignore the cost of comming back. It may lead to negative costs but also allows for greater exploration
if(!safe) returnCost = 0;
// If adding this item is better than the best found up until now, put it on the route.
// There is also a chance to ignore the current item, no matter how good/bad it is.
if(items[j].profit - goingCost - returnCost > bestValue && rand() % 100 >= randomChance){
bestValue = items[j].profit - goingCost - returnCost;
bestItem = j;
hasOption = true;
}
}
// No item worth the trouble for this Thief
if(bestItem == -1){
actualPos[i] = 0; // Goes back to the beginnig
ended[0] = true; // Flag indicating this Thief is done stealing
}
else {
// No cities on the route or the current item city isn't the last on the route means we can add a city
if(gang[i].route.empty() || gang[i].route.back().id != items[bestItem].city){
gang[i].route.push_back(Node());
gang[i].route.back().id = items[bestItem].city;
gang[i].route.back().capacity = 0;
}
// Adds the item weight in the last city and update its status as taken
gang[i].route.back().items.push_back(bestItem);
gang[i].route.back().capacity += items[bestItem].weight;
gang[i].capacity += items[bestItem].weight;
gangCapacity += items[bestItem].weight;
items[bestItem].thief = i;
actualPos[i] = items[bestItem].city;
// Calculates current solution cost and updates if benefitial
double actualCost = cost(vMax, vMin, W, R);
if(actualCost > bestCost){
bestCost = actualCost;
bestItems = items;
bestGang = gang;
bestGangCap = gangCapacity;
}
}
}
}
}
items = bestItems;
gang = bestGang;
gangCapacity = bestGangCap;
// This greedy can create invalid solutions (repeated cities in a route).
// To be able to use those solutions, fix them first.
for(int i = 0; i < numThiefs; i++)
fixRoute(i);
}
// Greedy Randomized Adaptive Search Procedure -- using a fixed random rate and a VND instead of a vanilla local search
void GRASP(int numThiefs, bool safe = false, int numMoves = 1, int randomChance = 0, int size = 300, int iter = 500) {
start = std::chrono::system_clock::now();
std::vector<Item> bestItem;
std::vector<Thief> bestGang;
int bestGangCap;
long double bestCost = std::numeric_limits<long double>::lowest();
while(iter-- && !timeLimit(600)) {
greedyInitialSolution(numThiefs, safe, numMoves, randomChance);
VND(size);
long double newCost = cost(vMax, vMin, W, R);
if(newCost > bestCost){
bestItem = items;
bestGang = gang;
bestGangCap = gangCapacity;
bestCost = newCost;
}
}
items = bestItem ;
gang = bestGang;
gangCapacity = bestGangCap;
}
// ILS auxiliar function
void perturb(double factor) {
// Ceiling to the factor in case it starts to grow too much
if(factor >= 0.4) factor = 0.5;
for(int i=0; i<gang.size(); i++){
if(gang[i].route.size() > 1){
int totalStolen = 0;
for(int k=0; k<gang[i].route.size(); k++) totalStolen += gang[i].route[k].items.size();
// How many items should we keep after the "shakedown" from perturb
totalStolen = (int) ((0.40 + factor) * totalStolen);
// Remove one random item at a time
while(totalStolen--) {
int pos = rand() % (gang[i].route.size() - 1) + 1; // + 1 Avoids drawing city zero ( by definition it has no item )
// If the random item is not the only one in the city inventory, take care to remove just it
if(gang[i].route[pos].items.size() > 1) {
int itemId = rand()%gang[i].route[pos].items.size();
removeItem(i,pos,itemId);
}
// If there is just one item, you might as well remove the whole city from the route
else {
removeItem(i, pos, 0);
gang[i].route.erase(gang[i].route.begin() + pos);
}
}
}
}
}
// Iterated Local Search with dinamic perturbation and VND as local search
void ILS(int numThiefs, bool safe = false, int numMoves = 1, int size = 100, int iter = 300) {
start = std::chrono::system_clock::now();
std::vector<Item> bestItem;
std::vector<Thief> bestGang;
int bestGangCap = 0;
long double bestCost = std::numeric_limits<long double>::lowest();
// Initial solution without a random
greedyInitialSolution(numThiefs, safe, numMoves, 0);
double factor = 0; // Initialing with a low perturbation
int ct = 0; // Non-improving iterations
bool improved = false;
while(iter-- && !timeLimit(600)) {
ct++;
improved = false;
perturb(factor);
VND(size);
// Updating solution if it improved and resetting the Non-improved counter
long double actualCost = cost(vMax, vMin, W, R);
if( actualCost > bestCost ) {
improved = true;
factor = 0;
ct = 0;
bestItem = items;
bestGang = gang;
bestGangCap = gangCapacity;
bestCost = actualCost;
}
// Not improving after 50 iterations triggers a factor increase (more severe perturbations willl be done)
if(ct >= 50 && !improved)
factor += 0.05;
}
items = bestItem;
gang = bestGang;
gangCapacity = bestGangCap;
}
#endif