def load(self, fname, verbose=True, **kwargs):
        """
        Load a data file. The expected data format is three columns 
        (comma seperated by default) with source, target, flux.
        No header should be included and the node IDs have to run contuously 
        from 0 to Number_of_nodes-1.

        Parameters
        ----------
            fname : str
                Path to the file
            
            verbose : bool
                Print information about the data. True by Default
                
            kwargs : dict
                Default parameters can be changed here. Supported key words are
                    dtype     : float (default)
                    delimiter : ","   (default)
        
            return_graph : bool
                If True, the graph is returned (False by default).
                
        Returns:
        --------
            The graph is saved internally in self.graph.
                
        """
        delimiter = kwargs["delimiter"] if "delimiter" in kwargs.keys(
        ) else " "

        data = np.genfromtxt(fname,
                             delimiter=delimiter,
                             dtype=int,
                             unpack=False)
        source, target = data[:, 0], data[:, 1]
        if data.shape[1] > 2:
            flux = data[:, 2]
        else:
            flux = np.ones_like(source)
        nodes = set(source) | set(target)
        self.nodes = len(nodes)
        lines = len(flux)
        if set(range(self.nodes)) != nodes:
            new_node_ID = {old: new for new, old in enumerate(nodes)}
            map_new_node_ID = np.vectorize(new_node_ID.__getitem__)
            source = map_new_node_ID(source)
            target = map_new_node_ID(target)
            if verbose:
                print(
                    "\nThe node IDs have to run continuously from 0 to Number_of_nodes-1."
                )
                print(
                    "Node IDs have been changed according to the requirement.\n-----------------------------------\n"
                )

            print('Lines: ', lines, ', Nodes: ', self.nodes)
            print(
                '-----------------------------------\nData Structure:\n\nsource,    target,    weight \n'
            )
            for ii in range(7):
                print("%i,       %i,       %1.2e" %
                      (source[ii], target[ii], flux[ii]))
            print('-----------------------------------\n')

        G = DiGraph()  # Empty, directed Graph
        G.add_nodes_from(range(self.nodes))
        for ii in range(lines):
            u, v, w = int(source[ii]), int(target[ii]), float(flux[ii])
            if u != v:  # ignore self loops
                assert not G.has_edge(u,
                                      v), "Edge appeared twice - not supported"
                G.add_edge(u, v, weight=w)
            else:
                if verbose:
                    print("ignore self loop at node", u)

        symmetric = True
        for s, t, w in G.edges(data=True):
            w1 = G[s][t]["weight"]
            try:
                w2 = G[t][s]["weight"]
            except KeyError:
                symmetric = False
                G.add_edge(t, s, weight=w1)
                w2 = w1
            if w1 != w2:
                symmetric = False
                G[s][t]["weight"] += G[t][s]["weight"]
                G[s][t]["weight"] /= 2
                G[t][s]["weight"] = G[s][t]["weight"]
        if verbose:
            if not symmetric:
                print("The network has been symmetricised.")

        ccs = [G.subgraph(c).copy() for c in strongly_connected_components(G)]
        ccs = sorted(ccs, key=len, reverse=True)

        G_GSCC = ccs[0]
        if G_GSCC.number_of_nodes() != G.number_of_nodes():
            G = G_GSCC
            if verbose:
                print(
                    "\n--------------------------------------------------------------------------"
                )
                print(
                    "The network has been restricted to the giant strongly connected component."
                )
        self.nodes = G.number_of_nodes()

        for u, v, data in G.edges(data=True):
            weight = G.out_degree(u, weight='weight')
            data['transition_rate'] = 1. * data['weight'] / weight

        for u, v, data in G.edges(data=True):
            data['effective_distance'] = 1. - log(data['transition_rate'])

        if verbose:
            print(
                "\n--------------------------------------------------------------------------"
            )
            print(
                "\nnode ID, out-weight,   normalized out-weight,  sum of effective distances \n "
            )
            for ii in range(7):
                out_edges = G.out_edges(ii, data=True)
                out_weight, effective_distance, transition_rate = 0, 0, 0
                for u, v, data in out_edges:
                    out_weight += data["weight"]
                    effective_distance += data["effective_distance"]
                    transition_rate += data["transition_rate"]
                print(
                    "  %i       %1.2e           %2.3f                 %1.2e " %
                    (ii, out_weight, transition_rate, effective_distance))
            print("\n ... graph is saved in self.graph")
        return G
class TaskGraph(object):
    """
    A tasks graph builder.
    Build an operations flow graph
    """
    def __init__(self, name):
        self.name = name
        self._id = generate_uuid(variant='uuid')
        self._graph = DiGraph()

    def __repr__(self):
        return '{name}(id={self._id}, name={self.name}, graph={self._graph!r})'.format(
            name=self.__class__.__name__, self=self)

    @property
    def id(self):
        """
        Represents the id of the graph
        :return: graph id
        """
        return self._id

    # graph traversal methods

    @property
    def tasks(self):
        """
        An iterator on tasks added to the graph
        :yields: Iterator over all tasks in the graph
        """
        for _, data in self._graph.nodes_iter(data=True):
            yield data['task']

    def topological_order(self, reverse=False):
        """
        Returns topological sort on the graph
        :param reverse: whether to reverse the sort
        :return: a list which represents the topological sort
        """
        for task_id in topological_sort(self._graph, reverse=reverse):
            yield self.get_task(task_id)

    def get_dependencies(self, dependent_task):
        """
        Iterates over the task's dependencies
        :param BaseTask dependent_task: The task whose dependencies are requested
        :yields: Iterator over all tasks which dependency_task depends on
        :raise: TaskNotInGraphError if dependent_task is not in the graph
        """
        if not self.has_tasks(dependent_task):
            raise TaskNotInGraphError('Task id: {0}'.format(dependent_task.id))
        for _, dependency_id in self._graph.out_edges_iter(dependent_task.id):
            yield self.get_task(dependency_id)

    def get_dependents(self, dependency_task):
        """
        Iterates over the task's dependents
        :param BaseTask dependency_task: The task whose dependents are requested
        :yields: Iterator over all tasks which depend on dependency_task
        :raise: TaskNotInGraphError if dependency_task is not in the graph
        """
        if not self.has_tasks(dependency_task):
            raise TaskNotInGraphError('Task id: {0}'.format(
                dependency_task.id))
        for dependent_id, _ in self._graph.in_edges_iter(dependency_task.id):
            yield self.get_task(dependent_id)

    # task methods

    def get_task(self, task_id):
        """
        Get a task instance that's been inserted to the graph by the task's id
        :param basestring task_id: The task's id
        :return: Requested task
        :rtype: BaseTask
        :raise: TaskNotInGraphError if no task found in the graph with the given id
        """
        if not self._graph.has_node(task_id):
            raise TaskNotInGraphError('Task id: {0}'.format(task_id))
        data = self._graph.node[task_id]
        return data['task']

    @_filter_out_empty_tasks
    def add_tasks(self, *tasks):
        """
        Add a task to the graph
        :param BaseTask task: The task
        :return: A list of added tasks
        :rtype: list
        """
        assert all([
            isinstance(task, (api_task.BaseTask, Iterable)) for task in tasks
        ])
        return_tasks = []

        for task in tasks:
            if isinstance(task, Iterable):
                return_tasks += self.add_tasks(*task)
            elif not self.has_tasks(task):
                self._graph.add_node(task.id, task=task)
                return_tasks.append(task)

        return return_tasks

    @_filter_out_empty_tasks
    def remove_tasks(self, *tasks):
        """
        Remove the provided task from the graph
        :param BaseTask task: The task
        :return: A list of removed tasks
        :rtype: list
        """
        return_tasks = []

        for task in tasks:
            if isinstance(task, Iterable):
                return_tasks += self.remove_tasks(*task)
            elif self.has_tasks(task):
                self._graph.remove_node(task.id)
                return_tasks.append(task)

        return return_tasks

    @_filter_out_empty_tasks
    def has_tasks(self, *tasks):
        """
        Check whether a task is in the graph or not
        :param BaseTask task: The task
        :return: True if all tasks are in the graph, otherwise True
        :rtype: list
        """
        assert all(isinstance(t, (api_task.BaseTask, Iterable)) for t in tasks)
        return_value = True

        for task in tasks:
            if isinstance(task, Iterable):
                return_value &= self.has_tasks(*task)
            else:
                return_value &= self._graph.has_node(task.id)

        return return_value

    def add_dependency(self, dependent, dependency):
        """
        Add a dependency for one item (task, sequence or parallel) on another
        The dependent will only be executed after the dependency terminates
        If either of the items is either a sequence or a parallel,
         multiple dependencies may be added
        :param BaseTask|_TasksArrangement dependent: The dependent (task, sequence or parallel)
        :param BaseTask|_TasksArrangement dependency: The dependency (task, sequence or parallel)
        :return: True if the dependency between the two hadn't already existed, otherwise False
        :rtype: bool
        :raise TaskNotInGraphError if either the dependent or dependency are tasks which
         are not in the graph
        """
        if not (self.has_tasks(dependent) and self.has_tasks(dependency)):
            raise TaskNotInGraphError()

        if self.has_dependency(dependent, dependency):
            return

        if isinstance(dependent, Iterable):
            for dependent_task in dependent:
                self.add_dependency(dependent_task, dependency)
        else:
            if isinstance(dependency, Iterable):
                for dependency_task in dependency:
                    self.add_dependency(dependent, dependency_task)
            else:
                self._graph.add_edge(dependent.id, dependency.id)

    def has_dependency(self, dependent, dependency):
        """
        Check whether one item (task, sequence or parallel) depends on another

        Note that if either of the items is either a sequence or a parallel,
        and some of the dependencies exist in the graph but not all of them,
        this method will return False

        :param BaseTask|_TasksArrangement dependent: The dependent (task, sequence or parallel)
        :param BaseTask|_TasksArrangement dependency: The dependency (task, sequence or parallel)
        :return: True if the dependency between the two exists, otherwise False
        :rtype: bool
        :raise TaskNotInGraphError if either the dependent or dependency are tasks
         which are not in the graph
        """
        if not (dependent and dependency):
            return False
        elif not (self.has_tasks(dependent) and self.has_tasks(dependency)):
            raise TaskNotInGraphError()

        return_value = True

        if isinstance(dependent, Iterable):
            for dependent_task in dependent:
                return_value &= self.has_dependency(dependent_task, dependency)
        else:
            if isinstance(dependency, Iterable):
                for dependency_task in dependency:
                    return_value &= self.has_dependency(
                        dependent, dependency_task)
            else:
                return_value &= self._graph.has_edge(dependent.id,
                                                     dependency.id)

        return return_value

    def remove_dependency(self, dependent, dependency):
        """
        Remove a dependency for one item (task, sequence or parallel) on another

        Note that if either of the items is either a sequence or a parallel, and some of
        the dependencies exist in the graph but not all of them, this method will not remove
        any of the dependencies and return False

        :param BaseTask|_TasksArrangement dependent: The dependent (task, sequence or parallel)
        :param BaseTask|_TasksArrangement dependency: The dependency (task, sequence or parallel)
        :return: False if the dependency between the two hadn't existed, otherwise True
        :rtype: bool
        :raise TaskNotInGraphError if either the dependent or dependency are tasks
         which are not in the graph
        """
        if not (self.has_tasks(dependent) and self.has_tasks(dependency)):
            raise TaskNotInGraphError()

        if not self.has_dependency(dependent, dependency):
            return

        if isinstance(dependent, Iterable):
            for dependent_task in dependent:
                self.remove_dependency(dependent_task, dependency)
        elif isinstance(dependency, Iterable):
            for dependency_task in dependency:
                self.remove_dependency(dependent, dependency_task)
        else:
            self._graph.remove_edge(dependent.id, dependency.id)

    @_filter_out_empty_tasks
    def sequence(self, *tasks):
        """
        Create and insert a sequence into the graph, effectively each task i depends on i-1
        :param tasks: an iterable of dependencies
        :return: the provided tasks
        """
        if tasks:
            self.add_tasks(*tasks)

            for i in xrange(1, len(tasks)):
                self.add_dependency(tasks[i], tasks[i - 1])

        return tasks
Beispiel #3
0
class TSN(object):
    """
    Time-Space-Network Object with generator and updater functions.

    Parameters
    ----------
    flights : list,
        list of :class:`classes.Flight` objects
    """
    def __init__(self, flights):
        self.flights = flights
        self.last_arr = max([f.arrival for f in flights])  # last arrival
        self.data = Expand()
        self.G = DiGraph(directed=True, n_res=8)
        # Init update params
        self.it = None
        self.k_make = None
        self.airline = None
        self.duals = None
        self.first_flight = None

    def _build(self):
        self._build_data()
        self._build_graph()
        return self

    def _build_data(self):
        airports_dep = [f.origin for f in self.flights]
        airports_arrival = [f.destination for f in self.flights]
        self.data.airports = list(set(airports_arrival + airports_dep))
        self.data.airports.sort()

    def _build_graph(self):
        # Construct an Activity-on-Arc network using flights as activities
        flights = self.flights
        airports = self.data.airports

        self.G.add_node('Source',
                        pos=(-1, len(airports) + 1),
                        i_d=-1,
                        airport='Source')
        self.G.add_node('Sink',
                        pos=(self.last_arr + 1, -2),
                        i_d=-1,
                        airport='Sink')
        list(map(self._init_graph, flights))
        self._add_edges()
        log.info("Generated TSN with {} edges {} nodes".format(
            len(self.G.edges()), len(self.G.nodes())))
        # plot_graph(self.G)

    def _init_graph(self, f):
        """ Populate graph using flights from csv file"""
        label_dep = "{}_{}".format(f.origin, f.departure)  # label
        # Arrival node label
        label_arr = "{}_{}".format(f.destination, f.arrival)  # label
        self._init_node(f, label_dep, label_arr)  # Init nodes
        self._init_edge(f, label_dep, label_arr)  # Init edges

    def _init_node(self, f, label_dep, label_arr):
        # Add a Departure Node
        self.G.add_node(label_dep,
                        pos=(f.departure, self.data.airports.index(f.origin)),
                        airport=f.origin)
        # Add an Arrival Node
        self.G.add_node(label_arr,
                        pos=(f.arrival,
                             self.data.airports.index(f.destination)),
                        airport=f.destination)

    def _init_edge(self, f, label_dep, label_arr):
        # Add all flight edges
        self.G.add_edge(label_dep, label_arr, data=f._full_dict(), weight=0)
        #  Add edge from Source to departure and arrival nodes
        data_source = {
            'origin': 'Source',
            'destination': f.origin,
            'departure': -1,
            'arrival': f.departure,
            'type': f._classify_flight(0, f.departure)
        }
        self.G.add_edge('Source',
                        label_dep,
                        data=data_source,
                        weight=self._edge_weight('Source', label_dep))
        # Add edge from arrival to Sink
        data_sink = {
            'origin': f.destination,
            'destination': 'Sink',
            'departure': f.arrival,
            'arrival': self.last_arr,
            'type': f._classify_flight(f.arrival, self.last_arr)
        }
        self.G.add_edge(label_arr,
                        'Sink',
                        data=data_sink,
                        weight=self._edge_weight(label_arr, 'Sink'))

    def _add_edges(self):
        # Add edges if not already exist with their respective data
        self._add_ground_edges()
        self._add_remaining_edges()

    def _add_ground_edges(self):
        nodes = sorted(
            [
                node for node in self.G.nodes(data=True)
                if node[0] not in ["Source", "Sink"]
            ],
            key=lambda x: x[1]['pos'][0],
        )

        for n in nodes:
            # first element in tuple has string 'Airport_time'
            # Second element in tuple has node data
            n_name, n_data = n[0], n[1]
            n_time = float(n_name.split('_')[1])
            ground_nodes_n = sorted(
                [
                    k for k in nodes if float(k[0].split('_')[1]) > n_time
                    and n_data['airport'] == k[1]['airport']
                ],
                key=lambda x: x[1]['pos'][0],
            )

            # ground_nodes_n.sort()
            if ground_nodes_n:
                m = ground_nodes_n[0]
                # for m in ground_nodes_n:
                path = None
                m_name, m_data = m[0], m[1]
                m_time = float(m_name.split('_')[1])
                if not self.G.has_edge(*(n_name, m_name)):
                    try:
                        path = astar_path(self.G, n_name, m_name)
                    except NetworkXNoPath:
                        self._add_edge(n_name, n_data, n_time, m_name, m_data,
                                       m_time)
                    if path and any(
                            p.split('_')[0] != n_data['airport']
                            for p in path):
                        # if edge doesn't exist, add it
                        self._add_edge(n_name, n_data, n_time, m_name, m_data,
                                       m_time)

    def _add_remaining_edges(self):
        nodes = sorted(
            [
                node for node in self.G.nodes(data=True)
                if node[0] not in ["Source", "Sink"]
            ],
            key=lambda x: x[1]['pos'][0],
        )

        for n in nodes:
            # first element in tuple has string 'Airport_time'
            # Second element in tuple has node data
            n_name, n_data = n[0], n[1]
            n_time = float(n_name.split('_')[1])
            nodes_n = sorted(
                [k for k in nodes if float(k[0].split('_')[1]) > n_time],
                key=lambda x: x[1]['pos'][0],
            )

            # nodes_n.sort()
            for m in nodes_n:
                m_name, m_data = m[0], m[1]
                m_time = float(m_name.split('_')[1])
                if (not has_path(self.G, n_name, m_name)):
                    # if path doesn't exist add edge
                    f = [
                        f for f in self.flights
                        if (f.origin == n_name and f.destination == m_name
                            and f.arrival - f.departure == n_time -
                            m_time and f.arrival < n_time)
                    ]
                    if f:
                        self._add_edge(n_name, n_data, n_time, m_name, m_data,
                                       m_time)

    def _add_edge(self, n_name, n_data, n_time, m_name, m_data, m_time):
        data = {
            'origin': n_data['airport'],
            'destination': m_data['airport'],
            'departure': n_time,
            'arrival': m_time,
            'type': Flight._classify_flight(n_time, m_time)
        }
        self.G.add_edge(n_name,
                        m_name,
                        data=data,
                        weight=self._edge_weight(n_name, m_name,
                                                 {'data': data}))

    ################
    # Updating TSN #
    ################
    def _update_TSN(self, G, it, k_type, k_make, airline, duals, first_flight,
                    drop_edges):
        """
        Updates TSN network and returns preprocessed version,
        with less edges
        """
        edges = G.edges(data=True)
        self.it = it
        self.k_make = k_make
        self.airline = airline
        self.duals = duals
        self.first_flight = first_flight

        list(map(self._update_edge_attrs, edges))
        if drop_edges:
            return self._drop_edges(G, k_type)
        else:
            return G

    @staticmethod
    def _drop_edges(G, k_type):
        """
        Creates a copy of the TSN graph and removes
        unnecessary (Source, *) edges.
        """
        G = copy.deepcopy(G)
        count, edges = 0, G.edges(data=True)
        number_edges = len(edges)
        edges_to_remove = []
        for edge in edges:
            edge_data = edge[2]
            i_airport = edge[0].split('_')[0]
            j_airport = edge[1].split('_')[0]
            if ((((k_type == 1 and edge_data['data']['type'] > k_type) or
                  (k_type == 2 and edge_data['data']['type'] != k_type))
                 and i_airport != j_airport) and i_airport != 'Source'
                    and j_airport != 'Sink'):
                edges_to_remove.append(edge[0:2])
                count += 1
        G.remove_edges_from(edges_to_remove)
        log.info('Removed {}/{} edges.'.format(count, number_edges))
        return G

    def _edge_weight(self, i, j, edge_data={}):
        """
        Weight function for edge between two pair of nodes.

        Parameters
        ----------
        i : string,
            tail node in the form 'LETTER_INTEGER'

        j : string,
            head node in the form 'LETTER_INTEGER'

        Returns
        -------
        int
            value with appropriate weight
        """
        if i == 'Source':
            return 0  # float(j.split('_')[1])
        elif j == 'Sink':
            return 0  # (self.last_arr - float(i.split('_')[1]))
        else:
            i_airport, i_time = i.split('_')
            j_airport, j_time = j.split('_')
            i_time, j_time = float(i_time), float(j_time)
            # if i_airport == j_airport:
            #     return (j_time - i_time)
            # else:
            if self.k_make:
                try:  # SCHEDULED CONNECTION
                    flight_dual = list(
                        w[0] for w in self.duals
                        if w[1]._full_dict() == edge_data['data'])[0]
                    cost = (OPERATING_COSTS[self.k_make]['standard']
                            if i_airport != j_airport else 0)
                    return cost * (j_time - i_time) - flight_dual
                except IndexError:  # NON-SCHEDULED CONNECTION
                    cost_delay, delay = 0, 0
                    if i_airport != j_airport:
                        closest_flight = self._get_flight_copy(
                            self.flights, edge_data)
                        delay = (edge_data['data']['departure'] -
                                 closest_flight.departure)
                        flight_dual = list(w[0] for w in self.duals
                                           if w[1]._full_dict() ==
                                           closest_flight._full_dict())[0]
                        cost = OPERATING_COSTS[self.k_make]['standard']
                        cost_delay = OPERATING_COSTS[self.k_make]['copy']
                        return (cost * (j_time - i_time) + cost_delay * delay -
                                flight_dual)
                    else:
                        return (OPERATING_COSTS[self.k_make]['ground'] *
                                (j_time - i_time))
            else:
                return 0

    @staticmethod
    def _get_flight_copy(flights, edge_data):
        previous_flights = [
            f for f in flights if
            (f._full_dict()['origin'] == edge_data['data']['origin'] and
             f._full_dict()['destination'] == edge_data['data']['destination']
             and f._full_dict()['departure'] < edge_data['data']['departure'])
        ]
        if previous_flights:
            return max(previous_flights, key=lambda x: x.departure)
        else:
            return

    def _update_edge_attrs(self, edge):
        """
        Update edge attributes using dual values from the solution of the
        relaxed master problem.

        Parameters
        ----------
        edge : edge
            edge to update.

        it : int
            iteration number.

        duals : list of tuples
            (dual, classes.Flight). dual values from the master problem
            and schedule.

        first_flight : object, :class:`classes.Flight`
            first flight scheduled.
        """
        def __update_weight(edge):
            edge_data = edge[2]
            weight = self._edge_weight(*edge)
            edge_data['weight'] = weight

        def __update_res_cost(edge):
            edge[2]['res_cost'] = np.zeros(self.G.graph['n_res'])

        __update_weight(edge)
        __update_res_cost(edge)
Beispiel #4
0
class TaskGraph(object):
    """
    Task graph builder.
    """

    def __init__(self, name):
        self.name = name
        self._id = generate_uuid(variant='uuid')
        self._graph = DiGraph()

    def __repr__(self):
        return u'{name}(id={self._id}, name={self.name}, graph={self._graph!r})'.format(            # pylint: disable=redundant-keyword-arg
            name=self.__class__.__name__, self=self)

    @property
    def id(self):
        """
        ID of the graph
        """
        return self._id

    # graph traversal methods

    @property
    def tasks(self):
        """
        Iterator over tasks in the graph.
        """
        for _, data in self._graph.nodes(data=True):
            yield data['task']

    def topological_order(self, reverse=False):
        """
        Topological sort of the graph.

        :param reverse: whether to reverse the sort
        :return: list which represents the topological sort
        """
        task_ids = topological_sort(self._graph)
        if reverse:
            task_ids = reversed(tuple(task_ids))
        for task_id in task_ids:
            yield self.get_task(task_id)

    def get_dependencies(self, dependent_task):
        """
        Iterates over the task's dependencies.

        :param dependent_task: task whose dependencies are requested
        :raises ~aria.orchestrator.workflows.api.task_graph.TaskNotInGraphError: if
         ``dependent_task`` is not in the graph
        """
        if not self.has_tasks(dependent_task):
            raise TaskNotInGraphError(u'Task id: {0}'.format(dependent_task.id))
        for _, dependency_id in self._graph.out_edges(dependent_task.id):
            yield self.get_task(dependency_id)

    def get_dependents(self, dependency_task):
        """
        Iterates over the task's dependents.

        :param dependency_task: task whose dependents are requested
        :raises ~aria.orchestrator.workflows.api.task_graph.TaskNotInGraphError: if
         ``dependency_task`` is not in the graph
        """
        if not self.has_tasks(dependency_task):
            raise TaskNotInGraphError(u'Task id: {0}'.format(dependency_task.id))
        for dependent_id, _ in self._graph.in_edges(dependency_task.id):
            yield self.get_task(dependent_id)

    # task methods

    def get_task(self, task_id):
        """
        Get a task instance that's been inserted to the graph by the task's ID.

        :param basestring task_id: task ID
        :raises ~aria.orchestrator.workflows.api.task_graph.TaskNotInGraphError: if no task found in
         the graph with the given ID
        """
        if not self._graph.has_node(task_id):
            raise TaskNotInGraphError(u'Task id: {0}'.format(task_id))
        data = self._graph.node[task_id]
        return data['task']

    @_filter_out_empty_tasks
    def add_tasks(self, *tasks):
        """
        Adds a task to the graph.

        :param task: task
        :return: list of added tasks
        :rtype: list
        """
        assert all([isinstance(task, (api_task.BaseTask, Iterable)) for task in tasks])
        return_tasks = []

        for task in tasks:
            if isinstance(task, Iterable):
                return_tasks += self.add_tasks(*task)
            elif not self.has_tasks(task):
                self._graph.add_node(task.id, task=task)
                return_tasks.append(task)

        return return_tasks

    @_filter_out_empty_tasks
    def remove_tasks(self, *tasks):
        """
        Removes the provided task from the graph.

        :param task: task
        :return: list of removed tasks
        :rtype: list
        """
        return_tasks = []

        for task in tasks:
            if isinstance(task, Iterable):
                return_tasks += self.remove_tasks(*task)
            elif self.has_tasks(task):
                self._graph.remove_node(task.id)
                return_tasks.append(task)

        return return_tasks

    @_filter_out_empty_tasks
    def has_tasks(self, *tasks):
        """
        Checks whether a task is in the graph.

        :param task: task
        :return: ``True`` if all tasks are in the graph, otherwise ``False``
        :rtype: list
        """
        assert all(isinstance(t, (api_task.BaseTask, Iterable)) for t in tasks)
        return_value = True

        for task in tasks:
            if isinstance(task, Iterable):
                return_value &= self.has_tasks(*task)
            else:
                return_value &= self._graph.has_node(task.id)

        return return_value

    def add_dependency(self, dependent, dependency):
        """
        Adds a dependency for one item (task, sequence or parallel) on another.

        The dependent will only be executed after the dependency terminates. If either of the items
        is either a sequence or a parallel, multiple dependencies may be added.

        :param dependent: dependent (task, sequence or parallel)
        :param dependency: dependency (task, sequence or parallel)
        :return: ``True`` if the dependency between the two hadn't already existed, otherwise
         ``False``
        :rtype: bool
        :raises ~aria.orchestrator.workflows.api.task_graph.TaskNotInGraphError: if either the
         dependent or dependency are tasks which are not in the graph
        """
        if not (self.has_tasks(dependent) and self.has_tasks(dependency)):
            raise TaskNotInGraphError()

        if self.has_dependency(dependent, dependency):
            return

        if isinstance(dependent, Iterable):
            for dependent_task in dependent:
                self.add_dependency(dependent_task, dependency)
        else:
            if isinstance(dependency, Iterable):
                for dependency_task in dependency:
                    self.add_dependency(dependent, dependency_task)
            else:
                self._graph.add_edge(dependent.id, dependency.id)

    def has_dependency(self, dependent, dependency):
        """
        Checks whether one item (task, sequence or parallel) depends on another.

        Note that if either of the items is either a sequence or a parallel, and some of the
        dependencies exist in the graph but not all of them, this method will return ``False``.

        :param dependent: dependent (task, sequence or parallel)
        :param dependency: dependency (task, sequence or parallel)
        :return: ``True`` if the dependency between the two exists, otherwise ``False``
        :rtype: bool
        :raises ~aria.orchestrator.workflows.api.task_graph.TaskNotInGraphError: if either the
         dependent or dependency are tasks which are not in the graph
        """
        if not (dependent and dependency):
            return False
        elif not (self.has_tasks(dependent) and self.has_tasks(dependency)):
            raise TaskNotInGraphError()

        return_value = True

        if isinstance(dependent, Iterable):
            for dependent_task in dependent:
                return_value &= self.has_dependency(dependent_task, dependency)
        else:
            if isinstance(dependency, Iterable):
                for dependency_task in dependency:
                    return_value &= self.has_dependency(dependent, dependency_task)
            else:
                return_value &= self._graph.has_edge(dependent.id, dependency.id)

        return return_value

    def remove_dependency(self, dependent, dependency):
        """
        Removes a dependency for one item (task, sequence or parallel) on another.

        Note that if either of the items is either a sequence or a parallel, and some of the
        dependencies exist in the graph but not all of them, this method will not remove any of the
        dependencies and return ``False``.

        :param dependent: dependent (task, sequence or parallel)
        :param dependency: dependency (task, sequence or parallel)
        :return: ``False`` if the dependency between the two hadn't existed, otherwise ``True``
        :rtype: bool
        :raises ~aria.orchestrator.workflows.api.task_graph.TaskNotInGraphError: if either the
         dependent or dependency are tasks which are not in the graph
        """
        if not (self.has_tasks(dependent) and self.has_tasks(dependency)):
            raise TaskNotInGraphError()

        if not self.has_dependency(dependent, dependency):
            return

        if isinstance(dependent, Iterable):
            for dependent_task in dependent:
                self.remove_dependency(dependent_task, dependency)
        elif isinstance(dependency, Iterable):
            for dependency_task in dependency:
                self.remove_dependency(dependent, dependency_task)
        else:
            self._graph.remove_edge(dependent.id, dependency.id)

    @_filter_out_empty_tasks
    def sequence(self, *tasks):
        """
        Creates and inserts a sequence into the graph, effectively each task i depends on i-1.

        :param tasks: iterable of dependencies
        :return: provided tasks
        """
        if tasks:
            self.add_tasks(*tasks)

            for i in xrange(1, len(tasks)):
                self.add_dependency(tasks[i], tasks[i-1])

        return tasks
    def load(self,fname, verbose=True, **kwargs):
        """
        Load a data file. The expected data format is three columns 
        (comma seperated by default) with source, target, flux.
        No header should be included and the node IDs have to run contuously 
        from 0 to Number_of_nodes-1.

        Parameters
        ----------
            fname : str
                Path to the file
            
            verbose : bool
                Print information about the data. True by Default
                
            kwargs : dict
                Default parameters can be changed here. Supported key words are
                    dtype     : float (default)
                    delimiter : ","   (default)
        
            return_graph : bool
                If True, the graph is returned (False by default).
                
        Returns:
        --------
            The graph is saved internally in self.graph.
                
        """
        delimiter = kwargs["delimiter"]      if "delimiter"      in kwargs.keys() else " "
        
        data = np.genfromtxt(fname, delimiter=delimiter, dtype=int, unpack=False)
        source, target = data[:,0], data[:,1]
        if data.shape[1] > 2:
            flux = data[:,2]
        else:
            flux = np.ones_like(source)
        nodes  = set(source) | set(target)
        self.nodes = len(nodes)
        lines  = len(flux)
        if set(range(self.nodes)) != nodes:
            new_node_ID = {old:new for new,old in enumerate(nodes)}
            map_new_node_ID = np.vectorize(new_node_ID.__getitem__)
            source = map_new_node_ID(source)
            target = map_new_node_ID(target)
            if verbose:
                print "\nThe node IDs have to run continuously from 0 to Number_of_nodes-1."
                print "Node IDs have been changed according to the requirement.\n-----------------------------------\n"
                
        
            print 'Lines: ',lines , ', Nodes: ', self.nodes
            print '-----------------------------------\nData Structure:\n\nsource,    target,    weight \n'
            for ii in range(7):            
                print "%i,       %i,       %1.2e" %(source[ii], target[ii], flux[ii])
            print '-----------------------------------\n'
        
        
        G = DiGraph()         # Empty, directed Graph
        G.add_nodes_from(range(self.nodes))
        for ii in xrange(lines):
            u, v, w = int(source[ii]), int(target[ii]), float(flux[ii])
            if u != v: # ignore self loops
                assert not G.has_edge(u,v), "Edge appeared twice - not supported"                    
                G.add_edge(u,v,weight=w)
            else:
                if verbose:
                    print "ignore self loop at node", u
        
        symmetric = True
        for s,t,w in G.edges(data=True):
            w1 = G[s][t]["weight"]
            try:
                w2 = G[t][s]["weight"]
            except KeyError:
                symmetric = False
                G.add_edge(t,s,weight=w1)
                w2 = w1
            if w1 != w2:
                symmetric = False
                G[s][t]["weight"] += G[t][s]["weight"]
                G[s][t]["weight"] /= 2
                G[t][s]["weight"]  = G[s][t]["weight"]
        if verbose:
            if not symmetric:
                print "The network has been symmetricised."
        
        
        ccs = strongly_connected_component_subgraphs(G)
        ccs = sorted(ccs, key=len, reverse=True)
        
        G_GSCC = ccs[0]
        if G_GSCC.number_of_nodes() != G.number_of_nodes():
            G = G_GSCC
            if verbose:
                print "\n--------------------------------------------------------------------------"
                print "The network has been restricted to the giant strongly connected component."
        self.nodes = G.number_of_nodes()
        
        
        
        
        for u, v, data in G.edges(data=True):
            weight = G.out_degree(u,weight='weight')
            data['transition_rate'] = 1.*data['weight']/weight
        
        
        for u, v, data in G.edges(data=True):
            data['effective_distance'] = 1. - log(data['transition_rate'])
        
        if verbose:
            print "\n--------------------------------------------------------------------------"
            print "\nnode ID, out-weight,   normalized out-weight,  sum of effective distances \n "
            for ii in range(7):
                out_edges = G.out_edges(ii, data=True)
                out_weight, effective_distance, transition_rate = 0, 0, 0
                for u, v, data in out_edges:
                    out_weight          += data["weight"]
                    effective_distance  += data["effective_distance"]
                    transition_rate     += data["transition_rate"]
                print "  %i       %1.2e           %2.3f                 %1.2e " %(ii,out_weight, transition_rate, effective_distance)
            print "\n ... graph is saved in self.graph"
        return G
Beispiel #6
0
def is_anti_reflexive(graph: DiGraph) -> bool:
    return all(not graph.has_edge(node, node) for node in graph.nodes)
Beispiel #7
0
def digraph2condensationgraph(digraph: networkx.DiGraph) -> networkx.DiGraph:
    """
    Creates the condensation graph from *digraph*.
    The condensation graph is similar to the SCC graph but it replaces
    cascades between SCCs by single edges.
    Its nodes are therefore non-trivial SCCs or inputs.
    As for the SCC graph, nodes are tuples of names that belong to the SCC.
    The condensation graph is cycle-free and does distinguish between inputs
    and constants.
    The graph has no additional data.

    **arguments**:
        * *digraph*: directed graph

    **returns**:
        * *condensation_graph*: the condensation graph

    **example**::

        >>> cgraph = digraph2condensationgraph(igraph)
        >>> cgraph.nodes()
        [('Ash1', 'Cbf1'), ('Gal4',), ('Gal80',), ('Cbf1','Swi5)]
        >>> cgraph.edges()
        [(('Gal4',), ('Ash1', 'Cbf1')), (('Gal4',), ('Gal80',)),
         (('Gal80',),('Cbf1','Swi5))]
    """

    sccs = sorted([
        tuple(sorted(scc))
        for scc in networkx.strongly_connected_components(digraph)
    ])
    cascades = [
        scc for scc in sccs
        if (len(scc) == 1) and not digraph.has_edge(scc[0], scc[0])
    ]
    noncascades = [scc for scc in sccs if scc not in cascades]

    cgraph = networkx.DiGraph()
    cgraph.add_nodes_from(noncascades)

    # rgraph is a copy of Digraph with edges leaving noncascade components removed.
    # will use rgraph to decide if there is a cascade path between U and W (i.e. edge in cgraph)
    rgraph = networkx.DiGraph(digraph.edges())

    for U, W in itertools.product(noncascades, noncascades):
        if U == W:
            continue

        rgraph = digraph.copy()
        for X in noncascades:
            if not X == U and not X == W:
                rgraph.remove_nodes_from(X)

        if has_path(rgraph, U, W):
            cgraph.add_edge(U, W)

    # annotate each node with its depth in the hierarchy and an integer ID
    for ID, target in enumerate(cgraph.nodes()):
        depth = 1

        for source in networkx.ancestors(cgraph, target):
            for p in networkx.all_simple_paths(cgraph, source, target):
                depth = max(depth, len(p))

        cgraph.nodes[target]["depth"] = depth
        cgraph.nodes[target]["id"] = ID

    return cgraph
Beispiel #8
0
}

# start the scanning process
switch = []

# direction: up. We will change the rows
prev = None
flag = 1
for coord in range(int(comr), -1, -1):
    r = coord
    c = comc

    if prev == None:
        prev = testcase[r, c]
    else:
        if g.has_edge(prev, testcase[r, c]):
            prev = testcase[r, c]
            continue
        else:
            print('invalid!')
            flag = -1
            break

if flag != -1:
    print('Passed the Up direction test!')
# direction: down
# direction: down
prev = None
flag = 1
for coord in range(int(comr), np.shape(testcase)[0], 1):
    r = coord
Beispiel #9
0
def is_not_transitive(graph: DiGraph) -> bool:
    return any(not graph.has_edge(x, z)
               for x, y in get_set_combination(graph.nodes)
               if x != y and graph.has_edge(x, y) for z in graph.adj[y]
               if y != z)
Beispiel #10
0
    def perception_text(
        self, perception: PerceptualRepresentation[
            DevelopmentalPrimitivePerceptionFrame]
    ) -> str:
        """
        Turns a perception into a list of items in the perceptions frames.
        """
        output_text: List[str] = []

        check_state(
            len(perception.frames) in (1, 2),
            "Only know how to handle 1 or 2 frame "
            "perceptions for now",
        )

        perception_is_dynamic = len(perception.frames) > 1

        # first, we build an index of objects to their properties.
        # This will be used so that when we list the objects,
        # we can easily list their properties in brackets right after them.
        def extract_subject(prop: PropertyPerception) -> ObjectPerception:
            return prop.perceived_object

        first_frame_properties = _index_to_setmultidict(
            perception.frames[0].property_assertions, extract_subject)
        second_frame_properties = (_index_to_setmultidict(
            perception.frames[1].property_assertions, extract_subject)
                                   if perception_is_dynamic else
                                   immutablesetmultidict())

        # Next, we determine what objects persist between both frames
        # and which do not.
        first_frame_objects = perception.frames[0].perceived_objects
        second_frame_objects = (perception.frames[1].perceived_objects
                                if perception_is_dynamic else immutableset())
        static_objects = (
            first_frame_objects.intersection(second_frame_objects)
            if perception_is_dynamic else first_frame_objects)
        all_objects = first_frame_objects.union(second_frame_objects)

        # For objects, properties, and relations we will use arrows to indicate
        # when something beings or ceased to exist between frames.
        # Since the logic will be the same for all three types,
        # we pull it out into a function.
        def compute_arrow(
                item: Any, static_items: AbstractSet[Any],
                first_frame_items: AbstractSet[Any]) -> Tuple[str, str]:
            if item in static_items:
                # item doesn't change - no arrow
                return ("", "")
            elif item in first_frame_items:
                # item ceases to exist
                return ("", " ---> Ø")
            else:
                # item beings to exist in the second frame
                return ("Ø ---> ", "")

        # the logic for rendering objects, which will be used in the loop below.
        # This needs to be an inner function so it can access the frame property maps, etc.
        def render_object(obj: ObjectPerception) -> str:
            obj_text = f"<i>{obj.debug_handle}</i>"
            first_frame_obj_properties = first_frame_properties[obj]
            second_frame_obj_properties = second_frame_properties[obj]
            static_properties = (second_frame_obj_properties.intersection(
                first_frame_obj_properties) if second_frame_obj_properties else
                                 first_frame_obj_properties)

            # logic for rendering properties, for use in the loop below.
            def render_property(prop: PropertyPerception) -> str:
                (prop_prefix,
                 prop_suffix) = compute_arrow(prop, static_properties,
                                              first_frame_obj_properties)
                prop_string: str
                if isinstance(prop, HasColor):
                    prop_string = (
                        f'<span style="background-color: {prop.color}; '
                        f'color: {prop.color.inverse()}; border: 1px solid black;">'
                        f"color={prop.color.hex}</span>")
                elif isinstance(prop, HasBinaryProperty):
                    prop_string = prop.binary_property.handle
                else:
                    raise RuntimeError(f"Cannot render property: {prop}")

                return f"{prop_prefix}{prop_string}{prop_suffix}"

            all_properties: ImmutableSet[PropertyPerception] = immutableset(
                flatten(
                    [first_frame_obj_properties, second_frame_obj_properties]))
            prop_strings = [render_property(prop) for prop in all_properties]

            if prop_strings:
                return f"{obj_text}[{'; '.join(prop_strings)}]"
            else:
                return obj_text

        # Here we process the relations between the two scenes to determine all relations.
        # This has to be done before rending objects so we can use the PART_OF relation to order
        # the objects.
        first_frame_relations = perception.frames[0].relations
        second_frame_relations = (perception.frames[1].relations
                                  if perception_is_dynamic else immutableset())
        static_relations = (
            second_frame_relations.intersection(first_frame_relations)
            if perception_is_dynamic else first_frame_relations)
        all_relations = first_frame_relations.union(second_frame_relations)

        # Here we add the perceived objects to a NetworkX DiGraph with PART_OF relations being the
        # edges between objects. This allows us to do pre-order traversal of the Graph to make a
        # nested <ul></ul> for the objects rather than a flat list.
        graph = DiGraph()
        root = ObjectPerception("root", axes=WORLD_AXES)
        graph.add_node(root)
        expressed_relations = set()
        axis_to_object: Dict[GeonAxis, ObjectPerception] = {}

        for object_ in all_objects:
            graph.add_node(object_)
            graph.add_edge(root, object_)
            for axis in object_.axes.all_axes:
                axis_to_object[axis] = object_

        for relation_ in all_relations:
            if relation_.relation_type == PART_OF:
                graph.add_edge(relation_.second_slot, relation_.first_slot)
                if graph.has_edge(root, relation_.first_slot):
                    graph.remove_edge(root, relation_.first_slot)
                expressed_relations.add(relation_)

        # Next, we render objects, together with their properties, using preorder DFS Traversal
        # We also add in `In Region` relationships at this step for objects which have them.
        output_text.append(
            "\n\t\t\t\t\t<h5>Perceived Objects</h5>\n\t\t\t\t\t<ul>")
        visited = set()
        region_relations = immutableset(region for region in all_relations
                                        if region.relation_type == IN_REGION)

        # This loop doesn't quite get the tab spacing right. It could at the cost of increased
        # complexity. Would need to track the "depth" we are currently at.
        axis_info = perception.frames[0].axis_info

        def dfs_walk(node, depth=0):
            visited.add(node)
            if not node == root:
                (obj_prefix,
                 obj_suffix) = compute_arrow(node, static_objects,
                                             first_frame_objects)
                output_text.append(
                    f"\t" * (6 + depth) +
                    f"<li>{obj_prefix}{render_object(node)}{obj_suffix}<ul>")
                if node.geon:
                    output_text.append(
                        f"\t\t\t\t\t\t<li>Geon: {self._render_geon(node.geon, indent_dept=7)}</li>"
                    )
                # Handle Region Relations
                for region_relation in region_relations:
                    if region_relation.first_slot == node:
                        (relation_prefix, relation_suffix) = compute_arrow(
                            region_relation, static_relations,
                            first_frame_relations)
                        relation_str = self._render_relation(
                            axis_info, region_relation)
                        output_text.append(
                            f"\t\t\t\t\t\t<li>{relation_prefix}"
                            f"{relation_str}{relation_suffix}</li>")
                        expressed_relations.add(region_relation)
            for succ in graph.successors(node):
                if succ not in visited:
                    depth = depth + 6
                    dfs_walk(succ, depth)
                    depth = depth - 6
            output_text.append("\t" * (6 + depth) + f"</ul></li>")

        dfs_walk(root)
        output_text.append("\t\t\t\t\t</ul>")

        # Finally we render remaining relations between objects
        remaining_relations = immutableset(
            relation for relation in all_relations
            if relation not in expressed_relations)
        if remaining_relations:
            output_text.append(
                "\t\t\t\t\t<h5>Other Relations</h5>\n\t\t\t\t\t<ul>")
            for relation in remaining_relations:
                (relation_prefix,
                 relation_suffix) = compute_arrow(relation, static_relations,
                                                  first_frame_relations)
                single_size_relation: Optional[Tuple[
                    Any, str, Any]] = self._get_single_size_relation(
                        relation, all_relations)
                if single_size_relation:
                    relation_text = f"{single_size_relation[0]} {single_size_relation[1]} {single_size_relation[2]}"
                    size_output = f"\t\t\t\t\t\t<li>{relation_prefix}{relation_text}{relation_suffix}</li>"
                    if size_output not in output_text:
                        output_text.append(size_output)
                else:
                    output_text.append(
                        f"\t\t\t\t\t\t<li>{relation_prefix}{relation}{relation_suffix}</li>"
                    )
            output_text.append("\t\t\t\t\t</ul>")

        if perception.during:
            output_text.append("\t\t\t\t\t<h5>During the action</h5>")
            output_text.append(
                self._render_during(perception.during, indent_depth=5))

        if axis_info and axis_info.axes_facing:
            output_text.append(("\t\t\t\t\t<h5>Axis Facings</h5>"))
            output_text.append(("\t\t\t\t\t<ul>"))
            for object_ in axis_info.axes_facing:
                output_text.append(
                    f"\t\t\t\t\t\t<li>{object_.debug_handle} faced by:\n\t\t\t\t\t\t<ul>"
                )
                for axis in axis_info.axes_facing[object_]:
                    output_text.append(
                        f"\t\t\t\t\t\t\t<li>{axis} possessed by {axis_to_object[axis]}</li>"
                    )
                output_text.append("\t\t\t\t\t\t</ul>")
            output_text.append("\t\t\t\t\t</ul>")

        return "\n".join(output_text)
Beispiel #11
0
def is_anti_symmetric(graph: DiGraph) -> bool:
    return all(not graph.has_edge(y, x) for x, y in graph.edges if x != y
               if graph.has_edge(x, y))
Beispiel #12
0
def is_symmetric(graph: DiGraph) -> bool:
    return all(
        graph.has_edge(y, x) for x, y in graph.edges
        if x != y and graph.has_edge(x, y))
Beispiel #13
0
def is_not_reflexive(graph: DiGraph) -> bool:
    if is_reflexive(graph):
        return False
    return any(graph.has_edge(node, node) for node in graph.nodes)
Beispiel #14
0
class CollectNodes(Visitor):
    def __init__(self, call_deps=False):
        self.graph = DiGraph()
        self.modified = set()
        self.used = set()
        self.undefined = set()
        self.sources = set()
        self.targets = set()

        self.context_names = set()

        self.call_deps = call_deps

    visitDefault = collect_

    def visitName(self, node):
        if isinstance(node.ctx, _ast.Store):
            self.modified.add(node.id)

        elif isinstance(node.ctx, _ast.Load):
            self.used.update(node.id)

        if not self.graph.has_node(node.id):
            self.graph.add_node(node.id)
            if isinstance(node.ctx, _ast.Load):
                self.undefined.add(node.id)

        for ctx_var in self.context_names:
            if not self.graph.has_edge(node.id, ctx_var):
                self.graph.add_edge(node.id, ctx_var)

        return {node.id}

    def visitalias(self, node):
        name = node.asname if node.asname else node.name

        if '.' in name:
            name = name.split('.', 1)[0]

        if not self.graph.has_node(name):
            self.graph.add_node(name)

        return {name}

    def visitCall(self, node):
        left = self.visit(node.func)

        right = set()
        for attr in ('args', 'keywords'):
            for child in getattr(node, attr):
                if child:
                    right.update(self.visit(child))

        for attr in ('starargs', 'kwargs'):
            child = getattr(node, attr)
            if child:
                right.update(self.visit(child))

        for src in left | right:
            if not self.graph.has_node(src):
                self.undefined.add(src)

        if self.call_deps:
            add_edges(self.graph, left, right)
            add_edges(self.graph, right, left)

        right.update(left)
        return right

    def visitSubscript(self, node):
        if isinstance(node.ctx, _ast.Load):
            return collect_(self, node)
        else:
            sources = self.visit(node.slice)
            targets = self.visit(node.value)
            self.modified.update(targets)
            add_edges(self.graph, targets, sources)
            return targets

    def handle_generators(self, generators):
        defined = set()
        required = set()
        for generator in generators:
            get_symbols(generator, _ast.Load)
            required.update(get_symbols(generator, _ast.Load) - defined)
            defined.update(get_symbols(generator, _ast.Store))

        return defined, required

    def visitListComp(self, node):

        defined, required = self.handle_generators(node.generators)
        required.update(get_symbols(node.elt, _ast.Load) - defined)

        for symbol in required:
            if not self.graph.has_node(symbol):
                self.graph.add_node(symbol)
                self.undefined.add(symbol)

        return required

    def visitSetComp(self, node):

        defined, required = self.handle_generators(node.generators)
        required.update(get_symbols(node.elt, _ast.Load) - defined)

        for symbol in required:
            if not self.graph.has_node(symbol):
                self.graph.add_node(symbol)
                self.undefined.add(symbol)

        return required

    def visitDictComp(self, node):

        defined, required = self.handle_generators(node.generators)
        required.update(get_symbols(node.key, _ast.Load) - defined)
        required.update(get_symbols(node.value, _ast.Load) - defined)

        for symbol in required:
            if not self.graph.has_node(symbol):
                self.graph.add_node(symbol)
                self.undefined.add(symbol)

        return required
Beispiel #15
0
def bidirectionalize(graph: nx.DiGraph) -> None:
    graph.add_edges_from([
        (node2, node1, reversed_edge_attrs(eattrs))
        for node1, node2, eattrs in graph.out_edges(data=True)
        if not graph.has_edge(node2, node1)
    ])
Beispiel #16
0
def restore_alphabet(input_file_path=None):
    """
    This function restores the original alphabet from given dictionary.
    Here dictionary is considered as passed in txt-file.
    Commands:
        python main.py - simple test, which shows result via console
        python main.py /tmp/alphabet.txt - run script with data from
            specific file. Results are put in 'result.txt'.
    :param input_file_path: file with dictionary.
    :return: list of chars, which are considered to be the wanted alphabet
    """
    with open(input_file_path, 'r') as dictionary:
        dg = DiGraph()

        # get first word
        previous_word = dictionary.readline()
        previous_word = previous_word.strip()
        previous_position = 1
        dg.add_nodes_from(previous_word)

        # get second word, if it is one
        current_word = dictionary.readline()
        while current_word:
            current_word = current_word.strip()
            current_position = previous_position + 1

            # look for the first non-equal chars,
            # which are situated on the same positions in
            # previous word and current word
            index = 0
            length_previous = len(previous_word)
            length_current = len(current_word)

            while (index < length_previous and index < length_current):
                if previous_word[index] == current_word[index]:
                    index += 1
                else:
                    # if the pair is found, and there is no opposite rule of
                    # any kind, add chars as nodes and an edge,
                    # which demonstrates their order.
                    if dg.has_edge(current_word[index], previous_word[index]):
                        raise Exception(
                            "Wrong words order!\n"
                            "This pair of words violates rule: {} -> {}.\n"
                            "Position: {}.\n"
                            "Words: '{}' and '{}'.\n"
                            "String numbers in dictionary: {} and {}."
                            "".format(previous_word[index],
                                      current_word[index], index + 1,
                                      previous_word, current_word,
                                      previous_position, current_position))
                    dg.add_edge(previous_word[index], current_word[index])
                    break
            else:
                if length_previous > length_current:
                    raise Exception("Wrong words order!\n"
                                    "Words: '{}' and '{}'.\n"
                                    "String numbers in dictionary: {} and {}."
                                    "".format(previous_word, current_word,
                                              previous_position,
                                              current_position))
            dg.add_nodes_from(current_word[index:])

            # move to the next word from dictionary
            previous_word = current_word
            previous_position = current_position
            current_word = dictionary.readline()

        # use topological sort to get the correct order of chars
        return topological_sort(dg)
Beispiel #17
0
class GraphBuilder():
    """
    A class for building the reaction graph.
    """
    def __init__(self) -> None:
        self._reaction_graph = DiGraph()

    def _add_node(self, node_id: str, type: NodeType, label: str) -> None:
        """
        Adding a node if the node is not already in the graph.

        Args:
            node_id: The ID of a graph node e.g. a domain will have the ID A_[dom]
            type: The type of a node e.g. component, domain, residue
            label: The label of the node e.g. the domain name (dom)

        Returns:

        """
        if not self._reaction_graph.has_node(node_id):
            logger.debug('Adding new node node_id: {0} label: {1}, type: {2}'.format(node_id, label, type))
            self._reaction_graph.add_node(node_id, label=label, type=type.value)

    def _add_edge(self, source: str, target: str, interaction: EdgeType, width: EdgeWith) -> None:
        """
        Adding an edge to the graph.

        Note:
             Internal edges are edges within the different levels of an specification.
             External edges are edges between specific resolution levels of two specifications
                e.g. between component and domain, domain and domain and so on.

        Args:
            source: The source of an edge e.g. component node, domain node, residue node.
            target: The target of an edge e.g. component node, domain node, residue node.
            interaction (EdgeType): The type of an interaction.
            width (EdgeWith): The width of an edge.

        Returns:
            None

        """
        if not self._reaction_graph.has_edge(source, target):
            logger.debug('Adding new edge source: {0} target: {1}, interaction: {2}, width: {3}'.format(source,
                                                                                                        target,
                                                                                                        interaction.value,
                                                                                                        width))
            self._reaction_graph.add_edge(source, target, interaction=interaction.value, width=width.value)
        elif width == EdgeWith.external:
            logger.debug('Adding replace inner edge with external edge source: {0} target: {1}, interaction: {2}, width: {3}'.format(source,
                                                                                                                                      target,
                                                                                                                                      interaction.value,
                                                                                                                                      width))
            self._reaction_graph.add_edge(source, target, interaction=interaction.value, width=width.value)

    def add_external_edge(self, source: Spec, target: Spec, type: EdgeType) -> None:
        """
        Adding an external edge.

        Note:
            An external edges is an edge between two specific resolution levels of two specifications respectively
                e.g. between component and domain, domain and domain and so on.
        Args:
            source: The source specification.
            target: The target specification.
            type (EdgeType): The type of this edge e.g. interaction, modification etc.

        Returns:
            None

        """
        logger.info('Adding external edge source: {0} target: {1}, interaction: {2}'.format(get_node_id(source, source.resolution),
                                                                                            get_node_id(target, target.resolution),
                                                                                            type))
        self._add_edge(get_node_id(source, source.resolution), get_node_id(target, target.resolution),
                       interaction=type, width=EdgeWith.external)

    def add_spec_information(self, specification: Spec) -> None:
        """
        Adding specification information to the reaction graph.

        Args:
            specification: The specification of a reaction reactant

        Returns:
            None

        """

        def _add_spec_nodes() -> None:
            logger.info('Adding component node -> id: {0}, label: {1}'.format(get_node_id(specification, NodeType.component),
                                                                              get_node_label(specification, NodeType.component)))
            self._add_node(node_id=get_node_id(specification, NodeType.component), type=NodeType.component,
                           label=get_node_label(specification, NodeType.component))

            if specification.locus.domain:
                logger.info('Adding domain node -> id: {0}, label: {1}'.format(get_node_id(specification, NodeType.domain),
                                                                               get_node_label(specification, NodeType.domain)))
                self._add_node(get_node_id(specification, NodeType.domain), type=NodeType.domain,
                               label=get_node_label(specification, NodeType.domain))
            if specification.locus.residue:
                logger.info('Adding residue node id: {0}, label: {1}'.format(get_node_id(specification, NodeType.residue),
                                                                             get_node_label(specification, NodeType.residue)))
                self._add_node(get_node_id(specification, NodeType.residue), type=NodeType.residue,
                               label=get_node_label(specification, NodeType.residue))

        def _add_spec_edges() -> None:
            """
            Adding internal edges between nodes.

            Note:
                Internal edges are edges within the different levels of an specification.

            Returns:
                None

            """

            if specification.locus.domain:
                logger.info('Adding internal edge component -> domain source: {} target: {}'.format(get_node_id(specification, NodeType.component),
                                                                                           get_node_id(specification, NodeType.domain)))
                self._add_edge(get_node_id(specification, NodeType.component),
                               get_node_id(specification, NodeType.domain), interaction=EdgeType.interaction,
                               width=EdgeWith.internal)

                if specification.locus.residue:
                    logger.info('Adding internal edge domain -> residue source: {} target: {}'.format(get_node_id(specification, NodeType.domain),
                                                                                             get_node_id(specification, NodeType.residue)))
                    self._add_edge(get_node_id(specification, NodeType.domain), get_node_id(specification, NodeType.residue),
                                   interaction=EdgeType.interaction, width=EdgeWith.internal)
            elif specification.locus.residue:
                logger.info('Adding internal edge component -> residue source: {} target: {}'.format(get_node_id(specification, NodeType.component),
                                                                       get_node_id(specification, NodeType.residue)))
                self._add_edge(get_node_id(specification, NodeType.component), get_node_id(specification, NodeType.residue),
                               interaction=EdgeType.interaction, width=EdgeWith.internal)
        logger.info('Adding nodes for {}'.format(specification))
        _add_spec_nodes()
        logger.info('Adding internal edges for {}'.format(specification))
        _add_spec_edges()

    def get_graph(self) -> DiGraph:
        """
        Returning the reaction graph

        Returns:
            The reaction graph (DiGraph).

        """
        return self._reaction_graph
Beispiel #18
0
class CollectNodes(Visitor):

    def __init__(self, call_deps=False):
        self.graph = DiGraph()
        self.modified = set()
        self.used = set()
        self.undefined = set()
        self.sources = set()
        self.targets = set()

        self.context_names = set()

        self.call_deps = call_deps

    visitDefault = collect_

    def visitName(self, node):
        if isinstance(node.ctx, _ast.Store):
            self.modified.add(node.id)

        elif isinstance(node.ctx, _ast.Load):
            self.used.update(node.id)

        if not self.graph.has_node(node.id):
            self.graph.add_node(node.id)
            if isinstance(node.ctx, _ast.Load):
                self.undefined.add(node.id)

        for ctx_var in self.context_names:
            if not self.graph.has_edge(node.id, ctx_var):
                self.graph.add_edge(node.id, ctx_var)

        return {node.id}

    def visitalias(self, node):
        name = node.asname if node.asname else node.name

        if '.' in name:
            name = name.split('.', 1)[0]

        if not self.graph.has_node(name):
            self.graph.add_node(name)

        return {name}

    def visitCall(self, node):
        left = self.visit(node.func)

        right = set()
        for attr in ('args', 'keywords'):
            for child in getattr(node, attr):
                if child:
                    right.update(self.visit(child))

        for attr in ('starargs', 'kwargs'):
            child = getattr(node, attr)
            if child:
                right.update(self.visit(child))

        for src in left | right:
            if not self.graph.has_node(src):
                self.undefined.add(src)

        if self.call_deps:
            add_edges(self.graph, left, right)
            add_edges(self.graph, right, left)

        right.update(left)
        return right

    def visitSubscript(self, node):
        if isinstance(node.ctx, _ast.Load):
            return collect_(self, node)
        else:
            sources = self.visit(node.slice)
            targets = self.visit(node.value)
            self.modified.update(targets)
            add_edges(self.graph, targets, sources)
            return targets
        
    def handle_generators(self, generators):
        defined = set()
        required = set()
        for generator in generators:
            get_symbols(generator, _ast.Load)
            required.update(get_symbols(generator, _ast.Load) - defined)
            defined.update(get_symbols(generator, _ast.Store))
            
        return defined, required
    
    def visitListComp(self, node):

        defined, required = self.handle_generators(node.generators)
        required.update(get_symbols(node.elt, _ast.Load) - defined)

        for symbol in required:
            if not self.graph.has_node(symbol):
                self.graph.add_node(symbol)
                self.undefined.add(symbol)

        return required

    def visitSetComp(self, node):

        defined, required = self.handle_generators(node.generators)
        required.update(get_symbols(node.elt, _ast.Load) - defined)

        for symbol in required:
            if not self.graph.has_node(symbol):
                self.graph.add_node(symbol)
                self.undefined.add(symbol)

        return required

    def visitDictComp(self, node):

        defined, required = self.handle_generators(node.generators)
        required.update(get_symbols(node.key, _ast.Load) - defined)
        required.update(get_symbols(node.value, _ast.Load) - defined)

        for symbol in required:
            if not self.graph.has_node(symbol):
                self.graph.add_node(symbol)
                self.undefined.add(symbol)

        return required
Beispiel #19
0
class ExecutionGraph:
    def __init__(self, ctx: Context):
        self.graph = DiGraph()
        self.ctx = ctx

        ctx.logger.info("Preparing execution plan...")

        comp_ids = self._effective_component_ids()
        for comp_id in comp_ids:
            self._add_component(comp_id)

        if ctx.flags.debug:
            self.ctx.logger.debug("Resolved execution order: {}".format(
                list(self.topologically_ordered_comp_ids())))

    def _add_deps(self, dependencies, comp_id):
        for dep in dependencies:
            self.graph.add_node(dep)

            if not self.graph.has_edge(dep, comp_id):
                self.ctx.logger.progress("Adding dependency: {} -> {}".format(
                    comp_id, dep))
                self.graph.add_edge(dep, comp_id)

            if not is_directed_acyclic_graph(self.graph):
                raise CyclicDependencyError(
                    "Dependency {} -> {} forms a cycle!".format(comp_id, dep))

            self._add_component(dep)

    def _effective_component_ids(self):
        if self.ctx.components is None:
            all_components = self.ctx.registry.component_ids()
            self.ctx.logger.debug(
                "No components have been explicitly specified. Will execute all: {}"
                .format(", ".join(all_components)))
            return all_components
        else:
            requested = self.ctx.components
            self.ctx.logger.info("Requested components: {}".format(
                ", ".join(requested)))
            return requested

    def _add_component(self, comp_id):
        self.graph.add_node(comp_id)

        if not self.ctx.registry.component_ids().__contains__(comp_id):
            raise MissingDependencyError(
                "No component with id '{}' is registered! "
                "You might need to add '--experimental' or '-e'".format(
                    comp_id))

        self._add_deps(
            prerequisites_of(self.ctx.registry.find_collector(comp_id)),
            comp_id)
        self._add_deps(
            prerequisites_of(self.ctx.registry.find_validator(comp_id)),
            comp_id)
        for reactor in self.ctx.registry.find_reactors(comp_id):
            self._add_deps(prerequisites_of(reactor), comp_id)

    def topologically_ordered_comp_ids(self):
        return topological_sort(self.graph)
Beispiel #20
0
class TransformTree:
    '''
    A feature complete if not particularly optimized implementation of a transform graph.
    
    Few allowances are made for thread safety, caching, or enforcing graph structure.
    '''

    def __init__(self, base_frame='world'):
        self._transforms = DiGraph()
        self._parents    = {}
        self._paths      = {}
        self._is_changed = False
        self.base_frame  = base_frame

    def update(self, 
               frame_to,
               frame_from = None,
               **kwargs):
        '''
        Update a transform in the tree.

        Arguments
        ---------
        frame_from: hashable object, usually a string (eg 'world').
                    If left as None it will be set to self.base_frame
        frame_to:   hashable object, usually a string (eg 'mesh_0')
        
        Additional kwargs (can be used in combinations)
        --------- 
        matrix:      (4,4) array 
        quaternion:  (4) quatenion
        axis:        (3) array
        angle:       float, radians
        translation: (3) array
        '''
        if frame_from is None:
            frame_from = self.base_frame

        matrix = np.eye(4)
        if 'matrix' in kwargs:
            # a matrix takes precedence over other options
            matrix = kwargs['matrix']
        elif 'quaternion' in kwargs:
            matrix = quaternion_matrix(kwargs['quaternion'])
        elif ('axis' in kwargs) and ('angle' in kwargs):
            matrix = rotation_matrix(kwargs['angle'],
                                     kwargs['axis'])
        else: 
            raise ValueError('Couldn\'t update transform!')

        if 'translation' in kwargs:
            # translation can be used in conjunction with any of the methods of 
            # specifying transforms. In the case a matrix and translation are passed,
            # we add the translations together rather than picking one. 
            matrix[0:3,3] += kwargs['translation']

        if self._transforms.has_edge(frame_from, frame_to):
            self._transforms.edge[frame_from][frame_to]['matrix'] = matrix
            self._transforms.edge[frame_from][frame_to]['time']   = time.time()
        else:
            # since the connectivity has changed, throw out previously computed
            # paths through the transform graph so queries compute new shortest paths
            # we could only throw out transforms that are connected to the new edge,
            # but this is less bookeeping at the expensive of being slower. 
            self._paths = {}
            self._transforms.add_edge(frame_from, 
                                      frame_to, 
                                      matrix = matrix, 
                                      time   = time.time())
        self._is_changed = True

    def get(self,
            frame_to,
            frame_from = None):
        '''
        Get the transform from one frame to another, assuming they are connected
        in the transform tree. 

        If the frames are not connected a NetworkXNoPath error will be raised.

        Arguments
        ---------
        frame_from: hashable object, usually a string (eg 'world').
                    If left as None it will be set to self.base_frame
        frame_to:   hashable object, usually a string (eg 'mesh_0')

        Returns
        ---------
        transform:  (4,4) homogenous transformation matrix
        '''
        if frame_from is None:
            frame_from = self.base_frame

        transform = np.eye(4)
        path, inverted = self._get_path(frame_from, frame_to)
        for i in range(len(path) - 1):
            matrix = self._transforms.get_edge_data(path[i], 
                                                    path[i+1])['matrix']
            transform = np.dot(transform, matrix)
        if inverted:
            transform = np.linalg.inv(transform)
        return transform

    def clear(self):
        self._transforms = DiGraph()
        self._paths      = {}
        
    def _get_path(self, 
                  frame_from,
                  frame_to):
        '''
        Find a path between two frames, either from cached paths or
        from the transform graph. 
        
        Arguments
        ---------
        frame_from: a frame key, usually a string 
                    example: 'world'
        frame_to:   a frame key, usually a string 
                    example: 'mesh_0'

        Returns
        ----------
        path: (n) list of frame keys
              example: ['mesh_finger', 'mesh_hand', 'world']
        inverted: boolean flag, whether the path is traversing stored
                  matrices forwards or backwards. 
        '''
        try: 
            return self._paths[(frame_from, frame_to)]
        except KeyError:
            return self._generate_path(frame_from, frame_to)

    def _generate_path(self, 
                       frame_from, 
                       frame_to):
        '''
        Generate a path between two frames.
        
        Arguments
        ---------
        frame_from: a frame key, usually a string 
                    example: 'world'
        frame_to:   a frame key, usually a string 
                    example: 'mesh_0'

        Returns
        ----------
        path: (n) list of frame keys
              example: ['mesh_finger', 'mesh_hand', 'world']
        inverted: boolean flag, whether the path is traversing stored
                  matrices forwards or backwards. 
        '''
        try: 
            path = shortest_path(self._transforms, frame_from, frame_to)
            inverted = False
        except NetworkXNoPath:
            path = shortest_path(self._transforms, frame_to, frame_from)
            inverted = True
        self._paths[(frame_from, frame_to)] = (path, inverted)
        return path, inverted
Beispiel #21
0
    def from_equilibrium_graph(cls, graph: nx.DiGraph, ph_values: np.ndarray):
        """Instantiate a titration curve specified using microequilibrium pKas

        Parameters
        ----------        
        graph - a directed graph of microequilibria.            
        ph_values - 1D-array of pH values that correspond to the curve

        Notes
        -----
        In the equilibrium graph every node is a state.
        Edges between states have a pKa associated with them. 
        Every edge has direction from Deprotonated -> Protonated.

        Can be generated using `micro_pKas_to_equilibrium_graph`
        """

        # First state has least protons bound, set to 0 to be reference to other states
        reference = list(nx.algorithms.dag.dag_longest_path(graph))[0]
        all_nodes = list(deepcopy(graph.nodes))
        all_nodes.remove(reference)
        all_nodes.insert(0, reference)
        augmented_graph = add_reverse_equilibrium_arrows(graph)
        augmented_graph = add_Ka_equil_graph(augmented_graph)

        instance = cls()
        instance.augmented_graph = augmented_graph
        energies: List[np.ndarray] = list()

        nbound: List[int] = [0]

        energies.append(free_energy_from_pka(0, 0.0, ph_values))

        # Every node is a state
        for s, state in enumerate(all_nodes[1:], start=1):
            # Least number of equilibria to pass through to reach a state
            # If there are more than one path, the shortest one is the one that uses pKas closer to 7
            # Which should be the most relevant range, and likely the applicable range of most techniques
            path = nx.shortest_path(augmented_graph,
                                    reference,
                                    all_nodes[s],
                                    weight="pKa7")
            # The number of protons is equal to the number of equilibria traversed
            bound_protons = len(path) - 1
            sumpKa = 0
            # Add pKa along edges of the path
            for edge in range(bound_protons):
                sumpKa += augmented_graph[path[edge]][path[edge + 1]]["pKa"]
                # For reverse paths, deduct one proton
                if not graph.has_edge(path[edge], path[edge + 1]):
                    bound_protons -= 1

            # Free energy calculated according to Ullmann (2003).
            energies.append(
                free_energy_from_pka(bound_protons, sumpKa, ph_values))
            nbound.append(bound_protons)

        instance.free_energies = np.asarray(energies)
        instance.populations = populations_from_free_energies(
            instance.free_energies)
        instance.ph_values = ph_values
        instance.state_ids = all_nodes
        instance.charges = np.asarray(nbound)
        instance.mean_charge = instance.charges @ instance.populations
        # Set lowest value to 0
        instance.mean_charge -= int(round(min(instance.mean_charge)))

        return instance