permafixed ?

This commit is contained in:
Helldragon67 2024-07-16 09:01:58 +02:00
parent 4896e95617
commit 50bc8150c8
7 changed files with 50 additions and 958 deletions

View File

@ -1,290 +0,0 @@
import networkx as nx
from typing import List, Tuple
from geopy.distance import geodesic
from scipy.spatial import KDTree
import numpy as np
from itertools import combinations
from structs.landmarks import Landmark
from optimizer_v4 import print_res, link_list_simple, get_time
import os
import json
import heapq
# Heuristic function: distance to the goal
def heuristic(loc1: Tuple[float, float], loc2: Tuple[float, float], score2: int) -> float:
return geodesic(loc1, loc2).meters
# A* planner to search through the graph
def a_star2(G, start_id, end_id, max_walking_time, must_do_nodes, max_landmarks, detour, speed):
open_set = []
heapq.heappush(open_set, (0, start_id, 0, [start_id], set([start_id])))
best_path = None
max_attractiveness = 0
visited_must_do = set()
while open_set:
_, current_node, current_length, path, visited = heapq.heappop(open_set)
# If current node is a must_do node and hasn't been visited yet, mark it as visited
if current_node in must_do_nodes and current_node not in visited_must_do:
visited_must_do.add(current_node)
# Check if path includes all must_do nodes and reaches the end
if current_node == end_id and all(node in visited for node in must_do_nodes):
attractiveness = sum(G.nodes[node]['weight'] for node in path)
if attractiveness > max_attractiveness:
best_path = path
max_attractiveness = attractiveness
continue
if len(path) > max_landmarks + 1:
continue
for neighbor in G.neighbors(current_node):
if neighbor not in visited:
#distance = int(geodesic(G.nodes[current_node]['pos'], G.nodes[neighbor]['pos']).meters * detour / (speed * 16.6666))
distance = get_time(G.nodes[current_node]['pos'], G.nodes[neighbor]['pos'], detour, speed)
if current_length + distance <= max_walking_time:
new_path = path + [neighbor]
new_visited = visited | {neighbor}
estimated_cost = current_length + distance + get_time(G.nodes[neighbor]['pos'], G.nodes[end_id]['pos'], detour, speed)
heapq.heappush(open_set, (estimated_cost, neighbor, current_length + distance, new_path, new_visited))
# Check if all must_do_nodes have been visited
if all(node in visited_must_do for node in must_do_nodes):
return best_path, max_attractiveness
else:
return None, 0
def a_star(G, start_id, end_id, max_walking_time, must_do_nodes, max_landmarks, detour, speed):
open_set = []
heapq.heappush(open_set, (0, start_id, 0, [start_id], set([start_id])))
best_path = None
max_attractiveness = 0
visited_must_do = set()
while open_set:
_, current_node, current_length, path, visited = heapq.heappop(open_set)
# If current node is a must_do node and hasn't been visited yet, mark it as visited
if current_node in must_do_nodes and current_node not in visited_must_do:
visited_must_do.add(current_node)
# Check if path includes all must_do nodes and reaches the end
if current_node == end_id and all(node in visited for node in must_do_nodes):
attractiveness = sum(G.nodes[node]['weight'] for node in path)
if attractiveness > max_attractiveness:
best_path = path
max_attractiveness = attractiveness
continue
if len(path) > max_landmarks + 1:
continue
for neighbor in G.neighbors(current_node):
if neighbor not in visited:
distance = get_time(G.nodes[current_node]['pos'], G.nodes[neighbor]['pos'], detour, speed)
if current_length + distance <= max_walking_time:
new_path = path + [neighbor]
new_visited = visited | {neighbor}
estimated_cost = current_length + distance + heuristic(G.nodes[neighbor]['pos'], G.nodes[end_id]['pos'], G.nodes[neighbor]['weight'])
heapq.heappush(open_set, (estimated_cost, neighbor, current_length + distance, new_path, new_visited))
# Check if all must_do_nodes have been visited
if all(node in visited_must_do for node in must_do_nodes):
return best_path, max_attractiveness
else:
return None, 0
def find_path(G, start_id, finish_id, max_walking_time, must_do_nodes, max_landmarks) -> List[str]:
# Read the parameters from the file
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f :
parameters = json.loads(f.read())
detour = parameters['detour factor']
speed = parameters['average walking speed']
best_path, _ = a_star(G, start_id, finish_id, max_walking_time, must_do_nodes, max_landmarks, detour, speed)
return best_path if best_path else []
# Function to dynamically adjust theta
def adjust_theta(num_nodes, theta_opt, target_ratio=2.0):
# Start with an initial guess
initial_theta = theta_opt
# Adjust theta to aim for the target ratio of edges to nodes
return initial_theta / (num_nodes ** (1 / target_ratio))
# Create a graph using NetworkX and generate the path without must_do
def generate_path(landmarks: List[Landmark], max_walking_time: float, max_landmarks: int) -> List[List[Landmark]]:
# Read the parameters from the file
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f :
parameters = json.loads(f.read())
detour = parameters['detour factor']
speed = parameters['average walking speed']
landmap = {}
pos_dict = {}
weight_dict = {}
G = nx.Graph()
# Add nodes to the graph with attractiveness
for i, landmark in enumerate(landmarks):
pos_dict[i] = landmark.location
weight_dict[i] = landmark.attractiveness
landmap[i] = landmark
G.add_node(i, pos=landmark.location, weight=landmark.attractiveness)
if landmark.name == 'start' :
start_id = i
elif landmark.name == 'finish' :
finish_id = i
coords = np.array(list(pos_dict.values()))
kdtree = KDTree(coords)
k = 5
if len(landmarks) <= k :
k = len(landmarks)-1
for node, coord in pos_dict.items():
indices = kdtree.query(coord, k + 1)[1] # k+1 because the closest neighbor is the node itself
for idx in indices[1:]: # skip the first one (itself)
neighbor = list(pos_dict.keys())[idx]
distance = get_time(coord, pos_dict[neighbor], detour, speed)
G.add_edge(node, neighbor, weight=distance)
print(f"Graph with {G.number_of_nodes()} nodes and {G.number_of_edges()} edges")
print("Start computing path...")
# Find the valid path using the greedy algorithm
valid_path = find_path(G, start_id, finish_id, max_walking_time, [], max_landmarks)
if not valid_path:
return [] # No valid path found
lis = [landmap[id] for id in valid_path]
lis, _ = link_list_simple(lis)
print_res(lis, len(landmarks))
return lis
# Create a graph using NetworkX and generate the path
def generate_path2(landmarks: List[Landmark], max_walking_time: float, max_landmarks: int) -> List[List[Landmark]]:
# Read the parameters from the file
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f :
parameters = json.loads(f.read())
detour = parameters['detour factor']
speed = parameters['average walking speed']
landmap = {}
pos_dict = {}
weight_dict = {}
must_do_nodes = []
G = nx.Graph()
# Add nodes to the graph with attractiveness
for i, landmark in enumerate(landmarks):
pos_dict[i] = landmark.location
weight_dict[i] = landmark.attractiveness
landmap[i] = landmark
if landmark.must_do :
must_do_nodes.append(i)
G.add_node(i, pos=landmark.location, weight=landmark.attractiveness)
if landmark.name == 'start' :
start_id = i
elif landmark.name == 'finish' :
finish_id = i
coords = np.array(list(pos_dict.values()))
kdtree = KDTree(coords)
k = 3
for node, coord in pos_dict.items():
indices = kdtree.query(coord, k + 1)[1] # k+1 because the closest neighbor is the node itself
for idx in indices[1:]: # skip the first one (itself)
neighbor = list(pos_dict.keys())[idx]
distance = get_time(coord, pos_dict[neighbor], detour, speed)
G.add_edge(node, neighbor, weight=distance)
# Add special edges between must_do nodes
if len(must_do_nodes) > 0 :
for node1, node2 in combinations(must_do_nodes, 2):
if not G.has_edge(node1, node2):
distance = get_time(G.nodes[node1]['pos'], G.nodes[node2]['pos'], detour, speed)
G.add_edge(node1, node2, weight=distance)
print(f"Graph with {G.number_of_nodes()} nodes and {G.number_of_edges()} edges")
print("Computing path...")
# Find the valid path using the greedy algorithm
valid_path = find_path(G, start_id, finish_id, max_walking_time, must_do_nodes, max_landmarks)
if not valid_path:
return [] # No valid path found
lis = [landmap[id] for id in valid_path]
lis, tot_dist = link_list_simple(lis)
print_res(lis, len(landmarks))
return lis
def correct_path(tour: List[Landmark]) -> List[Landmark] :
# Read the parameters from the file
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f :
parameters = json.loads(f.read())
detour = parameters['detour factor']
speed = parameters['average walking speed']
G = nx.Graph()
coords = []
landmap = {}
for i, landmark in enumerate(tour) :
coords.append(landmark.location)
landmap[i] = landmark
G.add_node(i, pos=landmark.location, weight=landmark.attractiveness)
kdtree = KDTree(coords)
k = 3
for node, coord in coords:
indices = kdtree.query(coord, k + 1)[1] # k+1 because the closest neighbor is the node itself
for idx in indices[1:]: # skip the first one (itself)
neighbor = list(coords)[idx]
distance = get_time(coord, coords[neighbor], detour, speed)
G.add_edge(node, neighbor, weight=distance)
path = nx.approximation.traveling_salesman_problem(G, weight='weight', cycle=True)
lis = [landmap[id] for id in path]
lis, tot_dist = link_list_simple(lis)
print_res(lis, len(tour))
return path

View File

@ -1,326 +0,0 @@
import numpy as np
import json, os
from typing import List, Tuple
from itertools import combinations
from scipy.optimize import linprog
from math import radians, sin, cos, acos
from shapely import Polygon
from geopy.distance import geodesic
from structs.landmarks import Landmark
# Function to print the result
def print_res(L: List[Landmark], L_tot):
if len(L) == L_tot:
print('\nAll landmarks can be visited within max_steps, the following order is suggested : ')
else :
print('Could not visit all the landmarks, the following order is suggested : ')
dist = 0
for elem in L :
if elem.time_to_reach_next is not None :
print('- ' + elem.name + ', time to reach next = ' + str(elem.time_to_reach_next))
dist += elem.time_to_reach_next
else :
print('- ' + elem.name)
print("\nMinutes walked : " + str(dist))
print(f"Visited {len(L)-2} out of {L_tot-2} landmarks")
# Function that returns the distance in meters from one location to another
def get_time(p1: Tuple[float, float], p2: Tuple[float, float], detour: float, speed: float) :
# Compute the straight-line distance in m
if p1 == p2 :
return 0
else:
#dist = 1000 * 6371.01 * acos(sin(radians(p1[0]))*sin(radians(p2[0])) + cos(radians(p1[0]))*cos(radians(p2[0]))*cos(radians(p1[1]) - radians(p2[1])))
dist = geodesic(p1, p2).meters
# Consider the detour factor for average city to determine walking distance (in m)
walk_dist = dist*detour
# Time to walk this distance (in minutes)
walk_time = walk_dist/speed*(60/1000)
"""if walk_time > 15 :
walk_time = 5*round(walk_time/5)
else :
walk_time = round(walk_time)"""
return round(walk_time)
# Checks if the path is connected, returns a circle if it finds one and the RESULT
def is_connected(resx) -> bool:
N = len(resx) # length of res
L = int(np.sqrt(N)) # number of landmarks. CAST INTO INT but should not be a problem because N = L**2 by def.
resx = resx[:L*L]
# first round the results to have only 0-1 values
for i, elem in enumerate(resx):
resx[i] = round(elem)
n_edges = resx.sum() # number of edges
nonzeroind = np.nonzero(resx)[0] # the return is a little funny so I use the [0]
nonzero_tup = np.unravel_index(nonzeroind, (L,L))
ind_a = nonzero_tup[0].tolist()
ind_b = nonzero_tup[1].tolist()
edges = []
edges_visited = []
vertices_visited = []
edge1 = (ind_a[0], ind_b[0])
edges_visited.append(edge1)
vertices_visited.append(edge1[0])
for i, a in enumerate(ind_a) :
edges.append((a, ind_b[i])) # Create the list of edges
remaining = edges
remaining.remove(edge1)
break_flag = False
while len(remaining) > 0 and not break_flag:
for edge2 in remaining :
if edge2[0] == edge1[1] :
if edge1[1] in vertices_visited :
edges_visited.append(edge2)
break_flag = True
break
else :
vertices_visited.append(edge1[1])
edges_visited.append(edge2)
remaining.remove(edge2)
edge1 = edge2
elif edge1[1] == L-1 or edge1[1] in vertices_visited:
break_flag = True
break
vertices_visited.append(edge1[1])
if len(vertices_visited) == n_edges +1 :
return vertices_visited, []
else:
return vertices_visited, edges_visited
# Computes the time to reach from each landmark to the next
def link_list(order: List[int], landmarks: List[Landmark])->List[Landmark] :
# Read the parameters from the file
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f :
parameters = json.loads(f.read())
detour_factor = parameters['detour factor']
speed = parameters['average walking speed']
L = []
j = 0
total_dist = 0
while j < len(order)-1 :
elem = landmarks[order[j]]
next = landmarks[order[j+1]]
d = get_time(elem.location, next.location, detour_factor, speed)
elem.time_to_reach_next = d
if elem.name not in ['start', 'finish'] :
elem.must_do = True
L.append(elem)
j += 1
total_dist += d
L.append(next)
return L, total_dist
# Constraint to respect only one travel per landmark. Also caps the total number of visited landmarks
def respect_number(L:int, A_ub, b_ub):
ones = [1]*L
zeros = [0]*L
for i in range(L) :
h = zeros*i + ones + zeros*(L-1-i) + [0]*(L-1)
A_ub.append(h)
b_ub.append(1)
# Read the parameters from the file
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f :
parameters = json.loads(f.read())
max_landmarks = parameters['max landmarks']
A_ub.append(ones*L + [0]*(L-1))
b_ub.append(max_landmarks+1)
return A_ub, b_ub
def solve_optimizationv3(landmarks, max_walking_time):
L = len(landmarks)
# Read the parameters from the file
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f :
parameters = json.loads(f.read())
detour = parameters['detour factor']
speed = parameters['average walking speed']
# Create distance matrix
A = np.zeros((L, L))
for i in range(L):
for j in range(L):
if i != j:
A[i, j] = get_time(landmarks[i].location, landmarks[j].location, detour, speed)
# Define the linear program
c = np.hstack((A.flatten(), [0]*(L-1)))
bounds = [(0, 1) for _ in range(L*L+L-1)]
# Flow conservation constraints
A_eq = []
b_eq = []
# Each node (except start and end) has one incoming and one outgoing edge
for i in range(L):
if i == 0 or i == L-1:
continue
A_eq.append([1 if j // L == i else 0 for j in range(L*L)] + [0]*(L-1))
b_eq.append(1)
A_eq.append([1 if j % L == i else 0 for j in range(L*L)] + [0]*(L-1))
b_eq.append(1)
# Start node constraint
A_eq.append([1 if j // L == 0 else 0 for j in range(L*L)] + [0]*(L-1))
b_eq.append(1)
# End node constraint
A_eq.append([1 if j % L == L-1 else 0 for j in range(L*L)] + [0]*(L-1))
b_eq.append(1)
# Subtour elimination constraints
A_ub = []
b_ub = []
# u_i - u_j + L*x_ij <= L-1 for all i != j
for i in range(1, L):
for j in range(1, L):
if i != j:
constraint = [0] * (L * L + L - 1)
constraint[i * L + j] = L
constraint[j * L + i] = -L
A_ub.append(constraint)
b_ub.append(L - 1)
A_ub, b_ub = respect_number(L, A_ub, b_ub) # Respect max number of visits (no more possible stops than landmarks).
# Convert constraints to numpy arrays
A_eq = np.array(A_eq)
A_ub = np.array(A_ub)
b_ub = np.array(b_ub)
b_eq = np.array(b_eq)
# Solve the linear program
result = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq, bounds=bounds, method='highs')
if result.success:
x = result.x[:L*L].reshape((L, L))
path = []
for i in range(L):
for j in range(L):
if x[i, j] > 0.5:
path.append((i, j))
print(f"({i}, {j})")
order, _ = is_connected(result.x)
L, _ = link_list(order, landmarks)
print_res(L, len(landmarks))
print("\nTotal score : " + str(int(-result.fun)))
return L
else:
print("no results")
return []
# Main optimization pipeline
# def solve_optimization (landmarks :List[Landmark], max_steps: int, printing_details: bool) :
# L = len(landmarks)
# # SET CONSTRAINTS FOR INEQUALITY
# #c, A_ub, b_ub = init_ub_dist(landmarks, max_steps) # Add the distances from each landmark to the other
# A_ub, b_ub = respect_number(L, A_ub, b_ub) # Respect max number of visits (no more possible stops than landmarks).
# #A_ub, b_ub = break_sym(L, A_ub, b_ub) # break the 'zig-zag' symmetry
# #A_ub, b_ub = prevent_subtours(L, A_ub, b_ub)
# # SET CONSTRAINTS FOR EQUALITY
# #A_eq, b_eq = init_eq_not_stay(L) # Force solution not to stay in same place
# #A_eq, b_eq = respect_user_mustsee(landmarks, A_eq, b_eq) # Check if there are user_defined must_see. Also takes care of start/goal
# #A_eq, b_eq = respect_start_finish(L, A_eq, b_eq) # Force start and finish positions
# #A_eq, b_eq = respect_order(L, A_eq, b_eq) # Respect order of visit (only works when max_steps is limiting factor)
# # SET BOUNDS FOR DECISION VARIABLE (x can only be 0 or 1)
# x_bounds = [(0, 1)]*(L*L + L)
# # Solve linear programming problem
# res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq = b_eq, bounds=x_bounds, method='highs', integrality=3)
# # Raise error if no solution is found
# if not res.success :
# raise ArithmeticError("No solution could be found, the problem is overconstrained. Please adapt your must_dos")
# # If there is a solution, we're good to go, just check for connectiveness
# else :
# order, circle = is_connected(res.x)
# i = 0
# timeout = 80
# """while len(circle) != 0 and i < timeout:
# A_ub, b_ub = prevent_config(res.x, A_ub, b_ub)
# #A_ub, b_ub = break_cricle(order, len(landmarks), A_ub, b_ub)
# res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq = b_eq, bounds=x_bounds, method='highs', integrality=3)
# if not res.success :
# raise ArithmeticError(f"No solution found after {timeout} iterations.")
# order, circle = is_connected(res.x)
# if len(circle) == 0 :
# break
# print(i)
# i += 1
# if i == timeout :
# raise TimeoutError(f"Optimization took too long. No solution found after {timeout} iterations.")
# """
# # Add the times to reach and stop optimizing
# L, total_dist = link_list(order, landmarks)
# if printing_details is True :
# if i != 0 :
# print(f"Neded to recompute paths {i} times because of unconnected loops...")
# print_res(L, len(landmarks))
# print("\nTotal score : " + str(int(-res.fun)))
# return L

View File

@ -11,12 +11,9 @@ from structs.landmarks import Landmark
# Function to print the result
def print_res(L: List[Landmark], L_tot):
def print_res(L: List[Landmark]):
if len(L) == L_tot:
print('\nAll landmarks can be visited within max_steps, the following order is suggested : ')
else :
print('Could not visit all the landmarks, the following order is suggested : ')
print('The following order is suggested : ')
dist = 0
for elem in L :
@ -27,7 +24,7 @@ def print_res(L: List[Landmark], L_tot):
print('- ' + elem.name)
print("\nMinutes walked : " + str(dist))
print(f"Visited {len(L)-2} out of {L_tot-2} landmarks")
print(f"Visited {len(L)-2} landmarks")
# Prevent the use of a particular solution
@ -178,7 +175,7 @@ def init_ub_dist(landmarks: List[Landmark], max_steps: int):
closest = sorted(dist_table)[:22]
for i, dist in enumerate(dist_table) :
if dist not in closest :
dist_table[i] = 10000000
dist_table[i] = 32700
A_ub += dist_table
c = c*len(landmarks)
@ -243,7 +240,7 @@ def init_eq_not_stay(L: int):
if j == i :
l[j + i*L] = 1
l = np.array(np.array(l))
l = np.array(np.array(l), dtype=np.int8)
return [l], [0]
@ -374,23 +371,23 @@ def solve_optimization (landmarks :List[Landmark], max_steps: int, printing_deta
# SET CONSTRAINTS FOR INEQUALITY
c, A_ub, b_ub = init_ub_dist(landmarks, max_steps) # Add the distances from each landmark to the other
A, b = respect_number(L, max_landmarks) # Respect max number of visits (no more possible stops than landmarks).
A_ub = np.vstack((A_ub, A))
A_ub = np.vstack((A_ub, A), dtype=np.int16)
b_ub += b
A, b = break_sym(L) # break the 'zig-zag' symmetry
A_ub = np.vstack((A_ub, A))
A_ub = np.vstack((A_ub, A), dtype=np.int16)
b_ub += b
# SET CONSTRAINTS FOR EQUALITY
A_eq, b_eq = init_eq_not_stay(L) # Force solution not to stay in same place
A, b = respect_user_must_do(landmarks) # Check if there are user_defined must_see. Also takes care of start/goal
A_eq = np.vstack((A_eq, A))
A_eq = np.vstack((A_eq, A), dtype=np.int8)
b_eq += b
A, b = respect_start_finish(L) # Force start and finish positions
A_eq = np.vstack((A_eq, A))
A_eq = np.vstack((A_eq, A), dtype=np.int8)
b_eq += b
A, b = respect_order(L) # Respect order of visit (only works when max_steps is limiting factor)
A_eq = np.vstack((A_eq, A))
A_eq = np.vstack((A_eq, A), dtype=np.int8)
b_eq += b
# SET BOUNDS FOR DECISION VARIABLE (x can only be 0 or 1)
@ -419,6 +416,9 @@ def solve_optimization (landmarks :List[Landmark], max_steps: int, printing_deta
A_eq = np.vstack((A_eq, A))
b_eq += b
res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq = b_eq, bounds=x_bounds, method='highs', integrality=3)
if not res.success :
print("Solving failed because of overconstrained problem")
return None
order, circles = is_connected(res.x)
#nodes, edges = is_connected(res.x)
if circles is None :
@ -435,7 +435,7 @@ def solve_optimization (landmarks :List[Landmark], max_steps: int, printing_deta
if printing_details is True :
if i != 0 :
print(f"Neded to recompute paths {i} times because of unconnected loops...")
print_res(L, len(landmarks))
print_res(L)
print("\nTotal score : " + str(int(-res.fun)))
return L

View File

@ -1,5 +1,5 @@
{
"city bbox side" : 10,
"city bbox side" : 3,
"radius close to" : 50,
"church coeff" : 0.9,
"park coeff" : 1.2,

View File

@ -47,7 +47,7 @@ def is_close_to(location1: Tuple[float], location2: Tuple[float]):
#return (round(location1[0], 3), round(location1[1], 3)) == (round(location2[0], 3), round(location2[1], 3))
# Rearrange some landmarks in the order of visit
# Rearrange some landmarks in the order of visit to group visit
def rearrange(landmarks: List[Landmark]) -> List[Landmark]:
i = 1
@ -171,45 +171,58 @@ def fix_using_polygon(tour: List[Landmark])-> List[Landmark] :
# Append the finish back and correct the time to reach
better_tour.append(tour[-1])
# Rearrange only if polygon
better_tour = rearrange(better_tour)
# Rearrange only if polygon still not simple
if not better_tour_poly.is_simple :
better_tour = rearrange(better_tour)
return better_tour
# Second stage of the optimization. Use linear programming again to refine the path
def refine_optimization(landmarks: List[Landmark], base_tour: List[Landmark], max_time: int, print_infos: bool) -> List[Landmark] :
def refine_optimization(landmarks: List[Landmark], base_tour: List[Landmark], max_time: int, detour: int, print_infos: bool) -> List[Landmark] :
# Read from the file
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f :
parameters = json.loads(f.read())
max_landmarks = parameters['max landmarks'] + 4
minor_landmarks = get_minor_landmarks(landmarks, base_tour, 200)
# No need to refine if no detour is taken
# if detour == 0 :
if False :
new_tour = base_tour
else :
minor_landmarks = get_minor_landmarks(landmarks, base_tour, 200)
if print_infos : print("Using " + str(len(minor_landmarks)) + " minor landmarks around the predicted path")
if print_infos : print("Using " + str(len(minor_landmarks)) + " minor landmarks around the predicted path")
# full set of visitable landmarks
full_set = base_tour[:-1] + minor_landmarks # create full set of possible landmarks (without finish)
full_set.append(base_tour[-1]) # add finish back
# full set of visitable landmarks
full_set = base_tour[:-1] + minor_landmarks # create full set of possible landmarks (without finish)
full_set.append(base_tour[-1]) # add finish back
# get a new tour
new_tour = solve_optimization(full_set, max_time, False, max_landmarks)
# get a new tour
new_tour = solve_optimization(full_set, max_time+detour, False, max_landmarks)
if new_tour is None :
new_tour = base_tour
# Link the new tour
new_tour, new_dist = link_list_simple(new_tour)
# if the tour contains only one landmark, return
# If the tour contains only one landmark, return
if len(new_tour) < 4 :
return new_tour
# find shortest path using the nearest neighbor heuristic
# Find shortest path using the nearest neighbor heuristic
better_tour, better_poly = find_shortest_path_through_all_landmarks(new_tour)
# Fix the tour using Polygons if the path looks weird
if base_tour[0].location == base_tour[-1].location and not better_poly.is_valid :
better_tour = fix_using_polygon(better_tour)
# Link the tour again
better_tour, better_dist = link_list_simple(better_tour)
# Choose the better tour depending on walked distance
if new_dist < better_dist :
final_tour = new_tour
else :
@ -217,7 +230,7 @@ def refine_optimization(landmarks: List[Landmark], base_tour: List[Landmark], ma
if print_infos :
print("\n\n\nRefined tour (result of second stage optimization): ")
print_res(final_tour, len(full_set))
print_res(final_tour)
total_score = 0
for elem in final_tour :
total_score += elem.attractiveness

View File

@ -1,306 +0,0 @@
from collections import defaultdict
from heapq import heappop, heappush
from itertools import permutations
import os, json
from shapely import buffer, LineString, Point, Polygon, MultiPoint, convex_hull, concave_hull, LinearRing
from typing import List, Tuple
from scipy.spatial import KDTree
from math import pi
import networkx as nx
from structs.landmarks import Landmark
from landmarks_manager import take_most_important
from optimizer_v4 import solve_optimization, link_list_simple, print_res, get_time
from optimizer_v2 import generate_path, generate_path2
def create_corridor(landmarks: List[Landmark], width: float) :
corrected_width = (180*width)/(6371000*pi)
path = create_linestring(landmarks)
obj = buffer(path, corrected_width, join_style="mitre", cap_style="square", mitre_limit=2)
return obj
def create_linestring(landmarks: List[Landmark])->List[Point] :
points = []
for landmark in landmarks :
points.append(Point(landmark.location))
return LineString(points)
def is_in_area(area: Polygon, coordinates) -> bool :
point = Point(coordinates)
return point.within(area)
def is_close_to(location1: Tuple[float], location2: Tuple[float]):
"""Determine if two locations are close by rounding their coordinates to 3 decimals."""
absx = abs(location1[0] - location2[0])
absy = abs(location1[1] - location2[1])
return absx < 0.001 and absy < 0.001
#return (round(location1[0], 3), round(location1[1], 3)) == (round(location2[0], 3), round(location2[1], 3))
def rearrange(landmarks: List[Landmark]) -> List[Landmark]:
i = 1
while i < len(landmarks):
j = i+1
while j < len(landmarks):
if is_close_to(landmarks[i].location, landmarks[j].location) and landmarks[i].name not in ['start', 'finish'] and landmarks[j].name not in ['start', 'finish']:
# If they are not adjacent, move the j-th element to be adjacent to the i-th element
if j != i + 1:
landmarks.insert(i + 1, landmarks.pop(j))
break # Move to the next i-th element after rearrangement
j += 1
i += 1
return landmarks
def find_shortest_path_through_all_landmarks(landmarks: List[Landmark]) -> Tuple[List[Landmark], Polygon]:
# Read from data
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f :
parameters = json.loads(f.read())
detour = parameters['detour factor']
speed = parameters['average walking speed']
# Step 1: Find 'start' and 'finish' landmarks
start_idx = next(i for i, lm in enumerate(landmarks) if lm.name == 'start')
finish_idx = next(i for i, lm in enumerate(landmarks) if lm.name == 'finish')
start_landmark = landmarks[start_idx]
finish_landmark = landmarks[finish_idx]
# Step 2: Create a list of unvisited landmarks excluding 'start' and 'finish'
unvisited_landmarks = [lm for i, lm in enumerate(landmarks) if i not in [start_idx, finish_idx]]
# Step 3: Initialize the path with the 'start' landmark
path = [start_landmark]
coordinates = [landmarks[start_idx].location]
current_landmark = start_landmark
# Step 4: Use nearest neighbor heuristic to visit all landmarks
while unvisited_landmarks:
nearest_landmark = min(unvisited_landmarks, key=lambda lm: get_time(current_landmark.location, lm.location, detour, speed))
path.append(nearest_landmark)
coordinates.append(nearest_landmark.location)
current_landmark = nearest_landmark
unvisited_landmarks.remove(nearest_landmark)
# Step 5: Finally add the 'finish' landmark to the path
path.append(finish_landmark)
coordinates.append(landmarks[finish_idx].location)
path_poly = Polygon(coordinates)
return path, path_poly
def get_minor_landmarks(all_landmarks: List[Landmark], visited_landmarks: List[Landmark], width: float) -> List[Landmark] :
second_order_landmarks = []
visited_names = []
area = create_corridor(visited_landmarks, width)
for visited in visited_landmarks :
visited_names.append(visited.name)
for landmark in all_landmarks :
if is_in_area(area, landmark.location) and landmark.name not in visited_names:
second_order_landmarks.append(landmark)
return take_most_important(second_order_landmarks, len(visited_landmarks))
"""def refine_optimization(landmarks: List[Landmark], base_tour: List[Landmark], max_time: int, print_infos: bool) -> List[Landmark] :
minor_landmarks = get_minor_landmarks(landmarks, base_tour, 200)
if print_infos : print("There are " + str(len(minor_landmarks)) + " minor landmarks around the predicted path")
full_set = base_tour[:-1] + minor_landmarks # create full set of possible landmarks (without finish)
full_set.append(base_tour[-1]) # add finish back
new_tour = solve_optimization(full_set, max_time, print_infos)
return new_tour"""
def fix_using_polygon(tour: List[Landmark])-> List[Landmark] :
coords = []
coords_dict = {}
for landmark in tour :
coords.append(landmark.location)
if landmark.name != 'finish' :
coords_dict[landmark.location] = landmark
tour_poly = Polygon(coords)
better_tour_poly = tour_poly.buffer(0)
try :
xs, ys = better_tour_poly.exterior.xy
if len(xs) != len(tour) :
better_tour_poly = concave_hull(MultiPoint(coords)) # Create concave hull with "core" of tour leaving out start and finish
xs, ys = better_tour_poly.exterior.xy
except :
better_tour_poly = concave_hull(MultiPoint(coords)) # Create concave hull with "core" of tour leaving out start and finish
xs, ys = better_tour_poly.exterior.xy
# reverse the xs and ys
xs.reverse()
ys.reverse()
better_tour = [] # List of ordered visit
name_index = {} # Maps the name of a landmark to its index in the concave polygon
# Loop through the polygon and generate the better (ordered) tour
for i,x in enumerate(xs[:-1]) :
y = ys[i]
better_tour.append(coords_dict[tuple((x,y))])
name_index[coords_dict[tuple((x,y))].name] = i
# Scroll the list to have start in front again
start_index = name_index['start']
better_tour = better_tour[start_index:] + better_tour[:start_index]
# Append the finish back and correct the time to reach
better_tour.append(tour[-1])
# Rearrange only if polygon
better_tour = rearrange(better_tour)
return better_tour
def refine_optimization(landmarks: List[Landmark], base_tour: List[Landmark], max_time: int, print_infos: bool) -> List[Landmark] :
# Read from the file
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f :
parameters = json.loads(f.read())
max_landmarks = parameters['max landmarks'] + 4
minor_landmarks = get_minor_landmarks(landmarks, base_tour, 200)
if print_infos : print("Using " + str(len(minor_landmarks)) + " minor landmarks around the predicted path")
# full set of visitable landmarks
full_set = base_tour[:-1] + minor_landmarks # create full set of possible landmarks (without finish)
full_set.append(base_tour[-1]) # add finish back
# get a new tour
new_tour = solve_optimization(full_set, max_time, False, max_landmarks)
new_tour, new_dist = link_list_simple(new_tour)
better_tour, better_poly = find_shortest_path_through_all_landmarks(new_tour)
if base_tour[0].location == base_tour[-1].location and not better_poly.is_valid :
better_tour = fix_using_polygon(better_tour)
better_tour, better_dist = link_list_simple(better_tour)
if new_dist < better_dist :
final_tour = new_tour
else :
final_tour = better_tour
if print_infos :
print("\n\n\nRefined tour (result of second stage optimization): ")
print_res(final_tour, len(full_set))
total_score = 0
for elem in final_tour :
total_score += elem.attractiveness
print("\nTotal score : " + str(total_score))
return final_tour
def refine_path(landmarks: List[Landmark], base_tour: List[Landmark], max_time: int, print_infos: bool) -> List[Landmark] :
print("\nRefining the base tour...")
# Read from the file
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f :
parameters = json.loads(f.read())
max_landmarks = parameters['max landmarks'] + 3
"""if len(base_tour)-2 >= max_landmarks :
return base_tour"""
minor_landmarks = get_minor_landmarks(landmarks, base_tour, 200)
if print_infos : print("Using " + str(len(minor_landmarks)) + " minor landmarks around the predicted path")
full_set = base_tour + minor_landmarks # create full set of possible landmarks
print("\nRefined tour (result of second stage optimization): ")
new_path = generate_path2(full_set, max_time, max_landmarks)
return new_path
# If a tour is not connected
def correct_path(tour: List[Landmark]) -> List[Landmark] :
# Read the parameters from the file
with open (os.path.dirname(os.path.abspath(__file__)) + '/parameters/optimizer.params', "r") as f :
parameters = json.loads(f.read())
detour = parameters['detour factor']
speed = parameters['average walking speed']
G = nx.Graph()
coords = []
landmap = {}
for i, landmark in enumerate(tour) :
coords.append(landmark.location)
landmap[i] = landmark
G.add_node(i, pos=landmark.location, weight=landmark.attractiveness)
kdtree = KDTree(coords)
k = 3
for node, coord in coords:
indices = kdtree.query(coord, k + 1)[1] # k+1 because the closest neighbor is the node itself
for idx in indices[1:]: # skip the first one (itself)
neighbor = list(coords)[idx]
distance = get_time(coord, coords[neighbor], detour, speed)
G.add_edge(node, neighbor, weight=distance)
path = nx.approximation.traveling_salesman_problem(G, weight='weight', cycle=True)
if len(path) != len(tour) :
print("nope")
lis = [landmap[id] for id in path]
lis, tot_dist = link_list_simple(lis)
print_res(lis, len(tour))
return path

View File

@ -31,7 +31,7 @@ def test4(coordinates: tuple[float, float]) -> List[Landmark]:
sightseeing=Preference(
name='sightseeing',
type=LandmarkType(landmark_type='sightseeing'),
score = 0),
score = 5),
nature=Preference(
name='nature',
type=LandmarkType(landmark_type='nature'),
@ -58,21 +58,22 @@ def test4(coordinates: tuple[float, float]) -> List[Landmark]:
landmarks_short.insert(0, start)
landmarks_short.append(finish)
max_walking_time = 50 # minutes
max_walking_time = 480 # minutes
detour = 0 # minutes
# First stage optimization
base_tour = solve_optimization(landmarks_short, max_walking_time, True)
# Second stage using linear optimization
if detour != 0 or len(base_tour) <= 4:
refined_tour = refine_optimization(landmarks, base_tour, max_walking_time+detour, True)
refined_tour = refine_optimization(landmarks, base_tour, max_walking_time, detour, True)
return refined_tour
test4(tuple((48.8344400, 2.3220540))) # Café Chez César
#test4(tuple((48.8344400, 2.3220540))) # Café Chez César
#test4(tuple((48.8375946, 2.2949904))) # Point random
#test4(tuple((47.377859, 8.540585))) # Zurich HB
#test4(tuple((45.7576485, 4.8330241))) # Lyon Bellecour
test4(tuple((45.7576485, 4.8330241))) # Lyon Bellecour
#test4(tuple((48.5848435, 7.7332974))) # Strasbourg Gare