upgraded optimizer

This commit is contained in:
Helldragon67 2024-07-08 01:20:17 +02:00
parent d4e964c5d4
commit 568e7bfbc4
3 changed files with 201 additions and 67 deletions

View File

@ -3,7 +3,7 @@ import json, os
from typing import List, Tuple from typing import List, Tuple
from scipy.optimize import linprog from scipy.optimize import linprog
from math import radians, sin, cos, acos from collections import defaultdict, deque
from geopy.distance import geodesic from geopy.distance import geodesic
from shapely import Polygon from shapely import Polygon
@ -31,7 +31,7 @@ def print_res(L: List[Landmark], L_tot):
# Prevent the use of a particular solution # Prevent the use of a particular solution
def prevent_config(resx, A_ub, b_ub) -> bool: def prevent_config(resx, A_ub, b_ub):
for i, elem in enumerate(resx): for i, elem in enumerate(resx):
resx[i] = round(elem) resx[i] = round(elem)
@ -58,8 +58,31 @@ def prevent_config(resx, A_ub, b_ub) -> bool:
return A_ub, b_ub return A_ub, b_ub
def prevent_circle(circle_vertices: list, L: int, A_eq: list, b_eq: list) :
l1 = [0]*L*L
l2 = [0]*L*L
for i, node in enumerate(circle_vertices[:-1]) :
next = circle_vertices[i+1]
l1[node*L + next] = 1
l2[next*L + node] = 1
s = circle_vertices[0]
g = circle_vertices[-1]
l1[g*L + s] = 1
l2[s*L + g] = 1
A_eq = np.vstack((A_eq, l1))
b_eq.append(0)
A_eq = np.vstack((A_eq, l2))
b_eq.append(0)
return A_eq, b_eq
# Prevent the possibility of a given solution bit # Prevent the possibility of a given solution bit
def break_cricle(circle_vertices: list, L: int, A_ub: list, b_ub: list) -> bool: def break_circle(circle_vertices: list, L: int, A_ub: list, b_ub: list):
if L-1 in circle_vertices : if L-1 in circle_vertices :
circle_vertices.remove(L-1) circle_vertices.remove(L-1)
@ -74,9 +97,26 @@ def break_cricle(circle_vertices: list, L: int, A_ub: list, b_ub: list) -> bool:
return A_ub, b_ub return A_ub, b_ub
"""
def break_circles(circle_edges_list: list, L: int, A_ub: list, b_ub: list):
for circle_edges in circle_edges_list :
if L-1 in circle_vertices :
circle_vertices.remove(L-1)
h = [0]*L*L
for i in range(L) :
if i in circle_vertices :
h[i*L:i*L+L] = [1]*L
A_ub = np.vstack((A_ub, h))
b_ub.append(len(circle_vertices)-1)
return A_ub, b_ub
"""
# Checks if the path is connected, returns a circle if it finds one and the RESULT # Checks if the path is connected, returns a circle if it finds one and the RESULT
def is_connected(resx) -> bool: def is_connected(resx):
# first round the results to have only 0-1 values # first round the results to have only 0-1 values
for i, elem in enumerate(resx): for i, elem in enumerate(resx):
@ -97,13 +137,15 @@ def is_connected(resx) -> bool:
edges_visited = [] edges_visited = []
vertices_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) : for i, a in enumerate(ind_a) :
edges.append((a, ind_b[i])) # Create the list of edges edges.append((a, ind_b[i])) # Create the list of edges
edge1 = (ind_a[0], ind_b[0])
del ind_a[0]
edges_visited.append(edge1)
vertices_visited.append(edge1[0])
remaining = edges remaining = edges
remaining.remove(edge1) remaining.remove(edge1)
@ -111,27 +153,135 @@ def is_connected(resx) -> bool:
while len(remaining) > 0 and not break_flag: while len(remaining) > 0 and not break_flag:
for edge2 in remaining : for edge2 in remaining :
if edge2[0] == edge1[1] : if edge2[0] == edge1[1] :
if edge1[1] in vertices_visited : """if edge1[1] in vertices_visited :
edges_visited.append(edge2) ind_b.remove(edge2[1])
#edges_visited.append(edge2)
break_flag = True break_flag = True
break break
else : else : """
vertices_visited.append(edge1[1]) vertices_visited.append(edge1[1])
edges_visited.append(edge2) ind_a.remove(edge2[0])
remaining.remove(edge2) ind_b.remove(edge2[0])
edge1 = edge2 #edges_visited.append(edge2)
remaining.remove(edge2)
edge1 = edge2
elif edge1[1] == L-1 or edge1[1] in vertices_visited: elif edge1[1] == L-1 or edge1[1] in vertices_visited:
break_flag = True ind_b.remove(edge1[1])
break break_flag = True
break
vertices_visited.append(edge1[1]) vertices_visited.append(edge1[1])
# Return order of visit if all good
if len(vertices_visited) == n_edges +1 : if len(vertices_visited) == n_edges +1 :
return vertices_visited, [] return vertices_visited, []
else:
return vertices_visited, edges_visited """edge1 = (ind_a[0], ind_b[0])
vertices_visited.clear()
edges_visited.clear()
edges_visited.append(edge1)
vertices_visited.append(edge1[0])
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])
"""
return vertices_visited, edges_visited
def is_connected2(resx) :
# first round the results to have only 0-1 values
for i, elem in enumerate(resx):
resx[i] = round(elem)
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.
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()
# print(f"ind_a = {ind_a}")
# print(f"ind_b = {ind_b}")
# Step 1: Create a graph representation
graph = defaultdict(list)
for a, b in zip(ind_a, ind_b):
graph[a].append(b)
# Step 2: Function to perform BFS/DFS to extract journeys
def get_journey(start):
journey_nodes = []
#journey_edges = []
visited = set()
stack = deque([start])
while stack:
node = stack.pop()
if node not in visited:
visited.add(node)
journey_nodes.append(node)
for neighbor in graph[node]:
#journey_edges.append((node, neighbor))
if neighbor not in visited:
stack.append(neighbor)
return journey_nodes#, journey_edges
# Step 3: Extract all journeys
all_journeys_nodes = []
#all_journeys_edges = []
visited_nodes = set()
for node in ind_a:
if node not in visited_nodes:
journey_nodes = get_journey(node)
all_journeys_nodes.append(journey_nodes)
#all_journeys_edges.append(journey_edges)
visited_nodes.update(journey_nodes)
for l in all_journeys_nodes :
if 0 in l :
order = l
all_journeys_nodes.remove(l)
break
if len(all_journeys_nodes) == 0 :
return order, None
return order, all_journeys_nodes
# Function that returns the distance in meters from one location to another # Function that returns the distance in meters from one location to another
@ -251,37 +401,30 @@ def respect_user_mustsee(landmarks: List[Landmark], A_eq: list, b_eq: list) :
for i, elem in enumerate(landmarks) : for i, elem in enumerate(landmarks) :
if elem.must_do is True and elem.name not in ['finish', 'start']: if elem.must_do is True and elem.name not in ['finish', 'start']:
l = [0]*L*L l = [0]*L*L
for j in range(L) : # sets the horizontal ones (go from) l[i*L:i*L+L] = [1]*L # set mandatory departures from landmarks tagged as 'must_do'
l[j +i*L] = 1 # sets the vertical ones (go to) double check if good
for k in range(L-1) :
l[k*L+L-1] = 1
A_eq = np.vstack((A_eq,l)) A_eq = np.vstack((A_eq,l))
b_eq.append(2) b_eq.append(1)
return A_eq, b_eq return A_eq, b_eq
# Constraint to ensure start at start and finish at goal # Constraint to ensure start at start and finish at goal
def respect_start_finish(L: int, A_eq: list, b_eq: list): def respect_start_finish(L: int, A_eq: list, b_eq: list):
ls = [1]*L + [0]*L*(L-1) # sets only horizontal ones for start (go from) l_start = [1]*L + [0]*L*(L-1) # sets departures only for start (horizontal ones)
ljump = [0]*L*L l_start[L-1] = 0 # prevents the jump from start to finish
ljump[L-1] = 1 # Prevent start finish jump l_goal = [0]*L*L # sets arrivals only for finish (vertical ones)
lg = [0]*L*L l_L = [0]*L*(L-1) + [1]*L # prevents arrivals at start and departures from goal
ll = [0]*L*(L-1) + [1]*L for k in range(L-1) : # sets only vertical ones for goal (go to)
for k in range(L-1) : # sets only vertical ones for goal (go to) l_L[k*L] = 1
ll[k*L] = 1 if k != 0 :
if k != 0 : # Prevent the shortcut start -> finish l_goal[k*L+L-1] = 1
lg[k*L+L-1] = 1
A_eq = np.vstack((A_eq,ls)) A_eq = np.vstack((A_eq,l_start))
A_eq = np.vstack((A_eq,ljump)) A_eq = np.vstack((A_eq,l_goal))
A_eq = np.vstack((A_eq,lg)) A_eq = np.vstack((A_eq,l_L))
A_eq = np.vstack((A_eq,ll))
b_eq.append(1) b_eq.append(1)
b_eq.append(0)
b_eq.append(1) b_eq.append(1)
b_eq.append(0) b_eq.append(0)
@ -393,15 +536,19 @@ def solve_optimization (landmarks :List[Landmark], max_steps: int, printing_deta
# If there is a solution, we're good to go, just check for connectiveness # If there is a solution, we're good to go, just check for connectiveness
else : else :
order, circle = is_connected(res.x) order, circles = is_connected2(res.x)
#nodes, edges = is_connected2(res.x)
i = 0 i = 0
timeout = 80 timeout = 80
while len(circle) != 0 and i < timeout: while circles is not None and i < timeout:
A_ub, b_ub = prevent_config(res.x, A_ub, b_ub) 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) #A_ub, b_ub = prevent_circle(order, len(landmarks), A_ub, b_ub)
for circle in circles :
A_ub, b_ub = prevent_circle(circle, 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) 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)
order, circle = is_connected(res.x) order, circles = is_connected2(res.x)
if len(circle) == 0 : #nodes, edges = is_connected2(res.x)
if circles is None :
break break
print(i) print(i)
i += 1 i += 1

View File

@ -197,6 +197,11 @@ def refine_optimization(landmarks: List[Landmark], base_tour: List[Landmark], ma
new_tour = solve_optimization(full_set, max_time, False, max_landmarks) new_tour = solve_optimization(full_set, max_time, False, max_landmarks)
new_tour, new_dist = link_list_simple(new_tour) new_tour, new_dist = link_list_simple(new_tour)
# if the tour contains only one landmark, return
if len(new_tour) < 4 :
return new_tour
# find shortest path using the nearest neighbor heuristic
better_tour, better_poly = find_shortest_path_through_all_landmarks(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 : if base_tour[0].location == base_tour[-1].location and not better_poly.is_valid :

View File

@ -7,8 +7,7 @@ from landmarks_manager import generate_landmarks
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from optimizer_v4 import solve_optimization from optimizer_v4 import solve_optimization
from optimizer_v2 import generate_path from refiner import refine_optimization
from refiner import refine_optimization, refine_path
from structs.landmarks import Landmark from structs.landmarks import Landmark
from structs.landmarktype import LandmarkType from structs.landmarktype import LandmarkType
from structs.preferences import Preferences, Preference from structs.preferences import Preferences, Preference
@ -81,7 +80,7 @@ def test4(coordinates: tuple[float, float]) -> List[Landmark]:
shopping=Preference( shopping=Preference(
name='shopping', name='shopping',
type=LandmarkType(landmark_type='shopping'), type=LandmarkType(landmark_type='shopping'),
score = 5)) score = 0))
# Create start and finish # Create start and finish
@ -97,36 +96,19 @@ def test4(coordinates: tuple[float, float]) -> List[Landmark]:
#write_data(landmarks, "landmarks_Lyon.txt") #write_data(landmarks, "landmarks_Lyon.txt")
# Insert start and finish to the landmarks list # Insert start and finish to the landmarks list
#landmarks_short = landmarks_short[:4]
landmarks_short.insert(0, start) landmarks_short.insert(0, start)
landmarks_short.append(finish) landmarks_short.append(finish)
# TODO use these parameters in another way
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']
max_walking_time = 120 # minutes max_walking_time = 120 # minutes
detour = 10 # minutes detour = 30 # minutes
# First stage optimization # First stage optimization
base_tour = solve_optimization(landmarks_short, max_walking_time, True) base_tour = solve_optimization(landmarks_short, max_walking_time, True)
#base_tour = solve_optimization(landmarks_short, max_walking_time, True)
# First stage using NetworkX
#base_tour = generate_path(landmarks_short, max_walking_time, max_landmarks)
# Second stage using linear optimization # Second stage using linear optimization
if detour != 0 : 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)
# Second stage using NetworkX
#refined_tour = refine_path(landmarks, base_tour, max_walking_time+detour, True)
# Use NetworkX again to correct to shortest path
#refined_tour = refine_path(landmarks, base_tour, max_walking_time+detour, True)
return refined_tour return refined_tour