├── pyCombinatorial ├── __init__.py ├── utils │ ├── __init__.py │ └── util.py └── algorithm │ ├── bf.py │ ├── bhk.py │ ├── opt_2.py │ ├── __init__.py │ ├── s_vns.py │ ├── opt_2s.py │ ├── s_shc.py │ ├── bb.py │ ├── opt_2_5.py │ ├── rt.py │ ├── opt_2_5s.py │ ├── tat.py │ ├── ins_c.py │ ├── nn.py │ ├── conv_hull.py │ ├── opt_3.py │ ├── s_itr.py │ ├── spfc_m.py │ ├── opt_3s.py │ ├── tbb.py │ ├── ins_n.py │ ├── ins_f.py │ ├── bt.py │ ├── ins_r.py │ ├── s_gui.py │ ├── mf.py │ ├── spfc_s.py │ ├── swp.py │ ├── spfc_h.py │ ├── s_sct.py │ ├── grasp.py │ ├── lns.py │ ├── rr.py │ ├── christofides.py │ ├── som.py │ ├── aco.py │ ├── rl_ql.py │ ├── cw.py │ ├── sa.py │ ├── eln.py │ ├── rl_sarsa.py │ ├── rl_double_ql.py │ ├── brkga.py │ ├── eo.py │ └── opt_4.py ├── LICENSE └── setup.py /pyCombinatorial/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyCombinatorial/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .graphs import * 2 | from .util import * -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2023 by Valdecy Pereira 2 | 3 | pyCombinatorial is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | pyCombinatorial is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with pyCombinatorial. If not, see . 15 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from pathlib import Path 3 | 4 | this_directory = Path(__file__).parent 5 | long_description = (this_directory / 'README.md').read_text() 6 | 7 | setup( 8 | name='pycombinatorial', 9 | version='2.1.0', 10 | license='GNU', 11 | author='Valdecy Pereira', 12 | author_email='valdecy.pereira@gmail.com', 13 | url='https://github.com/Valdecy/pyCombinatorial', 14 | packages=find_packages(), 15 | install_requires=[ 16 | 'folium', 17 | 'networkx', 18 | 'numpy', 19 | 'plotly', 20 | 'scipy' 21 | ], 22 | description='A library to solve TSP (Travelling Salesman Problem) using Exact Algorithms, Heuristics, Metaheuristics and Reinforcement Learning', 23 | long_description=long_description, 24 | long_description_content_type='text/markdown', 25 | ) 26 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/bf.py: -------------------------------------------------------------------------------- 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: 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 | ############################################################################ 42 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/bhk.py: -------------------------------------------------------------------------------- 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: 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 | ############################################################################ 55 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/opt_2.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Local Search-2-opt 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 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: 2_opt 28 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 29 | if (recursive_seeding < 0): 30 | count = -2 31 | else: 32 | count = 0 33 | city_list = copy.deepcopy(city_tour) 34 | distance = city_list[1]*2 35 | iteration = 0 36 | while (count < recursive_seeding): 37 | if (verbose == True): 38 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 39 | best_route = copy.deepcopy(city_list) 40 | seed = copy.deepcopy(city_list) 41 | for i in range(0, len(city_list[0]) - 2): 42 | for j in range(i+1, len(city_list[0]) - 1): 43 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 44 | best_route[0][-1] = best_route[0][0] 45 | best_route[1] = distance_calc(distance_matrix, best_route) 46 | if (city_list[1] > best_route[1]): 47 | city_list = copy.deepcopy(best_route) 48 | best_route = copy.deepcopy(seed) 49 | count = count + 1 50 | iteration = iteration + 1 51 | if (distance > city_list[1] and recursive_seeding < 0): 52 | distance = city_list[1] 53 | count = -2 54 | recursive_seeding = -1 55 | elif(city_list[1] >= distance and recursive_seeding < 0): 56 | count = -1 57 | recursive_seeding = -2 58 | return city_list[0], city_list[1] 59 | 60 | ############################################################################ 61 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/__init__.py: -------------------------------------------------------------------------------- 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 .bt import bitonic_tour 8 | from .christofides import christofides_algorithm 9 | from .conc_hull import concave_hull_algorithm 10 | from .conv_hull import convex_hull_algorithm 11 | from .cw import clarke_wright_savings 12 | from .eln import elastic_net_tsp 13 | from .eo import extremal_optimization 14 | from .frnn import fixed_radius_nn 15 | from .ga import genetic_algorithm 16 | from .grasp import greedy_randomized_adaptive_search_procedure 17 | from .gksp import greedy_karp_steele_patching 18 | from .hpn import hopfield_network_tsp 19 | from .ins_c import cheapest_insertion 20 | from .ins_f import farthest_insertion 21 | from .ins_n import nearest_insertion 22 | from .ins_r import random_insertion 23 | from .ksp import karp_steele_patching 24 | from .lns import large_neighborhood_search 25 | from .mf import multifragment_heuristic 26 | from .nn import nearest_neighbour 27 | from .opt_2 import local_search_2_opt 28 | from .opt_2_5 import local_search_2h_opt 29 | from .opt_3 import local_search_3_opt 30 | from .opt_4 import local_search_4_opt 31 | from .opt_5 import local_search_5_opt 32 | from .opt_2s import local_search_2_opt_stochastic 33 | from .opt_2_5s import local_search_2h_opt_stochastic 34 | from .opt_3s import local_search_3_opt_stochastic 35 | from .opt_4s import local_search_4_opt_stochastic 36 | from .opt_5s import local_search_5_opt_stochastic 37 | from .rl_double_ql import double_q_learning 38 | from .rl_ql import q_learning 39 | from .rl_sarsa import sarsa 40 | from .rt import random_tour 41 | from .rr import ruin_and_recreate 42 | from .s_gui import guided_search 43 | from .s_itr import iterated_search 44 | from .s_sct import scatter_search 45 | from .s_shc import stochastic_hill_climbing 46 | from .s_tabu import tabu_search 47 | from .s_vns import variable_neighborhood_search 48 | from .sa import simulated_annealing_tsp 49 | from .som import self_organizing_maps 50 | from .spfc_h import space_filling_curve_h 51 | from .spfc_m import space_filling_curve_m 52 | from .spfc_s import space_filling_curve_s 53 | from .swp import sweep 54 | from .tat import tat_algorithm 55 | from .tbb import truncated_branch_and_bound 56 | from .zs import zero_suffix_method 57 | -------------------------------------------------------------------------------- /pyCombinatorial/utils/util.py: -------------------------------------------------------------------------------- 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 - Util 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import numpy as np 14 | import random 15 | 16 | ############################################################################ 17 | 18 | # Function: Tour Distance 19 | def distance_calc(distance_matrix, city_tour): 20 | distance = 0 21 | for k in range(0, len(city_tour[0])-1): 22 | m = k + 1 23 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 24 | return distance 25 | 26 | # Function: Initial Seed 27 | def seed_function(distance_matrix): 28 | seed = [[], float('inf')] 29 | sequence = random.sample(list(range(1, distance_matrix.shape[0]+1)), distance_matrix.shape[0]) 30 | sequence.append(sequence[0]) 31 | seed[0] = sequence 32 | seed[1] = distance_calc(distance_matrix, seed) 33 | return seed 34 | 35 | # Function: Build Coordinates 36 | def build_coordinates(distance_matrix): 37 | a = distance_matrix[0,:].reshape(distance_matrix.shape[0], 1) 38 | b = distance_matrix[:,0].reshape(1, distance_matrix.shape[0]) 39 | m = (1/2)*(a**2 + b**2 - distance_matrix**2) 40 | w, u = np.linalg.eig(np.matmul(m.T, m)) 41 | s = (np.diag(np.sort(w)[::-1]))**(1/2) 42 | coordinates = np.matmul(u, s**(1/2)) 43 | coordinates = coordinates.real[:,0:2] 44 | return coordinates 45 | 46 | # Function: Build Distance Matrix 47 | def build_distance_matrix(coordinates): 48 | a = coordinates 49 | b = a.reshape(np.prod(a.shape[:-1]), 1, a.shape[-1]) 50 | return np.sqrt(np.einsum('ijk,ijk->ij', b - a, b - a)).squeeze() 51 | 52 | # Function: LatLong -> Cartesian 53 | def latlong_to_cartesian(lat_long): 54 | if (hasattr(lat_long, 'values')): 55 | lat_long = lat_long.values 56 | lat = lat_long[:, 0] 57 | lon = lat_long[:, 1] 58 | lat_rad = np.radians(lat) 59 | lon_rad = np.radians(lon) 60 | R = 6371.0 61 | x = R * np.cos(lat_rad) * np.cos(lon_rad) 62 | y = R * np.cos(lat_rad) * np.sin(lon_rad) 63 | #z = R * np.sin(lat_rad) 64 | return np.column_stack((x, y)) 65 | 66 | # Function: Build Distance Matrix Lat Long 67 | def latlong_distance_matrix(coordinates): 68 | coords_rad = np.radians(coordinates) 69 | coords_rad = coords_rad.values 70 | latitudes = coords_rad[:, 0][:, np.newaxis] 71 | longitudes = coords_rad[:, 1][:, np.newaxis] 72 | dlat = latitudes - latitudes.T 73 | dlon = longitudes - longitudes.T 74 | a = np.sin(dlat / 2)**2 + np.cos(latitudes) * np.cos(latitudes.T) * np.sin(dlon / 2)**2 75 | c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a)) 76 | R = 6371.0 77 | return R * c 78 | 79 | ############################################################################ 80 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/s_vns.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Variable Neighborhood Search 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import random 14 | import copy 15 | 16 | ############################################################################ 17 | 18 | # Function: Tour Distance 19 | def distance_calc(distance_matrix, city_tour): 20 | distance = 0 21 | for k in range(0, len(city_tour[0])-1): 22 | m = k + 1 23 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 24 | return distance 25 | 26 | ############################################################################ 27 | 28 | # Function: Stochastic 2_opt 29 | def stochastic_2_opt(distance_matrix, city_tour): 30 | best_route = copy.deepcopy(city_tour) 31 | i, j = random.sample(range(0, len(city_tour[0])-1), 2) 32 | if (i > j): 33 | i, j = j, i 34 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 35 | best_route[0][-1] = best_route[0][0] 36 | best_route[1] = distance_calc(distance_matrix, best_route) 37 | return best_route 38 | 39 | # Function: Local Search 40 | def local_search(distance_matrix, city_tour, max_attempts = 50, neighbourhood_size = 5): 41 | count = 0 42 | solution = copy.deepcopy(city_tour) 43 | while (count < max_attempts): 44 | for i in range(0, neighbourhood_size): 45 | candidate = stochastic_2_opt(distance_matrix, solution) 46 | if candidate[1] < solution[1]: 47 | solution = copy.deepcopy(candidate) 48 | count = 0 49 | else: 50 | count = count + 1 51 | return solution 52 | 53 | ############################################################################ 54 | 55 | # Function: Variable Neighborhood Search 56 | def variable_neighborhood_search(distance_matrix, city_tour, max_attempts = 20, neighbourhood_size = 5, iterations = 50, verbose = True): 57 | count = 0 58 | solution = copy.deepcopy(city_tour) 59 | best_solution = copy.deepcopy(city_tour) 60 | while (count < iterations): 61 | if (verbose == True): 62 | print('Iteration = ', count, 'Distance = ', round(best_solution[1], 2)) 63 | for i in range(0, neighbourhood_size): 64 | for j in range(0, neighbourhood_size): 65 | solution = stochastic_2_opt(distance_matrix, best_solution) 66 | solution = local_search(distance_matrix, solution, max_attempts, neighbourhood_size) 67 | if (solution[1] < best_solution[1]): 68 | best_solution = copy.deepcopy(solution) 69 | break 70 | count = count + 1 71 | route, distance = best_solution 72 | return route, distance 73 | 74 | ############################################################################ 75 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/opt_2s.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Local Search-2-opt Stochastic 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import random 15 | 16 | ############################################################################ 17 | 18 | # Function: Tour Distance 19 | def distance_calc(distance_matrix, city_tour): 20 | distance = 0 21 | for k in range(0, len(city_tour[0])-1): 22 | m = k + 1 23 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 24 | return distance 25 | 26 | ############################################################################ 27 | 28 | # Function: Possible Segments 29 | def segments_2_opt_stochastic(n, search): 30 | x = [] 31 | a, b = 0, 0 32 | for i in range(0, n): 33 | a = i 34 | for j in range(i + 1, n + (i > 0)): 35 | b = j 36 | x.append((a, b)) 37 | if (search > len(x)): 38 | search = len(x) 39 | x = random.sample(x, search) 40 | return x 41 | 42 | ############################################################################ 43 | 44 | # Function: 2_opt Stochastic 45 | def local_search_2_opt_stochastic(distance_matrix, city_tour, recursive_seeding = -1, search = 1000, verbose = True): 46 | if (recursive_seeding < 0): 47 | count = recursive_seeding - 1 48 | else: 49 | count = 0 50 | city_list = [city_tour[0][:-1], city_tour[1]] 51 | city_list_old = city_list[1]*2 52 | iteration = 0 53 | while (count < recursive_seeding): 54 | if (verbose == True): 55 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 56 | best_route = copy.deepcopy(city_list) 57 | best_route_1 = [[], 1] 58 | seed = copy.deepcopy(city_list) 59 | x = segments_2_opt_stochastic(len(city_list[0]), search) 60 | trial = [] 61 | for item in x: 62 | i, j = item 63 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 64 | trial.append([item for item in best_route[0]]) 65 | for item in trial: 66 | best_route_1[0] = item 67 | best_route_1[1] = distance_calc(distance_matrix, [best_route_1[0] + [best_route_1[0][0]], 1]) 68 | if (best_route_1[1] < best_route[1]): 69 | best_route = [best_route_1[0], best_route_1[1]] 70 | if (best_route[1] < city_list[1]): 71 | city_list = [best_route[0], best_route[1]] 72 | best_route = copy.deepcopy(seed) 73 | count = count + 1 74 | iteration = iteration + 1 75 | if (city_list_old > city_list[1] and recursive_seeding < 0): 76 | city_list_old = city_list[1] 77 | count = -2 78 | recursive_seeding = -1 79 | elif(city_list[1] >= city_list_old and recursive_seeding < 0): 80 | count = -1 81 | recursive_seeding = -2 82 | city_list = [city_list[0] + [city_list[0][0]], city_list[1]] 83 | return city_list[0], city_list[1] 84 | 85 | ############################################################################ 86 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/s_shc.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Local Search-Stochastic Hill Climbing 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import random 14 | import copy 15 | 16 | ############################################################################ 17 | 18 | # Function: Tour Distance 19 | def distance_calc(distance_matrix, city_tour): 20 | distance = 0 21 | for k in range(0, len(city_tour[0])-1): 22 | m = k + 1 23 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 24 | return distance 25 | 26 | # Function: 2_opt 27 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1): 28 | if (recursive_seeding < 0): 29 | count = -2 30 | else: 31 | count = 0 32 | city_list = copy.deepcopy(city_tour) 33 | distance = city_list[1]*2 34 | while (count < recursive_seeding): 35 | best_route = copy.deepcopy(city_list) 36 | seed = copy.deepcopy(city_list) 37 | for i in range(0, len(city_list[0]) - 2): 38 | for j in range(i+1, len(city_list[0]) - 1): 39 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 40 | best_route[0][-1] = best_route[0][0] 41 | best_route[1] = distance_calc(distance_matrix, best_route) 42 | if (city_list[1] > best_route[1]): 43 | city_list = copy.deepcopy(best_route) 44 | best_route = copy.deepcopy(seed) 45 | count = count + 1 46 | if (distance > city_list[1] and recursive_seeding < 0): 47 | distance = city_list[1] 48 | count = -2 49 | recursive_seeding = -1 50 | elif(city_list[1] >= distance and recursive_seeding < 0): 51 | count = -1 52 | recursive_seeding = -2 53 | return city_list 54 | 55 | ############################################################################ 56 | 57 | # Function: Mutation 58 | def mutate_candidate(distance_matrix, candidate): 59 | k = random.sample(list(range(1, len(candidate[0])-1)), 2) 60 | k1 = k[0] 61 | k2 = k[1] 62 | A = candidate[0][k1] 63 | B = candidate[0][k2] 64 | candidate[0][k1] = B 65 | candidate[0][k2] = A 66 | candidate[1] = distance_calc(distance_matrix, candidate) 67 | candidate = local_search_2_opt(distance_matrix, candidate, 2) 68 | return candidate 69 | 70 | ############################################################################ 71 | 72 | # Function: Stochastic Hill Climbing 73 | def stochastic_hill_climbing(distance_matrix, city_tour, iterations = 50, verbose = True): 74 | count = 0 75 | best_solution = copy.deepcopy(city_tour) 76 | candidate = copy.deepcopy(city_tour) 77 | while (count < iterations): 78 | if (verbose == True): 79 | print('Iteration = ', count, 'Distance = ', round(best_solution[1], 2)) 80 | candidate = mutate_candidate(distance_matrix, candidate) 81 | if (candidate[1] < best_solution[1]): 82 | best_solution = copy.deepcopy(candidate) 83 | count = count + 1 84 | route, distance = best_solution 85 | return route, distance 86 | 87 | ############################################################################ -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/bb.py: -------------------------------------------------------------------------------- 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: 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 | 82 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/opt_2_5.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Local Search-2.5-opt 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 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: Possible Segments 28 | def segments_2_opt(n): 29 | x = [] 30 | a, b = 0, 0 31 | for i in range(0, n): 32 | a = i 33 | for j in range(i + 1, n + (i > 0)): 34 | b = j 35 | x.append((a, b)) 36 | return x 37 | 38 | ############################################################################ 39 | 40 | # Function: 2_5_opt 41 | def local_search_2h_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 42 | if (recursive_seeding < 0): 43 | count = recursive_seeding - 1 44 | else: 45 | count = 0 46 | city_list = [city_tour[0][:-1], city_tour[1]] 47 | city_list_old = city_list[1]*2 48 | iteration = 0 49 | while (count < recursive_seeding): 50 | if (verbose == True): 51 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 52 | best_route = copy.deepcopy(city_list) 53 | best_route_1 = [[], 1] 54 | seed = copy.deepcopy(city_list) 55 | x = segments_2_opt(len(city_list[0])) 56 | for item in x: 57 | trial = [] 58 | i, j = item 59 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 60 | trial.append([item for item in best_route[0]]) 61 | insertion = [item for item in best_route[0][j+1:]] 62 | positions = list(range(i+1, j+1)) 63 | for k in insertion: 64 | new_route = [item for item in best_route[0]] 65 | new_route.remove(k) 66 | for pos in positions: 67 | new_route.insert(pos, k) 68 | trial.append([item for item in new_route]) 69 | new_route.remove(k) 70 | for item in trial: 71 | best_route_1[0] = item 72 | best_route_1[1] = distance_calc(distance_matrix, [best_route_1[0] + [best_route_1[0][0]], 1]) 73 | if (best_route_1[1] < best_route[1]): 74 | best_route = [best_route_1[0], best_route_1[1]] 75 | if (best_route[1] < city_list[1]): 76 | city_list = [best_route[0], best_route[1]] 77 | best_route = copy.deepcopy(seed) 78 | count = count + 1 79 | iteration = iteration + 1 80 | if (city_list_old > city_list[1] and recursive_seeding < 0): 81 | city_list_old = city_list[1] 82 | count = -2 83 | recursive_seeding = -1 84 | elif(city_list[1] >= city_list_old and recursive_seeding < 0): 85 | count = -1 86 | recursive_seeding = -2 87 | city_list = [city_list[0] + [city_list[0][0]], city_list[1]] 88 | return city_list[0], city_list[1] 89 | 90 | ############################################################################ 91 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/rt.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Random Tour 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import random 14 | import copy 15 | 16 | ############################################################################ 17 | 18 | # Function: Initial Seed 19 | def seed_function(distance_matrix): 20 | seed = [[], float('inf')] 21 | sequence = random.sample(list(range(1, distance_matrix.shape[0]+1)), distance_matrix.shape[0]) 22 | sequence.append(sequence[0]) 23 | seed[0] = sequence 24 | seed[1] = distance_calc(distance_matrix, seed) 25 | return seed 26 | 27 | ############################################################################ 28 | 29 | # Function: Tour Distance 30 | def distance_calc(distance_matrix, city_tour): 31 | distance = 0 32 | for k in range(0, len(city_tour[0])-1): 33 | m = k + 1 34 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 35 | return distance 36 | 37 | # Function: 2_opt 38 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 39 | if (recursive_seeding < 0): 40 | count = -2 41 | else: 42 | count = 0 43 | city_list = copy.deepcopy(city_tour) 44 | distance = city_list[1]*2 45 | iteration = 0 46 | while (count < recursive_seeding): 47 | if (verbose == True): 48 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 49 | best_route = copy.deepcopy(city_list) 50 | seed = copy.deepcopy(city_list) 51 | for i in range(0, len(city_list[0]) - 2): 52 | for j in range(i+1, len(city_list[0]) - 1): 53 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 54 | best_route[0][-1] = best_route[0][0] 55 | best_route[1] = distance_calc(distance_matrix, best_route) 56 | if (city_list[1] > best_route[1]): 57 | city_list = copy.deepcopy(best_route) 58 | best_route = copy.deepcopy(seed) 59 | count = count + 1 60 | iteration = iteration + 1 61 | if (distance > city_list[1] and recursive_seeding < 0): 62 | distance = city_list[1] 63 | count = -2 64 | recursive_seeding = -1 65 | elif(city_list[1] >= distance and recursive_seeding < 0): 66 | count = -1 67 | recursive_seeding = -2 68 | return city_list[0], city_list[1] 69 | 70 | ############################################################################ 71 | 72 | # Function: Random Tour 73 | def random_tour(distance_matrix, search = 25, local_search = True, verbose = True): 74 | count = 0 75 | seed = seed_function(distance_matrix) 76 | city_list = copy.deepcopy(seed) 77 | if (search <= 0): 78 | search = 1 79 | while (count < search): 80 | seed = seed_function(distance_matrix) 81 | best_route = copy.deepcopy(seed) 82 | if (verbose == True): 83 | print('Iteration = ', count, 'Distance = ', round(city_list[1], 2)) 84 | if (local_search == True): 85 | best_route = local_search_2_opt(distance_matrix, best_route, recursive_seeding = -1, verbose = False) 86 | if (best_route[1] < city_list[1]): 87 | city_list = copy.deepcopy(best_route) 88 | count = count + 1 89 | route, distance = city_list 90 | return route, distance 91 | 92 | ############################################################################ 93 | 94 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/opt_2_5s.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Local Search-2.5-opt Stochastic 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import random 15 | 16 | ############################################################################ 17 | 18 | # Function: Tour Distance 19 | def distance_calc(distance_matrix, city_tour): 20 | distance = 0 21 | for k in range(0, len(city_tour[0])-1): 22 | m = k + 1 23 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 24 | return distance 25 | 26 | ############################################################################ 27 | 28 | # Function: Possible Segments 29 | def segments_2_opt_stochastic(n, search): 30 | x = [] 31 | a, b = 0, 0 32 | for i in range(0, n): 33 | a = i 34 | for j in range(i + 1, n + (i > 0)): 35 | b = j 36 | x.append((a, b)) 37 | if (search > len(x)): 38 | search = len(x) 39 | x = random.sample(x, search) 40 | return x 41 | 42 | ############################################################################ 43 | 44 | # Function: 2_5_opt Stochastic 45 | def local_search_2h_opt_stochastic(distance_matrix, city_tour, recursive_seeding = -1, search = 1000, verbose = True): 46 | if (recursive_seeding < 0): 47 | count = recursive_seeding - 1 48 | else: 49 | count = 0 50 | city_list = [city_tour[0][:-1], city_tour[1]] 51 | city_list_old = city_list[1]*2 52 | iteration = 0 53 | while (count < recursive_seeding): 54 | if (verbose == True): 55 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 56 | best_route = copy.deepcopy(city_list) 57 | best_route_1 = [[], 1] 58 | seed = copy.deepcopy(city_list) 59 | x = segments_2_opt_stochastic(len(city_list[0]), search) 60 | for item in x: 61 | trial = [] 62 | i, j = item 63 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 64 | trial.append([item for item in best_route[0]]) 65 | insertion = [item for item in best_route[0][j+1:]] 66 | positions = list(range(i+1, j+1)) 67 | for k in insertion: 68 | new_route = [item for item in best_route[0]] 69 | new_route.remove(k) 70 | for pos in positions: 71 | new_route.insert(pos, k) 72 | trial.append([item for item in new_route]) 73 | new_route.remove(k) 74 | for item in trial: 75 | best_route_1[0] = item 76 | best_route_1[1] = distance_calc(distance_matrix, [best_route_1[0] + [best_route_1[0][0]], 1]) 77 | if (best_route_1[1] < best_route[1]): 78 | best_route = [best_route_1[0], best_route_1[1]] 79 | if (best_route[1] < city_list[1]): 80 | city_list = [best_route[0], best_route[1]] 81 | best_route = copy.deepcopy(seed) 82 | count = count + 1 83 | iteration = iteration + 1 84 | if (city_list_old > city_list[1] and recursive_seeding < 0): 85 | city_list_old = city_list[1] 86 | count = -2 87 | recursive_seeding = -1 88 | elif(city_list[1] >= city_list_old and recursive_seeding < 0): 89 | count = -1 90 | recursive_seeding = -2 91 | city_list = [city_list[0] + [city_list[0][0]], city_list[1]] 92 | return city_list[0], city_list[1] 93 | 94 | ############################################################################ 95 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/tat.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Twice-Around the Tree Algorithm (Double Tree Algorithm) 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import networkx as nx 15 | import numpy as np 16 | 17 | from scipy.sparse.csgraph import minimum_spanning_tree 18 | 19 | ############################################################################ 20 | 21 | # Function: Tour Distance 22 | def distance_calc(distance_matrix, city_tour): 23 | distance = 0 24 | for k in range(0, len(city_tour[0])-1): 25 | m = k + 1 26 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 27 | return distance 28 | 29 | # Function: 2_opt 30 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 31 | if (recursive_seeding < 0): 32 | count = -2 33 | else: 34 | count = 0 35 | city_list = copy.deepcopy(city_tour) 36 | distance = city_list[1]*2 37 | iteration = 0 38 | while (count < recursive_seeding): 39 | if (verbose == True): 40 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 41 | best_route = copy.deepcopy(city_list) 42 | seed = copy.deepcopy(city_list) 43 | for i in range(0, len(city_list[0]) - 2): 44 | for j in range(i+1, len(city_list[0]) - 1): 45 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 46 | best_route[0][-1] = best_route[0][0] 47 | best_route[1] = distance_calc(distance_matrix, best_route) 48 | if (city_list[1] > best_route[1]): 49 | city_list = copy.deepcopy(best_route) 50 | best_route = copy.deepcopy(seed) 51 | count = count + 1 52 | iteration = iteration + 1 53 | if (distance > city_list[1] and recursive_seeding < 0): 54 | distance = city_list[1] 55 | count = -2 56 | recursive_seeding = -1 57 | elif(city_list[1] >= distance and recursive_seeding < 0): 58 | count = -1 59 | recursive_seeding = -2 60 | return city_list[0], city_list[1] 61 | 62 | ############################################################################ 63 | 64 | # Function: Twice-Around the Tree Algorithm 65 | def tat_algorithm(distance_matrix, local_search = True, verbose = True): 66 | # Minimum Spanning Tree T 67 | graph_T = minimum_spanning_tree(distance_matrix) 68 | graph_T = graph_T.toarray().astype(int) 69 | # Double Minimum Spanning Tree H 70 | graph_H = np.zeros((graph_T.shape)) 71 | for i in range(0, graph_T.shape[0]): 72 | for j in range(0, graph_T.shape[1]): 73 | if (graph_T[i,j] > 0): 74 | graph_H[i,j] = 1 #graph_T[i,j] 75 | graph_H[j,i] = 1 #graph_T[i,j] 76 | # Eulerian Path 77 | H = nx.from_numpy_array(graph_H) 78 | if (nx.is_eulerian(H)): 79 | euler = list(nx.eulerian_path(H)) 80 | else: 81 | H = nx.eulerize(H) 82 | euler = list(nx.eulerian_path(H)) 83 | # Shortcutting 84 | route = [] 85 | for path in euler: 86 | i, j = path 87 | if (i not in route): 88 | route.append(i) 89 | if (j not in route): 90 | route.append(j) 91 | route = route + [route[0]] 92 | route = [item + 1 for item in route] 93 | distance = distance_calc(distance_matrix, [route, 1]) 94 | seed = [route, distance] 95 | if (local_search == True): 96 | route, distance = local_search_2_opt(distance_matrix, seed, recursive_seeding = -1, verbose = verbose) 97 | return route, distance 98 | 99 | ############################################################################ 100 | 101 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/ins_c.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Cheapest Insertion 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import numpy as np 15 | 16 | ############################################################################ 17 | 18 | # Function: Tour Distance 19 | def distance_calc(distance_matrix, city_tour): 20 | distance = 0 21 | for k in range(0, len(city_tour[0])-1): 22 | m = k + 1 23 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 24 | return distance 25 | 26 | # Function: 2_opt 27 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 28 | if (recursive_seeding < 0): 29 | count = -2 30 | else: 31 | count = 0 32 | city_list = copy.deepcopy(city_tour) 33 | distance = city_list[1]*2 34 | iteration = 0 35 | while (count < recursive_seeding): 36 | if (verbose == True): 37 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 38 | best_route = copy.deepcopy(city_list) 39 | seed = copy.deepcopy(city_list) 40 | for i in range(0, len(city_list[0]) - 2): 41 | for j in range(i+1, len(city_list[0]) - 1): 42 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 43 | best_route[0][-1] = best_route[0][0] 44 | best_route[1] = distance_calc(distance_matrix, best_route) 45 | if (city_list[1] > best_route[1]): 46 | city_list = copy.deepcopy(best_route) 47 | best_route = copy.deepcopy(seed) 48 | count = count + 1 49 | iteration = iteration + 1 50 | if (distance > city_list[1] and recursive_seeding < 0): 51 | distance = city_list[1] 52 | count = -2 53 | recursive_seeding = -1 54 | elif(city_list[1] >= distance and recursive_seeding < 0): 55 | count = -1 56 | recursive_seeding = -2 57 | return city_list[0], city_list[1] 58 | 59 | ############################################################################ 60 | 61 | # Function: Cheapest Insertion 62 | def cheapest_insertion(distance_matrix, verbose = True): 63 | route = [] 64 | temp = [] 65 | i, idx = np.unravel_index(np.argmax(distance_matrix, axis = None), distance_matrix.shape) 66 | temp.append(i) 67 | temp.append(idx) 68 | count = 0 69 | while (len(temp) < distance_matrix.shape[0]): 70 | temp_ = [item+1 for item in temp] 71 | temp_ = temp_ + [temp_[0]] 72 | d = distance_calc(distance_matrix, [temp_, 1]) 73 | seed = [temp_, d] 74 | temp_, _ = local_search_2_opt(distance_matrix, seed, recursive_seeding = -1, verbose = False) 75 | temp = [item-1 for item in temp_[:-1]] 76 | idx = [i for i in range(0, distance_matrix.shape[0]) if i not in temp] 77 | best_d = [] 78 | best_r = [] 79 | for i in idx: 80 | temp_ = [item for item in temp] 81 | temp_.append(i) 82 | temp_ = [item+1 for item in temp_] 83 | temp_ = temp_ + [temp_[0]] 84 | d = distance_calc(distance_matrix, [temp_, 1]) 85 | seed = [temp_, d] 86 | temp_, d = local_search_2_opt(distance_matrix, seed, recursive_seeding = -1, verbose = False) 87 | temp_ = [item-1 for item in temp_[:-1]] 88 | best_d.append(d) 89 | best_r.append(temp_) 90 | temp = [item for item in best_r[best_d.index(min(best_d))]] 91 | if (verbose == True): 92 | print('Iteration = ', count) 93 | count = count + 1 94 | route = temp + [temp[0]] 95 | route = [item + 1 for item in route] 96 | distance = distance_calc(distance_matrix, [route, 1]) 97 | return route, distance 98 | 99 | ############################################################################ -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/nn.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Nearest Neighbour 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import numpy as np 15 | 16 | ############################################################################ 17 | 18 | # Function: Tour Distance 19 | def distance_calc(distance_matrix, city_tour): 20 | distance = 0 21 | for k in range(0, len(city_tour[0])-1): 22 | m = k + 1 23 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 24 | return distance 25 | 26 | # Function: 2_opt 27 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 28 | if (recursive_seeding < 0): 29 | count = -2 30 | else: 31 | count = 0 32 | city_list = copy.deepcopy(city_tour) 33 | distance = city_list[1]*2 34 | iteration = 0 35 | while (count < recursive_seeding): 36 | if (verbose == True): 37 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 38 | best_route = copy.deepcopy(city_list) 39 | seed = copy.deepcopy(city_list) 40 | for i in range(0, len(city_list[0]) - 2): 41 | for j in range(i+1, len(city_list[0]) - 1): 42 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 43 | best_route[0][-1] = best_route[0][0] 44 | best_route[1] = distance_calc(distance_matrix, best_route) 45 | if (city_list[1] > best_route[1]): 46 | city_list = copy.deepcopy(best_route) 47 | best_route = copy.deepcopy(seed) 48 | count = count + 1 49 | iteration = iteration + 1 50 | if (distance > city_list[1] and recursive_seeding < 0): 51 | distance = city_list[1] 52 | count = -2 53 | recursive_seeding = -1 54 | elif(city_list[1] >= distance and recursive_seeding < 0): 55 | count = -1 56 | recursive_seeding = -2 57 | return city_list[0], city_list[1] 58 | 59 | ############################################################################ 60 | 61 | # Function: Nearest Neighbour 62 | def nearest_neighbour(distance_matrix, initial_location = -1, local_search = True, verbose = True): 63 | minimum = float('+inf') 64 | distance = float('+inf') 65 | route = [] 66 | for i1 in range(0, distance_matrix.shape[0]): 67 | if (initial_location != -1): 68 | i1 = initial_location-1 69 | temp = [] 70 | dist = np.copy(distance_matrix) 71 | dist = dist.astype(float) 72 | np.fill_diagonal(dist, float('+inf')) 73 | idx = dist[i1,:].argmin() 74 | dist[i1,:] = float('+inf') 75 | dist[:,i1] = float('+inf') 76 | temp.append(i1) 77 | temp.append(idx) 78 | for _ in range(0, distance_matrix.shape[0]-2): 79 | i2 = idx 80 | idx = dist[i2,:].argmin() 81 | dist[i2,:] = float('+inf') 82 | dist[:,i2] = float('+inf') 83 | temp.append(idx) 84 | temp = temp + [temp[0]] 85 | temp = [item + 1 for item in temp] 86 | val = distance_calc(distance_matrix, [temp, 1]) 87 | if (local_search == True): 88 | temp, val = local_search_2_opt(distance_matrix, [temp, val], recursive_seeding = -1, verbose = False) 89 | if (val < minimum): 90 | minimum = val 91 | distance = val 92 | route = [item for item in temp] 93 | if (verbose == True): 94 | print('Iteration = ', i1, 'Distance = ', round(distance, 2)) 95 | if (initial_location == -1): 96 | continue 97 | else: 98 | break 99 | return route, distance 100 | 101 | ############################################################################ 102 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/conv_hull.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Convex Hull Algorithm 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | 15 | from scipy.spatial import ConvexHull 16 | 17 | ############################################################################ 18 | 19 | # Function: Tour Distance 20 | def distance_calc(distance_matrix, city_tour): 21 | distance = 0 22 | for k in range(0, len(city_tour[0])-1): 23 | m = k + 1 24 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 25 | return distance 26 | 27 | # Function: 2_opt 28 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 29 | if (recursive_seeding < 0): 30 | count = -2 31 | else: 32 | count = 0 33 | city_list = copy.deepcopy(city_tour) 34 | distance = city_list[1]*2 35 | iteration = 0 36 | while (count < recursive_seeding): 37 | if (verbose == True): 38 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 39 | best_route = copy.deepcopy(city_list) 40 | seed = copy.deepcopy(city_list) 41 | for i in range(0, len(city_list[0]) - 2): 42 | for j in range(i+1, len(city_list[0]) - 1): 43 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 44 | best_route[0][-1] = best_route[0][0] 45 | best_route[1] = distance_calc(distance_matrix, best_route) 46 | if (city_list[1] > best_route[1]): 47 | city_list = copy.deepcopy(best_route) 48 | best_route = copy.deepcopy(seed) 49 | count = count + 1 50 | iteration = iteration + 1 51 | if (distance > city_list[1] and recursive_seeding < 0): 52 | distance = city_list[1] 53 | count = -2 54 | recursive_seeding = -1 55 | elif(city_list[1] >= distance and recursive_seeding < 0): 56 | count = -1 57 | recursive_seeding = -2 58 | return city_list[0], city_list[1] 59 | 60 | ############################################################################ 61 | 62 | # Function: Convex Hull 63 | def convex_hull_algorithm(coordinates, distance_matrix, local_search = True, verbose = True): 64 | hull = ConvexHull(coordinates) 65 | idx_h = hull.vertices.tolist() 66 | idx_h = [item+1 for item in idx_h] 67 | idx_h_pairs = [(idx_h[i], idx_h[i+1]) for i in range(0, len(idx_h)-1)] 68 | idx_h_pairs.append((idx_h[-1], idx_h[0])) 69 | idx_in = [item for item in list(range(1, coordinates.shape[0]+1)) if item not in idx_h] 70 | for _ in range(0, len(idx_in)): 71 | x = [] 72 | y = [] 73 | z = [] 74 | for i in range(0, len(idx_in)): 75 | L = idx_in[i] 76 | cost = [(distance_matrix[m-1, L-1], distance_matrix[L-1, n-1], distance_matrix[m-1, n-1]) for m, n in idx_h_pairs] 77 | cost_idx = [(m, L, n) for m, n in idx_h_pairs] 78 | cost_vec_1 = [ item[0] + item[1] - item[2] for item in cost] 79 | cost_vec_2 = [(item[0] + item[1]) / (item[2] + 0.00000000000000001) for item in cost] 80 | x.append(cost_vec_1.index(min(cost_vec_1))) 81 | y.append(cost_vec_2[x[-1]]) 82 | z.append(cost_idx[x[-1]]) 83 | m, L, n = z[y.index(min(y))] 84 | idx_in.remove(L) 85 | ins = idx_h.index(m) 86 | idx_h.insert(ins + 1, L) 87 | idx_h_pairs = [ (idx_h[i], idx_h[i+1]) for i in range(0, len(idx_h)-1)] 88 | idx_h_pairs.append((idx_h[-1], idx_h[0])) 89 | route = idx_h + [idx_h[0]] 90 | distance = distance_calc(distance_matrix, [route, 1]) 91 | seed = [route, distance] 92 | if (local_search == True): 93 | route, distance = local_search_2_opt(distance_matrix, seed, recursive_seeding = -1, verbose = verbose) 94 | return route, distance 95 | 96 | ############################################################################ 97 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/opt_3.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Local Search-3-opt 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 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: Possible Segments 28 | def segments_3_opt(n): 29 | x = [] 30 | a, b, c = 0, 0, 0 31 | for i in range(0, n): 32 | a = i 33 | for j in range(i + 1, n): 34 | b = j 35 | for k in range(j + 1, n + (i > 0)): 36 | c = k 37 | x.append((a, b, c)) 38 | return x 39 | 40 | ############################################################################ 41 | 42 | # Function: 3_opt 43 | def local_search_3_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 44 | if (recursive_seeding < 0): 45 | count = recursive_seeding - 1 46 | else: 47 | count = 0 48 | city_list = [city_tour[0][:-1], city_tour[1]] 49 | city_list_old = city_list[1]*2 50 | iteration = 0 51 | while (count < recursive_seeding): 52 | if (verbose == True): 53 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 54 | best_route = copy.deepcopy(city_list) 55 | best_route_1 = [[], 1] 56 | seed = copy.deepcopy(city_list) 57 | x = segments_3_opt(len(city_list[0])) 58 | for item in x: 59 | i, j, k = item 60 | A = best_route[0][:i+1] + best_route[0][i+1:j+1] 61 | a = best_route[0][:i+1] + list(reversed(best_route[0][i+1:j+1])) 62 | B = best_route[0][j+1:k+1] 63 | b = list(reversed(B)) 64 | C = best_route[0][k+1:] 65 | c = list(reversed(C)) 66 | trial = [ 67 | # Original Tour 68 | #[A + B + C], 69 | 70 | # 1 71 | [a + B + C], 72 | [A + b + C], 73 | [A + B + c], 74 | 75 | 76 | # 2 77 | [A + b + c], 78 | [a + b + C], 79 | [a + B + c], 80 | 81 | 82 | # 3 83 | [a + b + c] 84 | 85 | ] 86 | # Possibly, there is a sequence of 2-opt moves that decreases the total distance but it begins 87 | # with a move that first increases it 88 | for item in trial: 89 | best_route_1[0] = item[0] 90 | best_route_1[1] = distance_calc(distance_matrix, [best_route_1[0] + [best_route_1[0][0]], 1]) 91 | if (best_route_1[1] < best_route[1]): 92 | best_route = [best_route_1[0], best_route_1[1]] 93 | if (best_route[1] < city_list[1]): 94 | city_list = [best_route[0], best_route[1]] 95 | best_route = copy.deepcopy(seed) 96 | count = count + 1 97 | iteration = iteration + 1 98 | if (city_list_old > city_list[1] and recursive_seeding < 0): 99 | city_list_old = city_list[1] 100 | count = -2 101 | recursive_seeding = -1 102 | elif(city_list[1] >= city_list_old and recursive_seeding < 0): 103 | count = -1 104 | recursive_seeding = -2 105 | city_list = [city_list[0] + [city_list[0][0]], city_list[1]] 106 | return city_list[0], city_list[1] 107 | 108 | ############################################################################ 109 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/s_itr.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Iterated Search 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import random 14 | import copy 15 | 16 | ############################################################################ 17 | 18 | # Function: Tour Distance 19 | def distance_calc(distance_matrix, city_tour): 20 | distance = 0 21 | for k in range(0, len(city_tour[0])-1): 22 | m = k + 1 23 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 24 | return distance 25 | 26 | ############################################################################ 27 | 28 | # Function: Stochastic 2_opt 29 | def stochastic_2_opt(distance_matrix, city_tour): 30 | best_route = copy.deepcopy(city_tour) 31 | i, j = random.sample(list(range(0, len(city_tour[0])-1)), 2) 32 | if (i > j): 33 | i, j = j, i 34 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 35 | best_route[0][-1] = best_route[0][0] 36 | best_route[1] = distance_calc(distance_matrix, best_route) 37 | return best_route 38 | 39 | # Function: Local Search 40 | def local_search(distance_matrix, city_tour, max_attempts = 50): 41 | count = 0 42 | solution = copy.deepcopy(city_tour) 43 | while (count < max_attempts): 44 | candidate = stochastic_2_opt(distance_matrix, solution) 45 | if candidate[1] < solution[1]: 46 | solution = copy.deepcopy(candidate) 47 | count = 0 48 | else: 49 | count = count + 1 50 | return solution 51 | 52 | ############################################################################ 53 | 54 | # Function: 4-opt Pertubation 55 | def pertubation_4_opt(distance_matrix, city_tour): 56 | cl = [city_tour[0][:-1], city_tour[1]] 57 | i, j, k, L = random.sample(list(range(0, len(cl[0]))), 4) 58 | idx = [i, j, k, L] 59 | idx.sort() 60 | i, j, k, L = idx 61 | A = cl[0][:i+1] + cl[0][i+1:j+1] 62 | B = cl[0][j+1:k+1] 63 | b = list(reversed(B)) 64 | C = cl[0][k+1:L+1] 65 | c = list(reversed(C)) 66 | D = cl[0][L+1:] 67 | d = list(reversed(D)) 68 | trial = [ 69 | # 4-opt: Sequential 70 | [A + b + c + d], [A + C + B + d], [A + C + b + d], [A + c + B + d], [A + D + B + c], 71 | [A + D + b + C], [A + d + B + c], [A + d + b + C], [A + d + b + c], [A + b + D + C], 72 | [A + b + D + c], [A + b + d + C], [A + C + d + B], [A + C + d + b], [A + c + D + B], 73 | [A + c + D + b], [A + c + d + b], [A + D + C + b], [A + D + c + B], [A + d + C + B], 74 | 75 | # 4-opt: Non-Sequential 76 | [A + b + C + d], [A + D + b + c], [A + c + d + B], [A + D + C + B], [A + d + C + b] 77 | ] 78 | item = random.choice(trial) 79 | cl[0] = item[0] 80 | cl[0] = cl[0] + [cl[0][0]] 81 | cl[1] = distance_calc(distance_matrix, cl) 82 | return cl 83 | 84 | ############################################################################ 85 | 86 | # Function: Iterated Search 87 | def iterated_search(distance_matrix, city_tour, max_attempts = 20, iterations = 50, verbose = True): 88 | count = 0 89 | solution = copy.deepcopy(city_tour) 90 | best_solution = copy.deepcopy(city_tour) 91 | while (count < iterations): 92 | if (verbose == True): 93 | print('Iteration = ', count, 'Distance = ', round(best_solution[1], 2)) 94 | if (distance_matrix.shape[0] > 4): 95 | solution = pertubation_4_opt(distance_matrix, solution) 96 | solution = local_search(distance_matrix, solution, max_attempts) 97 | if (solution[1] < best_solution[1]): 98 | best_solution = copy.deepcopy(solution) 99 | count = count + 1 100 | route, distance = best_solution 101 | return route, distance 102 | 103 | ############################################################################ 104 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/spfc_m.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Space Filling Curve (Morton) 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import numpy as np 15 | 16 | ############################################################################ 17 | 18 | # Function: Tour Distance 19 | def distance_calc(distance_matrix, city_tour): 20 | distance = 0 21 | for k in range(0, len(city_tour[0])-1): 22 | m = k + 1 23 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 24 | return distance 25 | 26 | # Function: 2_opt 27 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 28 | if (recursive_seeding < 0): 29 | count = -2 30 | else: 31 | count = 0 32 | city_list = copy.deepcopy(city_tour) 33 | distance = city_list[1]*2 34 | iteration = 0 35 | while (count < recursive_seeding): 36 | if (verbose == True): 37 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 38 | best_route = copy.deepcopy(city_list) 39 | seed = copy.deepcopy(city_list) 40 | for i in range(0, len(city_list[0]) - 2): 41 | for j in range(i+1, len(city_list[0]) - 1): 42 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 43 | best_route[0][-1] = best_route[0][0] 44 | best_route[1] = distance_calc(distance_matrix, best_route) 45 | if (city_list[1] > best_route[1]): 46 | city_list = copy.deepcopy(best_route) 47 | best_route = copy.deepcopy(seed) 48 | count = count + 1 49 | iteration = iteration + 1 50 | if (distance > city_list[1] and recursive_seeding < 0): 51 | distance = city_list[1] 52 | count = -2 53 | recursive_seeding = -1 54 | elif(city_list[1] >= distance and recursive_seeding < 0): 55 | count = -1 56 | recursive_seeding = -2 57 | return city_list[0], city_list[1] 58 | 59 | ############################################################################ 60 | 61 | # Function: Get Morton 2D points. Adapted from: https://bertvandenbroucke.netlify.app/2019/01/18/space-filling-curves/ 62 | def get_morton_2d(coordinates): 63 | def get_idx(x, y, size): 64 | result = 0 65 | for i in range(0, size): 66 | result |= (x & 1) << (2 * i + 1) 67 | result |= (y & 1) << (2 * i) 68 | x >>= 1 69 | y >>= 1 70 | return result 71 | max_val = np.max(coordinates)+1 72 | limit = -1 73 | k = 1 74 | while (limit < max_val): 75 | limit = 2**k 76 | k = k + 1 77 | idxs = [get_idx(coordinates[i, 0], coordinates[i, 1], k) for i in range(0, coordinates.shape[0])] 78 | idxs = sorted(range(len(idxs)), key = lambda k: idxs[k]) 79 | return idxs 80 | 81 | ############################################################################ 82 | 83 | # Function: SFC (Morton) 84 | def space_filling_curve_m(coordinates, distance_matrix, local_search = True, verbose = True): 85 | flag = False 86 | n_coord = np.copy(coordinates) 87 | for i in range(0, coordinates.shape[0]): 88 | for j in range(0, coordinates.shape[1]): 89 | if ( coordinates[i,j] / int(coordinates[i,j]) > 1): 90 | flag = True 91 | break 92 | if (flag == True): 93 | n_coord = n_coord*100 94 | n_coord = n_coord.astype(int) 95 | else: 96 | n_coord = n_coord.astype(int) 97 | route = get_morton_2d(n_coord) 98 | route = route + [route[0]] 99 | route = [item+1 for item in route] 100 | distance = distance_calc(distance_matrix, [route, 1]) 101 | seed = [route, distance] 102 | if (local_search == True): 103 | route, distance = local_search_2_opt(distance_matrix, seed, recursive_seeding = -1, verbose = verbose) 104 | return route, distance 105 | 106 | ############################################################################ 107 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/opt_3s.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Local Search-3-opt Stochastic 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import random 15 | 16 | ############################################################################ 17 | 18 | # Function: Tour Distance 19 | def distance_calc(distance_matrix, city_tour): 20 | distance = 0 21 | for k in range(0, len(city_tour[0])-1): 22 | m = k + 1 23 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 24 | return distance 25 | 26 | ############################################################################ 27 | 28 | # Function: Possible Segments 29 | def segments_3_opt_stochastic(n, search): 30 | x = [] 31 | a, b, c = 0, 0, 0 32 | for i in range(0, n): 33 | a = i 34 | for j in range(i + 1, n): 35 | b = j 36 | for k in range(j + 1, n + (i > 0)): 37 | c = k 38 | x.append((a, b, c)) 39 | if (search > len(x)): 40 | search = len(x) 41 | x = random.sample(x, search) 42 | return x 43 | 44 | ############################################################################ 45 | 46 | # Function: 3_opt Stochastic 47 | def local_search_3_opt_stochastic(distance_matrix, city_tour, recursive_seeding = -1, search = 1000, verbose = True): 48 | if (recursive_seeding < 0): 49 | count = recursive_seeding - 1 50 | else: 51 | count = 0 52 | city_list = [city_tour[0][:-1], city_tour[1]] 53 | city_list_old = city_list[1]*2 54 | iteration = 0 55 | while (count < recursive_seeding): 56 | if (verbose == True): 57 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 58 | best_route = copy.deepcopy(city_list) 59 | best_route_1 = [[], 1] 60 | seed = copy.deepcopy(city_list) 61 | x = segments_3_opt_stochastic(len(city_list[0]), search) 62 | for item in x: 63 | i, j, k = item 64 | A = best_route[0][:i+1] + best_route[0][i+1:j+1] 65 | a = best_route[0][:i+1] + list(reversed(best_route[0][i+1:j+1])) 66 | B = best_route[0][j+1:k+1] 67 | b = list(reversed(B)) 68 | C = best_route[0][k+1:] 69 | c = list(reversed(C)) 70 | trial = [ 71 | # Original Tour 72 | #[A + B + C], 73 | 74 | # 1 75 | [a + B + C], 76 | [A + b + C], 77 | [A + B + c], 78 | 79 | 80 | # 2 81 | [A + b + c], 82 | [a + b + C], 83 | [a + B + c], 84 | 85 | 86 | # 3 87 | [a + b + c], 88 | ] 89 | for item in trial: 90 | best_route_1[0] = item[0] 91 | best_route_1[1] = distance_calc(distance_matrix, [best_route_1[0] + [best_route_1[0][0]], 1]) 92 | if (best_route_1[1] < best_route[1]): 93 | best_route = [best_route_1[0], best_route_1[1]] 94 | if (best_route[1] < city_list[1]): 95 | city_list = [best_route[0], best_route[1]] 96 | best_route = copy.deepcopy(seed) 97 | count = count + 1 98 | iteration = iteration + 1 99 | if (city_list_old > city_list[1] and recursive_seeding < 0): 100 | city_list_old = city_list[1] 101 | count = -2 102 | recursive_seeding = -1 103 | elif(city_list[1] >= city_list_old and recursive_seeding < 0): 104 | count = -1 105 | recursive_seeding = -2 106 | city_list = [city_list[0] + [city_list[0][0]], city_list[1]] 107 | return city_list[0], city_list[1] 108 | 109 | ############################################################################ 110 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/tbb.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Truncated Branch & Bound 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import numpy as np 15 | 16 | ############################################################################### 17 | 18 | # Function: Tour Distance 19 | def distance_calc(distance_matrix, city_tour): 20 | distance = 0 21 | for k in range(0, len(city_tour[0])-1): 22 | m = k + 1 23 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 24 | return distance 25 | 26 | # Function: 2_opt 27 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 28 | if (recursive_seeding < 0): 29 | count = -2 30 | else: 31 | count = 0 32 | city_list = copy.deepcopy(city_tour) 33 | distance = city_list[1]*2 34 | iteration = 0 35 | while (count < recursive_seeding): 36 | if (verbose == True): 37 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 38 | best_route = copy.deepcopy(city_list) 39 | seed = copy.deepcopy(city_list) 40 | for i in range(0, len(city_list[0]) - 2): 41 | for j in range(i+1, len(city_list[0]) - 1): 42 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 43 | best_route[0][-1] = best_route[0][0] 44 | best_route[1] = distance_calc(distance_matrix, best_route) 45 | if (city_list[1] > best_route[1]): 46 | city_list = copy.deepcopy(best_route) 47 | best_route = copy.deepcopy(seed) 48 | count = count + 1 49 | iteration = iteration + 1 50 | if (distance > city_list[1] and recursive_seeding < 0): 51 | distance = city_list[1] 52 | count = -2 53 | recursive_seeding = -1 54 | elif(city_list[1] >= distance and recursive_seeding < 0): 55 | count = -1 56 | recursive_seeding = -2 57 | return city_list[0], city_list[1] 58 | 59 | ############################################################################### 60 | 61 | # Function: Truncated B&B 62 | def truncated_branch_and_bound(distance_matrix, verbose = True): 63 | dist = np.copy(distance_matrix) 64 | dist = dist.astype(float) 65 | np.fill_diagonal(dist, float('+inf')) 66 | r_min = np.min(dist, axis = 1) 67 | dist = dist - r_min.reshape(-1,1) 68 | c_min = np.min(dist, axis = 0) 69 | dist = dist - c_min.reshape(1,-1) 70 | cost = np.sum(r_min + c_min) 71 | k = 0 72 | route = [k] 73 | nodes = [i for i in list(range(0, distance_matrix.shape[0])) if i not in route] 74 | count = 0 75 | while len(nodes) > 0: 76 | c_lst = [] 77 | for i in nodes: 78 | reduced = np.copy(dist) 79 | reduced[k, :] = float('+inf') 80 | reduced[:, i] = float('+inf') 81 | reduced[i, k] = float('+inf') 82 | r_min = np.min(reduced, axis = 1) 83 | c_min = np.min(reduced, axis = 0) 84 | r_min = np.where(r_min == float('+inf'), 0, r_min) 85 | c_min = np.where(c_min == float('+inf'), 0, c_min) 86 | c_lst.append(cost + dist[k, i] + np.sum(r_min + c_min)) 87 | i = nodes[c_lst.index(min(c_lst))] 88 | cost = cost + min(c_lst) 89 | dist[k, :] = float('+inf') 90 | dist[:, i] = float('+inf') 91 | k = i 92 | route.append(i) 93 | nodes.remove(i) 94 | if (verbose == True): 95 | print('Iteration = ', count, ' Visited Nodes = ', len(route)) 96 | count = count + 1 97 | route = route + [route[0]] 98 | route = [item + 1 for item in route] 99 | distance = distance_calc(distance_matrix, [route, 1]) 100 | seed = [route, distance] 101 | route, distance = local_search_2_opt(distance_matrix, seed, recursive_seeding = -1, verbose = False) 102 | return route, distance 103 | 104 | ############################################################################### 105 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/ins_n.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Nearest Insertion 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import numpy as np 15 | 16 | ############################################################################ 17 | 18 | # Function: Tour Distance 19 | def distance_calc(distance_matrix, city_tour): 20 | distance = 0 21 | for k in range(0, len(city_tour[0])-1): 22 | m = k + 1 23 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 24 | return distance 25 | 26 | # Function: 2_opt 27 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 28 | if (recursive_seeding < 0): 29 | count = -2 30 | else: 31 | count = 0 32 | city_list = copy.deepcopy(city_tour) 33 | distance = city_list[1]*2 34 | iteration = 0 35 | while (count < recursive_seeding): 36 | if (verbose == True): 37 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 38 | best_route = copy.deepcopy(city_list) 39 | seed = copy.deepcopy(city_list) 40 | for i in range(0, len(city_list[0]) - 2): 41 | for j in range(i+1, len(city_list[0]) - 1): 42 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 43 | best_route[0][-1] = best_route[0][0] 44 | best_route[1] = distance_calc(distance_matrix, best_route) 45 | if (city_list[1] > best_route[1]): 46 | city_list = copy.deepcopy(best_route) 47 | best_route = copy.deepcopy(seed) 48 | count = count + 1 49 | iteration = iteration + 1 50 | if (distance > city_list[1] and recursive_seeding < 0): 51 | distance = city_list[1] 52 | count = -2 53 | recursive_seeding = -1 54 | elif(city_list[1] >= distance and recursive_seeding < 0): 55 | count = -1 56 | recursive_seeding = -2 57 | return city_list[0], city_list[1] 58 | 59 | ############################################################################ 60 | 61 | # Function: Best Insertion 62 | def best_insertion(distance_matrix, temp): 63 | temp_ = [item+1 for item in temp] 64 | temp_ = temp_ + [temp_[0]] 65 | d = distance_calc(distance_matrix, [temp_, 1]) 66 | seed = [temp_, d] 67 | temp_, _ = local_search_2_opt(distance_matrix, seed, recursive_seeding = -1, verbose = False) 68 | temp = [item-1 for item in temp_[:-1]] 69 | return temp 70 | 71 | ############################################################################ 72 | 73 | # Function: Nearest Insertion 74 | def nearest_insertion(distance_matrix, initial_location = -1, verbose = True): 75 | minimum = float('+inf') 76 | distance = float('+inf') 77 | route = [] 78 | for i1 in range(0, distance_matrix.shape[0]): 79 | if (initial_location != -1): 80 | i1 = initial_location-1 81 | temp = [] 82 | dist = np.copy(distance_matrix) 83 | dist = dist.astype(float) 84 | np.fill_diagonal(dist, float('+inf')) 85 | idx = dist[i1,:].argmin() 86 | dist[i1,:] = float('+inf') 87 | dist[:,i1] = float('+inf') 88 | temp.append(i1) 89 | temp.append(idx) 90 | for j in range(0, distance_matrix.shape[0]-2): 91 | i2 = idx 92 | idx = dist[i2,:].argmin() 93 | dist[i2,:] = float('+inf') 94 | dist[:,i2] = float('+inf') 95 | temp.append(idx) 96 | temp = best_insertion(distance_matrix, temp) 97 | temp = temp + [temp[0]] 98 | temp = [item + 1 for item in temp] 99 | val = distance_calc(distance_matrix, [temp, 1]) 100 | if (val < minimum): 101 | minimum = val 102 | distance = val 103 | route = [item for item in temp] 104 | if (verbose == True): 105 | print('Iteration = ', i1, 'Distance = ', round(distance, 2)) 106 | if (initial_location == -1): 107 | continue 108 | else: 109 | break 110 | return route, distance 111 | 112 | ############################################################################ 113 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/ins_f.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Farthest Insertion 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import numpy as np 15 | 16 | ############################################################################ 17 | 18 | # Function: Tour Distance 19 | def distance_calc(distance_matrix, city_tour): 20 | distance = 0 21 | for k in range(0, len(city_tour[0])-1): 22 | m = k + 1 23 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 24 | return distance 25 | 26 | # Function: 2_opt 27 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 28 | if (recursive_seeding < 0): 29 | count = -2 30 | else: 31 | count = 0 32 | city_list = copy.deepcopy(city_tour) 33 | distance = city_list[1]*2 34 | iteration = 0 35 | while (count < recursive_seeding): 36 | if (verbose == True): 37 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 38 | best_route = copy.deepcopy(city_list) 39 | seed = copy.deepcopy(city_list) 40 | for i in range(0, len(city_list[0]) - 2): 41 | for j in range(i+1, len(city_list[0]) - 1): 42 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 43 | best_route[0][-1] = best_route[0][0] 44 | best_route[1] = distance_calc(distance_matrix, best_route) 45 | if (city_list[1] > best_route[1]): 46 | city_list = copy.deepcopy(best_route) 47 | best_route = copy.deepcopy(seed) 48 | count = count + 1 49 | iteration = iteration + 1 50 | if (distance > city_list[1] and recursive_seeding < 0): 51 | distance = city_list[1] 52 | count = -2 53 | recursive_seeding = -1 54 | elif(city_list[1] >= distance and recursive_seeding < 0): 55 | count = -1 56 | recursive_seeding = -2 57 | return city_list[0], city_list[1] 58 | 59 | ############################################################################ 60 | 61 | # Function: Best Insertion 62 | def best_insertion(distance_matrix, temp): 63 | temp_ = [item+1 for item in temp] 64 | temp_ = temp_ + [temp_[0]] 65 | d = distance_calc(distance_matrix, [temp_, 1]) 66 | seed = [temp_, d] 67 | temp_, _ = local_search_2_opt(distance_matrix, seed, recursive_seeding = -1, verbose = False) 68 | temp = [item-1 for item in temp_[:-1]] 69 | return temp 70 | 71 | ############################################################################ 72 | 73 | # Function: Farthest Insertion 74 | def farthest_insertion(distance_matrix, initial_location = -1, verbose = True): 75 | maximum = float('+inf') 76 | distance = float('+inf') 77 | route = [] 78 | for i1 in range(0, distance_matrix.shape[0]): 79 | if (initial_location != -1): 80 | i1 = initial_location-1 81 | temp = [] 82 | dist = np.copy(distance_matrix) 83 | dist = dist.astype(float) 84 | np.fill_diagonal(dist, float('-inf')) 85 | idx = dist[i1,:].argmax() 86 | dist[i1,:] = float('-inf') 87 | dist[:,i1] = float('-inf') 88 | temp.append(i1) 89 | temp.append(idx) 90 | for j in range(0, distance_matrix.shape[0]-2): 91 | i2 = idx 92 | idx = dist[i2,:].argmax() 93 | dist[i2,:] = float('-inf') 94 | dist[:,i2] = float('-inf') 95 | temp.append(idx) 96 | temp = best_insertion(distance_matrix, temp) 97 | temp = temp + [temp[0]] 98 | temp = [item + 1 for item in temp] 99 | val = distance_calc(distance_matrix, [temp, 1]) 100 | if (val < maximum): 101 | maximum = val 102 | distance = val 103 | route = [item for item in temp] 104 | if (verbose == True): 105 | print('Iteration = ', i1, 'Distance = ', round(distance, 2)) 106 | if (initial_location == -1): 107 | continue 108 | else: 109 | break 110 | return route, distance 111 | 112 | ############################################################################ 113 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/bt.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Bitonic Tour 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import numpy as np 15 | 16 | ############################################################################ 17 | 18 | # Function: Tour Distance 19 | def distance_calc(distance_matrix, city_tour): 20 | distance = 0 21 | for k in range(0, len(city_tour[0])-1): 22 | m = k + 1 23 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 24 | return distance 25 | 26 | # Function: 2_opt 27 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 28 | if (recursive_seeding < 0): 29 | count = -2 30 | else: 31 | count = 0 32 | city_list = copy.deepcopy(city_tour) 33 | distance = city_list[1]*2 34 | iteration = 0 35 | while (count < recursive_seeding): 36 | if (verbose == True): 37 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 38 | best_route = copy.deepcopy(city_list) 39 | seed = copy.deepcopy(city_list) 40 | for i in range(0, len(city_list[0]) - 2): 41 | for j in range(i+1, len(city_list[0]) - 1): 42 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 43 | best_route[0][-1] = best_route[0][0] 44 | best_route[1] = distance_calc(distance_matrix, best_route) 45 | if (city_list[1] > best_route[1]): 46 | city_list = copy.deepcopy(best_route) 47 | best_route = copy.deepcopy(seed) 48 | count = count + 1 49 | iteration = iteration + 1 50 | if (distance > city_list[1] and recursive_seeding < 0): 51 | distance = city_list[1] 52 | count = -2 53 | recursive_seeding = -1 54 | elif(city_list[1] >= distance and recursive_seeding < 0): 55 | count = -1 56 | recursive_seeding = -2 57 | return city_list[0], city_list[1] 58 | 59 | ############################################################################ 60 | 61 | # Function: Bitonic Tour 62 | def bitonic_tour(coordinates, distance_matrix, local_search = True, verbose = True): 63 | sorted_indices = np.argsort(coordinates[:, 0]) 64 | coords_sorted = coordinates[sorted_indices] 65 | dist_sorted = distance_matrix[sorted_indices][:, sorted_indices] 66 | n = len(coords_sorted) 67 | if (n <= 1): 68 | return 0.0, sorted_indices.tolist() 69 | elif (n == 2): 70 | return dist_sorted[0, 1] + dist_sorted[1, 0], sorted_indices.tolist() 71 | dp = np.full((n, n), np.inf) 72 | parent = [[None] * n for _ in range(0, n)] 73 | dp[0, 1] = dist_sorted[0, 1] 74 | parent[0][1] = None 75 | for j in range(2, n): 76 | for i in range(0, j): 77 | if (i == 0): 78 | dp[0, j] = dp[0, j - 1] + dist_sorted[j - 1, j] 79 | parent[0][j] = (0, j - 1) 80 | else: 81 | best_val = np.inf 82 | best_k = None 83 | for k in range(0, i): 84 | candidate = dp[k, i] + dist_sorted[k, j] 85 | if (candidate < best_val): 86 | best_val = candidate 87 | best_k = k 88 | dp[i, j] = best_val 89 | parent[i][j] = (best_k, i) 90 | distance = dp[0, n - 1] + dist_sorted[0, n - 1] 91 | route_indices = [n - 1] 92 | i, j = 0, n - 1 93 | while (True): 94 | if (verbose == True): 95 | print('Node = ', j+1) 96 | pval = parent[i][j] 97 | if (pval is None): 98 | route_indices.append(i) 99 | break 100 | route_indices.append(pval[1]) 101 | i, j = pval 102 | route_indices.reverse() 103 | route = sorted_indices[route_indices].tolist() 104 | route = [item + 1 for item in route] 105 | route.append(route[0]) 106 | distance = distance_calc(distance_matrix, [route, 1]) 107 | seed = [route, distance] 108 | if (local_search == True): 109 | route, distance = local_search_2_opt(distance_matrix, seed, recursive_seeding = -1, verbose = verbose) 110 | return route, distance 111 | 112 | ############################################################################ 113 | 114 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/ins_r.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Random Insertion 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import numpy as np 15 | import random 16 | 17 | ############################################################################ 18 | 19 | # Function: Tour Distance 20 | def distance_calc(distance_matrix, city_tour): 21 | distance = 0 22 | for k in range(0, len(city_tour[0])-1): 23 | m = k + 1 24 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 25 | return distance 26 | 27 | # Function: 2_opt 28 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 29 | if (recursive_seeding < 0): 30 | count = -2 31 | else: 32 | count = 0 33 | city_list = copy.deepcopy(city_tour) 34 | distance = city_list[1]*2 35 | iteration = 0 36 | while (count < recursive_seeding): 37 | if (verbose == True): 38 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 39 | best_route = copy.deepcopy(city_list) 40 | seed = copy.deepcopy(city_list) 41 | for i in range(0, len(city_list[0]) - 2): 42 | for j in range(i+1, len(city_list[0]) - 1): 43 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 44 | best_route[0][-1] = best_route[0][0] 45 | best_route[1] = distance_calc(distance_matrix, best_route) 46 | if (city_list[1] > best_route[1]): 47 | city_list = copy.deepcopy(best_route) 48 | best_route = copy.deepcopy(seed) 49 | count = count + 1 50 | iteration = iteration + 1 51 | if (distance > city_list[1] and recursive_seeding < 0): 52 | distance = city_list[1] 53 | count = -2 54 | recursive_seeding = -1 55 | elif(city_list[1] >= distance and recursive_seeding < 0): 56 | count = -1 57 | recursive_seeding = -2 58 | return city_list[0], city_list[1] 59 | 60 | ############################################################################ 61 | 62 | # Function: Best Insertion 63 | def best_insertion(distance_matrix, temp): 64 | temp_ = [item+1 for item in temp] 65 | temp_ = temp_ + [temp_[0]] 66 | d = distance_calc(distance_matrix, [temp_, 1]) 67 | seed = [temp_, d] 68 | temp_, _ = local_search_2_opt(distance_matrix, seed, recursive_seeding = -1, verbose = False) 69 | temp = [item-1 for item in temp_[:-1]] 70 | return temp 71 | 72 | ############################################################################ 73 | 74 | # Function: Random Insertion 75 | def random_insertion(distance_matrix, initial_location = -1, verbose = True): 76 | minimum = float('+inf') 77 | distance = float('+inf') 78 | route = [] 79 | for i1 in range(0, distance_matrix.shape[0]): 80 | if (initial_location != -1): 81 | i1 = initial_location-1 82 | temp = [] 83 | nodes = [i for i in range(0, distance_matrix.shape[0])] 84 | random.shuffle(nodes) 85 | dist = np.copy(distance_matrix) 86 | dist = dist.astype(float) 87 | np.fill_diagonal(dist, float('+inf')) 88 | idx = dist[i1,:].argmin() 89 | dist[i1,:] = float('+inf') 90 | dist[:,i1] = float('+inf') 91 | temp.append(i1) 92 | temp.append(idx) 93 | nodes.remove(i1) 94 | nodes.remove(idx) 95 | for j in nodes: 96 | i2 = idx 97 | idx = j 98 | dist[i2,:] = float('+inf') 99 | dist[:,i2] = float('+inf') 100 | temp.append(idx) 101 | temp = best_insertion(distance_matrix, temp) 102 | temp = temp + [temp[0]] 103 | temp = [item + 1 for item in temp] 104 | val = distance_calc(distance_matrix, [temp, 1]) 105 | if (val < minimum): 106 | minimum = val 107 | distance = val 108 | route = [item for item in temp] 109 | if (verbose == True): 110 | print('Iteration = ', i1, 'Distance = ', round(distance, 2)) 111 | if (initial_location == -1): 112 | continue 113 | else: 114 | break 115 | return route, distance 116 | 117 | ############################################################################ 118 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/s_gui.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Guided Search 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import random 14 | import copy 15 | 16 | ############################################################################ 17 | 18 | # Function: Tour Distance 19 | def distance_calc(distance_matrix, city_tour): 20 | distance = 0 21 | for k in range(0, len(city_tour[0])-1): 22 | m = k + 1 23 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 24 | return distance 25 | 26 | # Function: Stochastic 2_opt 27 | def stochastic_2_opt(distance_matrix, city_tour): 28 | best_route = copy.deepcopy(city_tour) 29 | i, j = random.sample(range(0, len(city_tour[0])-1), 2) 30 | if (i > j): 31 | i, j = j, i 32 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 33 | best_route[0][-1] = best_route[0][0] 34 | best_route[1] = distance_calc(distance_matrix, best_route) 35 | return best_route 36 | 37 | # Function: Local Search 38 | def local_search(distance_matrix, city_tour, penalty, max_attempts = 50, limit= 1): 39 | count = 0 40 | ag_cost = augumented_cost(distance_matrix, city_tour, penalty, limit) 41 | solution = copy.deepcopy(city_tour) 42 | while (count < max_attempts): 43 | candidate = stochastic_2_opt(distance_matrix, solution) 44 | candidate_augmented = augumented_cost(distance_matrix, candidate, penalty, limit) 45 | if candidate_augmented < ag_cost: 46 | solution = copy.deepcopy(candidate) 47 | ag_cost = augumented_cost(distance_matrix, solution, penalty, limit) 48 | count = 0 49 | else: 50 | count = count + 1 51 | return solution 52 | 53 | ############################################################################ 54 | 55 | #Function: Augmented Cost 56 | def augumented_cost(distance_matrix, city_tour, penalty, limit): 57 | augmented = 0 58 | for i in range(0, len(city_tour[0]) - 1): 59 | c1 = city_tour[0][i] 60 | c2 = city_tour[0][i + 1] 61 | if c2 < c1: 62 | c1, c2 = c2, c1 63 | augmented = augmented + distance_matrix[c1-1, c2-1] + (limit * penalty[c1-1][c2-1]) 64 | return augmented 65 | 66 | #Function: Utility 67 | def utility (distance_matrix, city_tour, penalty, limit = 1): 68 | utilities = [0 for i in city_tour[0]] 69 | for i in range(0, len(city_tour[0]) - 1): 70 | c1 = city_tour[0][i] 71 | c2 = city_tour[0][i + 1] 72 | if c2 < c1: 73 | c1, c2 = c2, c1 74 | utilities[i] = distance_matrix[c1-1, c2-1] /(1 + penalty[c1-1][c2-1]) 75 | return utilities 76 | 77 | #Function: Update Penalty 78 | def update_penalty(penalty, city_tour, utilities): 79 | max_utility = max(utilities) 80 | for i in range(0, len(city_tour[0]) - 1): 81 | c1 = city_tour[0][i] 82 | c2 = city_tour[0][i + 1] 83 | if c2 < c1: 84 | c1, c2 = c2, c1 85 | if (utilities[i] == max_utility): 86 | penalty[c1-1][c2-1] = penalty[c1-1][c2-1] + 1 87 | return penalty 88 | 89 | ############################################################################ 90 | 91 | # Function: Guided Search 92 | def guided_search(distance_matrix, city_tour, alpha = 0.3, local_search_optima = 1000, max_attempts = 20, iterations = 50, verbose = True): 93 | count = 0 94 | limit = alpha * (local_search_optima / len(city_tour[0])) 95 | penalty = [[0 for i in city_tour[0]] for j in city_tour[0]] 96 | solution = copy.deepcopy(city_tour) 97 | best_solution = [[],float('+inf')] 98 | while (count < iterations): 99 | if (verbose == True): 100 | print('Iteration = ', count, 'Distance = ', round(best_solution[1], 2)) 101 | solution = local_search(distance_matrix, city_tour = solution, penalty = penalty, max_attempts = max_attempts, limit = limit) 102 | utilities = utility(distance_matrix, city_tour = solution, penalty = penalty, limit = limit) 103 | penalty = update_penalty(penalty = penalty, city_tour = solution, utilities = utilities) 104 | if (solution[1] < best_solution[1]): 105 | best_solution = copy.deepcopy(solution) 106 | count = count + 1 107 | route, distance = best_solution 108 | return route, distance 109 | 110 | ############################################################################ -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/mf.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Multifragment Heuristic 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 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 | # Function: 2_opt 26 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 27 | if (recursive_seeding < 0): 28 | count = -2 29 | else: 30 | count = 0 31 | city_list = copy.deepcopy(city_tour) 32 | distance = city_list[1]*2 33 | iteration = 0 34 | while (count < recursive_seeding): 35 | if (verbose == True): 36 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 37 | best_route = copy.deepcopy(city_list) 38 | seed = copy.deepcopy(city_list) 39 | for i in range(0, len(city_list[0]) - 2): 40 | for j in range(i+1, len(city_list[0]) - 1): 41 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 42 | best_route[0][-1] = best_route[0][0] 43 | best_route[1] = distance_calc(distance_matrix, best_route) 44 | if (city_list[1] > best_route[1]): 45 | city_list = copy.deepcopy(best_route) 46 | best_route = copy.deepcopy(seed) 47 | count = count + 1 48 | iteration = iteration + 1 49 | if (distance > city_list[1] and recursive_seeding < 0): 50 | distance = city_list[1] 51 | count = -2 52 | recursive_seeding = -1 53 | elif(city_list[1] >= distance and recursive_seeding < 0): 54 | count = -1 55 | recursive_seeding = -2 56 | return city_list[0], city_list[1] 57 | 58 | ############################################################################ 59 | 60 | # Function: MF 61 | def multifragment_heuristic(distance_matrix, local_search = True, verbose = True): 62 | pairs = [ [i, j] for i in range(0, distance_matrix.shape[0]) for j in range(i, distance_matrix.shape[0]) if i != j] 63 | dist = [ distance_matrix[item[0], item[1]] for item in pairs] 64 | idx = sorted(range(0, len(dist)), key = dist.__getitem__) 65 | dist = [dist[i] for i in idx] 66 | pairs = [pairs[i] for i in idx] 67 | route = [] 68 | for pair in pairs: 69 | i, j = pair 70 | if (i not in route and j not in route): 71 | route.append(i) 72 | route.append(j) 73 | elif (i in route and j not in route): 74 | pos = route.index(i) 75 | A = route[:pos] 76 | B = route[pos:] 77 | r1 = A + [j] + B 78 | r2 = [j] + A + B 79 | ra = [node+1 for node in r1] 80 | rb = [node+1 for node in r2] 81 | ra = ra + [ra[0]] 82 | rb = rb + [rb[0]] 83 | d1 = distance_calc(distance_matrix, [ra, 1]) 84 | d2 = distance_calc(distance_matrix, [rb, 1]) 85 | if (d1 <= d2): 86 | route = [item for item in r1] 87 | else: 88 | route = [item for item in r2] 89 | elif (i not in route and j in route): 90 | pos = route.index(j) 91 | A = route[:pos] 92 | B = route[pos:] 93 | r1 = A + [i] + B 94 | r2 = [i] + A + B 95 | ra = [node + 1 for node in r1] 96 | rb = [node + 1 for node in r2] 97 | ra = ra + [ra[0]] 98 | rb = rb + [rb[0]] 99 | d1 = distance_calc(distance_matrix, [ra, 1]) 100 | d2 = distance_calc(distance_matrix, [rb, 1]) 101 | if (d1 <= d2): 102 | route = [item for item in r1] 103 | else: 104 | route = [item for item in r2] 105 | route = route + [route[0]] 106 | route = [node + 1 for node in route] 107 | distance = distance_calc(distance_matrix, [route, 1]) 108 | seed = [route, distance] 109 | if (local_search == True): 110 | route, distance = local_search_2_opt(distance_matrix, seed, recursive_seeding = -1, verbose = verbose) 111 | return route, distance 112 | 113 | ############################################################################ 114 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/spfc_s.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Space Filling Curve (Sierpinski) 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import numpy as np 15 | 16 | ############################################################################ 17 | 18 | # Function: Tour Distance 19 | def distance_calc(distance_matrix, city_tour): 20 | distance = 0 21 | for k in range(0, len(city_tour[0])-1): 22 | m = k + 1 23 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 24 | return distance 25 | 26 | # Function: 2_opt 27 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 28 | if (recursive_seeding < 0): 29 | count = -2 30 | else: 31 | count = 0 32 | city_list = copy.deepcopy(city_tour) 33 | distance = city_list[1]*2 34 | iteration = 0 35 | while (count < recursive_seeding): 36 | if (verbose == True): 37 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 38 | best_route = copy.deepcopy(city_list) 39 | seed = copy.deepcopy(city_list) 40 | for i in range(0, len(city_list[0]) - 2): 41 | for j in range(i+1, len(city_list[0]) - 1): 42 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 43 | best_route[0][-1] = best_route[0][0] 44 | best_route[1] = distance_calc(distance_matrix, best_route) 45 | if (city_list[1] > best_route[1]): 46 | city_list = copy.deepcopy(best_route) 47 | best_route = copy.deepcopy(seed) 48 | count = count + 1 49 | iteration = iteration + 1 50 | if (distance > city_list[1] and recursive_seeding < 0): 51 | distance = city_list[1] 52 | count = -2 53 | recursive_seeding = -1 54 | elif(city_list[1] >= distance and recursive_seeding < 0): 55 | count = -1 56 | recursive_seeding = -2 57 | return city_list[0], city_list[1] 58 | 59 | ############################################################################ 60 | 61 | # Function: Get Sierpinski 2D points. Adapted from: http://www2.isye.gatech.edu/~jjb/research/mow/mow.pdf and http://thirdlanding.com/generating-a-space-filling-curve-in-python/ 62 | def get_sierpinski_2d(coordinates): 63 | def get_idx(x, y, size): 64 | result = 0 65 | idx = size 66 | if (x > y): 67 | result = result + 1 68 | x = size - x 69 | y = size - y 70 | while (idx > 0): 71 | result = result + result 72 | if (x + y > size): 73 | result = result + 1 74 | x_ = x 75 | x = size - y 76 | y = x_ 77 | x = 2*x 78 | y = 2*y 79 | result = 2*result 80 | if (y > size): 81 | result = result + 1 82 | x_ = x 83 | x = y - size 84 | y = size - x_ 85 | idx = int(idx/2) 86 | return result 87 | idxs = [get_idx(coordinates[i, 0], coordinates[i, 1], np.max(coordinates)+1) for i in range(0, coordinates.shape[0])] 88 | idxs = sorted(range(len(idxs)), key = lambda k: idxs[k]) 89 | return idxs 90 | 91 | ############################################################################ 92 | 93 | # Function: SFC (Sierpinski) 94 | def space_filling_curve_s(coordinates, distance_matrix, local_search = True, verbose = True): 95 | flag = False 96 | n_coord = np.copy(coordinates) 97 | for i in range(0, coordinates.shape[0]): 98 | for j in range(0, coordinates.shape[1]): 99 | if ( coordinates[i,j] / int(coordinates[i,j]) > 1): 100 | flag = True 101 | break 102 | if (flag == True): 103 | n_coord = n_coord*100 104 | n_coord = n_coord.astype(int) 105 | else: 106 | n_coord = n_coord.astype(int) 107 | route = get_sierpinski_2d(n_coord) 108 | route = route + [route[0]] 109 | route = [item+1 for item in route] 110 | distance = distance_calc(distance_matrix, [route, 1]) 111 | seed = [route, distance] 112 | if (local_search == True): 113 | route, distance = local_search_2_opt(distance_matrix, seed, recursive_seeding = -1, verbose = verbose) 114 | return route, distance 115 | 116 | ############################################################################ -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/swp.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Sweep Algorithm 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import numpy as np 15 | np.seterr(divide = 'ignore', invalid = 'ignore') 16 | 17 | ############################################################################ 18 | 19 | # Function: Tour Distance 20 | def distance_calc(distance_matrix, city_tour): 21 | distance = 0 22 | for k in range(0, len(city_tour[0])-1): 23 | m = k + 1 24 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 25 | return distance 26 | 27 | # Function: 2_opt 28 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 29 | if (recursive_seeding < 0): 30 | count = -2 31 | else: 32 | count = 0 33 | city_list = copy.deepcopy(city_tour) 34 | distance = city_list[1]*2 35 | iteration = 0 36 | while (count < recursive_seeding): 37 | if (verbose == True): 38 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 39 | best_route = copy.deepcopy(city_list) 40 | seed = copy.deepcopy(city_list) 41 | for i in range(0, len(city_list[0]) - 2): 42 | for j in range(i+1, len(city_list[0]) - 1): 43 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 44 | best_route[0][-1] = best_route[0][0] 45 | best_route[1] = distance_calc(distance_matrix, best_route) 46 | if (city_list[1] > best_route[1]): 47 | city_list = copy.deepcopy(best_route) 48 | best_route = copy.deepcopy(seed) 49 | count = count + 1 50 | iteration = iteration + 1 51 | if (distance > city_list[1] and recursive_seeding < 0): 52 | distance = city_list[1] 53 | count = -2 54 | recursive_seeding = -1 55 | elif(city_list[1] >= distance and recursive_seeding < 0): 56 | count = -1 57 | recursive_seeding = -2 58 | return city_list[0], city_list[1] 59 | 60 | ############################################################################ 61 | 62 | # Function: Transform Cartesian to Polar (radians, degrees) 63 | def cartesian_polar(distance_matrix, coordinates, initial = 0): 64 | x = coordinates[:,0] 65 | y = coordinates[:,1] 66 | rho = np.array([distance_matrix[initial, j] for j in range(0, distance_matrix .shape[0])]) 67 | phi = np.arctan( (y - y[initial])/(x - x[initial])) 68 | phi = np.nan_to_num(phi) 69 | polar = np.concatenate((rho.reshape(-1, 1), phi.reshape(-1, 1)), axis = 1) 70 | return polar 71 | 72 | #def cartesian_polar(coordinates): 73 | #x = coordinates[:,0] 74 | #y = coordinates[:,1] 75 | #z = x + y * 1j 76 | #rho = np.abs(z) 77 | #phi = np.angle(z) 78 | #polar = np.concatenate((rho.reshape(-1, 1), phi.reshape(-1, 1)), axis = 1) 79 | #return polar 80 | 81 | #def polar_cartesian(polar): 82 | #rho = polar[:,0] 83 | #phi = polar[:,1] 84 | #z = rho * np.exp(1j * phi) 85 | #x = z.real 86 | #y = z.imag 87 | #cartesian = np.concatenate((x.reshape(-1, 1), y.reshape(-1, 1)), axis = 1) 88 | #return cartesian 89 | 90 | ############################################################################ 91 | 92 | # Function: Sweep 93 | def sweep(coordinates, distance_matrix, initial_location = -1, verbose = True): 94 | minimum = float('+inf') 95 | distance = float('+inf') 96 | nodes = np.array([i+1 for i in range(0, distance_matrix.shape[0])]) 97 | route = [] 98 | for i in range(0, distance_matrix.shape[0]): 99 | if (initial_location != -1): 100 | i = initial_location-1 101 | polar = cartesian_polar(distance_matrix, coordinates, initial = i) 102 | polar = np.c_[polar, nodes] 103 | polar = polar[polar[:, 1].argsort()] 104 | temp = [int(polar[i,-1]) for i in range(0, distance_matrix.shape[0])] 105 | temp = temp + [temp[0]] 106 | dist = distance_calc(distance_matrix, [temp, 1]) 107 | seed = [temp, dist] 108 | r, d = local_search_2_opt(distance_matrix, seed, recursive_seeding = -1, verbose = False) 109 | if (d < minimum): 110 | minimum = d 111 | distance = d 112 | route = [item for item in r] 113 | if (verbose == True): 114 | print('Iteration = ', i, 'Distance = ', round(distance, 2)) 115 | if (initial_location == -1): 116 | continue 117 | else: 118 | break 119 | return route, distance 120 | 121 | ############################################################################ -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/spfc_h.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Space Filling Curve (Hilbert) 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import numpy as np 15 | 16 | ############################################################################ 17 | 18 | # Function: Tour Distance 19 | def distance_calc(distance_matrix, city_tour): 20 | distance = 0 21 | for k in range(0, len(city_tour[0])-1): 22 | m = k + 1 23 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 24 | return distance 25 | 26 | # Function: 2_opt 27 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 28 | if (recursive_seeding < 0): 29 | count = -2 30 | else: 31 | count = 0 32 | city_list = copy.deepcopy(city_tour) 33 | distance = city_list[1]*2 34 | iteration = 0 35 | while (count < recursive_seeding): 36 | if (verbose == True): 37 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 38 | best_route = copy.deepcopy(city_list) 39 | seed = copy.deepcopy(city_list) 40 | for i in range(0, len(city_list[0]) - 2): 41 | for j in range(i+1, len(city_list[0]) - 1): 42 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 43 | best_route[0][-1] = best_route[0][0] 44 | best_route[1] = distance_calc(distance_matrix, best_route) 45 | if (city_list[1] > best_route[1]): 46 | city_list = copy.deepcopy(best_route) 47 | best_route = copy.deepcopy(seed) 48 | count = count + 1 49 | iteration = iteration + 1 50 | if (distance > city_list[1] and recursive_seeding < 0): 51 | distance = city_list[1] 52 | count = -2 53 | recursive_seeding = -1 54 | elif(city_list[1] >= distance and recursive_seeding < 0): 55 | count = -1 56 | recursive_seeding = -2 57 | return city_list[0], city_list[1] 58 | 59 | ############################################################################ 60 | 61 | # Function: Get Hilbert 2D points. Adapted from: http://blog.notdot.net/2009/11/Damn-Cool-Algorithms-Spatial-indexing-with-Quadtrees-and-Hilbert-Curves 62 | def get_hilbert_2d(coordinates): 63 | hilbert_map = { 64 | 'a': {(0, 0): (0, 'd'), (0, 1): (1, 'a'), (1, 0): (3, 'b'), (1, 1): (2, 'a')}, 65 | 'b': {(0, 0): (2, 'b'), (0, 1): (1, 'b'), (1, 0): (3, 'a'), (1, 1): (0, 'c')}, 66 | 'c': {(0, 0): (2, 'c'), (0, 1): (3, 'd'), (1, 0): (1, 'c'), (1, 1): (0, 'b')}, 67 | 'd': {(0, 0): (0, 'a'), (0, 1): (3, 'c'), (1, 0): (1, 'd'), (1, 1): (2, 'd')}, 68 | } 69 | def get_idx(x, y, size): 70 | current_square = 'a' 71 | result = 0 72 | for i in range(size - 1, -1, -1): 73 | result <<= 2 74 | quad_x = 1 if x & (1 << i) else 0 75 | quad_y = 1 if y & (1 << i) else 0 76 | quad_position, current_square = hilbert_map[current_square][(quad_x, quad_y)] 77 | result |= quad_position 78 | return result 79 | max_val = np.max(coordinates)+1 80 | limit = -1 81 | k = 1 82 | while (limit < max_val): 83 | limit = 2**k 84 | k = k + 1 85 | idxs = [get_idx( int(coordinates[i, 0]), int(coordinates[i, 1]), k) for i in range(0, coordinates.shape[0])] 86 | idxs = sorted(range(len(idxs)), key = lambda k: idxs[k]) 87 | return idxs 88 | 89 | ############################################################################ 90 | 91 | # Function: SFC (Hilbert) 92 | def space_filling_curve_h(coordinates, distance_matrix, local_search = True, verbose = True): 93 | flag = False 94 | n_coord = np.copy(coordinates) 95 | for i in range(0, coordinates.shape[0]): 96 | for j in range(0, coordinates.shape[1]): 97 | if ( coordinates[i,j] / int(coordinates[i,j]) > 1): 98 | flag = True 99 | break 100 | if (flag == True): 101 | n_coord = n_coord*100 102 | n_coord = n_coord.astype(int) 103 | else: 104 | n_coord = n_coord.astype(int) 105 | route = get_hilbert_2d(n_coord) 106 | route = route + [route[0]] 107 | route = [item+1 for item in route] 108 | distance = distance_calc(distance_matrix, [route, 1]) 109 | seed = [route, distance] 110 | if (local_search == True): 111 | route, distance = local_search_2_opt(distance_matrix, seed, recursive_seeding = -1, verbose = verbose) 112 | return route, distance 113 | 114 | ############################################################################ 115 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/s_sct.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Local Search-Scatter Search 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import random 14 | import copy 15 | import os 16 | 17 | from operator import itemgetter 18 | 19 | ############################################################################ 20 | 21 | # Function: Tour Distance 22 | def distance_calc(distance_matrix, city_tour): 23 | distance = 0 24 | for k in range(0, len(city_tour[0])-1): 25 | m = k + 1 26 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 27 | return distance 28 | 29 | # Function: Initial Seed 30 | def seed_function(distance_matrix): 31 | seed = [[],float('inf')] 32 | sequence = random.sample(list(range(1, distance_matrix.shape[0]+1)), distance_matrix.shape[0]) 33 | sequence.append(sequence[0]) 34 | seed[0] = sequence 35 | seed[1] = distance_calc(distance_matrix, seed) 36 | return seed 37 | 38 | ############################################################################ 39 | 40 | # Function: Local Improvement 2_opt 41 | def local_search_2_opt(distance_matrix, city_tour): 42 | city_list = copy.deepcopy(city_tour) 43 | best_route = copy.deepcopy(city_list) 44 | seed = copy.deepcopy(city_list) 45 | for i in range(0, len(city_list[0]) - 2): 46 | for j in range(i+1, len(city_list[0]) - 1): 47 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 48 | best_route[0][-1] = best_route[0][0] 49 | best_route[1] = distance_calc(distance_matrix, best_route) 50 | if (best_route[1] < city_list[1]): 51 | city_list[1] = copy.deepcopy(best_route[1]) 52 | for n in range(0, len(city_list[0])): 53 | city_list[0][n] = best_route[0][n] 54 | best_route = copy.deepcopy(seed) 55 | return city_list 56 | 57 | ############################################################################ 58 | 59 | # Function: Crossover 60 | def crossover_tsp(distance_matrix, reference_list, reverse_prob = 0.5, scramble_prob = 0.3): 61 | ix, iy = random.sample(list(range(0,len(reference_list))), 2) 62 | parent_1 = reference_list[ix][0] 63 | parent_1 = parent_1[:-1] 64 | parent_2 = reference_list[iy][0] 65 | parent_2 = parent_2[:-1] 66 | offspring = [0]*len(parent_2) 67 | i, j = random.sample(list(range(0,len(parent_1))), 2) 68 | if (i > j): 69 | i, j = j, i 70 | rand_1 = int.from_bytes(os.urandom(8), byteorder = 'big') / ((1 << 64) - 1) 71 | if (rand_1 < reverse_prob): 72 | parent_1[i:j+1] = list(reversed(parent_1[i:j+1])) 73 | offspring[i:j+1] = parent_1[i:j+1] 74 | parent_2 = [x for x in parent_2 if x not in parent_1[i:j+1]] 75 | rand_2 = int.from_bytes(os.urandom(8), byteorder = 'big') / ((1 << 64) - 1) 76 | if (rand_2 < scramble_prob): 77 | random.shuffle(parent_2) 78 | count = 0 79 | for i in range(0, len(offspring)): 80 | if (offspring[i] == 0): 81 | offspring[i] = parent_2[count] 82 | count = count + 1 83 | offspring.append(offspring[0]) 84 | offspring = [offspring, 1] 85 | offspring[1] = distance_calc(distance_matrix, offspring) 86 | return offspring 87 | 88 | ############################################################################ 89 | 90 | # Function: Scatter Search 91 | def scatter_search(distance_matrix, city_tour, iterations = 50, reference_size = 25, reverse_prob = 0.5, scramble_prob = 0.3, verbose = True): 92 | count = 0 93 | best_solution = copy.deepcopy(city_tour) 94 | reference_list = [] 95 | for i in range(0, reference_size): 96 | reference_list.append(seed_function(distance_matrix)) 97 | while (count < iterations): 98 | if (verbose == True): 99 | print('Iteration = ', count, 'Distance = ', round(best_solution[1], 2)) 100 | candidate_list = [] 101 | for i in range(0, reference_size): 102 | candidate_list.append(crossover_tsp(distance_matrix, reference_list = reference_list, reverse_prob = reverse_prob, scramble_prob = scramble_prob)) 103 | for i in range(0, reference_size): 104 | candidate_list[i] = local_search_2_opt(distance_matrix, city_tour = candidate_list[i]) 105 | for i in range(0, reference_size): 106 | reference_list.append(candidate_list[i]) 107 | reference_list = sorted(reference_list, key = itemgetter(1)) 108 | reference_list = reference_list[:reference_size] 109 | for i in range(0, reference_size): 110 | if (reference_list[i][1] < best_solution[1]): 111 | best_solution = copy.deepcopy(reference_list[i]) 112 | count = count + 1 113 | route, distance = best_solution 114 | return route, distance 115 | 116 | ############################################################################ 117 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/grasp.py: -------------------------------------------------------------------------------- 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 - GRASP 7 | 8 | # GitHub Repository: 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: Tour Distance 21 | def distance_calc(distance_matrix, city_tour): 22 | distance = 0 23 | for k in range(0, len(city_tour[0])-1): 24 | m = k + 1 25 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 26 | return distance 27 | 28 | # Function: 2_opt 29 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1): 30 | if (recursive_seeding < 0): 31 | count = -2 32 | else: 33 | count = 0 34 | city_list = copy.deepcopy(city_tour) 35 | distance = city_list[1]*2 36 | while (count < recursive_seeding): 37 | best_route = copy.deepcopy(city_list) 38 | seed = copy.deepcopy(city_list) 39 | for i in range(0, len(city_list[0]) - 2): 40 | for j in range(i+1, len(city_list[0]) - 1): 41 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 42 | best_route[0][-1] = best_route[0][0] 43 | best_route[1] = distance_calc(distance_matrix, best_route) 44 | if (city_list[1] > best_route[1]): 45 | city_list = copy.deepcopy(best_route) 46 | best_route = copy.deepcopy(seed) 47 | count = count + 1 48 | if (distance > city_list[1] and recursive_seeding < 0): 49 | distance = city_list[1] 50 | count = -2 51 | recursive_seeding = -1 52 | elif(city_list[1] >= distance and recursive_seeding < 0): 53 | count = -1 54 | recursive_seeding = -2 55 | return city_list 56 | 57 | ############################################################################ 58 | 59 | # Function: Rank Cities by Distance 60 | def ranking(distance_matrix, city = 0): 61 | rank = np.zeros((distance_matrix.shape[0], 2)) # ['Distance', 'City'] 62 | for i in range(0, rank.shape[0]): 63 | rank[i,0] = distance_matrix[i,city] 64 | rank[i,1] = i + 1 65 | rank = rank[rank[:,0].argsort()] 66 | return rank 67 | 68 | # Function: RCL 69 | def restricted_candidate_list(distance_matrix, greediness_value = 0.5): 70 | seed = [[], float('+inf')] 71 | sequence = [] 72 | sequence.append(random.sample(list(range(1, distance_matrix.shape[0]+1)), 1)[0]) 73 | count = 1 74 | for i in range(0, distance_matrix.shape[0]): 75 | count = 1 76 | rand = int.from_bytes(os.urandom(8), byteorder = 'big') / ((1 << 64) - 1) 77 | if (rand > greediness_value and len(sequence) < distance_matrix.shape[0]): 78 | next_city = int(ranking(distance_matrix, city = sequence[-1] - 1)[count,1]) 79 | while next_city in sequence: 80 | count = np.clip(count+1,1, distance_matrix.shape[0]-1) 81 | next_city = int(ranking(distance_matrix, city = sequence[-1] - 1)[count,1]) 82 | sequence.append(next_city) 83 | elif (rand <= greediness_value and len(sequence) < distance_matrix.shape[0]): 84 | next_city = random.sample(list(range(1, distance_matrix.shape[0]+1)), 1)[0] 85 | while next_city in sequence: 86 | next_city = int(random.sample(list(range(1, distance_matrix.shape[0]+1)), 1)[0]) 87 | sequence.append(next_city) 88 | sequence.append(sequence[0]) 89 | seed[0] = sequence 90 | seed[1] = distance_calc(distance_matrix, seed) 91 | return seed 92 | 93 | ############################################################################ 94 | 95 | def greedy_randomized_adaptive_search_procedure(distance_matrix, city_tour, iterations = 50, rcl = 25, greediness_value = 0.5, verbose = True): 96 | count = 0 97 | best_solution = copy.deepcopy(city_tour) 98 | while (count < iterations): 99 | if (verbose == True): 100 | print('Iteration = ', count, 'Distance = ', round(best_solution[1], 2)) 101 | rcl_list = [] 102 | for i in range(0, rcl): 103 | rcl_list.append(restricted_candidate_list(distance_matrix, greediness_value)) 104 | candidate = int(random.sample(list(range(0,rcl)), 1)[0]) 105 | city_tour = local_search_2_opt(distance_matrix, rcl_list[candidate], 2) 106 | while (city_tour[0] != rcl_list[candidate][0]): 107 | rcl_list[candidate] = copy.deepcopy(city_tour) 108 | city_tour = local_search_2_opt(distance_matrix, rcl_list[candidate], 2) 109 | if (city_tour[1] < best_solution[1]): 110 | best_solution = copy.deepcopy(city_tour) 111 | count = count + 1 112 | route, distance = best_solution 113 | return route, distance 114 | 115 | ############################################################################ 116 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/lns.py: -------------------------------------------------------------------------------- 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 - LNS - Large Neighborhood Search 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import numpy as np 15 | import random 16 | 17 | ############################################################################ 18 | 19 | # Function: Tour Distance 20 | def distance_calc(distance_matrix, city_tour): 21 | distance = 0 22 | for k in range(0, len(city_tour[0])-1): 23 | m = k + 1 24 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 25 | return distance 26 | 27 | # Function: Tour Distance 28 | def distance_point(tour, distance_matrix): 29 | tour_shifted = np.roll(tour, shift = -1) 30 | return np.sum(distance_matrix[tour, tour_shifted]) 31 | 32 | ############################################################################ 33 | 34 | # Function: 2_opt 35 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 36 | if (recursive_seeding < 0): 37 | count = -2 38 | else: 39 | count = 0 40 | city_list = copy.deepcopy(city_tour) 41 | distance = city_list[1]*2 42 | iteration = 0 43 | if (verbose == True): 44 | print('') 45 | print('Local Search') 46 | print('') 47 | while (count < recursive_seeding): 48 | if (verbose == True): 49 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 50 | best_route = copy.deepcopy(city_list) 51 | seed = copy.deepcopy(city_list) 52 | for i in range(0, len(city_list[0]) - 2): 53 | for j in range(i+1, len(city_list[0]) - 1): 54 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 55 | best_route[0][-1] = best_route[0][0] 56 | best_route[1] = distance_calc(distance_matrix, best_route) 57 | if (city_list[1] > best_route[1]): 58 | city_list = copy.deepcopy(best_route) 59 | best_route = copy.deepcopy(seed) 60 | count = count + 1 61 | iteration = iteration + 1 62 | if (distance > city_list[1] and recursive_seeding < 0): 63 | distance = city_list[1] 64 | count = -2 65 | recursive_seeding = -1 66 | elif(city_list[1] >= distance and recursive_seeding < 0): 67 | count = -1 68 | recursive_seeding = -2 69 | return city_list[0], city_list[1] 70 | 71 | ############################################################################ 72 | 73 | # Function: Removal 74 | def random_removal(city_tour, neighborhood_size): 75 | removed = random.sample(city_tour[1:], neighborhood_size) 76 | city_tour = [t for t in city_tour if t not in removed] 77 | return removed, city_tour 78 | 79 | # Function: Insertion 80 | def best_insertion(removed_nodes, city_tour, distance_matrix): 81 | for node in removed_nodes: 82 | best_insertion_cost = float('inf') 83 | best_insertion_index = -1 84 | for i in range(1, len(city_tour) + 1): 85 | last_node = city_tour[i - 1] 86 | next_node = city_tour[i % len(city_tour)] 87 | insertion_cost = (distance_matrix[last_node, node] + distance_matrix[node, next_node] - distance_matrix[last_node, next_node]) 88 | if (insertion_cost < best_insertion_cost): 89 | best_insertion_cost = insertion_cost 90 | best_insertion_index = i 91 | city_tour.insert(best_insertion_index, node) 92 | return city_tour 93 | 94 | ############################################################################ 95 | 96 | # Function: Large Neighborhood Search 97 | def large_neighborhood_search(distance_matrix, iterations = 1000, neighborhood_size = 4, local_search = True, verbose = True): 98 | initial_tour = list(range(0, distance_matrix.shape[0])) 99 | random.shuffle(initial_tour) 100 | route = initial_tour.copy() 101 | distance = distance_point(route, distance_matrix) 102 | count = 0 103 | while (count <= iterations): 104 | if (verbose == True and count > 0): 105 | print('Iteration = ', count, 'Distance = ', round(distance, 2)) 106 | city_tour = route.copy() 107 | removed_nodes, city_tour = random_removal(city_tour, neighborhood_size) 108 | new_tour = best_insertion(removed_nodes, city_tour, distance_matrix) 109 | new_tour_distance = distance_point(new_tour, distance_matrix) 110 | if (new_tour_distance < distance): 111 | route = new_tour 112 | distance = new_tour_distance 113 | count = count + 1 114 | route = route + [route[0]] 115 | route = [item + 1 for item in route] 116 | if (local_search == True): 117 | route, distance = local_search_2_opt(distance_matrix, [route, distance], -1, verbose) 118 | return route, distance 119 | 120 | ############################################################################ 121 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/rr.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Ruin & Recreate 7 | # 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import numpy as np 15 | 16 | ############################################################################ 17 | 18 | # Function: Tour Distance 19 | def distance_calc(distance_matrix, city_tour): 20 | distance = 0 21 | for k in range(0, len(city_tour[0])-1): 22 | m = k + 1 23 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 24 | return distance 25 | 26 | # Function: 2_opt 27 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 28 | if (recursive_seeding < 0): 29 | count = -2 30 | else: 31 | count = 0 32 | city_list = copy.deepcopy(city_tour) 33 | distance = city_list[1]*2 34 | iteration = 0 35 | while (count < recursive_seeding): 36 | if (verbose == True): 37 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 38 | best_route = copy.deepcopy(city_list) 39 | seed = copy.deepcopy(city_list) 40 | for i in range(0, len(city_list[0]) - 2): 41 | for j in range(i+1, len(city_list[0]) - 1): 42 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 43 | best_route[0][-1] = best_route[0][0] 44 | best_route[1] = distance_calc(distance_matrix, best_route) 45 | if (city_list[1] > best_route[1]): 46 | city_list = copy.deepcopy(best_route) 47 | best_route = copy.deepcopy(seed) 48 | count = count + 1 49 | iteration = iteration + 1 50 | if (distance > city_list[1] and recursive_seeding < 0): 51 | distance = city_list[1] 52 | count = -2 53 | recursive_seeding = -1 54 | elif(city_list[1] >= distance and recursive_seeding < 0): 55 | count = -1 56 | recursive_seeding = -2 57 | return city_list[0], city_list[1] 58 | 59 | ############################################################################ 60 | 61 | # Function: Regret-2 Insertion 62 | def regret2_insertion(new_tour, to_insert, distance_matrix): 63 | new_tour = [item-1 for item in new_tour] 64 | to_insert = [item-1 for item in to_insert] 65 | while to_insert: 66 | best_regret, best_city, best_pos = -np.inf, None, None 67 | for city in to_insert: 68 | insertion_costs = [] 69 | for j in range(0, len(new_tour) - 1): 70 | prev, nxt = new_tour[j], new_tour[j + 1] 71 | delta = distance_matrix[prev, city] + distance_matrix[city, nxt] - distance_matrix[prev, nxt] 72 | insertion_costs.append((delta, j + 1)) 73 | if not insertion_costs: 74 | continue 75 | insertion_costs.sort(key=lambda x: x[0]) 76 | d1, p1 = insertion_costs[0] 77 | d2 = insertion_costs[1][0] if len(insertion_costs) > 1 else d1 78 | regret = d2 - d1 79 | if regret > best_regret: 80 | best_regret = regret 81 | best_city = city 82 | best_pos = p1 83 | if best_city is None: 84 | break 85 | new_tour.insert(best_pos, best_city) 86 | to_insert.remove(best_city) 87 | new_tour = [item+1 for item in new_tour] 88 | return new_tour 89 | 90 | ############################################################################ 91 | 92 | # Function: Ruin & Recreate 93 | def ruin_and_recreate(city_tour, distance_matrix, iterations = 100, ruin_rate = 0.50, local_search = True, verbose = True): 94 | city_list = copy.deepcopy(city_tour) 95 | removable = [city for city in city_list[0][:-1]] 96 | route = city_list[0] 97 | distance = city_list[1] 98 | best_r = copy.deepcopy(route) 99 | best_d = distance 100 | iteration = 0 101 | for _ in range(0, iterations): 102 | n_remove = max(1, int(len(removable) * ruin_rate)) 103 | to_remove = set(np.random.choice(removable, min(n_remove, len(removable)), replace = False)) 104 | route = [city for city in route if city not in to_remove] 105 | if route[0] != route[-1]: 106 | route.append(route[0]) 107 | route = regret2_insertion(route, to_remove, distance_matrix) 108 | distance = distance_calc(distance_matrix, [route, distance]) 109 | if (verbose == True): 110 | print('Iteration = ', iteration, 'Distance = ', round(best_d, 2)) 111 | if distance < best_d: 112 | best_r = copy.deepcopy(route) 113 | best_d = distance 114 | iteration = iteration + 1 115 | if (local_search == True): 116 | print('') 117 | print('Local Search:') 118 | route, distance = local_search_2_opt(distance_matrix, [best_r, best_d], recursive_seeding = -1, verbose = verbose) 119 | return best_r, best_d 120 | 121 | ############################################################################ 122 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/christofides.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Christofides Algorithm 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import networkx as nx 15 | import numpy as np 16 | 17 | from scipy.sparse.csgraph import minimum_spanning_tree 18 | 19 | ############################################################################ 20 | 21 | # Function: Tour Distance 22 | def distance_calc(distance_matrix, city_tour): 23 | distance = 0 24 | for k in range(0, len(city_tour[0])-1): 25 | m = k + 1 26 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 27 | return distance 28 | 29 | # Function: 2_opt 30 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 31 | if (recursive_seeding < 0): 32 | count = -2 33 | else: 34 | count = 0 35 | city_list = copy.deepcopy(city_tour) 36 | distance = city_list[1]*2 37 | iteration = 0 38 | while (count < recursive_seeding): 39 | if (verbose == True): 40 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 41 | best_route = copy.deepcopy(city_list) 42 | seed = copy.deepcopy(city_list) 43 | for i in range(0, len(city_list[0]) - 2): 44 | for j in range(i+1, len(city_list[0]) - 1): 45 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 46 | best_route[0][-1] = best_route[0][0] 47 | best_route[1] = distance_calc(distance_matrix, best_route) 48 | if (city_list[1] > best_route[1]): 49 | city_list = copy.deepcopy(best_route) 50 | best_route = copy.deepcopy(seed) 51 | count = count + 1 52 | iteration = iteration + 1 53 | if (distance > city_list[1] and recursive_seeding < 0): 54 | distance = city_list[1] 55 | count = -2 56 | recursive_seeding = -1 57 | elif(city_list[1] >= distance and recursive_seeding < 0): 58 | count = -1 59 | recursive_seeding = -2 60 | return city_list[0], city_list[1] 61 | 62 | ############################################################################ 63 | 64 | # Function: Christofides Algorithm 65 | def christofides_algorithm(distance_matrix, local_search = True, verbose = True): 66 | # Minimum Spanning Tree T 67 | graph_T = minimum_spanning_tree(distance_matrix) 68 | graph_T = graph_T.toarray().astype(float) 69 | # Induced Subgraph G 70 | graph_O = np.array(graph_T, copy = True) 71 | count_r = np.count_nonzero(graph_T > 0, axis = 1) 72 | count_c = np.count_nonzero(graph_T > 0, axis = 0) 73 | degree = count_r + count_c 74 | graph_G = np.zeros((graph_O.shape)) 75 | for i in range(0, degree.shape[0]): 76 | if (degree[i] % 2 != 0): 77 | graph_G[i,:] = 1 78 | graph_G[:,i] = 1 79 | for i in range(0, degree.shape[0]): 80 | if (degree[i] % 2 == 0): 81 | graph_G[i,:] = 0 82 | graph_G[:,i] = 0 83 | np.fill_diagonal(graph_G, 0) 84 | for i in range(0, graph_G.shape[0]): 85 | for j in range(0, graph_G.shape[1]): 86 | if (graph_G[i, j] > 0): 87 | graph_G[i, j] = distance_matrix[i, j] 88 | # Minimum-Weight Perfect Matching M 89 | graph_G_inv = np.array(graph_G, copy = True) 90 | graph_G_inv = -graph_G_inv 91 | min_w_pm = nx.algorithms.matching.max_weight_matching(nx.from_numpy_array(graph_G_inv), maxcardinality = True) 92 | graph_M = np.zeros((graph_G.shape)) 93 | for item in min_w_pm: 94 | i, j = item 95 | graph_M[i, j] = distance_matrix[i, j] 96 | # Eulerian Multigraph H 97 | graph_H = np.array(graph_T, copy = True) 98 | for i in range(0, graph_H.shape[0]): 99 | for j in range(0, graph_H.shape[1]): 100 | if (graph_M[i, j] > 0 and graph_T[i, j] == 0): 101 | graph_H[i, j] = 1 #distance_matrix[i, j] 102 | elif (graph_M[i, j] > 0 and graph_T[i, j] > 0): 103 | graph_H[j, i] = 1 #distance_matrix[i, j] 104 | # Eulerian Path 105 | H = nx.from_numpy_array(graph_H) 106 | if (nx.is_eulerian(H)): 107 | euler = list(nx.eulerian_path(H)) 108 | else: 109 | H = nx.eulerize(H) 110 | euler = list(nx.eulerian_path(H)) 111 | # Shortcutting 112 | route = [] 113 | for path in euler: 114 | i, j = path 115 | if (i not in route): 116 | route.append(i) 117 | if (j not in route): 118 | route.append(j) 119 | route = route + [route[0]] 120 | route = [item + 1 for item in route] 121 | distance = distance_calc(distance_matrix, [route, 1]) 122 | seed = [route, distance] 123 | if (local_search == True): 124 | route, distance = local_search_2_opt(distance_matrix, seed, recursive_seeding = -1, verbose = verbose) 125 | return route, distance 126 | 127 | ############################################################################ 128 | 129 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/som.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: SOM (Self Organizing Maps) 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import numpy as np 15 | 16 | ############################################################################ 17 | 18 | # Function: Tour Distance 19 | def distance_calc(distance_matrix, city_tour): 20 | distance = 0 21 | for k in range(0, len(city_tour[0])-1): 22 | m = k + 1 23 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 24 | return distance 25 | 26 | # Function: 2_opt 27 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 28 | if (recursive_seeding < 0): 29 | count = -2 30 | else: 31 | count = 0 32 | city_list = copy.deepcopy(city_tour) 33 | distance = city_list[1]*2 34 | iteration = 0 35 | while (count < recursive_seeding): 36 | if (verbose == True): 37 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 38 | best_route = copy.deepcopy(city_list) 39 | seed = copy.deepcopy(city_list) 40 | for i in range(0, len(city_list[0]) - 2): 41 | for j in range(i+1, len(city_list[0]) - 1): 42 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 43 | best_route[0][-1] = best_route[0][0] 44 | best_route[1] = distance_calc(distance_matrix, best_route) 45 | if (city_list[1] > best_route[1]): 46 | city_list = copy.deepcopy(best_route) 47 | best_route = copy.deepcopy(seed) 48 | count = count + 1 49 | iteration = iteration + 1 50 | if (distance > city_list[1] and recursive_seeding < 0): 51 | distance = city_list[1] 52 | count = -2 53 | recursive_seeding = -1 54 | elif(city_list[1] >= distance and recursive_seeding < 0): 55 | count = -1 56 | recursive_seeding = -2 57 | return city_list[0], city_list[1] 58 | 59 | ############################################################################ 60 | 61 | # Function: Select Nearest Neuron 62 | def select_neuron(neurons, individual): 63 | idx = np.linalg.norm(neurons - individual, axis = 1).argmin() 64 | return idx 65 | 66 | # Function: Update Neurons 67 | def update_neurons(neurons, n_coord, idx, nr, learning_rate, individual): 68 | radius = np.clip(nr//10, 1, n_coord.shape[0]//10) 69 | delt = np.absolute(idx - np.arange(neurons.shape[0])) 70 | dist = np.minimum(delt, neurons.shape[0] - delt) 71 | noise = np.exp(-(dist**2) / (2*(radius**2))) 72 | noise = noise.reshape((-1, 1)) 73 | neurons = neurons + noise * learning_rate * (individual - neurons) 74 | return neurons 75 | 76 | ############################################################################ 77 | 78 | # Function: SOM (adapted from https://github.com/diego-vicente/som-tsp) 79 | def self_organizing_maps(coordinates, distance_matrix, size_multiplier = 4, iterations = 25000, decay_nr = 0.99997, decay_lr = 0.99997, learning_rate = 0.80, local_search = True, verbose = True): 80 | n_coord = (coordinates - np.min(coordinates)) / (np.max(coordinates) - np.min(coordinates) + 0.0000000000000001) 81 | neurons = np.random.rand(n_coord.shape[0]*size_multiplier, 2) 82 | nr = n_coord.shape[0] 83 | lr = learning_rate 84 | count = 0 85 | while (count <= iterations): 86 | if (verbose == True): 87 | print('Iteration = ', count) 88 | individual = n_coord[np.random.randint(n_coord.shape[0], size = 1)[0],:] 89 | idx = select_neuron(neurons, individual) 90 | neurons = update_neurons(neurons, n_coord, idx, nr, lr, individual) 91 | lr = lr * decay_lr 92 | nr = nr * decay_nr 93 | if (nr < 1): 94 | count = iterations + 1 95 | print('Radius has Completely Decayed') 96 | if (lr < 0.001): 97 | count = iterations + 1 98 | print('Learning Rate has Completely Decayed') 99 | count = count + 1 100 | selected = [] 101 | route = list(range(0, n_coord.shape[0])) 102 | for i in range(0, n_coord.shape[0]): 103 | selected.append(select_neuron(neurons, n_coord[i,:])) 104 | idx = sorted(range(0, len(selected)), key = selected.__getitem__) 105 | route = [route[i] for i in idx] 106 | route = route + [route[0]] 107 | route = [node + 1 for node in route] 108 | distance = distance_calc(distance_matrix, [route, 1]) 109 | seed = [route, distance] 110 | if (verbose == True): 111 | print('') 112 | print('Distance = ', round(distance, 2)) 113 | print('') 114 | if (local_search == True): 115 | if (verbose == True): 116 | print('Local Search...') 117 | route, distance = local_search_2_opt(distance_matrix, seed, recursive_seeding = -1, verbose = False) 118 | if (verbose == True): 119 | print('Distance = ', round(distance, 2) ) 120 | return route, distance 121 | 122 | ############################################################################ 123 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/aco.py: -------------------------------------------------------------------------------- 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: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import numpy as np 15 | 16 | ############################################################################ 17 | 18 | # Function: 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 | # Function: Perform Local Search 27 | def local_search_2_opt(distance_matrix, city_tour): 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 | # Function: 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 | # Function: 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 | # Function: 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 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/rl_ql.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Q-Learning 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import numpy as np 15 | import random 16 | 17 | ############################################################################ 18 | 19 | # Function: Tour Distance 20 | def distance_calc(distance_matrix, city_tour): 21 | distance = 0 22 | for k in range(0, len(city_tour[0])-1): 23 | m = k + 1 24 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 25 | return distance 26 | 27 | # Function: 2_opt 28 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 29 | if (recursive_seeding < 0): 30 | count = -2 31 | else: 32 | count = 0 33 | city_list = copy.deepcopy(city_tour) 34 | distance = city_list[1]*2 35 | iteration = 0 36 | while (count < recursive_seeding): 37 | if (verbose == True): 38 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 39 | best_route = copy.deepcopy(city_list) 40 | seed = copy.deepcopy(city_list) 41 | for i in range(0, len(city_list[0]) - 2): 42 | for j in range(i+1, len(city_list[0]) - 1): 43 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 44 | best_route[0][-1] = best_route[0][0] 45 | best_route[1] = distance_calc(distance_matrix, best_route) 46 | if (city_list[1] > best_route[1]): 47 | city_list = copy.deepcopy(best_route) 48 | best_route = copy.deepcopy(seed) 49 | count = count + 1 50 | iteration = iteration + 1 51 | if (distance > city_list[1] and recursive_seeding < 0): 52 | distance = city_list[1] 53 | count = -2 54 | recursive_seeding = -1 55 | elif(city_list[1] >= distance and recursive_seeding < 0): 56 | count = -1 57 | recursive_seeding = -2 58 | return city_list[0], city_list[1] 59 | 60 | ############################################################################ 61 | 62 | # Function: Q-Table Init 63 | def initialize_q_table(num_cities, seed): 64 | if seed is not None: 65 | np.random.seed(seed) 66 | q_table = np.zeros((num_cities, num_cities)) 67 | num_noisy_elements = int(1 * num_cities * num_cities) 68 | idx = np.random.choice(num_cities * num_cities, num_noisy_elements, replace = False) 69 | noise = np.random.uniform(-0.01, 0.01, size = num_noisy_elements) 70 | q_table.flat[idx] = noise 71 | return q_table 72 | 73 | # Function: Q-Learning 74 | def q_learning(distance_matrix, learning_rate = 0.1, discount_factor = 0.95, epsilon = 0.15, episodes = 5000, q_init = None, local_search = True, verbose = True): 75 | max_dist = np.max(distance_matrix) 76 | distance_matrix = distance_matrix/max_dist 77 | num_cities = distance_matrix.shape[0] 78 | if (q_init == None): 79 | q_table = np.zeros((num_cities, num_cities)) 80 | else: 81 | q_table = initialize_q_table(num_cities, q_init) 82 | 83 | for episode in range(0, episodes): 84 | current_city = random.randint(0, num_cities - 1) 85 | visited = set([current_city]) 86 | while (len(visited) < num_cities): 87 | unvisited_cities = [city for city in range(num_cities) if city not in visited] 88 | if (random.random() < epsilon): 89 | next_city = random.choice(unvisited_cities) 90 | else: 91 | next_city = unvisited_cities[np.argmax(q_table[current_city, unvisited_cities])] 92 | reward = -distance_matrix[current_city, next_city] 93 | max_future_q = max(q_table[next_city, unvisited_cities]) if unvisited_cities else 0 94 | q_table[current_city, next_city] = q_table[current_city, next_city] + learning_rate * (reward + discount_factor * max_future_q - q_table[current_city, next_city]) 95 | current_city = next_city 96 | visited.add(current_city) 97 | if (verbose == True and episode % 100 == 0): 98 | print(f"Episode {episode}") 99 | distance_matrix = distance_matrix*max_dist 100 | start_city = 0 101 | current_city = start_city 102 | visited = set([current_city]) 103 | route = [current_city] 104 | distance = 0 105 | while (len(visited) < num_cities): 106 | next_city = np.argmax([q_table[current_city, city] if city not in visited else -np.inf for city in range(0, num_cities)]) 107 | route.append(next_city) 108 | visited.add(next_city) 109 | distance = distance + distance_matrix[current_city, next_city] 110 | current_city = next_city 111 | route.append(start_city) 112 | distance = distance + distance_matrix[current_city, start_city] 113 | route = [node + 1 for node in route] 114 | seed = [route, distance] 115 | if (local_search == True): 116 | route, distance = local_search_2_opt(distance_matrix, seed, recursive_seeding = -1, verbose = verbose) 117 | return route, distance, q_table 118 | 119 | ############################################################################ 120 | 121 | 122 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/cw.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Clarke & Wright (Savings Heuristic) 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 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 | # Function: 2_opt 26 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 27 | if (recursive_seeding < 0): 28 | count = -2 29 | else: 30 | count = 0 31 | city_list = copy.deepcopy(city_tour) 32 | distance = city_list[1]*2 33 | iteration = 0 34 | while (count < recursive_seeding): 35 | if (verbose == True): 36 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 37 | best_route = copy.deepcopy(city_list) 38 | seed = copy.deepcopy(city_list) 39 | for i in range(0, len(city_list[0]) - 2): 40 | for j in range(i+1, len(city_list[0]) - 1): 41 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 42 | best_route[0][-1] = best_route[0][0] 43 | best_route[1] = distance_calc(distance_matrix, best_route) 44 | if (city_list[1] > best_route[1]): 45 | city_list = copy.deepcopy(best_route) 46 | best_route = copy.deepcopy(seed) 47 | count = count + 1 48 | iteration = iteration + 1 49 | if (distance > city_list[1] and recursive_seeding < 0): 50 | distance = city_list[1] 51 | count = -2 52 | recursive_seeding = -1 53 | elif(city_list[1] >= distance and recursive_seeding < 0): 54 | count = -1 55 | recursive_seeding = -2 56 | return city_list[0], city_list[1] 57 | 58 | ############################################################################ 59 | 60 | # Function: Memory List 61 | def memory_list(nodes, pairs, sav): 62 | memory = [ nodes[1:], [[] for item in nodes[1:]], [[] for item in nodes[1:]]] # Node, # Pair, # Savings 63 | for i in range(0, len(pairs)): 64 | m, n = pairs[i] 65 | m, n = m-1, n-1 66 | memory[1][m].append(n+1) 67 | memory[2][m].append(sav[i]) 68 | for i in range(0, len(pairs)): 69 | m, n = pairs[i] 70 | for j in range(0, len(memory[0])): 71 | if (n == memory[0][j]): 72 | memory[1][j].append(m) 73 | memory[2][j].append(sav[i]) 74 | return memory 75 | 76 | # Function: Next Neighbour to Enter 77 | def next_neighbour(memory, pairs, A, B, route): 78 | a = A - 1 79 | b = B - 1 80 | val_a = max(memory[2][a]) 81 | idx_a = memory[2][a].index(val_a) 82 | val_b = max(memory[2][b]) 83 | idx_b = memory[2][b].index(val_b) 84 | if (val_a >= val_b): 85 | candidate = memory[1][a][idx_a] 86 | route = [candidate] + route 87 | pair = [candidate, A] 88 | pair.sort() 89 | else: 90 | candidate = memory[1][b][idx_b] 91 | route = route + [candidate] 92 | pair = [candidate, B] 93 | pair.sort() 94 | idx = pairs.index(tuple(pair)) 95 | return route, idx 96 | 97 | ############################################################################ 98 | 99 | # Function: CW 100 | def clarke_wright_savings(distance_matrix, local_search = True, verbose = True): 101 | nodes = [item for item in list(range(0, distance_matrix.shape[0])) ] 102 | pairs = [ (nodes[i], nodes[j]) for i in range(1, len(nodes)-1) for j in range(i+1, len(nodes))] 103 | sav = [ distance_matrix[i,0] + distance_matrix[j, 0] - distance_matrix[i, j] for i,j in pairs] 104 | idx = sav.index(max(sav)) 105 | pair = pairs[idx] 106 | route = list(pair) 107 | while (len(route) < distance_matrix.shape[0]-1): 108 | sav[idx] = float('-inf') 109 | A = route[0] 110 | B = route[-1] 111 | if (len(route) >= 3): 112 | for i in route: 113 | if (A != i): 114 | pair = [A, i] 115 | pair.sort() 116 | pair = tuple(pair) 117 | idx = pairs.index(tuple(pair)) 118 | sav[idx] = float('-inf') 119 | if (B != i): 120 | pair = [B, i] 121 | pair.sort() 122 | pair = tuple(pair) 123 | idx = pairs.index(tuple(pair)) 124 | sav[idx] = float('-inf') 125 | memory = memory_list(nodes, pairs, sav) 126 | route, idx = next_neighbour(memory, pairs, A, B, route) 127 | route = [0] + route + [0] 128 | route = [node+1 for node in route] 129 | distance = distance_calc(distance_matrix, [route, 1]) 130 | seed = [route, distance] 131 | if (local_search == True): 132 | route, distance = local_search_2_opt(distance_matrix, seed, recursive_seeding = -1, verbose = verbose) 133 | return route, distance 134 | 135 | ############################################################################ 136 | 137 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/sa.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Simulated Annealing 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import numpy as np 15 | import os 16 | import random 17 | 18 | ############################################################################ 19 | 20 | # Function: Initial Seed 21 | def seed_function(distance_matrix): 22 | seed = [[], float('inf')] 23 | sequence = random.sample(list(range(1, distance_matrix.shape[0]+1)), distance_matrix.shape[0]) 24 | sequence.append(sequence[0]) 25 | seed[0] = sequence 26 | seed[1] = distance_calc(distance_matrix, seed) 27 | return seed 28 | 29 | ############################################################################ 30 | 31 | # Function: Tour Distance 32 | def distance_calc(distance_matrix, city_tour): 33 | distance = 0 34 | for k in range(0, len(city_tour[0])-1): 35 | m = k + 1 36 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 37 | return distance 38 | 39 | # Function: 2_opt 40 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 41 | if (recursive_seeding < 0): 42 | count = -2 43 | else: 44 | count = 0 45 | city_list = copy.deepcopy(city_tour) 46 | distance = city_list[1]*2 47 | iteration = 0 48 | while (count < recursive_seeding): 49 | if (verbose == True): 50 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 51 | best_route = copy.deepcopy(city_list) 52 | seed = copy.deepcopy(city_list) 53 | for i in range(0, len(city_list[0]) - 2): 54 | for j in range(i+1, len(city_list[0]) - 1): 55 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 56 | best_route[0][-1] = best_route[0][0] 57 | best_route[1] = distance_calc(distance_matrix, best_route) 58 | if (city_list[1] > best_route[1]): 59 | city_list = copy.deepcopy(best_route) 60 | best_route = copy.deepcopy(seed) 61 | count = count + 1 62 | iteration = iteration + 1 63 | if (distance > city_list[1] and recursive_seeding < 0): 64 | distance = city_list[1] 65 | count = -2 66 | recursive_seeding = -1 67 | elif(city_list[1] >= distance and recursive_seeding < 0): 68 | count = -1 69 | recursive_seeding = -2 70 | return city_list[0], city_list[1] 71 | 72 | ############################################################################ 73 | 74 | # Function: Update Solution 4-opt Pertubation 75 | def update_solution(distance_matrix, guess): 76 | cl = [guess[0][:-1], guess[1]] 77 | i, j, k, L = random.sample(list(range(0, len(cl[0]))), 4) 78 | idx = [i, j, k, L] 79 | idx.sort() 80 | i, j, k, L = idx 81 | A = cl[0][:i+1] + cl[0][i+1:j+1] 82 | B = cl[0][j+1:k+1] 83 | b = list(reversed(B)) 84 | C = cl[0][k+1:L+1] 85 | c = list(reversed(C)) 86 | D = cl[0][L+1:] 87 | d = list(reversed(D)) 88 | trial = [ 89 | # 4-opt: Sequential 90 | [A + b + c + d], [A + C + B + d], [A + C + b + d], [A + c + B + d], [A + D + B + c], 91 | [A + D + b + C], [A + d + B + c], [A + d + b + C], [A + d + b + c], [A + b + D + C], 92 | [A + b + D + c], [A + b + d + C], [A + C + d + B], [A + C + d + b], [A + c + D + B], 93 | [A + c + D + b], [A + c + d + b], [A + D + C + b], [A + D + c + B], [A + d + C + B], 94 | 95 | # 4-opt: Non-Sequential 96 | [A + b + C + d], [A + D + b + c], [A + c + d + B], [A + D + C + B], [A + d + C + b] 97 | ] 98 | item = random.choice(trial) 99 | cl[0] = item[0] 100 | cl[0] = cl[0] + [cl[0][0]] 101 | cl[1] = distance_calc(distance_matrix, cl) 102 | return cl 103 | 104 | ############################################################################ 105 | 106 | # Function: Simulated Annealing 107 | def simulated_annealing_tsp(distance_matrix, initial_temperature = 1.0, temperature_iterations = 10, final_temperature = 0.0001, alpha = 0.9, verbose = True): 108 | guess = seed_function(distance_matrix) 109 | best = copy.deepcopy(guess) 110 | temperature = float(initial_temperature) 111 | fx_best = float('+inf') 112 | while (temperature > final_temperature): 113 | for repeat in range(0, temperature_iterations): 114 | if (verbose == True): 115 | print('Temperature = ', round(temperature, 4), ' ; iteration = ', repeat, ' ; Distance = ', round(best[1], 2)) 116 | fx_old = guess[1] 117 | new_guess = update_solution(distance_matrix, guess) 118 | new_guess = local_search_2_opt(distance_matrix, new_guess, recursive_seeding = -1, verbose = False) 119 | fx_new = new_guess[1] 120 | delta = (fx_new - fx_old) 121 | r = int.from_bytes(os.urandom(8), byteorder = 'big') / ((1 << 64) - 1) 122 | p = np.exp(-delta/temperature) 123 | if (delta < 0 or r <= p): 124 | guess = copy.deepcopy(new_guess) 125 | if (fx_new < fx_best): 126 | fx_best = fx_new 127 | best = copy.deepcopy(guess) 128 | temperature = alpha*temperature 129 | route, distance = guess 130 | return route, distance 131 | 132 | ############################################################################ 133 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/eln.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Elastic Net 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import numpy as np 15 | 16 | ############################################################################ 17 | 18 | # Function: Decoder 19 | def decoder(ds): 20 | dx = np.copy(ds) 21 | nc_pairs = [] 22 | for i in range(0, dx.shape[0]): 23 | c = dx.min(axis = 1).argmin() 24 | n = dx[c,:].argmin() 25 | dx[c,:] = float('+inf') 26 | dx[:,n] = float('+inf') 27 | nc_pairs.append((n, c)) 28 | nc_pairs.sort(key = lambda x: x[0]) 29 | route = [x[1] for x in nc_pairs] 30 | route = route + [route[0]] 31 | route = [item+1 for item in route] 32 | return route 33 | 34 | ############################################################################ 35 | 36 | # Function: Tour Distance 37 | def distance_calc(distance_matrix, city_tour): 38 | distance = 0 39 | for k in range(0, len(city_tour[0])-1): 40 | m = k + 1 41 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 42 | return distance 43 | 44 | # Function: 2_opt 45 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 46 | if (recursive_seeding < 0): 47 | count = -2 48 | else: 49 | count = 0 50 | city_list = copy.deepcopy(city_tour) 51 | distance = city_list[1]*2 52 | iteration = 0 53 | while (count < recursive_seeding): 54 | if (verbose == True): 55 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 56 | best_route = copy.deepcopy(city_list) 57 | seed = copy.deepcopy(city_list) 58 | for i in range(0, len(city_list[0]) - 2): 59 | for j in range(i+1, len(city_list[0]) - 1): 60 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 61 | best_route[0][-1] = best_route[0][0] 62 | best_route[1] = distance_calc(distance_matrix, best_route) 63 | if (city_list[1] > best_route[1]): 64 | city_list = copy.deepcopy(best_route) 65 | best_route = copy.deepcopy(seed) 66 | count = count + 1 67 | iteration = iteration + 1 68 | if (distance > city_list[1] and recursive_seeding < 0): 69 | distance = city_list[1] 70 | count = -2 71 | recursive_seeding = -1 72 | elif(city_list[1] >= distance and recursive_seeding < 0): 73 | count = -1 74 | recursive_seeding = -2 75 | return city_list[0], city_list[1] 76 | 77 | ############################################################################ 78 | 79 | # Function: Initial Neurons 80 | def initial_neurons(coordinates, n_neurons, radius): 81 | theta = np.linspace(0, 2 * np.pi, n_neurons, False) 82 | centroid = coordinates.mean(axis = 0) 83 | neurons = np.vstack((np.cos(theta), np.sin(theta))) 84 | neurons = neurons * radius 85 | neurons = neurons + centroid[:, np.newaxis] 86 | neurons = neurons.T 87 | return neurons 88 | 89 | # Function: Update Weights 90 | def update_weights(coordinates, neurons, k): 91 | dt = coordinates[:, np.newaxis] - neurons 92 | ds = np.sum((dt**2), axis = 2) 93 | ws = np.exp(-ds / (2 * (k ** 2))) 94 | sr = ws.sum(axis = 1) 95 | ws = ws / sr[:, np.newaxis] 96 | return ds, dt, ws 97 | 98 | ############################################################################ 99 | 100 | # Function: Elastic Net (Adapted from: https://github.com/larose/ena) 101 | def elastic_net_tsp(coordinates, distance_matrix, alpha = 0.2, beta = 2.0, k = 0.2, learning_rate = 0.99, learning_upt = 25, iterations = 7000, n_neurons = 100, radius = 0.1, local_search = True, verbose = True): 102 | n_neurons = int(max(2.5*coordinates.shape[0], n_neurons)) 103 | k1 = k 104 | max_value = coordinates.max() 105 | min_value = coordinates.min() 106 | coords = (coordinates - min_value) / (max_value - min_value + 0.0000000000000001) 107 | neurons = initial_neurons(coords, n_neurons, radius) 108 | count = 0 109 | route = [] 110 | distance = float('+inf') 111 | d = float('+inf') 112 | while (count <= iterations): 113 | if (verbose == True): 114 | print('Iteration = ', count, ' Distance = ', round(distance, 2)) 115 | if (count % learning_upt == 0 and count > 0): 116 | k1 = max(0.01, k1 * learning_rate) 117 | ds, dt, ws = update_weights(coords, neurons, k1) 118 | D_force = np.array([np.dot(ws[:,i], dt[:,i]) for i in range(0, n_neurons)]) 119 | L_force = np.concatenate(( 120 | [ neurons[1] - 2 * neurons[0] + neurons[n_neurons-1] ], 121 | [(neurons[i+1] - 2 * neurons[i] + neurons[i-1]) for i in range(1, n_neurons-1) ], 122 | [ neurons[0] - 2 * neurons[n_neurons-1] + neurons[n_neurons-2] ] 123 | )) 124 | neurons = neurons + alpha * D_force + beta * k1 * L_force 125 | count = count + 1 126 | r = decoder(ds) 127 | d = distance_calc(distance_matrix, [r, 1]) 128 | if (d < distance): 129 | route = [item for item in r] 130 | distance = d 131 | if (local_search == True): 132 | print('') 133 | print('Local Search...') 134 | print('') 135 | seed = [route, distance] 136 | route, distance = local_search_2_opt(distance_matrix, seed, recursive_seeding = -1, verbose = verbose) 137 | return route, distance 138 | 139 | ############################################################################ 140 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/rl_sarsa.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: S.A.R.S.A 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import numpy as np 15 | import random 16 | 17 | ############################################################################ 18 | 19 | # Function: Tour Distance 20 | def distance_calc(distance_matrix, city_tour): 21 | distance = 0 22 | for k in range(0, len(city_tour[0])-1): 23 | m = k + 1 24 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 25 | return distance 26 | 27 | # Function: 2_opt 28 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 29 | if (recursive_seeding < 0): 30 | count = -2 31 | else: 32 | count = 0 33 | city_list = copy.deepcopy(city_tour) 34 | distance = city_list[1]*2 35 | iteration = 0 36 | while (count < recursive_seeding): 37 | if (verbose == True): 38 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 39 | best_route = copy.deepcopy(city_list) 40 | seed = copy.deepcopy(city_list) 41 | for i in range(0, len(city_list[0]) - 2): 42 | for j in range(i+1, len(city_list[0]) - 1): 43 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 44 | best_route[0][-1] = best_route[0][0] 45 | best_route[1] = distance_calc(distance_matrix, best_route) 46 | if (city_list[1] > best_route[1]): 47 | city_list = copy.deepcopy(best_route) 48 | best_route = copy.deepcopy(seed) 49 | count = count + 1 50 | iteration = iteration + 1 51 | if (distance > city_list[1] and recursive_seeding < 0): 52 | distance = city_list[1] 53 | count = -2 54 | recursive_seeding = -1 55 | elif(city_list[1] >= distance and recursive_seeding < 0): 56 | count = -1 57 | recursive_seeding = -2 58 | return city_list[0], city_list[1] 59 | 60 | ############################################################################ 61 | 62 | # Function: Q-Table Init 63 | def initialize_q_table(num_cities, seed): 64 | if seed is not None: 65 | np.random.seed(seed) 66 | q_table = np.zeros((num_cities, num_cities)) 67 | num_noisy_elements = int(1 * num_cities * num_cities) 68 | idx = np.random.choice(num_cities * num_cities, num_noisy_elements, replace = False) 69 | noise = np.random.uniform(-0.01, 0.01, size = num_noisy_elements) 70 | q_table.flat[idx] = noise 71 | return q_table 72 | 73 | # Function: SARSA 74 | def sarsa(distance_matrix, learning_rate = 0.1, discount_factor = 0.95, epsilon = 0.15, episodes = 5000, q_init = None, local_search = True, verbose = True): 75 | max_dist = np.max(distance_matrix) 76 | distance_matrix = distance_matrix / max_dist 77 | num_cities = distance_matrix.shape[0] 78 | if (q_init == None): 79 | q_table = np.zeros((num_cities, num_cities)) 80 | else: 81 | q_table = initialize_q_table(num_cities, q_init) 82 | 83 | for episode in range(0, episodes): 84 | current_city = random.randint(0, num_cities - 1) 85 | visited = set([current_city]) 86 | unvisited_cities = [city for city in range(num_cities) if city not in visited] 87 | if (random.random() < epsilon): 88 | next_city = random.choice(unvisited_cities) 89 | else: 90 | next_city = unvisited_cities[np.argmax(q_table[current_city, unvisited_cities])] 91 | 92 | while (len(visited) < num_cities): 93 | reward = -distance_matrix[current_city, next_city] 94 | visited.add(next_city) 95 | unvisited_cities = [city for city in range(num_cities) if city not in visited] 96 | if (len(unvisited_cities) == 0): 97 | next_next_city = None 98 | elif (random.random() < epsilon): 99 | next_next_city = random.choice(unvisited_cities) 100 | else: 101 | next_next_city = unvisited_cities[np.argmax(q_table[next_city, unvisited_cities])] 102 | if (next_next_city is not None): 103 | q_table[current_city, next_city] = q_table[current_city, next_city] + learning_rate * (reward + discount_factor * q_table[next_city, next_next_city] - q_table[current_city, next_city]) 104 | else: 105 | q_table[current_city, next_city] = q_table[current_city, next_city] + learning_rate * (reward - q_table[current_city, next_city]) 106 | current_city = next_city 107 | next_city = next_next_city 108 | if (verbose == True and episode % 100 == 0): 109 | print(f"Episode {episode}") 110 | distance_matrix = distance_matrix * max_dist 111 | start_city = 0 112 | current_city = start_city 113 | visited = set([current_city]) 114 | route = [current_city] 115 | distance = 0 116 | while (len(visited) < num_cities): 117 | unvisited_cities = [city for city in range(0, num_cities) if city not in visited] 118 | next_city = unvisited_cities[np.argmax(q_table[current_city, unvisited_cities])] 119 | route.append(next_city) 120 | visited.add (next_city) 121 | distance = distance + distance_matrix[current_city, next_city] 122 | current_city = next_city 123 | route.append(start_city) 124 | distance = distance + distance_matrix[current_city, start_city] 125 | route = [node + 1 for node in route] 126 | seed = [route, distance] 127 | 128 | if (local_search == True): 129 | route, distance = local_search_2_opt(distance_matrix, seed, recursive_seeding = -1, verbose = verbose) 130 | return route, distance, q_table 131 | 132 | ############################################################################ 133 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/rl_double_ql.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Double Q-Learning 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 14 | import numpy as np 15 | import random 16 | 17 | ############################################################################ 18 | 19 | # Function: Tour Distance 20 | def distance_calc(distance_matrix, city_tour): 21 | distance = 0 22 | for k in range(0, len(city_tour[0])-1): 23 | m = k + 1 24 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 25 | return distance 26 | 27 | # Function: 2_opt 28 | def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 29 | if (recursive_seeding < 0): 30 | count = -2 31 | else: 32 | count = 0 33 | city_list = copy.deepcopy(city_tour) 34 | distance = city_list[1]*2 35 | iteration = 0 36 | while (count < recursive_seeding): 37 | if (verbose == True): 38 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 39 | best_route = copy.deepcopy(city_list) 40 | seed = copy.deepcopy(city_list) 41 | for i in range(0, len(city_list[0]) - 2): 42 | for j in range(i+1, len(city_list[0]) - 1): 43 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 44 | best_route[0][-1] = best_route[0][0] 45 | best_route[1] = distance_calc(distance_matrix, best_route) 46 | if (city_list[1] > best_route[1]): 47 | city_list = copy.deepcopy(best_route) 48 | best_route = copy.deepcopy(seed) 49 | count = count + 1 50 | iteration = iteration + 1 51 | if (distance > city_list[1] and recursive_seeding < 0): 52 | distance = city_list[1] 53 | count = -2 54 | recursive_seeding = -1 55 | elif(city_list[1] >= distance and recursive_seeding < 0): 56 | count = -1 57 | recursive_seeding = -2 58 | return city_list[0], city_list[1] 59 | 60 | ############################################################################ 61 | 62 | # Function: Q-Table Init 63 | def initialize_q_table(num_cities, seed): 64 | if seed is not None: 65 | np.random.seed(seed) 66 | q_table = np.zeros((num_cities, num_cities)) 67 | num_noisy_elements = int(1 * num_cities * num_cities) 68 | idx = np.random.choice(num_cities * num_cities, num_noisy_elements, replace = False) 69 | noise = np.random.uniform(-0.01, 0.01, size = num_noisy_elements) 70 | q_table.flat[idx] = noise 71 | return q_table 72 | 73 | # Function: Reconstruct Route from Q-Tables 74 | def reconstruct_route(q_table_a, q_table_b, distance_matrix, num_cities): 75 | start_city = 0 76 | current_city = start_city 77 | visited = set([current_city]) 78 | route = [current_city] 79 | total_distance = 0 80 | while (len(visited) < num_cities): 81 | avg_q_values = (q_table_a[current_city] + q_table_b[current_city]) / 2 82 | next_city = np.argmax([avg_q_values[city] if city not in visited else -np.inf for city in range(num_cities)]) 83 | route.append(next_city) 84 | visited.add(next_city) 85 | total_distance = total_distance + distance_matrix[current_city, next_city] 86 | current_city = next_city 87 | route.append(start_city) 88 | total_distance = total_distance + distance_matrix[current_city, start_city] 89 | return [city + 1 for city in route], total_distance 90 | 91 | # Function: Double Q-Learning 92 | def double_q_learning(distance_matrix, learning_rate = 0.1, discount_factor = 0.95, epsilon = 0.15, episodes = 5000, q_init_a = None, q_init_b = None, local_search = True, verbose = True): 93 | max_dist = np.max(distance_matrix) 94 | distance_matrix = distance_matrix / max_dist 95 | num_cities = distance_matrix.shape[0] 96 | q_table_a = initialize_q_table(num_cities, q_init_a) 97 | q_table_b = initialize_q_table(num_cities, q_init_b) 98 | for episode in range(0, episodes): 99 | current_city = random.randint(0, num_cities - 1) 100 | visited = set([current_city]) 101 | while (len(visited) < num_cities): 102 | unvisited_cities = [city for city in range(0, num_cities) if city not in visited] 103 | if (random.random() < epsilon): 104 | next_city = random.choice(unvisited_cities) 105 | else: 106 | avg_q_values = (q_table_a[current_city] + q_table_b[current_city]) / 2 107 | next_city = unvisited_cities[np.argmax(avg_q_values[unvisited_cities])] 108 | reward = -distance_matrix[current_city, next_city] 109 | if (random.random() < 0.5): 110 | max_q_b = max(q_table_b[next_city, unvisited_cities]) if unvisited_cities else 0 111 | q_table_a[current_city, next_city] = q_table_a[current_city, next_city] + learning_rate * (reward + discount_factor * max_q_b - q_table_a[current_city, next_city]) 112 | else: 113 | max_q_a = max(q_table_a[next_city, unvisited_cities]) if unvisited_cities else 0 114 | q_table_b[current_city, next_city] = q_table_b[current_city, next_city] + learning_rate * (reward + discount_factor * max_q_a - q_table_b[current_city, next_city]) 115 | current_city = next_city 116 | visited.add(current_city) 117 | if (verbose and episode % 100 == 0): 118 | print(f"Episode {episode}") 119 | distance_matrix = distance_matrix * max_dist 120 | route, distance = reconstruct_route(q_table_a, q_table_b, distance_matrix, num_cities) 121 | seed = [route, distance] 122 | if (local_search == True): 123 | route, distance = local_search_2_opt(distance_matrix, seed, recursive_seeding = -1, verbose = verbose) 124 | return route, distance, q_table_a, q_table_b 125 | 126 | ############################################################################ 127 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/brkga.py: -------------------------------------------------------------------------------- 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: 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 | ############################################################################ 157 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/eo.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Extremal Optimization 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import numpy as np 14 | import copy 15 | import os 16 | 17 | ############################################################################ 18 | 19 | # Function: Tour Distance 20 | def distance_calc(distance_matrix, city_tour): 21 | distance = 0 22 | for k in range(0, len(city_tour[0])-1): 23 | m = k + 1 24 | distance = distance + distance_matrix[city_tour[0][k]-1, city_tour[0][m]-1] 25 | return distance 26 | 27 | ############################################################################ 28 | 29 | # Function: Rank Cities 30 | def ranking(distance_matrix, city = 0, tau = 1.8): 31 | rank = np.zeros((distance_matrix.shape[0], 4)) 32 | for i in range(0, rank.shape[0]): 33 | rank[i,0] = distance_matrix[i,city] 34 | rank[i,1] = i + 1 35 | rank = rank[rank[:,0].argsort()] 36 | for i in range(0, rank.shape[0]): 37 | rank[i,2] = i 38 | if (i> 0): 39 | rank[i,3] = i**(-tau) 40 | sum_prob = rank[:, 3].sum() 41 | for i in range(0, rank.shape[0]): 42 | rank[i, 3] = rank[i, 3]/sum_prob 43 | rank = rank[rank[:,-1].argsort()] 44 | for i in range(1, rank.shape[0]): 45 | rank[i,3] = rank[i,3] + rank[i-1,3] 46 | return rank 47 | 48 | # Function: Selection 49 | def roulette_wheel(rank, city_tour, tau = 1.8): 50 | fitness = np.zeros((rank.shape[0], 5)) 51 | fitness[:,0] = city_tour[0][0:-1] 52 | fitness[:,1] = city_tour[0][-2:-1] + city_tour[0][0:-2] 53 | fitness[:,2] = city_tour[0][1:] 54 | for i in range(0, fitness.shape[0]): 55 | left = rank[np.where(rank[:,1] == fitness[i, 1])] 56 | right = rank[np.where(rank[:,1] == fitness[i, 2])] 57 | fitness[i, 3] = 3/(left[0,2] + right[0,2]) 58 | fitness[i, 4] = fitness[i, 3]**(-tau) 59 | sum_prob = fitness[:, 4].sum() 60 | for i in range(0, fitness.shape[0]): 61 | fitness[i, 4] = fitness[i, 4]/sum_prob 62 | fitness = fitness[fitness[:,-1].argsort()] 63 | for i in range(1, fitness.shape[0]): 64 | fitness[i,4] = fitness[i,4] + fitness[i-1,4] 65 | ix = 1 66 | iy = -1 # left 67 | iz = -1 # rigth 68 | iw = 1 # change 69 | rand = int.from_bytes(os.urandom(8), byteorder = 'big') / ((1 << 64) - 1) 70 | for i in range(0, fitness.shape[0]): 71 | if (rand <= fitness[i, 4]): 72 | ix = fitness[i, 0] 73 | iw = fitness[i, 0] 74 | left = rank[np.where(rank[:,1] == fitness[i, 1])] 75 | right = rank[np.where(rank[:,1] == fitness[i, 2])] 76 | if (left[0,0] > right[0,0]): 77 | iy = fitness[i, 1] 78 | iz = -1 79 | else: 80 | iy = -1 81 | iz = fitness[i, 2] 82 | break 83 | while (ix == iw): 84 | rand = int.from_bytes(os.urandom(8), byteorder = "big") / ((1 << 64) - 1) 85 | for i in range(0, rank.shape[0]): 86 | if (rand <= rank[i, 3]): 87 | iw = fitness[i, 0] 88 | break 89 | return iy, ix, iz, iw 90 | 91 | # Function: Exchange 92 | def exchange(distance_matrix, city_tour, iy = 1, ix = 2, iz = 3, iw = 4): 93 | best_route = copy.deepcopy(city_tour) 94 | tour = copy.deepcopy(city_tour) 95 | if (iy == -1 and city_tour[0].index(iw) < city_tour[0].index(ix)): 96 | i = city_tour[0].index(ix) - 1 97 | j = city_tour[0].index(ix) 98 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 99 | best_route[0][-1] = best_route[0][0] 100 | i = city_tour[0].index(iw) 101 | j = city_tour[0].index(ix) - 1 102 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 103 | best_route[0][-1] = best_route[0][0] 104 | best_route[1] = distance_calc(distance_matrix, city_tour = best_route) 105 | elif (iy == -1 and city_tour[0].index(iw) > city_tour[0].index(ix)): 106 | i = city_tour[0].index(ix) 107 | j = city_tour[0].index(iw) 108 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 109 | best_route[0][-1] = best_route[0][0] 110 | best_route[1] = distance_calc(distance_matrix, city_tour = best_route) 111 | elif (iz == -1 and city_tour[0].index(iw) < city_tour[0].index(ix)): 112 | i = city_tour[0].index(iw) 113 | j = city_tour[0].index(ix) 114 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 115 | best_route[0][-1] = best_route[0][0] 116 | best_route[1] = distance_calc(distance_matrix, city_tour = best_route) 117 | elif (iz == -1 and city_tour[0].index(iw) > city_tour[0].index(ix)): 118 | i = city_tour[0].index(ix) 119 | j = city_tour[0].index(ix) + 1 120 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 121 | best_route[0][-1] = best_route[0][0] 122 | i = city_tour[0].index(ix) + 1 123 | j = city_tour[0].index(iw) 124 | best_route[0][i:j+1] = list(reversed(best_route[0][i:j+1])) 125 | best_route[0][-1] = best_route[0][0] 126 | best_route[1] = distance_calc(distance_matrix, city_tour = best_route) 127 | if (best_route[1] < tour[1]): 128 | tour[1] = copy.deepcopy(best_route[1]) 129 | for n in range(0, len(tour[0])): 130 | tour[0][n] = best_route[0][n] 131 | return tour 132 | 133 | ############################################################################ 134 | 135 | # Function: Extremal Optimization 136 | def extremal_optimization(distance_matrix, city_tour, iterations = 50, tau = 1.8, verbose = True): 137 | count = 0 138 | best_solution = copy.deepcopy(city_tour) 139 | while (count < iterations): 140 | if (verbose == True): 141 | print('Iteration = ', count, 'Distance = ', round(best_solution[1], 2)) 142 | for i in range(0, distance_matrix.shape[0]): 143 | rank = ranking(distance_matrix, city = i, tau = tau) 144 | iy, ix, iz, iw = roulette_wheel(rank, city_tour, tau = tau) 145 | city_tour = exchange(distance_matrix, city_tour, iy = iy, ix = ix, iz = iz, iw = iw) 146 | if (city_tour[1] < best_solution[1]): 147 | best_solution = copy.deepcopy(city_tour) 148 | count = count + 1 149 | city_tour = copy.deepcopy(best_solution) 150 | route, distance = best_solution 151 | return route, distance 152 | 153 | ############################################################################ 154 | -------------------------------------------------------------------------------- /pyCombinatorial/algorithm/opt_4.py: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | 3 | # Created by: Prof. Valdecy Pereira, D.Sc. 4 | # UFF - Universidade Federal Fluminense (Brazil) 5 | # email: valdecy.pereira@gmail.com 6 | # Lesson: Local Search-4-opt 7 | 8 | # GitHub Repository: 9 | 10 | ############################################################################ 11 | 12 | # Required Libraries 13 | import copy 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: Possible Segments 28 | def segments_4_opt(n): 29 | x = [] 30 | a, b, c, d = 0, 0, 0, 0 31 | for i in range(0, n): 32 | a = i 33 | for j in range(i + 1, n): 34 | b = j 35 | for k in range(j + 1, n): 36 | c = k 37 | for L in range(k + 1, n + (i > 0)): 38 | d = L 39 | x.append((a, b, c, d)) 40 | return x 41 | 42 | ############################################################################ 43 | 44 | # Function: 4_opt 45 | def local_search_4_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose = True): 46 | if (recursive_seeding < 0): 47 | count = recursive_seeding - 1 48 | else: 49 | count = 0 50 | city_list = [city_tour[0][:-1], city_tour[1]] 51 | city_list_old = city_list[1]*2 52 | iteration = 0 53 | while (count < recursive_seeding): 54 | if (verbose == True): 55 | print('Iteration = ', iteration, 'Distance = ', round(city_list[1], 2)) 56 | best_route = copy.deepcopy(city_list) 57 | best_route_1 = [[], 1] 58 | seed = copy.deepcopy(city_list) 59 | x = segments_4_opt(len(city_list[0])) 60 | for item in x: 61 | i, j, k, L = item 62 | A = best_route[0][:i+1] + best_route[0][i+1:j+1] 63 | a = best_route[0][:i+1] + list(reversed(best_route[0][i+1:j+1])) 64 | B = best_route[0][j+1:k+1] 65 | b = list(reversed(B)) 66 | C = best_route[0][k+1:L+1] 67 | c = list(reversed(C)) 68 | D = best_route[0][L+1:] 69 | d = list(reversed(D)) 70 | trial = [ 71 | # Original Tour 72 | #[A + B + C + D], 73 | 74 | # Permutation 75 | [A + C + B + D], 76 | [A + C + D + B], 77 | 78 | 79 | # 1 80 | [a + B + C + D], 81 | [a + C + B + D], 82 | [a + C + D + B], 83 | 84 | [A + b + C + D], 85 | [A + C + b + D], 86 | [A + C + D + b], 87 | 88 | [A + B + c + D], 89 | [A + c + B + D], 90 | [A + c + D + B], 91 | 92 | [A + B + C + d], 93 | [A + C + B + d], 94 | [A + C + d + B], 95 | 96 | 97 | # 2 98 | [a + b + C + D], 99 | [a + C + b + D], 100 | [a + C + D + b], 101 | 102 | [a + B + c + D], 103 | [a + c + B + D], 104 | [a + c + D + B], 105 | 106 | [a + B + C + d], 107 | [a + C + B + d], 108 | [a + C + d + B], 109 | 110 | [A + b + c + D], 111 | [A + c + b + D], 112 | [A + c + D + b], 113 | 114 | [A + b + C + d], 115 | [A + C + b + d], 116 | [A + C + d + b], 117 | 118 | [A + B + c + d], 119 | [A + c + B + d], 120 | [A + c + d + B], 121 | 122 | 123 | 124 | # 3 125 | [a + b + c + D], 126 | [a + c + b + D], 127 | [a + c + D + b], 128 | 129 | [a + b + C + d], 130 | [a + C + b + d], 131 | [a + C + d + b], 132 | 133 | [a + B + c + d], 134 | [a + c + B + d], 135 | [a + c + d + B], 136 | 137 | [A + b + c + d], 138 | [A + c + b + d], 139 | [A + c + d + b], 140 | 141 | 142 | 143 | # 4 144 | [a + b + c + d], 145 | [a + c + b + d], 146 | [a + c + d + b], 147 | 148 | ] 149 | for item in trial: 150 | best_route_1[0] = item[0] 151 | best_route_1[1] = distance_calc(distance_matrix, [best_route_1[0] + [best_route_1[0][0]], 1]) 152 | if (best_route_1[1] < best_route[1]): 153 | best_route = [best_route_1[0], best_route_1[1]] 154 | if (best_route[1] < city_list[1]): 155 | city_list = [best_route[0], best_route[1]] 156 | best_route = copy.deepcopy(seed) 157 | count = count + 1 158 | iteration = iteration + 1 159 | if (city_list_old > city_list[1] and recursive_seeding < 0): 160 | city_list_old = city_list[1] 161 | count = -2 162 | recursive_seeding = -1 163 | elif(city_list[1] >= city_list_old and recursive_seeding < 0): 164 | count = -1 165 | recursive_seeding = -2 166 | city_list = [city_list[0] + [city_list[0][0]], city_list[1]] 167 | return city_list[0], city_list[1] 168 | 169 | ############################################################################ 170 | 171 | --------------------------------------------------------------------------------