This commit is contained in:
		
							
								
								
									
										
											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,20 +259,8 @@ 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))) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # CONSTRAINT TO RESPECT MAX NUMBER OF STEPS |  | ||||||
| max_steps = 16 |  | ||||||
|  |  | ||||||
|     # SET CONSTRAINTS FOR INEQUALITY |     # SET CONSTRAINTS FOR INEQUALITY | ||||||
|     c, A_ub, b_ub = init_ub_dist(landmarks, max_steps)              # Add the distances from each landmark to the other |     c, A_ub, b_ub = init_ub_dist(landmarks, max_steps)              # Add the distances from each landmark to the other | ||||||
| @@ -330,20 +283,19 @@ x_bounds = [(0, 1)] * len(c) | |||||||
|  |  | ||||||
|     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 : |     while len(circle) != 0 : | ||||||
|     print("The solution has a circular path. Not interpretable.") |         A_ub, b_ub = break_circle(landmarks, A_ub, b_ub, circle) | ||||||
|     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 += 1 | ||||||
|  |  | ||||||
|  |  | ||||||
|     # Raise error if no solution is found |     # Raise error if no solution is found | ||||||
|     if not res.success : |     if not res.success : | ||||||
|     print(f"No solution has been found within given timeframe.\nMinimum steps to visit all must_see is : {H}") |  | ||||||
|         # Override the max_steps using the heuristic |         # Override the max_steps using the heuristic | ||||||
|         for i, val in enumerate(b_ub) : |         for i, val in enumerate(b_ub) : | ||||||
|             if val == max_steps : b_ub[i] = H |             if val == max_steps : b_ub[i] = H | ||||||
| @@ -351,11 +303,18 @@ if not res.success : | |||||||
|         # 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 : | ||||||
|  |             raise ValueError("No solution could be found, even when increasing max_steps using the heuristic") | ||||||
|      |      | ||||||
| # Print result |  | ||||||
| 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) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user