This commit is contained in:
parent
82a864e57f
commit
2b31ce5f6b
BIN
backend/src/__pycache__/optimizer.cpython-310.pyc
Normal file
BIN
backend/src/__pycache__/optimizer.cpython-310.pyc
Normal file
Binary file not shown.
@ -1,23 +1,26 @@
|
|||||||
import fastapi
|
from optimizer import solve_optimization
|
||||||
from dataclasses import dataclass
|
from optimizer import landmark
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
# CONSTRAINT TO RESPECT MAX NUMBER OF STEPS
|
||||||
|
max_steps = 16
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
# Initialize all landmarks (+ start and goal). Order matters here
|
||||||
class Destination:
|
landmarks = []
|
||||||
name: str
|
landmarks.append(landmark("départ", -1, (0, 0)))
|
||||||
location: tuple
|
landmarks.append(landmark("tour eiffel", 99, (0,2))) # PUT IN JSON
|
||||||
attractiveness: int
|
landmarks.append(landmark("arc de triomphe", 99, (0,4)))
|
||||||
|
landmarks.append(landmark("louvre", 99, (0,6)))
|
||||||
|
landmarks.append(landmark("montmartre", 99, (0,10)))
|
||||||
|
landmarks.append(landmark("concorde", 99, (0,8)))
|
||||||
|
landmarks.append(landmark("arrivée", -1, (0, 0)))
|
||||||
|
|
||||||
|
|
||||||
|
visiting_order = solve_optimization(landmarks, max_steps, True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
d = Destination()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_route() -> list[Destination]:
|
|
||||||
return {"route": "Hello World"}
|
|
||||||
|
|
||||||
endpoint = ("/get_route", get_route)
|
|
||||||
end
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
fastapi.run()
|
main()
|
23
backend/src/main_example.py
Normal file
23
backend/src/main_example.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import fastapi
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Destination:
|
||||||
|
name: str
|
||||||
|
location: tuple
|
||||||
|
attractiveness: int
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
d = Destination()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_route() -> list[Destination]:
|
||||||
|
return {"route": "Hello World"}
|
||||||
|
|
||||||
|
endpoint = ("/get_route", get_route)
|
||||||
|
end
|
||||||
|
if __name__ == "__main__":
|
||||||
|
fastapi.run()
|
@ -11,9 +11,8 @@ class landmark :
|
|||||||
self.loc = loc
|
self.loc = loc
|
||||||
|
|
||||||
|
|
||||||
|
# Convert the solution of the optimization into the list of edges to follow. Order is taken into account
|
||||||
|
def untangle(resx: list) :
|
||||||
def untangle2(resx: list) :
|
|
||||||
N = len(resx) # length of res
|
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.
|
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
|
n_edges = resx.sum() # number of edges
|
||||||
@ -40,65 +39,31 @@ def untangle2(resx: list) :
|
|||||||
|
|
||||||
return order
|
return order
|
||||||
|
|
||||||
# Convert the result (edges from j to k like d_25 = edge between vertex 2 and vertex 5) into the list of indices corresponding to the landmarks
|
|
||||||
def untangle(resx: list) :
|
|
||||||
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_landmarks = resx.sum() # number of edges
|
|
||||||
visit_order = []
|
|
||||||
cnt = 0
|
|
||||||
|
|
||||||
if n_landmarks % 2 == 1 : # if odd number of visited checkpoints
|
|
||||||
for i in range(L) :
|
|
||||||
for j in range(L) :
|
|
||||||
if res[i*L + j] == 1 : # if index is 1
|
|
||||||
cnt += 1 # increment counter
|
|
||||||
if cnt % 2 == 1 : # if counter odd
|
|
||||||
visit_order.append(i)
|
|
||||||
visit_order.append(j)
|
|
||||||
else : # if even number of ones
|
|
||||||
for i in range(L) :
|
|
||||||
for j in range(L) :
|
|
||||||
if res[i*L + j] == 1 : # if index is one
|
|
||||||
cnt += 1 # increment counter
|
|
||||||
if j % (L-1) == 0 : # if last node
|
|
||||||
visit_order.append(j) # append only the last index
|
|
||||||
return visit_order # return
|
|
||||||
if cnt % 2 == 1 :
|
|
||||||
visit_order.append(i)
|
|
||||||
visit_order.append(j)
|
|
||||||
return visit_order
|
|
||||||
|
|
||||||
# Just to print the result
|
# Just to print the result
|
||||||
def print_res(res: list, landmarks: list, P) :
|
def print_res(res, landmarks: list, P) :
|
||||||
X = abs(res.x)
|
X = abs(res.x)
|
||||||
|
order = untangle(X)
|
||||||
|
|
||||||
N = int(np.sqrt(len(X)))
|
"""N = int(np.sqrt(len(X)))
|
||||||
for i in range(N):
|
for i in range(N):
|
||||||
print(X[i*N:i*N+N])
|
print(X[i*N:i*N+N])
|
||||||
|
print("Optimal value:", -res.fun) # Minimization, so we negate to get the maximum
|
||||||
order = untangle2(X)
|
print("Optimal point:", res.x)
|
||||||
|
for i,x in enumerate(X) : X[i] = round(x,0)
|
||||||
order_ideal = [0, 0, 0, 0, 0, 0, 1, 0]
|
print(order)"""
|
||||||
|
|
||||||
# print("Optimal value:", -res.fun) # Minimization, so we negate to get the maximum
|
|
||||||
# print("Optimal point:", res.x)
|
|
||||||
|
|
||||||
#for i,x in enumerate(X) : X[i] = round(x,0)
|
|
||||||
|
|
||||||
#print(order)
|
|
||||||
|
|
||||||
if (X.sum()+1)**2 == len(X) :
|
if (X.sum()+1)**2 == len(X) :
|
||||||
print('\nAll landmarks can be visited within max_steps, the following order is most likely not the fastest')
|
print('\nAll landmarks can be visited within max_steps, the following order is suggested : ')
|
||||||
else :
|
else :
|
||||||
print('Could not visit all the landmarks, the following order could be the fastest but not sure')
|
print('Could not visit all the landmarks, the following order is suggested : ')
|
||||||
print("Order of visit :")
|
|
||||||
for idx in order :
|
for idx in order :
|
||||||
print('- ' + landmarks[idx].name)
|
print('- ' + landmarks[idx].name)
|
||||||
|
|
||||||
steps = path_length(P, abs(res.x))
|
steps = path_length(P, abs(res.x))
|
||||||
print("\nSteps walked : " + str(steps))
|
print("\nSteps walked : " + str(steps))
|
||||||
|
|
||||||
|
return order
|
||||||
|
|
||||||
# Checks for cases of circular symmetry in the result
|
# Checks for cases of circular symmetry in the result
|
||||||
def has_circle(resx: list) :
|
def has_circle(resx: list) :
|
||||||
@ -164,8 +129,8 @@ def break_sym(landmarks, A_ub, b_ub):
|
|||||||
|
|
||||||
return A_ub, b_ub
|
return A_ub, b_ub
|
||||||
|
|
||||||
|
# Constraint to not have circular paths. Want to go from start -> finish without unconnected loops
|
||||||
def prevent_circle(landmarks, A_ub, b_ub, circle) :
|
def break_circle(landmarks, A_ub, b_ub, circle) :
|
||||||
N = len(landmarks)
|
N = len(landmarks)
|
||||||
l = [0]*N*N
|
l = [0]*N*N
|
||||||
|
|
||||||
@ -195,7 +160,7 @@ def respect_number(landmarks, A_ub, b_ub):
|
|||||||
print("\n")"""
|
print("\n")"""
|
||||||
return np.vstack((A_ub, T)), b_ub + [1]*len(landmarks)
|
return np.vstack((A_ub, T)), b_ub + [1]*len(landmarks)
|
||||||
|
|
||||||
# Constraint to tie the problem together and have a connected path
|
# Constraint to tie the problem together. Necessary but not sufficient to avoid circles
|
||||||
def respect_order(landmarks: list, A_eq, b_eq):
|
def respect_order(landmarks: list, A_eq, b_eq):
|
||||||
N = len(landmarks)
|
N = len(landmarks)
|
||||||
for i in range(N-1) : # Prevent stacked ones
|
for i in range(N-1) : # Prevent stacked ones
|
||||||
@ -294,68 +259,62 @@ def respect_user_mustsee(landmarks: list, A_eq: list, b_eq: list) :
|
|||||||
def path_length(P: list, resx: list) :
|
def path_length(P: list, resx: list) :
|
||||||
return np.dot(P, resx)
|
return np.dot(P, resx)
|
||||||
|
|
||||||
# Initialize all landmarks (+ start and goal). Order matters here
|
# Main optimization pipeline
|
||||||
landmarks = []
|
def solve_optimization (landmarks, max_steps, printing) :
|
||||||
landmarks.append(landmark("départ", -1, (0, 0)))
|
|
||||||
landmarks.append(landmark("tour eiffel", 99, (0,2))) # PUT IN JSON
|
|
||||||
landmarks.append(landmark("arc de triomphe", 99, (0,4)))
|
|
||||||
landmarks.append(landmark("louvre", 99, (0,6)))
|
|
||||||
landmarks.append(landmark("montmartre", 99, (0,10)))
|
|
||||||
landmarks.append(landmark("concorde", 99, (0,8)))
|
|
||||||
landmarks.append(landmark("arrivée", -1, (0, 0)))
|
|
||||||
|
|
||||||
|
# SET CONSTRAINTS FOR INEQUALITY
|
||||||
|
c, A_ub, b_ub = init_ub_dist(landmarks, max_steps) # Add the distances from each landmark to the other
|
||||||
|
P = A_ub # store the paths for later. Needed to compute path length
|
||||||
|
A_ub, b_ub = respect_number(landmarks, A_ub, b_ub) # Respect max number of visits.
|
||||||
|
|
||||||
|
# TODO : Problems with circular symmetry
|
||||||
|
A_ub, b_ub = break_sym(landmarks, A_ub, b_ub) # break the symmetry. Only use the upper diagonal values
|
||||||
|
|
||||||
# CONSTRAINT TO RESPECT MAX NUMBER OF STEPS
|
# SET CONSTRAINTS FOR EQUALITY
|
||||||
max_steps = 16
|
A_eq, b_eq = init_eq_not_stay(landmarks) # Force solution not to stay in same place
|
||||||
|
A_eq, b_eq, H = respect_user_mustsee(landmarks, A_eq, b_eq) # Check if there are user_defined must_see. Also takes care of start/goal
|
||||||
|
|
||||||
# SET CONSTRAINTS FOR INEQUALITY
|
A_eq, b_eq = respect_order(landmarks, A_eq, b_eq) # Respect order of visit (only works when max_steps is limiting factor)
|
||||||
c, A_ub, b_ub = init_ub_dist(landmarks, max_steps) # Add the distances from each landmark to the other
|
|
||||||
P = A_ub # store the paths for later. Needed to compute path length
|
|
||||||
A_ub, b_ub = respect_number(landmarks, A_ub, b_ub) # Respect max number of visits.
|
|
||||||
|
|
||||||
# TODO : Problems with circular symmetry
|
# Bounds for variables (x can only be 0 or 1)
|
||||||
A_ub, b_ub = break_sym(landmarks, A_ub, b_ub) # break the symmetry. Only use the upper diagonal values
|
x_bounds = [(0, 1)] * len(c)
|
||||||
|
|
||||||
# SET CONSTRAINTS FOR EQUALITY
|
# Solve linear programming problem
|
||||||
A_eq, b_eq = init_eq_not_stay(landmarks) # Force solution not to stay in same place
|
|
||||||
A_eq, b_eq, H = 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_order(landmarks, A_eq, b_eq) # Respect order of visit (only works when max_steps is limiting factor)
|
|
||||||
|
|
||||||
# Bounds for variables (x can only be 0 or 1)
|
|
||||||
x_bounds = [(0, 1)] * len(c)
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
circle = has_circle(res.x)
|
|
||||||
|
|
||||||
while len(circle) != 0 :
|
|
||||||
print("The solution has a circular path. Not interpretable.")
|
|
||||||
print("Need to add constraints until no circle ")
|
|
||||||
|
|
||||||
A_ub, b_ub = prevent_circle(landmarks, A_ub, b_ub, circle)
|
|
||||||
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)
|
||||||
circle = has_circle(res.x)
|
circle = has_circle(res.x)
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
# Break the circular symmetry if needed
|
||||||
|
while len(circle) != 0 :
|
||||||
|
A_ub, b_ub = break_circle(landmarks, A_ub, b_ub, circle)
|
||||||
|
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)
|
||||||
|
circle = has_circle(res.x)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
|
||||||
|
# Raise error if no solution is found
|
||||||
|
if not res.success :
|
||||||
|
|
||||||
# Raise error if no solution is found
|
# Override the max_steps using the heuristic
|
||||||
if not res.success :
|
for i, val in enumerate(b_ub) :
|
||||||
print(f"No solution has been found within given timeframe.\nMinimum steps to visit all must_see is : {H}")
|
if val == max_steps : b_ub[i] = H
|
||||||
# Override the max_steps using the heuristic
|
|
||||||
for i, val in enumerate(b_ub) :
|
|
||||||
if val == max_steps : b_ub[i] = H
|
|
||||||
|
|
||||||
# Solve problem again :
|
# Solve problem again :
|
||||||
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)
|
||||||
|
|
||||||
|
if not res.success :
|
||||||
# Print result
|
raise ValueError("No solution could be found, even when increasing max_steps using the heuristic")
|
||||||
print_res(res, landmarks, P)
|
|
||||||
|
|
||||||
|
|
||||||
|
if printing is True :
|
||||||
|
if i != 0 :
|
||||||
|
print(f"Neded to recompute paths {i} times because of unconnected loops...")
|
||||||
|
X = print_res(res, landmarks, P)
|
||||||
|
return X
|
||||||
|
|
||||||
|
else :
|
||||||
|
return untangle(res.x)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user