upgraded optimizer
This commit is contained in:
parent
d4e964c5d4
commit
568e7bfbc4
@ -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
|
||||||
|
@ -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 :
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user