from __future__ import (
annotations,
)
import logging
from abc import (
ABC,
abstractmethod,
)
from statistics import (
mean,
)
from typing import (
TYPE_CHECKING,
)
from .constants import (
MAX_FLOAT,
MIN_FLOAT,
OptimizationDirection,
)
if TYPE_CHECKING:
from typing import (
Optional,
List,
Iterable,
)
from .routes import Route
logger = logging.getLogger(__name__)
[docs]class RouteCriterion(ABC):
[docs] def __init__(self, name: str, direction: OptimizationDirection, *args, **kwargs):
self.name = name
self.direction = direction
[docs] @abstractmethod
def scoring(self, route: Route) -> float:
pass
[docs] def best(self, *args: Optional[Route]) -> Route:
return self.direction((arg for arg in args if arg is not None), key=self.scoring, default=None,)
[docs] def sorted(self, routes: Iterable[Route], inplace: bool = False) -> List[Route]:
return self.direction.sorted(routes, key=self.scoring, inplace=inplace)
[docs] def nbest(self, n: int, routes: Iterable[Route], inplace: bool = False) -> List[Route]:
return self.direction.nbest(n, routes, key=self.scoring, inplace=inplace)
[docs]class EarliestLastDepartureTimeRouteCriterion(RouteCriterion):
[docs] def __init__(self, *args, **kwargs):
super().__init__(
direction=OptimizationDirection.MINIMIZATION, name="Shortest-Time", *args, **kwargs,
)
[docs] def scoring(self, route: Route) -> float:
if not route.feasible:
return MAX_FLOAT
return route.last_departure_time
[docs]class ShortestAveragePlannerTripDurationCriterion(RouteCriterion):
[docs] def __init__(self, *args, **kwargs):
super().__init__(
direction=OptimizationDirection.MINIMIZATION, name="Shortest-Time", *args, **kwargs,
)
[docs] def scoring(self, route: Route) -> float:
if not route.feasible:
return MAX_FLOAT
if not any(route.planned_trips):
return 0
return mean(planned_trip.duration for planned_trip in route.planned_trips)
[docs]class ShortestTimeRouteCriterion(RouteCriterion):
[docs] def __init__(self, *args, **kwargs):
super().__init__(
direction=OptimizationDirection.MINIMIZATION, name="Shortest-Time", *args, **kwargs,
)
[docs] def scoring(self, route: Route) -> float:
if not route.feasible:
return MAX_FLOAT
return route.duration
[docs]class LongestTimeRouteCriterion(RouteCriterion):
[docs] def __init__(self, *args, **kwargs):
super().__init__(
direction=OptimizationDirection.MAXIMIZATION, name="Longest-Time", *args, **kwargs,
)
[docs] def scoring(self, route: Route) -> float:
if not route.feasible:
return MIN_FLOAT
return route.duration
[docs]class LongestUtilTimeRouteCriterion(RouteCriterion):
[docs] def __init__(self, *args, **kwargs):
super().__init__(
direction=OptimizationDirection.MAXIMIZATION, name="Longest-Util-Time", *args, **kwargs,
)
[docs] def scoring(self, route: Route) -> float:
if not route.feasible:
return MIN_FLOAT
scoring = 0.0
for trip in route.trips:
scoring += trip.distance
return scoring
[docs]class HashCodeRouteCriterion(RouteCriterion):
[docs] def __init__(self, *args, **kwargs):
super().__init__(
direction=OptimizationDirection.MAXIMIZATION, name="Longest-Time", *args, **kwargs,
)
[docs] def scoring(self, route: Route) -> float:
if not route.feasible:
return MIN_FLOAT
scoring = 0.0
for planned_trip in route.planned_trips:
scoring += planned_trip.distance
if planned_trip.pickup_time == planned_trip.trip.origin_earliest:
scoring += planned_trip.trip.on_time_bonus
return scoring