-
Notifications
You must be signed in to change notification settings - Fork 0
/
Main.py
1815 lines (1458 loc) · 85.1 KB
/
Main.py
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
from replit import clear
from haversine import haversine, Unit
import copy
import matplotlib.pyplot as plt
import networkx as nx
import random
import math
import queue
from scipy.stats.morestats import probplot
from Generator import Generator
from NodeStatus import NodeStatus
from NodeType import NodeType
clear()
instance_generated = False
menu_options = {
1: 'Generate new instance',
2: 'Import an instance',
3: 'Export current instance',
4: 'Show instance data',
5: 'Create a solution',
6: 'Improve a solution',
7: 'Print solutions results',
0: 'Exit'
}
menu_options_show_instance = {
1: 'Show distance histogram',
2: 'Show angle histogram',
3: 'Show polar coordinates',
4: 'Show cartesian coordinates',
5: 'Print total distance',
6: 'Show graph',
7: 'Show map',
0: 'Back'
}
def print_menu():
print('Menu\n')
for key in menu_options.keys():
if (key == 3 or key == 4 or key == 5 or key == 6 or key == 7) and instance_generated == False:
continue
print(str(key) + ')', menu_options[key])
print()
def print_menu_show_instance():
print('Show instance\n')
for key in menu_options_show_instance.keys():
print(str(key) + ')', menu_options_show_instance[key])
print()
def print_menu_create_solution(results):
print('Choose the solution\n')
random_greedy_string = 'Random Greedy'
if 'random_greedy' in results:
random_greedy_string = random_greedy_string + ' (' + str(results['random_greedy']) + 'km)'
nearest_neighbor_string = 'Nearest Neighbor'
if 'nearest_neighbor' in results:
nearest_neighbor_string = nearest_neighbor_string + ' (' + str(results['nearest_neighbor']) + 'km)'
clarke_wright_string = 'Clarke & Wright'
if 'clarke_wright' in results:
clarke_wright_string = clarke_wright_string + ' (' + str(results['clarke_wright']) + 'km)'
genetic_algorithm_string = 'Genetic Algorithm'
if 'genetic_algorithm' in results:
genetic_algorithm_string = genetic_algorithm_string + ' (' + str(results['genetic_algorithm']) + 'km)'
ant_colony_optimization_string = 'Ant Colony Optimization'
if 'ant_colony_optimization' in results:
ant_colony_optimization_string = ant_colony_optimization_string + ' (' + str(results['ant_colony_optimization']) + 'km)'
menu_options_show_solutions = {
1: random_greedy_string,
2: nearest_neighbor_string,
3: clarke_wright_string,
4: genetic_algorithm_string,
5: ant_colony_optimization_string,
0: 'Back',
}
for key in menu_options_show_solutions.keys():
print(str(key) + ')', menu_options_show_solutions[key])
print()
def print_menu_improve_solution(results):
print('Choose the improvement\n')
string_exchange_string = 'String Exchange'
if 'string_exchange' in results:
string_exchange_string = string_exchange_string + ' (' + str(results['string_exchange']) + 'km)'
string_relocation_string = 'String Relocation'
if 'string_relocation' in results:
string_relocation_string = string_relocation_string + ' (' + str(results['string_relocation']) + 'km)'
simulated_annealing_string = 'Simulated Annealing'
if 'simulated_annealing' in results:
simulated_annealing_string = simulated_annealing_string + ' (' + str(results['simulated_annealing']) + 'km)'
tabu_search_string = 'Tabu Search'
if 'tabu_search' in results:
tabu_search_string = tabu_search_string + ' (' + str(results['tabu_search']) + 'km)'
menu_options_improve_solution = {
1: string_exchange_string,
2: string_relocation_string,
3: simulated_annealing_string,
4: tabu_search_string,
0: 'Back',
}
for key in menu_options_improve_solution.keys():
print(str(key) + ')', menu_options_improve_solution[key])
print()
def generate_instance():
global instance_generated, generator, drivers_coordinates, passengers_coordinates, ORIGIN, upp
clear()
print('Insert instance data\n')
origin_latitude = float(input('Latitude of origin (44.83436): ') or 44.83436)
origin_longitude = float(input('Longitude of origin (11.59934): ') or 11.59934)
ORIGIN = (origin_latitude, origin_longitude)
students_number = int(input('Insert students number (100): ') or 100)
drivers_percentage = float(input('Insert drivers percentage (25): ') or 25)
mean = float(input('Insert students distribution mean (1000): ') or 1000)
sd = float(input('Insert students distribution standard deviation (5000): ') or 5000)
low = float(input('Insert students truncated distribution lower limit (1000): ') or 1000)
upp = float(input('Insert students truncated distribution upper limit (10000): ') or 10000)
generator = Generator(ORIGIN, students_number, drivers_percentage, mean, sd, low, upp)
students_coordinates = generator.get_students_coordinates()
drivers_coordinates = generator.get_drivers_coordinates(students_coordinates)
passengers_coordinates = generator.get_passengers_coordinates(students_coordinates)
instance_generated = True
def random_greedy(G):
origin_node = generator.get_origin_node()
drivers = []
passengers = []
routes = []
# Creo le routes per i driver con come elemento iniziale il loro id
# Riempio la lista dei driver e quella dei passeggeri con come elementi i loro id
for node in G.nodes(data=True):
if node[1]['t'] == NodeType.DRIVER.value:
drivers.append(node[0])
routes.insert(node[0]-1, [node[0]])
elif node[1]['t'] == NodeType.PASSENGER.value:
passengers.append(node[0])
# Se la lista dei passeggeri non è vuota
if len(passengers) > 0:
# Allora ciclo fino alla capienza massima che può raggiungere ogni driver
for _ in range(4):
# Ciclo per ogni driver
for driver in drivers:
# Se sono ancora disponibili dei passeggeri
if len(passengers) > 0:
# Allora casualmente decido se aggiungerlo o meno alla route del driver corrente
new_passenger = random.choice([True, False])
# Se ho deciso di aggiungere un passeggero alla route del driver corrente
if new_passenger:
# Allora seleziono casualmente un passeggero tra quelli ancora disponibili
next_passenger = random.choice(passengers)
# Aggiungo il passeggero in coda alla route del driver corrente
routes[driver-1].append(next_passenger)
# Rimuovo il passeggero selezionato dalla lista dei passeggeri ancora disponibili
passengers.remove(next_passenger)
# Rimuovo dalla soluzione ammissibile iniziale a stella l'arco che collega il passeggero all'università
# Questo arco verrà aggiunto forse in seguito solo se questo passeggero sarà l'ultimo prima dell'università
G.remove_edge(next_passenger, origin_node[0])
# Controllo se il driver è collegato con un arco direttamente all'università
if G.has_edge(driver, origin_node[0]):
# Se il driver ha un arco collegato diretto verso l'università allora lo cancello
G.remove_edge(driver, origin_node[0])
# Ciclo sulle routes dei driver generate
add_edges = []
for route in routes:
# Ad ogni route aggiungo in coda l'id del nodo dell'università
route.append(origin_node[0])
# Per ogni arco ne calcolo la lunghezza per salvarla come metadato sul grafo
for idx, node in enumerate(route):
try:
node_i = route[idx]
node_j = route[idx+1]
if not G.has_edge(node_i, node_j):
add_edges.append((node_i, node_j, { 'distance': int(round(haversine(G.nodes[node_i]['g'], G.nodes[node_j]['g'], unit=Unit.METERS))) }))
except:
pass
G.add_edges_from(add_edges)
return G
def nearest_neighbor(G):
origin_node = generator.get_origin_node()
generator.show_graph(G)
drivers = {}
for node in G.nodes(data=True):
if node[1]['t'] == NodeType.DRIVER.value:
drivers[node[0]] = {}
drivers[node[0]]['snake'] = [node]
drivers[node[0]]['distance'] = int(round(haversine(node[1]['g'], origin_node[1]['g'], unit=Unit.METERS)))
drivers_ordered = dict(sorted(drivers.items(), key=lambda item: item[1]['distance'], reverse=True))
partial_distance = []
G2, partial_distance = get_neighbor(G, origin_node, drivers_ordered, partial_distance)
return G2, partial_distance
def get_neighbor(G1, origin_node, drivers, partial_distance):
drivers = dict(sorted(drivers.items(), key=lambda item: item[1]['distance'], reverse=True))
all_origin = False
all_full = False
break_full = False
break_origin = False
for idx, snake in drivers.copy().items():
driver_full = False
driver_arrived = False
if len(snake['snake']) == 5:
if not break_full:
all_full = True
driver_full = True
else:
all_full = False
break_full = True
if snake['snake'][-1][0] == origin_node[0]:
if not break_origin:
all_origin = True
driver_arrived = True
else:
all_origin = False
break_origin = True
if driver_full or driver_arrived:
drivers.pop(idx)
if all_full and all_origin or not bool(drivers):
return G1, partial_distance
driver = next(iter(drivers.items()))
driver_node = driver[1]['snake'][-1]
neighbors = []
for node in G1.nodes(data=True):
if driver[1]['snake'][-1][0] != node[0] and node[1]['t'] != NodeType.DRIVER.value and node[1]['s'] != NodeStatus.VISITED.value:
neighbors.append((node, int(round(haversine(driver[1]['snake'][-1][1]['g'], node[1]['g'], unit=Unit.METERS)))))
if len(neighbors) == 0:
return G1, partial_distance
neighbors_sorted = sorted(neighbors, key=lambda x: x[1])
next_neighbor = neighbors_sorted[0][0]
driver[1]['snake'].append(next_neighbor)
new_distance = 0
old_distance = driver[1]['distance']
for idx, node in enumerate(driver[1]['snake']):
node_i = driver[1]['snake'][idx]
try:
node_j = driver[1]['snake'][idx+1]
new_distance = new_distance + int(round(haversine(node_i[1]['g'], node_j[1]['g'], unit=Unit.METERS)))
distance_recalculated = True
except:
pass
if distance_recalculated == True:
driver[1]['distance'] = new_distance
else:
driver[1]['distance'] = old_distance
if G1.has_edge(driver_node[0], origin_node[0]):
G1.remove_edge(driver_node[0], origin_node[0])
add_edges = []
if not G1.has_edge(driver_node[0], next_neighbor[0]):
add_edges.append((driver_node[0], next_neighbor[0], { 'distance': generator.get_distance_geographic(driver_node, next_neighbor) }))
if next_neighbor[0] != origin_node[0]:
G1.nodes[next_neighbor[0]].update({ 's': NodeStatus.VISITED.value })
G1.add_edges_from(add_edges)
# total_distance_G = generator.get_total_distance(G)
total_distance_G1 = generator.get_total_distance(G1)
# if total_distance_G1 < total_distance_G:
# G_updated = G1
# partial_distance.append(total_distance_G1)
# else:
# G_updated = G
# partial_distance.append(total_distance_G)
partial_distance.append(total_distance_G1)
drivers[driver[0]] = driver[1]
generator.show_graph(G1, True)
return get_neighbor(G1, origin_node, drivers, partial_distance)
def clarke_wright(G):
origin_node = generator.get_origin_node()
generator.show_graph(G)
drivers = {}
for node in G.nodes(data=True):
if node[1]['t'] == NodeType.DRIVER.value:
drivers[node[0]] = {}
drivers[node[0]]['snake'] = [node]
drivers[node[0]]['distance'] = int(round(haversine(node[1]['g'], origin_node[1]['g'], unit=Unit.METERS)))
drivers_ordered = dict(sorted(drivers.items(), key=lambda item: item[1]['distance'], reverse=True))
partial_distance = []
G2, partial_distance = get_saver(G, origin_node, drivers_ordered, partial_distance)
return G2, partial_distance
def get_saver(G, origin_node, drivers, partial_distance):
drivers = dict(sorted(drivers.items(), key=lambda item: item[1]['distance'], reverse=True))
all_origin = False
all_full = False
break_full = False
break_origin = False
for idx, snake in drivers.copy().items():
driver_full = False
driver_arrived = False
if len(snake['snake']) == 5:
if not break_full:
all_full = True
driver_full = True
else:
all_full = False
break_full = True
if snake['snake'][-1][0] == origin_node[0]:
if not break_origin:
all_origin = True
driver_arrived = True
else:
all_origin = False
break_origin = True
if driver_full or driver_arrived:
drivers.pop(idx)
if all_full and all_origin or not bool(drivers):
return G, partial_distance
driver = next(iter(drivers.items()))
driver_node = driver[1]['snake'][-1]
savers = []
for node in G.nodes(data=True):
if driver[1]['snake'][-1][0] != node[0] and node[1]['t'] != NodeType.DRIVER.value and node[1]['s'] != NodeStatus.VISITED.value:
driver_origin_distance = haversine(driver[1]['snake'][-1][1]['g'], origin_node[1]['g'], unit=Unit.METERS)
driver_passenger_distance = haversine(driver[1]['snake'][-1][1]['g'], node[1]['g'], unit=Unit.METERS)
distance_formula = driver_origin_distance - driver_passenger_distance
savers.append((node, distance_formula))
if len(savers) == 0:
return G, partial_distance
savers_sorted = sorted(savers, key=lambda x: x[1], reverse=True)
if savers_sorted[0][1] >= 0:
next_saver = savers_sorted[0][0]
else:
next_saver = origin_node
driver[1]['snake'].append(next_saver)
new_distance = 0
old_distance = driver[1]['distance']
for idx, node in enumerate(driver[1]['snake']):
node_i = driver[1]['snake'][idx]
try:
node_j = driver[1]['snake'][idx+1]
new_distance = new_distance + int(round(haversine(node_i[1]['g'], node_j[1]['g'], unit=Unit.METERS)))
distance_recalculated = True
except:
pass
if distance_recalculated == True:
driver[1]['distance'] = new_distance
else:
driver[1]['distance'] = old_distance
if G.has_edge(driver_node[0], origin_node[0]):
G.remove_edge(driver_node[0], origin_node[0])
add_edges = []
if not G.has_edge(driver_node[0], next_saver[0]):
add_edges.append((driver_node[0], next_saver[0], { 'distance': generator.get_distance_geographic(driver_node, next_saver) }))
if next_saver[0] != origin_node[0]:
G.nodes[next_saver[0]].update({ 's': NodeStatus.VISITED.value })
G.add_edges_from(add_edges)
total_distance_G = generator.get_total_distance(G)
partial_distance.append(total_distance_G)
drivers[driver[0]] = driver[1]
generator.show_graph(G, update=True)
return get_saver(G, origin_node, drivers, partial_distance)
def genetic_algorithm(G):
I = 0
Imax = 100
stall = 0
stall_max = 10
population_size = 100
population = []
Pc = 0.9
Pm = 0.5
# Genero population_size grafi tramite metodo random_greedy() e li aggiungo alla lista population insieme alla loro distanza totale
for _ in range(population_size):
G1 = copy.deepcopy(G)
G2 = random_greedy(G1)
population.append((G2, generator.get_total_distance(G2)))
partial_distance = []
# Ciclo finchè non raggiungo il numero di iterazioni massime oppure uno stallo
while I < Imax and stall < stall_max:
# Ordino in ordine decrescente per distanza totale la popolazione dei grafi
population_ordered = list(sorted(population, key=lambda x: x[1], reverse=True))
# Assegno a stall_value l'elemento migliore della popolazione
stall_value = population_ordered[-1][1]
# Seleziono l'elemento migliore della popolazione corrente per poi riinserirlo dopo la fase di mutazione
elitism = population_ordered[-1]
# Calcolo la sommatoria dei rank totali
n = len(population_ordered)
rank_sum = n*(n+1)/2
# Per ogni elemento della popolazione associo una probabilità di essere scelto come genitore per la fase di crossover
fitness_sum_rank = []
for idx, i in enumerate(population_ordered, 1):
a = i
c = idx/rank_sum
if idx > 1:
c = c + fitness_sum_rank[-1][2] if idx > 0 else 0
fitness_sum_rank.append(a + (c,))
couples = []
parents = ()
m = 0
parent_dict = dict.fromkeys(range(len(fitness_sum_rank)), 0)
# Ciclo finché non genero m coppie di genitori per la fase di crossover
while m < len(fitness_sum_rank):
# Genero una probabilità casuale
p = random.uniform(0, 1)
# Per ogni elemento della popolazione cerco quello più aderente alla probabilità generata
for idx, i in enumerate(fitness_sum_rank):
# Se la probabilità dell'elemento corrente è inferiore a quella generata continuo
if i[2] <= p:
continue
else:
# Altrimenti se la coppia è formata da almeno 2 genitori
if len(parents) % 2 == 0 and len(parents) > 0:
# Se la coppia di genitori è già presente allora la svuoto per generarne un'altra
# Quindi prima cerco se la coppia generata è un duplicato
duplicates = False
for p1, p2 in couples:
if parents[0] == p1 and parents[1] == p2:
duplicates = True
break
# Se la coppia generata non è un duplicato allora la aggiungo a couples, svuoto la coppia parents per pi inserirci il genitore selezionato al ciclo attuale
# incremento m di 2 ed incremento il dizionario dei genitori selezionati per tenere traccia di quante volte i genitori siano stati selezionati
if not duplicates:
couples.append(parents)
parents = ()
parents = parents + (idx,)
m = m+2
parent_dict[idx] += 1
else:
parents = ()
break
else:
# Se la coppia ha solo un genitore e il suo partner è se stesso fermo il ciclo altrimenti lo aggiungo alla coppia
if len(parents) == 1 and idx == parents[0]:
break
parents = parents + (idx,)
parent_dict[idx] += 1
break
next_population = []
origin_node = generator.get_origin_node()
# Per ogni coppia generata genero un paio di figli che andranno a formare la popolazione all'iterazione successiva
first_passenger_id = []
for idd, (p1, p2) in enumerate(couples):
# Seleziono i grafi dei genitori della coppia
gp1 = fitness_sum_rank[p1][0]
gp2 = fitness_sum_rank[p2][0]
first_passenger_id.append([])
# Per ogni grafo dei della coppia selezionata genero le rispettive routes
routes = []
for idx, g in enumerate([gp1, gp2]):
routes.append([])
first_passenger_found = False
# Per ogni nodo del grafo del genitore corrente della coppia
for node in g.nodes(data=True):
# Se il nodo selezionato è un driver oppure è un passenger di grado 1 quindi solo collegato direttamente all'origine
if node[1]['t'] == NodeType.DRIVER.value or (node[1]['t'] == NodeType.PASSENGER.value and g.degree[node[0]] == 1):
# Se il nodo corrente è un passeggero di grado 1 che quindi punta all'origine e non è stato trovato prima un passeggero tale
if node[1]['t'] == NodeType.PASSENGER.value and g.degree[node[0]] == 1 and not first_passenger_found:
# Allora ne seleziono l'id
first_passenger_id[idd].append(node[0])
first_passenger_found = True
# In paths memorizzo tutti i percorsi che vanno dal nodo corrente al nodo origine del grafo del genitore
paths = nx.all_simple_paths(g, node[0], origin_node[0])
# Ogni percorso lo memorizzo nella lista routes con indice quello del genitore corrente
for path in list(paths):
routes[idx].append(path)
# Probabilità di eseguire il crossover o meno
crossover_probability = random.uniform(0, 1)
# Se il numero generato casualmente è inferiore a Pc allora eseguo il crossover
if crossover_probability < Pc:
# In o1 e o2 memorizzo le routes dei genitori
o1 = copy.deepcopy(routes[0])
o2 = copy.deepcopy(routes[1])
# Seleziono casualmente un path in entrambe le routes
edges_removed_1 = None
while edges_removed_1 == None:
sub_route_1 = random.choice(routes[0])
# Se il primo nodo del path selezionato casualmente è un driver e il path ha almeno un passeggero
if gp1.nodes[sub_route_1[0]]['t'] == NodeType.DRIVER.value and len(sub_route_1) > 2:
# Rimuovo i passeggeri tra il driver e l'origine
edges_removed_1 = o1[o1.index(sub_route_1)][1:-1]
# Altrimenti se è un nodo passeggero
elif gp1.nodes[sub_route_1[0]]['t'] == NodeType.PASSENGER.value:
# Rimuovo dall'altro figlio il passeggero appena selezionato
edges_removed_1 = o1[o1.index(sub_route_1)][:-1]
edges_removed_2 = None
while edges_removed_2 == None:
sub_route_2 = random.choice(routes[1])
if gp2.nodes[sub_route_2[0]]['t'] == NodeType.DRIVER.value and len(sub_route_2) > 2:
edges_removed_2 = o2[o2.index(sub_route_2)][1:-1]
elif gp2.nodes[sub_route_2[0]]['t'] == NodeType.PASSENGER.value:
edges_removed_2 = o2[o2.index(sub_route_2)][:-1]
if edges_removed_1:
# Per ogni path in routes di un figlio
for i in o2:
# Per ogni nodo da rimuovere
for x in edges_removed_1:
try:
# Se le lunghezza del path è maggiore di 2 quindi ha almeno un passeggero provo a rimuoverlo
if len(i) > 2:
i.remove(x)
else:
# ALtrimenti se il path è composto solo da un passeggero e l'origine e il nodo da rimuovere è presente nel path rimuovo direttamente il path
if x in i:
o2.remove(i)
# break?
except:
pass
if edges_removed_2:
for i in o1:
for x in edges_removed_2:
try:
if len(i) > 2:
i.remove(x)
else:
if x in i:
o1.remove(i)
# break?
except:
pass
skip_graph_equals = False
o2_loop = copy.deepcopy(o2)
# Per ogni nodo da rimuovere nell'altro fratello
for i in edges_removed_1:
g_cost_array = []
insert_possible = False
skip_graph_equals = False
# Per ogni route del fratello
for ido, j in enumerate(o2_loop):
if skip_graph_equals:
break
# Se la route corrente del fratello è di lunghezza inferiore a 6 allora ci sarebbe posto per un altro passeggero
if len(j) < 6:
if not insert_possible:
insert_possible = True
# Per ogni nodo della route del fratello vado a provare ad inserire il passeggero corrente i
for idx, _ in enumerate(range(len(j))):
o2_temp = copy.deepcopy(o2_loop)
# Ovviamente andrò a provare ad inserire il passeggero corrente i dopo il driver e prima dell'origine
if idx > 0:
# Quindi se l'indice della route è maggiore di 0 proverò al primo ciclo ad inserirlo nella prima posizione dopo il driver
if 0 <= 1 < len(first_passenger_id[idd]) and j[0] < first_passenger_id[idd][1]:
o2_temp[ido].insert(idx, i)
else:
o2_temp.append([i, origin_node[0]])
skip_graph_equals = True
# Pulisco il grafo del fratello da tutti gli archi
g_temp = nx.create_empty_copy(g)
add_edges = []
# Quindi per ogni possibile configurazione del passeggero nella route corrente del fratello vado a crearci un nuovo grafo
# Qui creo gli archi del grafo e ne calcolo la lunghezza che poi assegno
for route in o2_temp:
for idx, node in enumerate(route):
try:
node_i = route[idx]
node_j = route[idx+1]
if not g_temp.has_edge(node_i, node_j):
add_edges.append((node_i, node_j, { 'distance': int(round(haversine(g_temp.nodes[node_i]['g'], g_temp.nodes[node_j]['g'], unit=Unit.METERS))) }))
except:
pass
g_temp.add_edges_from(add_edges)
generator.show_graph(g_temp, True, True, 0.01)
# Aggiungo alla lista g_cost_array le routes, il grafo e la sua distanza totale per la corrente configurazione
g_cost_array.append((o2_temp, g_temp, generator.get_total_distance(g_temp)))
# Se l'inserimento del nodo corrente in almeno una route di un driver non è stato possibile allora ne creo una nuova che puntrà direttamente all'origine
# Questo vorrebbe dire che il grafo fratello avrebbe tutte route dei driver di massima capacità
if insert_possible == False:
o2_temp = copy.deepcopy(o2_loop)
o2_temp.append([i, origin_node[0]])
g_temp = nx.create_empty_copy(g, with_data=True)
add_edges = []
for route in o2_temp:
for idx, node in enumerate(route):
try:
node_i = route[idx]
node_j = route[idx+1]
if not g_temp.has_edge(node_i, node_j):
add_edges.append((node_i, node_j, { 'distance': int(round(haversine(g_temp.nodes[node_i]['g'], g_temp.nodes[node_j]['g'], unit=Unit.METERS))) }))
except:
pass
g_temp.add_edges_from(add_edges)
g_cost_array.append((o2_temp, g_temp, generator.get_total_distance(g_temp)))
# Una volta provate tutte le configurazioni dell'inserimento del nodo corrente e memorizzate nella lista g_cost_array le ordino in modo crescente per distanza totale del grafo corrispondente
g_cost_array_ordered = sorted(g_cost_array, key=lambda item: item[2])
# Una volta trovata la configurazione migliore per il nodo corrente i nel grafo del fratello riassegno il grafo aggiornato per passare al nodo successivo i di indice successivo
o2_loop = copy.deepcopy(g_cost_array_ordered[0][0])
# Una volta inseriti tutti i nodi nelle configurazioni migliori del fratello queso lo aggiungo alla successiva popolazione
next_population.append((g_cost_array_ordered[0][0], g_cost_array_ordered[0][1], g_cost_array_ordered[0][2]))
o1_loop = copy.deepcopy(o1)
for i in edges_removed_2:
g_cost_array = []
insert_possible = False
skip_graph_equals = False
for ido, j in enumerate(o1_loop):
if skip_graph_equals:
break
if len(j) < 6:
if not insert_possible:
insert_possible = True
for idx, _ in enumerate(range(len(j))):
o1_temp = copy.deepcopy(o1_loop)
if idx > 0:
if 0 <= 1 < len(first_passenger_id[idd]) and j[0] < first_passenger_id[idd][0]:
o1_temp[ido].insert(idx, i)
else:
o1_temp.append([i, origin_node[0]])
skip_graph_equals = True
g_temp = nx.create_empty_copy(g)
add_edges = []
for route in o1_temp:
for idx, node in enumerate(route):
try:
node_i = route[idx]
node_j = route[idx+1]
if not g_temp.has_edge(node_i, node_j):
add_edges.append((node_i, node_j, { 'distance': int(round(haversine(g_temp.nodes[node_i]['g'], g_temp.nodes[node_j]['g'], unit=Unit.METERS))) }))
except:
pass
g_temp.add_edges_from(add_edges)
g_cost_array.append((o1_temp, g_temp, generator.get_total_distance(g_temp)))
if insert_possible == False:
o1_temp = copy.deepcopy(o1_loop)
o1_temp.append([i, origin_node[0]])
g_temp = nx.create_empty_copy(g, with_data=True)
add_edges = []
for route in o1_temp:
for idx, node in enumerate(route):
try:
node_i = route[idx]
node_j = route[idx+1]
if not g_temp.has_edge(node_i, node_j):
add_edges.append((node_i, node_j, { 'distance': int(round(haversine(g_temp.nodes[node_i]['g'], g_temp.nodes[node_j]['g'], unit=Unit.METERS))) }))
except:
pass
g_temp.add_edges_from(add_edges)
g_cost_array.append((o1_temp, g_temp, generator.get_total_distance(g_temp)))
g_cost_array_ordered = sorted(g_cost_array, key=lambda item: item[2])
o1_loop = copy.deepcopy(g_cost_array_ordered[0][0])
next_population.append((g_cost_array_ordered[0][0], g_cost_array_ordered[0][1], g_cost_array_ordered[0][2]))
else:
# Se la probabilità del crossover fosse inferiore a Pc allora aggiungerò alla popolazione i genitori stessi
next_population.append((routes[0], population_ordered[p1][0], population_ordered[p1][1]))
next_population.append((routes[1], population_ordered[p2][0], population_ordered[p2][1]))
next_population_mutated = copy.deepcopy(next_population)
for idx, _ in enumerate(next_population_mutated):
mutation_probability = random.uniform(0, 1)
if mutation_probability < Pm:
route_to_mutate = random.choice(next_population_mutated[idx][0])
if len(route_to_mutate) > 4:
route_mutated = copy.deepcopy(route_to_mutate)
node_i = random.choice(route_mutated[1:-1])
node_j = node_i
while node_i == node_j:
node_j = random.choice(route_mutated[1:-1])
node_i_index, node_j_index = route_mutated.index(node_i), route_mutated.index(node_j)
route_mutated[node_j_index], route_mutated[node_i_index] = route_mutated[node_i_index], route_mutated[node_j_index]
next_population_mutated[idx][0].remove(route_to_mutate)
next_population_mutated[idx][0].insert(route_mutated[0]-1, route_mutated)
population = []
for idx, i in enumerate(next_population_mutated):
population.append((next_population_mutated[idx][1], next_population_mutated[idx][2]))
population_ordered = list(sorted(population, key=lambda x: x[1], reverse=True))
if population_ordered[-1][1] < stall_value:
stall = 0
else:
stall = stall + 1
population_ordered[0] = copy.deepcopy(elitism)
I = I + 1
partial_distance.append(population_ordered[-1][1])
return population_ordered[-1][0], partial_distance
def get_next_node(cursor, G_complete, id_ant, node_visited, alpha, beta, gamma):
origin_node = generator.get_origin_node()
# Costruisco il denominatore della formula quindi per ogni vicino del cursore non ancora visitato da altri driver
matrix_cost = {}
matrix_cost[cursor] = {}
denominator = 0
for neighbor in G_complete.neighbors(cursor):
if not node_visited[id_ant][neighbor] and G_complete.nodes[neighbor]['s'] == NodeStatus.FREE.value:
pheromone = G_complete[cursor][neighbor]['pheromone']
distance = G_complete[cursor][neighbor]['distance']
if neighbor != origin_node[0]:
saving = int(round(haversine(G_complete.nodes[cursor]['g'], origin_node[1]['g'], unit=Unit.METERS) - haversine(G_complete.nodes[cursor]['g'], G_complete.nodes[neighbor]['g'], unit=Unit.METERS)))
# saving = haversine(G_complete.nodes[cursor]['g'], origin_node[1]['g'], unit=Unit.METERS) / haversine(G_complete.nodes[cursor]['g'], G_complete.nodes[neighbor]['g'], unit=Unit.METERS)
else:
saving = int(round(haversine(G_complete.nodes[cursor]['g'], origin_node[1]['g'], unit=Unit.METERS)))
# saving = 1
matrix_cost[cursor][neighbor] = (pheromone, distance)
denominator = denominator + (pow(pheromone, alpha) * pow(1/distance, beta) * pow(saving, gamma))
# Una volta ottenuto il denominatore posso calcolare la formula
matrix_probability = {}
for neighbor in G_complete.neighbors(cursor):
if not node_visited[id_ant][neighbor] and G_complete.nodes[neighbor]['s'] == NodeStatus.FREE.value:
pheromone = matrix_cost[cursor][neighbor][0]
distance_inverse = 1/matrix_cost[cursor][neighbor][1]
if neighbor != origin_node[0]:
saving = int(round(haversine(G_complete.nodes[cursor]['g'], origin_node[1]['g'], unit=Unit.METERS) - haversine(G_complete.nodes[cursor]['g'], G_complete.nodes[neighbor]['g'], unit=Unit.METERS)))
# saving = haversine(G_complete.nodes[cursor]['g'], origin_node[1]['g'], unit=Unit.METERS) / haversine(G_complete.nodes[cursor]['g'], G_complete.nodes[neighbor]['g'], unit=Unit.METERS)
else:
saving = int(round(haversine(G_complete.nodes[cursor]['g'], origin_node[1]['g'], unit=Unit.METERS)))
# saving = 1
matrix_probability[neighbor] = pow(pheromone, alpha) * pow(distance_inverse, beta) * pow(saving, gamma) / denominator
# Ordino la lista delle tuple (id_nodo, probabilità)
matrix_probability_sorted = sorted(matrix_probability.items(), key=lambda x: x[1])
n = len(matrix_probability_sorted)
rank_sum = n*(n+1)/2
roulette_wheel = []
for idx, i in enumerate(matrix_probability_sorted, 1):
a = i
c = idx/rank_sum
if idx > 1:
c = c + roulette_wheel[-1][2]
roulette_wheel.append(a + (c,))
probability = random.uniform(0, 1)
for slice_roulette_wheel in roulette_wheel:
if slice_roulette_wheel[2] <= probability:
continue
else:
return slice_roulette_wheel[0]
def ant_colony_optimization(G):
origin_node = generator.get_origin_node()
# Dichiaro valori per i parametri dell'algoritmo
starting_pheromon = 1
ants = 100
iterations = 10
p = 0.2
# Dichiaro i pesi dei parametri per la decisione del prossimo nodo: feromone, inverso della distanza e saving
alpha = 2
beta = 12
gamma = 4
# Creo una copia del grafo originale ma privo di archi
G_empty_copy = nx.create_empty_copy(G)
# Creo un grafo semi-completo ovvero con solo archi del tipo driver-passenger, driver-origin, passenger-passenger, passenger-origin
# Gli archi del grafo oltre che alla distanza tra i nodi adiacenti, avranno anche il valore iniziale di feromone
add_edges = []
for node_i in G_empty_copy.nodes(data=True):
for increment in G_empty_copy.nodes(data=True):
if node_i[0] != increment[0] and increment[1]['t'] != NodeType.DRIVER.value:
if not G_empty_copy.has_edge(node_i[0], increment[0]):
add_edges.append((node_i[0], increment[0], {
'pheromone': starting_pheromon,
'distance': int(round(haversine(node_i[1]['g'], increment[1]['g'], unit=Unit.METERS)))
}))
G_complete = copy.deepcopy(G_empty_copy)
G_complete.add_edges_from(add_edges)
# Insieme non ordinato privo di duplicati dei nodi già prenotati
node_occupied = set()
# Ordino i drivers per distanza dall'origine in ordine descrescente quindi dal più distante al più vicino
drivers = {}
for node in G_complete.nodes(data=True):
if node[1]['t'] == NodeType.DRIVER.value:
drivers[node[0]] = {}
drivers[node[0]]['snake'] = [node[0]]
drivers[node[0]]['distance'] = int(round(haversine(node[1]['g'], origin_node[1]['g'], unit=Unit.METERS)))
node_occupied.add(node[0])
drivers_ordered = dict(sorted(drivers.items(), key=lambda item: item[1]['distance'], reverse=True))
# Conto i nodi passeggeri non ancora visitati da altri driver TODO: Spostare fuori dai cicli
last_node_id = 0
for node in G_complete.nodes(data=True):
last_node_id = node[0]
# node_visited = [[True if x in drivers or [True if x in driver['snake'] else False for _, driver in drivers.items() ] else False for x in range(last_node_id+2)] for _ in range(ants)]
# Continuo a ciclare finché tutti i driver non sono arrivati all'origine
all_drivers_origin = False
while not all_drivers_origin:
# Ciclo per ogni driver ordinati al primo ciclo per distanza dall'origine mentre dal secondo in poi per distanza percorsa
for driver_id, driver in drivers_ordered.items():
# Se il driver corrente è arrivato in università continua il ciclo per passare al driver successivo
if driver['snake'][-1] == origin_node[0]:
continue
#
for _ in range(iterations):
# for _ in range(1):
# Matrice a 2 dimensioni dei nodi visitati dalle formiche: una riga per ogni formica, una colonna per ogni nodo
# Inizialmente questa matrice ha tutti i suoi elementi impostati a False compreso lo 0, eccetto le colonne corrispondenti ai driver impostate a True.
node_visited = []
# Per ogni formica
for y in range(ants):
# Crea un array di dimensione pari ai nodi presenti sul grafo
node_visited.append([])
# Per ogni nodo del grafo + 2
for x in range(last_node_id+2):
# Se il nodo corrente è presente nei nodi occupati allora imposto l'elemento della matrice a True
if x in node_occupied:
node_visited[y].insert(x, True)
# Altrimenti il nodo corrente è libero quindi lo imposto a False
else:
node_visited[y].insert(x, False)
# Matrice a 3 dimensioni: per ogni formica, creo una matrice a 2 dimensioni quadrata contenente inizialmente tutti None
# Questa matrice servirà a tenere traccia degli incrementi di feromone per ogni formica
tau_tensor = [[[None for _ in range(last_node_id+2)] for _ in range(last_node_id+2)] for _ in range(ants)]
# Matrice quadrata a 2 dimensioni contenente inizialmente tutti 0
update_matrix = [[0 for _ in range(last_node_id+2)] for _ in range(last_node_id+2)]
# Lista di routes una per ogni formica contenenti inizialmente il nodo cursore dove si trova attualmente il driver
ants_list = []
for i in range(ants):
ants_list.append([driver['snake'][-1]])
# Finchè tutte le formiche non sono arrivate in università
all_ants_origin = False
while not all_ants_origin:
# Ciclo per ogni formica
for id_ant, ant_path in enumerate(ants_list):
# Estraggo la posizione attuale del cursore della formica corrente
cursor = ant_path[-1]
# Se la formica è arrivata in università ripeti il ciclo passando alla formica successiva
if cursor == origin_node[0]:
continue
# Se la formica corrente ha una route composta da meno di 5 nodi (Es: [1,7,8,9, _]) posso inserire un ultimo passeggero
if len(ant_path) < 5:
# Calcolo prossimo nodo passeggero o università per la formica corrente
next_node = get_next_node(cursor, G_complete, id_ant, node_visited, alpha, beta, gamma)
# Altrimenti chiudo la route della formica corrente aggiungendoci l'id del nodo università
else:
next_node = origin_node[0]
# Aggiungo il nodo selezionato alla route della formica corrente
ants_list[id_ant].append(next_node)