better array handling in the optimizer
Some checks failed
Build and deploy the backend to staging / Build and push image (pull_request) Failing after 2m39s
Build and deploy the backend to staging / Deploy to staging (pull_request) Has been skipped
Run linting on the backend code / Build (pull_request) Successful in 25s
Run testing on the backend code / Build (pull_request) Failing after 1m37s

This commit is contained in:
2025-01-14 11:59:23 +01:00
parent 41976e3e85
commit 4fae658dbb
6 changed files with 366 additions and 280 deletions

View File

@@ -1,7 +1,9 @@
import yaml, logging
from shapely import buffer, LineString, Point, Polygon, MultiPoint, concave_hull
"""Allows to refine the tour by adding more landmarks and making the path easier to follow."""
import logging
from math import pi
import yaml
from shapely import buffer, LineString, Point, Polygon, MultiPoint, concave_hull
from ..structs.landmark import Landmark
from . import take_most_important, get_time_separation
@@ -13,7 +15,7 @@ from ..constants import OPTIMIZER_PARAMETERS_PATH
class Refiner :
logger = logging.getLogger(__name__)
detour_factor: float # detour factor of straight line vs real distance in cities
detour_corridor_width: float # width of the corridor around the path
average_walking_speed: float # average walking speed of adult
@@ -45,7 +47,7 @@ class Refiner :
"""
corrected_width = (180*width)/(6371000*pi)
path = self.create_linestring(landmarks)
obj = buffer(path, corrected_width, join_style="mitre", cap_style="square", mitre_limit=2)
@@ -70,7 +72,7 @@ class Refiner :
return LineString(points)
# Check if some coordinates are in area. Used for the corridor
# Check if some coordinates are in area. Used for the corridor
def is_in_area(self, area: Polygon, coordinates) -> bool :
"""
Check if a given point is within a specified area.
@@ -86,7 +88,7 @@ class Refiner :
return point.within(area)
# Function to determine if two landmarks are close to each other
# Function to determine if two landmarks are close to each other
def is_close_to(self, location1: tuple[float], location2: tuple[float]):
"""
Determine if two locations are close to each other by rounding their coordinates to 3 decimal places.
@@ -119,7 +121,7 @@ class Refiner :
Returns:
list[Landmark]: The rearranged list of landmarks with grouped nearby visits.
"""
i = 1
while i < len(tour):
j = i+1
@@ -131,9 +133,9 @@ class Refiner :
break # Move to the next i-th element after rearrangement
j += 1
i += 1
return tour
def integrate_landmarks(self, sub_list: list[Landmark], main_list: list[Landmark]) :
"""
Inserts 'sub_list' of Landmarks inside the 'main_list' by leaving the ends untouched.
@@ -166,24 +168,24 @@ class Refiner :
should be visited, and the second element is a `Polygon` representing
the path connecting all landmarks.
"""
# Step 1: Find 'start' and 'finish' landmarks
start_idx = next(i for i, lm in enumerate(landmarks) if lm.type == 'start')
finish_idx = next(i for i, lm in enumerate(landmarks) if lm.type == 'finish')
start_landmark = landmarks[start_idx]
finish_landmark = landmarks[finish_idx]
# Step 2: Create a list of unvisited landmarks excluding 'start' and 'finish'
unvisited_landmarks = [lm for i, lm in enumerate(landmarks) if i not in [start_idx, finish_idx]]
# Step 3: Initialize the path with the 'start' landmark
path = [start_landmark]
coordinates = [landmarks[start_idx].location]
current_landmark = start_landmark
# Step 4: Use nearest neighbor heuristic to visit all landmarks
while unvisited_landmarks:
nearest_landmark = min(unvisited_landmarks, key=lambda lm: get_time_separation.get_time(current_landmark.location, lm.location))
@@ -224,7 +226,7 @@ class Refiner :
for visited in visited_landmarks :
visited_names.append(visited.name)
for landmark in all_landmarks :
if self.is_in_area(area, landmark.location) and landmark.name not in visited_names:
second_order_landmarks.append(landmark)
@@ -256,7 +258,7 @@ class Refiner :
coords_dict[landmark.location] = landmark
tour_poly = Polygon(coords)
better_tour_poly = tour_poly.buffer(0)
try :
xs, ys = better_tour_poly.exterior.xy
@@ -299,7 +301,7 @@ class Refiner :
# Rearrange only if polygon still not simple
if not better_tour_poly.is_simple :
better_tour = self.rearrange(better_tour)
return better_tour
@@ -330,7 +332,7 @@ class Refiner :
# No need to refine if no detour is taken
# if detour == 0:
# return base_tour
minor_landmarks = self.get_minor_landmarks(all_landmarks, base_tour, self.detour_corridor_width)
self.logger.debug(f"Using {len(minor_landmarks)} minor landmarks around the predicted path")
@@ -341,7 +343,7 @@ class Refiner :
# Generate a new tour with the optimizer.
new_tour = self.optimizer.solve_optimization(
max_time = max_time + detour,
landmarks = full_set,
landmarks = full_set,
max_landmarks = self.max_landmarks_refiner
)
@@ -357,7 +359,7 @@ class Refiner :
# Find shortest path using the nearest neighbor heuristic.
better_tour, better_poly = self.find_shortest_path_through_all_landmarks(new_tour)
# Fix the tour using Polygons if the path looks weird.
# Fix the tour using Polygons if the path looks weird.
# Conditions : circular trip and invalid polygon.
if base_tour[0].location == base_tour[-1].location and not better_poly.is_valid :
better_tour = self.fix_using_polygon(better_tour)