better dosctrings
Some checks failed
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 2m26s
Run linting on the backend code / Build (pull_request) Successful in 28s
Run testing on the backend code / Build (pull_request) Failing after 1m27s
Build and deploy the backend to staging / Deploy to staging (pull_request) Successful in 52s
Some checks failed
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 2m26s
Run linting on the backend code / Build (pull_request) Successful in 28s
Run testing on the backend code / Build (pull_request) Failing after 1m27s
Build and deploy the backend to staging / Deploy to staging (pull_request) Successful in 52s
This commit is contained in:
parent
28ff0460ab
commit
f67e2b5dd6
@ -59,20 +59,25 @@ class Optimizer:
|
|||||||
|
|
||||||
def init_ub_time(self, prob: pl.LpProblem, x: pl.LpVariable, L: int, landmarks: list[Landmark], max_time: int):
|
def init_ub_time(self, prob: pl.LpProblem, x: pl.LpVariable, L: int, landmarks: list[Landmark], max_time: int):
|
||||||
"""
|
"""
|
||||||
Initialize the objective function coefficients and inequality constraints.
|
Initialize the objective function and inequality constraints for the linear program.
|
||||||
-> Adds 1 row of constraints
|
|
||||||
-> Pre-allocates A_ub for the rest of the computations with L + (L*L-L)/2 rows
|
|
||||||
|
|
||||||
This function computes the distances between all landmarks and stores
|
This function sets up the objective to maximize the attractiveness of visiting landmarks,
|
||||||
their attractiveness to maximize sightseeing. The goal is to maximize
|
while ensuring that the total time (including travel and visit duration) does not exceed
|
||||||
the objective function subject to the constraints A*x < b and A_eq*x = b_eq.
|
the maximum allowed time. It calculates the pairwise travel times between landmarks and
|
||||||
|
incorporates visit duration to form the inequality constraints.
|
||||||
|
|
||||||
|
The objective is to maximize sightseeing by selecting the most attractive landmarks within
|
||||||
|
the time limit.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
landmarks (list[Landmark]): List of landmarks.
|
prob (pl.LpProblem): The linear programming problem where constraints and the objective will be added.
|
||||||
max_time (int): Maximum time of visit allowed.
|
x (pl.LpVariable): A decision variable representing whether a landmark is visited.
|
||||||
|
L (int): The number of landmarks.
|
||||||
|
landmarks (list[Landmark]): List of landmarks to visit.
|
||||||
|
max_time (int): Maximum allowable time for sightseeing, including travel and visit duration.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple[list[float], list[float], list[int]]: Objective function coefficients, inequality
|
None: Adds the objective function and constraints to the LP problem directly.
|
||||||
constraint coefficients, and the right-hand side of the inequality constraint.
|
constraint coefficients, and the right-hand side of the inequality constraint.
|
||||||
"""
|
"""
|
||||||
L = len(landmarks)
|
L = len(landmarks)
|
||||||
@ -117,14 +122,20 @@ class Optimizer:
|
|||||||
|
|
||||||
def respect_number(self, prob: pl.LpProblem, x: pl.LpVariable, L: int, max_landmarks: int):
|
def respect_number(self, prob: pl.LpProblem, x: pl.LpVariable, L: int, 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 at most once and cap the total number of visited landmarks.
|
||||||
-> Adds L-1 rows of constraints
|
|
||||||
|
This function adds the following constraints to the linear program:
|
||||||
|
1. Each landmark is visited at most once by creating L-2 constraints (one for each landmark).
|
||||||
|
2. The total number of visited landmarks is capped by the specified maximum number (`max_landmarks`) plus 2.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
L (int): Number of landmarks.
|
prob (pl.LpProblem): The linear programming problem where constraints will be added.
|
||||||
|
x (pl.LpVariable): Decision variable indicating whether a landmark is visited.
|
||||||
|
L (int): The total number of landmarks.
|
||||||
|
max_landmarks (int): The maximum number of landmarks that can be visited.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple[np.ndarray, list[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints.
|
None: This function directly modifies the `prob` object by adding constraints.
|
||||||
"""
|
"""
|
||||||
# L-2 constraints: each landmark is visited exactly once
|
# L-2 constraints: each landmark is visited exactly once
|
||||||
for i in range(1, L-1):
|
for i in range(1, L-1):
|
||||||
@ -137,16 +148,20 @@ class Optimizer:
|
|||||||
def break_sym(self, prob: pl.LpProblem, x: pl.LpVariable, L: int):
|
def break_sym(self, prob: pl.LpProblem, x: pl.LpVariable, L: int):
|
||||||
"""
|
"""
|
||||||
Generate constraints to prevent simultaneous travel between two landmarks
|
Generate constraints to prevent simultaneous travel between two landmarks
|
||||||
in both directions. Constraint to not have d14 and d41 simultaneously.
|
in both directions. This constraint ensures that, for any pair of landmarks,
|
||||||
Does not prevent cyclic paths with more elements
|
travel from landmark i to landmark j (dij) and travel from landmark j to landmark i (dji)
|
||||||
-> Adds (L*L-L)/2 rows of constraints (some of which might be zero)
|
cannot happen simultaneously.
|
||||||
|
|
||||||
|
This method adds constraints to break symmetry, specifically to prevent
|
||||||
|
cyclic paths with only two elements. It does not prevent cyclic paths involving more than two elements.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
L (int): Number of landmarks.
|
prob (pl.LpProblem): The linear programming problem where constraints will be added.
|
||||||
|
x (pl.LpVariable): Decision variable representing travel between landmarks.
|
||||||
|
L (int): The total number of landmarks.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple[np.ndarray, list[int]]: Inequality constraint coefficients and
|
None: This function modifies the `prob` object by adding constraints in-place.
|
||||||
the right-hand side of the inequality constraints.
|
|
||||||
"""
|
"""
|
||||||
upper_ind = np.triu_indices(L, 0, L) # Get the upper triangular indices
|
upper_ind = np.triu_indices(L, 0, L) # Get the upper triangular indices
|
||||||
up_ind_x = upper_ind[0]
|
up_ind_x = upper_ind[0]
|
||||||
@ -161,15 +176,20 @@ class Optimizer:
|
|||||||
|
|
||||||
def init_eq_not_stay(self, prob: pl.LpProblem, x: pl.LpVariable, L: int):
|
def init_eq_not_stay(self, prob: pl.LpProblem, x: pl.LpVariable, L: int):
|
||||||
"""
|
"""
|
||||||
Generate constraints to prevent staying in the same position (e.g., removing d11, d22, d33, etc.).
|
Generate constraints to prevent staying at the same position during travel.
|
||||||
-> Adds 1 row of constraints
|
Specifically, it removes travel from a landmark to itself (e.g., d11, d22, d33, etc.).
|
||||||
-> Pre-allocates A_eq for the rest of the computations with (L+ 2 + dynamic incr) rows
|
|
||||||
|
This function adds one equality constraint to the optimization problem that ensures
|
||||||
|
no decision variable corresponding to staying at the same landmark is included
|
||||||
|
in the solution. This helps in ensuring that the path does not include self-loops.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
L (int): Number of landmarks.
|
prob (pl.LpProblem): The linear programming problem where constraints will be added.
|
||||||
|
x (pl.LpVariable): Decision variable representing travel between landmarks.
|
||||||
|
L (int): The total number of landmarks.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple[list[np.ndarray], list[int]]: Equality constraint coefficients and the right-hand side of the equality constraints.
|
None: This function modifies the `prob` object by adding an equality constraint in-place.
|
||||||
"""
|
"""
|
||||||
A_eq = np.zeros((L, L), dtype=np.int8)
|
A_eq = np.zeros((L, L), dtype=np.int8)
|
||||||
|
|
||||||
@ -181,18 +201,23 @@ class Optimizer:
|
|||||||
prob += (pl.lpSum([A_eq[j] * x[j] for j in range(L*L)]) == 0)
|
prob += (pl.lpSum([A_eq[j] * x[j] for j in range(L*L)]) == 0)
|
||||||
|
|
||||||
|
|
||||||
# Constraint to ensure start at start and finish at goal
|
|
||||||
def respect_start_finish(self, prob: pl.LpProblem, x: pl.LpVariable, L: int):
|
def respect_start_finish(self, prob: pl.LpProblem, x: pl.LpVariable, L: int):
|
||||||
"""
|
"""
|
||||||
Generate constraints to ensure that the optimization starts at the designated
|
Generate constraints to ensure that the optimization starts at the designated
|
||||||
start landmark and finishes at the goal landmark.
|
start landmark and finishes at the goal landmark.
|
||||||
-> Adds 3 rows of constraints
|
|
||||||
|
Specifically, this function adds three equality constraints:
|
||||||
|
1. Ensures that the path starts at the designated start landmark (row 0).
|
||||||
|
2. Ensures that the path finishes at the designated goal landmark (row 1).
|
||||||
|
3. Prevents any arrivals at the start landmark or departures from the goal landmark (row 2).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
L (int): Number of landmarks.
|
prob (pl.LpProblem): The linear programming problem where constraints will be added.
|
||||||
|
x (pl.LpVariable): Decision variable representing travel between landmarks.
|
||||||
|
L (int): The total number of landmarks.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple[np.ndarray, list[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints.
|
None: This function modifies the `prob` object by adding three equality constraints in-place.
|
||||||
"""
|
"""
|
||||||
# Fill-in row 0.
|
# Fill-in row 0.
|
||||||
A_eq = np.zeros((3,L*L), dtype=np.int8)
|
A_eq = np.zeros((3,L*L), dtype=np.int8)
|
||||||
@ -211,27 +236,24 @@ class Optimizer:
|
|||||||
for i in range(3) :
|
for i in range(3) :
|
||||||
prob += (pl.lpSum([A_eq[i][j] * x[j] for j in range(L*L)]) == b_eq[i])
|
prob += (pl.lpSum([A_eq[i][j] * x[j] for j in range(L*L)]) == b_eq[i])
|
||||||
|
|
||||||
|
|
||||||
def respect_order(self, prob: pl.LpProblem, x: pl.LpVariable, L: int):
|
def respect_order(self, prob: pl.LpProblem, x: pl.LpVariable, L: int):
|
||||||
"""
|
"""
|
||||||
Generate constraints to tie the optimization problem together and prevent
|
Generate constraints to tie the optimization problem together and prevent
|
||||||
stacked ones, although this does not fully prevent circles.
|
stacked ones, although this does not fully prevent circles.
|
||||||
-> Adds L-2 rows of constraints
|
|
||||||
|
This function adds constraints to the optimization problem that prevent
|
||||||
|
simultaneous travel between landmarks in a way that would result in stacked ones.
|
||||||
|
However, it does not fully prevent circular paths.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
L (int): Number of landmarks.
|
prob (pl.LpProblem): The linear programming problem where constraints will be added.
|
||||||
|
x (pl.LpVariable): Decision variable representing travel between landmarks.
|
||||||
|
L (int): The total number of landmarks.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple[np.ndarray, list[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints.
|
None: This function modifies the `prob` object by adding L-2 equality constraints in-place.
|
||||||
"""
|
"""
|
||||||
# A_eq = np.zeros(L*L, dtype=np.int8)
|
|
||||||
# ones = np.ones(L, dtype=np.int8)
|
|
||||||
# # Fill-in rows 4 to L+2
|
|
||||||
# for i in range(1, L-1) : # Prevent stacked ones
|
|
||||||
# for j in range(L) :
|
|
||||||
# A_eq[i + j*L] = -1
|
|
||||||
# A_eq[i*L:(i+1)*L] = ones
|
|
||||||
# prob += (pl.lpSum([A_eq[j] * x[j] for j in range(L*L)]) == 0)
|
|
||||||
|
|
||||||
# FIXME: weird 0 artifact in the coefficients popping up
|
# FIXME: weird 0 artifact in the coefficients popping up
|
||||||
# Loop through rows 1 to L-2 to prevent stacked ones
|
# Loop through rows 1 to L-2 to prevent stacked ones
|
||||||
for i in range(1, L-1):
|
for i in range(1, L-1):
|
||||||
@ -243,14 +265,19 @@ class Optimizer:
|
|||||||
def respect_user_must(self, prob: pl.LpProblem, x: pl.LpVariable, L: int, landmarks: list[Landmark]) :
|
def respect_user_must(self, prob: pl.LpProblem, x: pl.LpVariable, L: int, landmarks: list[Landmark]) :
|
||||||
"""
|
"""
|
||||||
Generate constraints to ensure that landmarks marked as 'must_do' are included in the optimization.
|
Generate constraints to ensure that landmarks marked as 'must_do' are included in the optimization.
|
||||||
-> Adds a variable number of rows of constraints BUT CAN BE PRE COMPUTED
|
|
||||||
|
|
||||||
|
This function adds constraints to the optimization problem to ensure that landmarks marked as
|
||||||
|
'must_do' are included in the solution. It precomputes the constraints and adds them to the
|
||||||
|
problem accordingly.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
prob (pl.LpProblem): The linear programming problem where constraints will be added.
|
||||||
|
x (pl.LpVariable): Decision variable representing travel between landmarks.
|
||||||
|
L (int): The total number of landmarks.
|
||||||
landmarks (list[Landmark]): List of landmarks, where some are marked as 'must_do'.
|
landmarks (list[Landmark]): List of landmarks, where some are marked as 'must_do'.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple[np.ndarray, list[int]]: Inequality constraint coefficients and the right-hand side of the inequality constraints.
|
None: This function modifies the `prob` object by adding equality constraints in-place.
|
||||||
"""
|
"""
|
||||||
ones = np.ones(L, dtype=np.int8)
|
ones = np.ones(L, dtype=np.int8)
|
||||||
A_eq = np.zeros(L*L, dtype=np.int8)
|
A_eq = np.zeros(L*L, dtype=np.int8)
|
||||||
@ -264,52 +291,22 @@ class Optimizer:
|
|||||||
prob += (pl.lpSum([A_eq[j] * x[j] for j in range(L*L)]) == 2)
|
prob += (pl.lpSum([A_eq[j] * x[j] for j in range(L*L)]) == 2)
|
||||||
|
|
||||||
|
|
||||||
# Prevent the use of a particular solution. TODO probably can be done faster just using resx
|
|
||||||
# def prevent_config(self, prob: pl.LpProblem, x: pl.LpVariable, resx):
|
|
||||||
# """
|
|
||||||
# Prevent the use of a particular solution by adding constraints to the optimization.
|
|
||||||
|
|
||||||
# Args:
|
|
||||||
# resx (list[float]): List of edge weights.
|
|
||||||
|
|
||||||
# Returns:
|
|
||||||
# tuple[list[int], list[int]]: A tuple containing a new row for A and new value for ub.
|
|
||||||
# """
|
|
||||||
|
|
||||||
# for i, elem in enumerate(resx):
|
|
||||||
# resx[i] = round(elem)
|
|
||||||
|
|
||||||
# N = len(resx) # Number of edges
|
|
||||||
# L = int(np.sqrt(N)) # Number of landmarks
|
|
||||||
|
|
||||||
# nonzeroind = np.nonzero(resx)[0] # the return is a little funky so I use the [0]
|
|
||||||
# nonzero_tup = np.unravel_index(nonzeroind, (L,L))
|
|
||||||
|
|
||||||
# ind_a = nonzero_tup[0].tolist()
|
|
||||||
# vertices_visited = ind_a
|
|
||||||
# vertices_visited.remove(0)
|
|
||||||
|
|
||||||
# ones = np.ones(L, dtype=np.int8)
|
|
||||||
# h = np.zeros(L*L, dtype=np.int8)
|
|
||||||
|
|
||||||
# for i in range(L) :
|
|
||||||
# if i in vertices_visited :
|
|
||||||
# h[i*L:i*L+L] = ones
|
|
||||||
|
|
||||||
# return h, len(vertices_visited)-1
|
|
||||||
|
|
||||||
|
|
||||||
# Prevents the creation of the same circle (both directions)
|
|
||||||
def prevent_circle(self, prob: pl.LpProblem, x: pl.LpVariable, circle_vertices: list, L: int) :
|
def prevent_circle(self, prob: pl.LpProblem, x: pl.LpVariable, circle_vertices: list, L: int) :
|
||||||
"""
|
"""
|
||||||
Prevent circular paths by by adding constraints to the optimization.
|
Prevent circular paths by adding constraints to the optimization.
|
||||||
|
|
||||||
|
This function ensures that circular paths in both directions (i.e., forward and reverse)
|
||||||
|
between landmarks are avoided in the optimization problem by adding the corresponding constraints.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
circle_vertices (list): List of vertices forming a circle.
|
prob (pl.LpProblem): The linear programming problem instance to which the constraints will be added.
|
||||||
L (int): Number of landmarks.
|
x (pl.LpVariable): Decision variable representing the travel between landmarks in the problem.
|
||||||
|
circle_vertices (list): List of indices representing the landmarks that form a circular path.
|
||||||
|
L (int): The total number of landmarks.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple[np.ndarray, list[int]]: A tuple containing a new row for constraint matrix and new value for upper bound vector.
|
None: This function modifies the `prob` object by adding two equality constraints that
|
||||||
|
prevent circular paths in both directions for the specified circle vertices.
|
||||||
"""
|
"""
|
||||||
l = np.zeros((2, L*L), dtype=np.int8)
|
l = np.zeros((2, L*L), dtype=np.int8)
|
||||||
|
|
||||||
@ -544,12 +541,8 @@ class Optimizer:
|
|||||||
|
|
||||||
return prob, x
|
return prob, x
|
||||||
|
|
||||||
def solve_optimization(
|
|
||||||
self,
|
def solve_optimization(self, max_time: int, landmarks: list[Landmark], max_landmarks: int = None) -> list[Landmark]:
|
||||||
max_time: int,
|
|
||||||
landmarks: list[Landmark],
|
|
||||||
max_landmarks: int = None
|
|
||||||
) -> list[Landmark]:
|
|
||||||
"""
|
"""
|
||||||
Main optimization pipeline to solve the landmark visiting problem.
|
Main optimization pipeline to solve the landmark visiting problem.
|
||||||
|
|
||||||
@ -563,14 +556,12 @@ class Optimizer:
|
|||||||
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.
|
||||||
"""
|
"""
|
||||||
# 1. Setup the optimization proplem.
|
# Setup the optimization proplem.
|
||||||
L = len(landmarks)
|
L = len(landmarks)
|
||||||
prob, x = self.pre_processing(L, landmarks, max_time, max_landmarks)
|
prob, x = self.pre_processing(L, landmarks, max_time, max_landmarks)
|
||||||
|
|
||||||
# 2. Solve the problem
|
# Solve the problem and extract results.
|
||||||
prob.solve(pl.PULP_CBC_CMD(msg=False, gapRel=0.1))
|
prob.solve(pl.PULP_CBC_CMD(msg=False, gapRel=0.1))
|
||||||
|
|
||||||
# 3. Extract Results
|
|
||||||
status = pl.LpStatus[prob.status]
|
status = pl.LpStatus[prob.status]
|
||||||
solution = [pl.value(var) for var in x] # The values of the decision variables (will be 0 or 1)
|
solution = [pl.value(var) for var in x] # The values of the decision variables (will be 0 or 1)
|
||||||
|
|
||||||
@ -588,10 +579,6 @@ class Optimizer:
|
|||||||
timeout = 40
|
timeout = 40
|
||||||
while circles is not None :
|
while circles is not None :
|
||||||
i += 1
|
i += 1
|
||||||
# print(f"Iteration {i} of fixing circles")
|
|
||||||
# l, b = self.prevent_config(solution)
|
|
||||||
# prob += (pl.lpSum([l[j] * x[j] for j in range(L*L)]) == b)
|
|
||||||
|
|
||||||
if i == timeout :
|
if i == timeout :
|
||||||
self.logger.error(f'Timeout: No solution found after {timeout} iterations.')
|
self.logger.error(f'Timeout: No solution found after {timeout} iterations.')
|
||||||
raise TimeoutError(f"Optimization took too long. No solution found after {timeout} iterations.")
|
raise TimeoutError(f"Optimization took too long. No solution found after {timeout} iterations.")
|
||||||
@ -611,7 +598,6 @@ class Optimizer:
|
|||||||
if circles is None :
|
if circles is None :
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
# Sort the landmarks in the order of the solution
|
# Sort the landmarks in the order of the solution
|
||||||
order = self.get_order(solution)
|
order = self.get_order(solution)
|
||||||
tour = [landmarks[i] for i in order]
|
tour = [landmarks[i] for i in order]
|
||||||
|
@ -4,7 +4,6 @@ from math import pi
|
|||||||
import yaml
|
import yaml
|
||||||
from shapely import buffer, LineString, Point, Polygon, MultiPoint, concave_hull
|
from shapely import buffer, LineString, Point, Polygon, MultiPoint, concave_hull
|
||||||
|
|
||||||
|
|
||||||
from ..structs.landmark import Landmark
|
from ..structs.landmark import Landmark
|
||||||
from .get_time_distance import get_time
|
from .get_time_distance import get_time
|
||||||
from .take_most_important import take_most_important
|
from .take_most_important import take_most_important
|
||||||
|
Loading…
x
Reference in New Issue
Block a user