Beispiel #1
0
    def __is_time_to_load_truck(self, truck_id: int) -> bool:
        time_threshold = copy.copy(self.current_time).add_hours(1).add_minutes(15)
        truck_capacity = self.trucks[truck_id].package_capacity

        def __package_needs_delivery(p: Package) -> bool:
            return p.status is PackageStatus.PENDING or p.status is PackageStatus.READY_FOR_PICKUP

        packages_to_deliver = list(filter(__package_needs_delivery, self.packages.values()))
        min_delivery_deadline = time_threshold
        min_arrival_time = time_threshold
        assigned_truck_ids = []

        for package in packages_to_deliver:
            # Are the delivery deadlines close enough to the current time that a truck should be loaded
            if package.delivery_deadline != Time(0) and package.delivery_deadline < min_delivery_deadline:
                min_delivery_deadline = package.delivery_deadline
            # Are the arrival times of any packages close enough to wait to load the truck
            if package.arrival_time != Time(0) and package.arrival_time < min_arrival_time:
                if package.arrival_time > self.current_time:
                    min_arrival_time = package.arrival_time
            # What truck ids are packages assigned to
            if package.truck_id is not None:
                assigned_truck_ids.append(package.truck_id)

        # Determine if we can fit all packages on a truck and one or more have a truck already assigned
        remaining_packages_have_truck = len(packages_to_deliver) <= truck_capacity and len(assigned_truck_ids) > 0

        if min_delivery_deadline < time_threshold:
            return True
        elif min_arrival_time < time_threshold:
            return False
        elif remaining_packages_have_truck and truck_id not in assigned_truck_ids:
            return False
        else:
            return True
Beispiel #2
0
    def update_state(self, current_time: Time) -> None:
        # If at Hub wait
        if self.status is TruckStatus.AT_HUB:
            return
        # If out for deliveries
        if self.status is TruckStatus.OUT_FOR_DELIVERIES and self.next_destination is not None:
            # If next destination set and the arrival time hasn't occurred, wait
            if self.arrival_time > current_time:
                return
            # Else we have arrived
            else:
                # update package status, log package change, and pop id off package_ids
                debug(
                    f"Delivering Package {self.package_ids[0]} at {current_time}"
                )
                package_id_to_deliver = self.package_ids.pop(0)
                package_to_deliver = self.hub.packages[package_id_to_deliver]
                delivery_deadline = package_to_deliver.delivery_deadline

                # Determine if the delivery is on time or late
                if delivery_deadline != Time(
                        0) and delivery_deadline < current_time:
                    package_to_deliver.status = PackageStatus.DELIVERED_LATE
                else:
                    package_to_deliver.status = PackageStatus.DELIVERED

                self.hub.log_status(package_to_deliver.id,
                                    package_to_deliver.status)

                # If packages set the next destination and arrival time
                if len(self.package_ids) > 0:
                    self.__set_next_delivery_destination(current_time)
                # If out of packages set next destination as Hub
                else:
                    self.__return_to_hub(current_time)
Beispiel #3
0
 def __init__(self, status: TruckStatus, miles_traveled: int,
              route_traveled: List[Location], next_destination: Location,
              arrival_time: Time, start_time: Time):
     self.status = status
     self.miles_traveled = miles_traveled
     self.route_traveled = route_traveled
     self.next_destination = next_destination
     self.arrival_time = arrival_time
     self.start_time = start_time
     self.end_time = Time()
Beispiel #4
0
    def print_stats(self, time: Time = Time(0)):
        time = self.current_time if time == Time(0) else time
        time_operated = time - self.start_time

        package_logs = [(p_id, self.__get_log_by_id(p_id, time, self.packages_log)) for p_id in self.packages.keys()]
        pending = [p_id for p_id, log in package_logs if log.status is PackageStatus.PENDING]
        out_for_delivery = [p_id for p_id, log in package_logs if log.status is PackageStatus.OUT_FOR_DELIVERY]
        delivered = [p_id for p_id, log in package_logs if log.status is PackageStatus.DELIVERED]
        delivered_late = [p_id for p_id, log in package_logs if log.status is PackageStatus.DELIVERED_LATE]

        truck_logs = [(t_id, self.__get_log_by_id(t_id, time, self.trucks_log)) for t_id in self.trucks.keys()]
        total_miles_traveled = reduce(lambda stored, k_v: stored + k_v[1].miles_traveled, truck_logs, 0.0)
        trucks_used = [t_id for t_id, log in truck_logs if log.miles_traveled > 0.0]

        # Build package status details to print for each truck used
        trucks_str = ''

        def is_on_truck(package_id: int):
            return self.packages[package_id].truck_id == truck_id

        for truck_id in trucks_used:
            trucks_str += f"Truck {truck_id}:\n"
            trucks_str += ''.join([
                f"\tPackages out for delivery: {[p_id for p_id in out_for_delivery if is_on_truck(p_id)]}\n"
                f"\tPackages delivered: {[p_id for p_id in delivered if is_on_truck(p_id)]}\n",
                f"\tPackages delivered late: {[p_id for p_id in delivered_late if is_on_truck(p_id)]}\n",
            ])

        hub_stats = ''.join([
            f"{'-' * 32} Today the WGUPS Hub {'-' * 32}\n",
            f"Hours of operation: {self.start_time} to {time}",
            f"\t({time_operated[0]} Hrs and {time_operated[1]} Min)\n",
            f"Total number of packages handled: {len(self.packages)}\n",
            f"Number of trucks used: {len(trucks_used)}\n",
            f"Packages pending: {pending}\n",
            trucks_str,
            f"Total miles traveled for deliveries: {total_miles_traveled:.2f} Miles\n",
            '-' * 85
        ])

        print(hub_stats)
Beispiel #5
0
 def __init_package_data(self) -> None:
     # Load package data into the Hub
     with open("../resources/wgups_package_data.csv", newline='') as package_data_file:
         reader = csv.reader(package_data_file, delimiter=',')
         # Skip header row
         next(reader, None)
         # For each package
         for row in reader:
             # Map csv row to array of proper types for easy Package initialization
             mapped_row: List[Any] = row[:]
             mapped_row[0] = int(mapped_row[0])
             mapped_row[4] = int(mapped_row[4])
             mapped_row[5] = Time(*Time.str_to_time_tuple(mapped_row[5]))
             mapped_row[6] = Time(*Time.str_to_time_tuple(mapped_row[6]))
             mapped_row[7] = float(mapped_row[7])
             mapped_row[8] = str_to_int_or_none(mapped_row[8])
             mapped_row[9] = str_to_int_or_none(mapped_row[9])
             # Create a new package object with the data
             new_package = Package(*mapped_row)
             # Store the package in the Hub for retrieval later
             self.packages.insert(new_package.id, new_package)
Beispiel #6
0
    def __calculate_delivery_order(self, truck_id: int, current_location: Location) -> None:
        truck = self.trucks[truck_id]
        package_ids = truck.package_ids

        debug("Starting delivery order: " + str(package_ids))
        debug("Total miles: " + str(self.__calculate_total_route_distance(package_ids)))

        for i in range(len(truck.package_ids)):
            min_dist_index = i
            # Everything before 'i' is sorted. Each time we start looking for the closest location to our
            # current location, we set the first index of out unsorted portion as the potential closest location.
            for j in range(i, len(truck.package_ids)):
                delivery_location = self.packages[package_ids[j]].location
                delivery_distance = self.locations[current_location][delivery_location]
                delivery_deadline = self.packages[package_ids[j]].delivery_deadline

                min_dist_location = self.packages[package_ids[min_dist_index]].location
                current_min_distance = self.locations[current_location][min_dist_location]
                current_deadline = self.packages[package_ids[min_dist_index]].delivery_deadline

                # Maintains deadline priorities
                no_deadlines = delivery_deadline == Time(0) and current_deadline == Time(0)
                keep_deadline = delivery_deadline <= current_deadline and delivery_deadline != Time(0)

                # If we find a location closer that does not have a later deadline
                if delivery_distance < current_min_distance and (no_deadlines or keep_deadline):
                    min_dist_index = j

            # Set the closest location found as the new end of our sorted list portion
            temp_id = package_ids[i]
            package_ids[i] = package_ids[min_dist_index]
            package_ids[min_dist_index] = temp_id

            # Set out new current location as the closest location selected
            # and set a new initial min distance index
            current_location = self.packages[package_ids[i]].location

        debug("Ending delivery order: " + str(package_ids))
        debug("Total miles: " + str(self.__calculate_total_route_distance(package_ids)))
Beispiel #7
0
from wgups.time import Time
from wgups.hub import Hub
from wgups.models import Location
from wgups.view import View
from wgups.utilities import parse_user_choice

# Ryan Dorman
# ID: 001002824

if __name__ == "__main__":
    # Initialize WGUPS HUB and start the simulation
    wgups_hub = Hub("WGUPS", Location("4001 South 700 East", "Salt Lake City", "UT", 84107), Time(8))

    # Run the simulation
    wgups_hub.run()

    # Create View to allow easy access to pre-defined ui components
    view = View()

    # Controller logic to allow the user to use the view to query data stored in models
    # associated with the Hub, Truck, and Packages
    while True:
        view.print_logo()
        wgups_hub.print_stats()
        user_choice = parse_user_choice(input(view.main_menu))

        if user_choice == 0:  # Exit
            break
        elif user_choice == 1:  # View Hub info
            view.print_hr()
            time_filter = input(view.time_filter_prompt)
Beispiel #8
0
 def __init__(self, status: PackageStatus, start_time: Time):
     self.status = status
     self.start_time = start_time
     self.end_time = Time()
Beispiel #9
0
    def __load_truck_with_packages(self, truck_id: int) -> None:
        # Ensure truck is at the HUB, has package space remaining, and its an optimal time to load
        truck = self.trucks[truck_id]
        package_capacity_left = truck.package_capacity - len(truck.package_ids)
        no_capacity = package_capacity_left == 0
        truck_out = truck.status is not TruckStatus.AT_HUB

        if truck_out or no_capacity or not self.__is_time_to_load_truck(truck_id):
            return

        # Get ids for packages ready to go out for delivery, keep track of any packages
        # not going on this truck that belong to a package group. The remaining group
        # members will need to be removed too.
        groups_to_remove = []

        def __can_be_loaded(package_id: int) -> bool:
            potential_package = self.packages[package_id]
            ready = potential_package.status is PackageStatus.READY_FOR_PICKUP
            ok_for_truck_id = potential_package.truck_id is None or potential_package.truck_id == truck_id
            package_group_id = potential_package.group_id

            if (not ready or not ok_for_truck_id) and package_group_id is not None:
                groups_to_remove.append(package_group_id)

            return ready and ok_for_truck_id

        # Narrow down packages currently ready to go out for delivery. Space complexity
        # of O(1) and time complexity of O(n).
        package_ids_to_deliver = list(filter(__can_be_loaded, self.packages.keys()))

        # Filter out and group members of packages we can't deliver currently. Space
        # complexity of O(1) and time complexity of O(n).
        package_ids_to_deliver = list(filter(lambda package_id: self.packages[package_id].group_id not in groups_to_remove,
                                          package_ids_to_deliver))

        # If no packages meet our criteria we can stop here, otherwise we start prioritizing packages
        # to determine what gets loaded.
        if len(package_ids_to_deliver) == 0:
            return

        # We want to ensure any packages that are required to be on a certain truck get loaded when that
        # truck is docked.
        def truck_id_cmp(package_id: int) -> Optional[int]:
            truck_id_to_sort = self.packages[package_id].truck_id
            return truck_id_to_sort is None, truck_id_to_sort

        # First, we sort by delivery location so when possible we can load packages being delivered to the
        # same location on one truck. We then prioritize any packages that must be delivered on the truck being loaded.
        # Space complexity of O(1) and time complexity O(nlogn).
        package_ids_to_deliver.sort(key=lambda package_id: (self.packages[package_id].location.key(),
                                                         truck_id_cmp(package_id)))

        # Identify all packages to be delivered that have deadlines and those that have no deadlines.
        # Space complexity of O(1) and time complexity of O(n).
        package_ids_w_deadline = list(filter(lambda package_id: self.packages[package_id].delivery_deadline != Time(0),
                                          package_ids_to_deliver))
        package_ids_no_deadline = list(filter(lambda package_id: self.packages[package_id].delivery_deadline == Time(0),
                                           package_ids_to_deliver))

        # Last, sort the list of
        # package ids based on delivery deadlines so we end up with a list
        # of package ids sorted by deadline, same location, required truck id. # Space complexity
        # of O(1) and time complexity O(nlogn).
        package_ids_w_deadline.sort(key=lambda package_id: self.packages[package_id].delivery_deadline)

        # Combine package ids with deadlines and those without into one list with the highest priority package id
        # as the first element.
        package_ids_to_deliver_by_deadline = [*package_ids_w_deadline, *package_ids_no_deadline]

        # While the truck has space and there are packages to go out we load the truck.
        while package_capacity_left > 0 and len(package_ids_to_deliver) > 0:
            # Get the package id with the next highest priority to potentially load
            potential_package_id = package_ids_to_deliver_by_deadline[0]

            # If this package belongs to a group and the truck can fit
            # all those in the group, load them. Space complexity of O(1) and
            # time complexity of O(n).
            group_id = self.packages[potential_package_id].group_id
            packages_loaded = []
            if group_id is not None:
                packages_in_group = list(
                    filter(lambda package_id: self.packages[package_id].group_id == group_id, package_ids_to_deliver))
                if len(packages_in_group) <= package_capacity_left:
                    for p_id in packages_in_group:
                        truck.package_ids.append(p_id)
                        packages_loaded.append(p_id)
            # Otherwise add this package with no group to the truck alone
            else:
                truck.package_ids.append(potential_package_id)
                packages_loaded.append(potential_package_id)

            # Remove all packages loaded from the lists to load and update Package statuses and log the changes.
            # Space complexity of O(1) and time complexity of O(n).
            for loaded_id in packages_loaded:
                package = self.packages[loaded_id]
                package.status = PackageStatus.OUT_FOR_DELIVERY
                package.truck_id = truck_id
                self.log_status(loaded_id, package.status)

                package_ids_to_deliver.remove(loaded_id)
                package_ids_to_deliver_by_deadline.remove(loaded_id)

            package_capacity_left -= len(packages_loaded)

        # If any packages were loaded onto the truck calculate the delivery order and send the truck out to deliver
        if len(truck.package_ids) > 0:
            self.__calculate_delivery_order(truck_id, self.location)
            self.trucks[truck_id].depart(self.current_time)
Beispiel #10
0
 def __get_log_by_id(self, item_id: int, time: Time, logs: ChainingHashTable) -> StatusLog:
     items_log = logs[item_id]
     for log in items_log:
         before_log_end = log.end_time == Time(0) or time < log.end_time
         if log.start_time <= time and before_log_end:
             return log