Permafix-optimization-refiner #9
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -196,7 +196,12 @@ def refine_optimization(landmarks: List[Landmark], base_tour: List[Landmark], ma
 | 
			
		||||
  # get a new tour
 | 
			
		||||
  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,37 +96,20 @@ 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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user