Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 137 additions & 47 deletions README.md

Large diffs are not rendered by default.

Binary file added assets/ACO-Formula.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/Heuristic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/LRP-Random-Factor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/Logistis Route Problem Map.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/MTSP Test Case.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/Multiple TSP Test Case.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/Pathfinding 1992 to 2000.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/Pathfinding in TSP.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
138 changes: 138 additions & 0 deletions src/ant_colony.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# ant_colony.py
# Contain AntColony class to find the shortest path for TSP
# Modified ant_colony.py for incomplete graph and multiple TSP
# Reference for original source code : https://github.com/Akavall/AntColonyOptimization

import random as rn
import numpy as np
from numpy.random import choice as np_choice

class AntColony(object):
# Constructor to create an AntColony object
def __init__(self, distances, n_ants, n_best, n_iterations, decay, alpha=1, beta=1):
"""
Args:
distances (2D numpy.array): Square matrix of distances. Diagonal is assumed to be np.inf.
n_ants (int): Number of ants running per iteration
n_best (int): Number of best ants who deposit pheromone
n_iteration (int): Number of iterations
decay (float): Rate it which pheromone decays. The pheromone value is multiplied by decay, so 0.95 will lead to decay, 0.5 to much faster decay.
alpha (int or float): exponenet on pheromone, higher alpha gives pheromone more weight. Default=1
beta (int or float): exponent on distance, higher beta give distance more weight. Default=1

Example:
ant_colony = AntColony(german_distances, 100, 20, 2000, 0.95, alpha=1, beta=2)
"""
self.distances = distances
self.pheromone = np.ones(self.distances.shape) / len(distances)
self.all_inds = range(len(distances))
self.n_ants = n_ants
self.n_best = n_best
self.n_iterations = n_iterations
self.decay = decay
self.alpha = alpha
self.beta = beta

# Execute ant-colony optimization algorithm to get shortest TSP route
def run(self):
# Initialize shortest path parameter
shortest_path = None
found = False
all_time_shortest_path = ("placeholder", np.inf)
most_cities = ("placeholder", np.inf)

# Finding the shortest path by n_iteration
for i in range(self.n_iterations):
all_paths = self.gen_all_paths()
self.spread_pheronome(all_paths, self.n_best, shortest_path=shortest_path)

# Choose the shortest path and most city visited
for path in all_paths:
if (path[1] != np.inf):
if (shortest_path == None):
shortest_path = path
if (len(path[0]) == len(shortest_path[0])):
if (path[1] < shortest_path[1]):
shortest_path = path
elif (len(path[0]) > len(shortest_path[0])):
shortest_path = path

# The shortest path will be considered if it is None
if (shortest_path != None):
if (shortest_path[0] != None):
# print (shortest_path)

# The path is return to the origin city
if (shortest_path[0][-1][1] == 0):
found = True
if ((shortest_path[1] < all_time_shortest_path[1]) and (shortest_path[1] != np.inf)):
all_time_shortest_path = shortest_path

# Sometime, the path is not return to the origin city
else:
if ((shortest_path[1] < all_time_shortest_path[1]) and (shortest_path[1] != np.inf)):
most_cities = shortest_path

# Evaporation and decay every iteration
self.pheromone * self.decay

# From n_iteration, we found the shortest path, then return the shortest path
if (found):
return all_time_shortest_path

# From n_iteration, we didn't found the shortest path, then we return the path with the most city visited
else:
return most_cities

# Spread the pheromone on the n_best paths based on all paths and latest shortest path
def spread_pheronome(self, all_paths, n_best, shortest_path):
sorted_paths = sorted(all_paths, key=lambda x: x[1])
for path, dist in sorted_paths[:n_best]:
for move in path:
self.pheromone[move] += 1.0 / self.distances[move]

# Return distance of the path based on the distance matrix
def gen_path_dist(self, path):
total_dist = 0
for ele in path:
total_dist += self.distances[ele]
return total_dist

# Generate all random path for each ant
def gen_all_paths(self):
all_paths = []
for i in range(self.n_ants):
path = self.gen_path(0)
all_paths.append((path, self.gen_path_dist(path)))
return all_paths

# Generate a random path for an ant based on the probability
def gen_path(self, start):
path = []
visited = set()
visited.add(start)
prev = start
for i in range(len(self.distances) - 1):
move = self.pick_move(self.pheromone[prev], self.distances[prev], visited)
if (move == -1):
break
path.append((prev, move))
prev = move
visited.add(move)
if (move != -1):
path.append((prev, start)) # going back to where we started
return path

# Select the next city to visit
def pick_move(self, pheromone, dist, visited):
pheromone = np.copy(pheromone)
pheromone[list(visited)] = 0

row = pheromone ** self.alpha * (( 1.0 / dist) ** self.beta)

if (row.sum() == 0):
return -1
else:
norm_row = row / row.sum()
move = np_choice(self.all_inds, 1, p=norm_row)[0]
return move
116 changes: 116 additions & 0 deletions src/astar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# astar.py
# A* Algorithm for Pathfinding (Modified)
# Reference : https://www.annytab.com/a-star-search-algorithm-in-python/

from graph import *
from node import *
import numpy as np

# Calculate heuristic value : distance from every city to destination city
def heuristics(cities, destination):
h = {}
for i in range(len(cities)):
g = cities.get(str(i)).distanceTo(destination)
h.update({str(i):g})
return h

# A* search
def astar_search(graph, heuristics, start, end):
# Create lists for open nodes and closed nodes
open = []
closed = []

# Create a start node and an goal node
start_node = Node(start, None)
goal_node = Node(end, None)

# Add the start node
open.append(start_node)

# Loop until the open list is empty
while len(open) > 0:

# Sort the open list to get the node with the lowest cost first
open.sort()

# Get the node with the lowest cost
current_node = open.pop(0)

# Add the current node to the closed list
closed.append(current_node)

# Check if we have reached the goal, return the path
if current_node == goal_node:
path = []
while current_node != start_node:
path.append([current_node.name, current_node.g])
current_node = current_node.parent
path.append([start_node.name, start_node.g])
# Return reversed path
return path[::-1]

# Get neighbours
neighbors = graph.get(current_node.name)

# Loop neighbors
for key, value in neighbors.items():

# Create a neighbor node
neighbor = Node(key, current_node)

# Check if the neighbor is in the closed list
if(neighbor in closed):
continue

# Calculate full path cost
neighbor.g = current_node.g + graph.get(current_node.name, neighbor.name)
neighbor.h = heuristics.get(neighbor.name)
neighbor.f = neighbor.g + neighbor.h

# Check if neighbor is in open list and if it has a lower f value
if(add_to_open(open, neighbor) == True):
# Everything is green, add neighbor to open list
open.append(neighbor)

# Return None, no path is found
return None

# Check if a neighbor should be added to open list
def add_to_open(open, neighbor):
for node in open:
if (neighbor == node and neighbor.f > node.f):
return False
return True

# Return the route information of the path
def getRoute(path):
route = []
for i in range(len(path)):
route.append(int(path[i][0]))
return route

# Return the cost infromation of the path
def getCost(path):
return path[-1][1]

# Generate distance matrix based on distance of the A* path
# Milestone 1 : Get Complete Graph of all depot and station
def generateAStarParameter(destination, maps, graph):
heuristicList = []
for i in range(len(destination)):
h = {}
h = heuristics(maps, maps.get(destination[i]))
heuristicList.append(h)

distance = [[np.inf for j in range(len(destination))] for i in range(len(destination))]
routes = [['placeholder' for j in range(len(destination))] for i in range(len(destination))]
for i in range(len(destination)):
for j in range(len(destination)):
if (i != j):
# Run the search algorithm
path = astar_search(graph, h, destination[i], destination[j])
route = getRoute(path)
routes[i][j] = route
distance[i][j] = getCost(path)

return distance, routes
63 changes: 63 additions & 0 deletions src/dijkstra.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Dijkstra.py
# Dijkstra Algorithm for Pathfinding
# Reference : Rinaldi Munir

import numpy as np

# Get the nearest vertex with minimum distance
def minDistance(dist, S, n):
# Initialize minimum distance for next node
min = np.inf
# Choose v from among those vertices not in S such that dist[u] is minimum

min_index = 0
for v in range(n):
if ((dist[v] < min) and (S[v] == False)):
min = dist[v]
min_index = v

return min_index

# Dijkstra Algorithm to return all shortest path fro a to n-1 cities
def dijkstra(G, a, n):
# Initialize S and Distance
S = []
dist = []
parent = []

for i in range(n):
S.append(False)
dist.append(np.inf)
parent.append(-1)

S[a] = True
dist[a] = 0

# Search for minimum distance from a to every node
for num in range(2,n):
# Determine n-1 paths from v
# Choose u from among those vertices not in S such that dist[u] is minimum
u = minDistance(dist, S, n)

S[u] = True # Put u in S
for w in range(n):
if ((S[w] == False) and (dist[w] > dist[u] + G[u][w])):
dist[w] = dist[u] + G[u][w]
parent[w] = u

return dist, parent

# Print the path for all solution for dijkstra
def getPath(parent, v):
path = []
if (parent[v] == -1):
path = [v]
else:
path = getPath(parent, parent[v]) + [v]
return path

# Print all solution for dijkstra
def printSolution(solution, parent):
print("Vertex \tDistance \t\tPath")
for node in range(len(solution)):
print(node, "\t", solution[node], "\t\t", getPath(parent, node))
43 changes: 43 additions & 0 deletions src/graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# graph.py
# This class represent a graph
# Reference : https://www.annytab.com/a-star-search-algorithm-in-python/

class Graph:
# Initialize the class
def __init__(self, graph_dict=None, directed=True):
self.graph_dict = graph_dict or {}
self.directed = directed
if not directed:
self.make_undirected()

# Create an undirected graph by adding symmetric edges
def make_undirected(self):
for a in list(self.graph_dict.keys()):
for (b, dist) in self.graph_dict[a].items():
self.graph_dict.setdefault(b, {})[a] = dist

# Add a link from A and B of given distance, and also add the inverse link if the graph is undirected
def connect(self, A, B, distance=1):
self.graph_dict.setdefault(A, {})[B] = distance
if not self.directed:
self.graph_dict.setdefault(B, {})[A] = distance

# Get neighbors or a neighbor
def get(self, a, b=None):
links = self.graph_dict.setdefault(a, {})
if b is None:
return links
else:
return links.get(b)

# Return a list of nodes in the graph
def nodes(self):
s1 = set([k for k in self.graph_dict.keys()])
s2 = set([k2 for v in self.graph_dict.values() for k2, v2 in v.items()])
nodes = s1.union(s2)
return list(nodes)

# Convert from streets information into Graph edges
def streetsToGraph(self, streets):
for edge in streets:
self.connect(edge[1], edge[2], float(edge[3]))
Loading