more balanced scores
All checks were successful
Build and push docker image / Build (pull_request) Successful in 1m41s

This commit is contained in:
Helldragon67 2024-08-12 15:58:30 +02:00
parent bea3a65fec
commit da921171e9
7 changed files with 97 additions and 54 deletions

View File

@ -1,6 +1,11 @@
city_bbox_side: 5000 #m city_bbox_side: 7500 #m
radius_close_to: 50 radius_close_to: 50
church_coeff: 0.8 church_coeff: 0.75
park_coeff: 1.0 nature_coeff: 1.25
tag_coeff: 10 overall_coeff: 10
N_important: 40 tag_exponent: 1.15
image_bonus: 10
viewpoint_bonus: 15
wikipedia_bonus: 6
N_important: 50
pay_bonus: -1

View File

@ -1,4 +1,5 @@
detour_factor: 1.4 detour_factor: 1.4
detour_corridor_width: 200 detour_corridor_width: 200
average_walking_speed: 4.8 average_walking_speed: 4.8
max_landmarks: 7 max_landmarks: 10
max_landmarks_refiner: 20

View File

@ -25,6 +25,11 @@ class LinkedLandmarks:
""" """
Create the links between the landmarks in the list by setting their .next_uuid and the .time_to_next attributes. Create the links between the landmarks in the list by setting their .next_uuid and the .time_to_next attributes.
""" """
# Mark secondary landmarks as such
self.update_secondary_landmarks()
for i, landmark in enumerate(self._landmarks[:-1]): for i, landmark in enumerate(self._landmarks[:-1]):
landmark.next_uuid = self._landmarks[i + 1].uuid landmark.next_uuid = self._landmarks[i + 1].uuid
time_to_next = get_time(landmark.location, self._landmarks[i + 1].location) time_to_next = get_time(landmark.location, self._landmarks[i + 1].location)
@ -34,6 +39,22 @@ class LinkedLandmarks:
self._landmarks[-1].next_uuid = None self._landmarks[-1].next_uuid = None
self._landmarks[-1].time_to_reach_next = 0 self._landmarks[-1].time_to_reach_next = 0
def update_secondary_landmarks(self) -> None:
# Extract the attractiveness scores and sort them in descending order
scores = sorted([landmark.attractiveness for landmark in self._landmarks], reverse=True)
# Determine the 10th highest score
if len(scores) >= 10:
threshold_score = scores[9]
else:
# If there are fewer than 10 landmarks, use the lowest score in the list as the threshold
threshold_score = min(scores) if scores else 0
# Update 'is_secondary' for landmarks with attractiveness below the threshold score
for landmark in self._landmarks:
if landmark.attractiveness < threshold_score:
landmark.is_secondary = True
def __getitem__(self, index: int) -> Landmark: def __getitem__(self, index: int) -> Landmark:
return self._landmarks[index] return self._landmarks[index]

View File

@ -24,8 +24,8 @@ def test(start_coords: tuple[float, float], finish_coords: tuple[float, float] =
nature=Preference(type='nature', score = 5), nature=Preference(type='nature', score = 5),
shopping=Preference(type='shopping', score = 5), shopping=Preference(type='shopping', score = 5),
max_time_minute=180, max_time_minute=1000,
detour_tolerance_minute=30 detour_tolerance_minute=0
) )
# Create start and finish # Create start and finish
@ -60,7 +60,9 @@ def test(start_coords: tuple[float, float], finish_coords: tuple[float, float] =
refined_tour = refiner.refine_optimization(all_landmarks=landmarks, base_tour=base_tour, max_time = preferences.max_time_minute, detour = preferences.detour_tolerance_minute) refined_tour = refiner.refine_optimization(all_landmarks=landmarks, base_tour=base_tour, max_time = preferences.max_time_minute, detour = preferences.detour_tolerance_minute)
linked_tour = LinkedLandmarks(refined_tour) linked_tour = LinkedLandmarks(refined_tour)
logger.info(f"Optimized route: {linked_tour}") logger.info("Optimized route : ")
for l in linked_tour :
logger.info(f"{l}")
# with open('linked_tour.yaml', 'w') as f: # with open('linked_tour.yaml', 'w') as f:
# yaml.dump(linked_tour.asdict(), f) # yaml.dump(linked_tour.asdict(), f)
@ -68,9 +70,9 @@ def test(start_coords: tuple[float, float], finish_coords: tuple[float, float] =
return linked_tour return linked_tour
#test(tuple((48.8344400, 2.3220540))) # Café Chez César test(tuple((48.8344400, 2.3220540))) # Café Chez César
# test(tuple((48.8375946, 2.2949904))) # Point random # test(tuple((48.8375946, 2.2949904))) # Point random
# test(tuple((47.377859, 8.540585))) # Zurich HB # test(tuple((47.377859, 8.540585))) # Zurich HB
# test(tuple((45.7576485, 4.8330241))) # Lyon Bellecour # test(tuple((45.7576485, 4.8330241))) # Lyon Bellecour
test(tuple((48.5848435, 7.7332974))) # Strasbourg Gare # test(tuple((48.5848435, 7.7332974))) # Strasbourg Gare
# test(tuple((48.2067858, 16.3692340))) # Vienne # test(tuple((48.2067858, 16.3692340))) # Vienne

View File

@ -23,8 +23,8 @@ class LandmarkManager:
radius_close_to: int # radius in meters radius_close_to: int # radius in meters
church_coeff: float # coeff to adjsut score of churches church_coeff: float # coeff to adjsut score of churches
park_coeff: float # coeff to adjust score of parks nature_coeff: float # coeff to adjust score of parks
tag_coeff: float # coeff to adjust weight of tags overall_coeff: float # coeff to adjust weight of tags
N_important: int # number of important landmarks to consider N_important: int # number of important landmarks to consider
@ -38,8 +38,13 @@ class LandmarkManager:
self.max_bbox_side = parameters['city_bbox_side'] self.max_bbox_side = parameters['city_bbox_side']
self.radius_close_to = parameters['radius_close_to'] self.radius_close_to = parameters['radius_close_to']
self.church_coeff = parameters['church_coeff'] self.church_coeff = parameters['church_coeff']
self.park_coeff = parameters['park_coeff'] self.nature_coeff = parameters['nature_coeff']
self.tag_coeff = parameters['tag_coeff'] self.overall_coeff = parameters['overall_coeff']
self.tag_exponent = parameters['tag_exponent']
self.image_bonus = parameters['image_bonus']
self.wikipedia_bonus = parameters['wikipedia_bonus']
self.viewpoint_bonus = parameters['viewpoint_bonus']
self.pay_bonus = parameters['pay_bonus']
self.N_important = parameters['N_important'] self.N_important = parameters['N_important']
with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f: with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f:
@ -76,25 +81,25 @@ class LandmarkManager:
bbox = self.create_bbox(center_coordinates, reachable_bbox_side) bbox = self.create_bbox(center_coordinates, reachable_bbox_side)
# list for sightseeing # list for sightseeing
if preferences.sightseeing.score != 0: if preferences.sightseeing.score != 0:
score_function = lambda loc, n_tags: int((((n_tags**1.2)*self.tag_coeff) )*self.church_coeff) # self.count_elements_close_to(loc) + score_function = lambda score: int(score*10)*preferences.sightseeing.score/5 # self.count_elements_close_to(loc) +
L1 = self.fetch_landmarks(bbox, self.amenity_selectors['sightseeing'], preferences.sightseeing.type, score_function) L1 = self.fetch_landmarks(bbox, self.amenity_selectors['sightseeing'], preferences.sightseeing.type, score_function)
L += L1 L += L1
# list for nature # list for nature
if preferences.nature.score != 0: if preferences.nature.score != 0:
score_function = lambda loc, n_tags: int((((n_tags**1.2)*self.tag_coeff) )*self.park_coeff) # self.count_elements_close_to(loc) + score_function = lambda score: int(score*10*self.nature_coeff)*preferences.nature.score/5 # self.count_elements_close_to(loc) +
L2 = self.fetch_landmarks(bbox, self.amenity_selectors['nature'], preferences.nature.type, score_function) L2 = self.fetch_landmarks(bbox, self.amenity_selectors['nature'], preferences.nature.type, score_function)
L += L2 L += L2
# list for shopping # list for shopping
if preferences.shopping.score != 0: if preferences.shopping.score != 0:
score_function = lambda loc, n_tags: int(((n_tags**1.2)*self.tag_coeff)) # self.count_elements_close_to(loc) + score_function = lambda score: int(score*10)*preferences.shopping.score/5 # self.count_elements_close_to(loc) +
L3 = self.fetch_landmarks(bbox, self.amenity_selectors['shopping'], preferences.shopping.type, score_function) L3 = self.fetch_landmarks(bbox, self.amenity_selectors['shopping'], preferences.shopping.type, score_function)
L += L3 L += L3
L = self.remove_duplicates(L) L = self.remove_duplicates(L)
self.correct_score(L, preferences) # self.correct_score(L, preferences)
L_constrained = take_most_important(L, self.N_important) L_constrained = take_most_important(L, self.N_important)
self.logger.info(f'Generated {len(L)} landmarks around {center_coordinates}, and constrained to {len(L_constrained)} most important ones.') self.logger.info(f'Generated {len(L)} landmarks around {center_coordinates}, and constrained to {len(L_constrained)} most important ones.')
@ -273,8 +278,8 @@ class LandmarkManager:
continue continue
# skip if unused # skip if unused
if 'disused:leisure' in elem.tags().keys(): # if 'disused:leisure' in elem.tags().keys():
continue # continue
# skip if part of another building # skip if part of another building
if 'building:part' in elem.tags().keys() and elem.tag('building:part') == 'yes': if 'building:part' in elem.tags().keys() and elem.tag('building:part') == 'yes':
@ -284,19 +289,20 @@ class LandmarkManager:
osm_id = elem.id() # Add OSM id osm_id = elem.id() # Add OSM id
elem_type = landmarktype # Add the landmark type as 'sightseeing, elem_type = landmarktype # Add the landmark type as 'sightseeing,
n_tags = len(elem.tags().keys()) # Add number of tags n_tags = len(elem.tags().keys()) # Add number of tags
score = n_tags**self.tag_exponent # Add score
# remove specific tags # remove specific tags
skip = False skip = False
for tag in elem.tags().keys(): for tag in elem.tags().keys():
if "pay" in tag: if "pay" in tag:
n_tags -= 1 # discard payment options for tags score += self.pay_bonus # discard payment options for tags
if "disused" in tag: if "disused" in tag:
skip = True # skip disused amenities skip = True # skip disused amenities
break break
if "wikipedia" in tag: if "wiki" in tag:
n_tags += 1 # wikipedia entries count more score += self.wikipedia_bonus # wikipedia entries count more
# if tag == "wikidata": # if tag == "wikidata":
# Q = elem.tag('wikidata') # Q = elem.tag('wikidata')
@ -305,16 +311,17 @@ class LandmarkManager:
# item.get() # item.get()
# n_languages = len(item.labels) # n_languages = len(item.labels)
# n_tags += n_languages/10 # n_tags += n_languages/10
if "viewpoint" in tag: if "viewpoint" in tag:
n_tags += 10 score += self.viewpoint_bonus
if "image" in tag:
score += self.image_bonus
if elem_type != "nature": if elem_type != "nature":
if "leisure" in tag and elem.tag('leisure') == "park": if "leisure" in tag and elem.tag('leisure') == "park":
elem_type = "nature" elem_type = "nature"
if elem_type == "nature":
n_tags += 1
if landmarktype != "shopping": if landmarktype != "shopping":
if "shop" in tag: if "shop" in tag:
skip = True skip = True
@ -326,8 +333,10 @@ class LandmarkManager:
if skip: if skip:
continue continue
score = score_function(location, n_tags) score = score_function(score)
if score != 0: if "place_of_worship" in elem.tags().values() :
score = int(score*self.church_coeff)
# Generate the landmark and append it to the list # Generate the landmark and append it to the list
landmark = Landmark( landmark = Landmark(
name=name, name=name,

View File

@ -203,7 +203,7 @@ class Optimizer:
return c, A_ub, [max_steps] return c, A_ub, [max_steps]
def respect_number(self, L: int): def respect_number(self, L, max_landmarks: int):
""" """
Generate constraints to ensure each landmark is visited only once and cap the total number of visited landmarks. Generate constraints to ensure each landmark is visited only once and cap the total number of visited landmarks.
@ -224,7 +224,7 @@ class Optimizer:
b.append(1) b.append(1)
A = np.vstack((A, ones*L)) A = np.vstack((A, ones*L))
b.append(self.max_landmarks+1) b.append(max_landmarks+1)
return A, b return A, b
@ -433,6 +433,7 @@ class Optimizer:
self, self,
max_time: int, max_time: int,
landmarks: list[Landmark], landmarks: list[Landmark],
max_landmarks: int = None
) -> list[Landmark]: ) -> list[Landmark]:
""" """
Main optimization pipeline to solve the landmark visiting problem. Main optimization pipeline to solve the landmark visiting problem.
@ -443,15 +444,18 @@ class Optimizer:
Args: Args:
max_time (int): Maximum time allowed for the tour in minutes. max_time (int): Maximum time allowed for the tour in minutes.
landmarks (list[Landmark]): List of landmarks to visit. landmarks (list[Landmark]): List of landmarks to visit.
max_landmarks (int): Maximum number of landmarks visited
Returns: Returns:
list[Landmark]: The optimized tour of landmarks with updated travel times, or None if no valid solution is found. list[Landmark]: The optimized tour of landmarks with updated travel times, or None if no valid solution is found.
""" """
if max_landmarks is None :
max_landmarks = self.max_landmarks
L = len(landmarks) L = len(landmarks)
# SET CONSTRAINTS FOR INEQUALITY # SET CONSTRAINTS FOR INEQUALITY
c, A_ub, b_ub = self.init_ub_dist(landmarks, max_time) # Add the distances from each landmark to the other c, A_ub, b_ub = self.init_ub_dist(landmarks, max_time) # Add the distances from each landmark to the other
A, b = self.respect_number(L) # Respect max number of visits (no more possible stops than landmarks). A, b = self.respect_number(L, max_landmarks) # Respect max number of visits (no more possible stops than landmarks).
A_ub = np.vstack((A_ub, A), dtype=np.int16) A_ub = np.vstack((A_ub, A), dtype=np.int16)
b_ub += b b_ub += b
A, b = self.break_sym(L) # break the 'zig-zag' symmetry A, b = self.break_sym(L) # break the 'zig-zag' symmetry

View File

@ -17,7 +17,7 @@ class Refiner :
detour_factor: float # detour factor of straight line vs real distance in cities detour_factor: float # detour factor of straight line vs real distance in cities
detour_corridor_width: float # width of the corridor around the path detour_corridor_width: float # width of the corridor around the path
average_walking_speed: float # average walking speed of adult average_walking_speed: float # average walking speed of adult
max_landmarks: int # max number of landmarks to visit max_landmarks_refiner: int # max number of landmarks to visit
optimizer: Optimizer # optimizer object optimizer: Optimizer # optimizer object
def __init__(self, optimizer: Optimizer) : def __init__(self, optimizer: Optimizer) :
@ -29,7 +29,7 @@ class Refiner :
self.detour_factor = parameters['detour_factor'] self.detour_factor = parameters['detour_factor']
self.detour_corridor_width = parameters['detour_corridor_width'] self.detour_corridor_width = parameters['detour_corridor_width']
self.average_walking_speed = parameters['average_walking_speed'] self.average_walking_speed = parameters['average_walking_speed']
self.max_landmarks = parameters['max_landmarks'] + 4 self.max_landmarks_refiner = parameters['max_landmarks_refiner']
def create_corridor(self, landmarks: list[Landmark], width: float) : def create_corridor(self, landmarks: list[Landmark], width: float) :
@ -308,8 +308,8 @@ class Refiner :
""" """
# No need to refine if no detour is taken # No need to refine if no detour is taken
if detour == 0: # if detour == 0:
return base_tour # return base_tour
minor_landmarks = self.get_minor_landmarks(all_landmarks, base_tour, self.detour_corridor_width) minor_landmarks = self.get_minor_landmarks(all_landmarks, base_tour, self.detour_corridor_width)
@ -322,7 +322,8 @@ class Refiner :
# get a new tour # get a new tour
new_tour = self.optimizer.solve_optimization( new_tour = self.optimizer.solve_optimization(
max_time = max_time + detour, max_time = max_time + detour,
landmarks = full_set landmarks = full_set,
max_landmarks = self.max_landmarks_refiner
) )
if new_tour is None: if new_tour is None: