Compare commits

...

8 Commits

Author SHA1 Message Date
a060f93554
Improve the code.
- Add a docstring.
- Add type hinting.
- Change algorithm from steepest ascent to simple hill climbing.
2024-10-01 19:20:34 +02:00
667c19ecc3
Update docstring 2024-10-01 19:18:25 +02:00
375e2066b4
Change return statement to use numpy arrays 2024-10-01 19:17:10 +02:00
2ee3f59702
change plot_plan to take lists and numpy arrays. 2024-10-01 19:16:33 +02:00
b1c6ad01a0
Change function to indexes_to_cities 2024-10-01 19:07:14 +02:00
0e7965f06a
Make some cosmetic changes 2024-10-01 19:06:28 +02:00
4455a4e6f4
Add indexes_to_cities function 2024-10-01 19:05:58 +02:00
309cd59e7d
Add docstring to plot_plan. 2024-10-01 18:01:41 +02:00
3 changed files with 126 additions and 63 deletions

View File

@ -1,23 +1,55 @@
from typing import List, Tuple from typing import Dict, List, Tuple, Union
import matplotlib.pyplot as plt
import numpy as np import numpy as np
import numpy.typing as npt import numpy.typing as npt
import matplotlib.pyplot as plt
# Data given by the assignment # Data given by the assignment
city_coords = { city_coords: Dict = {
"Barcelona": [2.154007, 41.390205], "Belgrade": [20.46, 44.79], "Berlin": [13.40, 52.52], "Barcelona": [2.154007, 41.390205],
"Brussels": [4.35, 50.85], "Bucharest": [26.10, 44.44], "Budapest": [19.04, 47.50], "Belgrade": [20.46, 44.79],
"Copenhagen": [12.57, 55.68], "Dublin": [-6.27, 53.35], "Hamburg": [9.99, 53.55], "Berlin": [13.40, 52.52],
"Istanbul": [28.98, 41.02], "Kyiv": [30.52, 50.45], "London": [-0.12, 51.51], "Brussels": [4.35, 50.85],
"Madrid": [-3.70, 40.42], "Milan": [9.19, 45.46], "Moscow": [37.62, 55.75], "Bucharest": [26.10, 44.44],
"Munich": [11.58, 48.14], "Paris": [2.35, 48.86], "Prague": [14.42, 50.07], "Budapest": [19.04, 47.50],
"Rome": [12.50, 41.90], "Saint Petersburg": [30.31, 59.94], "Sofia": [23.32, 42.70], "Copenhagen": [12.57, 55.68],
"Stockholm": [18.06, 60.33], "Vienna": [16.36, 48.21], "Warsaw": [21.02, 52.24]} "Dublin": [-6.27, 53.35],
"Hamburg": [9.99, 53.55],
"Istanbul": [28.98, 41.02],
"Kyiv": [30.52, 50.45],
"London": [-0.12, 51.51],
"Madrid": [-3.70, 40.42],
"Milan": [9.19, 45.46],
"Moscow": [37.62, 55.75],
"Munich": [11.58, 48.14],
"Paris": [2.35, 48.86],
"Prague": [14.42, 50.07],
"Rome": [12.50, 41.90],
"Saint Petersburg": [30.31, 59.94],
"Sofia": [23.32, 42.70],
"Stockholm": [18.06, 60.33],
"Vienna": [16.36, 48.21],
"Warsaw": [21.02, 52.24],
}
def plot_plan(city_order: List[str]) -> None:
europe_map = plt.imread('map.png') def plot_plan(city_order: Union[List[str], npt.NDArray]) -> None:
fig, ax = plt.subplots(figsize=(10, 10)) """A function that plots the circuit given by city_order.
ax.imshow(europe_map, extent=[-14.56, 38.43, 37.697 + 0.3, 64.344 + 2.0], aspect="auto")
This function was given in the assignment from 2024.
Args:
city_order (List[str]): A list of cities in the order to be plotted.
Returns:
None
"""
europe_map = plt.imread("map.png")
_, ax = plt.subplots(figsize=(10, 10))
ax.imshow(
europe_map, extent=[-14.56, 38.43, 37.697 + 0.3, 64.344 + 2.0], aspect="auto"
)
# Map (long, lat) to (x, y) for plotting # Map (long, lat) to (x, y) for plotting
for index in range(len(city_order) - 1): for index in range(len(city_order) - 1):
@ -28,17 +60,32 @@ def plot_plan(city_order: List[str]) -> None:
next_x, next_y = next_city_coords[0], next_city_coords[1] next_x, next_y = next_city_coords[0], next_city_coords[1]
plt.plot([x, next_x], [y, next_y]) plt.plot([x, next_x], [y, next_y])
plt.plot(x, y, 'ok', markersize=5) plt.plot(x, y, "ok", markersize=5)
plt.text(x, y, str(index), fontsize=12) plt.text(x, y, str(index), fontsize=12)
# Finally, plotting from last to first city # Finally, plotting from last to first city
first_city_coords = city_coords[city_order[0]] first_city_coords = city_coords[city_order[0]]
first_x, first_y = first_city_coords[0], first_city_coords[1] first_x, first_y = first_city_coords[0], first_city_coords[1]
plt.plot([next_x, first_x], [next_y, first_y]) plt.plot([next_x, first_x], [next_y, first_y])
# Plotting a marker and index for the final city # Plotting a marker and index for the final city
plt.plot(next_x, next_y, 'ok', markersize=5) plt.plot(next_x, next_y, "ok", markersize=5)
plt.text(next_x, next_y, str(index + 1), fontsize=12) plt.text(next_x, next_y, str(index + 1), fontsize=12)
plt.show() plt.show()
def indexes_to_cities(indexes: npt.NDArray, cities: npt.NDArray) -> npt.NDArray:
"""Create an array of cities from indeces in a specific order.
Args:
indexes (npt.NDArray): An array of city indexes.
cities (npt.NDArray): An array of cities.
Returns:
npt.NDArray An array of cities in the same order as given in indexes.
"""
return np.array([cities[i] for i in indexes])
def read_data(file_path: str) -> Tuple[npt.NDArray, npt.NDArray]: def read_data(file_path: str) -> Tuple[npt.NDArray, npt.NDArray]:
"""Read the city data from a file given and return 2 arrays. """Read the city data from a file given and return 2 arrays.
@ -55,6 +102,7 @@ def read_data(file_path: str) -> Tuple[npt.NDArray, npt.NDArray]:
and the data associated with it. and the data associated with it.
""" """
cities = None cities = None
data = [] data = []
with open(file_path, "r") as f: with open(file_path, "r") as f:
@ -67,6 +115,7 @@ def read_data(file_path: str) -> Tuple[npt.NDArray, npt.NDArray]:
if __name__ == "__main__": if __name__ == "__main__":
# A test program to see that the file is read correctly.
cities, data = read_data("./european_cities.csv") cities, data = read_data("./european_cities.csv")
print(cities) print(cities)
print(data) print(data)

View File

@ -1,13 +1,14 @@
import time import time
from itertools import permutations from itertools import permutations
from typing import Callable, Tuple from typing import Tuple
import numpy as np
import numpy.typing as npt import numpy.typing as npt
from common import plot_plan, read_data from common import plot_plan, read_data
def exhaustive_search(distances: npt.NDArray) -> Tuple[float, Tuple]: def exhaustive_search(distances: npt.NDArray) -> Tuple[float, npt.NDArray]:
"""An implementation of exhaustive search. """An implementation of exhaustive search.
This implementation takes a permutation iterator, then maps each This implementation takes a permutation iterator, then maps each
@ -19,8 +20,8 @@ def exhaustive_search(distances: npt.NDArray) -> Tuple[float, Tuple]:
distances (npt.NDArray): An array containing distances to travel. distances (npt.NDArray): An array containing distances to travel.
Returns: Returns:
A tuple containing the shortest travel distance and its corresponding Tuple[float, npt.NDArray] A tuple containing the shortest travel
permutation. distance and its corresponding permutation.
""" """
size = len(distances) size = len(distances)
@ -29,7 +30,7 @@ def exhaustive_search(distances: npt.NDArray) -> Tuple[float, Tuple]:
map( # Map the permutation array to contain tuples (distance, permutation) map( # Map the permutation array to contain tuples (distance, permutation)
lambda perm: ( lambda perm: (
sum([distances[perm[i - 1], perm[i]] for i in range(size)]), sum([distances[perm[i - 1], perm[i]] for i in range(size)]),
perm, np.array(perm),
), ),
permutations(range(size)), permutations(range(size)),
), ),

View File

@ -1,53 +1,66 @@
import copy
from typing import Tuple from typing import Tuple
import numpy as np import numpy as np
import numpy.typing as npt import numpy.typing as npt
import copy
from common import plot_plan, read_data from common import indexes_to_cities, plot_plan, read_data
original_perm = None
def hill_climbing(distances: npt.NDArray) -> Tuple[float, npt.NDArray]: def hill_climbing(distances: npt.NDArray) -> Tuple[float, npt.NDArray]:
size = len(distances) """A simple hill climbing algorithm.
perm = np.arange(size)
np.random.shuffle(perm)
print(perm) The algorithm starts on a random permutation and attempts to improve
global original_perm the circuit by trying to switch neighboring elements. Each iteration
original_perm = copy.deepcopy(perm) tries to switch adjacent neighbors and sees which one yields the largest
current_route: float = np.sum([distances[perm[i - 1], perm[i]] for i in range(size)]) improvement.
found_improvement = True
while found_improvement:
found_improvement = False
tmp_improvement: float = current_route
improvement_index: int = -1
for i in range(size):
perm[i - 1], perm[i] = perm[i], perm[i - 1]
tmp_route: float = np.sum([distances[perm[i - 1], perm[i]] for i in range(size)])
if tmp_route < tmp_improvement:
tmp_improvement = tmp_route
improvement_index = i
found_improvement = True
perm[i - 1], perm[i] = perm[i], perm[i - 1] Args:
distances npt.NDArray: A matrix containing the distances between cities.
if found_improvement: Returns:
current_route = tmp_improvement Tuple[float, npt.NDArray] A tuple containing the distance of the
perm[improvement_index - 1], perm[improvement_index] = ( solution and the solution itself.
perm[improvement_index],
perm[improvement_index - 1], """
size: int = len(distances) # The size of the permutation array
perm: npt.NDArray = np.arange(size) # Create an array from 0..size
np.random.shuffle(perm) # Get random permutation
# Get the distance of the random permutation
current_distance: float = np.sum(
[distances[perm[i - 1], perm[i]] for i in range(size)]
) )
print(perm) found_improvement: bool = True
return (current_route, perm) while found_improvement:
found_improvement = False # Assume we haven't found an improvement
tmp_distance: float = current_distance
# Try to find an improvement
for i in range(size):
perm[[i - 1, i]] = perm[[i, i - 1]] # Swap i - 1 and i
tmp_distance: float = np.sum(
[distances[perm[i - 1], perm[i]] for i in range(size)]
)
if tmp_distance < current_distance:
current_distance = tmp_distance
found_improvement = True
break
perm[[i - 1, i]] = perm[[i, i - 1]] # Swap back i - 1 and i
return (current_distance, perm)
if __name__ == "__main__": if __name__ == "__main__":
cities, data = read_data("./european_cities.csv") cities, data = read_data("./european_cities.csv")
distance, perm = hill_climbing(data[:10, :10]) distance, perm = hill_climbing(data[:10, :10])
plot_plan(list(map(lambda i: cities[i], list(perm)))) plot_plan(indexes_to_cities(perm, cities))
plot_plan(list(map(lambda i: cities[i], list(original_perm))))