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 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

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, 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 :

View File

@ -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