UI elements using the new structs #8
							
								
								
									
										
											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 dataclasses import dataclass
 | 
			
		||||
from optimizer import solve_optimization
 | 
			
		||||
from optimizer import landmark
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    
 | 
			
		||||
    # CONSTRAINT TO RESPECT MAX NUMBER OF STEPS
 | 
			
		||||
    max_steps = 16
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@dataclass
 | 
			
		||||
class Destination:
 | 
			
		||||
    name: str
 | 
			
		||||
    location: tuple
 | 
			
		||||
    attractiveness: int
 | 
			
		||||
    # Initialize all landmarks (+ start and goal). Order matters here
 | 
			
		||||
    landmarks = []
 | 
			
		||||
    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)))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    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__":
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def untangle2(resx: list) :
 | 
			
		||||
# Convert the solution of the optimization into the list of edges to follow. Order is taken into account
 | 
			
		||||
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_edges = resx.sum()      # number of edges
 | 
			
		||||
@@ -40,65 +39,31 @@ def untangle2(resx: list) :
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
def print_res(res: list, landmarks: list, P) :
 | 
			
		||||
def print_res(res, landmarks: list, P) :
 | 
			
		||||
    X = abs(res.x)
 | 
			
		||||
    order = untangle(X)
 | 
			
		||||
 | 
			
		||||
    N = int(np.sqrt(len(X)))
 | 
			
		||||
    """N = int(np.sqrt(len(X)))
 | 
			
		||||
    for i in range(N):
 | 
			
		||||
        print(X[i*N:i*N+N])
 | 
			
		||||
 | 
			
		||||
    order = untangle2(X)
 | 
			
		||||
 | 
			
		||||
    order_ideal = [0, 0, 0, 0, 0, 0, 1, 0]
 | 
			
		||||
 | 
			
		||||
    # 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)
 | 
			
		||||
    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) : 
 | 
			
		||||
        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 :
 | 
			
		||||
        print('Could not visit all the landmarks, the following order could be the fastest but not sure')
 | 
			
		||||
    print("Order of visit :")
 | 
			
		||||
        print('Could not visit all the landmarks, the following order is suggested : ')
 | 
			
		||||
 | 
			
		||||
    for idx in order : 
 | 
			
		||||
        print('- ' + landmarks[idx].name)
 | 
			
		||||
 | 
			
		||||
    steps = path_length(P, abs(res.x))
 | 
			
		||||
    print("\nSteps walked : " + str(steps))
 | 
			
		||||
 | 
			
		||||
    return order
 | 
			
		||||
 | 
			
		||||
# Checks for cases of circular symmetry in the result
 | 
			
		||||
def has_circle(resx: list) :
 | 
			
		||||
@@ -164,8 +129,8 @@ def break_sym(landmarks, A_ub, b_ub):
 | 
			
		||||
 | 
			
		||||
    return A_ub, b_ub
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def prevent_circle(landmarks, A_ub, b_ub, circle) :
 | 
			
		||||
# Constraint to not have circular paths. Want to go from start -> finish without unconnected loops
 | 
			
		||||
def break_circle(landmarks, A_ub, b_ub, circle) :
 | 
			
		||||
    N = len(landmarks)
 | 
			
		||||
    l = [0]*N*N
 | 
			
		||||
 | 
			
		||||
@@ -195,7 +160,7 @@ def respect_number(landmarks, A_ub, b_ub):
 | 
			
		||||
        print("\n")"""
 | 
			
		||||
    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): 
 | 
			
		||||
    N = len(landmarks)
 | 
			
		||||
    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) :
 | 
			
		||||
    return np.dot(P, resx)
 | 
			
		||||
 | 
			
		||||
# Initialize all landmarks (+ start and goal). Order matters here
 | 
			
		||||
landmarks = []
 | 
			
		||||
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)))
 | 
			
		||||
# Main optimization pipeline
 | 
			
		||||
def solve_optimization (landmarks, max_steps, printing) :
 | 
			
		||||
 | 
			
		||||
    # 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
 | 
			
		||||
max_steps = 16
 | 
			
		||||
    # SET CONSTRAINTS FOR EQUALITY
 | 
			
		||||
    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
 | 
			
		||||
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. 
 | 
			
		||||
    A_eq, b_eq = respect_order(landmarks, A_eq, b_eq)              # Respect order of visit (only works when max_steps is limiting factor)
 | 
			
		||||
 | 
			
		||||
# 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
 | 
			
		||||
    # Bounds for variables (x can only be 0 or 1)
 | 
			
		||||
    x_bounds = [(0, 1)] * len(c)
 | 
			
		||||
 | 
			
		||||
# SET CONSTRAINTS FOR EQUALITY
 | 
			
		||||
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
 | 
			
		||||
    # Solve linear programming problem
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
    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
 | 
			
		||||
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
 | 
			
		||||
    for i, val in enumerate(b_ub) :
 | 
			
		||||
        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 :
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Print result
 | 
			
		||||
print_res(res, landmarks, P)
 | 
			
		||||
        # 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)
 | 
			
		||||
 | 
			
		||||
        if not res.success :
 | 
			
		||||
            raise ValueError("No solution could be found, even when increasing max_steps using the heuristic")
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    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