From 2a2a1d180937a4ab004faf3e16c01a9d01eb70f2 Mon Sep 17 00:00:00 2001 From: Serious-senpai <57554044+Serious-senpai@users.noreply.github.com> Date: Mon, 25 Dec 2023 13:43:06 +0700 Subject: [PATCH 1/2] Implement fined cost --- ts/d2d/mixins.py | 12 +-- ts/d2d/neighborhoods/factory.py | 6 ++ ts/d2d/neighborhoods/insert.py | 133 ++++++++++++++++---------- ts/d2d/neighborhoods/swap.py | 163 ++++++++++++++++++++++---------- 4 files changed, 206 insertions(+), 108 deletions(-) diff --git a/ts/d2d/mixins.py b/ts/d2d/mixins.py index f0636a0..254d2fb 100644 --- a/ts/d2d/mixins.py +++ b/ts/d2d/mixins.py @@ -13,14 +13,14 @@ class SolutionMetricsMixin(BaseMulticostComparison): __slots__ = ( - "__cost", + "_cost", "drone_timespans", "drone_waiting_times", "technician_timespans", "technician_waiting_times", ) if TYPE_CHECKING: - __cost: Optional[Tuple[float, float]] + _cost: Optional[Tuple[float, float]] drone_timespans: Final[Tuple[float, ...]] drone_waiting_times: Final[Tuple[Tuple[float, ...], ...]] technician_timespans: Final[Tuple[float, ...]] @@ -41,14 +41,14 @@ def __init__( self.technician_timespans = technician_timespans self.technician_waiting_times = technician_waiting_times - self.__cost = None + self._cost = None def cost(self) -> Tuple[float, float]: """The cost of the solution that this object represents.""" - if self.__cost is None: - self.__cost = ( + if self._cost is None: + self._cost = ( max(*self.drone_timespans, *self.technician_timespans), sum(sum(t) for t in self.drone_waiting_times) + sum(self.technician_waiting_times), ) - return self.__cost + return self._cost diff --git a/ts/d2d/neighborhoods/factory.py b/ts/d2d/neighborhoods/factory.py index 44af7aa..1273b83 100644 --- a/ts/d2d/neighborhoods/factory.py +++ b/ts/d2d/neighborhoods/factory.py @@ -8,6 +8,7 @@ __all__ = ("SolutionFactory",) +FINE_COEFFICIENT = 10 ** 6 class SolutionFactory(SolutionMetricsMixin): @@ -81,6 +82,11 @@ def __init__( self.__update_drones = update_drones self.__update_technicians = update_technicians + def add_violation(self, violation: float) -> None: + fined = FINE_COEFFICIENT * violation + cost = (self.cost()[0] + fined, self.cost()[1] + fined) + self._cost = cost + def from_solution(self, __s: D2DPathSolution, /) -> D2DPathSolution: if len(self.__append_drones) + len(self.__update_drones) > 0: _drone_paths = list(list(paths) for paths in __s.drone_paths) diff --git a/ts/d2d/neighborhoods/insert.py b/ts/d2d/neighborhoods/insert.py index 24ef0c2..5056009 100644 --- a/ts/d2d/neighborhoods/insert.py +++ b/ts/d2d/neighborhoods/insert.py @@ -163,9 +163,6 @@ def create_new() -> None: _first_path[first_point:first_point + neighborhood.length] = [] _second_path = (0,) + first_path[first_point:first_point + neighborhood.length] + (0,) - if solution.calculate_total_weight(_second_path) > second_config.capacity: - continue - first_arrival_timestamps = solution.calculate_drone_arrival_timestamps( _first_path, config_index=solution.drone_config_mapping[first_drone], @@ -204,6 +201,10 @@ def create_new() -> None: drone_timespans=tuple(_drone_timespans), drone_waiting_times=tuple(tuple(w) for w in _drone_waiting_times), ) + violation = (solution.calculate_total_weight(_second_path) - second_config.capacity) / second_config.capacity + if violation > 0: + factory.add_violation(violation) + if factory.add_to_pareto_set(results)[0]: swaps_mapping[factory] = ((first_path[first_point], first_path[first_point + neighborhood.length - 1]), 0) @@ -232,23 +233,6 @@ def create_new() -> None: offset=solution.drone_arrival_timestamps[second_drone][second_path_index - 1][-1] if second_path_index > 0 else 0.0, ) - # The first drone path was shortened, hence it will not violate any constraints as long as the initial - # path is also a feasible one - - if solution.calculate_total_weight(p2) > second_config.capacity: - continue - - if isinstance(second_config, DroneEnduranceConfig): - if solution.calculate_drone_flight_duration(p2, arrival_timestamps=second_arrival_timestamps) > second_config.fixed_time: - continue - - # No need to check for second_config.fixed_distance since the inserted segment also comes from another - # drone path - - else: - if solution.calculate_drone_energy_consumption(p2, config_index=solution.drone_config_mapping[second_drone]) > second_config.battery: - continue - _drone_timespans = list(solution.drone_timespans) _drone_timespans[first_drone] += first_arrival_timestamps[-1] - solution.drone_arrival_timestamps[first_drone][first_path_index][-1] _drone_timespans[second_drone] += second_arrival_timestamps[-1] - solution.drone_arrival_timestamps[second_drone][second_path_index][-1] @@ -264,6 +248,33 @@ def create_new() -> None: drone_timespans=tuple(_drone_timespans), drone_waiting_times=tuple(tuple(w) for w in _drone_waiting_times), ) + + # The first drone path was shortened, hence it will not violate any constraints as long as the initial + # path is also a feasible one + + violation = (solution.calculate_total_weight(p2) - second_config.capacity) / second_config.capacity + if violation > 0: + factory.add_violation(violation) + + if isinstance(second_config, DroneEnduranceConfig): + violation = ( + solution.calculate_drone_flight_duration(p2, arrival_timestamps=second_arrival_timestamps) + - second_config.fixed_time + ) / second_config.fixed_time + if violation > 0: + factory.add_violation(violation) + + # No need to check for second_config.fixed_distance since the inserted segment also comes from another + # drone path + + else: + violation = ( + solution.calculate_drone_energy_consumption(p2, config_index=solution.drone_config_mapping[second_drone]) + - second_config.battery + ) / second_config.battery + if violation > 0: + factory.add_violation(violation) + if factory.add_to_pareto_set(results)[0]: swaps_mapping[factory] = ((first_path[first_point], first_path[first_point + neighborhood.length - 1]), second_path[second_location]) @@ -348,9 +359,6 @@ def create_new() -> None: _tech_path[tech_point:tech_point + neighborhood.length] = [] _drone_path = (0,) + tuple(tech_path[tech_point:tech_point + neighborhood.length]) + (0,) - if solution.calculate_total_weight(_drone_path) > drone_config.capacity: - return - tech_arrival_timestamps = solution.calculate_technician_arrival_timestamps(_tech_path) drone_arrival_timestamps = solution.calculate_drone_arrival_timestamps( _drone_path, @@ -358,20 +366,6 @@ def create_new() -> None: offset=solution.drone_timespans[drone], ) - if isinstance(drone_config, DroneEnduranceConfig): - if solution.calculate_drone_flight_duration(_drone_path, arrival_timestamps=drone_arrival_timestamps) > drone_config.fixed_time: - return - - if solution.calculate_required_range(_drone_path) > drone_config.fixed_distance: - return - - else: - if solution.calculate_drone_energy_consumption( - _drone_path, - config_index=solution.drone_config_mapping[drone], - ) > drone_config.battery: - return - _technician_timespans = list(solution.technician_timespans) _technician_timespans[technician] = tech_arrival_timestamps[-1] @@ -397,6 +391,33 @@ def create_new() -> None: drone_timespans=tuple(_drone_timespans), drone_waiting_times=tuple(tuple(w) for w in _drone_total_waiting_times), ) + + violation = (solution.calculate_total_weight(_drone_path) - drone_config.capacity) / drone_config.capacity + if violation > 0: + factory.add_violation(violation) + + if isinstance(drone_config, DroneEnduranceConfig): + violation = ( + solution.calculate_drone_flight_duration(_drone_path, arrival_timestamps=drone_arrival_timestamps) + - drone_config.fixed_time + ) / drone_config.fixed_time + if violation > 0: + factory.add_violation(violation) + + violation = (solution.calculate_required_range(_drone_path) - drone_config.fixed_distance) / drone_config.fixed_distance + if violation > 0: + factory.add_violation(violation) + + else: + violation = ( + solution.calculate_drone_energy_consumption( + _drone_path, + config_index=solution.drone_config_mapping[drone], + ) - drone_config.battery + ) / drone_config.battery + if violation > 0: + factory.add_violation(violation) + if factory.add_to_pareto_set(results)[0]: swaps_mapping[factory] = ((tech_path[tech_point], tech_path[tech_point + neighborhood.length - 1]), 0) @@ -418,19 +439,6 @@ def create_new() -> None: config_index=solution.drone_config_mapping[drone], offset=solution.drone_arrival_timestamps[drone][drone_path_index - 1][-1] if drone_path_index > 0 else 0.0, ) - if solution.calculate_total_weight(_drone_path) > drone_config.capacity: - continue - - if isinstance(drone_config, DroneEnduranceConfig): - if solution.calculate_drone_flight_duration(_drone_path, arrival_timestamps=drone_arrival_timestamps) > drone_config.fixed_time: - continue - - if solution.calculate_required_range(_drone_path) > drone_config.fixed_distance: - continue - - else: - if solution.calculate_drone_energy_consumption(_drone_path, config_index=solution.drone_config_mapping[drone]) > drone_config.battery: - continue _technician_timespans = list(solution.technician_timespans) _technician_timespans[technician] = tech_arrival_timestamps[-1] @@ -455,6 +463,31 @@ def create_new() -> None: drone_timespans=tuple(_drone_timespans), drone_waiting_times=tuple(tuple(w) for w in _drone_total_waiting_times), ) + + violation = (solution.calculate_total_weight(_drone_path) - drone_config.capacity) / drone_config.capacity + if violation > 0: + factory.add_violation(violation) + + if isinstance(drone_config, DroneEnduranceConfig): + violation = ( + solution.calculate_drone_flight_duration(_drone_path, arrival_timestamps=drone_arrival_timestamps) + - drone_config.fixed_time + ) / drone_config.fixed_time + if violation > 0: + factory.add_violation(violation) + + violation = (solution.calculate_required_range(_drone_path) - drone_config.fixed_distance) / drone_config.fixed_distance + if violation > 0: + factory.add_violation(violation) + + else: + violation = ( + solution.calculate_drone_energy_consumption(_drone_path, config_index=solution.drone_config_mapping[drone]) + - drone_config.battery + ) / drone_config.battery + if violation > 0: + factory.add_violation(violation) + if factory.add_to_pareto_set(results)[0]: swaps_mapping[factory] = ((tech_path[tech_point], tech_path[tech_point + neighborhood.length - 1]), drone_path[drone_location]) diff --git a/ts/d2d/neighborhoods/swap.py b/ts/d2d/neighborhoods/swap.py index 6af41a1..42e0695 100644 --- a/ts/d2d/neighborhoods/swap.py +++ b/ts/d2d/neighborhoods/swap.py @@ -191,30 +191,6 @@ def swap_drone_drone(bundle: IPCBundle[Swap, List[Tuple[Tuple[int, int], Tuple[i config_index=solution.drone_config_mapping[second_drone], offset=solution.drone_arrival_timestamps[second_drone][second_path_index - 1][-1] if second_path_index > 0 else 0.0, ) - if solution.calculate_total_weight(_first_path) > first_config.capacity or solution.calculate_total_weight(_second_path) > second_config.capacity: - continue - - if isinstance(first_config, DroneEnduranceConfig): - if solution.calculate_drone_flight_duration(_first_path, arrival_timestamps=first_arrival_timestamps) > first_config.fixed_time: - continue - - if solution.calculate_required_range(_first_path) > first_config.fixed_distance: - continue - - else: - if solution.calculate_drone_energy_consumption(_first_path, config_index=solution.drone_config_mapping[first_drone]) > first_config.battery: - continue - - if isinstance(second_config, DroneEnduranceConfig): - if solution.calculate_drone_flight_duration(_second_path, arrival_timestamps=second_arrival_timestamps) > second_config.fixed_time: - continue - - if solution.calculate_required_range(_second_path) > second_config.fixed_distance: - continue - - else: - if solution.calculate_drone_energy_consumption(_second_path, config_index=solution.drone_config_mapping[second_drone]) > second_config.battery: - continue _drone_timespans = list(drone_timespans) _drone_timespans[first_drone] += first_arrival_timestamps[-1] - solution.drone_arrival_timestamps[first_drone][first_path_index][-1] @@ -232,6 +208,63 @@ def swap_drone_drone(bundle: IPCBundle[Swap, List[Tuple[Tuple[int, int], Tuple[i technician_waiting_times=solution.technician_waiting_times, ) + violation = ( + solution.calculate_total_weight(_first_path) + - first_config.capacity + ) / first_config.capacity + if violation > 0: + factory.add_violation(violation) + + violation = ( + solution.calculate_total_weight(_second_path) + - second_config.capacity + ) / second_config.capacity + if violation > 0: + factory.add_violation(violation) + + if isinstance(first_config, DroneEnduranceConfig): + violation = ( + solution.calculate_drone_flight_duration(_first_path, arrival_timestamps=first_arrival_timestamps) + - first_config.fixed_time + ) / first_config.fixed_time + if violation > 0: + factory.add_violation(violation) + + violation = ( + solution.calculate_required_range(_first_path) + - first_config.fixed_distance + ) / first_config.fixed_distance + if violation > 0: + factory.add_violation(violation) + + else: + violation = ( + solution.calculate_drone_energy_consumption(_first_path, config_index=solution.drone_config_mapping[first_drone]) + - first_config.battery + ) / first_config.battery + if violation > 0: + factory.add_violation(violation) + + if isinstance(second_config, DroneEnduranceConfig): + violation = ( + solution.calculate_drone_flight_duration(_second_path, arrival_timestamps=second_arrival_timestamps) + - second_config.fixed_time + ) / second_config.fixed_time + if violation > 0: + factory.add_violation(violation) + + violation = (solution.calculate_required_range(_second_path) - second_config.fixed_distance) / second_config.fixed_distance + if violation > 0: + factory.add_violation(violation) + + else: + violation = ( + solution.calculate_drone_energy_consumption(_second_path, config_index=solution.drone_config_mapping[second_drone]) + - second_config.battery + ) / second_config.battery + if violation > 0: + factory.add_violation(violation) + if factory.add_to_pareto_set(results)[0]: swaps_mapping[factory] = ( (first_path[first_start], first_path[first_start + first_length - 1]), @@ -329,20 +362,6 @@ def populate_results(technician: int, drone: int, drone_path_index: int, technic offset=solution.drone_arrival_timestamps[drone][drone_path_index - 1][-1] if drone_path_index > 0 else 0.0, ) - if solution.calculate_total_weight(_drone_path) > drone_config.capacity: - continue - - if isinstance(drone_config, DroneEnduranceConfig): - if solution.calculate_drone_flight_duration(_drone_path, arrival_timestamps=drone_arrival_timestamps) > drone_config.fixed_time: - continue - - if solution.calculate_required_range(_drone_path) > drone_config.fixed_distance: - continue - - else: - if solution.calculate_drone_energy_consumption(_drone_path, config_index=solution.drone_config_mapping[drone]) > drone_config.battery: - continue - _technician_timespans = list(solution.technician_timespans) _technician_timespans[technician] = technician_arrival_timestamps[-1] _drone_timespans = list(solution.drone_timespans) @@ -362,6 +381,33 @@ def populate_results(technician: int, drone: int, drone_path_index: int, technic technician_waiting_times=tuple(_technician_waiting_times), ) + violation = (solution.calculate_total_weight(_drone_path) - drone_config.capacity) / drone_config.capacity + if violation > 0: + factory.add_violation(violation) + + if isinstance(drone_config, DroneEnduranceConfig): + violation = ( + solution.calculate_drone_flight_duration(_drone_path, arrival_timestamps=drone_arrival_timestamps) + - drone_config.fixed_time + ) / drone_config.fixed_time + if violation > 0: + factory.add_violation(violation) + + violation = ( + solution.calculate_required_range(_drone_path) + - drone_config.fixed_distance + ) / drone_config.fixed_distance + if violation > 0: + factory.add_violation(violation) + + else: + violation = ( + solution.calculate_drone_energy_consumption(_drone_path, config_index=solution.drone_config_mapping[drone]) + - drone_config.battery + ) / drone_config.battery + if violation > 0: + factory.add_violation(violation) + if factory.add_to_pareto_set(results)[0]: swaps_mapping[factory] = ( (technician_path[technician_start], technician_path[technician_start + technician_length - 1]), @@ -408,20 +454,6 @@ def swap_drone_self(bundle: IPCBundle[Swap, List[Tuple[int, int]]]) -> Set[Tuple offset=offset, ) - if solution.calculate_total_weight(_path) > config.capacity: - continue - - if isinstance(config, DroneEnduranceConfig): - if solution.calculate_drone_flight_duration(_path, arrival_timestamps=arrival_timestamps) > config.fixed_time: - continue - - if solution.calculate_required_range(_path) > config.fixed_distance: - continue - - else: - if solution.calculate_drone_energy_consumption(_path, config_index=solution.drone_config_mapping[drone]) > config.battery: - continue - _drone_timespans = list(solution.drone_timespans) _drone_timespans[drone] += arrival_timestamps[-1] - solution.drone_arrival_timestamps[drone][path_index][-1] @@ -439,6 +471,33 @@ def swap_drone_self(bundle: IPCBundle[Swap, List[Tuple[int, int]]]) -> Set[Tuple technician_waiting_times=solution.technician_waiting_times, ) + violation = (solution.calculate_total_weight(_path) - config.capacity) / config.capacity + if violation > 0: + factory.add_violation(violation) + + if isinstance(config, DroneEnduranceConfig): + violation = ( + solution.calculate_drone_flight_duration(_path, arrival_timestamps=arrival_timestamps) + - config.fixed_time + ) / config.fixed_time + if violation > 0: + factory.add_violation(violation) + + violation = ( + solution.calculate_required_range(_path) + - config.fixed_distance + ) / config.fixed_distance + if violation > 0: + factory.add_violation(violation) + + else: + violation = ( + solution.calculate_drone_energy_consumption(_path, config_index=solution.drone_config_mapping[drone]) + - config.battery + ) / config.battery + if violation > 0: + factory.add_violation(violation) + if factory.add_to_pareto_set(results)[0]: swaps_mapping[factory] = ( (path[first_index], path[first_index + first_length - 1]), From bd66eb9bbb43b558197519750c437fed890388c2 Mon Sep 17 00:00:00 2001 From: Serious-senpai <57554044+Serious-senpai@users.noreply.github.com> Date: Tue, 26 Dec 2023 13:15:30 +0700 Subject: [PATCH 2/2] Increase fine coefficient to 10^9 --- ts/d2d/neighborhoods/factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/d2d/neighborhoods/factory.py b/ts/d2d/neighborhoods/factory.py index 1273b83..9a2c4b6 100644 --- a/ts/d2d/neighborhoods/factory.py +++ b/ts/d2d/neighborhoods/factory.py @@ -8,7 +8,7 @@ __all__ = ("SolutionFactory",) -FINE_COEFFICIENT = 10 ** 6 +FINE_COEFFICIENT = 10 ** 9 class SolutionFactory(SolutionMetricsMixin):