pycombinatorial 1.8.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. pyCombinatorial/__init__.py +0 -0
  2. pyCombinatorial/algorithm/__init__.py +52 -0
  3. pyCombinatorial/algorithm/aco.py +117 -0
  4. pyCombinatorial/algorithm/alns.py +148 -0
  5. pyCombinatorial/algorithm/bb.py +81 -0
  6. pyCombinatorial/algorithm/bf.py +41 -0
  7. pyCombinatorial/algorithm/bhk.py +54 -0
  8. pyCombinatorial/algorithm/brkga.py +156 -0
  9. pyCombinatorial/algorithm/christofides.py +128 -0
  10. pyCombinatorial/algorithm/conc_hull.py +214 -0
  11. pyCombinatorial/algorithm/conv_hull.py +96 -0
  12. pyCombinatorial/algorithm/cw.py +136 -0
  13. pyCombinatorial/algorithm/eln.py +139 -0
  14. pyCombinatorial/algorithm/eo.py +153 -0
  15. pyCombinatorial/algorithm/ga.py +257 -0
  16. pyCombinatorial/algorithm/gksp.py +213 -0
  17. pyCombinatorial/algorithm/grasp.py +115 -0
  18. pyCombinatorial/algorithm/hpn.py +245 -0
  19. pyCombinatorial/algorithm/ins_c.py +99 -0
  20. pyCombinatorial/algorithm/ins_f.py +112 -0
  21. pyCombinatorial/algorithm/ins_n.py +112 -0
  22. pyCombinatorial/algorithm/ins_r.py +117 -0
  23. pyCombinatorial/algorithm/ksp.py +248 -0
  24. pyCombinatorial/algorithm/lns.py +120 -0
  25. pyCombinatorial/algorithm/mf.py +113 -0
  26. pyCombinatorial/algorithm/nn.py +101 -0
  27. pyCombinatorial/algorithm/opt_2.py +60 -0
  28. pyCombinatorial/algorithm/opt_2_5.py +90 -0
  29. pyCombinatorial/algorithm/opt_2_5s.py +94 -0
  30. pyCombinatorial/algorithm/opt_2s.py +85 -0
  31. pyCombinatorial/algorithm/opt_3.py +108 -0
  32. pyCombinatorial/algorithm/opt_3s.py +109 -0
  33. pyCombinatorial/algorithm/opt_4.py +170 -0
  34. pyCombinatorial/algorithm/opt_4s.py +173 -0
  35. pyCombinatorial/algorithm/opt_5.py +519 -0
  36. pyCombinatorial/algorithm/opt_5s.py +523 -0
  37. pyCombinatorial/algorithm/rl_double_ql.py +126 -0
  38. pyCombinatorial/algorithm/rl_ql.py +121 -0
  39. pyCombinatorial/algorithm/rl_sarsa.py +132 -0
  40. pyCombinatorial/algorithm/rt.py +93 -0
  41. pyCombinatorial/algorithm/s_gui.py +110 -0
  42. pyCombinatorial/algorithm/s_itr.py +103 -0
  43. pyCombinatorial/algorithm/s_sct.py +116 -0
  44. pyCombinatorial/algorithm/s_shc.py +87 -0
  45. pyCombinatorial/algorithm/s_tabu.py +213 -0
  46. pyCombinatorial/algorithm/s_vns.py +74 -0
  47. pyCombinatorial/algorithm/sa.py +132 -0
  48. pyCombinatorial/algorithm/som.py +122 -0
  49. pyCombinatorial/algorithm/spfc_h.py +114 -0
  50. pyCombinatorial/algorithm/spfc_m.py +106 -0
  51. pyCombinatorial/algorithm/spfc_s.py +116 -0
  52. pyCombinatorial/algorithm/swp.py +121 -0
  53. pyCombinatorial/algorithm/tat.py +100 -0
  54. pyCombinatorial/algorithm/tbb.py +104 -0
  55. pyCombinatorial/utils/__init__.py +2 -0
  56. pyCombinatorial/utils/graphs.py +164 -0
  57. pyCombinatorial/utils/util.py +79 -0
  58. pycombinatorial-1.8.2.dist-info/LICENSE +14 -0
  59. pycombinatorial-1.8.2.dist-info/METADATA +130 -0
  60. pycombinatorial-1.8.2.dist-info/RECORD +62 -0
  61. pycombinatorial-1.8.2.dist-info/WHEEL +5 -0
  62. pycombinatorial-1.8.2.dist-info/top_level.txt +1 -0
File without changes
@@ -0,0 +1,52 @@
1
+ from .aco import ant_colony_optimization
2
+ from .alns import adaptive_large_neighborhood_search
3
+ from .bb import branch_and_bound
4
+ from .bf import brute_force_analysis
5
+ from .bhk import bellman_held_karp_exact_algorithm
6
+ from .brkga import biased_random_key_genetic_algorithm
7
+ from .christofides import christofides_algorithm
8
+ from .conc_hull import concave_hull_algorithm
9
+ from .conv_hull import convex_hull_algorithm
10
+ from .cw import clarke_wright_savings
11
+ from .eln import elastic_net_tsp
12
+ from .eo import extremal_optimization
13
+ from .ga import genetic_algorithm
14
+ from .grasp import greedy_randomized_adaptive_search_procedure
15
+ from .gksp import greedy_karp_steele_patching
16
+ from .hpn import hopfield_network_tsp
17
+ from .ins_c import cheapest_insertion
18
+ from .ins_f import farthest_insertion
19
+ from .ins_n import nearest_insertion
20
+ from .ins_r import random_insertion
21
+ from .ksp import karp_steele_patching
22
+ from .lns import large_neighborhood_search
23
+ from .mf import multifragment_heuristic
24
+ from .nn import nearest_neighbour
25
+ from .opt_2 import local_search_2_opt
26
+ from .opt_2_5 import local_search_2h_opt
27
+ from .opt_3 import local_search_3_opt
28
+ from .opt_4 import local_search_4_opt
29
+ from .opt_5 import local_search_5_opt
30
+ from .opt_2s import local_search_2_opt_stochastic
31
+ from .opt_2_5s import local_search_2h_opt_stochastic
32
+ from .opt_3s import local_search_3_opt_stochastic
33
+ from .opt_4s import local_search_4_opt_stochastic
34
+ from .opt_5s import local_search_5_opt_stochastic
35
+ from .rt import random_tour
36
+ from .rl_ql import q_learning
37
+ from .rl_double_ql import double_q_learning
38
+ from .rl_sarsa import sarsa
39
+ from .s_gui import guided_search
40
+ from .s_itr import iterated_search
41
+ from .s_sct import scatter_search
42
+ from .s_shc import stochastic_hill_climbing
43
+ from .s_tabu import tabu_search
44
+ from .s_vns import variable_neighborhood_search
45
+ from .sa import simulated_annealing_tsp
46
+ from .som import self_organizing_maps
47
+ from .spfc_h import space_filling_curve_h
48
+ from .spfc_m import space_filling_curve_m
49
+ from .spfc_s import space_filling_curve_s
50
+ from .swp import sweep
51
+ from .tat import tat_algorithm
52
+ from .tbb import truncated_branch_and_bound
@@ -0,0 +1,117 @@
1
+ ############################################################################
2
+
3
+ # Created by: Prof. Valdecy Pereira, D.Sc.
4
+ # UFF - Universidade Federal Fluminense (Brazil)
5
+ # email: valdecy.pereira@gmail.com
6
+ # Lesson: pyCombinatorial - Ant Colony Optimization
7
+
8
+ # GitHub Repository: <https://github.com/Valdecy>
9
+
10
+ ############################################################################
11
+
12
+ # Required Libraries
13
+ import copy
14
+ import numpy as np
15
+
16
+ ############################################################################
17
+
18
+ # Helper: Calculate Path Distance
19
+ def calculate_distance(distance_matrix, city_list):
20
+ path_distance = 0
21
+ for i in range(0, len(city_list) - 1):
22
+ path_distance = path_distance + distance_matrix[city_list[i]-1, city_list[i+1]-1]
23
+ path_distance = path_distance + distance_matrix[city_list[-1]-1, city_list[0]-1]
24
+ return path_distance
25
+
26
+ # Helper: Perform Local Search (Optional)
27
+ def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1):
28
+ city_list, best_path_distance = city_tour[0], city_tour[1]
29
+ improved = True
30
+ while (improved == True):
31
+ improved = False
32
+ for i in range(1, len(city_list) - 2):
33
+ for j in range(i + 1, len(city_list) - 1):
34
+ new_city_list = city_list[:]
35
+ new_city_list[i:j] = city_list[i:j][::-1]
36
+ new_distance = calculate_distance(distance_matrix, new_city_list)
37
+ if (new_distance < best_path_distance):
38
+ best_path_distance = new_distance
39
+ city_list = new_city_list
40
+ improved = True
41
+ return city_list, best_path_distance
42
+
43
+ ############################################################################
44
+
45
+ # Helper: Calculate Attractiveness
46
+ def attractiveness(distance_matrix):
47
+ h = 1 / (distance_matrix + 1e-10)
48
+ np.fill_diagonal(h, 0)
49
+ return h
50
+
51
+ # Helper: Update Pheromone Matrix
52
+ def update_thau(distance_matrix, thau, city_list):
53
+ path_distance = 0
54
+ for i in range(len(city_list) - 1):
55
+ path_distance = path_distance + distance_matrix[city_list[i]-1, city_list[i+1]-1]
56
+ path_distance = path_distance + distance_matrix[city_list[-1]-1, city_list[0]-1]
57
+ for i in range(len(city_list) - 1):
58
+ thau[city_list[ i ]-1, city_list[i+1]-1] = thau[city_list[ i ]-1, city_list[i+1]-1] + 1 / path_distance
59
+ thau[city_list[i+1]-1, city_list[ i ]-1] = thau[city_list[i+1]-1, city_list[ i ]-1] + 1 / path_distance
60
+ thau[city_list[-1]-1, city_list[ 0]-1] = thau[city_list[-1]-1, city_list[ 0]-1] + 1 / path_distance
61
+ thau[city_list[ 0]-1, city_list[-1]-1] = thau[city_list[ 0]-1, city_list[-1]-1] + 1 / path_distance
62
+ return thau
63
+
64
+ # Helper: Generate Ant Paths
65
+ def ants_path(distance_matrix, h, thau, alpha, beta, full_list, ants, local_search):
66
+ best_path_distance = float('inf')
67
+ best_city_list = None
68
+ for _ in range(0, ants):
69
+ city_list = [np.random.choice(full_list)]
70
+ while (len(city_list) < len(full_list)):
71
+ current_city = city_list[-1]
72
+ probabilities = []
73
+ for next_city in full_list:
74
+ if (next_city not in city_list):
75
+ p = (thau[current_city-1, next_city-1] ** alpha) * (h[current_city-1, next_city-1] ** beta)
76
+ probabilities.append(p)
77
+ else:
78
+ probabilities.append(0)
79
+ probabilities = np.array(probabilities) / np.sum(probabilities)
80
+ next_city = np.random.choice(full_list, p = probabilities)
81
+ city_list.append(next_city)
82
+ path_distance = calculate_distance(distance_matrix, city_list)
83
+ if (path_distance < best_path_distance):
84
+ best_city_list = copy.deepcopy(city_list)
85
+ best_path_distance = path_distance
86
+
87
+ if (local_search == True):
88
+ best_city_list, best_path_distance = local_search_2_opt(distance_matrix, city_tour = [best_city_list, best_path_distance])
89
+ thau = update_thau(distance_matrix, thau, city_list = best_city_list)
90
+ return best_city_list, best_path_distance, thau
91
+
92
+ ############################################################################
93
+
94
+ # ACO Function
95
+ def ant_colony_optimization(distance_matrix, ants = 5, iterations = 50, alpha = 1, beta = 2, decay = 0.05, local_search = True, verbose = True):
96
+ count = 0
97
+ best_route = []
98
+ full_list = list(range(1, distance_matrix.shape[0] + 1))
99
+ distance = np.sum(distance_matrix.sum())
100
+ h = attractiveness(distance_matrix)
101
+ thau = np.ones((distance_matrix.shape[0], distance_matrix.shape[0]))
102
+ while (count <= iterations):
103
+ if (verbose == True and count > 0):
104
+ print(f'Iteration = {count}, Distance = {round(best_route[1], 2)}')
105
+ city_list, path_distance, thau = ants_path(distance_matrix, h, thau, alpha, beta, full_list, ants, local_search)
106
+ thau = thau*(1 - decay)
107
+ if (distance > path_distance):
108
+ best_route = copy.deepcopy([city_list])
109
+ best_route.append(path_distance)
110
+ distance = best_route[1]
111
+ count = count + 1
112
+ init_city = best_route[0][0]
113
+ best_route[0].append(init_city)
114
+ return best_route[0], best_route[1]
115
+
116
+ ############################################################################
117
+
@@ -0,0 +1,148 @@
1
+ ############################################################################
2
+
3
+ # Created by: Prof. Valdecy Pereira, D.Sc.
4
+ # UFF - Universidade Federal Fluminense (Brazil)
5
+ # email: valdecy.pereira@gmail.com
6
+ # Lesson: pyCombinatorial - ALNS - Adaptive Large Neighborhood Search
7
+
8
+ # GitHub Repository: <https://github.com/Valdecy>
9
+
10
+ ############################################################################
11
+
12
+ # Required Libraries
13
+ import copy
14
+ import numpy as np
15
+ import random
16
+
17
+ ############################################################################
18
+
19
+ # Function: Euclidean Distance
20
+ def euclidean_distance(point1, point2):
21
+ return np.sqrt((point1[0] - point2[0])**2 + (point1[1] - point2[1])**2)
22
+
23
+ # Function: Tour Distance
24
+ def distance_calc(distance_matrix, city_tour):
25
+ distance = 0
26
+ for k in range(0, len(city_tour[0])-1):
27
+ m = k + 1
28
+ distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1]
29
+ return distance
30
+
31
+ # Function: Tour Distance
32
+ def distance_point(distance_matrix, city_tour):
33
+ distance = 0
34
+ for i in range(0, len(city_tour) - 1):
35
+ distance = distance + distance_matrix[city_tour[i]][city_tour[i + 1]]
36
+ distance = distance + distance_matrix[city_tour[-1]][city_tour[0]]
37
+ return distance
38
+
39
+ ############################################################################
40
+
41
+ # Function: 2_opt
42
+ def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True):
43
+ if (recursive_seeding < 0):
44
+ count = -2
45
+ else:
46
+ count = 0
47
+ city_list = copy.deepcopy(city_tour)
48
+ distance = city_list[1]*2
49
+ iteration = 0
50
+ if (verbose == True):
51
+ print('')
52
+ print('Local Search')
53
+ print('')
54
+ while (count < recursive_seeding):
55
+ if (verbose == True):
56
+ print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2))
57
+ best_route = copy.deepcopy(city_list)
58
+ seed = copy.deepcopy(city_list)
59
+ for i in range(0, len(city_list[0]) - 2):
60
+ for j in range(i+1, len(city_list[0]) - 1):
61
+ best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1]))
62
+ best_route[0][-1] = best_route[0][0]
63
+ best_route[1] = distance_calc(distance_matrix, best_route)
64
+ if (city_list[1] > best_route[1]):
65
+ city_list = copy.deepcopy(best_route)
66
+ best_route = copy.deepcopy(seed)
67
+ count = count + 1
68
+ iteration = iteration + 1
69
+ if (distance > city_list[1] and recursive_seeding < 0):
70
+ distance = city_list[1]
71
+ count = -2
72
+ recursive_seeding = -1
73
+ elif(city_list[1] >= distance and recursive_seeding < 0):
74
+ count = -1
75
+ recursive_seeding = -2
76
+ return city_list[0], city_list[1]
77
+
78
+ ############################################################################
79
+
80
+ # Function: Removal
81
+ def removal_operators():
82
+ def random_removal(city_tour, num_removals):
83
+ removed = set()
84
+ while (len(removed) < num_removals):
85
+ removed.add(random.choice(city_tour[1:]))
86
+ return list(removed)
87
+ return [random_removal]
88
+
89
+ # Function: Insertion
90
+ def insertion_operators():
91
+ def cheapest_insertion(removed_nodes, city_tour, distance_matrix):
92
+ for node in removed_nodes:
93
+ best_insertion_cost = float('inf')
94
+ best_insertion_index = -1
95
+ for i in range(1, len(city_tour) + 1):
96
+ insertion_cost = (distance_matrix[city_tour[i - 1]][node] + distance_matrix[node][city_tour[i % len(city_tour)]] - distance_matrix[city_tour[i - 1]][city_tour[i % len(city_tour)]])
97
+ if (insertion_cost < best_insertion_cost):
98
+ best_insertion_cost = insertion_cost
99
+ best_insertion_index = i
100
+ city_tour.insert(best_insertion_index, node)
101
+ return city_tour
102
+ return [cheapest_insertion]
103
+
104
+ ############################################################################
105
+
106
+ # Function: Adaptive Large Neighborhood Search
107
+ def adaptive_large_neighborhood_search(distance_matrix, iterations = 100, removal_fraction = 0.2, rho = 0.1, local_search = True, verbose = True):
108
+ initial_tour = list(range(0, distance_matrix.shape[0]))
109
+ random.shuffle(initial_tour)
110
+ route = initial_tour.copy()
111
+ distance = distance_point(distance_matrix, route)
112
+ removal_ops = removal_operators()
113
+ insertion_ops = insertion_operators()
114
+ weights_removal = [1.0] * len(removal_ops)
115
+ weights_insertion = [1.0] * len(insertion_ops)
116
+ count = 0
117
+ while (count <= iterations):
118
+ if (verbose == True and count > 0):
119
+ print('Iteration = ', count, 'Distance = ', round(distance, 2))
120
+ city_tour = route.copy()
121
+ removal_op = random.choices(removal_ops, weights = weights_removal)[0]
122
+ insertion_op = random.choices(insertion_ops, weights = weights_insertion)[0]
123
+ num_removals = int(removal_fraction * distance_matrix.shape[0])
124
+ removed_nodes = removal_op(city_tour, num_removals)
125
+ for node in removed_nodes:
126
+ city_tour.remove(node)
127
+ new_tour = insertion_op(removed_nodes, city_tour, distance_matrix)
128
+ new_tour_distance = distance_point(distance_matrix, new_tour)
129
+ if (new_tour_distance < distance):
130
+ route = new_tour
131
+ distance = new_tour_distance
132
+ weights_removal[removal_ops.index(removal_op)] = weights_removal[removal_ops.index(removal_op)] * (1 + rho)
133
+ weights_insertion[insertion_ops.index(insertion_op)] = weights_insertion[insertion_ops.index(insertion_op)] * (1 + rho)
134
+ else:
135
+ weights_removal[removal_ops.index(removal_op)] = weights_removal[removal_ops.index(removal_op)] * (1 - rho)
136
+ weights_insertion[insertion_ops.index(insertion_op)] = weights_insertion[insertion_ops.index(insertion_op)] * (1 - rho)
137
+ total_weight_removal = sum(weights_removal)
138
+ total_weight_insertion = sum(weights_insertion)
139
+ weights_removal = [w / total_weight_removal for w in weights_removal]
140
+ weights_insertion = [w / total_weight_insertion for w in weights_insertion]
141
+ count = count + 1
142
+ route = route + [route[0]]
143
+ route = [item + 1 for item in route]
144
+ if (local_search == True):
145
+ route, distance = local_search_2_opt(distance_matrix, [route, distance], -1, verbose)
146
+ return route, distance
147
+
148
+ ############################################################################
@@ -0,0 +1,81 @@
1
+ ############################################################################
2
+
3
+ # Created by: Prof. Valdecy Pereira, D.Sc.
4
+ # UFF - Universidade Federal Fluminense (Brazil)
5
+ # email: valdecy.pereira@gmail.com
6
+ # Lesson: Branch & Bound
7
+
8
+ # GitHub Repository: <https://github.com/Valdecy>
9
+
10
+ ############################################################################
11
+
12
+ # Required Libraries
13
+ import numpy as np
14
+
15
+ ############################################################################
16
+
17
+ # Function: First Minimum Distance
18
+ def min_1(distance_matrix, i):
19
+ vector = distance_matrix[i,:].tolist()
20
+ idx = np.argsort(vector)
21
+ m1 = vector[idx[1]]
22
+ return m1
23
+
24
+ # Function: Second Minimum Distance
25
+ def min_2(distance_matrix, i):
26
+ vector = distance_matrix[i,:].tolist()
27
+ idx = np.argsort(vector)
28
+ m2 = vector[idx[2]]
29
+ return m2
30
+
31
+ ############################################################################
32
+
33
+ # Function: Branch
34
+ def explore_path(route, distance, distance_matrix, bound, weight, level, path, visited):
35
+ if (level == distance_matrix.shape[0]):
36
+ if (distance_matrix[path[level - 1], path[0]] != 0):
37
+ dist = weight + distance_matrix[path[level - 1], path[0]]
38
+ if (dist < distance):
39
+ distance = dist
40
+ route[:distance_matrix.shape[0] + 1] = path[:]
41
+ route[distance_matrix.shape[0]] = path[0]
42
+ return route, distance, bound, weight, path, visited
43
+ for i in range(0, distance_matrix.shape[0]):
44
+ if (distance_matrix[path[level-1], i] != 0 and visited[i] == False):
45
+ temp = bound
46
+ weight = weight + distance_matrix[path[level - 1], i]
47
+ if (level == 1):
48
+ bound = bound - ((min_1(distance_matrix, path[level - 1]) + min_1(distance_matrix, i)) / 2)
49
+ else:
50
+ bound = bound - ((min_2(distance_matrix, path[level - 1]) + min_1(distance_matrix, i)) / 2)
51
+ if (bound + weight < distance):
52
+ path[level] = i
53
+ visited[i] = True
54
+ route, distance, bound, weight, path, visited = explore_path(route, distance, distance_matrix, bound, weight, level + 1, path, visited)
55
+ weight = weight - distance_matrix[path[level - 1], i]
56
+ bound = temp
57
+ visited = [False] * len(visited)
58
+ for j in range(level):
59
+ if (path[j] != -1):
60
+ visited[path[j]] = True
61
+ return route, distance, bound, weight, path, visited
62
+
63
+ ############################################################################
64
+
65
+ # Function: Branch and Bound (Adapted from: https://www.geeksforgeeks.org/traveling-salesman-problem-using-branch-and-bound-2/)
66
+ def branch_and_bound(distance_matrix):
67
+ distance = float('+inf')
68
+ path = [ -1 ] * (distance_matrix.shape[0] + 1)
69
+ path[0] = 0
70
+ visited = [ False ] * distance_matrix.shape[0]
71
+ visited[0] = True
72
+ route = [ None ] * (distance_matrix.shape[0] + 1)
73
+ weight = 0
74
+ level = 1
75
+ bound = np.ceil(sum([ (min_1(distance_matrix, i) + min_2(distance_matrix, i)) for i in range(0, distance_matrix.shape[0])])/2)
76
+ route, distance, bound, weight, path, visited = explore_path(route, distance, distance_matrix, bound, weight, level, path, visited)
77
+ route = [item+1 for item in route]
78
+ return route, distance
79
+
80
+ ############################################################################
81
+
@@ -0,0 +1,41 @@
1
+ ############################################################################
2
+
3
+ # Created by: Prof. Valdecy Pereira, D.Sc.
4
+ # UFF - Universidade Federal Fluminense (Brazil)
5
+ # email: valdecy.pereira@gmail.com
6
+ # Lesson: Brute Force
7
+
8
+ # GitHub Repository: <https://github.com/Valdecy>
9
+
10
+ ############################################################################
11
+
12
+ # Required Libraries
13
+ import itertools
14
+
15
+ ############################################################################
16
+
17
+ # Function: Tour Distance
18
+ def distance_calc(distance_matrix, city_tour):
19
+ distance = 0
20
+ for k in range(0, len(city_tour[0])-1):
21
+ m = k + 1
22
+ distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1]
23
+ return distance
24
+
25
+ ############################################################################
26
+
27
+ # Function: BF
28
+ def brute_force_analysis(distance_matrix):
29
+ n = distance_matrix.shape[1]
30
+ candidates = []
31
+ for p in itertools.permutations(range(2, n+1)):
32
+ if (p <= p[::-1]):
33
+ candidates.append(list(p))
34
+ routes = [ [1] + item + [1] for item in candidates ]
35
+ distances = [ distance_calc(distance_matrix, [item, 1]) for item in routes ]
36
+ idx = distances.index(min(distances))
37
+ route = routes[idx]
38
+ distance = distances[idx]
39
+ return route, distance
40
+
41
+ ############################################################################
@@ -0,0 +1,54 @@
1
+ ############################################################################
2
+
3
+ # Created by: Prof. Valdecy Pereira, D.Sc.
4
+ # UFF - Universidade Federal Fluminense (Brazil)
5
+ # email: valdecy.pereira@gmail.com
6
+ # Lesson: Bellman-Held-Karp Exact Algorithm
7
+
8
+ # GitHub Repository: <https://github.com/Valdecy>
9
+
10
+ ############################################################################
11
+
12
+ # Required Libraries
13
+ import itertools
14
+
15
+ ############################################################################
16
+
17
+ # Function: Bellman-Held-Karp Exact Algorithm (adapted from https://github.com/CarlEkerot/held-karp)
18
+ def bellman_held_karp_exact_algorithm(distance_matrix, verbose = True):
19
+ n = distance_matrix.shape[0]
20
+ C = {}
21
+ for k in range(1, n):
22
+ C[(2**k, k)] = (distance_matrix[0, k], 0)
23
+ for j in range(2, n):
24
+ combinations = list(itertools.combinations(range(1, n), j))
25
+ for i in combinations:
26
+ bits = 0
27
+ for bit in i:
28
+ bits = bits + 2**bit
29
+ for k in i:
30
+ prev = bits - 2**k
31
+ res = []
32
+ for m in i:
33
+ if( m == 0 or m == k):
34
+ continue
35
+ res.append((C[(prev, m)][0] + distance_matrix[m, k], m))
36
+ C[(bits, k)] = min(res)
37
+ if (verbose == True):
38
+ print('Iteration: ', j, ' of ', n-1, ' Analysed Combinations: ', len(combinations))
39
+ bits = (2**n - 1) - 1
40
+ res = []
41
+ for k in range(1, n):
42
+ res.append((C[(bits, k)][0] + distance_matrix[k, 0], k))
43
+ distance, parent = min(res)
44
+ route = []
45
+ for i in range(n - 1):
46
+ route.append(parent)
47
+ bits_ = bits - 2**parent
48
+ _, parent = C[(bits, parent)]
49
+ bits = bits_
50
+ route = [0] + route + [0]
51
+ route = [item + 1 for item in route]
52
+ return route, distance
53
+
54
+ ############################################################################
@@ -0,0 +1,156 @@
1
+ ############################################################################
2
+
3
+ # Created by: Prof. Valdecy Pereira, D.Sc.
4
+ # UFF - Universidade Federal Fluminense (Brazil)
5
+ # email: valdecy.pereira@gmail.com
6
+ # Lesson: BRKGA (Biased Random Key Genetic Algorithm)
7
+
8
+ # GitHub Repository: <https://github.com/Valdecy>
9
+
10
+ ############################################################################
11
+
12
+ # Required Libraries
13
+ import copy
14
+ import numpy as np
15
+ import random
16
+ import os
17
+
18
+ ############################################################################
19
+
20
+ # Function: Encoder
21
+ def encoder(seed):
22
+ route = seed[0][:-1]
23
+ route = [item - 1 for item in route]
24
+ individual = [item - 1 for item in route]
25
+ count = 0
26
+ for item in route:
27
+ individual[item] = count*(1/len(route))
28
+ count = count + 1
29
+ return individual
30
+
31
+ # Function: Decoder
32
+ def decoder(individual, distance_matrix, cost_only = False):
33
+ dec = sorted(range(0, len(individual)), key = individual.__getitem__)
34
+ dec = [item + 1 for item in dec]
35
+ route = dec + [dec[0]]
36
+ distance = distance_calc(distance_matrix, [route, 1])
37
+ if (cost_only == True):
38
+ return distance
39
+ else:
40
+ return route, distance
41
+
42
+ ############################################################################
43
+
44
+ # Function: Tour Distance
45
+ def distance_calc(distance_matrix, city_tour):
46
+ distance = 0
47
+ for k in range(0, len(city_tour[0])-1):
48
+ m = k + 1
49
+ distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1]
50
+ return distance
51
+
52
+ # Function: 2_opt
53
+ def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True):
54
+ if (recursive_seeding < 0):
55
+ count = -2
56
+ else:
57
+ count = 0
58
+ city_list = copy.deepcopy(city_tour)
59
+ distance = city_list[1]*2
60
+ iteration = 0
61
+ while (count < recursive_seeding):
62
+ if (verbose == True):
63
+ print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2))
64
+ best_route = copy.deepcopy(city_list)
65
+ seed = copy.deepcopy(city_list)
66
+ for i in range(0, len(city_list[0]) - 2):
67
+ for j in range(i+1, len(city_list[0]) - 1):
68
+ best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1]))
69
+ best_route[0][-1] = best_route[0][0]
70
+ best_route[1] = distance_calc(distance_matrix, best_route)
71
+ if (city_list[1] > best_route[1]):
72
+ city_list = copy.deepcopy(best_route)
73
+ best_route = copy.deepcopy(seed)
74
+ count = count + 1
75
+ iteration = iteration + 1
76
+ if (distance > city_list[1] and recursive_seeding < 0):
77
+ distance = city_list[1]
78
+ count = -2
79
+ recursive_seeding = -1
80
+ elif(city_list[1] >= distance and recursive_seeding < 0):
81
+ count = -1
82
+ recursive_seeding = -2
83
+ return city_list[0], city_list[1]
84
+
85
+ # Function: Elite Local Search
86
+ def elite_ls(population, distance_matrix, elite):
87
+ print('Preparing Elite...')
88
+ for i in range(0, elite):
89
+ idx = random.sample(range(0, len(population) - 1), 1)[0]
90
+ r, d = decoder(population[idx], distance_matrix, cost_only = False)
91
+ seed = [r, d]
92
+ r, d = local_search_2_opt(distance_matrix, seed, recursive_seeding = -1, verbose = False)
93
+ seed = [r, d]
94
+ population[idx] = encoder(seed)
95
+ return population
96
+
97
+ ############################################################################
98
+
99
+ # Function: Initialize Variables
100
+ def initial_population(population_size, min_values, max_values):
101
+ population = np.zeros((population_size, len(min_values)))
102
+ for i in range(0, population_size):
103
+ for j in range(0, len(min_values)):
104
+ population[i,j] = random.uniform(min_values[j], max_values[j])
105
+ return population
106
+
107
+ # Function: Offspring
108
+ def breeding(population, elite, bias):
109
+ offspring = np.copy(population)
110
+ for i in range (elite, offspring.shape[0]):
111
+ parent_1 = random.sample(range(0, elite), 1)[0]
112
+ parent_2 = random.sample(range(elite, len(population) - 1), 1)[0]
113
+ for j in range(0, offspring.shape[1]):
114
+ rand = int.from_bytes(os.urandom(8), byteorder = 'big') / ((1 << 64) - 1)
115
+ if (rand <= bias):
116
+ offspring[i,j] = population[parent_1, j]
117
+ else:
118
+ offspring[i,j] = population[parent_2, j]
119
+ return offspring
120
+
121
+ # Function: Mutation
122
+ def mutation(population, mutants, min_values, max_values):
123
+ mutated = initial_population(mutants, min_values, max_values)
124
+ population[population.shape[0]-mutated.shape[0]:, :] = mutated
125
+ return population
126
+
127
+ ############################################################################
128
+
129
+ # BRKGA Function
130
+ def biased_random_key_genetic_algorithm(distance_matrix, population_size = 25, elite = 1, bias = 0.5, mutants = 10, generations = 50000, verbose = True):
131
+ count = 0
132
+ min_values = [0]*distance_matrix.shape[0]
133
+ max_values = [1]*distance_matrix.shape[0]
134
+ population = initial_population(population_size, min_values, max_values)
135
+ population = elite_ls(population, distance_matrix, 1)
136
+ cost = [decoder(population[i,:].tolist(), distance_matrix, cost_only = True) for i in range(0, population.shape[0])]
137
+ idx = sorted(range(0, len(cost)), key = cost.__getitem__)
138
+ cost = [cost[i] for i in idx]
139
+ population = population[idx,:]
140
+ elite_ind = [population[0,:], cost[0]]
141
+ while (count <= generations):
142
+ if (verbose == True):
143
+ print('Generation = ', count, 'Distance = ', round(elite_ind[1], 2))
144
+ offspring = breeding(population, elite, bias)
145
+ cost = [decoder(offspring[i,:].tolist(), distance_matrix, cost_only = True) for i in range(0, offspring.shape[0])]
146
+ idx = sorted(range(0, len(cost)), key = cost.__getitem__)
147
+ cost = [cost[i] for i in idx]
148
+ population = offspring[idx,:]
149
+ population = mutation(population, mutants, min_values, max_values)
150
+ if(elite_ind[1] > cost[0]):
151
+ elite_ind = [population[0,:], cost[0]]
152
+ count = count + 1
153
+ route, distance = decoder(elite_ind[0].tolist(), distance_matrix, cost_only = False)
154
+ return route, distance
155
+
156
+ ############################################################################