├── 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 |
--------------------------------------------------------------------------------