upgraded optimizer
This commit is contained in:
parent
d4e964c5d4
commit
568e7bfbc4
@ -3,7 +3,7 @@ import json, os
|
||||
|
||||
from typing import List, Tuple
|
||||
from scipy.optimize import linprog
|
||||
from math import radians, sin, cos, acos
|
||||
from collections import defaultdict, deque
|
||||
from geopy.distance import geodesic
|
||||
from shapely import Polygon
|
||||
|
||||
@ -31,7 +31,7 @@ def print_res(L: List[Landmark], L_tot):
|
||||
|
||||
|
||||
# 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):
|
||||
resx[i] = round(elem)
|
||||
@ -58,8 +58,31 @@ def prevent_config(resx, A_ub, b_ub) -> bool:
|
||||
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
|
||||
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 :
|
||||
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
|
||||
|
||||
"""
|
||||
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
|
||||
def is_connected(resx) -> bool:
|
||||
def is_connected(resx):
|
||||
|
||||
# first round the results to have only 0-1 values
|
||||
for i, elem in enumerate(resx):
|
||||
@ -97,13 +137,15 @@ def is_connected(resx) -> bool:
|
||||
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
|
||||
|
||||
edge1 = (ind_a[0], ind_b[0])
|
||||
del ind_a[0]
|
||||
|
||||
edges_visited.append(edge1)
|
||||
vertices_visited.append(edge1[0])
|
||||
|
||||
remaining = edges
|
||||
remaining.remove(edge1)
|
||||
|
||||
@ -111,27 +153,135 @@ def is_connected(resx) -> bool:
|
||||
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)
|
||||
"""if edge1[1] in vertices_visited :
|
||||
ind_b.remove(edge2[1])
|
||||
#edges_visited.append(edge2)
|
||||
break_flag = True
|
||||
break
|
||||
else :
|
||||
vertices_visited.append(edge1[1])
|
||||
edges_visited.append(edge2)
|
||||
remaining.remove(edge2)
|
||||
edge1 = edge2
|
||||
else : """
|
||||
vertices_visited.append(edge1[1])
|
||||
ind_a.remove(edge2[0])
|
||||
ind_b.remove(edge2[0])
|
||||
#edges_visited.append(edge2)
|
||||
remaining.remove(edge2)
|
||||
edge1 = edge2
|
||||
|
||||
elif edge1[1] == L-1 or edge1[1] in vertices_visited:
|
||||
break_flag = True
|
||||
break
|
||||
ind_b.remove(edge1[1])
|
||||
break_flag = True
|
||||
break
|
||||
|
||||
vertices_visited.append(edge1[1])
|
||||
|
||||
|
||||
# Return order of visit if all good
|
||||
if len(vertices_visited) == n_edges +1 :
|
||||
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
|
||||
@ -251,37 +401,30 @@ def respect_user_mustsee(landmarks: List[Landmark], A_eq: list, b_eq: list) :
|
||||
for i, elem in enumerate(landmarks) :
|
||||
if elem.must_do is True and elem.name not in ['finish', 'start']:
|
||||
l = [0]*L*L
|
||||
for j in range(L) : # sets the horizontal ones (go from)
|
||||
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
|
||||
l[i*L:i*L+L] = [1]*L # set mandatory departures from landmarks tagged as 'must_do'
|
||||
|
||||
A_eq = np.vstack((A_eq,l))
|
||||
b_eq.append(2)
|
||||
b_eq.append(1)
|
||||
|
||||
return A_eq, b_eq
|
||||
|
||||
|
||||
# Constraint to ensure start at start and finish at goal
|
||||
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)
|
||||
ljump = [0]*L*L
|
||||
ljump[L-1] = 1 # Prevent start finish jump
|
||||
lg = [0]*L*L
|
||||
ll = [0]*L*(L-1) + [1]*L
|
||||
for k in range(L-1) : # sets only vertical ones for goal (go to)
|
||||
ll[k*L] = 1
|
||||
if k != 0 : # Prevent the shortcut start -> finish
|
||||
lg[k*L+L-1] = 1
|
||||
l_start = [1]*L + [0]*L*(L-1) # sets departures only for start (horizontal ones)
|
||||
l_start[L-1] = 0 # prevents the jump from start to finish
|
||||
l_goal = [0]*L*L # sets arrivals only for finish (vertical ones)
|
||||
l_L = [0]*L*(L-1) + [1]*L # prevents arrivals at start and departures from goal
|
||||
for k in range(L-1) : # sets only vertical ones for goal (go to)
|
||||
l_L[k*L] = 1
|
||||
if k != 0 :
|
||||
l_goal[k*L+L-1] = 1
|
||||
|
||||
|
||||
A_eq = np.vstack((A_eq,ls))
|
||||
A_eq = np.vstack((A_eq,ljump))
|
||||
A_eq = np.vstack((A_eq,lg))
|
||||
A_eq = np.vstack((A_eq,ll))
|
||||
A_eq = np.vstack((A_eq,l_start))
|
||||
A_eq = np.vstack((A_eq,l_goal))
|
||||
A_eq = np.vstack((A_eq,l_L))
|
||||
b_eq.append(1)
|
||||
b_eq.append(0)
|
||||
b_eq.append(1)
|
||||
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
|
||||
else :
|
||||
order, circle = is_connected(res.x)
|
||||
order, circles = is_connected2(res.x)
|
||||
#nodes, edges = is_connected2(res.x)
|
||||
i = 0
|
||||
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 = 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)
|
||||
order, circle = is_connected(res.x)
|
||||
if len(circle) == 0 :
|
||||
order, circles = is_connected2(res.x)
|
||||
#nodes, edges = is_connected2(res.x)
|
||||
if circles is None :
|
||||
break
|
||||
print(i)
|
||||
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, 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)
|
||||
|
||||
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 optimizer_v4 import solve_optimization
|
||||
from optimizer_v2 import generate_path
|
||||
from refiner import refine_optimization, refine_path
|
||||
from refiner import refine_optimization
|
||||
from structs.landmarks import Landmark
|
||||
from structs.landmarktype import LandmarkType
|
||||
from structs.preferences import Preferences, Preference
|
||||
@ -81,7 +80,7 @@ def test4(coordinates: tuple[float, float]) -> List[Landmark]:
|
||||
shopping=Preference(
|
||||
name='shopping',
|
||||
type=LandmarkType(landmark_type='shopping'),
|
||||
score = 5))
|
||||
score = 0))
|
||||
|
||||
|
||||
# Create start and finish
|
||||
@ -97,36 +96,19 @@ def test4(coordinates: tuple[float, float]) -> List[Landmark]:
|
||||
#write_data(landmarks, "landmarks_Lyon.txt")
|
||||
|
||||
# Insert start and finish to the landmarks list
|
||||
#landmarks_short = landmarks_short[:4]
|
||||
landmarks_short.insert(0, start)
|
||||
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
|
||||
detour = 10 # minutes
|
||||
|
||||
detour = 30 # minutes
|
||||
|
||||
# First stage optimization
|
||||
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
|
||||
if detour != 0 :
|
||||
if detour != 0 or len(base_tour) <= 4:
|
||||
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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user