more balanced scores
All checks were successful
Build and push docker image / Build (pull_request) Successful in 1m41s
All checks were successful
Build and push docker image / Build (pull_request) Successful in 1m41s
This commit is contained in:
parent
bea3a65fec
commit
da921171e9
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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]
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user