class TravelRequestsSimulator(object):
    def __init__(self):
        self.mongodb_database_connection = MongodbDatabaseConnection(host=mongodb_host, port=mongodb_port)
        log(module_name='travel_requests_simulator', log_type='DEBUG',
            log_message='mongodb_database_connection: established')

    def clear_travel_requests_collection(self):
        """
        Clear all the documents of the TravelRequests collection.

        :return: None
        """
        self.mongodb_database_connection.clear_travel_request_documents_collection()
        log(module_name='travel_requests_simulator', log_type='DEBUG',
            log_message='clear_travel_request_documents_collection: ok')

    def delete_travel_request_documents(self, object_ids=None, client_ids=None, bus_line_ids=None,
                                        min_departure_datetime=None, max_departure_datetime=None):
        """
        Delete multiple travel_request_documents.

        :param object_ids: [ObjectId]
        :param client_ids: [int]
        :param bus_line_ids: [int]
        :param min_departure_datetime: datetime
        :param max_departure_datetime
        :return: None
        """
        self.mongodb_database_connection.delete_travel_request_documents(
            object_ids=object_ids,
            client_ids=client_ids,
            bus_line_ids=bus_line_ids,
            min_departure_datetime=min_departure_datetime,
            max_departure_datetime=max_departure_datetime
        )
        log(module_name='travel_requests_simulator', log_type='DEBUG',
            log_message='delete_travel_request_documents: ok')

    def generate_random_travel_request_documents(self, initial_datetime, min_number_of_travel_request_documents,
                                                 max_number_of_travel_request_documents):
        """
        Generate random number of travel_request_documents for each bus_line,
        for a 24hour period starting from a selected datetime, and store them at the
        corresponding collection of the System Database.

        :param initial_datetime: datetime
        :param min_number_of_travel_request_documents: int
        :param max_number_of_travel_request_documents: int
        :return: None
        """
        bus_lines = self.mongodb_database_connection.find_bus_line_documents()

        for bus_line in bus_lines:
            number_of_travel_request_documents = random.randint(
                min_number_of_travel_request_documents,
                max_number_of_travel_request_documents
            )
            self.generate_travel_request_documents(
                initial_datetime=initial_datetime,
                number_of_travel_request_documents=number_of_travel_request_documents,
                bus_line=bus_line
            )

    def generate_travel_request_documents(self, initial_datetime, number_of_travel_request_documents,
                                          bus_line=None, bus_line_id=None):
        """
        Generate a specific number of travel_request_documents, for the selected bus_line,
        for a 24hour period starting from a selected datetime, and store them at the
        corresponding collection of the System Database.

        :param initial_datetime: datetime
        :param number_of_travel_request_documents: int
        :param bus_line: bus_line_document
        :param bus_line_id: int
        :return: None
        """
        # 1: The inputs: initial_datetime, number_of_travel_request_documents, and (bus_line or bus_line_id)
        #    are provided to the Travel Requests Simulator, so as a specific number of travel_request_documents
        #    to be generated, for the selected bus_line, for a 24hour period starting from
        #    the selected datetime.
        #
        # 2: If the provided bus_line is None, then the Travel Requests Simulator retrieves from the System Database
        #    the bus_line which corresponds to the provided bus_line_id.
        #
        if bus_line is None and bus_line_id is None:
            return None
        elif bus_line is None:
            bus_line = self.mongodb_database_connection.find_bus_line_document(bus_line_id=bus_line_id)
        else:
            pass

        bus_stops = bus_line.get('bus_stops')
        number_of_bus_stops = len(bus_stops)

        # 3: The Travel Requests Simulator generates the travel_request_documents, taking into consideration
        #    the variation of transportation demand during the hours of the day.
        #
        # distribution_weighted_datetimes = [
        #     (initial_datetime + timedelta(hours=0), 1),
        #     (initial_datetime + timedelta(hours=1), 1),
        #     (initial_datetime + timedelta(hours=2), 1),
        #     (initial_datetime + timedelta(hours=3), 1),
        #     (initial_datetime + timedelta(hours=4), 1),
        #     (initial_datetime + timedelta(hours=5), 1),
        #     (initial_datetime + timedelta(hours=6), 1),
        #     (initial_datetime + timedelta(hours=7), 1),
        #     (initial_datetime + timedelta(hours=8), 1),
        #     (initial_datetime + timedelta(hours=9), 1),
        #     (initial_datetime + timedelta(hours=10), 1),
        #     (initial_datetime + timedelta(hours=11), 1),
        #     (initial_datetime + timedelta(hours=12), 1),
        #     (initial_datetime + timedelta(hours=13), 1),
        #     (initial_datetime + timedelta(hours=14), 1),
        #     (initial_datetime + timedelta(hours=15), 1),
        #     (initial_datetime + timedelta(hours=16), 1),
        #     (initial_datetime + timedelta(hours=17), 1),
        #     (initial_datetime + timedelta(hours=18), 1),
        #     (initial_datetime + timedelta(hours=19), 1),
        #     (initial_datetime + timedelta(hours=20), 1),
        #     (initial_datetime + timedelta(hours=21), 1),
        #     (initial_datetime + timedelta(hours=22), 1),
        #     (initial_datetime + timedelta(hours=23), 1)
        # ]
        distribution_weighted_datetimes = [
            (initial_datetime + timedelta(hours=i),
             travel_requests_simulator_datetime_distribution_weights[i]) for i in range(0, 24)
        ]
        datetime_population = [val for val, cnt in distribution_weighted_datetimes for i in range(cnt)]
        travel_request_documents = []
        maximum_client_id = self.mongodb_database_connection.get_maximum_or_minimum(collection='travel_request')

        for i in range(0, number_of_travel_request_documents):
            client_id = maximum_client_id + 1
            maximum_client_id = client_id
            starting_bus_stop_index = random.randint(0, number_of_bus_stops - 2)
            starting_bus_stop = bus_stops[starting_bus_stop_index]
            ending_bus_stop_index = random.randint(starting_bus_stop_index + 1, number_of_bus_stops - 1)
            ending_bus_stop = bus_stops[ending_bus_stop_index]
            additional_departure_time_interval = random.randint(0, 59)
            departure_datetime = (random.choice(datetime_population) +
                                  timedelta(minutes=additional_departure_time_interval))

            travel_request_document = {
                'client_id': client_id,
                'bus_line_id': bus_line_id,
                'starting_bus_stop': starting_bus_stop,
                'ending_bus_stop': ending_bus_stop,
                'departure_datetime': departure_datetime,
                'arrival_datetime': None,
                'starting_timetable_entry_index': None,
                'ending_timetable_entry_index': None
            }
            travel_request_documents.append(travel_request_document)

        # 4: The generated travel_request_documents are stored at the
        #    TravelRequests collection of the System Database.
        #
        self.mongodb_database_connection.insert_travel_request_documents(
            travel_request_documents=travel_request_documents
        )
class LookAheadHandler(object):
    def __init__(self):
        self.mongodb_database_connection = MongodbDatabaseConnection(host=mongodb_host, port=mongodb_port)
        log(module_name='look_ahead_handler', log_type='DEBUG',
            log_message='mongodb_database_connection: established')

    def generate_bus_line(self, bus_stop_names, bus_line_id=None):
        """
        Generate a bus_line, consisted of a bus_line_id and a list of bus_stops, and store it to the corresponding
        collection of the System Database. Moreover, identify all the possible waypoints between the bus_stops
        of the bus_line, and populate the BusStopWaypoints collection.

        :param bus_stop_names: [string]
        :param bus_line_id: int
        :return: None
        """
        # 1: The inputs: bus_line_id and bus_stop_names are provided to the function, so as as a bus_line
        #    with the corresponding bus_line_id and bus_stops to be generated.
        #
        # 2: The Look Ahead connects to the System Database and retrieves the bus_stops which correspond to
        #    the provided bus_stop_names. The function returns None and the bus_line is not generated,
        #    in case there is a bus_stop_name which does not correspond to a stored bus_stop.
        #
        if bus_line_id is None:
            maximum_bus_line_id = self.mongodb_database_connection.get_maximum_or_minimum(collection='bus_line')
            bus_line_id = maximum_bus_line_id + 1

        bus_stops = []

        for bus_stop_name in bus_stop_names:
            bus_stop_document = self.mongodb_database_connection.find_bus_stop_document(name=bus_stop_name)

            if bus_stop_document is None:
                log_message = 'find_bus_stop_document (mongodb_database) - name:', bus_stop_name, '- result: None'
                log(module_name='look_ahead_handler', log_type='DEBUG', log_message=log_message)
                return None
            else:
                bus_stops.append(bus_stop_document)

        # 3: The intermediate waypoints of the bus_routes, which are generated while combining starting and
        #    ending bus_stops of the bus_line, should be stored as bus_stop_waypoints_documents at the System Database.
        #    The Look Ahead checks the existing bus_stop_waypoints_documents and communicates with the Route Generator
        #    in order to identify the waypoints of the bus_routes which are not already stored. The newly generated
        #    bus_stop_waypoints_documents are getting stored to the corresponding collection of the System Database.
        #    The function returns None and the bus_line is not generated, in case the Route Generator can not identify
        #    a possible route in order to connect the bus_stops of the bus_line.
        #
        number_of_bus_stops = len(bus_stops)

        for i in range(0, number_of_bus_stops - 1):
            starting_bus_stop = bus_stops[i]
            ending_bus_stop = bus_stops[i + 1]

            bus_stop_waypoints_document = self.mongodb_database_connection.find_bus_stop_waypoints_document(
                starting_bus_stop=starting_bus_stop,
                ending_bus_stop=ending_bus_stop
            )
            if bus_stop_waypoints_document is None:
                route_generator_response = get_waypoints_between_two_bus_stops(
                    starting_bus_stop=starting_bus_stop,
                    ending_bus_stop=ending_bus_stop
                )
                if route_generator_response is None:
                    log(module_name='look_ahead_handler', log_type='DEBUG',
                        log_message='get_waypoints_between_two_bus_stops (route_generator): None')
                    return None
                else:
                    waypoints = route_generator_response.get('waypoints')

                    if len(waypoints) == 0:
                        log(module_name='look_ahead_handler', log_type='DEBUG',
                            log_message='get_waypoints_between_two_bus_stops (route_generator): None')
                        return None

                    lists_of_edge_object_ids = []

                    for list_of_edges in waypoints:
                        list_of_edge_object_ids = []

                        for edge in list_of_edges:
                            edge_object_id = edge.get('_id')
                            list_of_edge_object_ids.append(edge_object_id)

                        lists_of_edge_object_ids.append(list_of_edge_object_ids)

                    # waypoints: [[edge_object_id]]
                    #
                    waypoints = lists_of_edge_object_ids

                    self.mongodb_database_connection.insert_bus_stop_waypoints_document(
                        starting_bus_stop=starting_bus_stop,
                        ending_bus_stop=ending_bus_stop,
                        waypoints=waypoints
                    )

        # 4: The Look Ahead stores the newly generated bus_line_document, which is consisted of the bus_line_id
        #    and the list of bus_stops, to the corresponding collection of the System Database.
        #    In case there is an already existing bus_line_document, with the same bus_line_id,
        #    then the list of bus_stops gets updated.
        #
        bus_line_document = {'bus_line_id': bus_line_id, 'bus_stops': bus_stops}
        self.mongodb_database_connection.insert_bus_line_document(bus_line_document=bus_line_document)

        log(module_name='look_ahead_handler', log_type='DEBUG',
            log_message='insert_bus_line_document (mongodb_database): ok')

    def generate_timetables_for_bus_line(self, timetables_starting_datetime, timetables_ending_datetime,
                                         requests_min_departure_datetime, requests_max_departure_datetime,
                                         bus_line=None, bus_line_id=None):
        """
        Generate timetables for a bus_line, for a selected datetime period,
        evaluating travel_requests of a specific datetime period.

        - The input: timetables_starting_datetime and input: timetables_ending_datetime
          are provided to the function, so as timetables for the specific datetime period
          to be generated.

        - The input: requests_min_departure_datetime and input: requests_max_departure_datetime
          are provided to the function, so as travel_requests with departure_datetime corresponding
          to the the specific datetime period to be evaluated.

        - The input: bus_line or input: bus_line_id is provided to the function,
          so as timetables for the specific bus_line to be generated.

        :param timetables_starting_datetime: datetime
        :param timetables_ending_datetime: datetime
        :param requests_min_departure_datetime: datetime
        :param requests_max_departure_datetime: datetime
        :param bus_line: bus_line_document
        :param bus_line_id: int
        :return: None
        """

        maximum_timetable_id_in_database = self.mongodb_database_connection.get_maximum_or_minimum(
            collection='timetable'
        )

        # 1: The list of bus_stops corresponding to the provided bus_line is retrieved.
        #
        # bus_stop_document: {
        #     '_id', 'osm_id', 'name', 'point': {'longitude', 'latitude'}
        # }
        # bus_stops: [bus_stop_document]
        #
        if bus_line is None and bus_line_id is None:
            return None
        elif bus_line is None:
            bus_line = self.mongodb_database_connection.find_bus_line_document(bus_line_id=bus_line_id)
        else:
            bus_line_id = bus_line.get('bus_line_id')

        bus_stops = bus_line.get('bus_stops')

        # 2: The Look Ahead retrieves from the System Database the travel_requests with
        #    departure_datetime higher than requests_min_departure_datetime and
        #    lower than requests_max_departure_datetime.
        #
        # travel_request_document: {
        #     '_id', 'client_id', 'bus_line_id',
        #     'starting_bus_stop': {'_id', 'osm_id', 'name', 'point': {'longitude', 'latitude'}},
        #     'ending_bus_stop': {'_id', 'osm_id', 'name', 'point': {'longitude', 'latitude'}},
        #     'departure_datetime', 'arrival_datetime'
        # }
        # travel_requests: [travel_request_document]
        #
        travel_requests = self.mongodb_database_connection.find_travel_request_documents(
            bus_line_ids=[bus_line_id],
            min_departure_datetime=requests_min_departure_datetime,
            max_departure_datetime=requests_max_departure_datetime
        )

        # 3: (TimetableGenerator is initialized) The Look Ahead sends a request to the Route Generator so as
        #    to identify the less time-consuming bus_route between the bus_stops of bus_line,
        #    while taking into consideration the current levels of traffic density.
        #
        timetable_generator = TimetableGenerator(
            maximum_timetable_id_in_database=maximum_timetable_id_in_database,
            bus_line_id=bus_line_id,
            bus_stops=bus_stops,
            travel_requests=travel_requests
        )

        # The list of bus_stops of a bus_line might contain the same bus_stop_osm_ids more than once.
        # For this reason, each travel_request needs to be related with the correct index in the bus_stops list.
        # So, the values 'starting_timetable_entry_index' and 'ending_timetable_entry_index' are estimated.
        #
        correspond_travel_requests_to_bus_stops(
            travel_requests=timetable_generator.travel_requests,
            bus_stops=timetable_generator.bus_stops
        )

        # 4: Based on the response of the Route Generator, which includes details about the followed bus_route,
        #    and using only one bus vehicle, the Look Ahead generates some initial timetables which cover the
        #    whole datetime period from timetables_starting_datetime to timetables_ending_datetime.
        #    Initially, the list of travel requests of these timetables is empty, and the departure_datetime and
        #    arrival_datetime values of the timetable_entries are based exclusively on the details of the bus_route.
        #    In the next steps of the algorithm, these timetables are used in the initial clustering
        #    of the travel requests.
        #
        timetable_generator.timetables = generate_initial_timetables(
            bus_line_id=bus_line_id,
            timetables_starting_datetime=timetables_starting_datetime,
            timetables_ending_datetime=timetables_ending_datetime,
            route_generator_response=timetable_generator.route_generator_response
        )

        current_average_waiting_time_of_timetables = float('Inf')

        while True:
            new_timetables = generate_new_timetables_based_on_travel_requests(
                current_timetables=timetable_generator.timetables,
                travel_requests=timetable_generator.travel_requests
            )
            new_average_waiting_time_of_timetables = calculate_average_waiting_time_of_timetables_in_seconds(
                timetables=new_timetables
            )
            if new_average_waiting_time_of_timetables < current_average_waiting_time_of_timetables:
                timetable_generator.timetables = new_timetables
                current_average_waiting_time_of_timetables = new_average_waiting_time_of_timetables
                print_timetables(timetables=timetable_generator.timetables)
            else:
                break

        print_timetables(timetables=timetable_generator.timetables)

        self.mongodb_database_connection.delete_timetable_documents(
            bus_line_id=bus_line.get('bus_line_id')
        )
        self.mongodb_database_connection.insert_timetable_documents(
            timetable_documents=timetable_generator.timetables
        )
        log(module_name='look_ahead_handler', log_type='DEBUG',
            log_message='insert_timetable_documents (mongodb_database): ok')

    def generate_timetables_for_bus_lines(self, timetables_starting_datetime, timetables_ending_datetime,
                                          requests_min_departure_datetime, requests_max_departure_datetime):
        """
        Generate timetables for all bus_lines, for a selected datetime period,
        evaluating travel_requests of a specific datetime period.

        :param timetables_starting_datetime: datetime
        :param timetables_ending_datetime: datetime
        :param requests_min_departure_datetime: datetime
        :param requests_max_departure_datetime: datetime
        :return: None
        """
        bus_lines = self.mongodb_database_connection.find_bus_line_documents()

        for bus_line in bus_lines:
            self.generate_timetables_for_bus_line(
                bus_line=bus_line,
                timetables_starting_datetime=timetables_starting_datetime,
                timetables_ending_datetime=timetables_ending_datetime,
                requests_min_departure_datetime=requests_min_departure_datetime,
                requests_max_departure_datetime=requests_max_departure_datetime
            )

    def update_timetables_of_bus_line(self, bus_line=None, bus_line_id=None):
        """
        Update the timetables of a bus_line, taking into consideration the current levels of traffic_density.

        :param bus_line: bus_line_document
        :param bus_line_id: int
        :return: None
        """
        if bus_line is None and bus_line_id is None:
            return None
        elif bus_line is None:
            bus_line = self.mongodb_database_connection.find_bus_line_document(bus_line_id=bus_line_id)
        else:
            bus_line_id = bus_line.get('bus_line_id')

        bus_stops = bus_line.get('bus_stops')
        timetables = self.mongodb_database_connection.find_timetable_documents(bus_line_ids=[bus_line_id])
        travel_requests = get_travel_requests_of_timetables(timetables=timetables)

        timetable_updater = TimetableUpdater(
            bus_stops=bus_stops,
            timetables=timetables,
            travel_requests=travel_requests
        )
        update_entries_of_timetables(
            timetables=timetable_updater.timetables,
            route_generator_response=timetable_updater.route_generator_response
        )
        current_average_waiting_time_of_timetables = calculate_average_waiting_time_of_timetables_in_seconds(
            timetables=timetable_updater.timetables
        )
        print_timetables(timetables=timetable_updater.timetables)

        while True:
            new_timetables = generate_new_timetables_based_on_travel_requests(
                current_timetables=timetable_updater.timetables,
                travel_requests=timetable_updater.travel_requests
            )
            new_average_waiting_time_of_timetables = calculate_average_waiting_time_of_timetables_in_seconds(
                timetables=new_timetables
            )
            if new_average_waiting_time_of_timetables < current_average_waiting_time_of_timetables:
                timetable_updater.timetables = new_timetables
                current_average_waiting_time_of_timetables = new_average_waiting_time_of_timetables
                print_timetables(timetables=timetable_updater.timetables)
            else:
                break

        print_timetables(timetables=timetable_updater.timetables)

        self.mongodb_database_connection.delete_timetable_documents(
            bus_line_id=bus_line.get('bus_line_id')
        )
        self.mongodb_database_connection.insert_timetable_documents(
            timetable_documents=timetable_updater.timetables
        )
        log(module_name='look_ahead_handler', log_type='DEBUG',
            log_message='update_timetable_documents (mongodb_database): ok')

    def update_timetables_of_bus_lines(self):
        """
        Update the timetables of all bus_lines, taking into consideration the current levels of traffic_density.

        :return: None
        """
        bus_lines = self.mongodb_database_connection.find_bus_line_documents()

        for bus_line in bus_lines:
            self.update_timetables_of_bus_line(bus_line=bus_line)
Example #3
0
class LookAheadHandler(object):
    def __init__(self):
        self.mongodb_database_connection = MongodbDatabaseConnection(
            host=mongodb_host, port=mongodb_port)
        log(module_name='look_ahead_handler',
            log_type='DEBUG',
            log_message='mongodb_database_connection: established')

    def generate_bus_line(self, bus_stop_names, bus_line_id=None):
        """
        Generate a bus_line, consisted of a bus_line_id and a list of bus_stops, and store it to the corresponding
        collection of the System Database. Moreover, identify all the possible waypoints between the bus_stops
        of the bus_line, and populate the BusStopWaypoints collection.

        :param bus_stop_names: [string]
        :param bus_line_id: int
        :return: None
        """
        # 1: The inputs: bus_line_id and bus_stop_names are provided to the function, so as as a bus_line
        #    with the corresponding bus_line_id and bus_stops to be generated.
        #
        # 2: The Look Ahead connects to the System Database and retrieves the bus_stops which correspond to
        #    the provided bus_stop_names. The function returns None and the bus_line is not generated,
        #    in case there is a bus_stop_name which does not correspond to a stored bus_stop.
        #
        if bus_line_id is None:
            maximum_bus_line_id = self.mongodb_database_connection.get_maximum_or_minimum(
                collection='bus_line')
            bus_line_id = maximum_bus_line_id + 1

        bus_stops = []

        for bus_stop_name in bus_stop_names:
            bus_stop_document = self.mongodb_database_connection.find_bus_stop_document(
                name=bus_stop_name)

            if bus_stop_document is None:
                log_message = 'find_bus_stop_document (mongodb_database) - name:', bus_stop_name, '- result: None'
                log(module_name='look_ahead_handler',
                    log_type='DEBUG',
                    log_message=log_message)
                return None
            else:
                bus_stops.append(bus_stop_document)

        # 3: The intermediate waypoints of the bus_routes, which are generated while combining starting and
        #    ending bus_stops of the bus_line, should be stored as bus_stop_waypoints_documents at the System Database.
        #    The Look Ahead checks the existing bus_stop_waypoints_documents and communicates with the Route Generator
        #    in order to identify the waypoints of the bus_routes which are not already stored. The newly generated
        #    bus_stop_waypoints_documents are getting stored to the corresponding collection of the System Database.
        #    The function returns None and the bus_line is not generated, in case the Route Generator can not identify
        #    a possible route in order to connect the bus_stops of the bus_line.
        #
        number_of_bus_stops = len(bus_stops)

        for i in range(0, number_of_bus_stops - 1):
            starting_bus_stop = bus_stops[i]
            ending_bus_stop = bus_stops[i + 1]

            bus_stop_waypoints_document = self.mongodb_database_connection.find_bus_stop_waypoints_document(
                starting_bus_stop=starting_bus_stop,
                ending_bus_stop=ending_bus_stop)
            if bus_stop_waypoints_document is None:
                route_generator_response = get_waypoints_between_two_bus_stops(
                    starting_bus_stop=starting_bus_stop,
                    ending_bus_stop=ending_bus_stop)
                if route_generator_response is None:
                    log(module_name='look_ahead_handler',
                        log_type='DEBUG',
                        log_message=
                        'get_waypoints_between_two_bus_stops (route_generator): None'
                        )
                    return None
                else:
                    waypoints = route_generator_response.get('waypoints')

                    if len(waypoints) == 0:
                        log(module_name='look_ahead_handler',
                            log_type='DEBUG',
                            log_message=
                            'get_waypoints_between_two_bus_stops (route_generator): None'
                            )
                        return None

                    lists_of_edge_object_ids = []

                    for list_of_edges in waypoints:
                        list_of_edge_object_ids = []

                        for edge in list_of_edges:
                            edge_object_id = edge.get('_id')
                            list_of_edge_object_ids.append(edge_object_id)

                        lists_of_edge_object_ids.append(
                            list_of_edge_object_ids)

                    # waypoints: [[edge_object_id]]
                    #
                    waypoints = lists_of_edge_object_ids

                    self.mongodb_database_connection.insert_bus_stop_waypoints_document(
                        starting_bus_stop=starting_bus_stop,
                        ending_bus_stop=ending_bus_stop,
                        waypoints=waypoints)

        # 4: The Look Ahead stores the newly generated bus_line_document, which is consisted of the bus_line_id
        #    and the list of bus_stops, to the corresponding collection of the System Database.
        #    In case there is an already existing bus_line_document, with the same bus_line_id,
        #    then the list of bus_stops gets updated.
        #
        bus_line_document = {
            'bus_line_id': bus_line_id,
            'bus_stops': bus_stops
        }
        self.mongodb_database_connection.insert_bus_line_document(
            bus_line_document=bus_line_document)

        log(module_name='look_ahead_handler',
            log_type='DEBUG',
            log_message='insert_bus_line_document (mongodb_database): ok')

    def generate_timetables_for_bus_line(self,
                                         timetables_starting_datetime,
                                         timetables_ending_datetime,
                                         requests_min_departure_datetime,
                                         requests_max_departure_datetime,
                                         bus_line=None,
                                         bus_line_id=None):
        """
        Generate timetables for a bus_line, for a selected datetime period,
        evaluating travel_requests of a specific datetime period.

        - The input: timetables_starting_datetime and input: timetables_ending_datetime
          are provided to the function, so as timetables for the specific datetime period
          to be generated.

        - The input: requests_min_departure_datetime and input: requests_max_departure_datetime
          are provided to the function, so as travel_requests with departure_datetime corresponding
          to the the specific datetime period to be evaluated.

        - The input: bus_line or input: bus_line_id is provided to the function,
          so as timetables for the specific bus_line to be generated.

        :param timetables_starting_datetime: datetime
        :param timetables_ending_datetime: datetime
        :param requests_min_departure_datetime: datetime
        :param requests_max_departure_datetime: datetime
        :param bus_line: bus_line_document
        :param bus_line_id: int
        :return: None
        """

        maximum_timetable_id_in_database = self.mongodb_database_connection.get_maximum_or_minimum(
            collection='timetable')

        # 1: The list of bus_stops corresponding to the provided bus_line is retrieved.
        #
        # bus_stop_document: {
        #     '_id', 'osm_id', 'name', 'point': {'longitude', 'latitude'}
        # }
        # bus_stops: [bus_stop_document]
        #
        if bus_line is None and bus_line_id is None:
            return None
        elif bus_line is None:
            bus_line = self.mongodb_database_connection.find_bus_line_document(
                bus_line_id=bus_line_id)
        else:
            bus_line_id = bus_line.get('bus_line_id')

        bus_stops = bus_line.get('bus_stops')

        # 2: The Look Ahead retrieves from the System Database the travel_requests with
        #    departure_datetime higher than requests_min_departure_datetime and
        #    lower than requests_max_departure_datetime.
        #
        # travel_request_document: {
        #     '_id', 'client_id', 'bus_line_id',
        #     'starting_bus_stop': {'_id', 'osm_id', 'name', 'point': {'longitude', 'latitude'}},
        #     'ending_bus_stop': {'_id', 'osm_id', 'name', 'point': {'longitude', 'latitude'}},
        #     'departure_datetime', 'arrival_datetime'
        # }
        # travel_requests: [travel_request_document]
        #
        travel_requests = self.mongodb_database_connection.find_travel_request_documents(
            bus_line_ids=[bus_line_id],
            min_departure_datetime=requests_min_departure_datetime,
            max_departure_datetime=requests_max_departure_datetime)

        # 3: (TimetableGenerator is initialized) The Look Ahead sends a request to the Route Generator so as
        #    to identify the less time-consuming bus_route between the bus_stops of bus_line,
        #    while taking into consideration the current levels of traffic density.
        #
        timetable_generator = TimetableGenerator(
            maximum_timetable_id_in_database=maximum_timetable_id_in_database,
            bus_line_id=bus_line_id,
            bus_stops=bus_stops,
            travel_requests=travel_requests)

        # The list of bus_stops of a bus_line might contain the same bus_stop_osm_ids more than once.
        # For this reason, each travel_request needs to be related with the correct index in the bus_stops list.
        # So, the values 'starting_timetable_entry_index' and 'ending_timetable_entry_index' are estimated.
        #
        correspond_travel_requests_to_bus_stops(
            travel_requests=timetable_generator.travel_requests,
            bus_stops=timetable_generator.bus_stops)

        # 4: Based on the response of the Route Generator, which includes details about the followed bus_route,
        #    and using only one bus vehicle, the Look Ahead generates some initial timetables which cover the
        #    whole datetime period from timetables_starting_datetime to timetables_ending_datetime.
        #    Initially, the list of travel requests of these timetables is empty, and the departure_datetime and
        #    arrival_datetime values of the timetable_entries are based exclusively on the details of the bus_route.
        #    In the next steps of the algorithm, these timetables are used in the initial clustering
        #    of the travel requests.
        #
        timetable_generator.timetables = generate_initial_timetables(
            bus_line_id=bus_line_id,
            timetables_starting_datetime=timetables_starting_datetime,
            timetables_ending_datetime=timetables_ending_datetime,
            route_generator_response=timetable_generator.
            route_generator_response)

        current_average_waiting_time_of_timetables = float('Inf')

        while True:
            new_timetables = generate_new_timetables_based_on_travel_requests(
                current_timetables=timetable_generator.timetables,
                travel_requests=timetable_generator.travel_requests)
            new_average_waiting_time_of_timetables = calculate_average_waiting_time_of_timetables_in_seconds(
                timetables=new_timetables)
            if new_average_waiting_time_of_timetables < current_average_waiting_time_of_timetables:
                timetable_generator.timetables = new_timetables
                current_average_waiting_time_of_timetables = new_average_waiting_time_of_timetables
                print_timetables(timetables=timetable_generator.timetables)
            else:
                break

        print_timetables(timetables=timetable_generator.timetables)

        self.mongodb_database_connection.delete_timetable_documents(
            bus_line_id=bus_line.get('bus_line_id'))
        self.mongodb_database_connection.insert_timetable_documents(
            timetable_documents=timetable_generator.timetables)
        log(module_name='look_ahead_handler',
            log_type='DEBUG',
            log_message='insert_timetable_documents (mongodb_database): ok')

    def generate_timetables_for_bus_lines(self, timetables_starting_datetime,
                                          timetables_ending_datetime,
                                          requests_min_departure_datetime,
                                          requests_max_departure_datetime):
        """
        Generate timetables for all bus_lines, for a selected datetime period,
        evaluating travel_requests of a specific datetime period.

        :param timetables_starting_datetime: datetime
        :param timetables_ending_datetime: datetime
        :param requests_min_departure_datetime: datetime
        :param requests_max_departure_datetime: datetime
        :return: None
        """
        bus_lines = self.mongodb_database_connection.find_bus_line_documents()

        for bus_line in bus_lines:
            self.generate_timetables_for_bus_line(
                bus_line=bus_line,
                timetables_starting_datetime=timetables_starting_datetime,
                timetables_ending_datetime=timetables_ending_datetime,
                requests_min_departure_datetime=requests_min_departure_datetime,
                requests_max_departure_datetime=requests_max_departure_datetime
            )

    def update_timetables_of_bus_line(self, bus_line=None, bus_line_id=None):
        """
        Update the timetables of a bus_line, taking into consideration the current levels of traffic_density.

        :param bus_line: bus_line_document
        :param bus_line_id: int
        :return: None
        """
        if bus_line is None and bus_line_id is None:
            return None
        elif bus_line is None:
            bus_line = self.mongodb_database_connection.find_bus_line_document(
                bus_line_id=bus_line_id)
        else:
            bus_line_id = bus_line.get('bus_line_id')

        bus_stops = bus_line.get('bus_stops')
        timetables = self.mongodb_database_connection.find_timetable_documents(
            bus_line_ids=[bus_line_id])
        travel_requests = get_travel_requests_of_timetables(
            timetables=timetables)

        timetable_updater = TimetableUpdater(bus_stops=bus_stops,
                                             timetables=timetables,
                                             travel_requests=travel_requests)
        update_entries_of_timetables(
            timetables=timetable_updater.timetables,
            route_generator_response=timetable_updater.route_generator_response
        )
        current_average_waiting_time_of_timetables = calculate_average_waiting_time_of_timetables_in_seconds(
            timetables=timetable_updater.timetables)
        print_timetables(timetables=timetable_updater.timetables)

        while True:
            new_timetables = generate_new_timetables_based_on_travel_requests(
                current_timetables=timetable_updater.timetables,
                travel_requests=timetable_updater.travel_requests)
            new_average_waiting_time_of_timetables = calculate_average_waiting_time_of_timetables_in_seconds(
                timetables=new_timetables)
            if new_average_waiting_time_of_timetables < current_average_waiting_time_of_timetables:
                timetable_updater.timetables = new_timetables
                current_average_waiting_time_of_timetables = new_average_waiting_time_of_timetables
                print_timetables(timetables=timetable_updater.timetables)
            else:
                break

        print_timetables(timetables=timetable_updater.timetables)

        self.mongodb_database_connection.delete_timetable_documents(
            bus_line_id=bus_line.get('bus_line_id'))
        self.mongodb_database_connection.insert_timetable_documents(
            timetable_documents=timetable_updater.timetables)
        log(module_name='look_ahead_handler',
            log_type='DEBUG',
            log_message='update_timetable_documents (mongodb_database): ok')

    def update_timetables_of_bus_lines(self):
        """
        Update the timetables of all bus_lines, taking into consideration the current levels of traffic_density.

        :return: None
        """
        bus_lines = self.mongodb_database_connection.find_bus_line_documents()

        for bus_line in bus_lines:
            self.update_timetables_of_bus_line(bus_line=bus_line)
class TravelRequestsSimulator(object):
    def __init__(self):
        self.mongodb_database_connection = MongodbDatabaseConnection(host=mongodb_host, port=mongodb_port)
        log(
            module_name="travel_requests_simulator",
            log_type="DEBUG",
            log_message="mongodb_database_connection: established",
        )

    def clear_travel_requests_collection(self):
        """
        Clear all the documents of the TravelRequests collection.

        :return: None
        """
        self.mongodb_database_connection.clear_travel_request_documents_collection()
        log(
            module_name="travel_requests_simulator",
            log_type="DEBUG",
            log_message="clear_travel_request_documents_collection: ok",
        )

    def delete_travel_request_documents(
        self, object_ids=None, client_ids=None, line_ids=None, min_departure_datetime=None, max_departure_datetime=None
    ):
        """
        Delete multiple travel_request_documents.

        travel_request_document: {
            '_id', 'client_id', 'line_id',
            'starting_bus_stop': {'_id', 'osm_id', 'name', 'point': {'longitude', 'latitude'}},
            'ending_bus_stop': {'_id', 'osm_id', 'name', 'point': {'longitude', 'latitude'}},
            'departure_datetime', 'arrival_datetime',
            'starting_timetable_entry_index', 'ending_timetable_entry_index'
        }
        :param object_ids: [ObjectId]
        :param client_ids: [int]
        :param line_ids: [int]
        :param min_departure_datetime: datetime
        :param max_departure_datetime
        :return: None
        """
        self.mongodb_database_connection.delete_travel_request_documents(
            object_ids=object_ids,
            client_ids=client_ids,
            line_ids=line_ids,
            min_departure_datetime=min_departure_datetime,
            max_departure_datetime=max_departure_datetime,
        )
        log(
            module_name="travel_requests_simulator", log_type="DEBUG", log_message="delete_travel_request_documents: ok"
        )

    def generate_random_travel_request_documents(
        self, initial_datetime, min_number_of_travel_request_documents, max_number_of_travel_request_documents
    ):
        """
        Generate random number of travel_request_documents for each bus_line,
        for a 24hour period starting from a selected datetime, and store them at the
        corresponding collection of the System Database.

        bus_line_document: {
            '_id', 'line_id', 'bus_stops': [{'_id', 'osm_id', 'name', 'point': {'longitude', 'latitude'}}]
        }
        :param initial_datetime: datetime
        :param min_number_of_travel_request_documents: int
        :param max_number_of_travel_request_documents: int
        :return: None
        """
        bus_lines = self.mongodb_database_connection.find_bus_line_documents()

        for bus_line in bus_lines:
            number_of_travel_request_documents = random.randint(
                min_number_of_travel_request_documents, max_number_of_travel_request_documents
            )
            self.generate_travel_request_documents(
                initial_datetime=initial_datetime,
                number_of_travel_request_documents=number_of_travel_request_documents,
                bus_line=bus_line,
            )

    def generate_travel_request_documents(
        self, initial_datetime, number_of_travel_request_documents, bus_line=None, line_id=None
    ):
        """
        Generate a specific number of travel_request_documents, for the selected bus_line,
        for a 24hour period starting from a selected datetime, and store them at the
        corresponding collection of the System Database.

        bus_line_document: {
            '_id', 'line_id', 'bus_stops': [{'_id', 'osm_id', 'name', 'point': {'longitude', 'latitude'}}]
        }
        :param initial_datetime: datetime
        :param number_of_travel_request_documents: int
        :param bus_line: bus_line_document
        :param line_id: int
        :return: None
        """
        # 1: The inputs: initial_datetime, number_of_travel_request_documents, and (bus_line or line_id)
        #    are provided to the Travel Requests Simulator, so as a specific number of travel_request_documents
        #    to be generated, for the selected bus_line, for a 24hour period starting from
        #    the selected datetime.
        #
        # 2: If the provided bus_line is None, then the Travel Requests Simulator retrieves from the System Database
        #    the bus_line which corresponds to the provided line_id.
        #
        if bus_line is None and line_id is None:
            return None
        elif bus_line is None:
            bus_line = self.mongodb_database_connection.find_bus_line_document(line_id=line_id)
        else:
            pass

        bus_stops = bus_line.get("bus_stops")
        number_of_bus_stops = len(bus_stops)

        # 3: The Travel Requests Simulator generates the travel_request_documents, taking into consideration
        #    the variation of transportation demand during the hours of the day.
        #
        weighted_datetimes = [
            (initial_datetime + timedelta(hours=0), 1),
            (initial_datetime + timedelta(hours=1), 1),
            (initial_datetime + timedelta(hours=2), 1),
            (initial_datetime + timedelta(hours=3), 1),
            (initial_datetime + timedelta(hours=4), 1),
            (initial_datetime + timedelta(hours=5), 1),
            (initial_datetime + timedelta(hours=6), 1),
            (initial_datetime + timedelta(hours=7), 1),
            (initial_datetime + timedelta(hours=8), 1),
            (initial_datetime + timedelta(hours=9), 1),
            (initial_datetime + timedelta(hours=10), 1),
            (initial_datetime + timedelta(hours=11), 1),
            (initial_datetime + timedelta(hours=12), 1),
            (initial_datetime + timedelta(hours=13), 1),
            (initial_datetime + timedelta(hours=14), 1),
            (initial_datetime + timedelta(hours=15), 1),
            (initial_datetime + timedelta(hours=16), 1),
            (initial_datetime + timedelta(hours=17), 1),
            (initial_datetime + timedelta(hours=18), 1),
            (initial_datetime + timedelta(hours=19), 1),
            (initial_datetime + timedelta(hours=20), 1),
            (initial_datetime + timedelta(hours=21), 1),
            (initial_datetime + timedelta(hours=22), 1),
            (initial_datetime + timedelta(hours=23), 1),
        ]
        datetime_population = [val for val, cnt in weighted_datetimes for i in range(cnt)]
        travel_request_documents = []

        for i in range(0, number_of_travel_request_documents):
            client_id = i
            starting_bus_stop_index = random.randint(0, number_of_bus_stops - 2)
            starting_bus_stop = bus_stops[starting_bus_stop_index]
            ending_bus_stop_index = random.randint(starting_bus_stop_index + 1, number_of_bus_stops - 1)
            ending_bus_stop = bus_stops[ending_bus_stop_index]
            additional_departure_time_interval = random.randint(0, 59)
            departure_datetime = random.choice(datetime_population) + timedelta(
                minutes=additional_departure_time_interval
            )

            travel_request_document = {
                "client_id": client_id,
                "line_id": line_id,
                "starting_bus_stop": starting_bus_stop,
                "ending_bus_stop": ending_bus_stop,
                "departure_datetime": departure_datetime,
                "arrival_datetime": None,
                "starting_timetable_entry_index": None,
                "ending_timetable_entry_index": None,
            }
            travel_request_documents.append(travel_request_document)

        # 4: The generated travel_request_documents are stored at the
        #    TravelRequests collection of the System Database.
        #
        self.mongodb_database_connection.insert_travel_request_documents(
            travel_request_documents=travel_request_documents
        )
class TravelRequestsSimulator(object):
    def __init__(self):
        self.mongodb_database_connection = MongodbDatabaseConnection(
            host=mongodb_host, port=mongodb_port)
        log(module_name='travel_requests_simulator',
            log_type='DEBUG',
            log_message='mongodb_database_connection: established')

    def clear_travel_requests_collection(self):
        """
        Clear all the documents of the TravelRequests collection.

        :return: None
        """
        self.mongodb_database_connection.clear_travel_request_documents_collection(
        )
        log(module_name='travel_requests_simulator',
            log_type='DEBUG',
            log_message='clear_travel_request_documents_collection: ok')

    def delete_travel_request_documents(self,
                                        object_ids=None,
                                        client_ids=None,
                                        bus_line_ids=None,
                                        min_departure_datetime=None,
                                        max_departure_datetime=None):
        """
        Delete multiple travel_request_documents.

        :param object_ids: [ObjectId]
        :param client_ids: [int]
        :param bus_line_ids: [int]
        :param min_departure_datetime: datetime
        :param max_departure_datetime
        :return: None
        """
        self.mongodb_database_connection.delete_travel_request_documents(
            object_ids=object_ids,
            client_ids=client_ids,
            bus_line_ids=bus_line_ids,
            min_departure_datetime=min_departure_datetime,
            max_departure_datetime=max_departure_datetime)
        log(module_name='travel_requests_simulator',
            log_type='DEBUG',
            log_message='delete_travel_request_documents: ok')

    def generate_random_travel_request_documents(
            self, initial_datetime, min_number_of_travel_request_documents,
            max_number_of_travel_request_documents):
        """
        Generate random number of travel_request_documents for each bus_line,
        for a 24hour period starting from a selected datetime, and store them at the
        corresponding collection of the System Database.

        :param initial_datetime: datetime
        :param min_number_of_travel_request_documents: int
        :param max_number_of_travel_request_documents: int
        :return: None
        """
        bus_lines = self.mongodb_database_connection.find_bus_line_documents()

        for bus_line in bus_lines:
            number_of_travel_request_documents = random.randint(
                min_number_of_travel_request_documents,
                max_number_of_travel_request_documents)
            self.generate_travel_request_documents(
                initial_datetime=initial_datetime,
                number_of_travel_request_documents=
                number_of_travel_request_documents,
                bus_line=bus_line)

    def generate_travel_request_documents(self,
                                          initial_datetime,
                                          number_of_travel_request_documents,
                                          bus_line=None,
                                          bus_line_id=None):
        """
        Generate a specific number of travel_request_documents, for the selected bus_line,
        for a 24hour period starting from a selected datetime, and store them at the
        corresponding collection of the System Database.

        :param initial_datetime: datetime
        :param number_of_travel_request_documents: int
        :param bus_line: bus_line_document
        :param bus_line_id: int
        :return: None
        """
        # 1: The inputs: initial_datetime, number_of_travel_request_documents, and (bus_line or bus_line_id)
        #    are provided to the Travel Requests Simulator, so as a specific number of travel_request_documents
        #    to be generated, for the selected bus_line, for a 24hour period starting from
        #    the selected datetime.
        #
        # 2: If the provided bus_line is None, then the Travel Requests Simulator retrieves from the System Database
        #    the bus_line which corresponds to the provided bus_line_id.
        #
        if bus_line is None and bus_line_id is None:
            return None
        elif bus_line is None:
            bus_line = self.mongodb_database_connection.find_bus_line_document(
                bus_line_id=bus_line_id)
        else:
            pass

        bus_stops = bus_line.get('bus_stops')
        number_of_bus_stops = len(bus_stops)

        # 3: The Travel Requests Simulator generates the travel_request_documents, taking into consideration
        #    the variation of transportation demand during the hours of the day.
        #
        # distribution_weighted_datetimes = [
        #     (initial_datetime + timedelta(hours=0), 1),
        #     (initial_datetime + timedelta(hours=1), 1),
        #     (initial_datetime + timedelta(hours=2), 1),
        #     (initial_datetime + timedelta(hours=3), 1),
        #     (initial_datetime + timedelta(hours=4), 1),
        #     (initial_datetime + timedelta(hours=5), 1),
        #     (initial_datetime + timedelta(hours=6), 1),
        #     (initial_datetime + timedelta(hours=7), 1),
        #     (initial_datetime + timedelta(hours=8), 1),
        #     (initial_datetime + timedelta(hours=9), 1),
        #     (initial_datetime + timedelta(hours=10), 1),
        #     (initial_datetime + timedelta(hours=11), 1),
        #     (initial_datetime + timedelta(hours=12), 1),
        #     (initial_datetime + timedelta(hours=13), 1),
        #     (initial_datetime + timedelta(hours=14), 1),
        #     (initial_datetime + timedelta(hours=15), 1),
        #     (initial_datetime + timedelta(hours=16), 1),
        #     (initial_datetime + timedelta(hours=17), 1),
        #     (initial_datetime + timedelta(hours=18), 1),
        #     (initial_datetime + timedelta(hours=19), 1),
        #     (initial_datetime + timedelta(hours=20), 1),
        #     (initial_datetime + timedelta(hours=21), 1),
        #     (initial_datetime + timedelta(hours=22), 1),
        #     (initial_datetime + timedelta(hours=23), 1)
        # ]
        distribution_weighted_datetimes = [
            (initial_datetime + timedelta(hours=i),
             travel_requests_simulator_datetime_distribution_weights[i])
            for i in range(0, 24)
        ]
        datetime_population = [
            val for val, cnt in distribution_weighted_datetimes
            for i in range(cnt)
        ]
        travel_request_documents = []
        maximum_client_id = self.mongodb_database_connection.get_maximum_or_minimum(
            collection='travel_request')

        for i in range(0, number_of_travel_request_documents):
            client_id = maximum_client_id + 1
            maximum_client_id = client_id
            starting_bus_stop_index = random.randint(0,
                                                     number_of_bus_stops - 2)
            starting_bus_stop = bus_stops[starting_bus_stop_index]
            ending_bus_stop_index = random.randint(starting_bus_stop_index + 1,
                                                   number_of_bus_stops - 1)
            ending_bus_stop = bus_stops[ending_bus_stop_index]
            additional_departure_time_interval = random.randint(0, 59)
            departure_datetime = (
                random.choice(datetime_population) +
                timedelta(minutes=additional_departure_time_interval))

            travel_request_document = {
                'client_id': client_id,
                'bus_line_id': bus_line_id,
                'starting_bus_stop': starting_bus_stop,
                'ending_bus_stop': ending_bus_stop,
                'departure_datetime': departure_datetime,
                'arrival_datetime': None,
                'starting_timetable_entry_index': None,
                'ending_timetable_entry_index': None
            }
            travel_request_documents.append(travel_request_document)

        # 4: The generated travel_request_documents are stored at the
        #    TravelRequests collection of the System Database.
        #
        self.mongodb_database_connection.insert_travel_request_documents(
            travel_request_documents=travel_request_documents)