We are hiring ! See our job offers.
Raw File
temporal_distances.py
import numpy as np

import backward_BFS as Bbfs
from utils import util


class ShortestPath:
    def __init__(self):
        self.__distances = []
        self.__eccentricity = None
        self.__idx_farther = None
        self.__reachables = 0
        self.__min_ecc = np.NINF  # minimum possible eccentricity
        self.__name = 'SP'  # Shortest Path
        self.__opposite_path = None

    def get_distances(self):
        return self.__distances

    def get_idx_farther(self):
        return self.__idx_farther

    def get_eccentricity(self):
        return self.__eccentricity

    def compare_eccentricity(self, ecc, idx):
        if self.__eccentricity > ecc:
            return self.__eccentricity, self.__idx_farther
        else:
            return ecc, idx

    def get_min_ecc(self):
        return self.__min_ecc

    def get_reachables(self):
        return self.__reachables

    def get_name(self):
        return self.__name

    def gen_opposite(self, folder, file):
        self.__opposite_path = util.opposite_graph_ascend_traversal(folder=folder, file=file)
        return self.__opposite_path

    def __compute_sp(self, u, v, t, traversal_time, start_node, list_of_sorted_lists):
        # print(list_of_sorted_lists)
        if u == start_node:
            if (0, t) not in list_of_sorted_lists[start_node]:
                list_of_sorted_lists[start_node].append((0, t))

        if list_of_sorted_lists[u]:  # check if list is not empty
            for i, elem in enumerate(list_of_sorted_lists[u]):
                if elem[1] <= t:
                    new_d = elem[0] + traversal_time
                    new_arrival_time = t + traversal_time
                    inserted_new = False  # True if new element is inserted
                    if not list_of_sorted_lists[v]:  # Check if list is empty
                        list_of_sorted_lists[v].append((new_d, new_arrival_time))
                        inserted_new = True
                    else:
                        same_arrival = False  # True if there is another elem. with same arrival time into the list
                        for j, e in enumerate(list_of_sorted_lists[v]):
                            if e[1] == new_arrival_time:
                                if e[0] > new_d:
                                    list_of_sorted_lists[v][j] = (new_d, new_arrival_time)
                                    inserted_new = True
                                same_arrival = True
                        if not same_arrival:

                            # check if new path is dominated
                            new_is_dominated = False
                            for pair in list_of_sorted_lists[v]:
                                if (pair[0] < new_d and pair[1] <= new_arrival_time) or (
                                        pair[0] == new_d and pair[1] < new_arrival_time):
                                    new_is_dominated = True
                            if not new_is_dominated:

                                list_of_sorted_lists[v].append((new_d, new_arrival_time))
                                inserted_new = True
                    if inserted_new:
                        # keep only not dominated paths
                        list_of_sorted_lists[v][:] = [e for e in list_of_sorted_lists[v] if not (
                                (e[0] > new_d and e[1] >= new_arrival_time) or (
                                                              e[0] == new_d and e[1] > new_arrival_time))]

                        # If the minimum duration changes, update it
                        if new_d < self.__distances[v]:
                            self.__distances[v] = new_d
                        # sort list
                        list_of_sorted_lists[v].sort(key=lambda tup: tup[0])
                    break
                else:
                    continue

    def compute_distances(self, graph, start_node, min_time=None, max_time=None):
        """
        :param graph: temporal graph
        :param start_node: node to start from
        :param min_time: lower bound time interval
        :param max_time: upper bound time interval
        :return: numpy array of duration of the fastest path from start_node to every nodes v in V within time interval
        """

        if graph.get_latest_node() is not None:
            raise Exception('Shortest path does not work with dummy nodes, give me in input weighted graph!')

        if min_time is None:
            min_time, _ = graph.get_time_interval()
        if max_time is None:
            _, max_time = graph.get_time_interval()

        list_of_sorted_lists = []
        for elem in range(graph.get_num_nodes()):
            list_of_sorted_lists.append([])

        self.__eccentricity = 0
        self.__idx_farther = start_node
        self.__distances = np.full((graph.get_num_nodes(),), np.inf)
        self.__distances[start_node] = 0

        for line in graph.graph_reader():
            if not line.strip():  # check if line is blank
                break
            li = line.split()
            u = int(li[0])
            v = int(li[1])
            t = int(li[2])

            if len(li) == 4:
                traversal_time = int(li[3])
            else:
                traversal_time = 1

            if t >= min_time and t + traversal_time <= max_time:
                self.__compute_sp(u=u, v=v, t=t, traversal_time=traversal_time, start_node=start_node,
                                  list_of_sorted_lists=list_of_sorted_lists)
                if not graph.get_is_directed():
                    self.__compute_sp(u=v, v=u, t=t, traversal_time=traversal_time, start_node=start_node,
                                      list_of_sorted_lists=list_of_sorted_lists)
            elif t >= max_time:
                break

        # To handle dummy nodes: discard them from the distances vector
        # if graph.get_latest_node() is not None:
        #    self.__distances = self.__distances[:graph.get_latest_node()]

        self.__idx_farther, self.__eccentricity, self.__reachables = util.get_max_ind(self.__distances, np.NINF)


class FastestPathOnePass:
    def __init__(self):
        self.__distances = []
        self.__eccentricity = None
        self.__idx_farther = None
        self.__min_ecc = np.NINF  # minimum possible eccentricity
        self.__reachables = 0
        self.__name = 'FP'  # Fastest Path
        self.__opposite_path = None

    def get_distances(self):
        return self.__distances

    def get_idx_farther(self):
        return self.__idx_farther

    def get_eccentricity(self):
        return self.__eccentricity

    def compare_eccentricity(self, ecc, idx):
        if self.__eccentricity > ecc:
            return self.__eccentricity, self.__idx_farther
        else:
            return ecc, idx

    def get_min_ecc(self):
        return self.__min_ecc

    def get_reachables(self):
        return self.__reachables

    def get_name(self):
        return self.__name

    def gen_opposite(self, folder, file):
        self.__opposite_path = util.opposite_graph_ascend(folder=folder, file=file)
        return self.__opposite_path

    def __compute_fp(self, u, v, t, start_node, list_of_queues):
        if u == start_node:
            if (t, t) not in list_of_queues[start_node]:
                list_of_queues[start_node].append((t, t))

        if list_of_queues[u]:  # check if list is not empty
            for i, elem in reversed(list(enumerate(list_of_queues[u]))):
                if elem[1] <= t:
                    list_of_queues[u] = list_of_queues[u][i:]
                    new_t = elem[0]
                    new_arrival_time = t + 1  # traversal_time = 1
                    if not list_of_queues[v]:  # Check if list is empty
                        list_of_queues[v].append((new_t, new_arrival_time))

                    # If the old path dominates the new (or they are equals), keep only the old
                    elif (list_of_queues[v][-1][0] >= new_t and
                          list_of_queues[v][-1][1] <= new_arrival_time):
                        break

                    # If new path dominates the old, keep only the new
                    elif (list_of_queues[v][-1][0] < new_t and
                          list_of_queues[v][-1][1] >= new_arrival_time) or (list_of_queues[v][-1][0] == new_t and
                                                                            list_of_queues[v][-1][
                                                                                1] > new_arrival_time):
                        list_of_queues[v][-1] = (new_t, new_arrival_time)

                    # Otherwise keep both (newest is added after the oldest)
                    else:
                        list_of_queues[v].append((new_t, new_arrival_time))

                    # If the minimum duration changes, update it
                    if list_of_queues[v][-1][1] - list_of_queues[v][-1][0] < self.__distances[v]:
                        self.__distances[v] = list_of_queues[v][-1][1] - list_of_queues[v][-1][0]

                    break
                else:
                    continue

    def compute_distances(self, graph, start_node, min_time=None, max_time=None):
        """
        :param graph: temporal graph
        :param start_node: node to start from
        :param min_time: lower bound time interval
        :param max_time: upper bound time interval
        :return: numpy array of duration of the fastest path from start_node to every nodes v in V within time interval
        """

        if min_time is None:
            min_time, _ = graph.get_time_interval()
        if max_time is None:
            _, max_time = graph.get_time_interval()

        list_of_queues = []
        for elem in range(graph.get_num_nodes()):
            list_of_queues.append([])

        self.__eccentricity = 0
        self.__idx_farther = start_node
        self.__distances = np.full((graph.get_num_nodes(),), np.inf)
        self.__distances[start_node] = 0

        for line in graph.graph_reader():
            if not line.strip():  # check if line is blank
                break
            li = line.split()
            u = int(li[0])
            v = int(li[1])
            t = int(li[2])

            if t >= min_time and t + 1 <= max_time:
                self.__compute_fp(u=u, v=v, t=t, start_node=start_node, list_of_queues=list_of_queues)
                if not graph.get_is_directed():
                    self.__compute_fp(u=v, v=u, t=t, start_node=start_node, list_of_queues=list_of_queues)
            elif t >= max_time:
                break

        # To handle dummy nodes: discard them from the distances vector
        if graph.get_latest_node() is not None:
            self.__distances = self.__distances[:graph.get_latest_node()]

        self.__idx_farther, self.__eccentricity, self.__reachables = util.get_max_ind(self.__distances, np.NINF)


class EarliestArrivalPath:
    def __init__(self):
        self.__distances = []
        self.__eccentricity = None
        self.__idx_farther = None
        self.__min_ecc = np.NINF  # minimum possible eccentricity
        self.__name = 'EAT'

    def get_distances(self):
        return self.__distances

    def get_eccentricity(self):
        return self.__eccentricity

    def get_idx_farther(self):
        return self.__idx_farther

    def compare_eccentricity(self, ecc, idx):
        if self.__eccentricity > ecc:
            return self.__eccentricity, self.__idx_farther
        else:
            return ecc, idx

    def get_min_ecc(self):
        return self.__min_ecc

    def get_name(self):
        return self.__name

    def compute_distances(self, graph, start_node, min_time=None, max_time=None):
        """
        :param graph: temporal graph in its edge stream representation
        :param start_node: node to start from
        :param min_time: lower bound time interval
        :param max_time: upper bound time interval
        :return: numpy array of the earliest arrival times from start_node to every nodes v in V within time interval
        """

        if min_time is None:
            min_time, _ = graph.get_time_interval()
        if max_time is None:
            _, max_time = graph.get_time_interval()

        self.__eccentricity = min_time
        self.__idx_farther = start_node

        self.__distances = np.full((graph.get_num_nodes(),), np.inf)
        self.__distances[start_node] = min_time

        traversal_time = 1
        for line in graph.graph_reader():
            if not line.strip():  # check if line is blank
                break
            li = line.split()
            u = int(li[0])
            v = int(li[1])
            t = int(li[2])
            if len(li) == 4:
                traversal_time = int(li[3])

            if (t + traversal_time) <= max_time:
                if t >= self.__distances[u]:
                    if (t + traversal_time) < self.__distances[v]:
                        self.__distances[v] = t + traversal_time
                        if self.__distances[v] > self.__eccentricity and \
                                (graph.get_latest_node() is None or v < graph.get_latest_node()):  # to handle dummy
                            self.__eccentricity = self.__distances[v]
                            self.__idx_farther = v

                elif not graph.get_is_directed() and t >= self.__distances[v]:  # If graph is undirected
                    if (t + traversal_time) < self.__distances[u]:
                        self.__distances[u] = t + traversal_time
                        if self.__distances[u] > self.__eccentricity and \
                                (graph.get_latest_node() is None or u < graph.get_latest_node()):  # to handle dummy
                            # nodes
                            self.__eccentricity = self.__distances[u]
                            self.__idx_farther = u
            elif t >= max_time:
                break
            else:
                continue
        # To handle dummy nodes: discard them from the distances vector
        if graph.get_latest_node() is not None:
            self.__distances = self.__distances[:graph.get_latest_node()]


class LatestDeparturePath:  # ( method compute_distances() compute backward LDT)
    def __init__(self):
        self.__distances = []
        self.__eccentricity = None
        self.__idx_farther = None
        self.__min_ecc = np.inf  # minimum possible eccentricity
        self.__name = 'LDT'

    def get_distances(self):
        return self.__distances

    def get_eccentricity(self):
        return self.__eccentricity

    def get_idx_farther(self):
        return self.__idx_farther

    def compare_eccentricity(self, ecc, idx):
        if self.__eccentricity < ecc:
            return self.__eccentricity, self.__idx_farther
        else:
            return ecc, idx

    def get_min_ecc(self):
        return self.__min_ecc

    def get_name(self):
        return self.__name

    def compute_distances(self, graph, start_node, min_time=None, max_time=None):
        """
        :param graph: temporal graph in REVERSE EDGE STREAM representation
        :param start_node: destination node
        :param min_time: lower bound time interval
        :param max_time: upper bound time interval
        :return: numpy array of the latest departure times from every vertex v in V to dest_node within time interval
        """

        if min_time is None:
            min_time, _ = graph.get_time_interval()
        if max_time is None:
            _, max_time = graph.get_time_interval()

        self.__eccentricity = max_time
        self.__idx_farther = start_node

        self.__distances = np.full((graph.get_num_nodes(),), np.NINF)
        self.__distances[start_node] = max_time

        traversal_time = 1
        for line in graph.graph_reader():
            if not line.strip():  # check if line is blank
                break
            li = line.split()

            u = int(li[0])
            v = int(li[1])
            t = int(li[2])
            if len(li) == 4:
                traversal_time = int(li[3])

            if t >= min_time:
                if t + traversal_time <= self.__distances[v]:
                    if t > self.__distances[u]:
                        self.__distances[u] = t
                        # I can compute eccentricity here (online) because the graph is in reverse edge stream
                        if self.__distances[u] < self.__eccentricity and \
                                (graph.get_latest_node() is None or u < graph.get_latest_node()):
                            self.__eccentricity = self.__distances[u]
                            self.__idx_farther = u

                elif not graph.get_is_directed() and t + traversal_time <= self.__distances[u]:
                    if t > self.__distances[v]:
                        self.__distances[v] = t
                        if self.__distances[v] < self.__eccentricity and \
                                (graph.get_latest_node() is None or v < graph.get_latest_node()):
                            self.__eccentricity = self.__distances[v]
                            self.__idx_farther = v
            else:
                break
        # To handle dummy nodes: discard them from the distances vector
        if graph.get_latest_node() is not None:
            self.__distances = self.__distances[:graph.get_latest_node()]
        # self.__idx_farther, self.__eccentricity = util.get_min_ind(self.__distances, np.inf)


def compute_diameter(graph, distance):
    """
    :param graph: temporal graph
    :param distance: Object distance: FastestPathOnePass, earliestArrivalPath etc...
    :return: diameter value
    """
    ecc = distance.get_min_ecc()
    idx = -1
    reachable_pairs = 0
    if graph.get_latest_node() is not None:
        num_nodes = graph.get_latest_node()
    else:
        num_nodes = graph.get_num_nodes()

    for node in range(num_nodes):
        distance.compute_distances(graph=graph, start_node=node)

        if distance.get_name() == 'FP' or distance.get_name() == 'SP':
            reachable_pairs += distance.get_reachables()

        ecc, idx = distance.compare_eccentricity(ecc, idx)
        # print(ecc)

        if (node + 1) % 1000 == 0:
            print('1000 visits done...', flush=True)

    if distance.get_name() == 'FP' or distance.get_name() == 'SP':
        print('Reachable Pairs ' + graph.get_file_path().rsplit('/', 1)[1] + ': ' + str(reachable_pairs), flush=True)
    return ecc


if __name__ == '__main__':
    import temporal_graph as tg

    sp_distance = ShortestPath()

    g = tg.Graph(file_path='./graphs/Weighted/kuopio-weighted-sorted.txt', is_directed=True)

    sp_dist = ShortestPath()

    diam = compute_diameter(graph=g, distance=sp_distance)
    print('diam:')
    print(diam)
back to top