-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathoperators.cpp
More file actions
2200 lines (1853 loc) · 115 KB
/
operators.cpp
File metadata and controls
2200 lines (1853 loc) · 115 KB
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
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include "operators.h"
/* VEHICLE AND CHARGE SCHEDULING OPERATORS
* Two main types of operators are used to improving vehicle and charge scheduling (in joint models).
* Exchanges are used to swap trips between two vehicle rotations.
* Shifts are used to move trips from one vehicle rotation to another.
* In each round, the best exchange or shift is applied in a greedy manner.
* Additionally, end depots are also exchanged at the end which keeps capacity limits at depots intact.
* The process is repeated until there is no improvement. */
// Function to find the best savings from exchanging trips
double scheduling::exchange_trips(std::vector<Vehicle>& vehicle, std::vector<Trip>& trip,
std::vector<Terminal>& terminal, ProcessedData& processed_data, Exchange& exchange)
{
double max_savings = 0.0; // Stores the maximum savings among all exchanges
// If CSP is to be solved jointly with scheduling, find the CSP solution before exchanges
double old_csp_cost = 0.0;
if (SOLVE_EVSP_CSP)
old_csp_cost = csp::select_optimization_model(vehicle, trip, terminal, processed_data, "csp-uniform");
// Pre-compute the pairs
std::vector<std::pair<int, int>> pairs;
for (int u = 0; u<vehicle.size(); ++u)
for (int v = u+1; v<vehicle.size(); ++v)
pairs.emplace_back(u, v);
// Exchange trips k and l of vehicles u and v. Pairs are created as collapse throws errors depending on the compiler
#pragma omp parallel for num_threads(NUM_THREADS)
for (int p = 0; p<pairs.size(); ++p) {
int u = pairs[p].first;
int v = pairs[p].second;
std::vector<int> update_vehicle_indices = {u, v};
for (int k = 1; k<vehicle[u].trip_id.size()-1; ++k) {
for (int l = 1; l<vehicle[v].trip_id.size()-1; ++l) {
// Store a vector of trip_id vectors after swapping trips
std::vector<std::vector<int>> swapped_rotations;
double savings = 0.0;
// Check if the exchanges is time compatible
if (evaluation::is_two_exchange_compatible(vehicle, trip, u, v, k, l)) {
// Check if exchanges are charge feasible. Push the original trip_ids to swapped_rotations
swapped_rotations.clear();
swapped_rotations.push_back(vehicle[u].trip_id); // This has index 0 in swapped_rotations
swapped_rotations.push_back(vehicle[v].trip_id); // This has index 1 in swapped_rotations
// Exchange trips k and l of vehicles u and v in swapped_rotations
int temp = swapped_rotations[0][k];
swapped_rotations[0][k] = swapped_rotations[1][l];
swapped_rotations[1][l] = temp;
if (evaluation::are_rotations_charge_feasible(trip, terminal, swapped_rotations)) {
// Calculate savings in deadheading from performing the exchange
savings += evaluation::calculate_trip_replacement_cost(vehicle, trip, u, v, k, l);
savings += evaluation::calculate_trip_replacement_cost(vehicle, trip, v, u, l, k);
if (SOLVE_EVSP_CSP) {
// Update a copy of the vehicle rotations
std::vector<Vehicle> vehicle_copy = vehicle;
vehicle_copy[u].trip_id = swapped_rotations[0];
vehicle_copy[v].trip_id = swapped_rotations[1];
// Solve the CSP model for the copy
double new_csp_cost = csp::select_optimization_model(vehicle_copy, trip, terminal,
processed_data, update_vehicle_indices, "csp-uniform");
savings += old_csp_cost-new_csp_cost;
}
// Check if the exchange is the best so far
// #pragma omp critical
{
if (evaluation::is_savings_maximum(savings, max_savings, u, v, k, l, exchange)) {
max_savings = savings;
exchange.first_vehicle_index = u;
exchange.first_trip_index = k;
exchange.second_vehicle_index = v;
exchange.second_trip_index = l;
}
}
}
}
}
}
}
return max_savings;
}
// Function to find the best savings from exchanging trips
double scheduling::exchange_trips_hybrid(std::vector<Vehicle>& vehicle, std::vector<Trip>& trip,
std::vector<Terminal>& terminal, ProcessedData& processed_data, Exchange& exchange)
{
double max_savings = 0.0; // Stores the maximum savings among all exchanges
// If CSP is to be solved jointly with scheduling, find the CSP solution before exchanges
double old_csp_cost = 0.0;
if (SOLVE_EVSP_CSP)
old_csp_cost = csp::select_optimization_model(vehicle, trip, terminal, processed_data, "csp-uniform");
// Pre-compute the pairs
std::vector<std::pair<int, int>> pairs;
for (int u = 0; u<vehicle.size(); ++u)
for (int v = u+1; v<vehicle.size(); ++v)
pairs.emplace_back(u, v);
std::vector<Exchange> exchanges;
// Exchange trips k and l of vehicles u and v. Pairs are created as collapse throws errors depending on the compiler
#pragma omp parallel for num_threads(NUM_THREADS)
for (int p = 0; p<pairs.size(); ++p) {
int u = pairs[p].first;
int v = pairs[p].second;
std::vector<int> update_vehicle_indices = {u, v};
for (int k = 1; k<vehicle[u].trip_id.size()-1; ++k) {
for (int l = 1; l<vehicle[v].trip_id.size()-1; ++l) {
// Store a vector of trip_id vectors after swapping trips
std::vector<std::vector<int>> swapped_rotations;
double savings = 0.0;
// Check if the exchanges is time compatible
if (evaluation::is_two_exchange_compatible(vehicle, trip, u, v, k, l)) {
// Check if exchanges are charge feasible. Push the original trip_ids to swapped_rotations
swapped_rotations.clear();
swapped_rotations.push_back(vehicle[u].trip_id); // This has index 0 in swapped_rotations
swapped_rotations.push_back(vehicle[v].trip_id); // This has index 1 in swapped_rotations
// Exchange trips k and l of vehicles u and v in swapped_rotations
int temp = swapped_rotations[0][k];
swapped_rotations[0][k] = swapped_rotations[1][l];
swapped_rotations[1][l] = temp;
if (evaluation::are_rotations_charge_feasible(trip, terminal, swapped_rotations)) {
// Calculate savings in deadheading from performing the exchange
savings += evaluation::calculate_trip_replacement_cost(vehicle, trip, u, v, k, l);
savings += evaluation::calculate_trip_replacement_cost(vehicle, trip, v, u, l, k);
// Store exchange information
#pragma omp critical
{
Exchange temp_exchange;
temp_exchange.first_vehicle_index = u;
temp_exchange.first_trip_index = k;
temp_exchange.second_vehicle_index = v;
temp_exchange.second_trip_index = l;
temp_exchange.savings = savings;
exchanges.push_back(temp_exchange);
// Sort exchanges based on savings and keep only top 100
std::sort(exchanges.begin(), exchanges.end(), [](const Exchange& a, const Exchange& b) {
return a.savings>b.savings; // Sort in descending order of savings
});
if (exchanges.size()>NUM_SHORTLISTED_SOLUTIONS) {
exchanges.resize(NUM_SHORTLISTED_SOLUTIONS); // Keep only a limited number of solutions
}
}
}
}
}
}
}
// Scan exchanges and solve CSP jointly to find the max savings
#pragma omp parallel for num_threads(NUM_THREADS)
for (int i = 0; i<exchanges.size(); ++i) {
int u = exchanges[i].first_vehicle_index;
int v = exchanges[i].second_vehicle_index;
int k = exchanges[i].first_trip_index;
int l = exchanges[i].second_trip_index;
std::vector<std::vector<int>> swapped_rotations;
std::vector<int> update_vehicle_indices = {u, v};
swapped_rotations.clear();
swapped_rotations.push_back(vehicle[u].trip_id); // This has index 0 in swapped_rotations
swapped_rotations.push_back(vehicle[v].trip_id); // This has index 1 in swapped_rotations
// Exchange trips k and l of vehicles u and v in swapped_rotations
int temp = swapped_rotations[0][k];
swapped_rotations[0][k] = swapped_rotations[1][l];
swapped_rotations[1][l] = temp;
// Update a copy of the vehicle rotations
std::vector<Vehicle> vehicle_copy = vehicle;
vehicle_copy[u].trip_id = swapped_rotations[0];
vehicle_copy[v].trip_id = swapped_rotations[1];
double savings = 0.0;
// Solve the CSP model for the copy
double new_csp_cost = csp::select_optimization_model(vehicle_copy, trip, terminal, processed_data,
update_vehicle_indices, "csp-uniform");
savings += old_csp_cost-new_csp_cost;
if (evaluation::is_savings_maximum(savings, max_savings, u, v, k, l, exchange)) {
max_savings = savings;
exchange.first_vehicle_index = u;
exchange.first_trip_index = k;
exchange.second_vehicle_index = v;
exchange.second_trip_index = l;
}
}
return max_savings;
}
// Function to find the best savings from shifting trips
double scheduling::shift_trips(std::vector<Vehicle>& vehicle, std::vector<Trip>& trip, std::vector<Terminal>& terminal,
ProcessedData& processed_data, Shift& shift)
{
double max_savings = 0.0; // Variables for calculating the savings from trip shifts
// If CSP is to be solved jointly with scheduling, find the CSP solution before shifts
double old_csp_cost = 0.0;
if (SOLVE_EVSP_CSP)
old_csp_cost = csp::select_optimization_model(vehicle, trip, terminal, processed_data, "csp-uniform");
// Pre-compute the pairs
std::vector<std::pair<int, int>> pairs;
for (int u = 0; u<vehicle.size(); ++u)
for (int v = 0; v<vehicle.size(); ++v)
if (u!=v)
pairs.emplace_back(u, v);
// Insert trip l of vehicle v after trip k of vehicle u
#pragma omp parallel for num_threads(NUM_THREADS)
for (int p = 0; p<pairs.size(); ++p) {
int u = pairs[p].first;
int v = pairs[p].second;
for (int k = 1; k<vehicle[u].trip_id.size()-1; ++k) {
for (int l = 1; l<vehicle[v].trip_id.size()-1; ++l) {
// Store a vector of trip_id vectors after shifting trips
std::vector<std::vector<int>> shifted_rotations;
double savings = 0.0;
// Check if the exchanges is time compatible and charge feasible
if (evaluation::is_shift_compatible(vehicle, trip, u, v, k, l)) {
// Check if exchanges are charge feasible. Insert the original trip_ids to shifted_rotations
shifted_rotations.clear();
shifted_rotations.push_back(vehicle[u].trip_id); // This has index 0 in shifted_rotations
shifted_rotations.push_back(vehicle[v].trip_id); // This has index 1 in shifted_rotations
// Insert trip l of vehicle v after trip k of vehicle u in shifted_rotations
shifted_rotations[0].insert(shifted_rotations[0].begin()+k+1, shifted_rotations[1][l]);
// Remove trip l of vehicle v from shifted_rotations
shifted_rotations[1].erase(shifted_rotations[1].begin()+l);
// Check if shifted_rotations[1] has only two trips. If so, delete it
if (shifted_rotations[1].size()==2)
shifted_rotations.erase(shifted_rotations.begin()+1);
if (evaluation::are_rotations_charge_feasible(trip, terminal, shifted_rotations)) {
// Calculate savings in deadheading from performing the exchange
savings += evaluation::calculate_trip_addition_cost(vehicle, trip, u, v, k, l);
savings += evaluation::calculate_trip_removal_cost(vehicle, trip, v, l);
if (SOLVE_EVSP_CSP) {
// Update a copy of the vehicle rotations
std::vector<Vehicle> vehicle_copy = vehicle;
std::vector<int> update_vehicle_indices;
vehicle_copy[u].trip_id = shifted_rotations[0];
if (shifted_rotations.size()==1) { // Delete vehicle with index v if needed
vehicle_copy.erase(vehicle_copy.begin()+v);
// Deleting a rotation can shift the index of the others by 1
update_vehicle_indices = (v>u) ? std::vector<int>{u} : std::vector<int>{u-1};
}
else {
vehicle_copy[v].trip_id = shifted_rotations[1];
update_vehicle_indices = {u, v};
}
// Solve the CSP model for the copy
double new_csp_cost = csp::select_optimization_model(vehicle_copy, trip, terminal,
processed_data, update_vehicle_indices, "csp-uniform");
savings += old_csp_cost-new_csp_cost;
}
// Check if the exchange is the best so far
//#pragma omp critical
{
if (evaluation::is_savings_maximum(savings, max_savings, u, v, k, l, shift)) {
max_savings = savings;
shift.dest_vehicle_index = u;
shift.dest_trip_index = k;
shift.source_vehicle_index = v;
shift.source_trip_index = l;
}
}
}
}
}
}
}
return max_savings;
}
double scheduling::shift_trips_hybrid(std::vector<Vehicle>& vehicle, std::vector<Trip>& trip,
std::vector<Terminal>& terminal,
ProcessedData& processed_data, Shift& shift)
{
double max_savings = 0.0; // Variables for calculating the savings from trip shifts
// If CSP is to be solved jointly with scheduling, find the CSP solution before shifts
double old_csp_cost = 0.0;
if (SOLVE_EVSP_CSP)
old_csp_cost = csp::select_optimization_model(vehicle, trip, terminal, processed_data, "csp-uniform");
// Pre-compute the pairs
std::vector<std::pair<int, int>> pairs;
for (int u = 0; u<vehicle.size(); ++u)
for (int v = 0; v<vehicle.size(); ++v)
if (u!=v)
pairs.emplace_back(u, v);
std::vector<Shift> shifts;
// Insert trip l of vehicle v after trip k of vehicle u
#pragma omp parallel for num_threads(NUM_THREADS)
for (int p = 0; p<pairs.size(); ++p) {
int u = pairs[p].first;
int v = pairs[p].second;
for (int k = 1; k<vehicle[u].trip_id.size()-1; ++k) {
for (int l = 1; l<vehicle[v].trip_id.size()-1; ++l) {
// Store a vector of trip_id vectors after shifting trips
std::vector<std::vector<int>> shifted_rotations;
double savings = 0.0;
// Check if the exchanges is time compatible and charge feasible
if (evaluation::is_shift_compatible(vehicle, trip, u, v, k, l)) {
// Check if exchanges are charge feasible. Insert the original trip_ids to shifted_rotations
shifted_rotations.clear();
shifted_rotations.push_back(vehicle[u].trip_id); // This has index 0 in shifted_rotations
shifted_rotations.push_back(vehicle[v].trip_id); // This has index 1 in shifted_rotations
// Insert trip l of vehicle v after trip k of vehicle u in shifted_rotations
shifted_rotations[0].insert(shifted_rotations[0].begin()+k+1, shifted_rotations[1][l]);
// Remove trip l of vehicle v from shifted_rotations
shifted_rotations[1].erase(shifted_rotations[1].begin()+l);
// Check if shifted_rotations[1] has only two trips. If so, delete it
if (shifted_rotations[1].size()==2)
shifted_rotations.erase(shifted_rotations.begin()+1);
if (evaluation::are_rotations_charge_feasible(trip, terminal, shifted_rotations)) {
// Calculate savings in deadheading from performing the exchange
savings += evaluation::calculate_trip_addition_cost(vehicle, trip, u, v, k, l);
savings += evaluation::calculate_trip_removal_cost(vehicle, trip, v, l);
#pragma omp critical
{
Shift temp_shift;
temp_shift.dest_vehicle_index = u;
temp_shift.dest_trip_index = k;
temp_shift.source_vehicle_index = v;
temp_shift.source_trip_index = l;
temp_shift.savings = savings;
shifts.push_back(temp_shift);
// Sort shifts based on savings and keep only the top 100
std::sort(shifts.begin(), shifts.end(), [](const Shift& a, const Shift& b) {
return a.savings>b.savings; // sort in descending order of savings
});
if (shifts.size()>NUM_SHORTLISTED_SOLUTIONS) {
shifts.resize(NUM_SHORTLISTED_SOLUTIONS); // keep only top 100
}
}
}
}
}
}
}
#pragma omp parallel for num_threads(NUM_THREADS)
for (int i = 0; i<shifts.size(); ++i) {
int u = shifts[i].dest_vehicle_index;
int v = shifts[i].source_vehicle_index;
int k = shifts[i].dest_trip_index;
int l = shifts[i].source_trip_index;
std::vector<std::vector<int>> shifted_rotations;
double savings = 0.0;
// Check if exchanges are charge feasible. Insert the original trip_ids to shifted_rotations
shifted_rotations.clear();
shifted_rotations.push_back(vehicle[u].trip_id); // This has index 0 in shifted_rotations
shifted_rotations.push_back(vehicle[v].trip_id); // This has index 1 in shifted_rotations
// Insert trip l of vehicle v after trip k of vehicle u in shifted_rotations
shifted_rotations[0].insert(shifted_rotations[0].begin()+k+1, shifted_rotations[1][l]);
// Remove trip l of vehicle v from shifted_rotations
shifted_rotations[1].erase(shifted_rotations[1].begin()+l);
// Check if shifted_rotations[1] has only two trips. If so, delete it
if (shifted_rotations[1].size()==2)
shifted_rotations.erase(shifted_rotations.begin()+1);
// Update a copy of the vehicle rotations
std::vector<Vehicle> vehicle_copy = vehicle;
std::vector<int> update_vehicle_indices;
vehicle_copy[u].trip_id = shifted_rotations[0];
if (shifted_rotations.size()==1) { // Delete vehicle with index v if needed
vehicle_copy.erase(vehicle_copy.begin()+v);
// Deleting a rotation can shift the index of the others by 1
update_vehicle_indices = (v>u) ? std::vector<int>{u} : std::vector<int>{u-1};
}
else {
vehicle_copy[v].trip_id = shifted_rotations[1];
update_vehicle_indices = {u, v};
}
// Solve the CSP model for the copy
double new_csp_cost = csp::select_optimization_model(vehicle_copy, trip, terminal,
processed_data, update_vehicle_indices, "csp-uniform");
savings += old_csp_cost-new_csp_cost;
if (evaluation::is_savings_maximum(savings, max_savings, u, v, k, l, shift)) {
max_savings = savings;
shift.dest_vehicle_index = u;
shift.dest_trip_index = k;
shift.source_vehicle_index = v;
shift.source_trip_index = l;
}
}
return max_savings;
}
// Function to exchange the depot trips of two vehicles
double scheduling::exchange_depots(std::vector<Vehicle>& vehicle, std::vector<Trip>& trip,
std::vector<Terminal>& terminal, ProcessedData& processed_data, Exchange& exchange)
{
// Find a pair of vehicle rotation as a set from vehicles
double max_savings = 0.0; // Stores the maximum savings among all exchanges
// If CSP is to be solved jointly with scheduling, find the CSP solution before exchanges
double old_csp_cost = 0.0;
if (SOLVE_EVSP_CSP)
old_csp_cost = csp::select_optimization_model(vehicle, trip, terminal, processed_data, "csp-uniform");
// Pre-compute the pairs
std::vector<std::pair<int, int>> pairs;
for (int u = 0; u<vehicle.size(); ++u)
for (int v = u+1; v<vehicle.size(); ++v)
pairs.emplace_back(u, v);
// Exchange trips k and l of vehicles u and v. Pairs are created as collapse throws errors depending on the compiler
#pragma omp parallel for num_threads(NUM_THREADS)
for (int p = 0; p<pairs.size(); ++p) {
int u = pairs[p].first;
int v = pairs[p].second;
// Store a vector of trip_id vectors after swapping trips
std::vector<std::vector<int>> swapped_rotations;
std::vector<int> update_vehicle_indices = {u, v};
// Exchange end depots of vehicles u and v
int k = vehicle[u].trip_id.size()-1;
int l = vehicle[v].trip_id.size()-1;
double savings = 0.0;
swapped_rotations.clear();
swapped_rotations.push_back(vehicle[u].trip_id); // This has index 0 in swapped_rotations
swapped_rotations.push_back(vehicle[v].trip_id); // This has index 1 in swapped_rotations
// Exchange trips k and l of vehicles u and v in swapped_rotations
int temp = swapped_rotations[0][k];
swapped_rotations[0][k] = swapped_rotations[1][l];
swapped_rotations[1][l] = temp;
if (evaluation::are_rotations_charge_feasible(trip, terminal, swapped_rotations)) {
// Calculate savings in deadheading from performing the exchange
savings += evaluation::calculate_end_depot_replacement_cost(vehicle, trip, u, v, k, l);
savings += evaluation::calculate_end_depot_replacement_cost(vehicle, trip, v, u, l, k);
if (SOLVE_EVSP_CSP) {
// Update a copy of the vehicle rotations
std::vector<Vehicle> vehicle_copy = vehicle;
vehicle_copy[u].trip_id = swapped_rotations[0];
vehicle_copy[v].trip_id = swapped_rotations[1];
// Solve the CSP model for the copy
double new_csp_cost = csp::select_optimization_model(vehicle_copy, trip, terminal, processed_data,
update_vehicle_indices, "csp-uniform");
savings += old_csp_cost-new_csp_cost;
}
// Check if the exchange is the best so far
//#pragma omp critical
{
if (evaluation::is_savings_maximum(savings, max_savings, u, v, k, l, exchange)) {
max_savings = savings;
exchange.first_vehicle_index = u;
exchange.first_trip_index = k;
exchange.second_vehicle_index = v;
exchange.second_trip_index = l;
}
}
}
// Exchange start depots of vehicles u and v
k = 0, l = 0;
savings = 0.0;
swapped_rotations.clear();
swapped_rotations.push_back(vehicle[u].trip_id); // This has index 0 in swapped_rotations
swapped_rotations.push_back(vehicle[v].trip_id); // This has index 1 in swapped_rotations
// Exchange trips k and l of vehicles u and v in swapped_rotations
temp = swapped_rotations[0][k];
swapped_rotations[0][k] = swapped_rotations[1][l];
swapped_rotations[1][l] = temp;
if (evaluation::are_rotations_charge_feasible(trip, terminal, swapped_rotations)) {
// Calculate savings in deadheading from performing the exchange
savings += evaluation::calculate_start_depot_replacement_cost(vehicle, trip, u, v, k, l);
savings += evaluation::calculate_start_depot_replacement_cost(vehicle, trip, v, u, l, k);
if (SOLVE_EVSP_CSP) {
// Update a copy of the vehicle rotations
std::vector<Vehicle> vehicle_copy = vehicle;
vehicle_copy[u].trip_id = swapped_rotations[0];
vehicle_copy[v].trip_id = swapped_rotations[1];
// Solve the CSP model for the copy
double new_csp_cost = csp::select_optimization_model(vehicle_copy, trip, terminal, processed_data,
update_vehicle_indices, "csp-uniform");
savings += old_csp_cost-new_csp_cost;
}
// Check if the exchange is the best so far
//#pragma omp critical
{
if (evaluation::is_savings_maximum(savings, max_savings, u, v, k, l, exchange)) {
max_savings = savings;
exchange.first_vehicle_index = u;
exchange.first_trip_index = k;
exchange.second_vehicle_index = v;
exchange.second_trip_index = l;
}
}
}
}
return max_savings;
}
// Function to exchange the depot trips of two vehicles
double scheduling::exchange_depots_hybrid(std::vector<Vehicle>& vehicle, std::vector<Trip>& trip,
std::vector<Terminal>& terminal, ProcessedData& processed_data, Exchange& exchange)
{
// Find a pair of vehicle rotation as a set from vehicles
double max_savings = 0.0; // Stores the maximum savings among all exchanges
// If CSP is to be solved jointly with scheduling, find the CSP solution before exchanges
double old_csp_cost = 0.0;
if (SOLVE_EVSP_CSP)
old_csp_cost = csp::select_optimization_model(vehicle, trip, terminal, processed_data, "csp-uniform");
// Pre-compute the pairs
std::vector<std::pair<int, int>> pairs;
for (int u = 0; u<vehicle.size(); ++u)
for (int v = u+1; v<vehicle.size(); ++v)
pairs.emplace_back(u, v);
std::vector<Exchange> exchanges;
// Exchange trips k and l of vehicles u and v. Pairs are created as collapse throws errors depending on the compiler
#pragma omp parallel for num_threads(NUM_THREADS)
for (int p = 0; p<pairs.size(); ++p) {
int u = pairs[p].first;
int v = pairs[p].second;
// Store a vector of trip_id vectors after swapping trips
std::vector<std::vector<int>> swapped_rotations;
std::vector<int> update_vehicle_indices = {u, v};
// Exchange end depots of vehicles u and v
int k = vehicle[u].trip_id.size()-1;
int l = vehicle[v].trip_id.size()-1;
double savings = 0.0;
swapped_rotations.clear();
swapped_rotations.push_back(vehicle[u].trip_id); // This has index 0 in swapped_rotations
swapped_rotations.push_back(vehicle[v].trip_id); // This has index 1 in swapped_rotations
// Exchange trips k and l of vehicles u and v in swapped_rotations
int temp = swapped_rotations[0][k];
swapped_rotations[0][k] = swapped_rotations[1][l];
swapped_rotations[1][l] = temp;
if (evaluation::are_rotations_charge_feasible(trip, terminal, swapped_rotations)) {
// Calculate savings in deadheading from performing the exchange
savings += evaluation::calculate_end_depot_replacement_cost(vehicle, trip, u, v, k, l);
savings += evaluation::calculate_end_depot_replacement_cost(vehicle, trip, v, u, l, k);
#pragma omp critical
{
Exchange temp_exchange;
temp_exchange.first_vehicle_index = u;
temp_exchange.first_trip_index = k;
temp_exchange.second_vehicle_index = v;
temp_exchange.second_trip_index = l;
temp_exchange.savings = savings;
exchanges.push_back(temp_exchange);
// Sort exchanges based on savings and keep only the top ones
std::sort(exchanges.begin(), exchanges.end(), [](const Exchange& a, const Exchange& b) {
return a.savings>b.savings; // sort in descending order of savings
});
if (exchanges.size()>NUM_SHORTLISTED_SOLUTIONS)
exchanges.resize(NUM_SHORTLISTED_SOLUTIONS); // keep only top solutions
}
}
// Exchange start depots of vehicles u and v
k = 0, l = 0;
savings = 0.0;
swapped_rotations.clear();
swapped_rotations.push_back(vehicle[u].trip_id); // This has index 0 in swapped_rotations
swapped_rotations.push_back(vehicle[v].trip_id); // This has index 1 in swapped_rotations
// Exchange trips k and l of vehicles u and v in swapped_rotations
temp = swapped_rotations[0][k];
swapped_rotations[0][k] = swapped_rotations[1][l];
swapped_rotations[1][l] = temp;
if (evaluation::are_rotations_charge_feasible(trip, terminal, swapped_rotations)) {
// Calculate savings in deadheading from performing the exchange
savings += evaluation::calculate_start_depot_replacement_cost(vehicle, trip, u, v, k, l);
savings += evaluation::calculate_start_depot_replacement_cost(vehicle, trip, v, u, l, k);
#pragma omp critical
{
Exchange temp_exchange;
temp_exchange.first_vehicle_index = u;
temp_exchange.first_trip_index = k;
temp_exchange.second_vehicle_index = v;
temp_exchange.second_trip_index = l;
temp_exchange.savings = savings;
exchanges.push_back(temp_exchange);
// Sort exchanges based on savings and keep only the top ones
std::sort(exchanges.begin(), exchanges.end(), [](const Exchange& a, const Exchange& b) {
return a.savings>b.savings; // sort in descending order of savings
});
if (exchanges.size()>NUM_SHORTLISTED_SOLUTIONS)
exchanges.resize(NUM_SHORTLISTED_SOLUTIONS); // keep only top solutions
}
}
}
// Scan exchanges and solve CSP jointly to find the max savings
#pragma omp parallel for num_threads(NUM_THREADS)
for (int i = 0; i<exchanges.size(); ++i) {
int u = exchanges[i].first_vehicle_index;
int v = exchanges[i].second_vehicle_index;
int k = exchanges[i].first_trip_index;
int l = exchanges[i].second_trip_index;
std::vector<std::vector<int>> swapped_rotations;
std::vector<int> update_vehicle_indices = {u, v};
swapped_rotations.clear();
swapped_rotations.push_back(vehicle[u].trip_id); // This has index 0 in swapped_rotations
swapped_rotations.push_back(vehicle[v].trip_id); // This has index 1 in swapped_rotations
// Exchange trips k and l of vehicles u and v in swapped_rotations
int temp = swapped_rotations[0][k];
swapped_rotations[0][k] = swapped_rotations[1][l];
swapped_rotations[1][l] = temp;
// Update a copy of the vehicle rotations
std::vector<Vehicle> vehicle_copy = vehicle;
vehicle_copy[u].trip_id = swapped_rotations[0];
vehicle_copy[v].trip_id = swapped_rotations[1];
double savings = 0.0;
// Solve the CSP model for the copy
double new_csp_cost = csp::select_optimization_model(vehicle_copy, trip, terminal, processed_data,
update_vehicle_indices, "csp-uniform");
savings += old_csp_cost-new_csp_cost;
if (evaluation::is_savings_maximum(savings, max_savings, u, v, k, l, exchange)) {
max_savings = savings;
exchange.first_vehicle_index = u;
exchange.first_trip_index = k;
exchange.second_vehicle_index = v;
exchange.second_trip_index = l;
}
}
return max_savings;
}
// Function that actually performs the exchange using the exchange object
void scheduling::perform_exchange(std::vector<Vehicle>& vehicle, Exchange& exchange)
{
logger.log(LogLevel::Info, "Performing exchange...");
// Exchange trips k and l of vehicles u and v
int first_vehicle_index = exchange.first_vehicle_index;
int second_vehicle_index = exchange.second_vehicle_index;
int first_trip_index = exchange.first_trip_index;
int second_trip_index = exchange.second_trip_index;
// Log trip IDs before exchange
logger.log(LogLevel::Debug, "First vehicle trip IDs: "+vector_to_string(vehicle[first_vehicle_index].trip_id));
logger.log(LogLevel::Debug,
"Second vehicle trip IDs: "+vector_to_string(vehicle[second_vehicle_index].trip_id));
// Log exchange information
logger.log(LogLevel::Debug, "Exchanging trip index "+std::to_string(first_trip_index)+" of vehicle index "
+std::to_string(first_vehicle_index)+" with trip index "+std::to_string(second_trip_index)
+" of vehicle index "
+std::to_string(second_vehicle_index)+"...");
// Swap the trips
int temp = vehicle[first_vehicle_index].trip_id[first_trip_index];
vehicle[first_vehicle_index].trip_id[first_trip_index] = vehicle[second_vehicle_index].trip_id[second_trip_index];
vehicle[second_vehicle_index].trip_id[second_trip_index] = temp;
// Log trip IDs after exchange for both first vehicle and second vehicle
logger.log(LogLevel::Debug,
"First vehicle new trip IDs: "+vector_to_string(vehicle[first_vehicle_index].trip_id));
logger.log(LogLevel::Debug,
"Second vehicle new trip IDs: "+vector_to_string(vehicle[second_vehicle_index].trip_id));
}
// Function that actually performs the shift using the shift object
void scheduling::perform_shift(std::vector<Vehicle>& vehicle, Shift& shift)
{
logger.log(LogLevel::Info, "Performing shift...");
// Insert trip l of vehicle v after trip k of vehicle u
int dest_vehicle_index = shift.dest_vehicle_index;
int source_vehicle_index = shift.source_vehicle_index;
int dest_trip_index = shift.dest_trip_index;
int source_trip_index = shift.source_trip_index;
// Log trip IDs before shift
logger.log(LogLevel::Debug,
"Source vehicle trip IDs: "+vector_to_string(vehicle[source_vehicle_index].trip_id));
logger.log(LogLevel::Debug,
"Destination vehicle trip IDs: "+vector_to_string(vehicle[dest_vehicle_index].trip_id));
// Log shift information
logger.log(LogLevel::Debug, "Shifting trip index "+std::to_string(source_trip_index)+" of vehicle index "
+std::to_string(source_vehicle_index)+" after trip index "+std::to_string(dest_trip_index)
+" of vehicle index "+std::to_string(dest_vehicle_index)+"...");
// Insert the trip
vehicle[dest_vehicle_index].trip_id.insert(vehicle[dest_vehicle_index].trip_id.begin()+dest_trip_index+1,
vehicle[source_vehicle_index].trip_id[source_trip_index]);
// Remove the trip
vehicle[source_vehicle_index].trip_id.erase(vehicle[source_vehicle_index].trip_id.begin()+source_trip_index);
// If the source vehicle has only two trips, remove the vehicle
if (vehicle[source_vehicle_index].trip_id.size()==2) {
logger.log(LogLevel::Info, "Source vehicle has only two trips. Removing it...");
vehicle.erase(vehicle.begin()+source_vehicle_index);
}
else
logger.log(LogLevel::Debug,
"Source vehicle new trip IDs: "+vector_to_string(vehicle[source_vehicle_index].trip_id));
// Log the rotations after the shift for the destination vehicle
logger.log(LogLevel::Debug,
"Destination vehicle new trip new IDs: "+vector_to_string(vehicle[dest_vehicle_index].trip_id));
}
// Best improvement function to optimize rotations using shifts and exchanges
void scheduling::apply_best_improvement(std::vector<Vehicle>& vehicle, std::vector<Trip>& trip,
std::vector<Terminal>& terminal, ProcessedData& processed_data)
{
logger.log(LogLevel::Info, "Finding the best improvement operator...");
Exchange exchange;
Shift shift;
static int num_exchanges = 0;
static int num_shifts = 0;
// Run the exchange and shift locations
double exchange_savings, shift_savings, depot_exchange_savings;
exchange_savings = USE_HYBRID_OPERATORS ? exchange_trips_hybrid(vehicle, trip, terminal, processed_data, exchange)
: exchange_trips(vehicle, trip, terminal, processed_data, exchange);
logger.log(LogLevel::Info, "Savings from exchange operator: "+std::to_string(exchange_savings));
shift_savings = USE_HYBRID_OPERATORS ? shift_trips_hybrid(vehicle, trip, terminal, processed_data, shift)
: shift_trips(vehicle, trip, terminal, processed_data, shift);
logger.log(LogLevel::Info, "Savings from shift operator: "+std::to_string(shift_savings));
// Check if savings are positive
if (exchange_savings<EPSILON and shift_savings<EPSILON) {
logger.log(LogLevel::Info, "No improvement possible from trip exchanges or shifts...");
logger.log(LogLevel::Info, "Checking for improvement from depot exchanges...");
depot_exchange_savings = USE_HYBRID_OPERATORS ? exchange_depots_hybrid(vehicle, trip, terminal, processed_data,
exchange) : exchange_depots(vehicle, trip, terminal, processed_data, exchange);
logger.log(LogLevel::Info, "Savings from depot exchanges: "+std::to_string(depot_exchange_savings));
if (depot_exchange_savings>EPSILON) {
perform_exchange(vehicle, exchange);
++num_exchanges;
}
else
logger.log(LogLevel::Info, "No improvement possible from depot exchanges...");
return;
}
if (exchange_savings>shift_savings) {
perform_exchange(vehicle, exchange);
++num_exchanges;
}
else {
perform_shift(vehicle, shift);
++num_shifts;
}
// Log the number of shifts and exchanges that were actually performed
logger.log(LogLevel::Info, "Number of exchanges performed: "+std::to_string(num_exchanges));
logger.log(LogLevel::Info, "Number of shifts performed: "+std::to_string(num_shifts));
}
// Function to optimize rotations using repeated shifts and exchanges till there is no improvement
void scheduling::optimize_rotations(std::vector<Vehicle>& vehicle, std::vector<Trip>& trip,
std::vector<Terminal>& terminal, ProcessedData& processed_data)
{
logger.log(LogLevel::Info, "Optimizing rotations...");
// Initialize variables
double old_objective = INF;
double new_objective = evaluation::calculate_objective(vehicle, trip, terminal, processed_data);
// Loop until there is no improvement in the objective or time exceeds the threshold
while (new_objective<old_objective) {
old_objective = new_objective;
scheduling::apply_best_improvement(vehicle, trip, terminal, processed_data);
new_objective = evaluation::calculate_objective(vehicle, trip, terminal, processed_data);
processed_data.objective_values.push_back(new_objective); // Store the objective value for visualization
}
diversification::optimize_multiple_shifts(vehicle, trip, terminal, processed_data);
}
/* LOCATION OPERATORS
* These are used to open/close charge stations or swap an open-closed station pair and serve as first-level decisions.
* Closing charging station can lead to charge infeasibility. This is addressed by splitting rotations.
* Opening and closing charging stations can lead to suboptimal rotations.
* Hence, the scheduling operators are applied after opening and closing charging stations. */
// Create new rotations by splitting after trip_index if it is not charge feasible
void locations::split_trips(std::vector<Vehicle>& vehicle, std::vector<int>& scan_eligible_vehicle_indices,
int vehicle_index, int trip_index)
{
// Log operations
logger.log(LogLevel::Debug, "Splitting vehicle index "+std::to_string(vehicle_index)+" after trip ID "
+std::to_string(vehicle[vehicle_index].trip_id[trip_index])+"...");
// Log trip IDs before splitting
logger.log(LogLevel::Debug, "Trip IDs before splitting: "+vector_to_string(vehicle[vehicle_index].trip_id));
Vehicle new_vehicle; // Create a new vehicle with the remaining trips
new_vehicle.id = vehicle[vehicle.size()-1].id+1;
new_vehicle.trip_id.push_back(vehicle[vehicle_index].trip_id[0]); // Add the depot
new_vehicle.trip_id.insert(new_vehicle.trip_id.begin()+1, vehicle[vehicle_index].trip_id.begin()+trip_index+1,
vehicle[vehicle_index].trip_id.end()); // Add the second half of the original rotation
vehicle.push_back(new_vehicle);
// Remove existing trips from vehicle_index after trip_index till the last but one element
vehicle[vehicle_index].trip_id.erase(vehicle[vehicle_index].trip_id.begin()+trip_index+1,
vehicle[vehicle_index].trip_id.end()-1);
// Log trip IDs after splitting
logger.log(LogLevel::Debug, "Old vehicle trip IDs: "+vector_to_string(vehicle[vehicle_index].trip_id));
logger.log(LogLevel::Debug, "New vehicle trip IDs: "+vector_to_string(vehicle[vehicle.size()-1].trip_id));
// Add the new and old vehicles to scan_eligible_vehicle_indices
scan_eligible_vehicle_indices.push_back(vehicle.size()-1);
scan_eligible_vehicle_indices.push_back(vehicle_index);
}
// Function that checks if a rotation is charge feasible. If not it also calls a function to split trips
bool locations::are_rotations_charge_feasible(std::vector<Vehicle>& vehicle, std::vector<Trip>& trip,
std::vector<Terminal>& terminal, std::vector<int>& scan_eligible_vehicle_indices)
{
// Loop until the list of un-checked rotations is empty
while (not scan_eligible_vehicle_indices.empty()) {
bool is_splitting_required = false; // Flag to indicate if splitting is required
int split_trip_index = -1; // Farthest trip index after which splitting is possible while reaching the depot
// Pop the last element from scan_eligible_vehicle_indices
int v = scan_eligible_vehicle_indices.back();
scan_eligible_vehicle_indices.pop_back();
logger.log(LogLevel::Debug, "Checking feasibility of vehicle: "+std::to_string(v));
// Calculate the energy required for deadheading from the depot to the end of the first trip
double charge_level =
MAX_CHARGE_LEVEL-(trip[vehicle[v].trip_id[0]-1].deadhead_distance[vehicle[v].trip_id[1]-1]
+trip[vehicle[v].trip_id[1]-1].distance)*ENERGY_PER_KM;
// If energy level is below the minimum threshold, splitting will not help. Return false and break.
// This condition isn't required for existing rotations. But second parts of split rotations can be infeasible.
if (charge_level<MIN_CHARGE_LEVEL)
return false;
int last_trip = vehicle[v].trip_id[vehicle[v].trip_id.size()-1]; // This represents the auxiliary depot trip
double charge_level_until_depot;
int curr_trip, next_trip; // Current trip and next trip IDs
int end_terminal_curr_trip, start_terminal_next_trip; // End terminal of current trip and start terminal of the next trip
bool is_curr_trip_end_charge_terminal, is_next_trip_start_charge_terminal;
int charge_time_window; // Idle time during which charging is allowed
// Scan through the trips in the rotation
for (int i = 1; i<vehicle[v].trip_id.size()-2; ++i) {
curr_trip = vehicle[v].trip_id[i];
next_trip = vehicle[v].trip_id[i+1];
end_terminal_curr_trip = trip[curr_trip-1].end_terminal; // End terminal id of current trip
start_terminal_next_trip = trip[next_trip-1].start_terminal; // Start terminal id of next trip
is_curr_trip_end_charge_terminal = terminal[end_terminal_curr_trip-1].is_charge_station;
is_next_trip_start_charge_terminal = terminal[start_terminal_next_trip-1].is_charge_station;
charge_time_window = trip[curr_trip-1].idle_time[next_trip-1];
charge_level_until_depot =
charge_level-(trip[curr_trip-1].deadhead_distance[last_trip-1])*ENERGY_PER_KM;
if (charge_level_until_depot>=MIN_CHARGE_LEVEL)
split_trip_index = i;
if (not evaluation::is_charge_adequate_next_trip(trip, curr_trip, next_trip,
is_curr_trip_end_charge_terminal, is_next_trip_start_charge_terminal, charge_time_window,
charge_level)) {
is_splitting_required = true;
break;
}
}
// If splitting is required, we need not check for charge feasibility from the last actual trip to depot
if (not is_splitting_required) {
// Add distance from the last actual trip to the depot to cumulative_energy
int penultimate_trip = vehicle[v].trip_id[vehicle[v].trip_id.size()-2];
charge_level -= trip[penultimate_trip-1].deadhead_distance[last_trip-1]*ENERGY_PER_KM;
if (charge_level<MIN_CHARGE_LEVEL)
is_splitting_required = true;
}
if (is_splitting_required) {
if (split_trip_index==-1) {
logger.log(LogLevel::Error, "Splitting is required, but split trip index not found...");
return false;
}
locations::split_trips(vehicle, scan_eligible_vehicle_indices, v, split_trip_index);
}
}
return true;
}
// Function to open charging stations and check if savings can be achieved from scheduling operators
void locations::open_charging_station(std::vector<Vehicle>& vehicle, std::vector<Trip>& trip,
std::vector<Terminal>& terminal, ProcessedData& processed_data, int open_terminal_id)
{
logger.log(LogLevel::Info,
"Checking for improvement from opening charge station at terminal ID "
+std::to_string(open_terminal_id));
logger.log(LogLevel::Info, "Before opening the charging station...");
double best_objective = evaluation::calculate_objective(vehicle, trip, terminal, processed_data);
// Opening charging station will increase the cost. Check if savings can be achieved from scheduling operators
logger.log(LogLevel::Info, "After opening the charging station...");
terminal[open_terminal_id-1].is_charge_station = true; // Open the terminal with the chosen index
logger.log(LogLevel::Info, "Applying local search operators to adjust scheduling...");
std::vector<Vehicle> vehicle_copy = vehicle; // Create a copy of the vehicle vector to check for savings
scheduling::optimize_rotations(vehicle_copy, trip, terminal, processed_data);
// Check if the current objective is better than the best objective that we started with
double curr_objective = evaluation::calculate_objective(vehicle_copy, trip, terminal, processed_data);
if (curr_objective<best_objective) {
logger.log(LogLevel::Info, "Objective improved by "+std::to_string(best_objective-curr_objective));
vehicle = vehicle_copy;
++processed_data.num_successful_openings;
// processed_data.successful_openings[open_terminal_id] = 1;
}
else {
logger.log(LogLevel::Info, "No improvement from opening the charging station. Reverting changes...");
terminal[open_terminal_id-1].is_charge_station = false;
}
}
// Function to open multiple charging stations and check if savings can be achieved from scheduling operators
void locations::open_charging_stations(std::vector<Vehicle>& vehicle, std::vector<Trip>& trip,
std::vector<Terminal>& terminal, ProcessedData& processed_data, std::vector<int>& open_terminal_ids)
{