Module coinor.gimpy.graph

A Graph class implementation. The aim for this implementation is 1. To reflect implementation methods in literature as much as possible 3. To have something close to a "classic" object-oriented design (compared to previous versions)

This implementation can be considered as a compromise between a graph class designed for visualization and an efficient graph data structure.

One deviation from standard Graph implementations is to keep in neighbors in an other adjacency list. We do this for efficiency reasons considering traversing residual graphs.

We have a class for Graph and a class for Node. Edges are not represented as objects. They are kept in a dictionary which also keeps their attributes.

Graph display related methods are inspired from Pydot. They are re-written considering GIMPy needs. We also borrow two methods from Pydot, see global_constants.py for details.

Default graph type is an undirected graph.

No custom exception will raise when the user tries to get in_neighbors of an undirected graph. She should be aware of this. Python will raise an exception since user is trying to read an attribute that does not exits.

Methods that implement algorithms has display argument in their API. If this argument is not specified global display setting will be used for display purposes of the algorithm method implements. You can use display argument to get visualization of algorithm without changing global display behavior of your Graph/Tree object.

Method documentation strings are orginized as follows. API: method_name(arguments) Description: Description of the method. Input: Arguments and their explanation. Pre: Necessary class attributes that should exists, methods to be called before this method. Post: Class attributes changed within the method. Return: Return value of the method.

TODO(aykut): -> svg display mode -> label_strong_components() API change. Check backward compatibilty. -> dfs should use search()? -> display mode svg is not supported. future: -> The solution we find is not strongly feasible. Fix this.

Expand source code
'''
A Graph class implementation. The aim for this implementation is
1. To reflect implementation methods in literature as much as possible
3. To have something close to a "classic" object-oriented design
(compared to previous versions)

This implementation can be considered as a compromise between a graph
class designed for visualization and an efficient graph data structure.

One deviation from standard Graph implementations is to keep in neighbors in
an other adjacency list. We do this for efficiency reasons considering
traversing residual graphs.

We have a class for Graph and a class for Node. Edges are not represented as
objects. They are kept in a dictionary which also keeps their attributes.

Graph display related methods are inspired from Pydot. They are re-written
considering GIMPy needs. We also borrow two methods from Pydot, see
global_constants.py for details.

Default graph type is an undirected graph.

No custom exception will raise when the user tries to get in_neighbors of an
undirected graph. She should be aware of this. Python will raise an exception
since user is trying to read an attribute that does not exits.

Methods that implement algorithms has display argument in their API. If this
argument is not specified global display setting will be used for display
purposes of the algorithm method implements. You can use display argument to
get visualization of algorithm without changing global display behavior of your
Graph/Tree object.

Method documentation strings are orginized as follows.
API: method_name(arguments)
Description: Description of the method.
Input: Arguments and their explanation.
Pre: Necessary class attributes that should exists, methods to be called
     before this method.
Post: Class attributes changed within the method.
Return: Return value of the method.

TODO(aykut):
-> svg display mode
-> label_strong_components() API change. Check backward compatibilty.
-> dfs should use search()?
-> display mode svg is not supported.
future:
-> The solution we find is not strongly feasible. Fix this.
'''
from __future__ import division
from __future__ import print_function
from __future__ import absolute_import
from future import standard_library
standard_library.install_aliases()
from builtins import str
from builtins import range
from past.utils import old_div
from builtins import object

from .global_constants import *
try:
    from src.blimpy import Stack, Queue, PriorityQueue
except ImportError:
    from coinor.blimpy import Stack, Queue, PriorityQueue
import subprocess # for call()
import io   # for StringIO()
import copy       # for deepcopy()
import sys        # for exit()
import random     # for seed, random, randint
import tempfile   # for mkstemp()
import os         # for close()
import operator   # for itemgetter()

try:
    import pygtk
    import gtk
    import xdot
except ImportError:
    XDOT_INSTALLED = False
else:
    XDOT_INSTALLED = True

try:
    import dot2tex # for dot2tex method
except ImportError:
    DOT2TEX_INSTALLED = False
else:
    DOT2TEX_INSTALLED = True

try:
    from PIL import Image as PIL_Image
except ImportError:
    PIL_INSTALLED = False
else:
    PIL_INSTALLED = True

try:
    import matplotlib
except ImportError:
    MATPLOTLIB_INSTALLED = False
else:
    MATPLOTLIB_INSTALLED = True
#    matplotlib.use('TkAgg')
    import matplotlib.pyplot as plt
    plt.rcParams['figure.dpi'] = 300

def handle_close(evt):
    print('Figure closed. Exiting!')
    plt.close('all')

class Node(object):
    '''
    Node class. A node object keeps node attributes. Has a method to write
    node in Dot language grammer.
    '''
    def __init__(self, name, **attr):
        '''
        API: __init__(self, name, **attrs)
        Description:
        Node class constructor. Sets name and attributes using arguments.
        Input:
            name: Name of node.
            **attrs: Node attributes.
        Post:
            Sets self.name and self.attr.
        '''
        self.name = name
        self.attr = copy.deepcopy(DEFAULT_NODE_ATTRIBUTES)
        for a in attr:
            self.attr[a] = attr[a]

    def get_attr(self, attr):
        '''
        API: get_attr(self, attr)
        Description:
        Returns node attribute attr.
        Input:
            attr: Node attribute to get.
        Return:
            Returns Node attribute attr if exists returns None, otherwise.
        '''
        if attr in self.attr:
            return self.attr[attr]
        else:
            return None

    def set_attr(self, attr, value):
        '''
        API: set_attr(self, attr, value)
        Description:
        Sets node attribute attr to value.
        Input:
            attr: Node attribute to set.
            value: New value of the attribute.
        Post:
            Updates self.attr[attr].
        '''
        self.attr[attr] = value

    def to_string(self):
        '''
        API: to_string(self)
        Description:
        Returns string representation of node in dot language.
        Return:
            String representation of node.
        '''
        node = list()
        node.append(quote_if_necessary(str(self.name)))
        node.append(' [')
        flag = False
        for a in self.attr:
            flag = True
            node.append(a)
            node.append('=')
            node.append(quote_if_necessary(str(self.attr[a])))
            node.append(', ')
        if flag is True:
            node = node[:-1]
        node.append(']')
        return ''.join(node)

    def __repr__(self):
        '''
        API: __repr__(self)
        Description:
        Returns string representation of node in dot language.
        Return:
            String representation of node.
        '''
        return self.to_string()


class Graph(object):
    '''
    Graph class, implemented using adjacency list. See GIMPy README for more
    information.
    '''
    def __init__(self, **attr):
        '''
        API: __init__(self, **attrs)
        Description:
        Graph class constructor. Sets attributes using argument.
        Input:
            **attrs: Graph attributes.
        Post:
            Sets following attributes using **attrs; self.attr,
            self.graph_type. Creates following initial attributes;
            self.neighbors, self.in_neighbors, self.nodes, self.out_neighbors,
            self.cluster
        '''
        # graph attributes
        self.attr = copy.deepcopy(DEFAULT_GRAPH_ATTRIBUTES)
        # set attributes using constructor
        for a in attr:
            self.attr[a] = attr[a]
        # set name
        if 'name' in self.attr:
            self.name = self.attr['name']
        else:
            self.name = 'G'
        # edge attributes
        self.edge_attr = dict()
        # we treat type attribute and keep it in a separate class attribute
        if 'type' in self.attr:
            self.graph_type = self.attr['type']
        else:
            self.graph_type = UNDIRECTED_GRAPH
        # adjacency list of nodes, it is a dictionary of lists
        self.neighbors = {}
        # if the graph is undirected we do not need in_neighbor
        if self.graph_type is DIRECTED_GRAPH:
            self.in_neighbors = {}
        self.nodes = {}
        self.edge_connect_symbol = EDGE_CONNECT_SYMBOL[self.graph_type]
        self.out_neighbors = self.neighbors
        if 'display' not in self.attr:
            self.attr['display']='off'
        if 'layout' not in self.attr:
            self.attr['layout'] = 'fdp'
        self.attr['cluster_count'] = 0
        self.cluster = {}

    def __repr__(self):
        '''
        API: __repr__(self)
        Description:
        Returns string representation of the graph.
        Return:
            String representation of the graph.
        '''
        data = str()
        for n in self.nodes:
            data += str(n)
            data += ' -> '
            data += self.neighbors[n].__repr__()
            data += '\n'
        data = data[:-1]
        return data

    def __contains__(self, item):
        '''
        API: __contains__(self, item)
        Description:
        Return true if item is in graph. item can be a node name or a tuple
        that represents an edge.
        Return:
            True if item is in graph.
        '''
        if isinstance(item, tuple):
            name1 = item[0]
            name2 = item[1]
            if self.graph_type is DIRECTED_GRAPH:
                return (name1, name2) in self.edge_attr
            else:
                return ((name1, name2) in self.edge_attr or
                        (name2, name1) in self.edge_attr)
        else:
            return item in self.nodes

    def add_node(self, name, **attr):
        '''
        API: add_node(self, name, **attr)
        Description:
        Adds node to the graph.
        Pre:
            Graph should not contain a node with this name. We do not allow
            multiple nodes with the same name.
        Input:
            name: Name of the node.
            attr: Node attributes.
        Post:
            self.neighbors, self.nodes and self.in_neighbors are updated.
        Return:
            Node (a Node class instance) added to the graph.
        '''
        if name in self.neighbors:
            raise MultipleNodeException
        self.neighbors[name] = list()
        if self.graph_type is DIRECTED_GRAPH:
            self.in_neighbors[name] = list()
        self.nodes[name] = Node(name, **attr)
        return self.nodes[name]

    def del_node(self, name):
        '''
        API: del_node(self, name)
        Description:
        Removes node from Graph.
        Input:
            name: Name of the node.
        Pre:
            Graph should contain a node with this name.
        Post:
            self.neighbors, self.nodes and self.in_neighbors are updated.
        '''
        if name not in self.neighbors:
            raise Exception('Node %s does not exist!' %str(name))
        for n in self.neighbors[name]:
            del self.edge_attr[(name, n)]
            if self.graph_type == UNDIRECTED_GRAPH:
                self.neighbors[n].remove(name)
            else:
                self.in_neighbors[n].remove(name)
        if self.graph_type is DIRECTED_GRAPH:
            for n in self.in_neighbors[name]:
                del self.edge_attr[(n, name)]
                self.neighbors[n].remove(name)
        del self.neighbors[name]
        del self.in_neighbors[name]
        del self.nodes[name]

    def add_edge(self, name1, name2, **attr):
        '''
        API: add_edge(self, name1, name2, **attr)
        Description:
        Adds edge to the graph. Sets edge attributes using attr argument.
        Input:
            name1: Name of the source node (if directed).
            name2: Name of the sink node (if directed).
            attr: Edge attributes.
        Pre:
            Graph should not already contain this edge. We do not allow
            multiple edges with same source and sink nodes.
        Post:
            self.edge_attr is updated.
            self.neighbors, self.nodes and self.in_neighbors are updated if
            graph was missing at least one of the nodes.
        '''
        if (name1, name2) in self.edge_attr:
            raise MultipleEdgeException
        if self.graph_type is UNDIRECTED_GRAPH and (name2,name1) in self.edge_attr:
            raise MultipleEdgeException
        self.edge_attr[(name1,name2)] = copy.deepcopy(DEFAULT_EDGE_ATTRIBUTES)
        for a in attr:
            self.edge_attr[(name1,name2)][a] = attr[a]
        if name1 not in self.nodes:
            self.add_node(name1)
        if name2 not in self.nodes:
            self.add_node(name2)
        self.neighbors[name1].append(name2)
        if self.graph_type is UNDIRECTED_GRAPH:
            self.neighbors[name2].append(name1)
        else:
            self.in_neighbors[name2].append(name1)

    def del_edge(self, e):
        '''
        API: del_edge(self, e)
        Description:
        Removes edge from graph.
        Input:
            e: Tuple that represents edge, in (source,sink) form.
        Pre:
            Graph should contain this edge.
        Post:
            self.edge_attr, self.neighbors and self.in_neighbors are updated.
        '''
        if self.graph_type is DIRECTED_GRAPH:
            try:
                del self.edge_attr[e]
            except KeyError:
                raise Exception('Edge %s does not exists!' %str(e))
            self.neighbors[e[0]].remove(e[1])
            self.in_neighbors[e[1]].remove(e[0])
        else:
            try:
                del self.edge_attr[e]
            except KeyError:
                try:
                    del self.edge_attr[(e[1],e[0])]
                except KeyError:
                    raise Exception('Edge %s does not exists!' %str(e))
            self.neighbors[e[0]].remove(e[1])
            self.neighbors[e[1]].remove(e[0])

    def get_node(self, name):
        '''
        API: get_node(self, name)
        Description:
        Returns node object with the provided name.
        Input:
            name: Name of the node.
        Return:
            Returns node object if node exists, returns None otherwise.
        '''
        if name in self.nodes:
            return self.nodes[name]
        else:
            return None

    def get_edge_cost(self, edge):
        '''
        API: get_edge_cost(self, edge)
        Description:
        Returns cost attr of edge, required for minimum_spanning_tree_kruskal().
        Input:
            edge: Tuple that represents edge, in (source,sink) form.
        Return:
            Returns cost attribute value of the edge.
        '''
        return self.get_edge_attr(edge[0], edge[1], 'cost')

    def check_edge(self, name1, name2):
        '''
        API: check_edge(self, name1, name2)
        Description:
        Return True if edge exists, False otherwise.
        Input:
            name1: name of the source node.
            name2: name of the sink node.
        Return:
            Returns True if edge exists, False otherwise.
        '''
        if self.graph_type is DIRECTED_GRAPH:
            return (name1, name2) in self.edge_attr
        else:
            return ((name1, name2) in self.edge_attr or
                    (name2, name1) in self.edge_attr)

    def get_node_list(self):
        '''
        API: get_node_list(self)
        Description:
        Returns node list.
        Return:
            List of nodes.
        '''
        return list(self.neighbors.keys())

    def get_edge_list(self):
        '''
        API: get_edge_list(self)
        Description:
        Returns edge list.
        Return:
            List of edges, edges are tuples and in (source,sink) format.
        '''
        return list(self.edge_attr.keys())

    def get_node_num(self):
        '''
        API: get_node_num(self)
        Description:
        Returns number of nodes.
        Return:
            Number of nodes.
        '''
        return len(self.neighbors)

    def get_edge_num(self):
        '''
        API: get_edge_num(self)
        Description:
        Returns number of edges.
        Return:
            Number of edges.
        '''
        return len(self.edge_attr)

    def get_node_attr(self, name, attr):
        '''
        API: get_node_attr(self, name, attr)
        Description:
        Returns attribute attr of given node.
        Input:
            name: Name of node.
            attr: Attribute of node.
        Pre:
            Graph should have this node.
        Return:
            Value of node attribute attr.
        '''
        return self.get_node(name).get_attr(attr)

    def get_edge_attr(self, n, m, attr):
        '''
        API: get_edge_attr(self, n, m, attr)
        Description:
        Returns attribute attr of edge (n,m).
        Input:
            n: Source node name.
            m: Sink node name.
            attr: Attribute of edge.
        Pre:
            Graph should have this edge.
        Return:
            Value of edge attribute attr.
        '''
        if self.graph_type is DIRECTED_GRAPH:
            return self.edge_attr[(n,m)][attr]
        else:
            try:
                return self.edge_attr[(n,m)][attr]
            except KeyError:
                return self.edge_attr[(m,n)][attr]

    def set_node_attr(self, name, attr, value):
        '''
        API: set_node_attr(self, name, attr)
        Description:
        Sets attr attribute of node named name to value.
        Input:
            name: Name of node.
            attr: Attribute of node to set.
        Pre:
            Graph should have this node.
        Post:
            Node attribute will be updated.
        '''
        self.get_node(name).set_attr(attr, value)

    def set_edge_attr(self, n, m, attr, value):
        '''
        API: set_edge_attr(self, n, m, attr, value)
        Description:
        Sets attr attribute of edge (n,m) to value.
        Input:
            n: Source node name.
            m: Sink node name.
            attr: Attribute of edge to set.
            value: New value of attribute.
        Pre:
            Graph should have this edge.
        Post:
            Edge attribute will be updated.
        '''
        if self.graph_type is DIRECTED_GRAPH:
            self.edge_attr[(n,m)][attr] = value
        else:
            try:
                self.edge_attr[(n,m)][attr] = value
            except KeyError:
                self.edge_attr[(m,n)][attr] = value

    def get_neighbors(self, name):
        '''
        API: get_neighbors(self, name)
        Description:
        Returns list of neighbors of given node.
        Input:
            name: Node name.
        Pre:
            Graph should have this node.
        Return:
            List of neighbor node names.
        '''
        return self.neighbors[name]

    def get_in_neighbors(self, name):
        '''
        API: get_in_neighbors(self, name)
        Description:
        Returns list of in neighbors of given node.
        Input:
            name: Node name.
        Pre:
            Graph should have this node.
        Return:
            List of in-neighbor node names.
        '''
        return self.in_neighbors[name]

    def get_out_neighbors(self, name):
        '''
        API: get_out_neighbors(self, name)
        Description:
        Returns list of out-neighbors of given node.
        Input:
            name: Node name.
        Pre:
            Graph should have this node.
        Return:
            List of out-neighbor node names.
        '''
        return self.neighbors[name]

    def edge_to_string(self, e):
        '''
        API: edge_to_string(self, e)
        Description:
        Return string that represents edge e in dot language.
        Input:
            e: Edge tuple in (source,sink) format.
        Pre:
            Graph should have this edge.
        Return:
            String that represents given edge.
        '''
        edge = list()
        edge.append(quote_if_necessary(str(e[0])))
        edge.append(self.edge_connect_symbol)
        edge.append(quote_if_necessary(str(e[1])))
        # return if there is nothing in self.edge_attr[e]
        if len(self.edge_attr[e]) == 0:
            return ''.join(edge)
        edge.append('  [')
        for a in self.edge_attr[e]:
            edge.append(a)
            edge.append('=')
            edge.append(quote_if_necessary(str(self.edge_attr[e][a])))
            edge.append(', ')
        edge = edge[:-1]
        edge.append(']')
        return ''.join(edge)

    def to_string(self):
        '''
        API: to_string(self)
        Description:
        This method is based on pydot Graph class with the same name.
        Returns a string representation of the graph in dot language.
        It will return the graph and all its subelements in string form.
        Return:
            String that represents graph in dot language.
        '''
        graph = list()
        processed_edges = {}
        graph.append('%s %s {\n' %(self.graph_type, self.name))
        for a in self.attr:
            if a not in GRAPH_ATTRIBUTES:
                continue
            val = self.attr[a]
            if val is not None:
                graph.append( '%s=%s' % (a, quote_if_necessary(val)) )
            else:
                graph.append(a)
            graph.append( ';\n' )
        # clusters
        for c in self.cluster:
            graph.append('subgraph cluster_%s {\n' %c)
            for a in self.cluster[c]['attrs']:
                if a=='label':
                    graph.append(a+'='+quote_if_necessary(self.cluster[c]['attrs'][a])+';\n')
                    continue
                graph.append(a+'='+self.cluster[c]['attrs'][a]+';\n')
            if len(self.cluster[c]['node_attrs'])!=0:
                graph.append('node [')
            for a in self.cluster[c]['node_attrs']:
                graph.append(a+'='+self.cluster[c]['node_attrs'][a])
                graph.append(',')
            if len(self.cluster[c]['node_attrs'])!=0:
                graph.pop()
                graph.append('];\n')
            # process cluster nodes
            for n in self.cluster[c]['node_list']:
                data = self.get_node(n).to_string()
                graph.append(data + ';\n')
            # process cluster edges
            for n in self.cluster[c]['node_list']:
                for m in self.cluster[c]['node_list']:
                    if self.check_edge(n,m):
                        data = self.edge_to_string((n,m))
                        graph.append(data + ';\n')
                        processed_edges[(n,m)]=None
            graph.append('}\n')
        # process remaining (non-cluster) nodes
        for n in self.neighbors:
            for c in self.cluster:
                if n in self.cluster[c]['node_list']:
                    break
            else:
                data = self.get_node(n).to_string()
                graph.append(data + ';\n')
        # process edges
        for e in self.edge_attr:
            if e in processed_edges:
                continue
            data = self.edge_to_string(e)
            graph.append(data + ';\n')
        graph.append( '}\n' )
        return ''.join(graph)

    def label_components(self, display = None):
        '''
        API: label_components(self, display=None)
        Description:
        This method labels the nodes of an undirected graph with component
        numbers so that each node has the same label as all nodes in the
        same component. It will display the algortihm if display argument is
        provided.
        Input:
            display: display method.
        Pre:
            self.graph_type should be UNDIRECTED_GRAPH.
        Post:
            Nodes will have 'component' attribute that will have component
            number as value.
        '''
        if self.graph_type == DIRECTED_GRAPH:
            raise Exception("label_components only works for ",
                            "undirected graphs")
        self.num_components = 0
        for n in self.get_node_list():
            self.get_node(n).set_attr('component', None)
        for n in self.neighbors:
            self.get_node(n).set_attr('label', '-')
        for n in self.get_node_list():
            if self.get_node(n).get_attr('component') == None:
                self.search(n, display=display,
                            component=self.num_components, algo='DFS')
                self.num_components += 1

    def tarjan(self):
        '''
        API: tarjan(self)
        Description:
        Implements Tarjan's algorithm for determining strongly connected set of
        nodes.
        Pre:
            self.graph_type should be DIRECTED_GRAPH.
        Post:
            Nodes will have 'component' attribute that will have component
            number as value. Changes 'index' attribute of nodes.
        '''
        index = 0
        component = 0
        q = []
        for n in self.get_node_list():
            if self.get_node_attr(n, 'index') is None:
                index, component = self.strong_connect(q, n, index, component)

    def strong_connect(self, q, node, index, component):
        '''
        API: strong_connect (self, q, node, index, component)
        Description:
        Used by tarjan method. This method should not be called directly by
        user.
        Input:
            q: Node list.
            node: Node that is being connected to nodes in q.
            index: Index used by tarjan method.
            component: Current component number.
        Pre:
            Should be called by tarjan and itself (recursive) only.
        Post:
            Nodes will have 'component' attribute that will have component
            number as value. Changes 'index' attribute of nodes.
        Return:
            Returns new index and component numbers.
        '''
        self.set_node_attr(node, 'index', index)
        self.set_node_attr(node, 'lowlink', index)
        index += 1
        q.append(node)
        for m in self.get_neighbors(node):
            if self.get_node_attr(m, 'index') is None:
                index, component = self.strong_connect(q, m, index, component)
                self.set_node_attr(node, 'lowlink',
                                   min([self.get_node_attr(node, 'lowlink'),
                                        self.get_node_attr(m, 'lowlink')]))
            elif m in q:
                self.set_node_attr(node, 'lowlink',
                                   min([self.get_node_attr(node, 'lowlink'),
                                        self.get_node_attr(m, 'index')]))
        if self.get_node_attr(node, 'lowlink') == self.get_node_attr(node, 'index'):
            m = q.pop()
            self.set_node_attr(m, 'component', component)
            while (node!=m):
                m = q.pop()
                self.set_node_attr(m, 'component', component)
            component += 1
            self.num_components = component

        return (index, component)

    def label_strong_component(self):
        '''
        API: label_strong_component(self)
        Description:
        This method labels the nodes of a directed graph with component
        numbers so that each node has the same label as all nodes in the
        same component.
        Pre:
            self.graph_type should be DIRECTED_GRAPH.
        Post:
            Nodes will have 'component' attribute that will have component
            number as value. Changes 'index' attribute of nodes.
        '''
        self.num_components = 0
        self.tarjan()

    def dfs(self, root, disc_count = 0, finish_count = 1, component = None,
            transpose = False, display = None, pred = None):
        '''
        API: dfs(self, root, disc_count = 0, finish_count = 1, component=None,
            transpose=False)
        Description:
        Make a depth-first search starting from node with name root.
        Input:
            root: Starting node name.
            disc_count: Discovery time.
            finish_count: Finishing time.
            component: component number.
            transpose: Goes in the reverse direction along edges if transpose
            is True.
        Post:
            Nodes will have 'component' attribute that will have component
            number as value. Updates 'disc_time' and 'finish_time' attributes
            of nodes which represents discovery time and finishing time.
        Return:
            Returns a tuple that has discovery time and finish time of the
            last node in the following form (disc_time,finish_time).
        '''
        if pred == None:
            pred = {}
        if display == None:
            display = self.attr['display']
        else:
            self.set_display_mode(display)
        neighbors = self.neighbors
        if self.graph_type == DIRECTED_GRAPH and transpose:
            neighbors = self.in_neighbors
        self.get_node(root).set_attr('component', component)
        disc_count += 1
        self.get_node(root).set_attr('disc_time', disc_count)
        self.get_node(root).set_attr('label', str(disc_count)+',-')
        self.get_node(root).set_attr('color', 'blue')
        if root in pred:
            self.set_edge_attr(pred[root], root, 'color', 'green')
        self.display()
        if transpose:
            fTime = []
            for n in neighbors[root]:
                fTime.append((n,self.get_node(n).get_attr('finish_time')))
            neighbor_list = sorted(fTime, key=operator.itemgetter(1))
            neighbor_list = list(t[0] for t in neighbor_list)
            neighbor_list.reverse()
        else:
            neighbor_list = neighbors[root]
        for i in neighbor_list:
            if not transpose:
                if self.get_node(i).get_attr('disc_time') is None:
                    pred[i] = root
                    disc_count, finish_count = self.dfs(i, disc_count,
                                                        finish_count,
                                                        component, transpose,
                                                        pred = pred)
            else:
                if self.get_node(i).get_attr('component') is None:
                    disc_count, finish_count = self.dfs(i, disc_count,
                                                        finish_count,
                                                        component, transpose,
                                                        pred = pred)
        self.get_node(root).set_attr('finish_time', finish_count)
        d_time = self.get_node(root).get_attr('disc_time')
        label = '"' + str(d_time) + ',' + str(finish_count) + '"'
        self.get_node(root).set_attr('label', label)
        self.get_node(root).set_attr('color', 'green')
        self.display()
        finish_count += 1
        return disc_count, finish_count

    def bfs(self, root, display = None, component = None):
        '''
        API: bfs(self, root, display = None, component=None)
        Description:
        Make a breadth-first search starting from node with name root.
        Input:
            root: Starting node name.
            display: display method.
            component: component number.
        Post:
            Nodes will have 'component' attribute that will have component
            number as value.
        '''
        self.search(root, display = display, component = component, q = Queue())

    def search(self, source, destination = None, display = None,
               component = None, q = None,
               algo = 'DFS', reverse = False, **kargs):
        '''
        API: search(self, source, destination = None, display = None,
               component = None, q = Stack(),
               algo = 'DFS', reverse = False, **kargs)
        Description:
        Generic search method. Changes behavior (dfs,bfs,dijkstra,prim)
        according to algo argument.
        if destination is not specified:
           This method determines all nodes reachable from "source" ie. creates
           precedence tree and returns it (dictionary).
        if destionation is given:
           If there exists a path from "source" to "destination" it will return
           list of the nodes is this path. If there is no such path, it will
           return the precedence tree constructed from source (dictionary).
        Optionally, it marks all nodes reachable from "source" with a component
        number. The variable "q" determines the order in which the nodes are
        searched.
        Input:
            source: Search starts from node with this name.
            destination: Destination node name.
            display: Display method.
            algo: Algortihm that specifies search. Available algortihms are
            'DFS', 'BFS', 'Dijkstra' and 'Prim'.
            reverse: Search goes in reverse arc directions if True.
            kargs: Additional keyword arguments.
        Post:
            Nodes will have 'component' attribute that will have component
            number as value (if component argument provided). Color attribute
            of nodes and edges may change.
        Return:
            Returns predecessor tree in dictionary form if destination is
            not specified, returns list of node names in the path from source
            to destionation if destionation is specified and there is a path.
            If there is no path returns predecessor tree in dictionary form.
            See description section.
        '''
        if display == None:
            display = self.attr['display']
        else:
            self.set_display_mode(display)
        if algo == 'DFS':
            if q is None:
                q = Stack()
            self.get_node(source).set_attr('component', component)
        elif algo == 'BFS' or algo == 'UnweightedSPT':
            if q is None:
                q = Queue()
            self.get_node(source).set_attr('component', component)
        elif algo == 'Dijkstra' or algo == 'Prim':
            if q is None:
                q = PriorityQueue()
        else:
            print("Unknown search algorithm...exiting")
            return
        neighbors = self.neighbors
        if self.graph_type == DIRECTED_GRAPH and reverse:
            neighbors = self.in_neighbors
        for i in self.get_node_list():
            self.get_node(i).set_attr('label', '-')
            self.get_node(i).attr.pop('priority', None)
            self.get_node(i).set_attr('distance', None)
            self.get_node(i).set_attr('color', 'black')
            for j in neighbors[i]:
                if reverse:
                    self.set_edge_attr(j, i, 'color', 'black')
                else:
                    self.set_edge_attr(i, j, 'color', 'black')
        self.display()
        pred = {}
        self.process_edge_search(None, source, pred, q, component, algo,
                                 **kargs)
        found = True
        if source != destination:
            found = False
        while not q.isEmpty() and not found:
            current = q.peek()
            if self.get_node(current).get_attr('color') == 'green':
                q.remove(current)
                continue
            self.process_node_search(current, q, **kargs)
            self.get_node(current).set_attr('color', 'blue')
            if current != source:
                if reverse:
                    self.set_edge_attr(current, pred[current], 'color', 'green')
                else:
                    self.set_edge_attr(pred[current], current, 'color', 'green')
            if current == destination:
                found = True
                break
            self.display()
            for n in neighbors[current]:
                if self.get_node(n).get_attr('color') != 'green':
                    if reverse:
                        self.set_edge_attr(n, current, 'color', 'yellow')
                    else:
                        self.set_edge_attr(current, n, 'color', 'yellow')
                    self.display()
                    self.process_edge_search(current, n, pred, q, component,
                                             algo, **kargs)
                    if reverse:
                        self.set_edge_attr(n, current, 'color', 'black')
                    else:
                        self.set_edge_attr(current, n, 'color', 'black')
            q.remove(current)
            self.get_node(current).set_attr('color', 'green')
            self.display()
        if found:
            path = [destination]
            current = destination
            while current != source:
                path.insert(0, pred[current])
                current = pred[current]
            return path
        if destination == None:
            return pred
        else:
            return None

    def process_node_search(self, node, q, **kwargs):
        '''
        API: process_node_search(self, node, q, **kwargs)
        Description:
        Used by search() method. Process nodes along the search. Should not be
        called by user directly.
        Input:
            node: Name of the node being processed.
            q: Queue data structure.
            kwargs: Keyword arguments.
        Post:
            'priority' attribute of the node may get updated.
        '''
        if isinstance(q, PriorityQueue):
            self.get_node(node).set_attr('priority', q.get_priority(node))

    def process_edge_dijkstra(self, current, neighbor, pred, q, component):
        '''
        API: process_edge_dijkstra(self, current, neighbor, pred, q, component)
        Description:
        Used by search() method if the algo argument is 'Dijkstra'. Processes
        edges along Dijkstra's algorithm. User does not need to call this
        method directly.
        Input:
            current: Name of the current node.
            neighbor: Name of the neighbor node.
            pred: Predecessor tree.
            q: Data structure that holds nodes to be processed in a queue.
            component: component number.
        Post:
            'color' attribute of nodes and edges may change.
        '''
        if current is None:
            self.get_node(neighbor).set_attr('color', 'red')
            self.get_node(neighbor).set_attr('label', 0)
            q.push(neighbor, 0)
            self.display()
            self.get_node(neighbor).set_attr('color', 'black')
            return
        new_estimate = (q.get_priority(current) +
                        self.get_edge_attr(current, neighbor, 'cost'))
        if neighbor not in pred or new_estimate < q.get_priority(neighbor):
            pred[neighbor] = current
            self.get_node(neighbor).set_attr('color', 'red')
            self.get_node(neighbor).set_attr('label', new_estimate)
            q.push(neighbor, new_estimate)
            self.display()
            self.get_node(neighbor).set_attr('color', 'black')

    def process_edge_prim(self, current, neighbor, pred, q, component):
        '''
        API: process_edge_prim(self, current, neighbor, pred, q, component)
        Description:
        Used by search() method if the algo argument is 'Prim'. Processes
        edges along Prim's algorithm. User does not need to call this method
        directly.
        Input:
            current: Name of the current node.
            neighbor: Name of the neighbor node.
            pred: Predecessor tree.
            q: Data structure that holds nodes to be processed in a queue.
            component: component number.
        Post:
            'color' attribute of nodes and edges may change.
        '''
        if current is None:
            self.get_node(neighbor).set_attr('color', 'red')
            self.get_node(neighbor).set_attr('label', 0)
            q.push(neighbor, 0)
            self.display()
            self.get_node(neighbor).set_attr('color', 'black')
            return
        new_estimate = self.get_edge_attr(current, neighbor, 'cost')
        if not neighbor in pred or new_estimate < q.get_priority(neighbor):
            pred[neighbor] = current
            self.get_node(neighbor).set_attr('color', 'red')
            self.get_node(neighbor).set_attr('label', new_estimate)
            q.push(neighbor, new_estimate)
            self.display()
            self.get_node(neighbor).set_attr('color', 'black')

    def process_edge_search(self, current, neighbor, pred, q, component, algo,
                            **kargs):
        '''
        API: process_edge_search(self, current, neighbor, pred, q, component,
                                 algo, **kargs)
        Description:
        Used by search() method. Processes edges according to the underlying
        algortihm. User does not need to call this method directly.
        Input:
            current: Name of the current node.
            neighbor: Name of the neighbor node.
            pred: Predecessor tree.
            q: Data structure that holds nodes to be processed in a queue.
            component: component number.
            algo: Search algorithm. See search() documentation.
            kwargs: Keyword arguments.
        Post:
            'color', 'distance', 'component' attribute of nodes and edges may
            change.
        '''
        if algo == 'Dijkstra':
            return self.process_edge_dijkstra(current, neighbor, pred, q,
                                              component)
        if algo == 'Prim':
            return self.process_edge_prim(current, neighbor, pred, q,
                                          component)
        neighbor_node = self.get_node(neighbor)
        if current == None:
            neighbor_node.set_attr('distance', 0)
            if isinstance(q, PriorityQueue):
                q.push(neighbor, 0)
            else:
                q.push(neighbor)
            if component != None:
                neighbor_node.set_attr('component', component)
                neighbor_node.set_attr('label', component)
            else:
                neighbor_node.set_attr('label', 0)
            return
        if isinstance(q, PriorityQueue):
            current_priority = q.get_priority(neighbor)
            if algo == 'UnweightedSPT' or algo == 'BFS':
                priority = self.get_node(current).get_attr('distance') + 1
            if algo == 'DFS':
                priority = -self.get_node(current).get_attr('distance') - 1
            if current_priority is not None and priority >= current_priority:
                return
            q.push(neighbor, priority)
            if algo == 'UnweightedSPT' or algo == 'BFS':
                neighbor_node.set_attr('distance', priority)
            if algo == 'DFS':
                neighbor_node.set_attr('depth', -priority)
        else:
            distance = self.get_node(current).get_attr('distance') + 1
            if ((algo == 'UnweightedSPT' or algo == 'BFS') and
                neighbor_node.get_attr('distance') is not None):
                return
            neighbor_node.set_attr('distance', distance)
            neighbor_node.set_attr('label', str(distance))
            q.push(neighbor)
        pred[neighbor] = current
        neighbor_node.set_attr('color', 'red')
        if component != None:
            neighbor_node.set_attr('component', component)
            neighbor_node.set_attr('label', component)
        self.display()

    def minimum_spanning_tree_prim(self, source, display = None,
                                   q = PriorityQueue()):
        '''
        API: minimum_spanning_tree_prim(self, source, display = None,
                                        q = PriorityQueue())
        Description:
        Determines a minimum spanning tree of all nodes reachable
        from source using Prim's Algorithm.
        Input:
            source: Name of source node.
            display: Display method.
            q: Data structure that holds nodes to be processed in a queue.
        Post:
            'color', 'distance', 'component' attribute of nodes and edges may
            change.
        Return:
            Returns predecessor tree in dictionary format.
        '''
        if display == None:
            display = self.attr['display']
        else:
            self.set_display_mode(display)
        if isinstance(q, PriorityQueue):
            addToQ = q.push
            removeFromQ = q.pop
            peek = q.peek
            isEmpty = q.isEmpty
        neighbors = self.get_neighbors
        pred = {}
        addToQ(source)
        done = False
        while not isEmpty() and not done:
            current = removeFromQ()
            self.set_node_attr(current, 'color', 'blue')
            if current != source:
                self.set_edge_attr(pred[current], current, 'color', 'green')
            self.display()
            for n in neighbors(current):
                if self.get_node_attr(n, 'color') != 'green':
                    self.set_edge_attr(current, n, 'color', 'yellow')
                    self.display()
                    new_estimate = self.get_edge_attr(current, n, 'cost')
                    if not n in pred or new_estimate < peek(n)[0]:
                        pred[n] = current
                        self.set_node_attr(n, 'color', 'red')
                        self.set_node_attr(n, 'label', new_estimate)
                        addToQ(n, new_estimate)
                        self.display()
                        self.set_node_attr(n, 'color', 'black')
                    self.set_edge_attr(current, n, 'color', 'black')
            self.set_node_attr(current, 'color', 'green')
            self.display()
        return pred

    def minimum_spanning_tree_kruskal(self, display = None, components = None):
        '''
        API: minimum_spanning_tree_kruskal(self, display = None,
                                           components = None)
        Description:
        Determines a minimum spanning tree using Kruskal's Algorithm.
        Input:
            display: Display method.
            component: component number.
        Post:
            'color' attribute of nodes and edges may change.
        Return:
            Returns list of edges where edges are tuples in (source,sink)
            format.
        '''
        if display == None:
            display = self.attr['display']
        else:
            self.set_display_mode(display)
        if components is None:
            components = DisjointSet(display = display, layout = 'dot',
                                     optimize = False)
        sorted_edge_list = sorted(self.get_edge_list(), key=self.get_edge_cost)
        edges = []
        for n in self.get_node_list():
            components.add([n])
        components.display()
        for e in sorted_edge_list:
            if len(edges) == len(self.get_node_list()) - 1:
                break
            self.set_edge_attr(e[0], e[1], 'color', 'yellow')
            self.display()
            if components.union(e[0], e[1]):
                self.set_edge_attr(e[0], e[1], 'color', 'green')
                self.display()
                edges.append(e)
            else:
                self.set_edge_attr(e[0], e[1], 'color', 'black')
                self.display()
            components.display()
        return edges

    def max_flow_preflowpush(self, source, sink, algo = 'FIFO', display = None):
        '''
        API: max_flow_preflowpush(self, source, sink, algo = 'FIFO',
                                  display = None)
        Description:
        Finds maximum flow from source to sink by a depth-first search based
        augmenting path algorithm.
        Pre:
             Assumes a directed graph in which each arc has a 'capacity'
             attribute and for which there does does not exist both arcs (i,j)
             and (j,i) for any pair of nodes i and j.
        Input:
            source: Source node name.
            sink: Sink node name.
            algo: Algorithm choice, 'FIFO', 'SAP' or 'HighestLabel'.
            display: display method.
        Post:
            The 'flow' attribute of each arc gives a maximum flow.
        '''
        if display == None:
            display = self.attr['display']
        else:
            self.set_display_mode(display)
        nl = self.get_node_list()
        # set excess of all nodes to 0
        for n in nl:
            self.set_node_attr(n, 'excess', 0)
        # set flow of all edges to 0
        for e in self.edge_attr:
            self.edge_attr[e]['flow'] = 0
            if 'capacity' in self.edge_attr[e]:
                capacity = self.edge_attr[e]['capacity']
                self.edge_attr[e]['label'] = str(capacity)+'/0'
            else:
                self.edge_attr[e]['capacity'] = INF
                self.edge_attr[e]['label'] = 'INF/0'
        self.display()
        self.set_display_mode('off')
        self.search(sink, algo = 'UnweightedSPT', reverse = True)
        self.set_display_mode(display)
        disconnect = False
        for n in nl:
            if self.get_node_attr(n, 'distance') is None:
                disconnect = True
                self.set_node_attr(n, 'distance',
                                   2*len(nl) + 1)
        if disconnect:
            print('Warning: graph contains nodes not connected to the sink...')
        if algo == 'FIFO':
            q = Queue()
        elif algo == 'SAP':
            q = Stack()
        elif algo == 'HighestLabel':
            q = PriorityQueue()
        for n in self.get_neighbors(source):
            capacity = self.get_edge_attr(source, n, 'capacity')
            self.set_edge_attr(source, n, 'flow', capacity)
            self.set_node_attr(n, 'excess', capacity)
            excess = self.get_node_attr(source, 'excess')
            self.set_node_attr(source, 'excess', excess - capacity)
            if algo == 'FIFO' or algo == 'SAP':
                q.push(n)
            elif algo == 'HighestLabel':
                q.push(n, -1)
        self.set_node_attr(source, 'distance', len(nl))
        self.show_flow()
        while not q.isEmpty():
            relabel = True
            current = q.peek()
            neighbors = (self.get_neighbors(current) +
                         self.get_in_neighbors(current))
            for n in neighbors:
                pushed = self.process_edge_flow(source, sink, current, n, algo,
                                                q)
                if pushed:
                    self.show_flow()
                    if algo == 'FIFO':
                        '''With FIFO, we need to add the neighbors to the queue
                        before the current is added back in or the nodes will
                        be out of order
                        '''
                        if q.peek(n) is None and n != source and n != sink:
                            q.push(n)
                        '''Keep pushing while there is excess'''
                        if self.get_node_attr(current, 'excess') > 0:
                            continue
                    '''If we were able to push, then there we should not
                    relabel
                    '''
                    relabel = False
                    break
            q.remove(current)
            if current != sink:
                if relabel:
                    self.relabel(current)
                    self.show_flow()
                if self.get_node_attr(current, 'excess') > 0:
                    if algo == 'FIFO' or algo == 'SAP':
                        q.push(current)
                    elif algo == 'HighestLabel':
                        q.push(current, -self.get_node_attr(current,
                                                            'distance'))
            if pushed and q.peek(n) is None and n != source:
                if algo == 'SAP':
                    q.push(n)
                elif algo == 'HighestLabel':
                    q.push(n, -self.get_node_attr(n, 'distance'))

    def process_edge_flow(self, source, sink, i, j, algo, q):
        '''
        API: process_edge_flow(self, source, sink, i, j, algo, q)
        Description:
        Used by by max_flow_preflowpush() method. Processes edges along
        prefolow push.
        Input:
            source: Source node name of flow graph.
            sink: Sink node name of flow graph.
            i: Source node in the processed edge (tail of arc).
            j: Sink node in the processed edge (head of arc).
        Post:
            The 'flow' and 'excess' attributes of nodes may get updated.
        Return:
            Returns False if residual capacity is 0, True otherwise.
        '''
        if (self.get_node_attr(i, 'distance') !=
            self.get_node_attr(j, 'distance') + 1):
            return False
        if (i, j) in self.edge_attr:
            edge = (i, j)
            capacity = self.get_edge_attr(i, j, 'capacity')
            mult = 1
        else:
            edge = (j, i)
            capacity = 0
            mult = -1
        flow = mult*self.edge_attr[edge]['flow']
        residual_capacity = capacity - flow
        if residual_capacity == 0:
            return False
        excess_i = self.get_node_attr(i, 'excess')
        excess_j = self.get_node_attr(j, 'excess')
        push_amount = min(excess_i, residual_capacity)
        self.edge_attr[edge]['flow'] = mult*(flow + push_amount)
        self.set_node_attr(i, 'excess', excess_i - push_amount)
        self.set_node_attr(j, 'excess', excess_j + push_amount)
        return True

    def relabel(self, i):
        '''
        API: relabel(self, i)
        Description:
        Used by max_flow_preflowpush() method for relabelling node i.
        Input:
            i: Node that is being relabelled.
        Post:
            'distance' attribute of node i is updated.
        '''
        min_distance = 2*len(self.get_node_list()) + 1
        for j in self.get_neighbors(i):
            if (self.get_node_attr(j, 'distance') < min_distance and
                (self.get_edge_attr(i, j, 'flow') <
                 self.get_edge_attr(i, j, 'capacity'))):
                min_distance = self.get_node_attr(j, 'distance')
        for j in self.get_in_neighbors(i):
            if (self.get_node_attr(j, 'distance') < min_distance and
                self.get_edge_attr(j, i, 'flow') > 0):
                min_distance = self.get_node_attr(j, 'distance')
        self.set_node_attr(i, 'distance', min_distance + 1)

    def show_flow(self):
        '''
        API: relabel(self, i)
        Description:
        Used by max_flow_preflowpush() method for display purposed.
        Post:
            'color' and 'label' attribute of edges/nodes are updated.
        '''
        for n in self.get_node_list():
            excess = self.get_node_attr(n, 'excess')
            distance = self.get_node_attr(n, 'distance')
            self.set_node_attr(n, 'label', str(excess)+'/'+str(distance))
            for neighbor in self.get_neighbors(n):
                capacity = self.get_edge_attr(n, neighbor, 'capacity')
                flow = self.get_edge_attr(n, neighbor, 'flow')
                if capacity == INF:
                    self.set_edge_attr(n, neighbor, 'label',
                                       'INF'+'/'+str(flow))
                else:
                    self.set_edge_attr(n, neighbor, 'label',
                                       str(capacity)+'/'+str(flow))
                if capacity == flow:
                    self.set_edge_attr(n, neighbor, 'color', 'red')
                elif flow > 0:
                    self.set_edge_attr(n, neighbor, 'color', 'green')
                else:
                    self.set_edge_attr(n, neighbor, 'color', 'black')
        self.display()

    def create_residual_graph(self):
        '''
        API: create_residual_graph(self)
        Description:
        Creates and returns residual graph, which is a Graph instance
        itself.
        Pre:
            (1) Arcs should have 'flow', 'capacity' and 'cost' attribute
            (2) Graph should be a directed graph
        Return:
            Returns residual graph, which is a Graph instance.
        '''
        if self.graph_type is UNDIRECTED_GRAPH:
            raise Exception('residual graph is defined for directed graphs.')
        residual_g = Graph(type = DIRECTED_GRAPH)
        for e in self.get_edge_list():
            capacity_e = self.get_edge_attr(e[0], e[1], 'capacity')
            flow_e = self.get_edge_attr(e[0], e[1], 'flow')
            cost_e = self.get_edge_attr(e[0], e[1], 'cost')
            if flow_e > 0:
                residual_g.add_edge(e[1], e[0], cost=-1*cost_e,
                                    capacity=flow_e)
            if capacity_e - flow_e > 0:
                residual_g.add_edge(e[0], e[1], cost=cost_e,
                                    capacity=capacity_e-flow_e)
        return residual_g

    def cycle_canceling(self, display):
        '''
        API:
            cycle_canceling(self, display)
        Description:
            Solves minimum cost feasible flow problem using cycle canceling
            algorithm. Returns True when an optimal solution is found, returns
            False otherwise. 'flow' attribute values of arcs should be
            considered as junk when returned False.
        Input:
            display: Display method.
        Pre:
            (1) Arcs should have 'capacity' and 'cost' attribute.
            (2) Nodes should have 'demand' attribute, this value should be
            positive if the node is a supply node, negative if it is demand
            node and 0 if it is transhipment node.
            (3) graph should not have node 's' and 't'.
        Post:
            Changes 'flow' attributes of arcs.
        Return:
            Returns True when an optimal solution is found, returns False
            otherwise.
        '''
        # find a feasible solution to flow problem
        if not self.find_feasible_flow():
            return False
        # create residual graph
        residual_g = self.create_residual_graph()
        # identify a negative cycle in residual graph
        ncycle = residual_g.get_negative_cycle()
        # loop while residual graph has a negative cycle
        while ncycle is not None:
            # find capacity of cycle
            cap = residual_g.find_cycle_capacity(ncycle)
            # augment capacity amount along the cycle
            self.augment_cycle(cap, ncycle)
            # create residual graph
            residual_g = self.create_residual_graph()
            # identify next negative cycle
            ncycle = residual_g.get_negative_cycle()
        return True

    def find_feasible_flow(self):
        '''
        API:
            find_feasible_flow(self)
        Description:
            Solves feasible flow problem, stores solution in 'flow' attribute
            or arcs. This method is used to get an initial feasible flow for
            simplex and cycle canceling algorithms. Uses max_flow() method.
            Other max flow methods can also be used. Returns True if a feasible
            flow is found, returns False, if the problem is infeasible. When
            the problem is infeasible 'flow' attributes of arcs should be
            considered as junk.
        Pre:
            (1) 'capacity' attribute of arcs
            (2) 'demand' attribute of nodes
        Post:
            Keeps solution in 'flow' attribute of arcs.
        Return:
            Returns True if a feasible flow is found, returns False, if the
            problem is infeasible
        '''
        # establish a feasible flow in the network, to do this add nodes s and
        # t and solve a max flow problem.
        nl = self.get_node_list()
        for i in nl:
            b_i = self.get_node(i).get_attr('demand')
            if b_i > 0:
                # i is a supply node, add (s,i) arc
                self.add_edge('s', i, capacity=b_i)
            elif b_i < 0:
                # i is a demand node, add (i,t) arc
                self.add_edge(i, 't', capacity=-1*b_i)
        # solve max flow on this modified graph
        self.max_flow('s', 't', 'off')
        # check if all demand is satisfied, i.e. the min cost problem is
        # feasible or not
        for i in self.neighbors['s']:
            flow = self.get_edge_attr('s', i, 'flow')
            capacity = self.get_edge_attr('s', i, 'capacity')
            if flow != capacity:
                self.del_node('s')
                self.del_node('t')
                return False
        # remove node 's' and node 't'
        self.del_node('s')
        self.del_node('t')
        return True

    def get_layout(self):
        '''
        API:
            get_layout(self)
        Description:
        Returns layout attribute of the graph.
        Return:
            Returns layout attribute of the graph.
        '''
        return self.attr['layout']

    def set_layout(self, value):
        '''
        API:
            set_layout(self, value)
        Description:
        Sets layout attribute of the graph to value.
        Input:
            value: New value of the layout.
        '''
        self.attr['layout']=value
        if value == 'dot2tex':
            self.attr['d2tgraphstyle'] = 'every text node part/.style={align=center}'

    def write(self, file_obj, layout = None, format='png'):
        '''
        API:
            write(self, basename = 'graph', layout = None, format='png')
        Description:
        Writes graph to dist using layout and format.
        Input:
            basename: name of the file that will be written.
            layout: Dot layout for generating graph image.
            format: Image format, all format supported by Dot are wellcome.
        Post:
            File will be written to disk.
        '''
        if layout == None:
            layout = self.get_layout()
        if format == 'dot':
            file_obj.write(bytearray(self.to_string(), 'utf8'))
        else:
            out = self.create(layout, format)
            if (out != None):
                file_obj.write(out)

    def create(self, layout, format, **args):
        '''
        API:
            create(self, layout, format, **args)
        Description:
            Returns postscript representation of graph.
        Input:
            layout: Dot layout for generating graph image.
            format: Image format, all format supported by Dot are wellcome.
        Return:
            Returns postscript representation of graph.
        '''
        tmp_fd, tmp_name = tempfile.mkstemp()
        tmp_file = os.fdopen(tmp_fd, 'w')
        tmp_file.write(self.to_string())
        tmp_file.close()
        try:
            p = subprocess.run([layout, '-T'+format, tmp_name],
                               capture_output = True)
        except OSError:
            print('''Graphviz executable not found.
Graphviz must be installed and in your search path.
Please visit http://www.graphviz.org/ for information on installation.
After installation, ensure that the PATH variable is properly set.''')
            return None

        
        p.check_returncode()

        os.remove(tmp_name)
            
        if p.stderr:
            print(p.stderr)
        return p.stdout

    def display(self, highlight = None, basename = 'graph', format = 'png',
                pause = False, wait_for_click = True):
        '''
        API:
            display(self, highlight = None, basename = 'graph', format = 'png',
                pause = True)
        Description:
            Displays graph according to the arguments provided.
            Current display modes: 'off', 'file', 'PIL', 'matplotlib', 'xdot',
            'svg'
            Current layout modes: Layouts provided by graphviz ('dot', 'fdp',
            'circo', etc.) and 'dot2tex'.
            Current formats: Formats provided by graphviz ('ps', 'pdf', 'png',
            etc.)
        Input:
            highlight: List of nodes to be highlighted.
            basename: File name. It will be used if display mode is 'file'.
            format: Image format, all format supported by Dot are wellcome.
            pause: If display is 'matplotlib', window will remain open until closed.
            wait_for_click: If display is 'matplotlib', setting to True will 
            wait for a button click before proceeding. This is useful when 
            animating an algorithm. 
        Post:
            A display window will pop up or a file will be written depending
            on display mode.
        '''
        if self.attr['display'] == 'off':
            return
        if highlight != None:
            for n in highlight:
                if not isinstance(n, Node):
                    n = self.get_node(n)
                n.set_attr('color', 'red')
        if self.get_layout() == 'dot2tex':
            if self.attr['display'] != 'file':
                self.attr['display'] = 'file'
                print("Warning: Dot2tex layout can only be used with display mode 'file'")
                print("         Automatically changing setting")
        if self.attr['display'] == 'file':
            if self.get_layout() == 'dot2tex':
                try:
                    if DOT2TEX_INSTALLED:
                        if format != 'pdf' or format != 'ps':
                            print("Dot2tex only supports pdf and ps formats, falling back to pdf")
                            format = 'pdf'
                        self.set_layout('dot')
                        tex = dot2tex.dot2tex(self.to_string(), autosize=True, texmode = 'math', template = DOT2TEX_TEMPLATE)
                    else:
                        print("Error: Dot2tex not installed.")
                except:
                    try:
                        self.set_layout('dot')
                        with open(basename+'.dot', "w+b") as f:
                            self.write(f, self.get_layout(), 'dot')
                            p = subprocess.call(['dot2tex', '-t math',
                                                 basename + '.dot'])
                    except:
                        print("There was an error running dot2tex.")
                with open(basename+'.tex', 'w') as f:
                    f.write(tex)
                try: 
                    subprocess.call(['latex', basename])
                    if format == 'ps':
                        subprocess.call(['dvips', basename])
                    elif format == 'pdf':
                        subprocess.call(['pdflatex', basename])
                    self.set_layout('dot2tex')
                except:
                    print("There was an error runing latex. Is it installed?")
            else:
                with open(basename+'.'+format, "w+b") as f:
                    self.write(f, self.get_layout(), format)
            return
        elif self.attr['display'] == 'PIL':
            if PIL_INSTALLED:
                tmp_fd, tmp_name = tempfile.mkstemp()
                tmp_file = os.fdopen(tmp_fd, 'w+b')
                self.write(tmp_file, self.get_layout(), format)
                tmp_file.close()
                im = PIL_Image.open(tmp_name)
                im.show()
                os.remove(tmp_name)
            else:
                print('Error: PIL not installed. Display disabled.')
                self.attr['display'] = 'off'
        elif self.attr['display'] == 'matplotlib':
            if MATPLOTLIB_INSTALLED and PIL_INSTALLED:
                tmp_fd, tmp_name = tempfile.mkstemp()
                tmp_file = os.fdopen(tmp_fd, 'w+b')
                self.write(tmp_file, self.get_layout(), format)
                tmp_file.close()
                im = PIL_Image.open(tmp_name)
                fig = plt.figure(1)
                fig.canvas.mpl_connect('close_event', handle_close)
                plt.clf()
                plt.axis('off')
                plt.imshow(im, interpolation='bilinear' #resample=True
                           #extent = (0, 100, 0, 100)
                )
                if wait_for_click == True:
                    plt.draw()
                    try:
                        if plt.waitforbuttonpress(timeout = 10000):
                            plt.close()
                            exit()
                    except:
                        exit()
                else:
                    plt.show(block=pause)
                im.close()
                os.remove(tmp_name)
            else:
                print('Warning: Either matplotlib or Pillow is not installed. Display disabled.')
                self.attr['display'] = 'off'
        elif self.attr['display'] == 'xdot':
            if XDOT_INSTALLED:
                window = xdot.DotWindow()
                window.set_dotcode(self.to_string())
                window.connect('destroy', gtk.main_quit)
                gtk.main()
            else:
                print('Error: xdot not installed. Display disabled.')
                self.attr['display'] = 'off'
        else:
            print("Unknown display mode: ", end=' ')
            print(self.attr['display'])
        if highlight != None:
            for n in highlight:
                if not isinstance(n, Node):
                    n = self.get_node(n)
                n.set_attr('color', 'black')

    def set_display_mode(self, value):
        '''
        API:
            set_display_mode(self, value)
        Description:
            Sets display mode to value.
        Input:
            value: New display mode.
        Post:
            Display mode attribute of graph is updated.
        '''
        self.attr['display'] = value

    def max_flow(self, source, sink, display = None, algo = 'DFS'):
        '''
        API: max_flow(self, source, sink, display=None)
        Description:
        Finds maximum flow from source to sink by a depth-first search based
        augmenting path algorithm.
        Pre:
            Assumes a directed graph in which each arc has a 'capacity'
            attribute and for which there does does not exist both arcs (i,j)
            and (j, i) for any pair of nodes i and j.
        Input:
            source: Source node name.
            sink: Sink node name.
            display: Display mode.
        Post:
            The 'flow" attribute of each arc gives a maximum flow.
        '''
        if display is not None:
            old_display =  self.attr['display']
            self.attr['display'] = display
        nl = self.get_node_list()
        # set flow of all edges to 0
        for e in self.edge_attr:
            self.edge_attr[e]['flow'] = 0
            if 'capacity' in self.edge_attr[e]:
                capacity = self.edge_attr[e]['capacity']
                self.edge_attr[e]['label'] = str(capacity)+'/0'
            else:
                self.edge_attr[e]['capacity'] = INF
                self.edge_attr[e]['label'] = 'INF/0'
        while True:
            # find an augmenting path from source to sink using DFS
            if algo == 'DFS':
                q = Stack()
            elif algo == 'BFS':
                q = Queue()
            q.push(source)
            pred = {source:None}
            explored = [source]
            for n in nl:
                self.get_node(n).set_attr('color', 'black')
            for e in self.edge_attr:
                if self.edge_attr[e]['flow'] == 0:
                    self.edge_attr[e]['color'] = 'black'
                elif self.edge_attr[e]['flow']==self.edge_attr[e]['capacity']:
                    self.edge_attr[e]['color'] = 'red'
                else:
                    self.edge_attr[e]['color'] = 'green'
            self.display()
            while not q.isEmpty():
                current = q.peek()
                q.remove(current)
                if current == sink:
                    break
                out_neighbor = self.neighbors[current]
                in_neighbor = self.in_neighbors[current]
                neighbor = out_neighbor+in_neighbor
                for m in neighbor:
                    if m in explored:
                        continue
                    self.get_node(m).set_attr('color', 'yellow')
                    if m in out_neighbor:
                        self.set_edge_attr(current, m, 'color', 'yellow')
                        available_capacity = (
                            self.get_edge_attr(current, m, 'capacity')-
                            self.get_edge_attr(current, m, 'flow'))
                    else:
                        self.set_edge_attr(m, current, 'color', 'yellow')
                        available_capacity=self.get_edge_attr(m, current, 'flow')
                    self.display()
                    if available_capacity > 0:
                        self.get_node(m).set_attr('color', 'blue')
                        if m in out_neighbor:
                            self.set_edge_attr(current, m, 'color', 'blue')
                        else:
                            self.set_edge_attr(m, current, 'color', 'blue')
                        explored.append(m)
                        pred[m] = current
                        q.push(m)
                    else:
                        self.get_node(m).set_attr('color', 'black')
                        if m in out_neighbor:
                            if (self.get_edge_attr(current, m, 'flow') ==
                                self.get_edge_attr(current, m, 'capacity')):
                                self.set_edge_attr(current, m, 'color', 'red')
                            elif self.get_edge_attr(current, m, 'flow') == 0:
                                self.set_edge_attr(current, m, 'color', 'black')
                            #else:
                            #    self.set_edge_attr(current, m, 'color', 'green')
                        else:
                            if (self.get_edge_attr(m, current, 'flow') ==
                                self.get_edge_attr(m, current, 'capacity')):
                                self.set_edge_attr(m, current, 'color', 'red')
                            elif self.get_edge_attr(m, current, 'flow') == 0:
                                self.set_edge_attr(m, current, 'color', 'black')
                            #else:
                            #    self.set_edge_attr(m, current, 'color', 'green')
                    self.display()
            # if no path with positive capacity from source sink exists, stop
            if sink not in pred:
                break
            # find capacity of the path
            current = sink
            min_capacity = 'infinite'
            while True:
                m = pred[current]
                if (m,current) in self.edge_attr:
                    arc_capacity = self.edge_attr[(m, current)]['capacity']
                    flow = self.edge_attr[(m, current)]['flow']
                    potential = arc_capacity-flow
                    if min_capacity == 'infinite':
                        min_capacity = potential
                    elif min_capacity > potential:
                        min_capacity = potential
                else:
                    potential = self.edge_attr[(current, m)]['flow']
                    if min_capacity == 'infinite':
                        min_capacity = potential
                    elif min_capacity > potential:
                        min_capacity = potential
                if m == source:
                    break
                current = m
            # update flows on the path
            current = sink
            while True:
                m = pred[current]
                if (m, current) in self.edge_attr:
                    flow = self.edge_attr[(m, current)]['flow']
                    capacity = self.edge_attr[(m, current)]['capacity']
                    new_flow = flow+min_capacity
                    self.edge_attr[(m, current)]['flow'] = new_flow
                    if capacity == INF:
                        self.edge_attr[(m, current)]['label'] = \
                            'INF' + '/'+str(new_flow)
                    else:
                        self.edge_attr[(m, current)]['label'] = \
                            str(capacity)+'/'+str(new_flow)
                    if new_flow==capacity:
                        self.edge_attr[(m, current)]['color'] = 'red'
                    else:
                        self.edge_attr[(m, current)]['color'] = 'green'
                    self.display()
                else:
                    flow = self.edge_attr[(current, m)]['flow']
                    capacity = self.edge_attr[(current, m)]['capacity']
                    new_flow = flow-min_capacity
                    self.edge_attr[(current, m)]['flow'] = new_flow
                    if capacity == INF:
                        self.edge_attr[(current, m)]['label'] = \
                            'INF' + '/'+str(new_flow)
                    else:
                        self.edge_attr[(current, m)]['label'] = \
                            str(capacity)+'/'+str(new_flow)
                    if new_flow==0:
                        self.edge_attr[(current, m)]['color'] = 'red'
                    else:
                        self.edge_attr[(current, m)]['color'] = 'green'
                    self.display()
                if m == source:
                    break
                current = m
        if display is not None:
            self.attr['display'] = old_display

    def get_negative_cycle(self):
        '''
        API:
            get_negative_cycle(self)
        Description:
            Finds and returns negative cost cycle using 'cost' attribute of
            arcs. Return value is a list of nodes representing cycle it is in
            the following form; n_1-n_2-...-n_k, when the cycle has k nodes.
        Pre:
            Arcs should have 'cost' attribute.
        Return:
            Returns a list of nodes in the cycle if a negative cycle exists,
            returns None otherwise.
        '''
        nl = self.get_node_list()
        i = nl[0]
        (valid, distance, nextn) = self.floyd_warshall()
        if not valid:
            cycle = self.floyd_warshall_get_cycle(distance, nextn)
            return cycle
        else:
            return None

    def floyd_warshall(self):
        '''
        API:
            floyd_warshall(self)
        Description:
            Finds all pair shortest paths and stores it in a list of lists.
            This is possible if the graph does not have negative cycles. It will
            return a tuple with 3 elements. The first element indicates whether
            the graph has a negative cycle. It is true if the graph does not
            have a negative cycle, ie. distances found are valid shortest
            distances. The second element is a dictionary of shortest distances
            between nodes. Keys are tuple of node pairs ie. (i,j). The third
            element is a dictionary that helps to retrieve the shortest path
            between nodes. Then return value can be represented as (validity,
            distance, nextn) where nextn is the dictionary to retrieve paths.
            distance and nextn can be used as inputs to other methods to get
            shortest path between nodes.
        Pre:
            Arcs should have 'cost' attribute.
        Return:
            Returns (validity, distance, nextn). The distances are valid if
            validity is True.
        '''
        nl = self.get_node_list()
        el = self.get_edge_list()
        # initialize distance
        distance = {}
        for i in nl:
            for j in nl:
                distance[(i,j)] = 'infinity'
        for i in nl:
            distance[(i,i)] = 0
        for e in el:
            distance[(e[0],e[1])] = self.get_edge_cost(e)
        # == end of distance initialization
        # initialize next
        nextn = {}
        for i in nl:
            for j in nl:
                if i==j or distance[(i,j)]=='infinity':
                    nextn[(i,j)] = None
                else:
                    nextn[(i,j)] = i
        # == end of next initialization
        # compute shortest distance
        for k in nl:
            for i in nl:
                for j in nl:
                    if distance[(i,k)]=='infinity' or distance[(k,j)]=='infinity':
                        continue
                    elif distance[(i,j)]=='infinity':
                        distance[(i,j)] = distance[(i,k)] + distance[(k,j)]
                        nextn[(i,j)] = nextn[(k,j)]
                    elif distance[(i,j)] > distance[(i,k)] + distance[(k,j)]:
                        distance[(i,j)] = distance[(i,k)] + distance[(k,j)]
                        nextn[(i,j)] = nextn[(k,j)]
        # == end of compute shortest distance
        # check if graph has negative cycles
        for i in nl:
            if distance[(i,i)] < 0:
                # shortest distances are not valid
                # graph has negative cycle
                return (False, distance, nextn)
        return (True, distance, nextn)

    def floyd_warshall_get_path(self, distance, nextn, i, j):
        '''
        API:
            floyd_warshall_get_path(self, distance, nextn, i, j):
        Description:
            Finds shortest path between i and j using distance and nextn
            dictionaries.
        Pre:
            (1) distance and nextn are outputs of floyd_warshall method.
            (2) The graph does not have a negative cycle, , ie.
            distance[(i,i)] >=0 for all node i.
        Return:
            Returns the list of nodes on the path from i to j, ie. [i,...,j]
        '''
        if distance[(i,j)]=='infinity':
            return None
        k = nextn[(i,j)]
        path = self.floyd_warshall_get_path
        if i==k:
            return [i, j]
        else:
            return path(distance, nextn, i,k) + [k] + path(distance, nextn, k,j)

    def floyd_warshall_get_cycle(self, distance, nextn, element = None):
        '''
        API:
            floyd_warshall_get_cycle(self, distance, nextn, element = None)
        Description:
            Finds a negative cycle in the graph.
        Pre:
            (1) distance and nextn are outputs of floyd_warshall method.
            (2) The graph should have a negative cycle, , ie.
            distance[(i,i)] < 0 for some node i.
        Return:
            Returns the list of nodes on the cycle. Ex: [i,j,k,...,r], where
            (i,j), (j,k) and (r,i) are some edges in the cycle.
        '''
        nl = self.get_node_list()
        if element is None:
            for i in nl:
                if distance[(i,i)] < 0:
                    # graph has a cycle on the path from i to i.
                    element = i
                    break
            else:
                raise Exception('Graph does not have a negative cycle!')
        elif distance[(element,element)] >= 0:
            raise Exception('Graph does not have a negative cycle that contains node '+str(element)+'!')
        # find the cycle on the path from i to i.
        cycle = [element]
        k = nextn[(element,element)]
        while k not in cycle:
            cycle.insert(1,k)
            k = nextn[(element,k)]
        if k==element:
            return cycle
        else:
            return self.floyd_warshall_get_cycle(distance, nextn, k)

    def find_cycle_capacity(self, cycle):
        '''
        API:
            find_cycle_capacity(self, cycle):
        Description:
            Finds capacity of the cycle input.
        Pre:
            (1) Arcs should have 'capacity' attribute.
        Input:
            cycle: a list representing a cycle
        Return:
            Returns an integer number representing capacity of cycle.
        '''
        index = 0
        k = len(cycle)
        capacity = self.get_edge_attr(cycle[k-1], cycle[0], 'capacity')
        while index<(k-1):
            i = cycle[index]
            j = cycle[index+1]
            capacity_ij = self.get_edge_attr(i, j, 'capacity')
            if capacity > capacity_ij:
                capacity = capacity_ij
            index += 1
        return capacity

    def fifo_label_correcting(self, source):
        '''
        API:
            fifo_label_correcting(self, source)
        Description:
            finds shortest path from source to every other node. Returns
            predecessor dictionary. If graph has a negative cycle, detects it
            and returns to it.
        Pre:
            (1) 'cost' attribute of arcs. It will be used to compute shortest
            path.
        Input:
            source: source node
        Post:
            Modifies 'distance' attribute of nodes.
        Return:
            If there is no negative cycle returns to (True, pred), otherwise
            returns to (False, cycle) where pred is the predecessor dictionary
            and cycle is a list of nodes that represents cycle. It is in
            [n_1, n_2, ..., n_k] form where the cycle has k nodes.
        '''
        pred = {}
        self.get_node(source).set_attr('distance', 0)
        pred[source] = None
        for n in self.neighbors:
            if n!=source:
                self.get_node(n).set_attr('distance', 'inf')
        q = [source]
        while q:
            i = q[0]
            q = q[1:]
            for j in self.neighbors[i]:
                distance_j = self.get_node(j).get_attr('distance')
                distance_i = self.get_node(i).get_attr('distance')
                c_ij = self.get_edge_attr(i, j, 'cost')
                if distance_j > distance_i + c_ij:
                    self.get_node(j).set_attr('distance', distance_i+c_ij)
                    if j in pred:
                        pred[j] = i
                        cycle = self.label_correcting_check_cycle(j, pred)
                        if cycle is not None:
                            return (False, cycle)
                    else:
                        pred[j] = i
                    if j not in q:
                        q.append(j)
        return (True, pred)

    def label_correcting_check_cycle(self, j, pred):
        '''
        API:
            label_correcting_check_cycle(self, j, pred)
        Description:
            Checks if predecessor dictionary has a cycle, j represents the node
            that predecessor is recently updated.
        Pre:
            (1) predecessor of source node should be None.
        Input:
            j: node that predecessor is recently updated.
            pred: predecessor dictionary
        Return:
            If there exists a cycle, returns the list that represents the
            cycle, otherwise it returns to None.
        '''
        labelled = {}
        for n in self.neighbors:
            labelled[n] = None
        current = j
        while current != None:
            if labelled[current]==j:
                cycle = self.label_correcting_get_cycle(j, pred)
                return cycle
            labelled[current] = j
            current = pred[current]
        return None

    def label_correcting_get_cycle(self, j, pred):
        '''
        API:
            label_correcting_get_cycle(self, labelled, pred)
        Description:
            In label correcting check cycle it is decided pred has a cycle and
            nodes in the cycle are labelled. We will create a list of nodes
            in the cycle using labelled and pred inputs.
        Pre:
            This method should be called from label_correcting_check_cycle(),
            unless you are sure about what you are doing.
        Input:
            j: Node that predecessor is recently updated. We know that it is
            in the cycle
            pred: Predecessor dictionary that contains a cycle
        Post:
            Returns a list of nodes that represents cycle. It is in
            [n_1, n_2, ..., n_k] form where the cycle has k nodes.
        '''
        cycle = []
        cycle.append(j)
        current = pred[j]
        while current!=j:
            cycle.append(current)
            current = pred[current]
        cycle.reverse()
        return cycle

    def augment_cycle(self, amount, cycle):
        '''
        API:
            augment_cycle(self, amount, cycle):
        Description:
            Augments 'amount' unit of flow along cycle.
        Pre:
            Arcs should have 'flow' attribute.
        Inputs:
            amount: An integer representing the amount to augment
            cycle: A list representing a cycle
        Post:
            Changes 'flow' attributes of arcs.
        '''
        index = 0
        k = len(cycle)
        while index<(k-1):
            i = cycle[index]
            j = cycle[index+1]
            if (i,j) in self.edge_attr:
                flow_ij = self.edge_attr[(i,j)]['flow']
                self.edge_attr[(i,j)]['flow'] = flow_ij+amount
            else:
                flow_ji = self.edge_attr[(j,i)]['flow']
                self.edge_attr[(j,i)]['flow'] = flow_ji-amount
            index += 1
        i = cycle[k-1]
        j = cycle[0]
        if (i,j) in self.edge_attr:
            flow_ij = self.edge_attr[(i,j)]['flow']
            self.edge_attr[(i,j)]['flow'] = flow_ij+amount
        else:
            flow_ji = self.edge_attr[(j,i)]['flow']
            self.edge_attr[(j,i)]['flow'] = flow_ji-amount

    def network_simplex(self, display, pivot, root):
        '''
        API:
            network_simplex(self, display, pivot, root)
        Description:
            Solves minimum cost feasible flow problem using network simplex
            algorithm. It is recommended to use min_cost_flow(algo='simplex')
            instead of using network_simplex() directly. Returns True when an
            optimal solution is found, returns False otherwise. 'flow' attribute
            values of arcs should be considered as junk when returned False.
        Pre:
            (1) check Pre section of min_cost_flow()
        Input:
            pivot: specifies pivot rule. Check min_cost_flow()
            display: 'off' for no display, 'matplotlib' for live update of
            spanning tree.
            root: Root node for the underlying spanning trees that will be
            generated by network simplex algorthm.
        Post:
            (1) Changes 'flow' attribute of edges.
        Return:
            Returns True when an optimal solution is found, returns
            False otherwise.
        '''
        # ==== determine an initial tree structure (T,L,U)
        # find a feasible flow
        if not self.find_feasible_flow():
            return False
        t = self.simplex_find_tree()
        self.set_display_mode(display)
        # mark spanning tree arcs
        self.simplex_mark_st_arcs(t)
        # display initial spanning tree
        t.simplex_redraw(display, root)
        t.set_display_mode(display)
        #t.display()
        self.display()
        # set predecessor, depth and thread indexes
        t.simplex_search(root, 1)
        # compute potentials
        self.simplex_compute_potentials(t, root)
        # while some nontree arc violates optimality conditions
        while not self.simplex_optimal(t):
            self.display()
            # select an entering arc (k,l)
            (k,l) = self.simplex_select_entering_arc(t, pivot)
            self.simplex_mark_entering_arc(k, l)
            self.display()
            # determine leaving arc
            ((p,q), capacity, cycle)=self.simplex_determine_leaving_arc(t,k,l)
            # mark leaving arc
            self.simplex_mark_leaving_arc(p, q)
            self.display()
            self.simplex_remove_arc(t, p, q, capacity, cycle)
            # display after arc removed
            self.display()
            self.simplex_mark_st_arcs(t)
            self.display()
            # set predecessor, depth and thread indexes
            t.simplex_redraw(display, root)
            #t.display()
            t.simplex_search(root, 1)
            # compute potentials
            self.simplex_compute_potentials(t, root)
        return True

    def simplex_mark_leaving_arc(self, p, q):
        '''
        API:
            simplex_mark_leving_arc(self, p, q)
        Description:
            Marks leaving arc.
        Input:
            p: tail of the leaving arc
            q: head of the leaving arc
        Post:
            Changes color attribute of leaving arc.
        '''
        self.set_edge_attr(p, q, 'color', 'red')

    def simplex_determine_leaving_arc(self, t, k, l):
        '''
        API:
            simplex_determine_leaving_arc(self, t, k, l)
        Description:
            Determines and returns the leaving arc.
        Input:
            t: current spanning tree solution.
            k: tail of the entering arc.
            l: head of the entering arc.
        Return:
            Returns the tuple that represents leaving arc, capacity of the
            cycle and cycle.
        '''
        # k,l are the first two elements of the cycle
        cycle = self.simplex_identify_cycle(t, k, l)
        flow_kl = self.get_edge_attr(k, l, 'flow')
        capacity_kl = self.get_edge_attr(k, l, 'capacity')
        min_capacity = capacity_kl
        # check if k,l is in U or L
        if flow_kl==capacity_kl:
            # l,k will be the last two elements
            cycle.reverse()
        n = len(cycle)
        index = 0
        # determine last blocking arc
        t.add_edge(k, l)
        tel = t.get_edge_list()
        while index < (n-1):
            if (cycle[index], cycle[index+1]) in tel:
                flow = self.edge_attr[(cycle[index], cycle[index+1])]['flow']
                capacity = \
                    self.edge_attr[(cycle[index],cycle[index+1])]['capacity']
                if min_capacity >= (capacity-flow):
                    candidate = (cycle[index], cycle[index+1])
                    min_capacity = capacity-flow
            else:
                flow = self.edge_attr[(cycle[index+1], cycle[index])]['flow']
                if min_capacity >= flow:
                    candidate = (cycle[index+1], cycle[index])
                    min_capacity = flow
            index += 1
        # check arc (cycle[n-1], cycle[0])
        if (cycle[n-1], cycle[0]) in tel:
            flow = self.edge_attr[(cycle[n-1], cycle[0])]['flow']
            capacity = self.edge_attr[(cycle[n-1], cycle[0])]['capacity']
            if min_capacity >= (capacity-flow):
                candidate = (cycle[n-1], cycle[0])
                min_capacity = capacity-flow
        else:
            flow = self.edge_attr[(cycle[0], cycle[n-1])]['flow']
            if min_capacity >= flow:
                candidate = (cycle[0], cycle[n-1])
                min_capacity = flow
        return (candidate, min_capacity, cycle)

    def simplex_mark_entering_arc(self, k, l):
        '''
        API:
            simplex_mark_entering_arc(self, k, l)
        Description:
            Marks entering arc (k,l)
        Input:
            k: tail of the entering arc
            l: head of the entering arc
        Post:
            (1) color attribute of the arc (k,l)
        '''
        self.set_edge_attr(k, l, 'color', 'green')

    def simplex_mark_st_arcs(self, t):
        '''
        API:
            simplex_mark_st_arcs(self, t)
        Description:
            Marks spanning tree arcs.
            Case 1, Blue: Arcs that are at lower bound and in tree.
            Case 2, Red: Arcs that are at upper bound and in tree.
            Case 3, Green: Arcs that are between bounds are green.
            Case 4, Brown: Non-tree arcs at lower bound.
            Case 5, Violet: Non-tree arcs at upper bound.
        Input:
            t: t is the current spanning tree
        Post:
            (1) color attribute of edges.
        '''
        tel = list(t.edge_attr.keys())
        for e in self.get_edge_list():
            flow_e = self.edge_attr[e]['flow']
            capacity_e = self.edge_attr[e]['capacity']
            if e in tel:
                if flow_e == 0:
                    self.edge_attr[e]['color'] = 'blue'
                elif flow_e == capacity_e:
                    self.edge_attr[e]['color'] = 'blue'
                else:
                    self.edge_attr[e]['color'] = 'blue'
            else:
                if flow_e == 0:
                    self.edge_attr[e]['color'] = 'black'
                elif flow_e == capacity_e:
                    self.edge_attr[e]['color'] = 'black'
                else:
                    msg = "Arc is not in ST but has flow between bounds."
                    raise Exception(msg)

    def print_flow(self):
        '''
        API:
            print_flow(self)
        Description:
            Prints all positive flows to stdout. This method can be used for
            debugging purposes.
        '''
        print('printing current edge, flow, capacity')
        for e in self.edge_attr:
            if self.edge_attr[e]['flow']!=0:
                print(e, str(self.edge_attr[e]['flow']).ljust(4), end=' ')
                print(str(self.edge_attr[e]['capacity']).ljust(4))

    def simplex_redraw(self, display, root):
        '''
        API:
            simplex_redraw(self, display, root)
        Description:
            Returns a new graph instance that is same as self but adds nodes
            and arcs in a way that the resulting tree will be displayed
            properly.
        Input:
            display: display mode
            root: root node in tree.
        Return:
            Returns a graph same as self.
        '''
        nl = self.get_node_list()
        el = self.get_edge_list()
        new = Graph(type=DIRECTED_GRAPH, layout='dot', display=display)
        pred_i = self.get_node(root).get_attr('pred')
        thread_i = self.get_node(root).get_attr('thread')
        depth_i = self.get_node(root).get_attr('depth')
        new.add_node(root, pred=pred_i, thread=thread_i, depth=depth_i)
        q = [root]
        visited = [root]
        while q:
            name = q.pop()
            visited.append(name)
            neighbors = self.neighbors[name] + self.in_neighbors[name]
            for n in neighbors:
                if n not in new.get_node_list():
                    pred_i = self.get_node(n).get_attr('pred')
                    thread_i = self.get_node(n).get_attr('thread')
                    depth_i = self.get_node(n).get_attr('depth')
                    new.add_node(n, pred=pred_i, thread=thread_i, depth=depth_i)
                if (name,n) in el:
                    if (name,n) not in new.edge_attr:
                        new.add_edge(name,n)
                else:
                    if (n,name) not in new.edge_attr:
                        new.add_edge(n,name)
                if n not in visited:
                    q.append(n)
        for e in el:
            flow = self.edge_attr[e]['flow']
            capacity = self.edge_attr[e]['capacity']
            cost = self.edge_attr[e]['cost']
            new.edge_attr[e]['flow'] = flow
            new.edge_attr[e]['capacity'] = capacity
            new.edge_attr[e]['cost'] = cost
            new.edge_attr[e]['label'] =  "%d/%d/%d" %(flow,capacity,cost)
        return new

    def simplex_remove_arc(self, t, p, q, min_capacity, cycle):
        '''
        API:
            simplex_remove_arc(self, p, q, min_capacity, cycle)
        Description:
            Removes arc (p,q), updates t, updates flows, where (k,l) is
            the entering arc.
        Input:
            t: tree solution to be updated.
            p: tail of the leaving arc.
            q: head of the leaving arc.
            min_capacity: capacity of the cycle.
            cycle: cycle obtained when entering arc considered.
        Post:
            (1) updates t.
            (2) updates 'flow' attributes.
        '''
        # augment min_capacity along cycle
        n = len(cycle)
        tel = list(t.edge_attr.keys())
        index = 0
        while index < (n-1):
            if (cycle[index], cycle[index+1]) in tel:
                flow_e = self.edge_attr[(cycle[index], cycle[index+1])]['flow']
                self.edge_attr[(cycle[index], cycle[index+1])]['flow'] =\
                    flow_e+min_capacity
            else:
                flow_e = self.edge_attr[(cycle[index+1], cycle[index])]['flow']
                self.edge_attr[(cycle[index+1], cycle[index])]['flow'] =\
                    flow_e-min_capacity
            index += 1
        # augment arc cycle[n-1], cycle[0]
        if (cycle[n-1], cycle[0]) in tel:
            flow_e = self.edge_attr[(cycle[n-1], cycle[0])]['flow']
            self.edge_attr[(cycle[n-1], cycle[0])]['flow'] =\
                flow_e+min_capacity
        else:
            flow_e = self.edge_attr[(cycle[0], cycle[n-1])]['flow']
            self.edge_attr[(cycle[0], cycle[n-1])]['flow'] =\
                flow_e-min_capacity
        # remove leaving arc
        t.del_edge((p, q))
        # set label of removed arc
        flow_pq = self.get_edge_attr(p, q, 'flow')
        capacity_pq = self.get_edge_attr(p, q, 'capacity')
        cost_pq = self.get_edge_attr(p, q, 'cost')
        self.set_edge_attr(p, q, 'label',
                           "%d/%d/%d" %(flow_pq,capacity_pq,cost_pq))
        for e in t.edge_attr:
            flow = self.edge_attr[e]['flow']
            capacity = self.edge_attr[e]['capacity']
            cost = self.edge_attr[e]['cost']
            t.edge_attr[e]['flow'] = flow
            t.edge_attr[e]['capacity'] = capacity
            t.edge_attr[e]['cost'] = cost
            t.edge_attr[e]['label'] = "%d/%d/%d" %(flow,capacity,cost)
            self.edge_attr[e]['label'] = "%d/%d/%d" %(flow,capacity,cost)

    def simplex_select_entering_arc(self, t, pivot):
        '''
        API:
            simplex_select_entering_arc(self, t, pivot)
        Description:
            Decides and returns entering arc using pivot rule.
        Input:
            t: current spanning tree solution
            pivot: May be one of the following; 'first_eligible' or 'dantzig'.
            'dantzig' is the default value.
        Return:
            Returns entering arc tuple (k,l)
        '''
        if pivot=='dantzig':
            # pick the maximum violation
            candidate = {}
            for e in self.edge_attr:
                if e in t.edge_attr:
                    continue
                flow_ij = self.edge_attr[e]['flow']
                potential_i = self.get_node(e[0]).get_attr('potential')
                potential_j = self.get_node(e[1]).get_attr('potential')
                capacity_ij = self.edge_attr[e]['capacity']
                c_ij = self.edge_attr[e]['cost']
                cpi_ij = c_ij - potential_i + potential_j
                if flow_ij==0:
                    if cpi_ij < 0:
                        candidate[e] = cpi_ij
                elif flow_ij==capacity_ij:
                    if cpi_ij > 0:
                        candidate[e] = cpi_ij
            for e in candidate:
                max_c = e
                max_v = abs(candidate[e])
                break
            for e in candidate:
                if max_v < abs(candidate[e]):
                    max_c = e
                    max_v = abs(candidate[e])
        elif pivot=='first_eligible':
            # pick the first eligible
            for e in self.edge_attr:
                if e in t.edge_attr:
                    continue
                flow_ij = self.edge_attr[e]['flow']
                potential_i = self.get_node(e[0]).get_attr('potential')
                potential_j = self.get_node(e[1]).get_attr('potential')
                capacity_ij = self.edge_attr[e]['capacity']
                c_ij = self.edge_attr[e]['cost']
                cpi_ij = c_ij - potential_i + potential_j
                if flow_ij==0:
                    if cpi_ij < 0:
                        max_c = e
                        max_v = abs(cpi_ij)
                elif flow_ij==capacity_ij:
                    if cpi_ij > 0:
                        max_c = e
                        max_v = cpi_ij
        else:
            raise Exception("Unknown pivot rule.")
        return max_c

    def simplex_optimal(self, t):
        '''
        API:
            simplex_optimal(self, t)
        Description:
            Checks if the current solution is optimal, if yes returns True,
            False otherwise.
        Pre:
            'flow' attributes represents a solution.
        Input:
            t: Graph instance tat reperesents spanning tree solution.
        Return:
            Returns True if the current solution is optimal (optimality
            conditions are satisfied), else returns False
        '''
        for e in self.edge_attr:
            if e in t.edge_attr:
                continue
            flow_ij = self.edge_attr[e]['flow']
            potential_i = self.get_node(e[0]).get_attr('potential')
            potential_j = self.get_node(e[1]).get_attr('potential')
            capacity_ij = self.edge_attr[e]['capacity']
            c_ij = self.edge_attr[e]['cost']
            cpi_ij = c_ij - potential_i + potential_j
            if flow_ij==0:
                if cpi_ij < 0:
                    return False
            elif flow_ij==capacity_ij:
                if cpi_ij > 0:
                    return False
        return True

    def simplex_find_tree(self):
        '''
        API:
            simplex_find_tree(self)
        Description:
            Assumes a feasible flow solution stored in 'flow' attribute's of
            arcs and converts this solution to a feasible spanning tree
            solution.
        Pre:
            (1) 'flow' attributes represents a feasible flow solution.
        Post:
            (1) 'flow' attributes may change when eliminating cycles.
        Return:
            Return a Graph instance that is a spanning tree solution.
        '''
        # find a cycle
        solution_g = self.get_simplex_solution_graph()
        cycle = solution_g.simplex_find_cycle()
        while cycle is not None:
            # find amount to augment and direction
            amount = self.simplex_augment_cycle(cycle)
            # augment along the cycle
            self.augment_cycle(amount, cycle)
            # find a new cycle
            solution_g = self.get_simplex_solution_graph()
            cycle = solution_g.simplex_find_cycle()
        # check if the solution is connected
        while self.simplex_connect(solution_g):
            pass
        # add attributes
        for e in self.edge_attr:
            flow = self.edge_attr[e]['flow']
            capacity = self.edge_attr[e]['capacity']
            cost = self.edge_attr[e]['cost']
            self.edge_attr[e]['label'] = "%d/%d/%d" %(flow,capacity,cost)
            if e in solution_g.edge_attr:
                solution_g.edge_attr[e]['flow'] = flow
                solution_g.edge_attr[e]['capacity'] = capacity
                solution_g.edge_attr[e]['cost'] = cost
                solution_g.edge_attr[e]['label'] = "%d/%d/%d" %(flow,capacity,cost)
        return solution_g

    def simplex_connect(self, solution_g):
        '''
        API:
            simplex_connect(self, solution_g)
        Description:
            At this point we assume that the solution does not have a cycle.
            We check if all the nodes are connected, if not we add an arc to
            solution_g that does not create a cycle and return True. Otherwise
            we do nothing and return False.
        Pre:
            (1) We assume there is no cycle in the solution.
        Input:
            solution_g: current spanning tree solution instance.
        Post:
            (1) solution_g is updated. An arc that does not create a cycle is
            added.
            (2) 'component' attribute of nodes are changed.
        Return:
            Returns True if an arc is added, returns False otherwise.
        '''
        nl = solution_g.get_node_list()
        current = nl[0]
        pred = solution_g.simplex_search(current, current)
        separated = list(pred.keys())
        for n in nl:
            if solution_g.get_node(n).get_attr('component') != current:
                # find an arc from n to seperated
                for m in separated:
                    if (n,m) in self.edge_attr:
                        solution_g.add_edge(n,m)
                        return True
                    elif (m,n) in self.edge_attr:
                        solution_g.add_edge(m,n)
                        return True
        return False

    def simplex_search(self, source, component_nr):
        '''
        API:
            simplex_search(self, source, component_nr)
        Description:
            Searches graph starting from source. Its difference from usual
            search is we can also go backwards along an arc. When the graph
            is a spanning tree it computes predecessor, thread and depth
            indexes and stores them as node attributes. These values should be
            considered as junk when the graph is not a spanning tree.
        Input:
            source: source node
            component_nr: component number
        Post:
            (1) Sets the component number of all reachable nodes to component.
            Changes 'component' attribute of nodes.
            (2) Sets 'pred', 'thread' and 'depth' attributes of nodes. These
            values are junk if the graph is not a tree.
        Return:
            Returns predecessor dictionary.
        '''
        q = [source]
        pred = {source:None}
        depth = {source:0}
        sequence = []
        for n in self.neighbors:
            self.get_node(n).set_attr('component', None)
        while q:
            current = q.pop()
            self.get_node(current).set_attr('component', component_nr)
            sequence.append(current)
            neighbors = self.in_neighbors[current] + self.neighbors[current]
            for n in neighbors:
                if n in pred:
                    continue
                self.get_node(n).set_attr('component', component_nr)
                pred[n] = current
                depth[n] = depth[current]+1
                q.append(n)
        for i in range(len(sequence)-1):
            self.get_node(sequence[i]).set_attr('thread', int(sequence[i+1]))
        self.get_node(sequence[-1]).set_attr('thread', int(sequence[0]))
        for n in pred:
            self.get_node(n).set_attr('pred', pred[n])
            self.get_node(n).set_attr('depth', depth[n])
        return pred

    def simplex_augment_cycle(self, cycle):
        '''
        API:
            simplex_augment_cycle(self, cycle)
        Description:
            Augments along the cycle to break it.
        Pre:
            'flow', 'capacity' attributes on arcs.
        Input:
            cycle: list representing a cycle in the solution
        Post:
            'flow' attribute will be modified.
        '''
        # find amount to augment
        index = 0
        k = len(cycle)
        el = list(self.edge_attr.keys())
        # check arc (cycle[k-1], cycle[0])
        if (cycle[k-1], cycle[0]) in el:
            min_capacity = self.edge_attr[(cycle[k-1], cycle[0])]['capacity']-\
                              self.edge_attr[(cycle[k-1], cycle[0])]['flow']
        else:
            min_capacity = self.edge_attr[(cycle[0], cycle[k-1])]['flow']
        # check rest of the arcs in the cycle
        while index<(k-1):
            i = cycle[index]
            j = cycle[index+1]
            if (i,j) in el:
                capacity_ij = self.edge_attr[(i,j)]['capacity'] -\
                              self.edge_attr[(i,j)]['flow']
            else:
                capacity_ij = self.edge_attr[(j,i)]['flow']
            if min_capacity > capacity_ij:
                min_capacity = capacity_ij
            index += 1
        return min_capacity

    def simplex_find_cycle(self):
        '''
        API:
            simplex_find_cycle(self)
        Description:
            Returns a cycle (list of nodes) if the graph has one, returns None
            otherwise. Uses DFS. During DFS checks existence of arcs to lower
            depth regions. Note that direction of the arcs are not important.
        Return:
            Returns list of nodes that represents cycle. Returns None if the
            graph does not have any cycle.
        '''
        # make a dfs, if you identify an arc to a lower depth node we have a
        # cycle
        nl = self.get_node_list()
        q = [nl[0]]
        visited = []
        depth = {nl[0]:0}
        pred = {nl[0]:None}
        for n in nl:
            self.get_node(n).set_attr('component', None)
        component_nr = int(nl[0])
        self.get_node(nl[0]).set_attr('component', component_nr)
        while True:
            while q:
                current = q.pop()
                visited.append(current)
                neighbors = self.in_neighbors[current] +\
                    self.neighbors[current]
                for n in neighbors:
                    if n==pred[current]:
                        continue
                    self.get_node(n).set_attr('component', component_nr)
                    if n in depth:
                        # we have a cycle
                        cycle1 = []
                        cycle2 = []
                        temp = n
                        while temp is not None:
                            cycle1.append(temp)
                            temp = pred[temp]
                        temp = current
                        while temp is not None:
                            cycle2.append(temp)
                            temp = pred[temp]
                        cycle1.pop()
                        cycle1.reverse()
                        cycle2.extend(cycle1)
                        return cycle2
                    else:
                        pred[n] = current
                        depth[n] = depth[current] + 1
                    if n not in visited:
                        q.append(n)
            flag = False
            for n in nl:
                if self.get_node(n).get_attr('component') is None:
                    q.append(n)
                    depth = {n:0}
                    pred = {n:None}
                    visited = []
                    component_nr = int(n)
                    self.get_node(n).set_attr('component', component_nr)
                    flag = True
                    break
            if not flag:
                break
        return None

    def get_simplex_solution_graph(self):
        '''
        API:
            get_simplex_solution_graph(self):
        Description:
            Assumes a feasible flow solution stored in 'flow' attribute's of
            arcs. Returns the graph with arcs that have flow between 0 and
            capacity.
        Pre:
            (1) 'flow' attribute represents a feasible flow solution. See
            Pre section of min_cost_flow() for details.
        Return:
            Graph instance that only has the arcs that have flow strictly
            between 0 and capacity.
        '''
        simplex_g = Graph(type=DIRECTED_GRAPH)
        for i in self.neighbors:
            simplex_g.add_node(i)
        for e in self.edge_attr:
            flow_e = self.edge_attr[e]['flow']
            capacity_e = self.edge_attr[e]['capacity']
            if flow_e>0 and flow_e<capacity_e:
                simplex_g.add_edge(e[0], e[1])
        return simplex_g

    def simplex_compute_potentials(self, t, root):
        '''
        API:
            simplex_compute_potentials(self, t, root)
        Description:
            Computes node potentials for a minimum cost flow problem and stores
            them as node attribute 'potential'. Based on pseudocode given in
            Network Flows by Ahuja et al.
        Pre:
            (1) Assumes a directed graph in which each arc has a 'cost'
            attribute.
            (2) Uses 'thread' and 'pred' attributes of nodes.
        Input:
            t: Current spanning tree solution, its type is Graph.
            root: root node of the tree.
        Post:
            Keeps the node potentials as 'potential' attribute.
        '''
        self.get_node(root).set_attr('potential', 0)
        j = t.get_node(root).get_attr('thread')
        while j is not root:
            i = t.get_node(j).get_attr('pred')
            potential_i = self.get_node(i).get_attr('potential')
            if (i,j) in self.edge_attr:
                c_ij = self.edge_attr[(i,j)]['cost']
                self.get_node(j).set_attr('potential', potential_i-c_ij)
            if (j,i) in self.edge_attr:
                c_ji = self.edge_attr[(j,i)]['cost']
                self.get_node(j).set_attr('potential', potential_i+c_ji)
            j = t.get_node(j).get_attr('thread')

    def simplex_identify_cycle(self, t, k, l):
        '''
        API:
            identify_cycle(self, t, k, l)
        Description:
            Identifies and returns to the pivot cycle, which is a list of
            nodes.
        Pre:
            (1) t is spanning tree solution, (k,l) is the entering arc.
        Input:
            t: current spanning tree solution
            k: tail of the entering arc
            l: head of the entering arc
        Returns:
            List of nodes in the cycle.
        '''
        i = k
        j = l
        cycle = []
        li = [k]
        lj = [j]
        while i is not j:
            depth_i = t.get_node(i).get_attr('depth')
            depth_j = t.get_node(j).get_attr('depth')
            if depth_i > depth_j:
                i = t.get_node(i).get_attr('pred')
                li.append(i)
            elif depth_i < depth_j:
                j = t.get_node(j).get_attr('pred')
                lj.append(j)
            else:
                i = t.get_node(i).get_attr('pred')
                li.append(i)
                j = t.get_node(j).get_attr('pred')
                lj.append(j)
        cycle.extend(lj)
        li.pop()
        li.reverse()
        cycle.extend(li)
        # l is beginning k is end
        return cycle

    def min_cost_flow(self, display = None, **args):
        '''
        API:
            min_cost_flow(self, display='off', **args)
        Description:
            Solves minimum cost flow problem using node/edge attributes with
            the algorithm specified.
        Pre:
            (1) Assumes a directed graph in which each arc has 'capacity' and
            'cost' attributes.
            (2) Nodes should have 'demand' attribute. This value should be
            positive for supply and negative for demand, and 0 for transhipment
            nodes.
            (3) The graph should be connected.
            (4) Assumes (i,j) and (j,i) does not exist together. Needed when
            solving max flow. (max flow problem is solved to get a feasible
            flow).
        Input:
            display: 'off' for no display, 'matplotlib' for live update of tree
            args: may have the following
                display: display method, if not given current mode (the one
                    specified by __init__ or set_display) will be used.
                algo: determines algorithm to use, can be one of the following
                    'simplex': network simplex algorithm
                    'cycle_canceling': cycle canceling algorithm
                    'simplex' is used if not given.
                    see Network Flows by Ahuja et al. for details of algorithms.
                pivot: valid if algo is 'simlex', determines pivoting rule for
                    simplex, may be one of the following; 'first_eligible',
                    'dantzig' or 'scaled'.
                    'dantzig' is used if not given.
                    see Network Flows by Ahuja et al. for pivot rules.
                root: valid if algo is 'simlex', specifies the root node for
                    simplex algorithm. It is name of the one of the nodes. It
                    will be chosen randomly if not provided.
        Post:
            The 'flow' attribute of each arc gives the optimal flows.
            'distance' attribute of the nodes are also changed during max flow
            solution process.
        Examples:
            g.min_cost_flow():
                solves minimum cost feasible flow problem using simplex
                algorithm with dantzig pivoting rule.
                See pre section for details.
            g.min_cost_flow(algo='cycle_canceling'):
                solves minimum cost feasible flow problem using cycle canceling
                agorithm.
            g.min_cost_flow(algo='simplex', pivot='scaled'):
                solves minimum cost feasible flow problem using network simplex
                agorithm with scaled pivot rule.
        '''
        if display is None:
            display = self.attr['display']
        if 'algo' in args:
            algorithm = args['algo']
        else:
            algorithm = 'simplex'
        if algorithm == 'simplex':
            if 'root' in args:
                root = args['root']
            else:
                for k in self.neighbors:
                    root = k
                    break
            if 'pivot' in args:
                if not self.network_simplex(display, args['pivot'], root):
                    print('problem is infeasible')
            else:
                if not self.network_simplex(display, 'dantzig', root):
                    print('problem is infeasible')
        elif algorithm == 'cycle_canceling':
            if not self.cycle_canceling(display):
                print('problem is infeasible')
        else:
            print(args['algo'], 'is not a defined algorithm. Exiting.')
            return

    def random(self, numnodes = 10, degree_range = (2, 4), length_range = (1, 10),
               density = None, edge_format = None, node_format = None,
               Euclidean = False, seedInput = 0, add_labels = True,
               parallel_allowed = False, node_selection = 'closest',
               scale = 10, scale_cost = 5):
        '''
        API:
            random(self, numnodes = 10, degree_range = None, length_range = None,
               density = None, edge_format = None, node_format = None,
               Euclidean = False, seedInput = 0)
        Description:
            Populates graph with random edges and nodes.
        Input:
            numnodes: Number of nodes to add.
            degree_range: A tuple that has lower and upper bounds of degree for
            a node.
            length_range: A tuple that has lower and upper bounds for 'cost'
            attribute of edges.
            density: Density of edges, ie. 0.5 indicates a node will
            approximately have edge to half of the other nodes.
            edge_format: Dictionary that specifies attribute values for edges.
            node_format: Dictionary that specifies attribute values for nodes.
            Euclidean: Creates an Euclidean graph (Euclidean distance between
            nodes) if True.
            seedInput: Seed that will be used for random number generation.
        Pre:
            It is recommended to call this method on empty Graph objects.
        Post:
            Graph will be populated by nodes and edges.
        '''
        random.seed(seedInput)
        if edge_format == None:
            edge_format = {'fontsize':10,
                           'fontcolor':'blue'}
        if node_format == None:
            node_format = {'height':0.5,
                           'width':0.5,
                           'fixedsize':'true',
                           'fontsize':10,
                           'fontcolor':'red',
                           'shape':'circle',
                           }
        if Euclidean == False:
            for m in range(numnodes):
                self.add_node(m, **node_format)
            if degree_range is not None and density is None:
                for m in range(numnodes):
                    degree = random.randint(degree_range[0], degree_range[1])
                    i = 0
                    while i < degree:
                        n = random.randint(1, numnodes-1)
                        if (((m,n) not in self.edge_attr and m != n) and
                            (parallel_allowed or (n, m) not in self.edge_attr)):
                            if length_range is not None:
                                length = random.randint(length_range[0],
                                                        length_range[1])
                                self.add_edge(m, n, cost = length, **edge_format)
                                if add_labels:
                                    self.set_edge_attr(m, n, 'label', str(length))
                                else:
                                    self.add_edge(m, n, **edge_format)
                        i += 1
            elif density != None:
                for m in range(numnodes):
                    if self.graph_type == DIRECTED_GRAPH:
                        numnodes2 = numnodes
                    else:
                        numnodes2 = m
                    for n in range(numnodes2):
                        if ((parallel_allowed or (n, m) not in self.edge_attr)
                            and m != n):
                            if random.random() < density:
                                if length_range is not None:
                                    length = random.randint(length_range[0],
                                                     length_range[1])
                                    self.add_edge(m, n, cost = length,
                                                  **edge_format)
                                    if add_labels:
                                        self.set_edge_attr(m, n, 'label', str(length))
                                else:
                                    self.add_edge(m, n, **edge_format)
            else:
                print("Must set either degree range or density")
        else:
            for m in range(numnodes):
                ''' Assigns random coordinates (between 1 and 20) to the nodes
                '''
                x = random.random()*scale
                y = random.random()*scale
                self.add_node(m, locationx = x, locationy = y,
                              pos = '"'+str(x) + "," + str(y)+'!"',
                              **node_format)
            if degree_range is not None and density is None:
                for m in range(numnodes):
                    degree = random.randint(degree_range[0], degree_range[1])
                    i = 0
                    neighbors = []
                    if node_selection == 'random':
                        while i < degree:
                            length = round((((self.get_node(n).get_attr('locationx') -
                                              self.get_node(m).get_attr('locationx')) ** 2 +
                                             (self.get_node(n).get_attr('locationy') -
                                              self.get_node(m).get_attr('locationy')) ** 2) ** 0.5)*scale_cost,
                                           0)
                            if (((m,n) not in self.edge_attr and m != n) and
                                (parallel_allowed or (n, m) not in self.edge_attr)):
                                neighbors.append(random.randint(0, numnodes-1))
                                self.add_edge(m, n, cost = int(length), **edge_format)
                                if add_labels:
                                    self.set_edge_attr(m, n, 'label', str(int(length)))
                                i += 1
                    elif node_selection == 'closest':
                        lengths = []
                        for n in range(numnodes):
                            lengths.append((n, round((((self.get_node(n).get_attr('locationx') -
                                                        self.get_node(m).get_attr('locationx')) ** 2 +
                                                       (self.get_node(n).get_attr('locationy') -
                                                        self.get_node(m).get_attr('locationy')) ** 2) ** 0.5)*scale_cost,
                                                     0)))
                        lengths.sort(key = lambda l : l[1])
                        for i in range(degree+1):
                            if not (lengths[i][0] == m or self.check_edge(m, lengths[i][0])):
                                self.add_edge(m, lengths[i][0], cost = int(lengths[i][1]), **edge_format)
                                if add_labels:
                                    self.set_edge_attr(m, lengths[i][0], 'label', str(int(lengths[i][1])))
                    else:
                        print("Unknown node selection rule...exiting")
                        return
            elif density != None:
                for m in range(numnodes):
                    if self.graph_type == DIRECTED_GRAPH:
                        numnodes2 = numnodes
                    else:
                        numnodes2 = m
                    for n in range(numnodes2):
                        if ((parallel_allowed or (n, m) not in self.edge_attr)
                            and m != n):
                            if random.random() < density:
                                if length_range is None:
                                    ''' calculates the euclidean norm and round it
                                    to an integer '''
                                    length = round((((self.get_node(n).get_attr('locationx') -
                                                      self.get_node(m).get_attr('locationx')) ** 2 +
                                                     (self.get_node(n).get_attr('locationy') -
                                                      self.get_node(m).get_attr('locationy')) ** 2) ** 0.5), 0)
                                    self.add_edge(m, n, cost = int(length), **edge_format)
                                    if add_labels:
                                        self.set_edge_attr(m, n, 'label', str(int(length)))
                                else:
                                    self.add_edge(m, n, **edge_format)
            else:
                print("Must set either degree range or density")

    def page_rank(self, damping_factor=0.85, max_iterations=100,
                  min_delta=0.00001):
        '''
        API:
            page_rank(self, damping_factor=0.85, max_iterations=100,
                  min_delta=0.00001)
        Description:
            Compute and return the page-rank of a directed graph.
            This function was originally taken from here and modified for this
            graph class: http://code.google.com/p/python-graph/source/browse/
            trunk/core/pygraph/algorithms/pagerank.py
        Input:
            damping_factor: Damping factor.
            max_iterations: Maximum number of iterations.
            min_delta: Smallest variation required to have a new iteration.
        Pre:
            Graph should be a directed graph.
        Return:
            Returns dictionary of page-ranks. Keys are node names, values are
            corresponding page-ranks.
        '''
        nodes = self.get_node_list()
        graph_size = len(nodes)
        if graph_size == 0:
            return {}
        #value for nodes without inbound links
        min_value = old_div((1.0-damping_factor),graph_size)
        # itialize the page rank dict with 1/N for all nodes
        pagerank = dict.fromkeys(nodes, old_div(1.0,graph_size))
        for _ in range(max_iterations):
            diff = 0 #total difference compared to last iteraction
            # computes each node PageRank based on inbound links
            for node in nodes:
                rank = min_value
                for referring_page in self.get_in_neighbors(node):
                    rank += (damping_factor * pagerank[referring_page] /
                             len(self.get_neighbors(referring_page)))
                diff += abs(pagerank[node] - rank)
                pagerank[node] = rank
            #stop if PageRank has converged
            if diff < min_delta:
                break
        return pagerank

    def get_degrees(self):
        '''
        API:
            get_degree(self)
        Description:
            Returns degrees of nodes in dictionary format.
        Return:
            Returns a dictionary of node degrees. Keys are node names, values
            are corresponding degrees.
        '''
        degree = {}
        if self.attr['type'] is not DIRECTED_GRAPH:
            for n in self.get_node_list():
                degree[n] = len(self.get_neighbors(n))
            return degree
        else:
            for n in self.get_node_list():
                degree[n] = (len(self.get_in_neighbors(n)) +
                             len(self.get_out_neighbors(n)))

    def get_in_degrees(self):
        '''
        API:
            get_degree(self)
        Description:
            Returns degrees of nodes in dictionary format.
        Return:
            Returns a dictionary of node degrees. Keys are node names, values
            are corresponding degrees.
        '''
        degree = {}
        if self.attr['type'] is not DIRECTED_GRAPH:
            print('This function only works for directed graphs')
            return
        for n in self.get_node_list():
            degree[n] = len(self.get_in_neighbors(n))
        return degree

    def get_out_degrees(self):
        '''
        API:
            get_degree(self)
        Description:
            Returns degrees of nodes in dictionary format.
        Return:
            Returns a dictionary of node degrees. Keys are node names, values
            are corresponding degrees.
        '''
        degree = {}
        if self.attr['type'] is not DIRECTED_GRAPH:
            print('This function only works for directed graphs')
            return
        for n in self.get_node_list():
            degree[n] = len(self.get_out_neighbors(n))
        return degree

    def get_diameter(self):
        '''
        API:
            get_diameter(self)
        Description:
            Returns diameter of the graph. Diameter is defined as follows.
            distance(n,m): shortest unweighted path from n to m
            eccentricity(n) = $\max _m distance(n,m)$
            diameter = $\min _n eccentricity(n) = \min _n \max _m distance(n,m)$
        Return:
            Returns diameter of the graph.
        '''

        if self.attr['type'] is not UNDIRECTED_GRAPH:
            print('This function only works for undirected graphs')
            return
        diameter = 'infinity'
        eccentricity_n = 0
        for n in self.get_node_list():
            for m in self.get_node_list():
                path_n_m = self.search(n, destination = m, algo = 'BFS')
                if path_n_m is None:
                    # this indicates there is no path from n to m, no diameter
                    # is defined, since the graph is not connected, return
                    # 'infinity'
                    return 'infinity'
                distance_n_m = len(path_n_m)-1
                if distance_n_m > eccentricity_n:
                    eccentricity_n = distance_n_m
            if diameter == 'infinity' or eccentricity_n > diameter:
                diameter = eccentricity_n
        return diameter

    def create_cluster(self, node_list, cluster_attrs={}, node_attrs={}):
        '''
        API:
            create_cluster(self, node_list, cluster_attrs, node_attrs)
        Description:
            Creates a cluster from the node given in the node list.
        Input:
            node_list: List of nodes in the cluster.
            cluster_attrs: Dictionary of cluster attributes, see Dot language
            grammer documentation for details.
            node_attrs: Dictionary of node attributes. It will overwrite
            previous attributes of the nodes in the cluster.
        Post:
            A cluster will be created. Attributes of the nodes in the cluster
            may change.
        '''
        if 'name' in cluster_attrs:
            if 'name' in self.cluster:
                raise Exception('A cluster with name %s already exists!' %cluster_attrs['name'])
            else:
                name = cluster_attrs['name']
        else:
            name = 'c%d' %self.attr['cluster_count']
            self.attr['cluster_count'] += 1
            cluster_attrs['name'] = name
        #cluster_attrs['name'] =
        self.cluster[name] = {'node_list':node_list,
                              'attrs':copy.deepcopy(cluster_attrs),
                              'node_attrs':copy.deepcopy(node_attrs)}


class DisjointSet(Graph):
    '''
    Disjoint set data structure. Inherits Graph class.
    '''
    def __init__(self, optimize = True, **attrs):
        '''
        API:
            __init__(self, optimize = True, **attrs):
        Description:
            Class constructor.
        Input:
            optimize: Optimizes find() if True.
            attrs: Graph attributes.
        Post:
            self.optimize will be updated.
        '''
        attrs['type'] = DIRECTED_GRAPH
        Graph.__init__(self, **attrs)
        self.sizes = {}
        self.optimize = optimize

    def add(self, aList):
        '''
        API:
            add(self, aList)
        Description:
            Adds items in the list to the set.
        Input:
            aList: List of items.
        Post:
            self.sizes will be updated.
        '''
        self.add_node(aList[0])
        for i in range(1, len(aList)):
            self.add_edge(aList[i], aList[0])
        self.sizes[aList[0]] = len(aList)

    def union(self, i, j):
        '''
        API:
            union(self, i, j):
        Description:
            Finds sets of i and j and unites them.
        Input:
            i: Item.
            j: Item.
        Post:
            self.sizes will be updated.
        '''
        roots = (self.find(i), self.find(j))
        if roots[0] == roots[1]:
            return False
        if self.sizes[roots[0]] <= self.sizes[roots[1]] or not self.optimize:
            self.add_edge(roots[0], roots[1])
            self.sizes[roots[1]] += self.sizes[roots[0]]
            return True
        else:
            self.add_edge(roots[1], roots[0])
            self.sizes[roots[0]] += self.sizes[roots[1]]
            return True

    def find(self, i):
        '''
        API:
            find(self, i)
        Description:
            Returns root of set that has i.
        Input:
            i: Item.
        Return:
            Returns root of set that has i.
        '''
        current = i
        edge_list = []
        while len(self.get_neighbors(current)) != 0:
            successor = self.get_neighbors(current)[0]
            edge_list.append((current, successor))
            current = successor
        if self.optimize:
            for e in edge_list:
                if e[1] != current:
                    self.del_edge((e[0], e[1]))
                    self.add_edge(e[0], current)
        return current


if __name__ == '__main__':
    G = Graph(type = UNDIRECTED_GRAPH, splines = 'true', K = 1.5)
    #G.random(numnodes = 20, Euclidean = True, seedInput = 11,
    #         add_labels = False,
    #         scale = 10,
    #         scale_cost = 10,
    #         #degree_range = (2, 4),
    #         #length_range = (1, 10)
    #         )
    #page_ranks = sorted(G.page_rank().iteritems(), key=operator.itemgetter(1))
    #page_ranks.reverse()
    #for i in page_ranks:
    #    print i    #G = Graph(type = UNDIRECTED_GRAPH, splines = 'true', K = 1.5)
    G.random(numnodes = 10, Euclidean = True, seedInput = 13,
             add_labels = True,
             scale = 10,
             scale_cost = 10,
             #degree_range = (2, 4),
             #length_range = (1, 10)
             )
    G.set_display_mode('matplotlib')
    G.display()
    #G.dfs(0)
    G.search(0, display = 'matplotlib', algo = 'Prim')
    #G.minimum_spanning_tree_kruskal()

Functions

def handle_close(evt)
Expand source code
def handle_close(evt):
    print('Figure closed. Exiting!')
    plt.close('all')

Classes

class DisjointSet (optimize=True, **attrs)

Disjoint set data structure. Inherits Graph class.

Api

init(self, optimize = True, **attrs):

Description

Class constructor.

Input

optimize: Optimizes find() if True. attrs: Graph attributes.

Post

self.optimize will be updated.

Expand source code
class DisjointSet(Graph):
    '''
    Disjoint set data structure. Inherits Graph class.
    '''
    def __init__(self, optimize = True, **attrs):
        '''
        API:
            __init__(self, optimize = True, **attrs):
        Description:
            Class constructor.
        Input:
            optimize: Optimizes find() if True.
            attrs: Graph attributes.
        Post:
            self.optimize will be updated.
        '''
        attrs['type'] = DIRECTED_GRAPH
        Graph.__init__(self, **attrs)
        self.sizes = {}
        self.optimize = optimize

    def add(self, aList):
        '''
        API:
            add(self, aList)
        Description:
            Adds items in the list to the set.
        Input:
            aList: List of items.
        Post:
            self.sizes will be updated.
        '''
        self.add_node(aList[0])
        for i in range(1, len(aList)):
            self.add_edge(aList[i], aList[0])
        self.sizes[aList[0]] = len(aList)

    def union(self, i, j):
        '''
        API:
            union(self, i, j):
        Description:
            Finds sets of i and j and unites them.
        Input:
            i: Item.
            j: Item.
        Post:
            self.sizes will be updated.
        '''
        roots = (self.find(i), self.find(j))
        if roots[0] == roots[1]:
            return False
        if self.sizes[roots[0]] <= self.sizes[roots[1]] or not self.optimize:
            self.add_edge(roots[0], roots[1])
            self.sizes[roots[1]] += self.sizes[roots[0]]
            return True
        else:
            self.add_edge(roots[1], roots[0])
            self.sizes[roots[0]] += self.sizes[roots[1]]
            return True

    def find(self, i):
        '''
        API:
            find(self, i)
        Description:
            Returns root of set that has i.
        Input:
            i: Item.
        Return:
            Returns root of set that has i.
        '''
        current = i
        edge_list = []
        while len(self.get_neighbors(current)) != 0:
            successor = self.get_neighbors(current)[0]
            edge_list.append((current, successor))
            current = successor
        if self.optimize:
            for e in edge_list:
                if e[1] != current:
                    self.del_edge((e[0], e[1]))
                    self.add_edge(e[0], current)
        return current

Ancestors

Methods

def add(self, aList)

Api

add(self, aList)

Description

Adds items in the list to the set.

Input

aList: List of items.

Post

self.sizes will be updated.

Expand source code
def add(self, aList):
    '''
    API:
        add(self, aList)
    Description:
        Adds items in the list to the set.
    Input:
        aList: List of items.
    Post:
        self.sizes will be updated.
    '''
    self.add_node(aList[0])
    for i in range(1, len(aList)):
        self.add_edge(aList[i], aList[0])
    self.sizes[aList[0]] = len(aList)
def find(self, i)

Api

find(self, i)

Description

Returns root of set that has i.

Input

i: Item.

Return

Returns root of set that has i.

Expand source code
def find(self, i):
    '''
    API:
        find(self, i)
    Description:
        Returns root of set that has i.
    Input:
        i: Item.
    Return:
        Returns root of set that has i.
    '''
    current = i
    edge_list = []
    while len(self.get_neighbors(current)) != 0:
        successor = self.get_neighbors(current)[0]
        edge_list.append((current, successor))
        current = successor
    if self.optimize:
        for e in edge_list:
            if e[1] != current:
                self.del_edge((e[0], e[1]))
                self.add_edge(e[0], current)
    return current
def union(self, i, j)

Api

union(self, i, j):

Description

Finds sets of i and j and unites them.

Input

i: Item. j: Item.

Post

self.sizes will be updated.

Expand source code
def union(self, i, j):
    '''
    API:
        union(self, i, j):
    Description:
        Finds sets of i and j and unites them.
    Input:
        i: Item.
        j: Item.
    Post:
        self.sizes will be updated.
    '''
    roots = (self.find(i), self.find(j))
    if roots[0] == roots[1]:
        return False
    if self.sizes[roots[0]] <= self.sizes[roots[1]] or not self.optimize:
        self.add_edge(roots[0], roots[1])
        self.sizes[roots[1]] += self.sizes[roots[0]]
        return True
    else:
        self.add_edge(roots[1], roots[0])
        self.sizes[roots[0]] += self.sizes[roots[1]]
        return True

Inherited members

class Graph (**attr)

Graph class, implemented using adjacency list. See GIMPy README for more information.

API: init(self, **attrs) Description: Graph class constructor. Sets attributes using argument.

Input

**attrs: Graph attributes.

Post

Sets following attributes using **attrs; self.attr, self.graph_type. Creates following initial attributes; self.neighbors, self.in_neighbors, self.nodes, self.out_neighbors, self.cluster

Expand source code
class Graph(object):
    '''
    Graph class, implemented using adjacency list. See GIMPy README for more
    information.
    '''
    def __init__(self, **attr):
        '''
        API: __init__(self, **attrs)
        Description:
        Graph class constructor. Sets attributes using argument.
        Input:
            **attrs: Graph attributes.
        Post:
            Sets following attributes using **attrs; self.attr,
            self.graph_type. Creates following initial attributes;
            self.neighbors, self.in_neighbors, self.nodes, self.out_neighbors,
            self.cluster
        '''
        # graph attributes
        self.attr = copy.deepcopy(DEFAULT_GRAPH_ATTRIBUTES)
        # set attributes using constructor
        for a in attr:
            self.attr[a] = attr[a]
        # set name
        if 'name' in self.attr:
            self.name = self.attr['name']
        else:
            self.name = 'G'
        # edge attributes
        self.edge_attr = dict()
        # we treat type attribute and keep it in a separate class attribute
        if 'type' in self.attr:
            self.graph_type = self.attr['type']
        else:
            self.graph_type = UNDIRECTED_GRAPH
        # adjacency list of nodes, it is a dictionary of lists
        self.neighbors = {}
        # if the graph is undirected we do not need in_neighbor
        if self.graph_type is DIRECTED_GRAPH:
            self.in_neighbors = {}
        self.nodes = {}
        self.edge_connect_symbol = EDGE_CONNECT_SYMBOL[self.graph_type]
        self.out_neighbors = self.neighbors
        if 'display' not in self.attr:
            self.attr['display']='off'
        if 'layout' not in self.attr:
            self.attr['layout'] = 'fdp'
        self.attr['cluster_count'] = 0
        self.cluster = {}

    def __repr__(self):
        '''
        API: __repr__(self)
        Description:
        Returns string representation of the graph.
        Return:
            String representation of the graph.
        '''
        data = str()
        for n in self.nodes:
            data += str(n)
            data += ' -> '
            data += self.neighbors[n].__repr__()
            data += '\n'
        data = data[:-1]
        return data

    def __contains__(self, item):
        '''
        API: __contains__(self, item)
        Description:
        Return true if item is in graph. item can be a node name or a tuple
        that represents an edge.
        Return:
            True if item is in graph.
        '''
        if isinstance(item, tuple):
            name1 = item[0]
            name2 = item[1]
            if self.graph_type is DIRECTED_GRAPH:
                return (name1, name2) in self.edge_attr
            else:
                return ((name1, name2) in self.edge_attr or
                        (name2, name1) in self.edge_attr)
        else:
            return item in self.nodes

    def add_node(self, name, **attr):
        '''
        API: add_node(self, name, **attr)
        Description:
        Adds node to the graph.
        Pre:
            Graph should not contain a node with this name. We do not allow
            multiple nodes with the same name.
        Input:
            name: Name of the node.
            attr: Node attributes.
        Post:
            self.neighbors, self.nodes and self.in_neighbors are updated.
        Return:
            Node (a Node class instance) added to the graph.
        '''
        if name in self.neighbors:
            raise MultipleNodeException
        self.neighbors[name] = list()
        if self.graph_type is DIRECTED_GRAPH:
            self.in_neighbors[name] = list()
        self.nodes[name] = Node(name, **attr)
        return self.nodes[name]

    def del_node(self, name):
        '''
        API: del_node(self, name)
        Description:
        Removes node from Graph.
        Input:
            name: Name of the node.
        Pre:
            Graph should contain a node with this name.
        Post:
            self.neighbors, self.nodes and self.in_neighbors are updated.
        '''
        if name not in self.neighbors:
            raise Exception('Node %s does not exist!' %str(name))
        for n in self.neighbors[name]:
            del self.edge_attr[(name, n)]
            if self.graph_type == UNDIRECTED_GRAPH:
                self.neighbors[n].remove(name)
            else:
                self.in_neighbors[n].remove(name)
        if self.graph_type is DIRECTED_GRAPH:
            for n in self.in_neighbors[name]:
                del self.edge_attr[(n, name)]
                self.neighbors[n].remove(name)
        del self.neighbors[name]
        del self.in_neighbors[name]
        del self.nodes[name]

    def add_edge(self, name1, name2, **attr):
        '''
        API: add_edge(self, name1, name2, **attr)
        Description:
        Adds edge to the graph. Sets edge attributes using attr argument.
        Input:
            name1: Name of the source node (if directed).
            name2: Name of the sink node (if directed).
            attr: Edge attributes.
        Pre:
            Graph should not already contain this edge. We do not allow
            multiple edges with same source and sink nodes.
        Post:
            self.edge_attr is updated.
            self.neighbors, self.nodes and self.in_neighbors are updated if
            graph was missing at least one of the nodes.
        '''
        if (name1, name2) in self.edge_attr:
            raise MultipleEdgeException
        if self.graph_type is UNDIRECTED_GRAPH and (name2,name1) in self.edge_attr:
            raise MultipleEdgeException
        self.edge_attr[(name1,name2)] = copy.deepcopy(DEFAULT_EDGE_ATTRIBUTES)
        for a in attr:
            self.edge_attr[(name1,name2)][a] = attr[a]
        if name1 not in self.nodes:
            self.add_node(name1)
        if name2 not in self.nodes:
            self.add_node(name2)
        self.neighbors[name1].append(name2)
        if self.graph_type is UNDIRECTED_GRAPH:
            self.neighbors[name2].append(name1)
        else:
            self.in_neighbors[name2].append(name1)

    def del_edge(self, e):
        '''
        API: del_edge(self, e)
        Description:
        Removes edge from graph.
        Input:
            e: Tuple that represents edge, in (source,sink) form.
        Pre:
            Graph should contain this edge.
        Post:
            self.edge_attr, self.neighbors and self.in_neighbors are updated.
        '''
        if self.graph_type is DIRECTED_GRAPH:
            try:
                del self.edge_attr[e]
            except KeyError:
                raise Exception('Edge %s does not exists!' %str(e))
            self.neighbors[e[0]].remove(e[1])
            self.in_neighbors[e[1]].remove(e[0])
        else:
            try:
                del self.edge_attr[e]
            except KeyError:
                try:
                    del self.edge_attr[(e[1],e[0])]
                except KeyError:
                    raise Exception('Edge %s does not exists!' %str(e))
            self.neighbors[e[0]].remove(e[1])
            self.neighbors[e[1]].remove(e[0])

    def get_node(self, name):
        '''
        API: get_node(self, name)
        Description:
        Returns node object with the provided name.
        Input:
            name: Name of the node.
        Return:
            Returns node object if node exists, returns None otherwise.
        '''
        if name in self.nodes:
            return self.nodes[name]
        else:
            return None

    def get_edge_cost(self, edge):
        '''
        API: get_edge_cost(self, edge)
        Description:
        Returns cost attr of edge, required for minimum_spanning_tree_kruskal().
        Input:
            edge: Tuple that represents edge, in (source,sink) form.
        Return:
            Returns cost attribute value of the edge.
        '''
        return self.get_edge_attr(edge[0], edge[1], 'cost')

    def check_edge(self, name1, name2):
        '''
        API: check_edge(self, name1, name2)
        Description:
        Return True if edge exists, False otherwise.
        Input:
            name1: name of the source node.
            name2: name of the sink node.
        Return:
            Returns True if edge exists, False otherwise.
        '''
        if self.graph_type is DIRECTED_GRAPH:
            return (name1, name2) in self.edge_attr
        else:
            return ((name1, name2) in self.edge_attr or
                    (name2, name1) in self.edge_attr)

    def get_node_list(self):
        '''
        API: get_node_list(self)
        Description:
        Returns node list.
        Return:
            List of nodes.
        '''
        return list(self.neighbors.keys())

    def get_edge_list(self):
        '''
        API: get_edge_list(self)
        Description:
        Returns edge list.
        Return:
            List of edges, edges are tuples and in (source,sink) format.
        '''
        return list(self.edge_attr.keys())

    def get_node_num(self):
        '''
        API: get_node_num(self)
        Description:
        Returns number of nodes.
        Return:
            Number of nodes.
        '''
        return len(self.neighbors)

    def get_edge_num(self):
        '''
        API: get_edge_num(self)
        Description:
        Returns number of edges.
        Return:
            Number of edges.
        '''
        return len(self.edge_attr)

    def get_node_attr(self, name, attr):
        '''
        API: get_node_attr(self, name, attr)
        Description:
        Returns attribute attr of given node.
        Input:
            name: Name of node.
            attr: Attribute of node.
        Pre:
            Graph should have this node.
        Return:
            Value of node attribute attr.
        '''
        return self.get_node(name).get_attr(attr)

    def get_edge_attr(self, n, m, attr):
        '''
        API: get_edge_attr(self, n, m, attr)
        Description:
        Returns attribute attr of edge (n,m).
        Input:
            n: Source node name.
            m: Sink node name.
            attr: Attribute of edge.
        Pre:
            Graph should have this edge.
        Return:
            Value of edge attribute attr.
        '''
        if self.graph_type is DIRECTED_GRAPH:
            return self.edge_attr[(n,m)][attr]
        else:
            try:
                return self.edge_attr[(n,m)][attr]
            except KeyError:
                return self.edge_attr[(m,n)][attr]

    def set_node_attr(self, name, attr, value):
        '''
        API: set_node_attr(self, name, attr)
        Description:
        Sets attr attribute of node named name to value.
        Input:
            name: Name of node.
            attr: Attribute of node to set.
        Pre:
            Graph should have this node.
        Post:
            Node attribute will be updated.
        '''
        self.get_node(name).set_attr(attr, value)

    def set_edge_attr(self, n, m, attr, value):
        '''
        API: set_edge_attr(self, n, m, attr, value)
        Description:
        Sets attr attribute of edge (n,m) to value.
        Input:
            n: Source node name.
            m: Sink node name.
            attr: Attribute of edge to set.
            value: New value of attribute.
        Pre:
            Graph should have this edge.
        Post:
            Edge attribute will be updated.
        '''
        if self.graph_type is DIRECTED_GRAPH:
            self.edge_attr[(n,m)][attr] = value
        else:
            try:
                self.edge_attr[(n,m)][attr] = value
            except KeyError:
                self.edge_attr[(m,n)][attr] = value

    def get_neighbors(self, name):
        '''
        API: get_neighbors(self, name)
        Description:
        Returns list of neighbors of given node.
        Input:
            name: Node name.
        Pre:
            Graph should have this node.
        Return:
            List of neighbor node names.
        '''
        return self.neighbors[name]

    def get_in_neighbors(self, name):
        '''
        API: get_in_neighbors(self, name)
        Description:
        Returns list of in neighbors of given node.
        Input:
            name: Node name.
        Pre:
            Graph should have this node.
        Return:
            List of in-neighbor node names.
        '''
        return self.in_neighbors[name]

    def get_out_neighbors(self, name):
        '''
        API: get_out_neighbors(self, name)
        Description:
        Returns list of out-neighbors of given node.
        Input:
            name: Node name.
        Pre:
            Graph should have this node.
        Return:
            List of out-neighbor node names.
        '''
        return self.neighbors[name]

    def edge_to_string(self, e):
        '''
        API: edge_to_string(self, e)
        Description:
        Return string that represents edge e in dot language.
        Input:
            e: Edge tuple in (source,sink) format.
        Pre:
            Graph should have this edge.
        Return:
            String that represents given edge.
        '''
        edge = list()
        edge.append(quote_if_necessary(str(e[0])))
        edge.append(self.edge_connect_symbol)
        edge.append(quote_if_necessary(str(e[1])))
        # return if there is nothing in self.edge_attr[e]
        if len(self.edge_attr[e]) == 0:
            return ''.join(edge)
        edge.append('  [')
        for a in self.edge_attr[e]:
            edge.append(a)
            edge.append('=')
            edge.append(quote_if_necessary(str(self.edge_attr[e][a])))
            edge.append(', ')
        edge = edge[:-1]
        edge.append(']')
        return ''.join(edge)

    def to_string(self):
        '''
        API: to_string(self)
        Description:
        This method is based on pydot Graph class with the same name.
        Returns a string representation of the graph in dot language.
        It will return the graph and all its subelements in string form.
        Return:
            String that represents graph in dot language.
        '''
        graph = list()
        processed_edges = {}
        graph.append('%s %s {\n' %(self.graph_type, self.name))
        for a in self.attr:
            if a not in GRAPH_ATTRIBUTES:
                continue
            val = self.attr[a]
            if val is not None:
                graph.append( '%s=%s' % (a, quote_if_necessary(val)) )
            else:
                graph.append(a)
            graph.append( ';\n' )
        # clusters
        for c in self.cluster:
            graph.append('subgraph cluster_%s {\n' %c)
            for a in self.cluster[c]['attrs']:
                if a=='label':
                    graph.append(a+'='+quote_if_necessary(self.cluster[c]['attrs'][a])+';\n')
                    continue
                graph.append(a+'='+self.cluster[c]['attrs'][a]+';\n')
            if len(self.cluster[c]['node_attrs'])!=0:
                graph.append('node [')
            for a in self.cluster[c]['node_attrs']:
                graph.append(a+'='+self.cluster[c]['node_attrs'][a])
                graph.append(',')
            if len(self.cluster[c]['node_attrs'])!=0:
                graph.pop()
                graph.append('];\n')
            # process cluster nodes
            for n in self.cluster[c]['node_list']:
                data = self.get_node(n).to_string()
                graph.append(data + ';\n')
            # process cluster edges
            for n in self.cluster[c]['node_list']:
                for m in self.cluster[c]['node_list']:
                    if self.check_edge(n,m):
                        data = self.edge_to_string((n,m))
                        graph.append(data + ';\n')
                        processed_edges[(n,m)]=None
            graph.append('}\n')
        # process remaining (non-cluster) nodes
        for n in self.neighbors:
            for c in self.cluster:
                if n in self.cluster[c]['node_list']:
                    break
            else:
                data = self.get_node(n).to_string()
                graph.append(data + ';\n')
        # process edges
        for e in self.edge_attr:
            if e in processed_edges:
                continue
            data = self.edge_to_string(e)
            graph.append(data + ';\n')
        graph.append( '}\n' )
        return ''.join(graph)

    def label_components(self, display = None):
        '''
        API: label_components(self, display=None)
        Description:
        This method labels the nodes of an undirected graph with component
        numbers so that each node has the same label as all nodes in the
        same component. It will display the algortihm if display argument is
        provided.
        Input:
            display: display method.
        Pre:
            self.graph_type should be UNDIRECTED_GRAPH.
        Post:
            Nodes will have 'component' attribute that will have component
            number as value.
        '''
        if self.graph_type == DIRECTED_GRAPH:
            raise Exception("label_components only works for ",
                            "undirected graphs")
        self.num_components = 0
        for n in self.get_node_list():
            self.get_node(n).set_attr('component', None)
        for n in self.neighbors:
            self.get_node(n).set_attr('label', '-')
        for n in self.get_node_list():
            if self.get_node(n).get_attr('component') == None:
                self.search(n, display=display,
                            component=self.num_components, algo='DFS')
                self.num_components += 1

    def tarjan(self):
        '''
        API: tarjan(self)
        Description:
        Implements Tarjan's algorithm for determining strongly connected set of
        nodes.
        Pre:
            self.graph_type should be DIRECTED_GRAPH.
        Post:
            Nodes will have 'component' attribute that will have component
            number as value. Changes 'index' attribute of nodes.
        '''
        index = 0
        component = 0
        q = []
        for n in self.get_node_list():
            if self.get_node_attr(n, 'index') is None:
                index, component = self.strong_connect(q, n, index, component)

    def strong_connect(self, q, node, index, component):
        '''
        API: strong_connect (self, q, node, index, component)
        Description:
        Used by tarjan method. This method should not be called directly by
        user.
        Input:
            q: Node list.
            node: Node that is being connected to nodes in q.
            index: Index used by tarjan method.
            component: Current component number.
        Pre:
            Should be called by tarjan and itself (recursive) only.
        Post:
            Nodes will have 'component' attribute that will have component
            number as value. Changes 'index' attribute of nodes.
        Return:
            Returns new index and component numbers.
        '''
        self.set_node_attr(node, 'index', index)
        self.set_node_attr(node, 'lowlink', index)
        index += 1
        q.append(node)
        for m in self.get_neighbors(node):
            if self.get_node_attr(m, 'index') is None:
                index, component = self.strong_connect(q, m, index, component)
                self.set_node_attr(node, 'lowlink',
                                   min([self.get_node_attr(node, 'lowlink'),
                                        self.get_node_attr(m, 'lowlink')]))
            elif m in q:
                self.set_node_attr(node, 'lowlink',
                                   min([self.get_node_attr(node, 'lowlink'),
                                        self.get_node_attr(m, 'index')]))
        if self.get_node_attr(node, 'lowlink') == self.get_node_attr(node, 'index'):
            m = q.pop()
            self.set_node_attr(m, 'component', component)
            while (node!=m):
                m = q.pop()
                self.set_node_attr(m, 'component', component)
            component += 1
            self.num_components = component

        return (index, component)

    def label_strong_component(self):
        '''
        API: label_strong_component(self)
        Description:
        This method labels the nodes of a directed graph with component
        numbers so that each node has the same label as all nodes in the
        same component.
        Pre:
            self.graph_type should be DIRECTED_GRAPH.
        Post:
            Nodes will have 'component' attribute that will have component
            number as value. Changes 'index' attribute of nodes.
        '''
        self.num_components = 0
        self.tarjan()

    def dfs(self, root, disc_count = 0, finish_count = 1, component = None,
            transpose = False, display = None, pred = None):
        '''
        API: dfs(self, root, disc_count = 0, finish_count = 1, component=None,
            transpose=False)
        Description:
        Make a depth-first search starting from node with name root.
        Input:
            root: Starting node name.
            disc_count: Discovery time.
            finish_count: Finishing time.
            component: component number.
            transpose: Goes in the reverse direction along edges if transpose
            is True.
        Post:
            Nodes will have 'component' attribute that will have component
            number as value. Updates 'disc_time' and 'finish_time' attributes
            of nodes which represents discovery time and finishing time.
        Return:
            Returns a tuple that has discovery time and finish time of the
            last node in the following form (disc_time,finish_time).
        '''
        if pred == None:
            pred = {}
        if display == None:
            display = self.attr['display']
        else:
            self.set_display_mode(display)
        neighbors = self.neighbors
        if self.graph_type == DIRECTED_GRAPH and transpose:
            neighbors = self.in_neighbors
        self.get_node(root).set_attr('component', component)
        disc_count += 1
        self.get_node(root).set_attr('disc_time', disc_count)
        self.get_node(root).set_attr('label', str(disc_count)+',-')
        self.get_node(root).set_attr('color', 'blue')
        if root in pred:
            self.set_edge_attr(pred[root], root, 'color', 'green')
        self.display()
        if transpose:
            fTime = []
            for n in neighbors[root]:
                fTime.append((n,self.get_node(n).get_attr('finish_time')))
            neighbor_list = sorted(fTime, key=operator.itemgetter(1))
            neighbor_list = list(t[0] for t in neighbor_list)
            neighbor_list.reverse()
        else:
            neighbor_list = neighbors[root]
        for i in neighbor_list:
            if not transpose:
                if self.get_node(i).get_attr('disc_time') is None:
                    pred[i] = root
                    disc_count, finish_count = self.dfs(i, disc_count,
                                                        finish_count,
                                                        component, transpose,
                                                        pred = pred)
            else:
                if self.get_node(i).get_attr('component') is None:
                    disc_count, finish_count = self.dfs(i, disc_count,
                                                        finish_count,
                                                        component, transpose,
                                                        pred = pred)
        self.get_node(root).set_attr('finish_time', finish_count)
        d_time = self.get_node(root).get_attr('disc_time')
        label = '"' + str(d_time) + ',' + str(finish_count) + '"'
        self.get_node(root).set_attr('label', label)
        self.get_node(root).set_attr('color', 'green')
        self.display()
        finish_count += 1
        return disc_count, finish_count

    def bfs(self, root, display = None, component = None):
        '''
        API: bfs(self, root, display = None, component=None)
        Description:
        Make a breadth-first search starting from node with name root.
        Input:
            root: Starting node name.
            display: display method.
            component: component number.
        Post:
            Nodes will have 'component' attribute that will have component
            number as value.
        '''
        self.search(root, display = display, component = component, q = Queue())

    def search(self, source, destination = None, display = None,
               component = None, q = None,
               algo = 'DFS', reverse = False, **kargs):
        '''
        API: search(self, source, destination = None, display = None,
               component = None, q = Stack(),
               algo = 'DFS', reverse = False, **kargs)
        Description:
        Generic search method. Changes behavior (dfs,bfs,dijkstra,prim)
        according to algo argument.
        if destination is not specified:
           This method determines all nodes reachable from "source" ie. creates
           precedence tree and returns it (dictionary).
        if destionation is given:
           If there exists a path from "source" to "destination" it will return
           list of the nodes is this path. If there is no such path, it will
           return the precedence tree constructed from source (dictionary).
        Optionally, it marks all nodes reachable from "source" with a component
        number. The variable "q" determines the order in which the nodes are
        searched.
        Input:
            source: Search starts from node with this name.
            destination: Destination node name.
            display: Display method.
            algo: Algortihm that specifies search. Available algortihms are
            'DFS', 'BFS', 'Dijkstra' and 'Prim'.
            reverse: Search goes in reverse arc directions if True.
            kargs: Additional keyword arguments.
        Post:
            Nodes will have 'component' attribute that will have component
            number as value (if component argument provided). Color attribute
            of nodes and edges may change.
        Return:
            Returns predecessor tree in dictionary form if destination is
            not specified, returns list of node names in the path from source
            to destionation if destionation is specified and there is a path.
            If there is no path returns predecessor tree in dictionary form.
            See description section.
        '''
        if display == None:
            display = self.attr['display']
        else:
            self.set_display_mode(display)
        if algo == 'DFS':
            if q is None:
                q = Stack()
            self.get_node(source).set_attr('component', component)
        elif algo == 'BFS' or algo == 'UnweightedSPT':
            if q is None:
                q = Queue()
            self.get_node(source).set_attr('component', component)
        elif algo == 'Dijkstra' or algo == 'Prim':
            if q is None:
                q = PriorityQueue()
        else:
            print("Unknown search algorithm...exiting")
            return
        neighbors = self.neighbors
        if self.graph_type == DIRECTED_GRAPH and reverse:
            neighbors = self.in_neighbors
        for i in self.get_node_list():
            self.get_node(i).set_attr('label', '-')
            self.get_node(i).attr.pop('priority', None)
            self.get_node(i).set_attr('distance', None)
            self.get_node(i).set_attr('color', 'black')
            for j in neighbors[i]:
                if reverse:
                    self.set_edge_attr(j, i, 'color', 'black')
                else:
                    self.set_edge_attr(i, j, 'color', 'black')
        self.display()
        pred = {}
        self.process_edge_search(None, source, pred, q, component, algo,
                                 **kargs)
        found = True
        if source != destination:
            found = False
        while not q.isEmpty() and not found:
            current = q.peek()
            if self.get_node(current).get_attr('color') == 'green':
                q.remove(current)
                continue
            self.process_node_search(current, q, **kargs)
            self.get_node(current).set_attr('color', 'blue')
            if current != source:
                if reverse:
                    self.set_edge_attr(current, pred[current], 'color', 'green')
                else:
                    self.set_edge_attr(pred[current], current, 'color', 'green')
            if current == destination:
                found = True
                break
            self.display()
            for n in neighbors[current]:
                if self.get_node(n).get_attr('color') != 'green':
                    if reverse:
                        self.set_edge_attr(n, current, 'color', 'yellow')
                    else:
                        self.set_edge_attr(current, n, 'color', 'yellow')
                    self.display()
                    self.process_edge_search(current, n, pred, q, component,
                                             algo, **kargs)
                    if reverse:
                        self.set_edge_attr(n, current, 'color', 'black')
                    else:
                        self.set_edge_attr(current, n, 'color', 'black')
            q.remove(current)
            self.get_node(current).set_attr('color', 'green')
            self.display()
        if found:
            path = [destination]
            current = destination
            while current != source:
                path.insert(0, pred[current])
                current = pred[current]
            return path
        if destination == None:
            return pred
        else:
            return None

    def process_node_search(self, node, q, **kwargs):
        '''
        API: process_node_search(self, node, q, **kwargs)
        Description:
        Used by search() method. Process nodes along the search. Should not be
        called by user directly.
        Input:
            node: Name of the node being processed.
            q: Queue data structure.
            kwargs: Keyword arguments.
        Post:
            'priority' attribute of the node may get updated.
        '''
        if isinstance(q, PriorityQueue):
            self.get_node(node).set_attr('priority', q.get_priority(node))

    def process_edge_dijkstra(self, current, neighbor, pred, q, component):
        '''
        API: process_edge_dijkstra(self, current, neighbor, pred, q, component)
        Description:
        Used by search() method if the algo argument is 'Dijkstra'. Processes
        edges along Dijkstra's algorithm. User does not need to call this
        method directly.
        Input:
            current: Name of the current node.
            neighbor: Name of the neighbor node.
            pred: Predecessor tree.
            q: Data structure that holds nodes to be processed in a queue.
            component: component number.
        Post:
            'color' attribute of nodes and edges may change.
        '''
        if current is None:
            self.get_node(neighbor).set_attr('color', 'red')
            self.get_node(neighbor).set_attr('label', 0)
            q.push(neighbor, 0)
            self.display()
            self.get_node(neighbor).set_attr('color', 'black')
            return
        new_estimate = (q.get_priority(current) +
                        self.get_edge_attr(current, neighbor, 'cost'))
        if neighbor not in pred or new_estimate < q.get_priority(neighbor):
            pred[neighbor] = current
            self.get_node(neighbor).set_attr('color', 'red')
            self.get_node(neighbor).set_attr('label', new_estimate)
            q.push(neighbor, new_estimate)
            self.display()
            self.get_node(neighbor).set_attr('color', 'black')

    def process_edge_prim(self, current, neighbor, pred, q, component):
        '''
        API: process_edge_prim(self, current, neighbor, pred, q, component)
        Description:
        Used by search() method if the algo argument is 'Prim'. Processes
        edges along Prim's algorithm. User does not need to call this method
        directly.
        Input:
            current: Name of the current node.
            neighbor: Name of the neighbor node.
            pred: Predecessor tree.
            q: Data structure that holds nodes to be processed in a queue.
            component: component number.
        Post:
            'color' attribute of nodes and edges may change.
        '''
        if current is None:
            self.get_node(neighbor).set_attr('color', 'red')
            self.get_node(neighbor).set_attr('label', 0)
            q.push(neighbor, 0)
            self.display()
            self.get_node(neighbor).set_attr('color', 'black')
            return
        new_estimate = self.get_edge_attr(current, neighbor, 'cost')
        if not neighbor in pred or new_estimate < q.get_priority(neighbor):
            pred[neighbor] = current
            self.get_node(neighbor).set_attr('color', 'red')
            self.get_node(neighbor).set_attr('label', new_estimate)
            q.push(neighbor, new_estimate)
            self.display()
            self.get_node(neighbor).set_attr('color', 'black')

    def process_edge_search(self, current, neighbor, pred, q, component, algo,
                            **kargs):
        '''
        API: process_edge_search(self, current, neighbor, pred, q, component,
                                 algo, **kargs)
        Description:
        Used by search() method. Processes edges according to the underlying
        algortihm. User does not need to call this method directly.
        Input:
            current: Name of the current node.
            neighbor: Name of the neighbor node.
            pred: Predecessor tree.
            q: Data structure that holds nodes to be processed in a queue.
            component: component number.
            algo: Search algorithm. See search() documentation.
            kwargs: Keyword arguments.
        Post:
            'color', 'distance', 'component' attribute of nodes and edges may
            change.
        '''
        if algo == 'Dijkstra':
            return self.process_edge_dijkstra(current, neighbor, pred, q,
                                              component)
        if algo == 'Prim':
            return self.process_edge_prim(current, neighbor, pred, q,
                                          component)
        neighbor_node = self.get_node(neighbor)
        if current == None:
            neighbor_node.set_attr('distance', 0)
            if isinstance(q, PriorityQueue):
                q.push(neighbor, 0)
            else:
                q.push(neighbor)
            if component != None:
                neighbor_node.set_attr('component', component)
                neighbor_node.set_attr('label', component)
            else:
                neighbor_node.set_attr('label', 0)
            return
        if isinstance(q, PriorityQueue):
            current_priority = q.get_priority(neighbor)
            if algo == 'UnweightedSPT' or algo == 'BFS':
                priority = self.get_node(current).get_attr('distance') + 1
            if algo == 'DFS':
                priority = -self.get_node(current).get_attr('distance') - 1
            if current_priority is not None and priority >= current_priority:
                return
            q.push(neighbor, priority)
            if algo == 'UnweightedSPT' or algo == 'BFS':
                neighbor_node.set_attr('distance', priority)
            if algo == 'DFS':
                neighbor_node.set_attr('depth', -priority)
        else:
            distance = self.get_node(current).get_attr('distance') + 1
            if ((algo == 'UnweightedSPT' or algo == 'BFS') and
                neighbor_node.get_attr('distance') is not None):
                return
            neighbor_node.set_attr('distance', distance)
            neighbor_node.set_attr('label', str(distance))
            q.push(neighbor)
        pred[neighbor] = current
        neighbor_node.set_attr('color', 'red')
        if component != None:
            neighbor_node.set_attr('component', component)
            neighbor_node.set_attr('label', component)
        self.display()

    def minimum_spanning_tree_prim(self, source, display = None,
                                   q = PriorityQueue()):
        '''
        API: minimum_spanning_tree_prim(self, source, display = None,
                                        q = PriorityQueue())
        Description:
        Determines a minimum spanning tree of all nodes reachable
        from source using Prim's Algorithm.
        Input:
            source: Name of source node.
            display: Display method.
            q: Data structure that holds nodes to be processed in a queue.
        Post:
            'color', 'distance', 'component' attribute of nodes and edges may
            change.
        Return:
            Returns predecessor tree in dictionary format.
        '''
        if display == None:
            display = self.attr['display']
        else:
            self.set_display_mode(display)
        if isinstance(q, PriorityQueue):
            addToQ = q.push
            removeFromQ = q.pop
            peek = q.peek
            isEmpty = q.isEmpty
        neighbors = self.get_neighbors
        pred = {}
        addToQ(source)
        done = False
        while not isEmpty() and not done:
            current = removeFromQ()
            self.set_node_attr(current, 'color', 'blue')
            if current != source:
                self.set_edge_attr(pred[current], current, 'color', 'green')
            self.display()
            for n in neighbors(current):
                if self.get_node_attr(n, 'color') != 'green':
                    self.set_edge_attr(current, n, 'color', 'yellow')
                    self.display()
                    new_estimate = self.get_edge_attr(current, n, 'cost')
                    if not n in pred or new_estimate < peek(n)[0]:
                        pred[n] = current
                        self.set_node_attr(n, 'color', 'red')
                        self.set_node_attr(n, 'label', new_estimate)
                        addToQ(n, new_estimate)
                        self.display()
                        self.set_node_attr(n, 'color', 'black')
                    self.set_edge_attr(current, n, 'color', 'black')
            self.set_node_attr(current, 'color', 'green')
            self.display()
        return pred

    def minimum_spanning_tree_kruskal(self, display = None, components = None):
        '''
        API: minimum_spanning_tree_kruskal(self, display = None,
                                           components = None)
        Description:
        Determines a minimum spanning tree using Kruskal's Algorithm.
        Input:
            display: Display method.
            component: component number.
        Post:
            'color' attribute of nodes and edges may change.
        Return:
            Returns list of edges where edges are tuples in (source,sink)
            format.
        '''
        if display == None:
            display = self.attr['display']
        else:
            self.set_display_mode(display)
        if components is None:
            components = DisjointSet(display = display, layout = 'dot',
                                     optimize = False)
        sorted_edge_list = sorted(self.get_edge_list(), key=self.get_edge_cost)
        edges = []
        for n in self.get_node_list():
            components.add([n])
        components.display()
        for e in sorted_edge_list:
            if len(edges) == len(self.get_node_list()) - 1:
                break
            self.set_edge_attr(e[0], e[1], 'color', 'yellow')
            self.display()
            if components.union(e[0], e[1]):
                self.set_edge_attr(e[0], e[1], 'color', 'green')
                self.display()
                edges.append(e)
            else:
                self.set_edge_attr(e[0], e[1], 'color', 'black')
                self.display()
            components.display()
        return edges

    def max_flow_preflowpush(self, source, sink, algo = 'FIFO', display = None):
        '''
        API: max_flow_preflowpush(self, source, sink, algo = 'FIFO',
                                  display = None)
        Description:
        Finds maximum flow from source to sink by a depth-first search based
        augmenting path algorithm.
        Pre:
             Assumes a directed graph in which each arc has a 'capacity'
             attribute and for which there does does not exist both arcs (i,j)
             and (j,i) for any pair of nodes i and j.
        Input:
            source: Source node name.
            sink: Sink node name.
            algo: Algorithm choice, 'FIFO', 'SAP' or 'HighestLabel'.
            display: display method.
        Post:
            The 'flow' attribute of each arc gives a maximum flow.
        '''
        if display == None:
            display = self.attr['display']
        else:
            self.set_display_mode(display)
        nl = self.get_node_list()
        # set excess of all nodes to 0
        for n in nl:
            self.set_node_attr(n, 'excess', 0)
        # set flow of all edges to 0
        for e in self.edge_attr:
            self.edge_attr[e]['flow'] = 0
            if 'capacity' in self.edge_attr[e]:
                capacity = self.edge_attr[e]['capacity']
                self.edge_attr[e]['label'] = str(capacity)+'/0'
            else:
                self.edge_attr[e]['capacity'] = INF
                self.edge_attr[e]['label'] = 'INF/0'
        self.display()
        self.set_display_mode('off')
        self.search(sink, algo = 'UnweightedSPT', reverse = True)
        self.set_display_mode(display)
        disconnect = False
        for n in nl:
            if self.get_node_attr(n, 'distance') is None:
                disconnect = True
                self.set_node_attr(n, 'distance',
                                   2*len(nl) + 1)
        if disconnect:
            print('Warning: graph contains nodes not connected to the sink...')
        if algo == 'FIFO':
            q = Queue()
        elif algo == 'SAP':
            q = Stack()
        elif algo == 'HighestLabel':
            q = PriorityQueue()
        for n in self.get_neighbors(source):
            capacity = self.get_edge_attr(source, n, 'capacity')
            self.set_edge_attr(source, n, 'flow', capacity)
            self.set_node_attr(n, 'excess', capacity)
            excess = self.get_node_attr(source, 'excess')
            self.set_node_attr(source, 'excess', excess - capacity)
            if algo == 'FIFO' or algo == 'SAP':
                q.push(n)
            elif algo == 'HighestLabel':
                q.push(n, -1)
        self.set_node_attr(source, 'distance', len(nl))
        self.show_flow()
        while not q.isEmpty():
            relabel = True
            current = q.peek()
            neighbors = (self.get_neighbors(current) +
                         self.get_in_neighbors(current))
            for n in neighbors:
                pushed = self.process_edge_flow(source, sink, current, n, algo,
                                                q)
                if pushed:
                    self.show_flow()
                    if algo == 'FIFO':
                        '''With FIFO, we need to add the neighbors to the queue
                        before the current is added back in or the nodes will
                        be out of order
                        '''
                        if q.peek(n) is None and n != source and n != sink:
                            q.push(n)
                        '''Keep pushing while there is excess'''
                        if self.get_node_attr(current, 'excess') > 0:
                            continue
                    '''If we were able to push, then there we should not
                    relabel
                    '''
                    relabel = False
                    break
            q.remove(current)
            if current != sink:
                if relabel:
                    self.relabel(current)
                    self.show_flow()
                if self.get_node_attr(current, 'excess') > 0:
                    if algo == 'FIFO' or algo == 'SAP':
                        q.push(current)
                    elif algo == 'HighestLabel':
                        q.push(current, -self.get_node_attr(current,
                                                            'distance'))
            if pushed and q.peek(n) is None and n != source:
                if algo == 'SAP':
                    q.push(n)
                elif algo == 'HighestLabel':
                    q.push(n, -self.get_node_attr(n, 'distance'))

    def process_edge_flow(self, source, sink, i, j, algo, q):
        '''
        API: process_edge_flow(self, source, sink, i, j, algo, q)
        Description:
        Used by by max_flow_preflowpush() method. Processes edges along
        prefolow push.
        Input:
            source: Source node name of flow graph.
            sink: Sink node name of flow graph.
            i: Source node in the processed edge (tail of arc).
            j: Sink node in the processed edge (head of arc).
        Post:
            The 'flow' and 'excess' attributes of nodes may get updated.
        Return:
            Returns False if residual capacity is 0, True otherwise.
        '''
        if (self.get_node_attr(i, 'distance') !=
            self.get_node_attr(j, 'distance') + 1):
            return False
        if (i, j) in self.edge_attr:
            edge = (i, j)
            capacity = self.get_edge_attr(i, j, 'capacity')
            mult = 1
        else:
            edge = (j, i)
            capacity = 0
            mult = -1
        flow = mult*self.edge_attr[edge]['flow']
        residual_capacity = capacity - flow
        if residual_capacity == 0:
            return False
        excess_i = self.get_node_attr(i, 'excess')
        excess_j = self.get_node_attr(j, 'excess')
        push_amount = min(excess_i, residual_capacity)
        self.edge_attr[edge]['flow'] = mult*(flow + push_amount)
        self.set_node_attr(i, 'excess', excess_i - push_amount)
        self.set_node_attr(j, 'excess', excess_j + push_amount)
        return True

    def relabel(self, i):
        '''
        API: relabel(self, i)
        Description:
        Used by max_flow_preflowpush() method for relabelling node i.
        Input:
            i: Node that is being relabelled.
        Post:
            'distance' attribute of node i is updated.
        '''
        min_distance = 2*len(self.get_node_list()) + 1
        for j in self.get_neighbors(i):
            if (self.get_node_attr(j, 'distance') < min_distance and
                (self.get_edge_attr(i, j, 'flow') <
                 self.get_edge_attr(i, j, 'capacity'))):
                min_distance = self.get_node_attr(j, 'distance')
        for j in self.get_in_neighbors(i):
            if (self.get_node_attr(j, 'distance') < min_distance and
                self.get_edge_attr(j, i, 'flow') > 0):
                min_distance = self.get_node_attr(j, 'distance')
        self.set_node_attr(i, 'distance', min_distance + 1)

    def show_flow(self):
        '''
        API: relabel(self, i)
        Description:
        Used by max_flow_preflowpush() method for display purposed.
        Post:
            'color' and 'label' attribute of edges/nodes are updated.
        '''
        for n in self.get_node_list():
            excess = self.get_node_attr(n, 'excess')
            distance = self.get_node_attr(n, 'distance')
            self.set_node_attr(n, 'label', str(excess)+'/'+str(distance))
            for neighbor in self.get_neighbors(n):
                capacity = self.get_edge_attr(n, neighbor, 'capacity')
                flow = self.get_edge_attr(n, neighbor, 'flow')
                if capacity == INF:
                    self.set_edge_attr(n, neighbor, 'label',
                                       'INF'+'/'+str(flow))
                else:
                    self.set_edge_attr(n, neighbor, 'label',
                                       str(capacity)+'/'+str(flow))
                if capacity == flow:
                    self.set_edge_attr(n, neighbor, 'color', 'red')
                elif flow > 0:
                    self.set_edge_attr(n, neighbor, 'color', 'green')
                else:
                    self.set_edge_attr(n, neighbor, 'color', 'black')
        self.display()

    def create_residual_graph(self):
        '''
        API: create_residual_graph(self)
        Description:
        Creates and returns residual graph, which is a Graph instance
        itself.
        Pre:
            (1) Arcs should have 'flow', 'capacity' and 'cost' attribute
            (2) Graph should be a directed graph
        Return:
            Returns residual graph, which is a Graph instance.
        '''
        if self.graph_type is UNDIRECTED_GRAPH:
            raise Exception('residual graph is defined for directed graphs.')
        residual_g = Graph(type = DIRECTED_GRAPH)
        for e in self.get_edge_list():
            capacity_e = self.get_edge_attr(e[0], e[1], 'capacity')
            flow_e = self.get_edge_attr(e[0], e[1], 'flow')
            cost_e = self.get_edge_attr(e[0], e[1], 'cost')
            if flow_e > 0:
                residual_g.add_edge(e[1], e[0], cost=-1*cost_e,
                                    capacity=flow_e)
            if capacity_e - flow_e > 0:
                residual_g.add_edge(e[0], e[1], cost=cost_e,
                                    capacity=capacity_e-flow_e)
        return residual_g

    def cycle_canceling(self, display):
        '''
        API:
            cycle_canceling(self, display)
        Description:
            Solves minimum cost feasible flow problem using cycle canceling
            algorithm. Returns True when an optimal solution is found, returns
            False otherwise. 'flow' attribute values of arcs should be
            considered as junk when returned False.
        Input:
            display: Display method.
        Pre:
            (1) Arcs should have 'capacity' and 'cost' attribute.
            (2) Nodes should have 'demand' attribute, this value should be
            positive if the node is a supply node, negative if it is demand
            node and 0 if it is transhipment node.
            (3) graph should not have node 's' and 't'.
        Post:
            Changes 'flow' attributes of arcs.
        Return:
            Returns True when an optimal solution is found, returns False
            otherwise.
        '''
        # find a feasible solution to flow problem
        if not self.find_feasible_flow():
            return False
        # create residual graph
        residual_g = self.create_residual_graph()
        # identify a negative cycle in residual graph
        ncycle = residual_g.get_negative_cycle()
        # loop while residual graph has a negative cycle
        while ncycle is not None:
            # find capacity of cycle
            cap = residual_g.find_cycle_capacity(ncycle)
            # augment capacity amount along the cycle
            self.augment_cycle(cap, ncycle)
            # create residual graph
            residual_g = self.create_residual_graph()
            # identify next negative cycle
            ncycle = residual_g.get_negative_cycle()
        return True

    def find_feasible_flow(self):
        '''
        API:
            find_feasible_flow(self)
        Description:
            Solves feasible flow problem, stores solution in 'flow' attribute
            or arcs. This method is used to get an initial feasible flow for
            simplex and cycle canceling algorithms. Uses max_flow() method.
            Other max flow methods can also be used. Returns True if a feasible
            flow is found, returns False, if the problem is infeasible. When
            the problem is infeasible 'flow' attributes of arcs should be
            considered as junk.
        Pre:
            (1) 'capacity' attribute of arcs
            (2) 'demand' attribute of nodes
        Post:
            Keeps solution in 'flow' attribute of arcs.
        Return:
            Returns True if a feasible flow is found, returns False, if the
            problem is infeasible
        '''
        # establish a feasible flow in the network, to do this add nodes s and
        # t and solve a max flow problem.
        nl = self.get_node_list()
        for i in nl:
            b_i = self.get_node(i).get_attr('demand')
            if b_i > 0:
                # i is a supply node, add (s,i) arc
                self.add_edge('s', i, capacity=b_i)
            elif b_i < 0:
                # i is a demand node, add (i,t) arc
                self.add_edge(i, 't', capacity=-1*b_i)
        # solve max flow on this modified graph
        self.max_flow('s', 't', 'off')
        # check if all demand is satisfied, i.e. the min cost problem is
        # feasible or not
        for i in self.neighbors['s']:
            flow = self.get_edge_attr('s', i, 'flow')
            capacity = self.get_edge_attr('s', i, 'capacity')
            if flow != capacity:
                self.del_node('s')
                self.del_node('t')
                return False
        # remove node 's' and node 't'
        self.del_node('s')
        self.del_node('t')
        return True

    def get_layout(self):
        '''
        API:
            get_layout(self)
        Description:
        Returns layout attribute of the graph.
        Return:
            Returns layout attribute of the graph.
        '''
        return self.attr['layout']

    def set_layout(self, value):
        '''
        API:
            set_layout(self, value)
        Description:
        Sets layout attribute of the graph to value.
        Input:
            value: New value of the layout.
        '''
        self.attr['layout']=value
        if value == 'dot2tex':
            self.attr['d2tgraphstyle'] = 'every text node part/.style={align=center}'

    def write(self, file_obj, layout = None, format='png'):
        '''
        API:
            write(self, basename = 'graph', layout = None, format='png')
        Description:
        Writes graph to dist using layout and format.
        Input:
            basename: name of the file that will be written.
            layout: Dot layout for generating graph image.
            format: Image format, all format supported by Dot are wellcome.
        Post:
            File will be written to disk.
        '''
        if layout == None:
            layout = self.get_layout()
        if format == 'dot':
            file_obj.write(bytearray(self.to_string(), 'utf8'))
        else:
            out = self.create(layout, format)
            if (out != None):
                file_obj.write(out)

    def create(self, layout, format, **args):
        '''
        API:
            create(self, layout, format, **args)
        Description:
            Returns postscript representation of graph.
        Input:
            layout: Dot layout for generating graph image.
            format: Image format, all format supported by Dot are wellcome.
        Return:
            Returns postscript representation of graph.
        '''
        tmp_fd, tmp_name = tempfile.mkstemp()
        tmp_file = os.fdopen(tmp_fd, 'w')
        tmp_file.write(self.to_string())
        tmp_file.close()
        try:
            p = subprocess.run([layout, '-T'+format, tmp_name],
                               capture_output = True)
        except OSError:
            print('''Graphviz executable not found.
Graphviz must be installed and in your search path.
Please visit http://www.graphviz.org/ for information on installation.
After installation, ensure that the PATH variable is properly set.''')
            return None

        
        p.check_returncode()

        os.remove(tmp_name)
            
        if p.stderr:
            print(p.stderr)
        return p.stdout

    def display(self, highlight = None, basename = 'graph', format = 'png',
                pause = False, wait_for_click = True):
        '''
        API:
            display(self, highlight = None, basename = 'graph', format = 'png',
                pause = True)
        Description:
            Displays graph according to the arguments provided.
            Current display modes: 'off', 'file', 'PIL', 'matplotlib', 'xdot',
            'svg'
            Current layout modes: Layouts provided by graphviz ('dot', 'fdp',
            'circo', etc.) and 'dot2tex'.
            Current formats: Formats provided by graphviz ('ps', 'pdf', 'png',
            etc.)
        Input:
            highlight: List of nodes to be highlighted.
            basename: File name. It will be used if display mode is 'file'.
            format: Image format, all format supported by Dot are wellcome.
            pause: If display is 'matplotlib', window will remain open until closed.
            wait_for_click: If display is 'matplotlib', setting to True will 
            wait for a button click before proceeding. This is useful when 
            animating an algorithm. 
        Post:
            A display window will pop up or a file will be written depending
            on display mode.
        '''
        if self.attr['display'] == 'off':
            return
        if highlight != None:
            for n in highlight:
                if not isinstance(n, Node):
                    n = self.get_node(n)
                n.set_attr('color', 'red')
        if self.get_layout() == 'dot2tex':
            if self.attr['display'] != 'file':
                self.attr['display'] = 'file'
                print("Warning: Dot2tex layout can only be used with display mode 'file'")
                print("         Automatically changing setting")
        if self.attr['display'] == 'file':
            if self.get_layout() == 'dot2tex':
                try:
                    if DOT2TEX_INSTALLED:
                        if format != 'pdf' or format != 'ps':
                            print("Dot2tex only supports pdf and ps formats, falling back to pdf")
                            format = 'pdf'
                        self.set_layout('dot')
                        tex = dot2tex.dot2tex(self.to_string(), autosize=True, texmode = 'math', template = DOT2TEX_TEMPLATE)
                    else:
                        print("Error: Dot2tex not installed.")
                except:
                    try:
                        self.set_layout('dot')
                        with open(basename+'.dot', "w+b") as f:
                            self.write(f, self.get_layout(), 'dot')
                            p = subprocess.call(['dot2tex', '-t math',
                                                 basename + '.dot'])
                    except:
                        print("There was an error running dot2tex.")
                with open(basename+'.tex', 'w') as f:
                    f.write(tex)
                try: 
                    subprocess.call(['latex', basename])
                    if format == 'ps':
                        subprocess.call(['dvips', basename])
                    elif format == 'pdf':
                        subprocess.call(['pdflatex', basename])
                    self.set_layout('dot2tex')
                except:
                    print("There was an error runing latex. Is it installed?")
            else:
                with open(basename+'.'+format, "w+b") as f:
                    self.write(f, self.get_layout(), format)
            return
        elif self.attr['display'] == 'PIL':
            if PIL_INSTALLED:
                tmp_fd, tmp_name = tempfile.mkstemp()
                tmp_file = os.fdopen(tmp_fd, 'w+b')
                self.write(tmp_file, self.get_layout(), format)
                tmp_file.close()
                im = PIL_Image.open(tmp_name)
                im.show()
                os.remove(tmp_name)
            else:
                print('Error: PIL not installed. Display disabled.')
                self.attr['display'] = 'off'
        elif self.attr['display'] == 'matplotlib':
            if MATPLOTLIB_INSTALLED and PIL_INSTALLED:
                tmp_fd, tmp_name = tempfile.mkstemp()
                tmp_file = os.fdopen(tmp_fd, 'w+b')
                self.write(tmp_file, self.get_layout(), format)
                tmp_file.close()
                im = PIL_Image.open(tmp_name)
                fig = plt.figure(1)
                fig.canvas.mpl_connect('close_event', handle_close)
                plt.clf()
                plt.axis('off')
                plt.imshow(im, interpolation='bilinear' #resample=True
                           #extent = (0, 100, 0, 100)
                )
                if wait_for_click == True:
                    plt.draw()
                    try:
                        if plt.waitforbuttonpress(timeout = 10000):
                            plt.close()
                            exit()
                    except:
                        exit()
                else:
                    plt.show(block=pause)
                im.close()
                os.remove(tmp_name)
            else:
                print('Warning: Either matplotlib or Pillow is not installed. Display disabled.')
                self.attr['display'] = 'off'
        elif self.attr['display'] == 'xdot':
            if XDOT_INSTALLED:
                window = xdot.DotWindow()
                window.set_dotcode(self.to_string())
                window.connect('destroy', gtk.main_quit)
                gtk.main()
            else:
                print('Error: xdot not installed. Display disabled.')
                self.attr['display'] = 'off'
        else:
            print("Unknown display mode: ", end=' ')
            print(self.attr['display'])
        if highlight != None:
            for n in highlight:
                if not isinstance(n, Node):
                    n = self.get_node(n)
                n.set_attr('color', 'black')

    def set_display_mode(self, value):
        '''
        API:
            set_display_mode(self, value)
        Description:
            Sets display mode to value.
        Input:
            value: New display mode.
        Post:
            Display mode attribute of graph is updated.
        '''
        self.attr['display'] = value

    def max_flow(self, source, sink, display = None, algo = 'DFS'):
        '''
        API: max_flow(self, source, sink, display=None)
        Description:
        Finds maximum flow from source to sink by a depth-first search based
        augmenting path algorithm.
        Pre:
            Assumes a directed graph in which each arc has a 'capacity'
            attribute and for which there does does not exist both arcs (i,j)
            and (j, i) for any pair of nodes i and j.
        Input:
            source: Source node name.
            sink: Sink node name.
            display: Display mode.
        Post:
            The 'flow" attribute of each arc gives a maximum flow.
        '''
        if display is not None:
            old_display =  self.attr['display']
            self.attr['display'] = display
        nl = self.get_node_list()
        # set flow of all edges to 0
        for e in self.edge_attr:
            self.edge_attr[e]['flow'] = 0
            if 'capacity' in self.edge_attr[e]:
                capacity = self.edge_attr[e]['capacity']
                self.edge_attr[e]['label'] = str(capacity)+'/0'
            else:
                self.edge_attr[e]['capacity'] = INF
                self.edge_attr[e]['label'] = 'INF/0'
        while True:
            # find an augmenting path from source to sink using DFS
            if algo == 'DFS':
                q = Stack()
            elif algo == 'BFS':
                q = Queue()
            q.push(source)
            pred = {source:None}
            explored = [source]
            for n in nl:
                self.get_node(n).set_attr('color', 'black')
            for e in self.edge_attr:
                if self.edge_attr[e]['flow'] == 0:
                    self.edge_attr[e]['color'] = 'black'
                elif self.edge_attr[e]['flow']==self.edge_attr[e]['capacity']:
                    self.edge_attr[e]['color'] = 'red'
                else:
                    self.edge_attr[e]['color'] = 'green'
            self.display()
            while not q.isEmpty():
                current = q.peek()
                q.remove(current)
                if current == sink:
                    break
                out_neighbor = self.neighbors[current]
                in_neighbor = self.in_neighbors[current]
                neighbor = out_neighbor+in_neighbor
                for m in neighbor:
                    if m in explored:
                        continue
                    self.get_node(m).set_attr('color', 'yellow')
                    if m in out_neighbor:
                        self.set_edge_attr(current, m, 'color', 'yellow')
                        available_capacity = (
                            self.get_edge_attr(current, m, 'capacity')-
                            self.get_edge_attr(current, m, 'flow'))
                    else:
                        self.set_edge_attr(m, current, 'color', 'yellow')
                        available_capacity=self.get_edge_attr(m, current, 'flow')
                    self.display()
                    if available_capacity > 0:
                        self.get_node(m).set_attr('color', 'blue')
                        if m in out_neighbor:
                            self.set_edge_attr(current, m, 'color', 'blue')
                        else:
                            self.set_edge_attr(m, current, 'color', 'blue')
                        explored.append(m)
                        pred[m] = current
                        q.push(m)
                    else:
                        self.get_node(m).set_attr('color', 'black')
                        if m in out_neighbor:
                            if (self.get_edge_attr(current, m, 'flow') ==
                                self.get_edge_attr(current, m, 'capacity')):
                                self.set_edge_attr(current, m, 'color', 'red')
                            elif self.get_edge_attr(current, m, 'flow') == 0:
                                self.set_edge_attr(current, m, 'color', 'black')
                            #else:
                            #    self.set_edge_attr(current, m, 'color', 'green')
                        else:
                            if (self.get_edge_attr(m, current, 'flow') ==
                                self.get_edge_attr(m, current, 'capacity')):
                                self.set_edge_attr(m, current, 'color', 'red')
                            elif self.get_edge_attr(m, current, 'flow') == 0:
                                self.set_edge_attr(m, current, 'color', 'black')
                            #else:
                            #    self.set_edge_attr(m, current, 'color', 'green')
                    self.display()
            # if no path with positive capacity from source sink exists, stop
            if sink not in pred:
                break
            # find capacity of the path
            current = sink
            min_capacity = 'infinite'
            while True:
                m = pred[current]
                if (m,current) in self.edge_attr:
                    arc_capacity = self.edge_attr[(m, current)]['capacity']
                    flow = self.edge_attr[(m, current)]['flow']
                    potential = arc_capacity-flow
                    if min_capacity == 'infinite':
                        min_capacity = potential
                    elif min_capacity > potential:
                        min_capacity = potential
                else:
                    potential = self.edge_attr[(current, m)]['flow']
                    if min_capacity == 'infinite':
                        min_capacity = potential
                    elif min_capacity > potential:
                        min_capacity = potential
                if m == source:
                    break
                current = m
            # update flows on the path
            current = sink
            while True:
                m = pred[current]
                if (m, current) in self.edge_attr:
                    flow = self.edge_attr[(m, current)]['flow']
                    capacity = self.edge_attr[(m, current)]['capacity']
                    new_flow = flow+min_capacity
                    self.edge_attr[(m, current)]['flow'] = new_flow
                    if capacity == INF:
                        self.edge_attr[(m, current)]['label'] = \
                            'INF' + '/'+str(new_flow)
                    else:
                        self.edge_attr[(m, current)]['label'] = \
                            str(capacity)+'/'+str(new_flow)
                    if new_flow==capacity:
                        self.edge_attr[(m, current)]['color'] = 'red'
                    else:
                        self.edge_attr[(m, current)]['color'] = 'green'
                    self.display()
                else:
                    flow = self.edge_attr[(current, m)]['flow']
                    capacity = self.edge_attr[(current, m)]['capacity']
                    new_flow = flow-min_capacity
                    self.edge_attr[(current, m)]['flow'] = new_flow
                    if capacity == INF:
                        self.edge_attr[(current, m)]['label'] = \
                            'INF' + '/'+str(new_flow)
                    else:
                        self.edge_attr[(current, m)]['label'] = \
                            str(capacity)+'/'+str(new_flow)
                    if new_flow==0:
                        self.edge_attr[(current, m)]['color'] = 'red'
                    else:
                        self.edge_attr[(current, m)]['color'] = 'green'
                    self.display()
                if m == source:
                    break
                current = m
        if display is not None:
            self.attr['display'] = old_display

    def get_negative_cycle(self):
        '''
        API:
            get_negative_cycle(self)
        Description:
            Finds and returns negative cost cycle using 'cost' attribute of
            arcs. Return value is a list of nodes representing cycle it is in
            the following form; n_1-n_2-...-n_k, when the cycle has k nodes.
        Pre:
            Arcs should have 'cost' attribute.
        Return:
            Returns a list of nodes in the cycle if a negative cycle exists,
            returns None otherwise.
        '''
        nl = self.get_node_list()
        i = nl[0]
        (valid, distance, nextn) = self.floyd_warshall()
        if not valid:
            cycle = self.floyd_warshall_get_cycle(distance, nextn)
            return cycle
        else:
            return None

    def floyd_warshall(self):
        '''
        API:
            floyd_warshall(self)
        Description:
            Finds all pair shortest paths and stores it in a list of lists.
            This is possible if the graph does not have negative cycles. It will
            return a tuple with 3 elements. The first element indicates whether
            the graph has a negative cycle. It is true if the graph does not
            have a negative cycle, ie. distances found are valid shortest
            distances. The second element is a dictionary of shortest distances
            between nodes. Keys are tuple of node pairs ie. (i,j). The third
            element is a dictionary that helps to retrieve the shortest path
            between nodes. Then return value can be represented as (validity,
            distance, nextn) where nextn is the dictionary to retrieve paths.
            distance and nextn can be used as inputs to other methods to get
            shortest path between nodes.
        Pre:
            Arcs should have 'cost' attribute.
        Return:
            Returns (validity, distance, nextn). The distances are valid if
            validity is True.
        '''
        nl = self.get_node_list()
        el = self.get_edge_list()
        # initialize distance
        distance = {}
        for i in nl:
            for j in nl:
                distance[(i,j)] = 'infinity'
        for i in nl:
            distance[(i,i)] = 0
        for e in el:
            distance[(e[0],e[1])] = self.get_edge_cost(e)
        # == end of distance initialization
        # initialize next
        nextn = {}
        for i in nl:
            for j in nl:
                if i==j or distance[(i,j)]=='infinity':
                    nextn[(i,j)] = None
                else:
                    nextn[(i,j)] = i
        # == end of next initialization
        # compute shortest distance
        for k in nl:
            for i in nl:
                for j in nl:
                    if distance[(i,k)]=='infinity' or distance[(k,j)]=='infinity':
                        continue
                    elif distance[(i,j)]=='infinity':
                        distance[(i,j)] = distance[(i,k)] + distance[(k,j)]
                        nextn[(i,j)] = nextn[(k,j)]
                    elif distance[(i,j)] > distance[(i,k)] + distance[(k,j)]:
                        distance[(i,j)] = distance[(i,k)] + distance[(k,j)]
                        nextn[(i,j)] = nextn[(k,j)]
        # == end of compute shortest distance
        # check if graph has negative cycles
        for i in nl:
            if distance[(i,i)] < 0:
                # shortest distances are not valid
                # graph has negative cycle
                return (False, distance, nextn)
        return (True, distance, nextn)

    def floyd_warshall_get_path(self, distance, nextn, i, j):
        '''
        API:
            floyd_warshall_get_path(self, distance, nextn, i, j):
        Description:
            Finds shortest path between i and j using distance and nextn
            dictionaries.
        Pre:
            (1) distance and nextn are outputs of floyd_warshall method.
            (2) The graph does not have a negative cycle, , ie.
            distance[(i,i)] >=0 for all node i.
        Return:
            Returns the list of nodes on the path from i to j, ie. [i,...,j]
        '''
        if distance[(i,j)]=='infinity':
            return None
        k = nextn[(i,j)]
        path = self.floyd_warshall_get_path
        if i==k:
            return [i, j]
        else:
            return path(distance, nextn, i,k) + [k] + path(distance, nextn, k,j)

    def floyd_warshall_get_cycle(self, distance, nextn, element = None):
        '''
        API:
            floyd_warshall_get_cycle(self, distance, nextn, element = None)
        Description:
            Finds a negative cycle in the graph.
        Pre:
            (1) distance and nextn are outputs of floyd_warshall method.
            (2) The graph should have a negative cycle, , ie.
            distance[(i,i)] < 0 for some node i.
        Return:
            Returns the list of nodes on the cycle. Ex: [i,j,k,...,r], where
            (i,j), (j,k) and (r,i) are some edges in the cycle.
        '''
        nl = self.get_node_list()
        if element is None:
            for i in nl:
                if distance[(i,i)] < 0:
                    # graph has a cycle on the path from i to i.
                    element = i
                    break
            else:
                raise Exception('Graph does not have a negative cycle!')
        elif distance[(element,element)] >= 0:
            raise Exception('Graph does not have a negative cycle that contains node '+str(element)+'!')
        # find the cycle on the path from i to i.
        cycle = [element]
        k = nextn[(element,element)]
        while k not in cycle:
            cycle.insert(1,k)
            k = nextn[(element,k)]
        if k==element:
            return cycle
        else:
            return self.floyd_warshall_get_cycle(distance, nextn, k)

    def find_cycle_capacity(self, cycle):
        '''
        API:
            find_cycle_capacity(self, cycle):
        Description:
            Finds capacity of the cycle input.
        Pre:
            (1) Arcs should have 'capacity' attribute.
        Input:
            cycle: a list representing a cycle
        Return:
            Returns an integer number representing capacity of cycle.
        '''
        index = 0
        k = len(cycle)
        capacity = self.get_edge_attr(cycle[k-1], cycle[0], 'capacity')
        while index<(k-1):
            i = cycle[index]
            j = cycle[index+1]
            capacity_ij = self.get_edge_attr(i, j, 'capacity')
            if capacity > capacity_ij:
                capacity = capacity_ij
            index += 1
        return capacity

    def fifo_label_correcting(self, source):
        '''
        API:
            fifo_label_correcting(self, source)
        Description:
            finds shortest path from source to every other node. Returns
            predecessor dictionary. If graph has a negative cycle, detects it
            and returns to it.
        Pre:
            (1) 'cost' attribute of arcs. It will be used to compute shortest
            path.
        Input:
            source: source node
        Post:
            Modifies 'distance' attribute of nodes.
        Return:
            If there is no negative cycle returns to (True, pred), otherwise
            returns to (False, cycle) where pred is the predecessor dictionary
            and cycle is a list of nodes that represents cycle. It is in
            [n_1, n_2, ..., n_k] form where the cycle has k nodes.
        '''
        pred = {}
        self.get_node(source).set_attr('distance', 0)
        pred[source] = None
        for n in self.neighbors:
            if n!=source:
                self.get_node(n).set_attr('distance', 'inf')
        q = [source]
        while q:
            i = q[0]
            q = q[1:]
            for j in self.neighbors[i]:
                distance_j = self.get_node(j).get_attr('distance')
                distance_i = self.get_node(i).get_attr('distance')
                c_ij = self.get_edge_attr(i, j, 'cost')
                if distance_j > distance_i + c_ij:
                    self.get_node(j).set_attr('distance', distance_i+c_ij)
                    if j in pred:
                        pred[j] = i
                        cycle = self.label_correcting_check_cycle(j, pred)
                        if cycle is not None:
                            return (False, cycle)
                    else:
                        pred[j] = i
                    if j not in q:
                        q.append(j)
        return (True, pred)

    def label_correcting_check_cycle(self, j, pred):
        '''
        API:
            label_correcting_check_cycle(self, j, pred)
        Description:
            Checks if predecessor dictionary has a cycle, j represents the node
            that predecessor is recently updated.
        Pre:
            (1) predecessor of source node should be None.
        Input:
            j: node that predecessor is recently updated.
            pred: predecessor dictionary
        Return:
            If there exists a cycle, returns the list that represents the
            cycle, otherwise it returns to None.
        '''
        labelled = {}
        for n in self.neighbors:
            labelled[n] = None
        current = j
        while current != None:
            if labelled[current]==j:
                cycle = self.label_correcting_get_cycle(j, pred)
                return cycle
            labelled[current] = j
            current = pred[current]
        return None

    def label_correcting_get_cycle(self, j, pred):
        '''
        API:
            label_correcting_get_cycle(self, labelled, pred)
        Description:
            In label correcting check cycle it is decided pred has a cycle and
            nodes in the cycle are labelled. We will create a list of nodes
            in the cycle using labelled and pred inputs.
        Pre:
            This method should be called from label_correcting_check_cycle(),
            unless you are sure about what you are doing.
        Input:
            j: Node that predecessor is recently updated. We know that it is
            in the cycle
            pred: Predecessor dictionary that contains a cycle
        Post:
            Returns a list of nodes that represents cycle. It is in
            [n_1, n_2, ..., n_k] form where the cycle has k nodes.
        '''
        cycle = []
        cycle.append(j)
        current = pred[j]
        while current!=j:
            cycle.append(current)
            current = pred[current]
        cycle.reverse()
        return cycle

    def augment_cycle(self, amount, cycle):
        '''
        API:
            augment_cycle(self, amount, cycle):
        Description:
            Augments 'amount' unit of flow along cycle.
        Pre:
            Arcs should have 'flow' attribute.
        Inputs:
            amount: An integer representing the amount to augment
            cycle: A list representing a cycle
        Post:
            Changes 'flow' attributes of arcs.
        '''
        index = 0
        k = len(cycle)
        while index<(k-1):
            i = cycle[index]
            j = cycle[index+1]
            if (i,j) in self.edge_attr:
                flow_ij = self.edge_attr[(i,j)]['flow']
                self.edge_attr[(i,j)]['flow'] = flow_ij+amount
            else:
                flow_ji = self.edge_attr[(j,i)]['flow']
                self.edge_attr[(j,i)]['flow'] = flow_ji-amount
            index += 1
        i = cycle[k-1]
        j = cycle[0]
        if (i,j) in self.edge_attr:
            flow_ij = self.edge_attr[(i,j)]['flow']
            self.edge_attr[(i,j)]['flow'] = flow_ij+amount
        else:
            flow_ji = self.edge_attr[(j,i)]['flow']
            self.edge_attr[(j,i)]['flow'] = flow_ji-amount

    def network_simplex(self, display, pivot, root):
        '''
        API:
            network_simplex(self, display, pivot, root)
        Description:
            Solves minimum cost feasible flow problem using network simplex
            algorithm. It is recommended to use min_cost_flow(algo='simplex')
            instead of using network_simplex() directly. Returns True when an
            optimal solution is found, returns False otherwise. 'flow' attribute
            values of arcs should be considered as junk when returned False.
        Pre:
            (1) check Pre section of min_cost_flow()
        Input:
            pivot: specifies pivot rule. Check min_cost_flow()
            display: 'off' for no display, 'matplotlib' for live update of
            spanning tree.
            root: Root node for the underlying spanning trees that will be
            generated by network simplex algorthm.
        Post:
            (1) Changes 'flow' attribute of edges.
        Return:
            Returns True when an optimal solution is found, returns
            False otherwise.
        '''
        # ==== determine an initial tree structure (T,L,U)
        # find a feasible flow
        if not self.find_feasible_flow():
            return False
        t = self.simplex_find_tree()
        self.set_display_mode(display)
        # mark spanning tree arcs
        self.simplex_mark_st_arcs(t)
        # display initial spanning tree
        t.simplex_redraw(display, root)
        t.set_display_mode(display)
        #t.display()
        self.display()
        # set predecessor, depth and thread indexes
        t.simplex_search(root, 1)
        # compute potentials
        self.simplex_compute_potentials(t, root)
        # while some nontree arc violates optimality conditions
        while not self.simplex_optimal(t):
            self.display()
            # select an entering arc (k,l)
            (k,l) = self.simplex_select_entering_arc(t, pivot)
            self.simplex_mark_entering_arc(k, l)
            self.display()
            # determine leaving arc
            ((p,q), capacity, cycle)=self.simplex_determine_leaving_arc(t,k,l)
            # mark leaving arc
            self.simplex_mark_leaving_arc(p, q)
            self.display()
            self.simplex_remove_arc(t, p, q, capacity, cycle)
            # display after arc removed
            self.display()
            self.simplex_mark_st_arcs(t)
            self.display()
            # set predecessor, depth and thread indexes
            t.simplex_redraw(display, root)
            #t.display()
            t.simplex_search(root, 1)
            # compute potentials
            self.simplex_compute_potentials(t, root)
        return True

    def simplex_mark_leaving_arc(self, p, q):
        '''
        API:
            simplex_mark_leving_arc(self, p, q)
        Description:
            Marks leaving arc.
        Input:
            p: tail of the leaving arc
            q: head of the leaving arc
        Post:
            Changes color attribute of leaving arc.
        '''
        self.set_edge_attr(p, q, 'color', 'red')

    def simplex_determine_leaving_arc(self, t, k, l):
        '''
        API:
            simplex_determine_leaving_arc(self, t, k, l)
        Description:
            Determines and returns the leaving arc.
        Input:
            t: current spanning tree solution.
            k: tail of the entering arc.
            l: head of the entering arc.
        Return:
            Returns the tuple that represents leaving arc, capacity of the
            cycle and cycle.
        '''
        # k,l are the first two elements of the cycle
        cycle = self.simplex_identify_cycle(t, k, l)
        flow_kl = self.get_edge_attr(k, l, 'flow')
        capacity_kl = self.get_edge_attr(k, l, 'capacity')
        min_capacity = capacity_kl
        # check if k,l is in U or L
        if flow_kl==capacity_kl:
            # l,k will be the last two elements
            cycle.reverse()
        n = len(cycle)
        index = 0
        # determine last blocking arc
        t.add_edge(k, l)
        tel = t.get_edge_list()
        while index < (n-1):
            if (cycle[index], cycle[index+1]) in tel:
                flow = self.edge_attr[(cycle[index], cycle[index+1])]['flow']
                capacity = \
                    self.edge_attr[(cycle[index],cycle[index+1])]['capacity']
                if min_capacity >= (capacity-flow):
                    candidate = (cycle[index], cycle[index+1])
                    min_capacity = capacity-flow
            else:
                flow = self.edge_attr[(cycle[index+1], cycle[index])]['flow']
                if min_capacity >= flow:
                    candidate = (cycle[index+1], cycle[index])
                    min_capacity = flow
            index += 1
        # check arc (cycle[n-1], cycle[0])
        if (cycle[n-1], cycle[0]) in tel:
            flow = self.edge_attr[(cycle[n-1], cycle[0])]['flow']
            capacity = self.edge_attr[(cycle[n-1], cycle[0])]['capacity']
            if min_capacity >= (capacity-flow):
                candidate = (cycle[n-1], cycle[0])
                min_capacity = capacity-flow
        else:
            flow = self.edge_attr[(cycle[0], cycle[n-1])]['flow']
            if min_capacity >= flow:
                candidate = (cycle[0], cycle[n-1])
                min_capacity = flow
        return (candidate, min_capacity, cycle)

    def simplex_mark_entering_arc(self, k, l):
        '''
        API:
            simplex_mark_entering_arc(self, k, l)
        Description:
            Marks entering arc (k,l)
        Input:
            k: tail of the entering arc
            l: head of the entering arc
        Post:
            (1) color attribute of the arc (k,l)
        '''
        self.set_edge_attr(k, l, 'color', 'green')

    def simplex_mark_st_arcs(self, t):
        '''
        API:
            simplex_mark_st_arcs(self, t)
        Description:
            Marks spanning tree arcs.
            Case 1, Blue: Arcs that are at lower bound and in tree.
            Case 2, Red: Arcs that are at upper bound and in tree.
            Case 3, Green: Arcs that are between bounds are green.
            Case 4, Brown: Non-tree arcs at lower bound.
            Case 5, Violet: Non-tree arcs at upper bound.
        Input:
            t: t is the current spanning tree
        Post:
            (1) color attribute of edges.
        '''
        tel = list(t.edge_attr.keys())
        for e in self.get_edge_list():
            flow_e = self.edge_attr[e]['flow']
            capacity_e = self.edge_attr[e]['capacity']
            if e in tel:
                if flow_e == 0:
                    self.edge_attr[e]['color'] = 'blue'
                elif flow_e == capacity_e:
                    self.edge_attr[e]['color'] = 'blue'
                else:
                    self.edge_attr[e]['color'] = 'blue'
            else:
                if flow_e == 0:
                    self.edge_attr[e]['color'] = 'black'
                elif flow_e == capacity_e:
                    self.edge_attr[e]['color'] = 'black'
                else:
                    msg = "Arc is not in ST but has flow between bounds."
                    raise Exception(msg)

    def print_flow(self):
        '''
        API:
            print_flow(self)
        Description:
            Prints all positive flows to stdout. This method can be used for
            debugging purposes.
        '''
        print('printing current edge, flow, capacity')
        for e in self.edge_attr:
            if self.edge_attr[e]['flow']!=0:
                print(e, str(self.edge_attr[e]['flow']).ljust(4), end=' ')
                print(str(self.edge_attr[e]['capacity']).ljust(4))

    def simplex_redraw(self, display, root):
        '''
        API:
            simplex_redraw(self, display, root)
        Description:
            Returns a new graph instance that is same as self but adds nodes
            and arcs in a way that the resulting tree will be displayed
            properly.
        Input:
            display: display mode
            root: root node in tree.
        Return:
            Returns a graph same as self.
        '''
        nl = self.get_node_list()
        el = self.get_edge_list()
        new = Graph(type=DIRECTED_GRAPH, layout='dot', display=display)
        pred_i = self.get_node(root).get_attr('pred')
        thread_i = self.get_node(root).get_attr('thread')
        depth_i = self.get_node(root).get_attr('depth')
        new.add_node(root, pred=pred_i, thread=thread_i, depth=depth_i)
        q = [root]
        visited = [root]
        while q:
            name = q.pop()
            visited.append(name)
            neighbors = self.neighbors[name] + self.in_neighbors[name]
            for n in neighbors:
                if n not in new.get_node_list():
                    pred_i = self.get_node(n).get_attr('pred')
                    thread_i = self.get_node(n).get_attr('thread')
                    depth_i = self.get_node(n).get_attr('depth')
                    new.add_node(n, pred=pred_i, thread=thread_i, depth=depth_i)
                if (name,n) in el:
                    if (name,n) not in new.edge_attr:
                        new.add_edge(name,n)
                else:
                    if (n,name) not in new.edge_attr:
                        new.add_edge(n,name)
                if n not in visited:
                    q.append(n)
        for e in el:
            flow = self.edge_attr[e]['flow']
            capacity = self.edge_attr[e]['capacity']
            cost = self.edge_attr[e]['cost']
            new.edge_attr[e]['flow'] = flow
            new.edge_attr[e]['capacity'] = capacity
            new.edge_attr[e]['cost'] = cost
            new.edge_attr[e]['label'] =  "%d/%d/%d" %(flow,capacity,cost)
        return new

    def simplex_remove_arc(self, t, p, q, min_capacity, cycle):
        '''
        API:
            simplex_remove_arc(self, p, q, min_capacity, cycle)
        Description:
            Removes arc (p,q), updates t, updates flows, where (k,l) is
            the entering arc.
        Input:
            t: tree solution to be updated.
            p: tail of the leaving arc.
            q: head of the leaving arc.
            min_capacity: capacity of the cycle.
            cycle: cycle obtained when entering arc considered.
        Post:
            (1) updates t.
            (2) updates 'flow' attributes.
        '''
        # augment min_capacity along cycle
        n = len(cycle)
        tel = list(t.edge_attr.keys())
        index = 0
        while index < (n-1):
            if (cycle[index], cycle[index+1]) in tel:
                flow_e = self.edge_attr[(cycle[index], cycle[index+1])]['flow']
                self.edge_attr[(cycle[index], cycle[index+1])]['flow'] =\
                    flow_e+min_capacity
            else:
                flow_e = self.edge_attr[(cycle[index+1], cycle[index])]['flow']
                self.edge_attr[(cycle[index+1], cycle[index])]['flow'] =\
                    flow_e-min_capacity
            index += 1
        # augment arc cycle[n-1], cycle[0]
        if (cycle[n-1], cycle[0]) in tel:
            flow_e = self.edge_attr[(cycle[n-1], cycle[0])]['flow']
            self.edge_attr[(cycle[n-1], cycle[0])]['flow'] =\
                flow_e+min_capacity
        else:
            flow_e = self.edge_attr[(cycle[0], cycle[n-1])]['flow']
            self.edge_attr[(cycle[0], cycle[n-1])]['flow'] =\
                flow_e-min_capacity
        # remove leaving arc
        t.del_edge((p, q))
        # set label of removed arc
        flow_pq = self.get_edge_attr(p, q, 'flow')
        capacity_pq = self.get_edge_attr(p, q, 'capacity')
        cost_pq = self.get_edge_attr(p, q, 'cost')
        self.set_edge_attr(p, q, 'label',
                           "%d/%d/%d" %(flow_pq,capacity_pq,cost_pq))
        for e in t.edge_attr:
            flow = self.edge_attr[e]['flow']
            capacity = self.edge_attr[e]['capacity']
            cost = self.edge_attr[e]['cost']
            t.edge_attr[e]['flow'] = flow
            t.edge_attr[e]['capacity'] = capacity
            t.edge_attr[e]['cost'] = cost
            t.edge_attr[e]['label'] = "%d/%d/%d" %(flow,capacity,cost)
            self.edge_attr[e]['label'] = "%d/%d/%d" %(flow,capacity,cost)

    def simplex_select_entering_arc(self, t, pivot):
        '''
        API:
            simplex_select_entering_arc(self, t, pivot)
        Description:
            Decides and returns entering arc using pivot rule.
        Input:
            t: current spanning tree solution
            pivot: May be one of the following; 'first_eligible' or 'dantzig'.
            'dantzig' is the default value.
        Return:
            Returns entering arc tuple (k,l)
        '''
        if pivot=='dantzig':
            # pick the maximum violation
            candidate = {}
            for e in self.edge_attr:
                if e in t.edge_attr:
                    continue
                flow_ij = self.edge_attr[e]['flow']
                potential_i = self.get_node(e[0]).get_attr('potential')
                potential_j = self.get_node(e[1]).get_attr('potential')
                capacity_ij = self.edge_attr[e]['capacity']
                c_ij = self.edge_attr[e]['cost']
                cpi_ij = c_ij - potential_i + potential_j
                if flow_ij==0:
                    if cpi_ij < 0:
                        candidate[e] = cpi_ij
                elif flow_ij==capacity_ij:
                    if cpi_ij > 0:
                        candidate[e] = cpi_ij
            for e in candidate:
                max_c = e
                max_v = abs(candidate[e])
                break
            for e in candidate:
                if max_v < abs(candidate[e]):
                    max_c = e
                    max_v = abs(candidate[e])
        elif pivot=='first_eligible':
            # pick the first eligible
            for e in self.edge_attr:
                if e in t.edge_attr:
                    continue
                flow_ij = self.edge_attr[e]['flow']
                potential_i = self.get_node(e[0]).get_attr('potential')
                potential_j = self.get_node(e[1]).get_attr('potential')
                capacity_ij = self.edge_attr[e]['capacity']
                c_ij = self.edge_attr[e]['cost']
                cpi_ij = c_ij - potential_i + potential_j
                if flow_ij==0:
                    if cpi_ij < 0:
                        max_c = e
                        max_v = abs(cpi_ij)
                elif flow_ij==capacity_ij:
                    if cpi_ij > 0:
                        max_c = e
                        max_v = cpi_ij
        else:
            raise Exception("Unknown pivot rule.")
        return max_c

    def simplex_optimal(self, t):
        '''
        API:
            simplex_optimal(self, t)
        Description:
            Checks if the current solution is optimal, if yes returns True,
            False otherwise.
        Pre:
            'flow' attributes represents a solution.
        Input:
            t: Graph instance tat reperesents spanning tree solution.
        Return:
            Returns True if the current solution is optimal (optimality
            conditions are satisfied), else returns False
        '''
        for e in self.edge_attr:
            if e in t.edge_attr:
                continue
            flow_ij = self.edge_attr[e]['flow']
            potential_i = self.get_node(e[0]).get_attr('potential')
            potential_j = self.get_node(e[1]).get_attr('potential')
            capacity_ij = self.edge_attr[e]['capacity']
            c_ij = self.edge_attr[e]['cost']
            cpi_ij = c_ij - potential_i + potential_j
            if flow_ij==0:
                if cpi_ij < 0:
                    return False
            elif flow_ij==capacity_ij:
                if cpi_ij > 0:
                    return False
        return True

    def simplex_find_tree(self):
        '''
        API:
            simplex_find_tree(self)
        Description:
            Assumes a feasible flow solution stored in 'flow' attribute's of
            arcs and converts this solution to a feasible spanning tree
            solution.
        Pre:
            (1) 'flow' attributes represents a feasible flow solution.
        Post:
            (1) 'flow' attributes may change when eliminating cycles.
        Return:
            Return a Graph instance that is a spanning tree solution.
        '''
        # find a cycle
        solution_g = self.get_simplex_solution_graph()
        cycle = solution_g.simplex_find_cycle()
        while cycle is not None:
            # find amount to augment and direction
            amount = self.simplex_augment_cycle(cycle)
            # augment along the cycle
            self.augment_cycle(amount, cycle)
            # find a new cycle
            solution_g = self.get_simplex_solution_graph()
            cycle = solution_g.simplex_find_cycle()
        # check if the solution is connected
        while self.simplex_connect(solution_g):
            pass
        # add attributes
        for e in self.edge_attr:
            flow = self.edge_attr[e]['flow']
            capacity = self.edge_attr[e]['capacity']
            cost = self.edge_attr[e]['cost']
            self.edge_attr[e]['label'] = "%d/%d/%d" %(flow,capacity,cost)
            if e in solution_g.edge_attr:
                solution_g.edge_attr[e]['flow'] = flow
                solution_g.edge_attr[e]['capacity'] = capacity
                solution_g.edge_attr[e]['cost'] = cost
                solution_g.edge_attr[e]['label'] = "%d/%d/%d" %(flow,capacity,cost)
        return solution_g

    def simplex_connect(self, solution_g):
        '''
        API:
            simplex_connect(self, solution_g)
        Description:
            At this point we assume that the solution does not have a cycle.
            We check if all the nodes are connected, if not we add an arc to
            solution_g that does not create a cycle and return True. Otherwise
            we do nothing and return False.
        Pre:
            (1) We assume there is no cycle in the solution.
        Input:
            solution_g: current spanning tree solution instance.
        Post:
            (1) solution_g is updated. An arc that does not create a cycle is
            added.
            (2) 'component' attribute of nodes are changed.
        Return:
            Returns True if an arc is added, returns False otherwise.
        '''
        nl = solution_g.get_node_list()
        current = nl[0]
        pred = solution_g.simplex_search(current, current)
        separated = list(pred.keys())
        for n in nl:
            if solution_g.get_node(n).get_attr('component') != current:
                # find an arc from n to seperated
                for m in separated:
                    if (n,m) in self.edge_attr:
                        solution_g.add_edge(n,m)
                        return True
                    elif (m,n) in self.edge_attr:
                        solution_g.add_edge(m,n)
                        return True
        return False

    def simplex_search(self, source, component_nr):
        '''
        API:
            simplex_search(self, source, component_nr)
        Description:
            Searches graph starting from source. Its difference from usual
            search is we can also go backwards along an arc. When the graph
            is a spanning tree it computes predecessor, thread and depth
            indexes and stores them as node attributes. These values should be
            considered as junk when the graph is not a spanning tree.
        Input:
            source: source node
            component_nr: component number
        Post:
            (1) Sets the component number of all reachable nodes to component.
            Changes 'component' attribute of nodes.
            (2) Sets 'pred', 'thread' and 'depth' attributes of nodes. These
            values are junk if the graph is not a tree.
        Return:
            Returns predecessor dictionary.
        '''
        q = [source]
        pred = {source:None}
        depth = {source:0}
        sequence = []
        for n in self.neighbors:
            self.get_node(n).set_attr('component', None)
        while q:
            current = q.pop()
            self.get_node(current).set_attr('component', component_nr)
            sequence.append(current)
            neighbors = self.in_neighbors[current] + self.neighbors[current]
            for n in neighbors:
                if n in pred:
                    continue
                self.get_node(n).set_attr('component', component_nr)
                pred[n] = current
                depth[n] = depth[current]+1
                q.append(n)
        for i in range(len(sequence)-1):
            self.get_node(sequence[i]).set_attr('thread', int(sequence[i+1]))
        self.get_node(sequence[-1]).set_attr('thread', int(sequence[0]))
        for n in pred:
            self.get_node(n).set_attr('pred', pred[n])
            self.get_node(n).set_attr('depth', depth[n])
        return pred

    def simplex_augment_cycle(self, cycle):
        '''
        API:
            simplex_augment_cycle(self, cycle)
        Description:
            Augments along the cycle to break it.
        Pre:
            'flow', 'capacity' attributes on arcs.
        Input:
            cycle: list representing a cycle in the solution
        Post:
            'flow' attribute will be modified.
        '''
        # find amount to augment
        index = 0
        k = len(cycle)
        el = list(self.edge_attr.keys())
        # check arc (cycle[k-1], cycle[0])
        if (cycle[k-1], cycle[0]) in el:
            min_capacity = self.edge_attr[(cycle[k-1], cycle[0])]['capacity']-\
                              self.edge_attr[(cycle[k-1], cycle[0])]['flow']
        else:
            min_capacity = self.edge_attr[(cycle[0], cycle[k-1])]['flow']
        # check rest of the arcs in the cycle
        while index<(k-1):
            i = cycle[index]
            j = cycle[index+1]
            if (i,j) in el:
                capacity_ij = self.edge_attr[(i,j)]['capacity'] -\
                              self.edge_attr[(i,j)]['flow']
            else:
                capacity_ij = self.edge_attr[(j,i)]['flow']
            if min_capacity > capacity_ij:
                min_capacity = capacity_ij
            index += 1
        return min_capacity

    def simplex_find_cycle(self):
        '''
        API:
            simplex_find_cycle(self)
        Description:
            Returns a cycle (list of nodes) if the graph has one, returns None
            otherwise. Uses DFS. During DFS checks existence of arcs to lower
            depth regions. Note that direction of the arcs are not important.
        Return:
            Returns list of nodes that represents cycle. Returns None if the
            graph does not have any cycle.
        '''
        # make a dfs, if you identify an arc to a lower depth node we have a
        # cycle
        nl = self.get_node_list()
        q = [nl[0]]
        visited = []
        depth = {nl[0]:0}
        pred = {nl[0]:None}
        for n in nl:
            self.get_node(n).set_attr('component', None)
        component_nr = int(nl[0])
        self.get_node(nl[0]).set_attr('component', component_nr)
        while True:
            while q:
                current = q.pop()
                visited.append(current)
                neighbors = self.in_neighbors[current] +\
                    self.neighbors[current]
                for n in neighbors:
                    if n==pred[current]:
                        continue
                    self.get_node(n).set_attr('component', component_nr)
                    if n in depth:
                        # we have a cycle
                        cycle1 = []
                        cycle2 = []
                        temp = n
                        while temp is not None:
                            cycle1.append(temp)
                            temp = pred[temp]
                        temp = current
                        while temp is not None:
                            cycle2.append(temp)
                            temp = pred[temp]
                        cycle1.pop()
                        cycle1.reverse()
                        cycle2.extend(cycle1)
                        return cycle2
                    else:
                        pred[n] = current
                        depth[n] = depth[current] + 1
                    if n not in visited:
                        q.append(n)
            flag = False
            for n in nl:
                if self.get_node(n).get_attr('component') is None:
                    q.append(n)
                    depth = {n:0}
                    pred = {n:None}
                    visited = []
                    component_nr = int(n)
                    self.get_node(n).set_attr('component', component_nr)
                    flag = True
                    break
            if not flag:
                break
        return None

    def get_simplex_solution_graph(self):
        '''
        API:
            get_simplex_solution_graph(self):
        Description:
            Assumes a feasible flow solution stored in 'flow' attribute's of
            arcs. Returns the graph with arcs that have flow between 0 and
            capacity.
        Pre:
            (1) 'flow' attribute represents a feasible flow solution. See
            Pre section of min_cost_flow() for details.
        Return:
            Graph instance that only has the arcs that have flow strictly
            between 0 and capacity.
        '''
        simplex_g = Graph(type=DIRECTED_GRAPH)
        for i in self.neighbors:
            simplex_g.add_node(i)
        for e in self.edge_attr:
            flow_e = self.edge_attr[e]['flow']
            capacity_e = self.edge_attr[e]['capacity']
            if flow_e>0 and flow_e<capacity_e:
                simplex_g.add_edge(e[0], e[1])
        return simplex_g

    def simplex_compute_potentials(self, t, root):
        '''
        API:
            simplex_compute_potentials(self, t, root)
        Description:
            Computes node potentials for a minimum cost flow problem and stores
            them as node attribute 'potential'. Based on pseudocode given in
            Network Flows by Ahuja et al.
        Pre:
            (1) Assumes a directed graph in which each arc has a 'cost'
            attribute.
            (2) Uses 'thread' and 'pred' attributes of nodes.
        Input:
            t: Current spanning tree solution, its type is Graph.
            root: root node of the tree.
        Post:
            Keeps the node potentials as 'potential' attribute.
        '''
        self.get_node(root).set_attr('potential', 0)
        j = t.get_node(root).get_attr('thread')
        while j is not root:
            i = t.get_node(j).get_attr('pred')
            potential_i = self.get_node(i).get_attr('potential')
            if (i,j) in self.edge_attr:
                c_ij = self.edge_attr[(i,j)]['cost']
                self.get_node(j).set_attr('potential', potential_i-c_ij)
            if (j,i) in self.edge_attr:
                c_ji = self.edge_attr[(j,i)]['cost']
                self.get_node(j).set_attr('potential', potential_i+c_ji)
            j = t.get_node(j).get_attr('thread')

    def simplex_identify_cycle(self, t, k, l):
        '''
        API:
            identify_cycle(self, t, k, l)
        Description:
            Identifies and returns to the pivot cycle, which is a list of
            nodes.
        Pre:
            (1) t is spanning tree solution, (k,l) is the entering arc.
        Input:
            t: current spanning tree solution
            k: tail of the entering arc
            l: head of the entering arc
        Returns:
            List of nodes in the cycle.
        '''
        i = k
        j = l
        cycle = []
        li = [k]
        lj = [j]
        while i is not j:
            depth_i = t.get_node(i).get_attr('depth')
            depth_j = t.get_node(j).get_attr('depth')
            if depth_i > depth_j:
                i = t.get_node(i).get_attr('pred')
                li.append(i)
            elif depth_i < depth_j:
                j = t.get_node(j).get_attr('pred')
                lj.append(j)
            else:
                i = t.get_node(i).get_attr('pred')
                li.append(i)
                j = t.get_node(j).get_attr('pred')
                lj.append(j)
        cycle.extend(lj)
        li.pop()
        li.reverse()
        cycle.extend(li)
        # l is beginning k is end
        return cycle

    def min_cost_flow(self, display = None, **args):
        '''
        API:
            min_cost_flow(self, display='off', **args)
        Description:
            Solves minimum cost flow problem using node/edge attributes with
            the algorithm specified.
        Pre:
            (1) Assumes a directed graph in which each arc has 'capacity' and
            'cost' attributes.
            (2) Nodes should have 'demand' attribute. This value should be
            positive for supply and negative for demand, and 0 for transhipment
            nodes.
            (3) The graph should be connected.
            (4) Assumes (i,j) and (j,i) does not exist together. Needed when
            solving max flow. (max flow problem is solved to get a feasible
            flow).
        Input:
            display: 'off' for no display, 'matplotlib' for live update of tree
            args: may have the following
                display: display method, if not given current mode (the one
                    specified by __init__ or set_display) will be used.
                algo: determines algorithm to use, can be one of the following
                    'simplex': network simplex algorithm
                    'cycle_canceling': cycle canceling algorithm
                    'simplex' is used if not given.
                    see Network Flows by Ahuja et al. for details of algorithms.
                pivot: valid if algo is 'simlex', determines pivoting rule for
                    simplex, may be one of the following; 'first_eligible',
                    'dantzig' or 'scaled'.
                    'dantzig' is used if not given.
                    see Network Flows by Ahuja et al. for pivot rules.
                root: valid if algo is 'simlex', specifies the root node for
                    simplex algorithm. It is name of the one of the nodes. It
                    will be chosen randomly if not provided.
        Post:
            The 'flow' attribute of each arc gives the optimal flows.
            'distance' attribute of the nodes are also changed during max flow
            solution process.
        Examples:
            g.min_cost_flow():
                solves minimum cost feasible flow problem using simplex
                algorithm with dantzig pivoting rule.
                See pre section for details.
            g.min_cost_flow(algo='cycle_canceling'):
                solves minimum cost feasible flow problem using cycle canceling
                agorithm.
            g.min_cost_flow(algo='simplex', pivot='scaled'):
                solves minimum cost feasible flow problem using network simplex
                agorithm with scaled pivot rule.
        '''
        if display is None:
            display = self.attr['display']
        if 'algo' in args:
            algorithm = args['algo']
        else:
            algorithm = 'simplex'
        if algorithm == 'simplex':
            if 'root' in args:
                root = args['root']
            else:
                for k in self.neighbors:
                    root = k
                    break
            if 'pivot' in args:
                if not self.network_simplex(display, args['pivot'], root):
                    print('problem is infeasible')
            else:
                if not self.network_simplex(display, 'dantzig', root):
                    print('problem is infeasible')
        elif algorithm == 'cycle_canceling':
            if not self.cycle_canceling(display):
                print('problem is infeasible')
        else:
            print(args['algo'], 'is not a defined algorithm. Exiting.')
            return

    def random(self, numnodes = 10, degree_range = (2, 4), length_range = (1, 10),
               density = None, edge_format = None, node_format = None,
               Euclidean = False, seedInput = 0, add_labels = True,
               parallel_allowed = False, node_selection = 'closest',
               scale = 10, scale_cost = 5):
        '''
        API:
            random(self, numnodes = 10, degree_range = None, length_range = None,
               density = None, edge_format = None, node_format = None,
               Euclidean = False, seedInput = 0)
        Description:
            Populates graph with random edges and nodes.
        Input:
            numnodes: Number of nodes to add.
            degree_range: A tuple that has lower and upper bounds of degree for
            a node.
            length_range: A tuple that has lower and upper bounds for 'cost'
            attribute of edges.
            density: Density of edges, ie. 0.5 indicates a node will
            approximately have edge to half of the other nodes.
            edge_format: Dictionary that specifies attribute values for edges.
            node_format: Dictionary that specifies attribute values for nodes.
            Euclidean: Creates an Euclidean graph (Euclidean distance between
            nodes) if True.
            seedInput: Seed that will be used for random number generation.
        Pre:
            It is recommended to call this method on empty Graph objects.
        Post:
            Graph will be populated by nodes and edges.
        '''
        random.seed(seedInput)
        if edge_format == None:
            edge_format = {'fontsize':10,
                           'fontcolor':'blue'}
        if node_format == None:
            node_format = {'height':0.5,
                           'width':0.5,
                           'fixedsize':'true',
                           'fontsize':10,
                           'fontcolor':'red',
                           'shape':'circle',
                           }
        if Euclidean == False:
            for m in range(numnodes):
                self.add_node(m, **node_format)
            if degree_range is not None and density is None:
                for m in range(numnodes):
                    degree = random.randint(degree_range[0], degree_range[1])
                    i = 0
                    while i < degree:
                        n = random.randint(1, numnodes-1)
                        if (((m,n) not in self.edge_attr and m != n) and
                            (parallel_allowed or (n, m) not in self.edge_attr)):
                            if length_range is not None:
                                length = random.randint(length_range[0],
                                                        length_range[1])
                                self.add_edge(m, n, cost = length, **edge_format)
                                if add_labels:
                                    self.set_edge_attr(m, n, 'label', str(length))
                                else:
                                    self.add_edge(m, n, **edge_format)
                        i += 1
            elif density != None:
                for m in range(numnodes):
                    if self.graph_type == DIRECTED_GRAPH:
                        numnodes2 = numnodes
                    else:
                        numnodes2 = m
                    for n in range(numnodes2):
                        if ((parallel_allowed or (n, m) not in self.edge_attr)
                            and m != n):
                            if random.random() < density:
                                if length_range is not None:
                                    length = random.randint(length_range[0],
                                                     length_range[1])
                                    self.add_edge(m, n, cost = length,
                                                  **edge_format)
                                    if add_labels:
                                        self.set_edge_attr(m, n, 'label', str(length))
                                else:
                                    self.add_edge(m, n, **edge_format)
            else:
                print("Must set either degree range or density")
        else:
            for m in range(numnodes):
                ''' Assigns random coordinates (between 1 and 20) to the nodes
                '''
                x = random.random()*scale
                y = random.random()*scale
                self.add_node(m, locationx = x, locationy = y,
                              pos = '"'+str(x) + "," + str(y)+'!"',
                              **node_format)
            if degree_range is not None and density is None:
                for m in range(numnodes):
                    degree = random.randint(degree_range[0], degree_range[1])
                    i = 0
                    neighbors = []
                    if node_selection == 'random':
                        while i < degree:
                            length = round((((self.get_node(n).get_attr('locationx') -
                                              self.get_node(m).get_attr('locationx')) ** 2 +
                                             (self.get_node(n).get_attr('locationy') -
                                              self.get_node(m).get_attr('locationy')) ** 2) ** 0.5)*scale_cost,
                                           0)
                            if (((m,n) not in self.edge_attr and m != n) and
                                (parallel_allowed or (n, m) not in self.edge_attr)):
                                neighbors.append(random.randint(0, numnodes-1))
                                self.add_edge(m, n, cost = int(length), **edge_format)
                                if add_labels:
                                    self.set_edge_attr(m, n, 'label', str(int(length)))
                                i += 1
                    elif node_selection == 'closest':
                        lengths = []
                        for n in range(numnodes):
                            lengths.append((n, round((((self.get_node(n).get_attr('locationx') -
                                                        self.get_node(m).get_attr('locationx')) ** 2 +
                                                       (self.get_node(n).get_attr('locationy') -
                                                        self.get_node(m).get_attr('locationy')) ** 2) ** 0.5)*scale_cost,
                                                     0)))
                        lengths.sort(key = lambda l : l[1])
                        for i in range(degree+1):
                            if not (lengths[i][0] == m or self.check_edge(m, lengths[i][0])):
                                self.add_edge(m, lengths[i][0], cost = int(lengths[i][1]), **edge_format)
                                if add_labels:
                                    self.set_edge_attr(m, lengths[i][0], 'label', str(int(lengths[i][1])))
                    else:
                        print("Unknown node selection rule...exiting")
                        return
            elif density != None:
                for m in range(numnodes):
                    if self.graph_type == DIRECTED_GRAPH:
                        numnodes2 = numnodes
                    else:
                        numnodes2 = m
                    for n in range(numnodes2):
                        if ((parallel_allowed or (n, m) not in self.edge_attr)
                            and m != n):
                            if random.random() < density:
                                if length_range is None:
                                    ''' calculates the euclidean norm and round it
                                    to an integer '''
                                    length = round((((self.get_node(n).get_attr('locationx') -
                                                      self.get_node(m).get_attr('locationx')) ** 2 +
                                                     (self.get_node(n).get_attr('locationy') -
                                                      self.get_node(m).get_attr('locationy')) ** 2) ** 0.5), 0)
                                    self.add_edge(m, n, cost = int(length), **edge_format)
                                    if add_labels:
                                        self.set_edge_attr(m, n, 'label', str(int(length)))
                                else:
                                    self.add_edge(m, n, **edge_format)
            else:
                print("Must set either degree range or density")

    def page_rank(self, damping_factor=0.85, max_iterations=100,
                  min_delta=0.00001):
        '''
        API:
            page_rank(self, damping_factor=0.85, max_iterations=100,
                  min_delta=0.00001)
        Description:
            Compute and return the page-rank of a directed graph.
            This function was originally taken from here and modified for this
            graph class: http://code.google.com/p/python-graph/source/browse/
            trunk/core/pygraph/algorithms/pagerank.py
        Input:
            damping_factor: Damping factor.
            max_iterations: Maximum number of iterations.
            min_delta: Smallest variation required to have a new iteration.
        Pre:
            Graph should be a directed graph.
        Return:
            Returns dictionary of page-ranks. Keys are node names, values are
            corresponding page-ranks.
        '''
        nodes = self.get_node_list()
        graph_size = len(nodes)
        if graph_size == 0:
            return {}
        #value for nodes without inbound links
        min_value = old_div((1.0-damping_factor),graph_size)
        # itialize the page rank dict with 1/N for all nodes
        pagerank = dict.fromkeys(nodes, old_div(1.0,graph_size))
        for _ in range(max_iterations):
            diff = 0 #total difference compared to last iteraction
            # computes each node PageRank based on inbound links
            for node in nodes:
                rank = min_value
                for referring_page in self.get_in_neighbors(node):
                    rank += (damping_factor * pagerank[referring_page] /
                             len(self.get_neighbors(referring_page)))
                diff += abs(pagerank[node] - rank)
                pagerank[node] = rank
            #stop if PageRank has converged
            if diff < min_delta:
                break
        return pagerank

    def get_degrees(self):
        '''
        API:
            get_degree(self)
        Description:
            Returns degrees of nodes in dictionary format.
        Return:
            Returns a dictionary of node degrees. Keys are node names, values
            are corresponding degrees.
        '''
        degree = {}
        if self.attr['type'] is not DIRECTED_GRAPH:
            for n in self.get_node_list():
                degree[n] = len(self.get_neighbors(n))
            return degree
        else:
            for n in self.get_node_list():
                degree[n] = (len(self.get_in_neighbors(n)) +
                             len(self.get_out_neighbors(n)))

    def get_in_degrees(self):
        '''
        API:
            get_degree(self)
        Description:
            Returns degrees of nodes in dictionary format.
        Return:
            Returns a dictionary of node degrees. Keys are node names, values
            are corresponding degrees.
        '''
        degree = {}
        if self.attr['type'] is not DIRECTED_GRAPH:
            print('This function only works for directed graphs')
            return
        for n in self.get_node_list():
            degree[n] = len(self.get_in_neighbors(n))
        return degree

    def get_out_degrees(self):
        '''
        API:
            get_degree(self)
        Description:
            Returns degrees of nodes in dictionary format.
        Return:
            Returns a dictionary of node degrees. Keys are node names, values
            are corresponding degrees.
        '''
        degree = {}
        if self.attr['type'] is not DIRECTED_GRAPH:
            print('This function only works for directed graphs')
            return
        for n in self.get_node_list():
            degree[n] = len(self.get_out_neighbors(n))
        return degree

    def get_diameter(self):
        '''
        API:
            get_diameter(self)
        Description:
            Returns diameter of the graph. Diameter is defined as follows.
            distance(n,m): shortest unweighted path from n to m
            eccentricity(n) = $\max _m distance(n,m)$
            diameter = $\min _n eccentricity(n) = \min _n \max _m distance(n,m)$
        Return:
            Returns diameter of the graph.
        '''

        if self.attr['type'] is not UNDIRECTED_GRAPH:
            print('This function only works for undirected graphs')
            return
        diameter = 'infinity'
        eccentricity_n = 0
        for n in self.get_node_list():
            for m in self.get_node_list():
                path_n_m = self.search(n, destination = m, algo = 'BFS')
                if path_n_m is None:
                    # this indicates there is no path from n to m, no diameter
                    # is defined, since the graph is not connected, return
                    # 'infinity'
                    return 'infinity'
                distance_n_m = len(path_n_m)-1
                if distance_n_m > eccentricity_n:
                    eccentricity_n = distance_n_m
            if diameter == 'infinity' or eccentricity_n > diameter:
                diameter = eccentricity_n
        return diameter

    def create_cluster(self, node_list, cluster_attrs={}, node_attrs={}):
        '''
        API:
            create_cluster(self, node_list, cluster_attrs, node_attrs)
        Description:
            Creates a cluster from the node given in the node list.
        Input:
            node_list: List of nodes in the cluster.
            cluster_attrs: Dictionary of cluster attributes, see Dot language
            grammer documentation for details.
            node_attrs: Dictionary of node attributes. It will overwrite
            previous attributes of the nodes in the cluster.
        Post:
            A cluster will be created. Attributes of the nodes in the cluster
            may change.
        '''
        if 'name' in cluster_attrs:
            if 'name' in self.cluster:
                raise Exception('A cluster with name %s already exists!' %cluster_attrs['name'])
            else:
                name = cluster_attrs['name']
        else:
            name = 'c%d' %self.attr['cluster_count']
            self.attr['cluster_count'] += 1
            cluster_attrs['name'] = name
        #cluster_attrs['name'] =
        self.cluster[name] = {'node_list':node_list,
                              'attrs':copy.deepcopy(cluster_attrs),
                              'node_attrs':copy.deepcopy(node_attrs)}

Subclasses

Methods

def add_edge(self, name1, name2, **attr)

API: add_edge(self, name1, name2, **attr) Description: Adds edge to the graph. Sets edge attributes using attr argument.

Input

name1: Name of the source node (if directed). name2: Name of the sink node (if directed). attr: Edge attributes.

Pre

Graph should not already contain this edge. We do not allow multiple edges with same source and sink nodes.

Post

self.edge_attr is updated. self.neighbors, self.nodes and self.in_neighbors are updated if graph was missing at least one of the nodes.

Expand source code
def add_edge(self, name1, name2, **attr):
    '''
    API: add_edge(self, name1, name2, **attr)
    Description:
    Adds edge to the graph. Sets edge attributes using attr argument.
    Input:
        name1: Name of the source node (if directed).
        name2: Name of the sink node (if directed).
        attr: Edge attributes.
    Pre:
        Graph should not already contain this edge. We do not allow
        multiple edges with same source and sink nodes.
    Post:
        self.edge_attr is updated.
        self.neighbors, self.nodes and self.in_neighbors are updated if
        graph was missing at least one of the nodes.
    '''
    if (name1, name2) in self.edge_attr:
        raise MultipleEdgeException
    if self.graph_type is UNDIRECTED_GRAPH and (name2,name1) in self.edge_attr:
        raise MultipleEdgeException
    self.edge_attr[(name1,name2)] = copy.deepcopy(DEFAULT_EDGE_ATTRIBUTES)
    for a in attr:
        self.edge_attr[(name1,name2)][a] = attr[a]
    if name1 not in self.nodes:
        self.add_node(name1)
    if name2 not in self.nodes:
        self.add_node(name2)
    self.neighbors[name1].append(name2)
    if self.graph_type is UNDIRECTED_GRAPH:
        self.neighbors[name2].append(name1)
    else:
        self.in_neighbors[name2].append(name1)
def add_node(self, name, **attr)

API: add_node(self, name, **attr) Description: Adds node to the graph.

Pre

Graph should not contain a node with this name. We do not allow multiple nodes with the same name.

Input

name: Name of the node. attr: Node attributes.

Post

self.neighbors, self.nodes and self.in_neighbors are updated.

Return

Node (a Node class instance) added to the graph.

Expand source code
def add_node(self, name, **attr):
    '''
    API: add_node(self, name, **attr)
    Description:
    Adds node to the graph.
    Pre:
        Graph should not contain a node with this name. We do not allow
        multiple nodes with the same name.
    Input:
        name: Name of the node.
        attr: Node attributes.
    Post:
        self.neighbors, self.nodes and self.in_neighbors are updated.
    Return:
        Node (a Node class instance) added to the graph.
    '''
    if name in self.neighbors:
        raise MultipleNodeException
    self.neighbors[name] = list()
    if self.graph_type is DIRECTED_GRAPH:
        self.in_neighbors[name] = list()
    self.nodes[name] = Node(name, **attr)
    return self.nodes[name]
def augment_cycle(self, amount, cycle)

Api

augment_cycle(self, amount, cycle):

Description

Augments 'amount' unit of flow along cycle.

Pre

Arcs should have 'flow' attribute.

Inputs

amount: An integer representing the amount to augment cycle: A list representing a cycle

Post

Changes 'flow' attributes of arcs.

Expand source code
def augment_cycle(self, amount, cycle):
    '''
    API:
        augment_cycle(self, amount, cycle):
    Description:
        Augments 'amount' unit of flow along cycle.
    Pre:
        Arcs should have 'flow' attribute.
    Inputs:
        amount: An integer representing the amount to augment
        cycle: A list representing a cycle
    Post:
        Changes 'flow' attributes of arcs.
    '''
    index = 0
    k = len(cycle)
    while index<(k-1):
        i = cycle[index]
        j = cycle[index+1]
        if (i,j) in self.edge_attr:
            flow_ij = self.edge_attr[(i,j)]['flow']
            self.edge_attr[(i,j)]['flow'] = flow_ij+amount
        else:
            flow_ji = self.edge_attr[(j,i)]['flow']
            self.edge_attr[(j,i)]['flow'] = flow_ji-amount
        index += 1
    i = cycle[k-1]
    j = cycle[0]
    if (i,j) in self.edge_attr:
        flow_ij = self.edge_attr[(i,j)]['flow']
        self.edge_attr[(i,j)]['flow'] = flow_ij+amount
    else:
        flow_ji = self.edge_attr[(j,i)]['flow']
        self.edge_attr[(j,i)]['flow'] = flow_ji-amount
def bfs(self, root, display=None, component=None)

API: bfs(self, root, display = None, component=None) Description: Make a breadth-first search starting from node with name root.

Input

root: Starting node name. display: display method. component: component number.

Post

Nodes will have 'component' attribute that will have component number as value.

Expand source code
def bfs(self, root, display = None, component = None):
    '''
    API: bfs(self, root, display = None, component=None)
    Description:
    Make a breadth-first search starting from node with name root.
    Input:
        root: Starting node name.
        display: display method.
        component: component number.
    Post:
        Nodes will have 'component' attribute that will have component
        number as value.
    '''
    self.search(root, display = display, component = component, q = Queue())
def check_edge(self, name1, name2)

API: check_edge(self, name1, name2) Description: Return True if edge exists, False otherwise.

Input

name1: name of the source node. name2: name of the sink node.

Return

Returns True if edge exists, False otherwise.

Expand source code
def check_edge(self, name1, name2):
    '''
    API: check_edge(self, name1, name2)
    Description:
    Return True if edge exists, False otherwise.
    Input:
        name1: name of the source node.
        name2: name of the sink node.
    Return:
        Returns True if edge exists, False otherwise.
    '''
    if self.graph_type is DIRECTED_GRAPH:
        return (name1, name2) in self.edge_attr
    else:
        return ((name1, name2) in self.edge_attr or
                (name2, name1) in self.edge_attr)
def create(self, layout, format, **args)

Api

create(self, layout, format, **args)

Description

Returns postscript representation of graph.

Input

layout: Dot layout for generating graph image. format: Image format, all format supported by Dot are wellcome.

Return

Returns postscript representation of graph.

Expand source code
    def create(self, layout, format, **args):
        '''
        API:
            create(self, layout, format, **args)
        Description:
            Returns postscript representation of graph.
        Input:
            layout: Dot layout for generating graph image.
            format: Image format, all format supported by Dot are wellcome.
        Return:
            Returns postscript representation of graph.
        '''
        tmp_fd, tmp_name = tempfile.mkstemp()
        tmp_file = os.fdopen(tmp_fd, 'w')
        tmp_file.write(self.to_string())
        tmp_file.close()
        try:
            p = subprocess.run([layout, '-T'+format, tmp_name],
                               capture_output = True)
        except OSError:
            print('''Graphviz executable not found.
Graphviz must be installed and in your search path.
Please visit http://www.graphviz.org/ for information on installation.
After installation, ensure that the PATH variable is properly set.''')
            return None

        
        p.check_returncode()

        os.remove(tmp_name)
            
        if p.stderr:
            print(p.stderr)
        return p.stdout
def create_cluster(self, node_list, cluster_attrs={}, node_attrs={})

Api

create_cluster(self, node_list, cluster_attrs, node_attrs)

Description

Creates a cluster from the node given in the node list.

Input

node_list: List of nodes in the cluster. cluster_attrs: Dictionary of cluster attributes, see Dot language grammer documentation for details. node_attrs: Dictionary of node attributes. It will overwrite previous attributes of the nodes in the cluster.

Post

A cluster will be created. Attributes of the nodes in the cluster may change.

Expand source code
def create_cluster(self, node_list, cluster_attrs={}, node_attrs={}):
    '''
    API:
        create_cluster(self, node_list, cluster_attrs, node_attrs)
    Description:
        Creates a cluster from the node given in the node list.
    Input:
        node_list: List of nodes in the cluster.
        cluster_attrs: Dictionary of cluster attributes, see Dot language
        grammer documentation for details.
        node_attrs: Dictionary of node attributes. It will overwrite
        previous attributes of the nodes in the cluster.
    Post:
        A cluster will be created. Attributes of the nodes in the cluster
        may change.
    '''
    if 'name' in cluster_attrs:
        if 'name' in self.cluster:
            raise Exception('A cluster with name %s already exists!' %cluster_attrs['name'])
        else:
            name = cluster_attrs['name']
    else:
        name = 'c%d' %self.attr['cluster_count']
        self.attr['cluster_count'] += 1
        cluster_attrs['name'] = name
    #cluster_attrs['name'] =
    self.cluster[name] = {'node_list':node_list,
                          'attrs':copy.deepcopy(cluster_attrs),
                          'node_attrs':copy.deepcopy(node_attrs)}
def create_residual_graph(self)

API: create_residual_graph(self) Description: Creates and returns residual graph, which is a Graph instance itself.

Pre

(1) Arcs should have 'flow', 'capacity' and 'cost' attribute (2) Graph should be a directed graph

Return

Returns residual graph, which is a Graph instance.

Expand source code
def create_residual_graph(self):
    '''
    API: create_residual_graph(self)
    Description:
    Creates and returns residual graph, which is a Graph instance
    itself.
    Pre:
        (1) Arcs should have 'flow', 'capacity' and 'cost' attribute
        (2) Graph should be a directed graph
    Return:
        Returns residual graph, which is a Graph instance.
    '''
    if self.graph_type is UNDIRECTED_GRAPH:
        raise Exception('residual graph is defined for directed graphs.')
    residual_g = Graph(type = DIRECTED_GRAPH)
    for e in self.get_edge_list():
        capacity_e = self.get_edge_attr(e[0], e[1], 'capacity')
        flow_e = self.get_edge_attr(e[0], e[1], 'flow')
        cost_e = self.get_edge_attr(e[0], e[1], 'cost')
        if flow_e > 0:
            residual_g.add_edge(e[1], e[0], cost=-1*cost_e,
                                capacity=flow_e)
        if capacity_e - flow_e > 0:
            residual_g.add_edge(e[0], e[1], cost=cost_e,
                                capacity=capacity_e-flow_e)
    return residual_g
def cycle_canceling(self, display)

Api

cycle_canceling(self, display)

Description

Solves minimum cost feasible flow problem using cycle canceling algorithm. Returns True when an optimal solution is found, returns False otherwise. 'flow' attribute values of arcs should be considered as junk when returned False.

Input

display: Display method.

Pre

(1) Arcs should have 'capacity' and 'cost' attribute. (2) Nodes should have 'demand' attribute, this value should be positive if the node is a supply node, negative if it is demand node and 0 if it is transhipment node. (3) graph should not have node 's' and 't'.

Post

Changes 'flow' attributes of arcs.

Return

Returns True when an optimal solution is found, returns False otherwise.

Expand source code
def cycle_canceling(self, display):
    '''
    API:
        cycle_canceling(self, display)
    Description:
        Solves minimum cost feasible flow problem using cycle canceling
        algorithm. Returns True when an optimal solution is found, returns
        False otherwise. 'flow' attribute values of arcs should be
        considered as junk when returned False.
    Input:
        display: Display method.
    Pre:
        (1) Arcs should have 'capacity' and 'cost' attribute.
        (2) Nodes should have 'demand' attribute, this value should be
        positive if the node is a supply node, negative if it is demand
        node and 0 if it is transhipment node.
        (3) graph should not have node 's' and 't'.
    Post:
        Changes 'flow' attributes of arcs.
    Return:
        Returns True when an optimal solution is found, returns False
        otherwise.
    '''
    # find a feasible solution to flow problem
    if not self.find_feasible_flow():
        return False
    # create residual graph
    residual_g = self.create_residual_graph()
    # identify a negative cycle in residual graph
    ncycle = residual_g.get_negative_cycle()
    # loop while residual graph has a negative cycle
    while ncycle is not None:
        # find capacity of cycle
        cap = residual_g.find_cycle_capacity(ncycle)
        # augment capacity amount along the cycle
        self.augment_cycle(cap, ncycle)
        # create residual graph
        residual_g = self.create_residual_graph()
        # identify next negative cycle
        ncycle = residual_g.get_negative_cycle()
    return True
def del_edge(self, e)

API: del_edge(self, e) Description: Removes edge from graph.

Input

e: Tuple that represents edge, in (source,sink) form.

Pre

Graph should contain this edge.

Post

self.edge_attr, self.neighbors and self.in_neighbors are updated.

Expand source code
def del_edge(self, e):
    '''
    API: del_edge(self, e)
    Description:
    Removes edge from graph.
    Input:
        e: Tuple that represents edge, in (source,sink) form.
    Pre:
        Graph should contain this edge.
    Post:
        self.edge_attr, self.neighbors and self.in_neighbors are updated.
    '''
    if self.graph_type is DIRECTED_GRAPH:
        try:
            del self.edge_attr[e]
        except KeyError:
            raise Exception('Edge %s does not exists!' %str(e))
        self.neighbors[e[0]].remove(e[1])
        self.in_neighbors[e[1]].remove(e[0])
    else:
        try:
            del self.edge_attr[e]
        except KeyError:
            try:
                del self.edge_attr[(e[1],e[0])]
            except KeyError:
                raise Exception('Edge %s does not exists!' %str(e))
        self.neighbors[e[0]].remove(e[1])
        self.neighbors[e[1]].remove(e[0])
def del_node(self, name)

API: del_node(self, name) Description: Removes node from Graph.

Input

name: Name of the node.

Pre

Graph should contain a node with this name.

Post

self.neighbors, self.nodes and self.in_neighbors are updated.

Expand source code
def del_node(self, name):
    '''
    API: del_node(self, name)
    Description:
    Removes node from Graph.
    Input:
        name: Name of the node.
    Pre:
        Graph should contain a node with this name.
    Post:
        self.neighbors, self.nodes and self.in_neighbors are updated.
    '''
    if name not in self.neighbors:
        raise Exception('Node %s does not exist!' %str(name))
    for n in self.neighbors[name]:
        del self.edge_attr[(name, n)]
        if self.graph_type == UNDIRECTED_GRAPH:
            self.neighbors[n].remove(name)
        else:
            self.in_neighbors[n].remove(name)
    if self.graph_type is DIRECTED_GRAPH:
        for n in self.in_neighbors[name]:
            del self.edge_attr[(n, name)]
            self.neighbors[n].remove(name)
    del self.neighbors[name]
    del self.in_neighbors[name]
    del self.nodes[name]
def dfs(self, root, disc_count=0, finish_count=1, component=None, transpose=False, display=None, pred=None)

API: dfs(self, root, disc_count = 0, finish_count = 1, component=None, transpose=False) Description: Make a depth-first search starting from node with name root.

Input

root: Starting node name. disc_count: Discovery time. finish_count: Finishing time. component: component number. transpose: Goes in the reverse direction along edges if transpose is True.

Post

Nodes will have 'component' attribute that will have component number as value. Updates 'disc_time' and 'finish_time' attributes of nodes which represents discovery time and finishing time.

Return

Returns a tuple that has discovery time and finish time of the last node in the following form (disc_time,finish_time).

Expand source code
def dfs(self, root, disc_count = 0, finish_count = 1, component = None,
        transpose = False, display = None, pred = None):
    '''
    API: dfs(self, root, disc_count = 0, finish_count = 1, component=None,
        transpose=False)
    Description:
    Make a depth-first search starting from node with name root.
    Input:
        root: Starting node name.
        disc_count: Discovery time.
        finish_count: Finishing time.
        component: component number.
        transpose: Goes in the reverse direction along edges if transpose
        is True.
    Post:
        Nodes will have 'component' attribute that will have component
        number as value. Updates 'disc_time' and 'finish_time' attributes
        of nodes which represents discovery time and finishing time.
    Return:
        Returns a tuple that has discovery time and finish time of the
        last node in the following form (disc_time,finish_time).
    '''
    if pred == None:
        pred = {}
    if display == None:
        display = self.attr['display']
    else:
        self.set_display_mode(display)
    neighbors = self.neighbors
    if self.graph_type == DIRECTED_GRAPH and transpose:
        neighbors = self.in_neighbors
    self.get_node(root).set_attr('component', component)
    disc_count += 1
    self.get_node(root).set_attr('disc_time', disc_count)
    self.get_node(root).set_attr('label', str(disc_count)+',-')
    self.get_node(root).set_attr('color', 'blue')
    if root in pred:
        self.set_edge_attr(pred[root], root, 'color', 'green')
    self.display()
    if transpose:
        fTime = []
        for n in neighbors[root]:
            fTime.append((n,self.get_node(n).get_attr('finish_time')))
        neighbor_list = sorted(fTime, key=operator.itemgetter(1))
        neighbor_list = list(t[0] for t in neighbor_list)
        neighbor_list.reverse()
    else:
        neighbor_list = neighbors[root]
    for i in neighbor_list:
        if not transpose:
            if self.get_node(i).get_attr('disc_time') is None:
                pred[i] = root
                disc_count, finish_count = self.dfs(i, disc_count,
                                                    finish_count,
                                                    component, transpose,
                                                    pred = pred)
        else:
            if self.get_node(i).get_attr('component') is None:
                disc_count, finish_count = self.dfs(i, disc_count,
                                                    finish_count,
                                                    component, transpose,
                                                    pred = pred)
    self.get_node(root).set_attr('finish_time', finish_count)
    d_time = self.get_node(root).get_attr('disc_time')
    label = '"' + str(d_time) + ',' + str(finish_count) + '"'
    self.get_node(root).set_attr('label', label)
    self.get_node(root).set_attr('color', 'green')
    self.display()
    finish_count += 1
    return disc_count, finish_count
def display(self, highlight=None, basename='graph', format='png', pause=False, wait_for_click=True)

Api

display(self, highlight = None, basename = 'graph', format = 'png', pause = True)

Description

Displays graph according to the arguments provided. Current display modes: 'off', 'file', 'PIL', 'matplotlib', 'xdot', 'svg' Current layout modes: Layouts provided by graphviz ('dot', 'fdp', 'circo', etc.) and 'dot2tex'. Current formats: Formats provided by graphviz ('ps', 'pdf', 'png', etc.)

Input

highlight: List of nodes to be highlighted. basename: File name. It will be used if display mode is 'file'. format: Image format, all format supported by Dot are wellcome. pause: If display is 'matplotlib', window will remain open until closed. wait_for_click: If display is 'matplotlib', setting to True will wait for a button click before proceeding. This is useful when animating an algorithm.

Post

A display window will pop up or a file will be written depending on display mode.

Expand source code
def display(self, highlight = None, basename = 'graph', format = 'png',
            pause = False, wait_for_click = True):
    '''
    API:
        display(self, highlight = None, basename = 'graph', format = 'png',
            pause = True)
    Description:
        Displays graph according to the arguments provided.
        Current display modes: 'off', 'file', 'PIL', 'matplotlib', 'xdot',
        'svg'
        Current layout modes: Layouts provided by graphviz ('dot', 'fdp',
        'circo', etc.) and 'dot2tex'.
        Current formats: Formats provided by graphviz ('ps', 'pdf', 'png',
        etc.)
    Input:
        highlight: List of nodes to be highlighted.
        basename: File name. It will be used if display mode is 'file'.
        format: Image format, all format supported by Dot are wellcome.
        pause: If display is 'matplotlib', window will remain open until closed.
        wait_for_click: If display is 'matplotlib', setting to True will 
        wait for a button click before proceeding. This is useful when 
        animating an algorithm. 
    Post:
        A display window will pop up or a file will be written depending
        on display mode.
    '''
    if self.attr['display'] == 'off':
        return
    if highlight != None:
        for n in highlight:
            if not isinstance(n, Node):
                n = self.get_node(n)
            n.set_attr('color', 'red')
    if self.get_layout() == 'dot2tex':
        if self.attr['display'] != 'file':
            self.attr['display'] = 'file'
            print("Warning: Dot2tex layout can only be used with display mode 'file'")
            print("         Automatically changing setting")
    if self.attr['display'] == 'file':
        if self.get_layout() == 'dot2tex':
            try:
                if DOT2TEX_INSTALLED:
                    if format != 'pdf' or format != 'ps':
                        print("Dot2tex only supports pdf and ps formats, falling back to pdf")
                        format = 'pdf'
                    self.set_layout('dot')
                    tex = dot2tex.dot2tex(self.to_string(), autosize=True, texmode = 'math', template = DOT2TEX_TEMPLATE)
                else:
                    print("Error: Dot2tex not installed.")
            except:
                try:
                    self.set_layout('dot')
                    with open(basename+'.dot', "w+b") as f:
                        self.write(f, self.get_layout(), 'dot')
                        p = subprocess.call(['dot2tex', '-t math',
                                             basename + '.dot'])
                except:
                    print("There was an error running dot2tex.")
            with open(basename+'.tex', 'w') as f:
                f.write(tex)
            try: 
                subprocess.call(['latex', basename])
                if format == 'ps':
                    subprocess.call(['dvips', basename])
                elif format == 'pdf':
                    subprocess.call(['pdflatex', basename])
                self.set_layout('dot2tex')
            except:
                print("There was an error runing latex. Is it installed?")
        else:
            with open(basename+'.'+format, "w+b") as f:
                self.write(f, self.get_layout(), format)
        return
    elif self.attr['display'] == 'PIL':
        if PIL_INSTALLED:
            tmp_fd, tmp_name = tempfile.mkstemp()
            tmp_file = os.fdopen(tmp_fd, 'w+b')
            self.write(tmp_file, self.get_layout(), format)
            tmp_file.close()
            im = PIL_Image.open(tmp_name)
            im.show()
            os.remove(tmp_name)
        else:
            print('Error: PIL not installed. Display disabled.')
            self.attr['display'] = 'off'
    elif self.attr['display'] == 'matplotlib':
        if MATPLOTLIB_INSTALLED and PIL_INSTALLED:
            tmp_fd, tmp_name = tempfile.mkstemp()
            tmp_file = os.fdopen(tmp_fd, 'w+b')
            self.write(tmp_file, self.get_layout(), format)
            tmp_file.close()
            im = PIL_Image.open(tmp_name)
            fig = plt.figure(1)
            fig.canvas.mpl_connect('close_event', handle_close)
            plt.clf()
            plt.axis('off')
            plt.imshow(im, interpolation='bilinear' #resample=True
                       #extent = (0, 100, 0, 100)
            )
            if wait_for_click == True:
                plt.draw()
                try:
                    if plt.waitforbuttonpress(timeout = 10000):
                        plt.close()
                        exit()
                except:
                    exit()
            else:
                plt.show(block=pause)
            im.close()
            os.remove(tmp_name)
        else:
            print('Warning: Either matplotlib or Pillow is not installed. Display disabled.')
            self.attr['display'] = 'off'
    elif self.attr['display'] == 'xdot':
        if XDOT_INSTALLED:
            window = xdot.DotWindow()
            window.set_dotcode(self.to_string())
            window.connect('destroy', gtk.main_quit)
            gtk.main()
        else:
            print('Error: xdot not installed. Display disabled.')
            self.attr['display'] = 'off'
    else:
        print("Unknown display mode: ", end=' ')
        print(self.attr['display'])
    if highlight != None:
        for n in highlight:
            if not isinstance(n, Node):
                n = self.get_node(n)
            n.set_attr('color', 'black')
def edge_to_string(self, e)

API: edge_to_string(self, e) Description: Return string that represents edge e in dot language.

Input

e: Edge tuple in (source,sink) format.

Pre

Graph should have this edge.

Return

String that represents given edge.

Expand source code
def edge_to_string(self, e):
    '''
    API: edge_to_string(self, e)
    Description:
    Return string that represents edge e in dot language.
    Input:
        e: Edge tuple in (source,sink) format.
    Pre:
        Graph should have this edge.
    Return:
        String that represents given edge.
    '''
    edge = list()
    edge.append(quote_if_necessary(str(e[0])))
    edge.append(self.edge_connect_symbol)
    edge.append(quote_if_necessary(str(e[1])))
    # return if there is nothing in self.edge_attr[e]
    if len(self.edge_attr[e]) == 0:
        return ''.join(edge)
    edge.append('  [')
    for a in self.edge_attr[e]:
        edge.append(a)
        edge.append('=')
        edge.append(quote_if_necessary(str(self.edge_attr[e][a])))
        edge.append(', ')
    edge = edge[:-1]
    edge.append(']')
    return ''.join(edge)
def fifo_label_correcting(self, source)

Api

fifo_label_correcting(self, source)

Description

finds shortest path from source to every other node. Returns predecessor dictionary. If graph has a negative cycle, detects it and returns to it.

Pre

(1) 'cost' attribute of arcs. It will be used to compute shortest path.

Input

source: source node

Post

Modifies 'distance' attribute of nodes.

Return

If there is no negative cycle returns to (True, pred), otherwise returns to (False, cycle) where pred is the predecessor dictionary and cycle is a list of nodes that represents cycle. It is in [n_1, n_2, …, n_k] form where the cycle has k nodes.

Expand source code
def fifo_label_correcting(self, source):
    '''
    API:
        fifo_label_correcting(self, source)
    Description:
        finds shortest path from source to every other node. Returns
        predecessor dictionary. If graph has a negative cycle, detects it
        and returns to it.
    Pre:
        (1) 'cost' attribute of arcs. It will be used to compute shortest
        path.
    Input:
        source: source node
    Post:
        Modifies 'distance' attribute of nodes.
    Return:
        If there is no negative cycle returns to (True, pred), otherwise
        returns to (False, cycle) where pred is the predecessor dictionary
        and cycle is a list of nodes that represents cycle. It is in
        [n_1, n_2, ..., n_k] form where the cycle has k nodes.
    '''
    pred = {}
    self.get_node(source).set_attr('distance', 0)
    pred[source] = None
    for n in self.neighbors:
        if n!=source:
            self.get_node(n).set_attr('distance', 'inf')
    q = [source]
    while q:
        i = q[0]
        q = q[1:]
        for j in self.neighbors[i]:
            distance_j = self.get_node(j).get_attr('distance')
            distance_i = self.get_node(i).get_attr('distance')
            c_ij = self.get_edge_attr(i, j, 'cost')
            if distance_j > distance_i + c_ij:
                self.get_node(j).set_attr('distance', distance_i+c_ij)
                if j in pred:
                    pred[j] = i
                    cycle = self.label_correcting_check_cycle(j, pred)
                    if cycle is not None:
                        return (False, cycle)
                else:
                    pred[j] = i
                if j not in q:
                    q.append(j)
    return (True, pred)
def find_cycle_capacity(self, cycle)

Api

find_cycle_capacity(self, cycle):

Description

Finds capacity of the cycle input.

Pre

(1) Arcs should have 'capacity' attribute.

Input

cycle: a list representing a cycle

Return

Returns an integer number representing capacity of cycle.

Expand source code
def find_cycle_capacity(self, cycle):
    '''
    API:
        find_cycle_capacity(self, cycle):
    Description:
        Finds capacity of the cycle input.
    Pre:
        (1) Arcs should have 'capacity' attribute.
    Input:
        cycle: a list representing a cycle
    Return:
        Returns an integer number representing capacity of cycle.
    '''
    index = 0
    k = len(cycle)
    capacity = self.get_edge_attr(cycle[k-1], cycle[0], 'capacity')
    while index<(k-1):
        i = cycle[index]
        j = cycle[index+1]
        capacity_ij = self.get_edge_attr(i, j, 'capacity')
        if capacity > capacity_ij:
            capacity = capacity_ij
        index += 1
    return capacity
def find_feasible_flow(self)

Api

find_feasible_flow(self)

Description

Solves feasible flow problem, stores solution in 'flow' attribute or arcs. This method is used to get an initial feasible flow for simplex and cycle canceling algorithms. Uses max_flow() method. Other max flow methods can also be used. Returns True if a feasible flow is found, returns False, if the problem is infeasible. When the problem is infeasible 'flow' attributes of arcs should be considered as junk.

Pre

(1) 'capacity' attribute of arcs (2) 'demand' attribute of nodes

Post

Keeps solution in 'flow' attribute of arcs.

Return

Returns True if a feasible flow is found, returns False, if the problem is infeasible

Expand source code
def find_feasible_flow(self):
    '''
    API:
        find_feasible_flow(self)
    Description:
        Solves feasible flow problem, stores solution in 'flow' attribute
        or arcs. This method is used to get an initial feasible flow for
        simplex and cycle canceling algorithms. Uses max_flow() method.
        Other max flow methods can also be used. Returns True if a feasible
        flow is found, returns False, if the problem is infeasible. When
        the problem is infeasible 'flow' attributes of arcs should be
        considered as junk.
    Pre:
        (1) 'capacity' attribute of arcs
        (2) 'demand' attribute of nodes
    Post:
        Keeps solution in 'flow' attribute of arcs.
    Return:
        Returns True if a feasible flow is found, returns False, if the
        problem is infeasible
    '''
    # establish a feasible flow in the network, to do this add nodes s and
    # t and solve a max flow problem.
    nl = self.get_node_list()
    for i in nl:
        b_i = self.get_node(i).get_attr('demand')
        if b_i > 0:
            # i is a supply node, add (s,i) arc
            self.add_edge('s', i, capacity=b_i)
        elif b_i < 0:
            # i is a demand node, add (i,t) arc
            self.add_edge(i, 't', capacity=-1*b_i)
    # solve max flow on this modified graph
    self.max_flow('s', 't', 'off')
    # check if all demand is satisfied, i.e. the min cost problem is
    # feasible or not
    for i in self.neighbors['s']:
        flow = self.get_edge_attr('s', i, 'flow')
        capacity = self.get_edge_attr('s', i, 'capacity')
        if flow != capacity:
            self.del_node('s')
            self.del_node('t')
            return False
    # remove node 's' and node 't'
    self.del_node('s')
    self.del_node('t')
    return True
def floyd_warshall(self)

Api

floyd_warshall(self)

Description

Finds all pair shortest paths and stores it in a list of lists. This is possible if the graph does not have negative cycles. It will return a tuple with 3 elements. The first element indicates whether the graph has a negative cycle. It is true if the graph does not have a negative cycle, ie. distances found are valid shortest distances. The second element is a dictionary of shortest distances between nodes. Keys are tuple of node pairs ie. (i,j). The third element is a dictionary that helps to retrieve the shortest path between nodes. Then return value can be represented as (validity, distance, nextn) where nextn is the dictionary to retrieve paths. distance and nextn can be used as inputs to other methods to get shortest path between nodes.

Pre

Arcs should have 'cost' attribute.

Return

Returns (validity, distance, nextn). The distances are valid if validity is True.

Expand source code
def floyd_warshall(self):
    '''
    API:
        floyd_warshall(self)
    Description:
        Finds all pair shortest paths and stores it in a list of lists.
        This is possible if the graph does not have negative cycles. It will
        return a tuple with 3 elements. The first element indicates whether
        the graph has a negative cycle. It is true if the graph does not
        have a negative cycle, ie. distances found are valid shortest
        distances. The second element is a dictionary of shortest distances
        between nodes. Keys are tuple of node pairs ie. (i,j). The third
        element is a dictionary that helps to retrieve the shortest path
        between nodes. Then return value can be represented as (validity,
        distance, nextn) where nextn is the dictionary to retrieve paths.
        distance and nextn can be used as inputs to other methods to get
        shortest path between nodes.
    Pre:
        Arcs should have 'cost' attribute.
    Return:
        Returns (validity, distance, nextn). The distances are valid if
        validity is True.
    '''
    nl = self.get_node_list()
    el = self.get_edge_list()
    # initialize distance
    distance = {}
    for i in nl:
        for j in nl:
            distance[(i,j)] = 'infinity'
    for i in nl:
        distance[(i,i)] = 0
    for e in el:
        distance[(e[0],e[1])] = self.get_edge_cost(e)
    # == end of distance initialization
    # initialize next
    nextn = {}
    for i in nl:
        for j in nl:
            if i==j or distance[(i,j)]=='infinity':
                nextn[(i,j)] = None
            else:
                nextn[(i,j)] = i
    # == end of next initialization
    # compute shortest distance
    for k in nl:
        for i in nl:
            for j in nl:
                if distance[(i,k)]=='infinity' or distance[(k,j)]=='infinity':
                    continue
                elif distance[(i,j)]=='infinity':
                    distance[(i,j)] = distance[(i,k)] + distance[(k,j)]
                    nextn[(i,j)] = nextn[(k,j)]
                elif distance[(i,j)] > distance[(i,k)] + distance[(k,j)]:
                    distance[(i,j)] = distance[(i,k)] + distance[(k,j)]
                    nextn[(i,j)] = nextn[(k,j)]
    # == end of compute shortest distance
    # check if graph has negative cycles
    for i in nl:
        if distance[(i,i)] < 0:
            # shortest distances are not valid
            # graph has negative cycle
            return (False, distance, nextn)
    return (True, distance, nextn)
def floyd_warshall_get_cycle(self, distance, nextn, element=None)

Api

floyd_warshall_get_cycle(self, distance, nextn, element = None)

Description

Finds a negative cycle in the graph.

Pre

(1) distance and nextn are outputs of floyd_warshall method. (2) The graph should have a negative cycle, , ie. distance[(i,i)] < 0 for some node i.

Return

Returns the list of nodes on the cycle. Ex: [i,j,k,…,r], where (i,j), (j,k) and (r,i) are some edges in the cycle.

Expand source code
def floyd_warshall_get_cycle(self, distance, nextn, element = None):
    '''
    API:
        floyd_warshall_get_cycle(self, distance, nextn, element = None)
    Description:
        Finds a negative cycle in the graph.
    Pre:
        (1) distance and nextn are outputs of floyd_warshall method.
        (2) The graph should have a negative cycle, , ie.
        distance[(i,i)] < 0 for some node i.
    Return:
        Returns the list of nodes on the cycle. Ex: [i,j,k,...,r], where
        (i,j), (j,k) and (r,i) are some edges in the cycle.
    '''
    nl = self.get_node_list()
    if element is None:
        for i in nl:
            if distance[(i,i)] < 0:
                # graph has a cycle on the path from i to i.
                element = i
                break
        else:
            raise Exception('Graph does not have a negative cycle!')
    elif distance[(element,element)] >= 0:
        raise Exception('Graph does not have a negative cycle that contains node '+str(element)+'!')
    # find the cycle on the path from i to i.
    cycle = [element]
    k = nextn[(element,element)]
    while k not in cycle:
        cycle.insert(1,k)
        k = nextn[(element,k)]
    if k==element:
        return cycle
    else:
        return self.floyd_warshall_get_cycle(distance, nextn, k)
def floyd_warshall_get_path(self, distance, nextn, i, j)

Api

floyd_warshall_get_path(self, distance, nextn, i, j):

Description

Finds shortest path between i and j using distance and nextn dictionaries.

Pre

(1) distance and nextn are outputs of floyd_warshall method. (2) The graph does not have a negative cycle, , ie. distance[(i,i)] >=0 for all node i.

Return

Returns the list of nodes on the path from i to j, ie. [i,…,j]

Expand source code
def floyd_warshall_get_path(self, distance, nextn, i, j):
    '''
    API:
        floyd_warshall_get_path(self, distance, nextn, i, j):
    Description:
        Finds shortest path between i and j using distance and nextn
        dictionaries.
    Pre:
        (1) distance and nextn are outputs of floyd_warshall method.
        (2) The graph does not have a negative cycle, , ie.
        distance[(i,i)] >=0 for all node i.
    Return:
        Returns the list of nodes on the path from i to j, ie. [i,...,j]
    '''
    if distance[(i,j)]=='infinity':
        return None
    k = nextn[(i,j)]
    path = self.floyd_warshall_get_path
    if i==k:
        return [i, j]
    else:
        return path(distance, nextn, i,k) + [k] + path(distance, nextn, k,j)
def get_degrees(self)

Api

get_degree(self)

Description

Returns degrees of nodes in dictionary format.

Return

Returns a dictionary of node degrees. Keys are node names, values are corresponding degrees.

Expand source code
def get_degrees(self):
    '''
    API:
        get_degree(self)
    Description:
        Returns degrees of nodes in dictionary format.
    Return:
        Returns a dictionary of node degrees. Keys are node names, values
        are corresponding degrees.
    '''
    degree = {}
    if self.attr['type'] is not DIRECTED_GRAPH:
        for n in self.get_node_list():
            degree[n] = len(self.get_neighbors(n))
        return degree
    else:
        for n in self.get_node_list():
            degree[n] = (len(self.get_in_neighbors(n)) +
                         len(self.get_out_neighbors(n)))
def get_diameter(self)

Api

get_diameter(self)

Description

Returns diameter of the graph. Diameter is defined as follows. distance(n,m): shortest unweighted path from n to m eccentricity(n) = $\max _m distance(n,m)$ diameter = $\min _n eccentricity(n) = \min _n \max _m distance(n,m)$

Return

Returns diameter of the graph.

Expand source code
def get_diameter(self):
    '''
    API:
        get_diameter(self)
    Description:
        Returns diameter of the graph. Diameter is defined as follows.
        distance(n,m): shortest unweighted path from n to m
        eccentricity(n) = $\max _m distance(n,m)$
        diameter = $\min _n eccentricity(n) = \min _n \max _m distance(n,m)$
    Return:
        Returns diameter of the graph.
    '''

    if self.attr['type'] is not UNDIRECTED_GRAPH:
        print('This function only works for undirected graphs')
        return
    diameter = 'infinity'
    eccentricity_n = 0
    for n in self.get_node_list():
        for m in self.get_node_list():
            path_n_m = self.search(n, destination = m, algo = 'BFS')
            if path_n_m is None:
                # this indicates there is no path from n to m, no diameter
                # is defined, since the graph is not connected, return
                # 'infinity'
                return 'infinity'
            distance_n_m = len(path_n_m)-1
            if distance_n_m > eccentricity_n:
                eccentricity_n = distance_n_m
        if diameter == 'infinity' or eccentricity_n > diameter:
            diameter = eccentricity_n
    return diameter
def get_edge_attr(self, n, m, attr)

API: get_edge_attr(self, n, m, attr) Description: Returns attribute attr of edge (n,m).

Input

n: Source node name. m: Sink node name. attr: Attribute of edge.

Pre

Graph should have this edge.

Return

Value of edge attribute attr.

Expand source code
def get_edge_attr(self, n, m, attr):
    '''
    API: get_edge_attr(self, n, m, attr)
    Description:
    Returns attribute attr of edge (n,m).
    Input:
        n: Source node name.
        m: Sink node name.
        attr: Attribute of edge.
    Pre:
        Graph should have this edge.
    Return:
        Value of edge attribute attr.
    '''
    if self.graph_type is DIRECTED_GRAPH:
        return self.edge_attr[(n,m)][attr]
    else:
        try:
            return self.edge_attr[(n,m)][attr]
        except KeyError:
            return self.edge_attr[(m,n)][attr]
def get_edge_cost(self, edge)

API: get_edge_cost(self, edge) Description: Returns cost attr of edge, required for minimum_spanning_tree_kruskal().

Input

edge: Tuple that represents edge, in (source,sink) form.

Return

Returns cost attribute value of the edge.

Expand source code
def get_edge_cost(self, edge):
    '''
    API: get_edge_cost(self, edge)
    Description:
    Returns cost attr of edge, required for minimum_spanning_tree_kruskal().
    Input:
        edge: Tuple that represents edge, in (source,sink) form.
    Return:
        Returns cost attribute value of the edge.
    '''
    return self.get_edge_attr(edge[0], edge[1], 'cost')
def get_edge_list(self)

API: get_edge_list(self) Description: Returns edge list.

Return

List of edges, edges are tuples and in (source,sink) format.

Expand source code
def get_edge_list(self):
    '''
    API: get_edge_list(self)
    Description:
    Returns edge list.
    Return:
        List of edges, edges are tuples and in (source,sink) format.
    '''
    return list(self.edge_attr.keys())
def get_edge_num(self)

API: get_edge_num(self) Description: Returns number of edges.

Return

Number of edges.

Expand source code
def get_edge_num(self):
    '''
    API: get_edge_num(self)
    Description:
    Returns number of edges.
    Return:
        Number of edges.
    '''
    return len(self.edge_attr)
def get_in_degrees(self)

Api

get_degree(self)

Description

Returns degrees of nodes in dictionary format.

Return

Returns a dictionary of node degrees. Keys are node names, values are corresponding degrees.

Expand source code
def get_in_degrees(self):
    '''
    API:
        get_degree(self)
    Description:
        Returns degrees of nodes in dictionary format.
    Return:
        Returns a dictionary of node degrees. Keys are node names, values
        are corresponding degrees.
    '''
    degree = {}
    if self.attr['type'] is not DIRECTED_GRAPH:
        print('This function only works for directed graphs')
        return
    for n in self.get_node_list():
        degree[n] = len(self.get_in_neighbors(n))
    return degree
def get_in_neighbors(self, name)

API: get_in_neighbors(self, name) Description: Returns list of in neighbors of given node.

Input

name: Node name.

Pre

Graph should have this node.

Return

List of in-neighbor node names.

Expand source code
def get_in_neighbors(self, name):
    '''
    API: get_in_neighbors(self, name)
    Description:
    Returns list of in neighbors of given node.
    Input:
        name: Node name.
    Pre:
        Graph should have this node.
    Return:
        List of in-neighbor node names.
    '''
    return self.in_neighbors[name]
def get_layout(self)

Api

get_layout(self) Description: Returns layout attribute of the graph.

Return

Returns layout attribute of the graph.

Expand source code
def get_layout(self):
    '''
    API:
        get_layout(self)
    Description:
    Returns layout attribute of the graph.
    Return:
        Returns layout attribute of the graph.
    '''
    return self.attr['layout']
def get_negative_cycle(self)

Api

get_negative_cycle(self)

Description

Finds and returns negative cost cycle using 'cost' attribute of arcs. Return value is a list of nodes representing cycle it is in the following form; n_1-n_2-…-n_k, when the cycle has k nodes.

Pre

Arcs should have 'cost' attribute.

Return

Returns a list of nodes in the cycle if a negative cycle exists, returns None otherwise.

Expand source code
def get_negative_cycle(self):
    '''
    API:
        get_negative_cycle(self)
    Description:
        Finds and returns negative cost cycle using 'cost' attribute of
        arcs. Return value is a list of nodes representing cycle it is in
        the following form; n_1-n_2-...-n_k, when the cycle has k nodes.
    Pre:
        Arcs should have 'cost' attribute.
    Return:
        Returns a list of nodes in the cycle if a negative cycle exists,
        returns None otherwise.
    '''
    nl = self.get_node_list()
    i = nl[0]
    (valid, distance, nextn) = self.floyd_warshall()
    if not valid:
        cycle = self.floyd_warshall_get_cycle(distance, nextn)
        return cycle
    else:
        return None
def get_neighbors(self, name)

API: get_neighbors(self, name) Description: Returns list of neighbors of given node.

Input

name: Node name.

Pre

Graph should have this node.

Return

List of neighbor node names.

Expand source code
def get_neighbors(self, name):
    '''
    API: get_neighbors(self, name)
    Description:
    Returns list of neighbors of given node.
    Input:
        name: Node name.
    Pre:
        Graph should have this node.
    Return:
        List of neighbor node names.
    '''
    return self.neighbors[name]
def get_node(self, name)

API: get_node(self, name) Description: Returns node object with the provided name.

Input

name: Name of the node.

Return

Returns node object if node exists, returns None otherwise.

Expand source code
def get_node(self, name):
    '''
    API: get_node(self, name)
    Description:
    Returns node object with the provided name.
    Input:
        name: Name of the node.
    Return:
        Returns node object if node exists, returns None otherwise.
    '''
    if name in self.nodes:
        return self.nodes[name]
    else:
        return None
def get_node_attr(self, name, attr)

API: get_node_attr(self, name, attr) Description: Returns attribute attr of given node.

Input

name: Name of node. attr: Attribute of node.

Pre

Graph should have this node.

Return

Value of node attribute attr.

Expand source code
def get_node_attr(self, name, attr):
    '''
    API: get_node_attr(self, name, attr)
    Description:
    Returns attribute attr of given node.
    Input:
        name: Name of node.
        attr: Attribute of node.
    Pre:
        Graph should have this node.
    Return:
        Value of node attribute attr.
    '''
    return self.get_node(name).get_attr(attr)
def get_node_list(self)

API: get_node_list(self) Description: Returns node list.

Return

List of nodes.

Expand source code
def get_node_list(self):
    '''
    API: get_node_list(self)
    Description:
    Returns node list.
    Return:
        List of nodes.
    '''
    return list(self.neighbors.keys())
def get_node_num(self)

API: get_node_num(self) Description: Returns number of nodes.

Return

Number of nodes.

Expand source code
def get_node_num(self):
    '''
    API: get_node_num(self)
    Description:
    Returns number of nodes.
    Return:
        Number of nodes.
    '''
    return len(self.neighbors)
def get_out_degrees(self)

Api

get_degree(self)

Description

Returns degrees of nodes in dictionary format.

Return

Returns a dictionary of node degrees. Keys are node names, values are corresponding degrees.

Expand source code
def get_out_degrees(self):
    '''
    API:
        get_degree(self)
    Description:
        Returns degrees of nodes in dictionary format.
    Return:
        Returns a dictionary of node degrees. Keys are node names, values
        are corresponding degrees.
    '''
    degree = {}
    if self.attr['type'] is not DIRECTED_GRAPH:
        print('This function only works for directed graphs')
        return
    for n in self.get_node_list():
        degree[n] = len(self.get_out_neighbors(n))
    return degree
def get_out_neighbors(self, name)

API: get_out_neighbors(self, name) Description: Returns list of out-neighbors of given node.

Input

name: Node name.

Pre

Graph should have this node.

Return

List of out-neighbor node names.

Expand source code
def get_out_neighbors(self, name):
    '''
    API: get_out_neighbors(self, name)
    Description:
    Returns list of out-neighbors of given node.
    Input:
        name: Node name.
    Pre:
        Graph should have this node.
    Return:
        List of out-neighbor node names.
    '''
    return self.neighbors[name]
def get_simplex_solution_graph(self)

Api

get_simplex_solution_graph(self):

Description

Assumes a feasible flow solution stored in 'flow' attribute's of arcs. Returns the graph with arcs that have flow between 0 and capacity.

Pre

(1) 'flow' attribute represents a feasible flow solution. See Pre section of min_cost_flow() for details.

Return

Graph instance that only has the arcs that have flow strictly between 0 and capacity.

Expand source code
def get_simplex_solution_graph(self):
    '''
    API:
        get_simplex_solution_graph(self):
    Description:
        Assumes a feasible flow solution stored in 'flow' attribute's of
        arcs. Returns the graph with arcs that have flow between 0 and
        capacity.
    Pre:
        (1) 'flow' attribute represents a feasible flow solution. See
        Pre section of min_cost_flow() for details.
    Return:
        Graph instance that only has the arcs that have flow strictly
        between 0 and capacity.
    '''
    simplex_g = Graph(type=DIRECTED_GRAPH)
    for i in self.neighbors:
        simplex_g.add_node(i)
    for e in self.edge_attr:
        flow_e = self.edge_attr[e]['flow']
        capacity_e = self.edge_attr[e]['capacity']
        if flow_e>0 and flow_e<capacity_e:
            simplex_g.add_edge(e[0], e[1])
    return simplex_g
def label_components(self, display=None)

API: label_components(self, display=None) Description: This method labels the nodes of an undirected graph with component numbers so that each node has the same label as all nodes in the same component. It will display the algortihm if display argument is provided.

Input

display: display method.

Pre

self.graph_type should be UNDIRECTED_GRAPH.

Post

Nodes will have 'component' attribute that will have component number as value.

Expand source code
def label_components(self, display = None):
    '''
    API: label_components(self, display=None)
    Description:
    This method labels the nodes of an undirected graph with component
    numbers so that each node has the same label as all nodes in the
    same component. It will display the algortihm if display argument is
    provided.
    Input:
        display: display method.
    Pre:
        self.graph_type should be UNDIRECTED_GRAPH.
    Post:
        Nodes will have 'component' attribute that will have component
        number as value.
    '''
    if self.graph_type == DIRECTED_GRAPH:
        raise Exception("label_components only works for ",
                        "undirected graphs")
    self.num_components = 0
    for n in self.get_node_list():
        self.get_node(n).set_attr('component', None)
    for n in self.neighbors:
        self.get_node(n).set_attr('label', '-')
    for n in self.get_node_list():
        if self.get_node(n).get_attr('component') == None:
            self.search(n, display=display,
                        component=self.num_components, algo='DFS')
            self.num_components += 1
def label_correcting_check_cycle(self, j, pred)

Api

label_correcting_check_cycle(self, j, pred)

Description

Checks if predecessor dictionary has a cycle, j represents the node that predecessor is recently updated.

Pre

(1) predecessor of source node should be None.

Input

j: node that predecessor is recently updated. pred: predecessor dictionary

Return

If there exists a cycle, returns the list that represents the cycle, otherwise it returns to None.

Expand source code
def label_correcting_check_cycle(self, j, pred):
    '''
    API:
        label_correcting_check_cycle(self, j, pred)
    Description:
        Checks if predecessor dictionary has a cycle, j represents the node
        that predecessor is recently updated.
    Pre:
        (1) predecessor of source node should be None.
    Input:
        j: node that predecessor is recently updated.
        pred: predecessor dictionary
    Return:
        If there exists a cycle, returns the list that represents the
        cycle, otherwise it returns to None.
    '''
    labelled = {}
    for n in self.neighbors:
        labelled[n] = None
    current = j
    while current != None:
        if labelled[current]==j:
            cycle = self.label_correcting_get_cycle(j, pred)
            return cycle
        labelled[current] = j
        current = pred[current]
    return None
def label_correcting_get_cycle(self, j, pred)

Api

label_correcting_get_cycle(self, labelled, pred)

Description

In label correcting check cycle it is decided pred has a cycle and nodes in the cycle are labelled. We will create a list of nodes in the cycle using labelled and pred inputs.

Pre

This method should be called from label_correcting_check_cycle(), unless you are sure about what you are doing.

Input

j: Node that predecessor is recently updated. We know that it is in the cycle pred: Predecessor dictionary that contains a cycle

Post

Returns a list of nodes that represents cycle. It is in [n_1, n_2, …, n_k] form where the cycle has k nodes.

Expand source code
def label_correcting_get_cycle(self, j, pred):
    '''
    API:
        label_correcting_get_cycle(self, labelled, pred)
    Description:
        In label correcting check cycle it is decided pred has a cycle and
        nodes in the cycle are labelled. We will create a list of nodes
        in the cycle using labelled and pred inputs.
    Pre:
        This method should be called from label_correcting_check_cycle(),
        unless you are sure about what you are doing.
    Input:
        j: Node that predecessor is recently updated. We know that it is
        in the cycle
        pred: Predecessor dictionary that contains a cycle
    Post:
        Returns a list of nodes that represents cycle. It is in
        [n_1, n_2, ..., n_k] form where the cycle has k nodes.
    '''
    cycle = []
    cycle.append(j)
    current = pred[j]
    while current!=j:
        cycle.append(current)
        current = pred[current]
    cycle.reverse()
    return cycle
def label_strong_component(self)

API: label_strong_component(self) Description: This method labels the nodes of a directed graph with component numbers so that each node has the same label as all nodes in the same component.

Pre

self.graph_type should be DIRECTED_GRAPH.

Post

Nodes will have 'component' attribute that will have component number as value. Changes 'index' attribute of nodes.

Expand source code
def label_strong_component(self):
    '''
    API: label_strong_component(self)
    Description:
    This method labels the nodes of a directed graph with component
    numbers so that each node has the same label as all nodes in the
    same component.
    Pre:
        self.graph_type should be DIRECTED_GRAPH.
    Post:
        Nodes will have 'component' attribute that will have component
        number as value. Changes 'index' attribute of nodes.
    '''
    self.num_components = 0
    self.tarjan()
def max_flow(self, source, sink, display=None, algo='DFS')

API: max_flow(self, source, sink, display=None) Description: Finds maximum flow from source to sink by a depth-first search based augmenting path algorithm.

Pre

Assumes a directed graph in which each arc has a 'capacity' attribute and for which there does does not exist both arcs (i,j) and (j, i) for any pair of nodes i and j.

Input

source: Source node name. sink: Sink node name. display: Display mode.

Post

The 'flow" attribute of each arc gives a maximum flow.

Expand source code
def max_flow(self, source, sink, display = None, algo = 'DFS'):
    '''
    API: max_flow(self, source, sink, display=None)
    Description:
    Finds maximum flow from source to sink by a depth-first search based
    augmenting path algorithm.
    Pre:
        Assumes a directed graph in which each arc has a 'capacity'
        attribute and for which there does does not exist both arcs (i,j)
        and (j, i) for any pair of nodes i and j.
    Input:
        source: Source node name.
        sink: Sink node name.
        display: Display mode.
    Post:
        The 'flow" attribute of each arc gives a maximum flow.
    '''
    if display is not None:
        old_display =  self.attr['display']
        self.attr['display'] = display
    nl = self.get_node_list()
    # set flow of all edges to 0
    for e in self.edge_attr:
        self.edge_attr[e]['flow'] = 0
        if 'capacity' in self.edge_attr[e]:
            capacity = self.edge_attr[e]['capacity']
            self.edge_attr[e]['label'] = str(capacity)+'/0'
        else:
            self.edge_attr[e]['capacity'] = INF
            self.edge_attr[e]['label'] = 'INF/0'
    while True:
        # find an augmenting path from source to sink using DFS
        if algo == 'DFS':
            q = Stack()
        elif algo == 'BFS':
            q = Queue()
        q.push(source)
        pred = {source:None}
        explored = [source]
        for n in nl:
            self.get_node(n).set_attr('color', 'black')
        for e in self.edge_attr:
            if self.edge_attr[e]['flow'] == 0:
                self.edge_attr[e]['color'] = 'black'
            elif self.edge_attr[e]['flow']==self.edge_attr[e]['capacity']:
                self.edge_attr[e]['color'] = 'red'
            else:
                self.edge_attr[e]['color'] = 'green'
        self.display()
        while not q.isEmpty():
            current = q.peek()
            q.remove(current)
            if current == sink:
                break
            out_neighbor = self.neighbors[current]
            in_neighbor = self.in_neighbors[current]
            neighbor = out_neighbor+in_neighbor
            for m in neighbor:
                if m in explored:
                    continue
                self.get_node(m).set_attr('color', 'yellow')
                if m in out_neighbor:
                    self.set_edge_attr(current, m, 'color', 'yellow')
                    available_capacity = (
                        self.get_edge_attr(current, m, 'capacity')-
                        self.get_edge_attr(current, m, 'flow'))
                else:
                    self.set_edge_attr(m, current, 'color', 'yellow')
                    available_capacity=self.get_edge_attr(m, current, 'flow')
                self.display()
                if available_capacity > 0:
                    self.get_node(m).set_attr('color', 'blue')
                    if m in out_neighbor:
                        self.set_edge_attr(current, m, 'color', 'blue')
                    else:
                        self.set_edge_attr(m, current, 'color', 'blue')
                    explored.append(m)
                    pred[m] = current
                    q.push(m)
                else:
                    self.get_node(m).set_attr('color', 'black')
                    if m in out_neighbor:
                        if (self.get_edge_attr(current, m, 'flow') ==
                            self.get_edge_attr(current, m, 'capacity')):
                            self.set_edge_attr(current, m, 'color', 'red')
                        elif self.get_edge_attr(current, m, 'flow') == 0:
                            self.set_edge_attr(current, m, 'color', 'black')
                        #else:
                        #    self.set_edge_attr(current, m, 'color', 'green')
                    else:
                        if (self.get_edge_attr(m, current, 'flow') ==
                            self.get_edge_attr(m, current, 'capacity')):
                            self.set_edge_attr(m, current, 'color', 'red')
                        elif self.get_edge_attr(m, current, 'flow') == 0:
                            self.set_edge_attr(m, current, 'color', 'black')
                        #else:
                        #    self.set_edge_attr(m, current, 'color', 'green')
                self.display()
        # if no path with positive capacity from source sink exists, stop
        if sink not in pred:
            break
        # find capacity of the path
        current = sink
        min_capacity = 'infinite'
        while True:
            m = pred[current]
            if (m,current) in self.edge_attr:
                arc_capacity = self.edge_attr[(m, current)]['capacity']
                flow = self.edge_attr[(m, current)]['flow']
                potential = arc_capacity-flow
                if min_capacity == 'infinite':
                    min_capacity = potential
                elif min_capacity > potential:
                    min_capacity = potential
            else:
                potential = self.edge_attr[(current, m)]['flow']
                if min_capacity == 'infinite':
                    min_capacity = potential
                elif min_capacity > potential:
                    min_capacity = potential
            if m == source:
                break
            current = m
        # update flows on the path
        current = sink
        while True:
            m = pred[current]
            if (m, current) in self.edge_attr:
                flow = self.edge_attr[(m, current)]['flow']
                capacity = self.edge_attr[(m, current)]['capacity']
                new_flow = flow+min_capacity
                self.edge_attr[(m, current)]['flow'] = new_flow
                if capacity == INF:
                    self.edge_attr[(m, current)]['label'] = \
                        'INF' + '/'+str(new_flow)
                else:
                    self.edge_attr[(m, current)]['label'] = \
                        str(capacity)+'/'+str(new_flow)
                if new_flow==capacity:
                    self.edge_attr[(m, current)]['color'] = 'red'
                else:
                    self.edge_attr[(m, current)]['color'] = 'green'
                self.display()
            else:
                flow = self.edge_attr[(current, m)]['flow']
                capacity = self.edge_attr[(current, m)]['capacity']
                new_flow = flow-min_capacity
                self.edge_attr[(current, m)]['flow'] = new_flow
                if capacity == INF:
                    self.edge_attr[(current, m)]['label'] = \
                        'INF' + '/'+str(new_flow)
                else:
                    self.edge_attr[(current, m)]['label'] = \
                        str(capacity)+'/'+str(new_flow)
                if new_flow==0:
                    self.edge_attr[(current, m)]['color'] = 'red'
                else:
                    self.edge_attr[(current, m)]['color'] = 'green'
                self.display()
            if m == source:
                break
            current = m
    if display is not None:
        self.attr['display'] = old_display
def max_flow_preflowpush(self, source, sink, algo='FIFO', display=None)

API: max_flow_preflowpush(self, source, sink, algo = 'FIFO', display = None) Description: Finds maximum flow from source to sink by a depth-first search based augmenting path algorithm.

Pre

Assumes a directed graph in which each arc has a 'capacity' attribute and for which there does does not exist both arcs (i,j) and (j,i) for any pair of nodes i and j.

Input

source: Source node name. sink: Sink node name. algo: Algorithm choice, 'FIFO', 'SAP' or 'HighestLabel'. display: display method.

Post

The 'flow' attribute of each arc gives a maximum flow.

Expand source code
def max_flow_preflowpush(self, source, sink, algo = 'FIFO', display = None):
    '''
    API: max_flow_preflowpush(self, source, sink, algo = 'FIFO',
                              display = None)
    Description:
    Finds maximum flow from source to sink by a depth-first search based
    augmenting path algorithm.
    Pre:
         Assumes a directed graph in which each arc has a 'capacity'
         attribute and for which there does does not exist both arcs (i,j)
         and (j,i) for any pair of nodes i and j.
    Input:
        source: Source node name.
        sink: Sink node name.
        algo: Algorithm choice, 'FIFO', 'SAP' or 'HighestLabel'.
        display: display method.
    Post:
        The 'flow' attribute of each arc gives a maximum flow.
    '''
    if display == None:
        display = self.attr['display']
    else:
        self.set_display_mode(display)
    nl = self.get_node_list()
    # set excess of all nodes to 0
    for n in nl:
        self.set_node_attr(n, 'excess', 0)
    # set flow of all edges to 0
    for e in self.edge_attr:
        self.edge_attr[e]['flow'] = 0
        if 'capacity' in self.edge_attr[e]:
            capacity = self.edge_attr[e]['capacity']
            self.edge_attr[e]['label'] = str(capacity)+'/0'
        else:
            self.edge_attr[e]['capacity'] = INF
            self.edge_attr[e]['label'] = 'INF/0'
    self.display()
    self.set_display_mode('off')
    self.search(sink, algo = 'UnweightedSPT', reverse = True)
    self.set_display_mode(display)
    disconnect = False
    for n in nl:
        if self.get_node_attr(n, 'distance') is None:
            disconnect = True
            self.set_node_attr(n, 'distance',
                               2*len(nl) + 1)
    if disconnect:
        print('Warning: graph contains nodes not connected to the sink...')
    if algo == 'FIFO':
        q = Queue()
    elif algo == 'SAP':
        q = Stack()
    elif algo == 'HighestLabel':
        q = PriorityQueue()
    for n in self.get_neighbors(source):
        capacity = self.get_edge_attr(source, n, 'capacity')
        self.set_edge_attr(source, n, 'flow', capacity)
        self.set_node_attr(n, 'excess', capacity)
        excess = self.get_node_attr(source, 'excess')
        self.set_node_attr(source, 'excess', excess - capacity)
        if algo == 'FIFO' or algo == 'SAP':
            q.push(n)
        elif algo == 'HighestLabel':
            q.push(n, -1)
    self.set_node_attr(source, 'distance', len(nl))
    self.show_flow()
    while not q.isEmpty():
        relabel = True
        current = q.peek()
        neighbors = (self.get_neighbors(current) +
                     self.get_in_neighbors(current))
        for n in neighbors:
            pushed = self.process_edge_flow(source, sink, current, n, algo,
                                            q)
            if pushed:
                self.show_flow()
                if algo == 'FIFO':
                    '''With FIFO, we need to add the neighbors to the queue
                    before the current is added back in or the nodes will
                    be out of order
                    '''
                    if q.peek(n) is None and n != source and n != sink:
                        q.push(n)
                    '''Keep pushing while there is excess'''
                    if self.get_node_attr(current, 'excess') > 0:
                        continue
                '''If we were able to push, then there we should not
                relabel
                '''
                relabel = False
                break
        q.remove(current)
        if current != sink:
            if relabel:
                self.relabel(current)
                self.show_flow()
            if self.get_node_attr(current, 'excess') > 0:
                if algo == 'FIFO' or algo == 'SAP':
                    q.push(current)
                elif algo == 'HighestLabel':
                    q.push(current, -self.get_node_attr(current,
                                                        'distance'))
        if pushed and q.peek(n) is None and n != source:
            if algo == 'SAP':
                q.push(n)
            elif algo == 'HighestLabel':
                q.push(n, -self.get_node_attr(n, 'distance'))
def min_cost_flow(self, display=None, **args)

Api

min_cost_flow(self, display='off', **args)

Description

Solves minimum cost flow problem using node/edge attributes with the algorithm specified.

Pre

(1) Assumes a directed graph in which each arc has 'capacity' and 'cost' attributes. (2) Nodes should have 'demand' attribute. This value should be positive for supply and negative for demand, and 0 for transhipment nodes. (3) The graph should be connected. (4) Assumes (i,j) and (j,i) does not exist together. Needed when solving max flow. (max flow problem is solved to get a feasible flow).

Input

display: 'off' for no display, 'matplotlib' for live update of tree args: may have the following display: display method, if not given current mode (the one specified by init or set_display) will be used. algo: determines algorithm to use, can be one of the following 'simplex': network simplex algorithm 'cycle_canceling': cycle canceling algorithm 'simplex' is used if not given. see Network Flows by Ahuja et al. for details of algorithms. pivot: valid if algo is 'simlex', determines pivoting rule for simplex, may be one of the following; 'first_eligible', 'dantzig' or 'scaled'. 'dantzig' is used if not given. see Network Flows by Ahuja et al. for pivot rules. root: valid if algo is 'simlex', specifies the root node for simplex algorithm. It is name of the one of the nodes. It will be chosen randomly if not provided.

Post

The 'flow' attribute of each arc gives the optimal flows. 'distance' attribute of the nodes are also changed during max flow solution process.

Examples

g.min_cost_flow(): solves minimum cost feasible flow problem using simplex algorithm with dantzig pivoting rule. See pre section for details. g.min_cost_flow(algo='cycle_canceling'): solves minimum cost feasible flow problem using cycle canceling agorithm. g.min_cost_flow(algo='simplex', pivot='scaled'): solves minimum cost feasible flow problem using network simplex agorithm with scaled pivot rule.

Expand source code
def min_cost_flow(self, display = None, **args):
    '''
    API:
        min_cost_flow(self, display='off', **args)
    Description:
        Solves minimum cost flow problem using node/edge attributes with
        the algorithm specified.
    Pre:
        (1) Assumes a directed graph in which each arc has 'capacity' and
        'cost' attributes.
        (2) Nodes should have 'demand' attribute. This value should be
        positive for supply and negative for demand, and 0 for transhipment
        nodes.
        (3) The graph should be connected.
        (4) Assumes (i,j) and (j,i) does not exist together. Needed when
        solving max flow. (max flow problem is solved to get a feasible
        flow).
    Input:
        display: 'off' for no display, 'matplotlib' for live update of tree
        args: may have the following
            display: display method, if not given current mode (the one
                specified by __init__ or set_display) will be used.
            algo: determines algorithm to use, can be one of the following
                'simplex': network simplex algorithm
                'cycle_canceling': cycle canceling algorithm
                'simplex' is used if not given.
                see Network Flows by Ahuja et al. for details of algorithms.
            pivot: valid if algo is 'simlex', determines pivoting rule for
                simplex, may be one of the following; 'first_eligible',
                'dantzig' or 'scaled'.
                'dantzig' is used if not given.
                see Network Flows by Ahuja et al. for pivot rules.
            root: valid if algo is 'simlex', specifies the root node for
                simplex algorithm. It is name of the one of the nodes. It
                will be chosen randomly if not provided.
    Post:
        The 'flow' attribute of each arc gives the optimal flows.
        'distance' attribute of the nodes are also changed during max flow
        solution process.
    Examples:
        g.min_cost_flow():
            solves minimum cost feasible flow problem using simplex
            algorithm with dantzig pivoting rule.
            See pre section for details.
        g.min_cost_flow(algo='cycle_canceling'):
            solves minimum cost feasible flow problem using cycle canceling
            agorithm.
        g.min_cost_flow(algo='simplex', pivot='scaled'):
            solves minimum cost feasible flow problem using network simplex
            agorithm with scaled pivot rule.
    '''
    if display is None:
        display = self.attr['display']
    if 'algo' in args:
        algorithm = args['algo']
    else:
        algorithm = 'simplex'
    if algorithm == 'simplex':
        if 'root' in args:
            root = args['root']
        else:
            for k in self.neighbors:
                root = k
                break
        if 'pivot' in args:
            if not self.network_simplex(display, args['pivot'], root):
                print('problem is infeasible')
        else:
            if not self.network_simplex(display, 'dantzig', root):
                print('problem is infeasible')
    elif algorithm == 'cycle_canceling':
        if not self.cycle_canceling(display):
            print('problem is infeasible')
    else:
        print(args['algo'], 'is not a defined algorithm. Exiting.')
        return
def minimum_spanning_tree_kruskal(self, display=None, components=None)

API: minimum_spanning_tree_kruskal(self, display = None, components = None) Description: Determines a minimum spanning tree using Kruskal's Algorithm.

Input

display: Display method. component: component number.

Post

'color' attribute of nodes and edges may change.

Return

Returns list of edges where edges are tuples in (source,sink) format.

Expand source code
def minimum_spanning_tree_kruskal(self, display = None, components = None):
    '''
    API: minimum_spanning_tree_kruskal(self, display = None,
                                       components = None)
    Description:
    Determines a minimum spanning tree using Kruskal's Algorithm.
    Input:
        display: Display method.
        component: component number.
    Post:
        'color' attribute of nodes and edges may change.
    Return:
        Returns list of edges where edges are tuples in (source,sink)
        format.
    '''
    if display == None:
        display = self.attr['display']
    else:
        self.set_display_mode(display)
    if components is None:
        components = DisjointSet(display = display, layout = 'dot',
                                 optimize = False)
    sorted_edge_list = sorted(self.get_edge_list(), key=self.get_edge_cost)
    edges = []
    for n in self.get_node_list():
        components.add([n])
    components.display()
    for e in sorted_edge_list:
        if len(edges) == len(self.get_node_list()) - 1:
            break
        self.set_edge_attr(e[0], e[1], 'color', 'yellow')
        self.display()
        if components.union(e[0], e[1]):
            self.set_edge_attr(e[0], e[1], 'color', 'green')
            self.display()
            edges.append(e)
        else:
            self.set_edge_attr(e[0], e[1], 'color', 'black')
            self.display()
        components.display()
    return edges
def minimum_spanning_tree_prim(self, source, display=None, q=<coinor.blimpy.Queues.PriorityQueue object>)

API: minimum_spanning_tree_prim(self, source, display = None, q = PriorityQueue()) Description: Determines a minimum spanning tree of all nodes reachable from source using Prim's Algorithm.

Input

source: Name of source node. display: Display method. q: Data structure that holds nodes to be processed in a queue.

Post

'color', 'distance', 'component' attribute of nodes and edges may change.

Return

Returns predecessor tree in dictionary format.

Expand source code
def minimum_spanning_tree_prim(self, source, display = None,
                               q = PriorityQueue()):
    '''
    API: minimum_spanning_tree_prim(self, source, display = None,
                                    q = PriorityQueue())
    Description:
    Determines a minimum spanning tree of all nodes reachable
    from source using Prim's Algorithm.
    Input:
        source: Name of source node.
        display: Display method.
        q: Data structure that holds nodes to be processed in a queue.
    Post:
        'color', 'distance', 'component' attribute of nodes and edges may
        change.
    Return:
        Returns predecessor tree in dictionary format.
    '''
    if display == None:
        display = self.attr['display']
    else:
        self.set_display_mode(display)
    if isinstance(q, PriorityQueue):
        addToQ = q.push
        removeFromQ = q.pop
        peek = q.peek
        isEmpty = q.isEmpty
    neighbors = self.get_neighbors
    pred = {}
    addToQ(source)
    done = False
    while not isEmpty() and not done:
        current = removeFromQ()
        self.set_node_attr(current, 'color', 'blue')
        if current != source:
            self.set_edge_attr(pred[current], current, 'color', 'green')
        self.display()
        for n in neighbors(current):
            if self.get_node_attr(n, 'color') != 'green':
                self.set_edge_attr(current, n, 'color', 'yellow')
                self.display()
                new_estimate = self.get_edge_attr(current, n, 'cost')
                if not n in pred or new_estimate < peek(n)[0]:
                    pred[n] = current
                    self.set_node_attr(n, 'color', 'red')
                    self.set_node_attr(n, 'label', new_estimate)
                    addToQ(n, new_estimate)
                    self.display()
                    self.set_node_attr(n, 'color', 'black')
                self.set_edge_attr(current, n, 'color', 'black')
        self.set_node_attr(current, 'color', 'green')
        self.display()
    return pred
def network_simplex(self, display, pivot, root)

Api

network_simplex(self, display, pivot, root)

Description

Solves minimum cost feasible flow problem using network simplex algorithm. It is recommended to use min_cost_flow(algo='simplex') instead of using network_simplex() directly. Returns True when an optimal solution is found, returns False otherwise. 'flow' attribute values of arcs should be considered as junk when returned False.

Pre

(1) check Pre section of min_cost_flow()

Input

pivot: specifies pivot rule. Check min_cost_flow() display: 'off' for no display, 'matplotlib' for live update of spanning tree. root: Root node for the underlying spanning trees that will be generated by network simplex algorthm.

Post

(1) Changes 'flow' attribute of edges.

Return

Returns True when an optimal solution is found, returns False otherwise.

Expand source code
def network_simplex(self, display, pivot, root):
    '''
    API:
        network_simplex(self, display, pivot, root)
    Description:
        Solves minimum cost feasible flow problem using network simplex
        algorithm. It is recommended to use min_cost_flow(algo='simplex')
        instead of using network_simplex() directly. Returns True when an
        optimal solution is found, returns False otherwise. 'flow' attribute
        values of arcs should be considered as junk when returned False.
    Pre:
        (1) check Pre section of min_cost_flow()
    Input:
        pivot: specifies pivot rule. Check min_cost_flow()
        display: 'off' for no display, 'matplotlib' for live update of
        spanning tree.
        root: Root node for the underlying spanning trees that will be
        generated by network simplex algorthm.
    Post:
        (1) Changes 'flow' attribute of edges.
    Return:
        Returns True when an optimal solution is found, returns
        False otherwise.
    '''
    # ==== determine an initial tree structure (T,L,U)
    # find a feasible flow
    if not self.find_feasible_flow():
        return False
    t = self.simplex_find_tree()
    self.set_display_mode(display)
    # mark spanning tree arcs
    self.simplex_mark_st_arcs(t)
    # display initial spanning tree
    t.simplex_redraw(display, root)
    t.set_display_mode(display)
    #t.display()
    self.display()
    # set predecessor, depth and thread indexes
    t.simplex_search(root, 1)
    # compute potentials
    self.simplex_compute_potentials(t, root)
    # while some nontree arc violates optimality conditions
    while not self.simplex_optimal(t):
        self.display()
        # select an entering arc (k,l)
        (k,l) = self.simplex_select_entering_arc(t, pivot)
        self.simplex_mark_entering_arc(k, l)
        self.display()
        # determine leaving arc
        ((p,q), capacity, cycle)=self.simplex_determine_leaving_arc(t,k,l)
        # mark leaving arc
        self.simplex_mark_leaving_arc(p, q)
        self.display()
        self.simplex_remove_arc(t, p, q, capacity, cycle)
        # display after arc removed
        self.display()
        self.simplex_mark_st_arcs(t)
        self.display()
        # set predecessor, depth and thread indexes
        t.simplex_redraw(display, root)
        #t.display()
        t.simplex_search(root, 1)
        # compute potentials
        self.simplex_compute_potentials(t, root)
    return True
def page_rank(self, damping_factor=0.85, max_iterations=100, min_delta=1e-05)

Api

page_rank(self, damping_factor=0.85, max_iterations=100, min_delta=0.00001)

Description

Compute and return the page-rank of a directed graph. This function was originally taken from here and modified for this graph class: http://code.google.com/p/python-graph/source/browse/ trunk/core/pygraph/algorithms/pagerank.py

Input

damping_factor: Damping factor. max_iterations: Maximum number of iterations. min_delta: Smallest variation required to have a new iteration.

Pre

Graph should be a directed graph.

Return

Returns dictionary of page-ranks. Keys are node names, values are corresponding page-ranks.

Expand source code
def page_rank(self, damping_factor=0.85, max_iterations=100,
              min_delta=0.00001):
    '''
    API:
        page_rank(self, damping_factor=0.85, max_iterations=100,
              min_delta=0.00001)
    Description:
        Compute and return the page-rank of a directed graph.
        This function was originally taken from here and modified for this
        graph class: http://code.google.com/p/python-graph/source/browse/
        trunk/core/pygraph/algorithms/pagerank.py
    Input:
        damping_factor: Damping factor.
        max_iterations: Maximum number of iterations.
        min_delta: Smallest variation required to have a new iteration.
    Pre:
        Graph should be a directed graph.
    Return:
        Returns dictionary of page-ranks. Keys are node names, values are
        corresponding page-ranks.
    '''
    nodes = self.get_node_list()
    graph_size = len(nodes)
    if graph_size == 0:
        return {}
    #value for nodes without inbound links
    min_value = old_div((1.0-damping_factor),graph_size)
    # itialize the page rank dict with 1/N for all nodes
    pagerank = dict.fromkeys(nodes, old_div(1.0,graph_size))
    for _ in range(max_iterations):
        diff = 0 #total difference compared to last iteraction
        # computes each node PageRank based on inbound links
        for node in nodes:
            rank = min_value
            for referring_page in self.get_in_neighbors(node):
                rank += (damping_factor * pagerank[referring_page] /
                         len(self.get_neighbors(referring_page)))
            diff += abs(pagerank[node] - rank)
            pagerank[node] = rank
        #stop if PageRank has converged
        if diff < min_delta:
            break
    return pagerank
def print_flow(self)

Api

print_flow(self)

Description

Prints all positive flows to stdout. This method can be used for debugging purposes.

Expand source code
def print_flow(self):
    '''
    API:
        print_flow(self)
    Description:
        Prints all positive flows to stdout. This method can be used for
        debugging purposes.
    '''
    print('printing current edge, flow, capacity')
    for e in self.edge_attr:
        if self.edge_attr[e]['flow']!=0:
            print(e, str(self.edge_attr[e]['flow']).ljust(4), end=' ')
            print(str(self.edge_attr[e]['capacity']).ljust(4))
def process_edge_dijkstra(self, current, neighbor, pred, q, component)

API: process_edge_dijkstra(self, current, neighbor, pred, q, component) Description: Used by search() method if the algo argument is 'Dijkstra'. Processes edges along Dijkstra's algorithm. User does not need to call this method directly.

Input

current: Name of the current node. neighbor: Name of the neighbor node. pred: Predecessor tree. q: Data structure that holds nodes to be processed in a queue. component: component number.

Post

'color' attribute of nodes and edges may change.

Expand source code
def process_edge_dijkstra(self, current, neighbor, pred, q, component):
    '''
    API: process_edge_dijkstra(self, current, neighbor, pred, q, component)
    Description:
    Used by search() method if the algo argument is 'Dijkstra'. Processes
    edges along Dijkstra's algorithm. User does not need to call this
    method directly.
    Input:
        current: Name of the current node.
        neighbor: Name of the neighbor node.
        pred: Predecessor tree.
        q: Data structure that holds nodes to be processed in a queue.
        component: component number.
    Post:
        'color' attribute of nodes and edges may change.
    '''
    if current is None:
        self.get_node(neighbor).set_attr('color', 'red')
        self.get_node(neighbor).set_attr('label', 0)
        q.push(neighbor, 0)
        self.display()
        self.get_node(neighbor).set_attr('color', 'black')
        return
    new_estimate = (q.get_priority(current) +
                    self.get_edge_attr(current, neighbor, 'cost'))
    if neighbor not in pred or new_estimate < q.get_priority(neighbor):
        pred[neighbor] = current
        self.get_node(neighbor).set_attr('color', 'red')
        self.get_node(neighbor).set_attr('label', new_estimate)
        q.push(neighbor, new_estimate)
        self.display()
        self.get_node(neighbor).set_attr('color', 'black')
def process_edge_flow(self, source, sink, i, j, algo, q)

API: process_edge_flow(self, source, sink, i, j, algo, q) Description: Used by by max_flow_preflowpush() method. Processes edges along prefolow push.

Input

source: Source node name of flow graph. sink: Sink node name of flow graph. i: Source node in the processed edge (tail of arc). j: Sink node in the processed edge (head of arc).

Post

The 'flow' and 'excess' attributes of nodes may get updated.

Return

Returns False if residual capacity is 0, True otherwise.

Expand source code
def process_edge_flow(self, source, sink, i, j, algo, q):
    '''
    API: process_edge_flow(self, source, sink, i, j, algo, q)
    Description:
    Used by by max_flow_preflowpush() method. Processes edges along
    prefolow push.
    Input:
        source: Source node name of flow graph.
        sink: Sink node name of flow graph.
        i: Source node in the processed edge (tail of arc).
        j: Sink node in the processed edge (head of arc).
    Post:
        The 'flow' and 'excess' attributes of nodes may get updated.
    Return:
        Returns False if residual capacity is 0, True otherwise.
    '''
    if (self.get_node_attr(i, 'distance') !=
        self.get_node_attr(j, 'distance') + 1):
        return False
    if (i, j) in self.edge_attr:
        edge = (i, j)
        capacity = self.get_edge_attr(i, j, 'capacity')
        mult = 1
    else:
        edge = (j, i)
        capacity = 0
        mult = -1
    flow = mult*self.edge_attr[edge]['flow']
    residual_capacity = capacity - flow
    if residual_capacity == 0:
        return False
    excess_i = self.get_node_attr(i, 'excess')
    excess_j = self.get_node_attr(j, 'excess')
    push_amount = min(excess_i, residual_capacity)
    self.edge_attr[edge]['flow'] = mult*(flow + push_amount)
    self.set_node_attr(i, 'excess', excess_i - push_amount)
    self.set_node_attr(j, 'excess', excess_j + push_amount)
    return True
def process_edge_prim(self, current, neighbor, pred, q, component)

API: process_edge_prim(self, current, neighbor, pred, q, component) Description: Used by search() method if the algo argument is 'Prim'. Processes edges along Prim's algorithm. User does not need to call this method directly.

Input

current: Name of the current node. neighbor: Name of the neighbor node. pred: Predecessor tree. q: Data structure that holds nodes to be processed in a queue. component: component number.

Post

'color' attribute of nodes and edges may change.

Expand source code
def process_edge_prim(self, current, neighbor, pred, q, component):
    '''
    API: process_edge_prim(self, current, neighbor, pred, q, component)
    Description:
    Used by search() method if the algo argument is 'Prim'. Processes
    edges along Prim's algorithm. User does not need to call this method
    directly.
    Input:
        current: Name of the current node.
        neighbor: Name of the neighbor node.
        pred: Predecessor tree.
        q: Data structure that holds nodes to be processed in a queue.
        component: component number.
    Post:
        'color' attribute of nodes and edges may change.
    '''
    if current is None:
        self.get_node(neighbor).set_attr('color', 'red')
        self.get_node(neighbor).set_attr('label', 0)
        q.push(neighbor, 0)
        self.display()
        self.get_node(neighbor).set_attr('color', 'black')
        return
    new_estimate = self.get_edge_attr(current, neighbor, 'cost')
    if not neighbor in pred or new_estimate < q.get_priority(neighbor):
        pred[neighbor] = current
        self.get_node(neighbor).set_attr('color', 'red')
        self.get_node(neighbor).set_attr('label', new_estimate)
        q.push(neighbor, new_estimate)
        self.display()
        self.get_node(neighbor).set_attr('color', 'black')

API: process_edge_search(self, current, neighbor, pred, q, component, algo, **kargs) Description: Used by search() method. Processes edges according to the underlying algortihm. User does not need to call this method directly.

Input

current: Name of the current node. neighbor: Name of the neighbor node. pred: Predecessor tree. q: Data structure that holds nodes to be processed in a queue. component: component number. algo: Search algorithm. See search() documentation. kwargs: Keyword arguments.

Post

'color', 'distance', 'component' attribute of nodes and edges may change.

Expand source code
def process_edge_search(self, current, neighbor, pred, q, component, algo,
                        **kargs):
    '''
    API: process_edge_search(self, current, neighbor, pred, q, component,
                             algo, **kargs)
    Description:
    Used by search() method. Processes edges according to the underlying
    algortihm. User does not need to call this method directly.
    Input:
        current: Name of the current node.
        neighbor: Name of the neighbor node.
        pred: Predecessor tree.
        q: Data structure that holds nodes to be processed in a queue.
        component: component number.
        algo: Search algorithm. See search() documentation.
        kwargs: Keyword arguments.
    Post:
        'color', 'distance', 'component' attribute of nodes and edges may
        change.
    '''
    if algo == 'Dijkstra':
        return self.process_edge_dijkstra(current, neighbor, pred, q,
                                          component)
    if algo == 'Prim':
        return self.process_edge_prim(current, neighbor, pred, q,
                                      component)
    neighbor_node = self.get_node(neighbor)
    if current == None:
        neighbor_node.set_attr('distance', 0)
        if isinstance(q, PriorityQueue):
            q.push(neighbor, 0)
        else:
            q.push(neighbor)
        if component != None:
            neighbor_node.set_attr('component', component)
            neighbor_node.set_attr('label', component)
        else:
            neighbor_node.set_attr('label', 0)
        return
    if isinstance(q, PriorityQueue):
        current_priority = q.get_priority(neighbor)
        if algo == 'UnweightedSPT' or algo == 'BFS':
            priority = self.get_node(current).get_attr('distance') + 1
        if algo == 'DFS':
            priority = -self.get_node(current).get_attr('distance') - 1
        if current_priority is not None and priority >= current_priority:
            return
        q.push(neighbor, priority)
        if algo == 'UnweightedSPT' or algo == 'BFS':
            neighbor_node.set_attr('distance', priority)
        if algo == 'DFS':
            neighbor_node.set_attr('depth', -priority)
    else:
        distance = self.get_node(current).get_attr('distance') + 1
        if ((algo == 'UnweightedSPT' or algo == 'BFS') and
            neighbor_node.get_attr('distance') is not None):
            return
        neighbor_node.set_attr('distance', distance)
        neighbor_node.set_attr('label', str(distance))
        q.push(neighbor)
    pred[neighbor] = current
    neighbor_node.set_attr('color', 'red')
    if component != None:
        neighbor_node.set_attr('component', component)
        neighbor_node.set_attr('label', component)
    self.display()

API: process_node_search(self, node, q, **kwargs) Description: Used by search() method. Process nodes along the search. Should not be called by user directly.

Input

node: Name of the node being processed. q: Queue data structure. kwargs: Keyword arguments.

Post

'priority' attribute of the node may get updated.

Expand source code
def process_node_search(self, node, q, **kwargs):
    '''
    API: process_node_search(self, node, q, **kwargs)
    Description:
    Used by search() method. Process nodes along the search. Should not be
    called by user directly.
    Input:
        node: Name of the node being processed.
        q: Queue data structure.
        kwargs: Keyword arguments.
    Post:
        'priority' attribute of the node may get updated.
    '''
    if isinstance(q, PriorityQueue):
        self.get_node(node).set_attr('priority', q.get_priority(node))
def random(self, numnodes=10, degree_range=(2, 4), length_range=(1, 10), density=None, edge_format=None, node_format=None, Euclidean=False, seedInput=0, add_labels=True, parallel_allowed=False, node_selection='closest', scale=10, scale_cost=5)

Api

random(self, numnodes = 10, degree_range = None, length_range = None, density = None, edge_format = None, node_format = None, Euclidean = False, seedInput = 0)

Description

Populates graph with random edges and nodes.

Input

numnodes: Number of nodes to add. degree_range: A tuple that has lower and upper bounds of degree for a node. length_range: A tuple that has lower and upper bounds for 'cost' attribute of edges. density: Density of edges, ie. 0.5 indicates a node will approximately have edge to half of the other nodes. edge_format: Dictionary that specifies attribute values for edges. node_format: Dictionary that specifies attribute values for nodes. Euclidean: Creates an Euclidean graph (Euclidean distance between nodes) if True. seedInput: Seed that will be used for random number generation.

Pre

It is recommended to call this method on empty Graph objects.

Post

Graph will be populated by nodes and edges.

Expand source code
def random(self, numnodes = 10, degree_range = (2, 4), length_range = (1, 10),
           density = None, edge_format = None, node_format = None,
           Euclidean = False, seedInput = 0, add_labels = True,
           parallel_allowed = False, node_selection = 'closest',
           scale = 10, scale_cost = 5):
    '''
    API:
        random(self, numnodes = 10, degree_range = None, length_range = None,
           density = None, edge_format = None, node_format = None,
           Euclidean = False, seedInput = 0)
    Description:
        Populates graph with random edges and nodes.
    Input:
        numnodes: Number of nodes to add.
        degree_range: A tuple that has lower and upper bounds of degree for
        a node.
        length_range: A tuple that has lower and upper bounds for 'cost'
        attribute of edges.
        density: Density of edges, ie. 0.5 indicates a node will
        approximately have edge to half of the other nodes.
        edge_format: Dictionary that specifies attribute values for edges.
        node_format: Dictionary that specifies attribute values for nodes.
        Euclidean: Creates an Euclidean graph (Euclidean distance between
        nodes) if True.
        seedInput: Seed that will be used for random number generation.
    Pre:
        It is recommended to call this method on empty Graph objects.
    Post:
        Graph will be populated by nodes and edges.
    '''
    random.seed(seedInput)
    if edge_format == None:
        edge_format = {'fontsize':10,
                       'fontcolor':'blue'}
    if node_format == None:
        node_format = {'height':0.5,
                       'width':0.5,
                       'fixedsize':'true',
                       'fontsize':10,
                       'fontcolor':'red',
                       'shape':'circle',
                       }
    if Euclidean == False:
        for m in range(numnodes):
            self.add_node(m, **node_format)
        if degree_range is not None and density is None:
            for m in range(numnodes):
                degree = random.randint(degree_range[0], degree_range[1])
                i = 0
                while i < degree:
                    n = random.randint(1, numnodes-1)
                    if (((m,n) not in self.edge_attr and m != n) and
                        (parallel_allowed or (n, m) not in self.edge_attr)):
                        if length_range is not None:
                            length = random.randint(length_range[0],
                                                    length_range[1])
                            self.add_edge(m, n, cost = length, **edge_format)
                            if add_labels:
                                self.set_edge_attr(m, n, 'label', str(length))
                            else:
                                self.add_edge(m, n, **edge_format)
                    i += 1
        elif density != None:
            for m in range(numnodes):
                if self.graph_type == DIRECTED_GRAPH:
                    numnodes2 = numnodes
                else:
                    numnodes2 = m
                for n in range(numnodes2):
                    if ((parallel_allowed or (n, m) not in self.edge_attr)
                        and m != n):
                        if random.random() < density:
                            if length_range is not None:
                                length = random.randint(length_range[0],
                                                 length_range[1])
                                self.add_edge(m, n, cost = length,
                                              **edge_format)
                                if add_labels:
                                    self.set_edge_attr(m, n, 'label', str(length))
                            else:
                                self.add_edge(m, n, **edge_format)
        else:
            print("Must set either degree range or density")
    else:
        for m in range(numnodes):
            ''' Assigns random coordinates (between 1 and 20) to the nodes
            '''
            x = random.random()*scale
            y = random.random()*scale
            self.add_node(m, locationx = x, locationy = y,
                          pos = '"'+str(x) + "," + str(y)+'!"',
                          **node_format)
        if degree_range is not None and density is None:
            for m in range(numnodes):
                degree = random.randint(degree_range[0], degree_range[1])
                i = 0
                neighbors = []
                if node_selection == 'random':
                    while i < degree:
                        length = round((((self.get_node(n).get_attr('locationx') -
                                          self.get_node(m).get_attr('locationx')) ** 2 +
                                         (self.get_node(n).get_attr('locationy') -
                                          self.get_node(m).get_attr('locationy')) ** 2) ** 0.5)*scale_cost,
                                       0)
                        if (((m,n) not in self.edge_attr and m != n) and
                            (parallel_allowed or (n, m) not in self.edge_attr)):
                            neighbors.append(random.randint(0, numnodes-1))
                            self.add_edge(m, n, cost = int(length), **edge_format)
                            if add_labels:
                                self.set_edge_attr(m, n, 'label', str(int(length)))
                            i += 1
                elif node_selection == 'closest':
                    lengths = []
                    for n in range(numnodes):
                        lengths.append((n, round((((self.get_node(n).get_attr('locationx') -
                                                    self.get_node(m).get_attr('locationx')) ** 2 +
                                                   (self.get_node(n).get_attr('locationy') -
                                                    self.get_node(m).get_attr('locationy')) ** 2) ** 0.5)*scale_cost,
                                                 0)))
                    lengths.sort(key = lambda l : l[1])
                    for i in range(degree+1):
                        if not (lengths[i][0] == m or self.check_edge(m, lengths[i][0])):
                            self.add_edge(m, lengths[i][0], cost = int(lengths[i][1]), **edge_format)
                            if add_labels:
                                self.set_edge_attr(m, lengths[i][0], 'label', str(int(lengths[i][1])))
                else:
                    print("Unknown node selection rule...exiting")
                    return
        elif density != None:
            for m in range(numnodes):
                if self.graph_type == DIRECTED_GRAPH:
                    numnodes2 = numnodes
                else:
                    numnodes2 = m
                for n in range(numnodes2):
                    if ((parallel_allowed or (n, m) not in self.edge_attr)
                        and m != n):
                        if random.random() < density:
                            if length_range is None:
                                ''' calculates the euclidean norm and round it
                                to an integer '''
                                length = round((((self.get_node(n).get_attr('locationx') -
                                                  self.get_node(m).get_attr('locationx')) ** 2 +
                                                 (self.get_node(n).get_attr('locationy') -
                                                  self.get_node(m).get_attr('locationy')) ** 2) ** 0.5), 0)
                                self.add_edge(m, n, cost = int(length), **edge_format)
                                if add_labels:
                                    self.set_edge_attr(m, n, 'label', str(int(length)))
                            else:
                                self.add_edge(m, n, **edge_format)
        else:
            print("Must set either degree range or density")
def relabel(self, i)

API: relabel(self, i) Description: Used by max_flow_preflowpush() method for relabelling node i.

Input

i: Node that is being relabelled.

Post

'distance' attribute of node i is updated.

Expand source code
def relabel(self, i):
    '''
    API: relabel(self, i)
    Description:
    Used by max_flow_preflowpush() method for relabelling node i.
    Input:
        i: Node that is being relabelled.
    Post:
        'distance' attribute of node i is updated.
    '''
    min_distance = 2*len(self.get_node_list()) + 1
    for j in self.get_neighbors(i):
        if (self.get_node_attr(j, 'distance') < min_distance and
            (self.get_edge_attr(i, j, 'flow') <
             self.get_edge_attr(i, j, 'capacity'))):
            min_distance = self.get_node_attr(j, 'distance')
    for j in self.get_in_neighbors(i):
        if (self.get_node_attr(j, 'distance') < min_distance and
            self.get_edge_attr(j, i, 'flow') > 0):
            min_distance = self.get_node_attr(j, 'distance')
    self.set_node_attr(i, 'distance', min_distance + 1)
def search(self, source, destination=None, display=None, component=None, q=None, algo='DFS', reverse=False, **kargs)

API: search(self, source, destination = None, display = None, component = None, q = Stack(), algo = 'DFS', reverse = False, **kargs) Description: Generic search method. Changes behavior (dfs,bfs,dijkstra,prim) according to algo argument. if destination is not specified: This method determines all nodes reachable from "source" ie. creates precedence tree and returns it (dictionary). if destionation is given: If there exists a path from "source" to "destination" it will return list of the nodes is this path. If there is no such path, it will return the precedence tree constructed from source (dictionary). Optionally, it marks all nodes reachable from "source" with a component number. The variable "q" determines the order in which the nodes are searched.

Input

source: Search starts from node with this name. destination: Destination node name. display: Display method. algo: Algortihm that specifies search. Available algortihms are 'DFS', 'BFS', 'Dijkstra' and 'Prim'. reverse: Search goes in reverse arc directions if True. kargs: Additional keyword arguments.

Post

Nodes will have 'component' attribute that will have component number as value (if component argument provided). Color attribute of nodes and edges may change.

Return

Returns predecessor tree in dictionary form if destination is not specified, returns list of node names in the path from source to destionation if destionation is specified and there is a path. If there is no path returns predecessor tree in dictionary form. See description section.

Expand source code
def search(self, source, destination = None, display = None,
           component = None, q = None,
           algo = 'DFS', reverse = False, **kargs):
    '''
    API: search(self, source, destination = None, display = None,
           component = None, q = Stack(),
           algo = 'DFS', reverse = False, **kargs)
    Description:
    Generic search method. Changes behavior (dfs,bfs,dijkstra,prim)
    according to algo argument.
    if destination is not specified:
       This method determines all nodes reachable from "source" ie. creates
       precedence tree and returns it (dictionary).
    if destionation is given:
       If there exists a path from "source" to "destination" it will return
       list of the nodes is this path. If there is no such path, it will
       return the precedence tree constructed from source (dictionary).
    Optionally, it marks all nodes reachable from "source" with a component
    number. The variable "q" determines the order in which the nodes are
    searched.
    Input:
        source: Search starts from node with this name.
        destination: Destination node name.
        display: Display method.
        algo: Algortihm that specifies search. Available algortihms are
        'DFS', 'BFS', 'Dijkstra' and 'Prim'.
        reverse: Search goes in reverse arc directions if True.
        kargs: Additional keyword arguments.
    Post:
        Nodes will have 'component' attribute that will have component
        number as value (if component argument provided). Color attribute
        of nodes and edges may change.
    Return:
        Returns predecessor tree in dictionary form if destination is
        not specified, returns list of node names in the path from source
        to destionation if destionation is specified and there is a path.
        If there is no path returns predecessor tree in dictionary form.
        See description section.
    '''
    if display == None:
        display = self.attr['display']
    else:
        self.set_display_mode(display)
    if algo == 'DFS':
        if q is None:
            q = Stack()
        self.get_node(source).set_attr('component', component)
    elif algo == 'BFS' or algo == 'UnweightedSPT':
        if q is None:
            q = Queue()
        self.get_node(source).set_attr('component', component)
    elif algo == 'Dijkstra' or algo == 'Prim':
        if q is None:
            q = PriorityQueue()
    else:
        print("Unknown search algorithm...exiting")
        return
    neighbors = self.neighbors
    if self.graph_type == DIRECTED_GRAPH and reverse:
        neighbors = self.in_neighbors
    for i in self.get_node_list():
        self.get_node(i).set_attr('label', '-')
        self.get_node(i).attr.pop('priority', None)
        self.get_node(i).set_attr('distance', None)
        self.get_node(i).set_attr('color', 'black')
        for j in neighbors[i]:
            if reverse:
                self.set_edge_attr(j, i, 'color', 'black')
            else:
                self.set_edge_attr(i, j, 'color', 'black')
    self.display()
    pred = {}
    self.process_edge_search(None, source, pred, q, component, algo,
                             **kargs)
    found = True
    if source != destination:
        found = False
    while not q.isEmpty() and not found:
        current = q.peek()
        if self.get_node(current).get_attr('color') == 'green':
            q.remove(current)
            continue
        self.process_node_search(current, q, **kargs)
        self.get_node(current).set_attr('color', 'blue')
        if current != source:
            if reverse:
                self.set_edge_attr(current, pred[current], 'color', 'green')
            else:
                self.set_edge_attr(pred[current], current, 'color', 'green')
        if current == destination:
            found = True
            break
        self.display()
        for n in neighbors[current]:
            if self.get_node(n).get_attr('color') != 'green':
                if reverse:
                    self.set_edge_attr(n, current, 'color', 'yellow')
                else:
                    self.set_edge_attr(current, n, 'color', 'yellow')
                self.display()
                self.process_edge_search(current, n, pred, q, component,
                                         algo, **kargs)
                if reverse:
                    self.set_edge_attr(n, current, 'color', 'black')
                else:
                    self.set_edge_attr(current, n, 'color', 'black')
        q.remove(current)
        self.get_node(current).set_attr('color', 'green')
        self.display()
    if found:
        path = [destination]
        current = destination
        while current != source:
            path.insert(0, pred[current])
            current = pred[current]
        return path
    if destination == None:
        return pred
    else:
        return None
def set_display_mode(self, value)

Api

set_display_mode(self, value)

Description

Sets display mode to value.

Input

value: New display mode.

Post

Display mode attribute of graph is updated.

Expand source code
def set_display_mode(self, value):
    '''
    API:
        set_display_mode(self, value)
    Description:
        Sets display mode to value.
    Input:
        value: New display mode.
    Post:
        Display mode attribute of graph is updated.
    '''
    self.attr['display'] = value
def set_edge_attr(self, n, m, attr, value)

API: set_edge_attr(self, n, m, attr, value) Description: Sets attr attribute of edge (n,m) to value.

Input

n: Source node name. m: Sink node name. attr: Attribute of edge to set. value: New value of attribute.

Pre

Graph should have this edge.

Post

Edge attribute will be updated.

Expand source code
def set_edge_attr(self, n, m, attr, value):
    '''
    API: set_edge_attr(self, n, m, attr, value)
    Description:
    Sets attr attribute of edge (n,m) to value.
    Input:
        n: Source node name.
        m: Sink node name.
        attr: Attribute of edge to set.
        value: New value of attribute.
    Pre:
        Graph should have this edge.
    Post:
        Edge attribute will be updated.
    '''
    if self.graph_type is DIRECTED_GRAPH:
        self.edge_attr[(n,m)][attr] = value
    else:
        try:
            self.edge_attr[(n,m)][attr] = value
        except KeyError:
            self.edge_attr[(m,n)][attr] = value
def set_layout(self, value)

Api

set_layout(self, value) Description: Sets layout attribute of the graph to value.

Input

value: New value of the layout.

Expand source code
def set_layout(self, value):
    '''
    API:
        set_layout(self, value)
    Description:
    Sets layout attribute of the graph to value.
    Input:
        value: New value of the layout.
    '''
    self.attr['layout']=value
    if value == 'dot2tex':
        self.attr['d2tgraphstyle'] = 'every text node part/.style={align=center}'
def set_node_attr(self, name, attr, value)

API: set_node_attr(self, name, attr) Description: Sets attr attribute of node named name to value.

Input

name: Name of node. attr: Attribute of node to set.

Pre

Graph should have this node.

Post

Node attribute will be updated.

Expand source code
def set_node_attr(self, name, attr, value):
    '''
    API: set_node_attr(self, name, attr)
    Description:
    Sets attr attribute of node named name to value.
    Input:
        name: Name of node.
        attr: Attribute of node to set.
    Pre:
        Graph should have this node.
    Post:
        Node attribute will be updated.
    '''
    self.get_node(name).set_attr(attr, value)
def show_flow(self)

API: relabel(self, i) Description: Used by max_flow_preflowpush() method for display purposed.

Post

'color' and 'label' attribute of edges/nodes are updated.

Expand source code
def show_flow(self):
    '''
    API: relabel(self, i)
    Description:
    Used by max_flow_preflowpush() method for display purposed.
    Post:
        'color' and 'label' attribute of edges/nodes are updated.
    '''
    for n in self.get_node_list():
        excess = self.get_node_attr(n, 'excess')
        distance = self.get_node_attr(n, 'distance')
        self.set_node_attr(n, 'label', str(excess)+'/'+str(distance))
        for neighbor in self.get_neighbors(n):
            capacity = self.get_edge_attr(n, neighbor, 'capacity')
            flow = self.get_edge_attr(n, neighbor, 'flow')
            if capacity == INF:
                self.set_edge_attr(n, neighbor, 'label',
                                   'INF'+'/'+str(flow))
            else:
                self.set_edge_attr(n, neighbor, 'label',
                                   str(capacity)+'/'+str(flow))
            if capacity == flow:
                self.set_edge_attr(n, neighbor, 'color', 'red')
            elif flow > 0:
                self.set_edge_attr(n, neighbor, 'color', 'green')
            else:
                self.set_edge_attr(n, neighbor, 'color', 'black')
    self.display()
def simplex_augment_cycle(self, cycle)

Api

simplex_augment_cycle(self, cycle)

Description

Augments along the cycle to break it.

Pre

'flow', 'capacity' attributes on arcs.

Input

cycle: list representing a cycle in the solution

Post

'flow' attribute will be modified.

Expand source code
def simplex_augment_cycle(self, cycle):
    '''
    API:
        simplex_augment_cycle(self, cycle)
    Description:
        Augments along the cycle to break it.
    Pre:
        'flow', 'capacity' attributes on arcs.
    Input:
        cycle: list representing a cycle in the solution
    Post:
        'flow' attribute will be modified.
    '''
    # find amount to augment
    index = 0
    k = len(cycle)
    el = list(self.edge_attr.keys())
    # check arc (cycle[k-1], cycle[0])
    if (cycle[k-1], cycle[0]) in el:
        min_capacity = self.edge_attr[(cycle[k-1], cycle[0])]['capacity']-\
                          self.edge_attr[(cycle[k-1], cycle[0])]['flow']
    else:
        min_capacity = self.edge_attr[(cycle[0], cycle[k-1])]['flow']
    # check rest of the arcs in the cycle
    while index<(k-1):
        i = cycle[index]
        j = cycle[index+1]
        if (i,j) in el:
            capacity_ij = self.edge_attr[(i,j)]['capacity'] -\
                          self.edge_attr[(i,j)]['flow']
        else:
            capacity_ij = self.edge_attr[(j,i)]['flow']
        if min_capacity > capacity_ij:
            min_capacity = capacity_ij
        index += 1
    return min_capacity
def simplex_compute_potentials(self, t, root)

Api

simplex_compute_potentials(self, t, root)

Description

Computes node potentials for a minimum cost flow problem and stores them as node attribute 'potential'. Based on pseudocode given in Network Flows by Ahuja et al.

Pre

(1) Assumes a directed graph in which each arc has a 'cost' attribute. (2) Uses 'thread' and 'pred' attributes of nodes.

Input

t: Current spanning tree solution, its type is Graph. root: root node of the tree.

Post

Keeps the node potentials as 'potential' attribute.

Expand source code
def simplex_compute_potentials(self, t, root):
    '''
    API:
        simplex_compute_potentials(self, t, root)
    Description:
        Computes node potentials for a minimum cost flow problem and stores
        them as node attribute 'potential'. Based on pseudocode given in
        Network Flows by Ahuja et al.
    Pre:
        (1) Assumes a directed graph in which each arc has a 'cost'
        attribute.
        (2) Uses 'thread' and 'pred' attributes of nodes.
    Input:
        t: Current spanning tree solution, its type is Graph.
        root: root node of the tree.
    Post:
        Keeps the node potentials as 'potential' attribute.
    '''
    self.get_node(root).set_attr('potential', 0)
    j = t.get_node(root).get_attr('thread')
    while j is not root:
        i = t.get_node(j).get_attr('pred')
        potential_i = self.get_node(i).get_attr('potential')
        if (i,j) in self.edge_attr:
            c_ij = self.edge_attr[(i,j)]['cost']
            self.get_node(j).set_attr('potential', potential_i-c_ij)
        if (j,i) in self.edge_attr:
            c_ji = self.edge_attr[(j,i)]['cost']
            self.get_node(j).set_attr('potential', potential_i+c_ji)
        j = t.get_node(j).get_attr('thread')
def simplex_connect(self, solution_g)

Api

simplex_connect(self, solution_g)

Description

At this point we assume that the solution does not have a cycle. We check if all the nodes are connected, if not we add an arc to solution_g that does not create a cycle and return True. Otherwise we do nothing and return False.

Pre

(1) We assume there is no cycle in the solution.

Input

solution_g: current spanning tree solution instance.

Post

(1) solution_g is updated. An arc that does not create a cycle is added. (2) 'component' attribute of nodes are changed.

Return

Returns True if an arc is added, returns False otherwise.

Expand source code
def simplex_connect(self, solution_g):
    '''
    API:
        simplex_connect(self, solution_g)
    Description:
        At this point we assume that the solution does not have a cycle.
        We check if all the nodes are connected, if not we add an arc to
        solution_g that does not create a cycle and return True. Otherwise
        we do nothing and return False.
    Pre:
        (1) We assume there is no cycle in the solution.
    Input:
        solution_g: current spanning tree solution instance.
    Post:
        (1) solution_g is updated. An arc that does not create a cycle is
        added.
        (2) 'component' attribute of nodes are changed.
    Return:
        Returns True if an arc is added, returns False otherwise.
    '''
    nl = solution_g.get_node_list()
    current = nl[0]
    pred = solution_g.simplex_search(current, current)
    separated = list(pred.keys())
    for n in nl:
        if solution_g.get_node(n).get_attr('component') != current:
            # find an arc from n to seperated
            for m in separated:
                if (n,m) in self.edge_attr:
                    solution_g.add_edge(n,m)
                    return True
                elif (m,n) in self.edge_attr:
                    solution_g.add_edge(m,n)
                    return True
    return False
def simplex_determine_leaving_arc(self, t, k, l)

Api

simplex_determine_leaving_arc(self, t, k, l)

Description

Determines and returns the leaving arc.

Input

t: current spanning tree solution. k: tail of the entering arc. l: head of the entering arc.

Return

Returns the tuple that represents leaving arc, capacity of the cycle and cycle.

Expand source code
def simplex_determine_leaving_arc(self, t, k, l):
    '''
    API:
        simplex_determine_leaving_arc(self, t, k, l)
    Description:
        Determines and returns the leaving arc.
    Input:
        t: current spanning tree solution.
        k: tail of the entering arc.
        l: head of the entering arc.
    Return:
        Returns the tuple that represents leaving arc, capacity of the
        cycle and cycle.
    '''
    # k,l are the first two elements of the cycle
    cycle = self.simplex_identify_cycle(t, k, l)
    flow_kl = self.get_edge_attr(k, l, 'flow')
    capacity_kl = self.get_edge_attr(k, l, 'capacity')
    min_capacity = capacity_kl
    # check if k,l is in U or L
    if flow_kl==capacity_kl:
        # l,k will be the last two elements
        cycle.reverse()
    n = len(cycle)
    index = 0
    # determine last blocking arc
    t.add_edge(k, l)
    tel = t.get_edge_list()
    while index < (n-1):
        if (cycle[index], cycle[index+1]) in tel:
            flow = self.edge_attr[(cycle[index], cycle[index+1])]['flow']
            capacity = \
                self.edge_attr[(cycle[index],cycle[index+1])]['capacity']
            if min_capacity >= (capacity-flow):
                candidate = (cycle[index], cycle[index+1])
                min_capacity = capacity-flow
        else:
            flow = self.edge_attr[(cycle[index+1], cycle[index])]['flow']
            if min_capacity >= flow:
                candidate = (cycle[index+1], cycle[index])
                min_capacity = flow
        index += 1
    # check arc (cycle[n-1], cycle[0])
    if (cycle[n-1], cycle[0]) in tel:
        flow = self.edge_attr[(cycle[n-1], cycle[0])]['flow']
        capacity = self.edge_attr[(cycle[n-1], cycle[0])]['capacity']
        if min_capacity >= (capacity-flow):
            candidate = (cycle[n-1], cycle[0])
            min_capacity = capacity-flow
    else:
        flow = self.edge_attr[(cycle[0], cycle[n-1])]['flow']
        if min_capacity >= flow:
            candidate = (cycle[0], cycle[n-1])
            min_capacity = flow
    return (candidate, min_capacity, cycle)
def simplex_find_cycle(self)

Api

simplex_find_cycle(self)

Description

Returns a cycle (list of nodes) if the graph has one, returns None otherwise. Uses DFS. During DFS checks existence of arcs to lower depth regions. Note that direction of the arcs are not important.

Return

Returns list of nodes that represents cycle. Returns None if the graph does not have any cycle.

Expand source code
def simplex_find_cycle(self):
    '''
    API:
        simplex_find_cycle(self)
    Description:
        Returns a cycle (list of nodes) if the graph has one, returns None
        otherwise. Uses DFS. During DFS checks existence of arcs to lower
        depth regions. Note that direction of the arcs are not important.
    Return:
        Returns list of nodes that represents cycle. Returns None if the
        graph does not have any cycle.
    '''
    # make a dfs, if you identify an arc to a lower depth node we have a
    # cycle
    nl = self.get_node_list()
    q = [nl[0]]
    visited = []
    depth = {nl[0]:0}
    pred = {nl[0]:None}
    for n in nl:
        self.get_node(n).set_attr('component', None)
    component_nr = int(nl[0])
    self.get_node(nl[0]).set_attr('component', component_nr)
    while True:
        while q:
            current = q.pop()
            visited.append(current)
            neighbors = self.in_neighbors[current] +\
                self.neighbors[current]
            for n in neighbors:
                if n==pred[current]:
                    continue
                self.get_node(n).set_attr('component', component_nr)
                if n in depth:
                    # we have a cycle
                    cycle1 = []
                    cycle2 = []
                    temp = n
                    while temp is not None:
                        cycle1.append(temp)
                        temp = pred[temp]
                    temp = current
                    while temp is not None:
                        cycle2.append(temp)
                        temp = pred[temp]
                    cycle1.pop()
                    cycle1.reverse()
                    cycle2.extend(cycle1)
                    return cycle2
                else:
                    pred[n] = current
                    depth[n] = depth[current] + 1
                if n not in visited:
                    q.append(n)
        flag = False
        for n in nl:
            if self.get_node(n).get_attr('component') is None:
                q.append(n)
                depth = {n:0}
                pred = {n:None}
                visited = []
                component_nr = int(n)
                self.get_node(n).set_attr('component', component_nr)
                flag = True
                break
        if not flag:
            break
    return None
def simplex_find_tree(self)

Api

simplex_find_tree(self)

Description

Assumes a feasible flow solution stored in 'flow' attribute's of arcs and converts this solution to a feasible spanning tree solution.

Pre

(1) 'flow' attributes represents a feasible flow solution.

Post

(1) 'flow' attributes may change when eliminating cycles.

Return

Return a Graph instance that is a spanning tree solution.

Expand source code
def simplex_find_tree(self):
    '''
    API:
        simplex_find_tree(self)
    Description:
        Assumes a feasible flow solution stored in 'flow' attribute's of
        arcs and converts this solution to a feasible spanning tree
        solution.
    Pre:
        (1) 'flow' attributes represents a feasible flow solution.
    Post:
        (1) 'flow' attributes may change when eliminating cycles.
    Return:
        Return a Graph instance that is a spanning tree solution.
    '''
    # find a cycle
    solution_g = self.get_simplex_solution_graph()
    cycle = solution_g.simplex_find_cycle()
    while cycle is not None:
        # find amount to augment and direction
        amount = self.simplex_augment_cycle(cycle)
        # augment along the cycle
        self.augment_cycle(amount, cycle)
        # find a new cycle
        solution_g = self.get_simplex_solution_graph()
        cycle = solution_g.simplex_find_cycle()
    # check if the solution is connected
    while self.simplex_connect(solution_g):
        pass
    # add attributes
    for e in self.edge_attr:
        flow = self.edge_attr[e]['flow']
        capacity = self.edge_attr[e]['capacity']
        cost = self.edge_attr[e]['cost']
        self.edge_attr[e]['label'] = "%d/%d/%d" %(flow,capacity,cost)
        if e in solution_g.edge_attr:
            solution_g.edge_attr[e]['flow'] = flow
            solution_g.edge_attr[e]['capacity'] = capacity
            solution_g.edge_attr[e]['cost'] = cost
            solution_g.edge_attr[e]['label'] = "%d/%d/%d" %(flow,capacity,cost)
    return solution_g
def simplex_identify_cycle(self, t, k, l)

Api

identify_cycle(self, t, k, l)

Description

Identifies and returns to the pivot cycle, which is a list of nodes.

Pre

(1) t is spanning tree solution, (k,l) is the entering arc.

Input

t: current spanning tree solution k: tail of the entering arc l: head of the entering arc

Returns

List of nodes in the cycle.

Expand source code
def simplex_identify_cycle(self, t, k, l):
    '''
    API:
        identify_cycle(self, t, k, l)
    Description:
        Identifies and returns to the pivot cycle, which is a list of
        nodes.
    Pre:
        (1) t is spanning tree solution, (k,l) is the entering arc.
    Input:
        t: current spanning tree solution
        k: tail of the entering arc
        l: head of the entering arc
    Returns:
        List of nodes in the cycle.
    '''
    i = k
    j = l
    cycle = []
    li = [k]
    lj = [j]
    while i is not j:
        depth_i = t.get_node(i).get_attr('depth')
        depth_j = t.get_node(j).get_attr('depth')
        if depth_i > depth_j:
            i = t.get_node(i).get_attr('pred')
            li.append(i)
        elif depth_i < depth_j:
            j = t.get_node(j).get_attr('pred')
            lj.append(j)
        else:
            i = t.get_node(i).get_attr('pred')
            li.append(i)
            j = t.get_node(j).get_attr('pred')
            lj.append(j)
    cycle.extend(lj)
    li.pop()
    li.reverse()
    cycle.extend(li)
    # l is beginning k is end
    return cycle
def simplex_mark_entering_arc(self, k, l)

Api

simplex_mark_entering_arc(self, k, l)

Description

Marks entering arc (k,l)

Input

k: tail of the entering arc l: head of the entering arc

Post

(1) color attribute of the arc (k,l)

Expand source code
def simplex_mark_entering_arc(self, k, l):
    '''
    API:
        simplex_mark_entering_arc(self, k, l)
    Description:
        Marks entering arc (k,l)
    Input:
        k: tail of the entering arc
        l: head of the entering arc
    Post:
        (1) color attribute of the arc (k,l)
    '''
    self.set_edge_attr(k, l, 'color', 'green')
def simplex_mark_leaving_arc(self, p, q)

Api

simplex_mark_leving_arc(self, p, q)

Description

Marks leaving arc.

Input

p: tail of the leaving arc q: head of the leaving arc

Post

Changes color attribute of leaving arc.

Expand source code
def simplex_mark_leaving_arc(self, p, q):
    '''
    API:
        simplex_mark_leving_arc(self, p, q)
    Description:
        Marks leaving arc.
    Input:
        p: tail of the leaving arc
        q: head of the leaving arc
    Post:
        Changes color attribute of leaving arc.
    '''
    self.set_edge_attr(p, q, 'color', 'red')
def simplex_mark_st_arcs(self, t)

Api

simplex_mark_st_arcs(self, t)

Description

Marks spanning tree arcs. Case 1, Blue: Arcs that are at lower bound and in tree. Case 2, Red: Arcs that are at upper bound and in tree. Case 3, Green: Arcs that are between bounds are green. Case 4, Brown: Non-tree arcs at lower bound. Case 5, Violet: Non-tree arcs at upper bound.

Input

t: t is the current spanning tree

Post

(1) color attribute of edges.

Expand source code
def simplex_mark_st_arcs(self, t):
    '''
    API:
        simplex_mark_st_arcs(self, t)
    Description:
        Marks spanning tree arcs.
        Case 1, Blue: Arcs that are at lower bound and in tree.
        Case 2, Red: Arcs that are at upper bound and in tree.
        Case 3, Green: Arcs that are between bounds are green.
        Case 4, Brown: Non-tree arcs at lower bound.
        Case 5, Violet: Non-tree arcs at upper bound.
    Input:
        t: t is the current spanning tree
    Post:
        (1) color attribute of edges.
    '''
    tel = list(t.edge_attr.keys())
    for e in self.get_edge_list():
        flow_e = self.edge_attr[e]['flow']
        capacity_e = self.edge_attr[e]['capacity']
        if e in tel:
            if flow_e == 0:
                self.edge_attr[e]['color'] = 'blue'
            elif flow_e == capacity_e:
                self.edge_attr[e]['color'] = 'blue'
            else:
                self.edge_attr[e]['color'] = 'blue'
        else:
            if flow_e == 0:
                self.edge_attr[e]['color'] = 'black'
            elif flow_e == capacity_e:
                self.edge_attr[e]['color'] = 'black'
            else:
                msg = "Arc is not in ST but has flow between bounds."
                raise Exception(msg)
def simplex_optimal(self, t)

Api

simplex_optimal(self, t)

Description

Checks if the current solution is optimal, if yes returns True, False otherwise.

Pre

'flow' attributes represents a solution.

Input

t: Graph instance tat reperesents spanning tree solution.

Return

Returns True if the current solution is optimal (optimality conditions are satisfied), else returns False

Expand source code
def simplex_optimal(self, t):
    '''
    API:
        simplex_optimal(self, t)
    Description:
        Checks if the current solution is optimal, if yes returns True,
        False otherwise.
    Pre:
        'flow' attributes represents a solution.
    Input:
        t: Graph instance tat reperesents spanning tree solution.
    Return:
        Returns True if the current solution is optimal (optimality
        conditions are satisfied), else returns False
    '''
    for e in self.edge_attr:
        if e in t.edge_attr:
            continue
        flow_ij = self.edge_attr[e]['flow']
        potential_i = self.get_node(e[0]).get_attr('potential')
        potential_j = self.get_node(e[1]).get_attr('potential')
        capacity_ij = self.edge_attr[e]['capacity']
        c_ij = self.edge_attr[e]['cost']
        cpi_ij = c_ij - potential_i + potential_j
        if flow_ij==0:
            if cpi_ij < 0:
                return False
        elif flow_ij==capacity_ij:
            if cpi_ij > 0:
                return False
    return True
def simplex_redraw(self, display, root)

Api

simplex_redraw(self, display, root)

Description

Returns a new graph instance that is same as self but adds nodes and arcs in a way that the resulting tree will be displayed properly.

Input

display: display mode root: root node in tree.

Return

Returns a graph same as self.

Expand source code
def simplex_redraw(self, display, root):
    '''
    API:
        simplex_redraw(self, display, root)
    Description:
        Returns a new graph instance that is same as self but adds nodes
        and arcs in a way that the resulting tree will be displayed
        properly.
    Input:
        display: display mode
        root: root node in tree.
    Return:
        Returns a graph same as self.
    '''
    nl = self.get_node_list()
    el = self.get_edge_list()
    new = Graph(type=DIRECTED_GRAPH, layout='dot', display=display)
    pred_i = self.get_node(root).get_attr('pred')
    thread_i = self.get_node(root).get_attr('thread')
    depth_i = self.get_node(root).get_attr('depth')
    new.add_node(root, pred=pred_i, thread=thread_i, depth=depth_i)
    q = [root]
    visited = [root]
    while q:
        name = q.pop()
        visited.append(name)
        neighbors = self.neighbors[name] + self.in_neighbors[name]
        for n in neighbors:
            if n not in new.get_node_list():
                pred_i = self.get_node(n).get_attr('pred')
                thread_i = self.get_node(n).get_attr('thread')
                depth_i = self.get_node(n).get_attr('depth')
                new.add_node(n, pred=pred_i, thread=thread_i, depth=depth_i)
            if (name,n) in el:
                if (name,n) not in new.edge_attr:
                    new.add_edge(name,n)
            else:
                if (n,name) not in new.edge_attr:
                    new.add_edge(n,name)
            if n not in visited:
                q.append(n)
    for e in el:
        flow = self.edge_attr[e]['flow']
        capacity = self.edge_attr[e]['capacity']
        cost = self.edge_attr[e]['cost']
        new.edge_attr[e]['flow'] = flow
        new.edge_attr[e]['capacity'] = capacity
        new.edge_attr[e]['cost'] = cost
        new.edge_attr[e]['label'] =  "%d/%d/%d" %(flow,capacity,cost)
    return new
def simplex_remove_arc(self, t, p, q, min_capacity, cycle)

Api

simplex_remove_arc(self, p, q, min_capacity, cycle)

Description

Removes arc (p,q), updates t, updates flows, where (k,l) is the entering arc.

Input

t: tree solution to be updated. p: tail of the leaving arc. q: head of the leaving arc. min_capacity: capacity of the cycle. cycle: cycle obtained when entering arc considered.

Post

(1) updates t. (2) updates 'flow' attributes.

Expand source code
def simplex_remove_arc(self, t, p, q, min_capacity, cycle):
    '''
    API:
        simplex_remove_arc(self, p, q, min_capacity, cycle)
    Description:
        Removes arc (p,q), updates t, updates flows, where (k,l) is
        the entering arc.
    Input:
        t: tree solution to be updated.
        p: tail of the leaving arc.
        q: head of the leaving arc.
        min_capacity: capacity of the cycle.
        cycle: cycle obtained when entering arc considered.
    Post:
        (1) updates t.
        (2) updates 'flow' attributes.
    '''
    # augment min_capacity along cycle
    n = len(cycle)
    tel = list(t.edge_attr.keys())
    index = 0
    while index < (n-1):
        if (cycle[index], cycle[index+1]) in tel:
            flow_e = self.edge_attr[(cycle[index], cycle[index+1])]['flow']
            self.edge_attr[(cycle[index], cycle[index+1])]['flow'] =\
                flow_e+min_capacity
        else:
            flow_e = self.edge_attr[(cycle[index+1], cycle[index])]['flow']
            self.edge_attr[(cycle[index+1], cycle[index])]['flow'] =\
                flow_e-min_capacity
        index += 1
    # augment arc cycle[n-1], cycle[0]
    if (cycle[n-1], cycle[0]) in tel:
        flow_e = self.edge_attr[(cycle[n-1], cycle[0])]['flow']
        self.edge_attr[(cycle[n-1], cycle[0])]['flow'] =\
            flow_e+min_capacity
    else:
        flow_e = self.edge_attr[(cycle[0], cycle[n-1])]['flow']
        self.edge_attr[(cycle[0], cycle[n-1])]['flow'] =\
            flow_e-min_capacity
    # remove leaving arc
    t.del_edge((p, q))
    # set label of removed arc
    flow_pq = self.get_edge_attr(p, q, 'flow')
    capacity_pq = self.get_edge_attr(p, q, 'capacity')
    cost_pq = self.get_edge_attr(p, q, 'cost')
    self.set_edge_attr(p, q, 'label',
                       "%d/%d/%d" %(flow_pq,capacity_pq,cost_pq))
    for e in t.edge_attr:
        flow = self.edge_attr[e]['flow']
        capacity = self.edge_attr[e]['capacity']
        cost = self.edge_attr[e]['cost']
        t.edge_attr[e]['flow'] = flow
        t.edge_attr[e]['capacity'] = capacity
        t.edge_attr[e]['cost'] = cost
        t.edge_attr[e]['label'] = "%d/%d/%d" %(flow,capacity,cost)
        self.edge_attr[e]['label'] = "%d/%d/%d" %(flow,capacity,cost)

Api

simplex_search(self, source, component_nr)

Description

Searches graph starting from source. Its difference from usual search is we can also go backwards along an arc. When the graph is a spanning tree it computes predecessor, thread and depth indexes and stores them as node attributes. These values should be considered as junk when the graph is not a spanning tree.

Input

source: source node component_nr: component number

Post

(1) Sets the component number of all reachable nodes to component. Changes 'component' attribute of nodes. (2) Sets 'pred', 'thread' and 'depth' attributes of nodes. These values are junk if the graph is not a tree.

Return

Returns predecessor dictionary.

Expand source code
def simplex_search(self, source, component_nr):
    '''
    API:
        simplex_search(self, source, component_nr)
    Description:
        Searches graph starting from source. Its difference from usual
        search is we can also go backwards along an arc. When the graph
        is a spanning tree it computes predecessor, thread and depth
        indexes and stores them as node attributes. These values should be
        considered as junk when the graph is not a spanning tree.
    Input:
        source: source node
        component_nr: component number
    Post:
        (1) Sets the component number of all reachable nodes to component.
        Changes 'component' attribute of nodes.
        (2) Sets 'pred', 'thread' and 'depth' attributes of nodes. These
        values are junk if the graph is not a tree.
    Return:
        Returns predecessor dictionary.
    '''
    q = [source]
    pred = {source:None}
    depth = {source:0}
    sequence = []
    for n in self.neighbors:
        self.get_node(n).set_attr('component', None)
    while q:
        current = q.pop()
        self.get_node(current).set_attr('component', component_nr)
        sequence.append(current)
        neighbors = self.in_neighbors[current] + self.neighbors[current]
        for n in neighbors:
            if n in pred:
                continue
            self.get_node(n).set_attr('component', component_nr)
            pred[n] = current
            depth[n] = depth[current]+1
            q.append(n)
    for i in range(len(sequence)-1):
        self.get_node(sequence[i]).set_attr('thread', int(sequence[i+1]))
    self.get_node(sequence[-1]).set_attr('thread', int(sequence[0]))
    for n in pred:
        self.get_node(n).set_attr('pred', pred[n])
        self.get_node(n).set_attr('depth', depth[n])
    return pred
def simplex_select_entering_arc(self, t, pivot)

Api

simplex_select_entering_arc(self, t, pivot)

Description

Decides and returns entering arc using pivot rule.

Input

t: current spanning tree solution pivot: May be one of the following; 'first_eligible' or 'dantzig'. 'dantzig' is the default value.

Return

Returns entering arc tuple (k,l)

Expand source code
def simplex_select_entering_arc(self, t, pivot):
    '''
    API:
        simplex_select_entering_arc(self, t, pivot)
    Description:
        Decides and returns entering arc using pivot rule.
    Input:
        t: current spanning tree solution
        pivot: May be one of the following; 'first_eligible' or 'dantzig'.
        'dantzig' is the default value.
    Return:
        Returns entering arc tuple (k,l)
    '''
    if pivot=='dantzig':
        # pick the maximum violation
        candidate = {}
        for e in self.edge_attr:
            if e in t.edge_attr:
                continue
            flow_ij = self.edge_attr[e]['flow']
            potential_i = self.get_node(e[0]).get_attr('potential')
            potential_j = self.get_node(e[1]).get_attr('potential')
            capacity_ij = self.edge_attr[e]['capacity']
            c_ij = self.edge_attr[e]['cost']
            cpi_ij = c_ij - potential_i + potential_j
            if flow_ij==0:
                if cpi_ij < 0:
                    candidate[e] = cpi_ij
            elif flow_ij==capacity_ij:
                if cpi_ij > 0:
                    candidate[e] = cpi_ij
        for e in candidate:
            max_c = e
            max_v = abs(candidate[e])
            break
        for e in candidate:
            if max_v < abs(candidate[e]):
                max_c = e
                max_v = abs(candidate[e])
    elif pivot=='first_eligible':
        # pick the first eligible
        for e in self.edge_attr:
            if e in t.edge_attr:
                continue
            flow_ij = self.edge_attr[e]['flow']
            potential_i = self.get_node(e[0]).get_attr('potential')
            potential_j = self.get_node(e[1]).get_attr('potential')
            capacity_ij = self.edge_attr[e]['capacity']
            c_ij = self.edge_attr[e]['cost']
            cpi_ij = c_ij - potential_i + potential_j
            if flow_ij==0:
                if cpi_ij < 0:
                    max_c = e
                    max_v = abs(cpi_ij)
            elif flow_ij==capacity_ij:
                if cpi_ij > 0:
                    max_c = e
                    max_v = cpi_ij
    else:
        raise Exception("Unknown pivot rule.")
    return max_c
def strong_connect(self, q, node, index, component)

API: strong_connect (self, q, node, index, component) Description: Used by tarjan method. This method should not be called directly by user.

Input

q: Node list. node: Node that is being connected to nodes in q. index: Index used by tarjan method. component: Current component number.

Pre

Should be called by tarjan and itself (recursive) only.

Post

Nodes will have 'component' attribute that will have component number as value. Changes 'index' attribute of nodes.

Return

Returns new index and component numbers.

Expand source code
def strong_connect(self, q, node, index, component):
    '''
    API: strong_connect (self, q, node, index, component)
    Description:
    Used by tarjan method. This method should not be called directly by
    user.
    Input:
        q: Node list.
        node: Node that is being connected to nodes in q.
        index: Index used by tarjan method.
        component: Current component number.
    Pre:
        Should be called by tarjan and itself (recursive) only.
    Post:
        Nodes will have 'component' attribute that will have component
        number as value. Changes 'index' attribute of nodes.
    Return:
        Returns new index and component numbers.
    '''
    self.set_node_attr(node, 'index', index)
    self.set_node_attr(node, 'lowlink', index)
    index += 1
    q.append(node)
    for m in self.get_neighbors(node):
        if self.get_node_attr(m, 'index') is None:
            index, component = self.strong_connect(q, m, index, component)
            self.set_node_attr(node, 'lowlink',
                               min([self.get_node_attr(node, 'lowlink'),
                                    self.get_node_attr(m, 'lowlink')]))
        elif m in q:
            self.set_node_attr(node, 'lowlink',
                               min([self.get_node_attr(node, 'lowlink'),
                                    self.get_node_attr(m, 'index')]))
    if self.get_node_attr(node, 'lowlink') == self.get_node_attr(node, 'index'):
        m = q.pop()
        self.set_node_attr(m, 'component', component)
        while (node!=m):
            m = q.pop()
            self.set_node_attr(m, 'component', component)
        component += 1
        self.num_components = component

    return (index, component)
def tarjan(self)

API: tarjan(self) Description: Implements Tarjan's algorithm for determining strongly connected set of nodes.

Pre

self.graph_type should be DIRECTED_GRAPH.

Post

Nodes will have 'component' attribute that will have component number as value. Changes 'index' attribute of nodes.

Expand source code
def tarjan(self):
    '''
    API: tarjan(self)
    Description:
    Implements Tarjan's algorithm for determining strongly connected set of
    nodes.
    Pre:
        self.graph_type should be DIRECTED_GRAPH.
    Post:
        Nodes will have 'component' attribute that will have component
        number as value. Changes 'index' attribute of nodes.
    '''
    index = 0
    component = 0
    q = []
    for n in self.get_node_list():
        if self.get_node_attr(n, 'index') is None:
            index, component = self.strong_connect(q, n, index, component)
def to_string(self)

API: to_string(self) Description: This method is based on pydot Graph class with the same name. Returns a string representation of the graph in dot language. It will return the graph and all its subelements in string form.

Return

String that represents graph in dot language.

Expand source code
def to_string(self):
    '''
    API: to_string(self)
    Description:
    This method is based on pydot Graph class with the same name.
    Returns a string representation of the graph in dot language.
    It will return the graph and all its subelements in string form.
    Return:
        String that represents graph in dot language.
    '''
    graph = list()
    processed_edges = {}
    graph.append('%s %s {\n' %(self.graph_type, self.name))
    for a in self.attr:
        if a not in GRAPH_ATTRIBUTES:
            continue
        val = self.attr[a]
        if val is not None:
            graph.append( '%s=%s' % (a, quote_if_necessary(val)) )
        else:
            graph.append(a)
        graph.append( ';\n' )
    # clusters
    for c in self.cluster:
        graph.append('subgraph cluster_%s {\n' %c)
        for a in self.cluster[c]['attrs']:
            if a=='label':
                graph.append(a+'='+quote_if_necessary(self.cluster[c]['attrs'][a])+';\n')
                continue
            graph.append(a+'='+self.cluster[c]['attrs'][a]+';\n')
        if len(self.cluster[c]['node_attrs'])!=0:
            graph.append('node [')
        for a in self.cluster[c]['node_attrs']:
            graph.append(a+'='+self.cluster[c]['node_attrs'][a])
            graph.append(',')
        if len(self.cluster[c]['node_attrs'])!=0:
            graph.pop()
            graph.append('];\n')
        # process cluster nodes
        for n in self.cluster[c]['node_list']:
            data = self.get_node(n).to_string()
            graph.append(data + ';\n')
        # process cluster edges
        for n in self.cluster[c]['node_list']:
            for m in self.cluster[c]['node_list']:
                if self.check_edge(n,m):
                    data = self.edge_to_string((n,m))
                    graph.append(data + ';\n')
                    processed_edges[(n,m)]=None
        graph.append('}\n')
    # process remaining (non-cluster) nodes
    for n in self.neighbors:
        for c in self.cluster:
            if n in self.cluster[c]['node_list']:
                break
        else:
            data = self.get_node(n).to_string()
            graph.append(data + ';\n')
    # process edges
    for e in self.edge_attr:
        if e in processed_edges:
            continue
        data = self.edge_to_string(e)
        graph.append(data + ';\n')
    graph.append( '}\n' )
    return ''.join(graph)
def write(self, file_obj, layout=None, format='png')

Api

write(self, basename = 'graph', layout = None, format='png') Description: Writes graph to dist using layout and format.

Input

basename: name of the file that will be written. layout: Dot layout for generating graph image. format: Image format, all format supported by Dot are wellcome.

Post

File will be written to disk.

Expand source code
def write(self, file_obj, layout = None, format='png'):
    '''
    API:
        write(self, basename = 'graph', layout = None, format='png')
    Description:
    Writes graph to dist using layout and format.
    Input:
        basename: name of the file that will be written.
        layout: Dot layout for generating graph image.
        format: Image format, all format supported by Dot are wellcome.
    Post:
        File will be written to disk.
    '''
    if layout == None:
        layout = self.get_layout()
    if format == 'dot':
        file_obj.write(bytearray(self.to_string(), 'utf8'))
    else:
        out = self.create(layout, format)
        if (out != None):
            file_obj.write(out)
class Node (name, **attr)

Node class. A node object keeps node attributes. Has a method to write node in Dot language grammer.

API: init(self, name, **attrs) Description: Node class constructor. Sets name and attributes using arguments.

Input

name: Name of node. **attrs: Node attributes.

Post

Sets self.name and self.attr.

Expand source code
class Node(object):
    '''
    Node class. A node object keeps node attributes. Has a method to write
    node in Dot language grammer.
    '''
    def __init__(self, name, **attr):
        '''
        API: __init__(self, name, **attrs)
        Description:
        Node class constructor. Sets name and attributes using arguments.
        Input:
            name: Name of node.
            **attrs: Node attributes.
        Post:
            Sets self.name and self.attr.
        '''
        self.name = name
        self.attr = copy.deepcopy(DEFAULT_NODE_ATTRIBUTES)
        for a in attr:
            self.attr[a] = attr[a]

    def get_attr(self, attr):
        '''
        API: get_attr(self, attr)
        Description:
        Returns node attribute attr.
        Input:
            attr: Node attribute to get.
        Return:
            Returns Node attribute attr if exists returns None, otherwise.
        '''
        if attr in self.attr:
            return self.attr[attr]
        else:
            return None

    def set_attr(self, attr, value):
        '''
        API: set_attr(self, attr, value)
        Description:
        Sets node attribute attr to value.
        Input:
            attr: Node attribute to set.
            value: New value of the attribute.
        Post:
            Updates self.attr[attr].
        '''
        self.attr[attr] = value

    def to_string(self):
        '''
        API: to_string(self)
        Description:
        Returns string representation of node in dot language.
        Return:
            String representation of node.
        '''
        node = list()
        node.append(quote_if_necessary(str(self.name)))
        node.append(' [')
        flag = False
        for a in self.attr:
            flag = True
            node.append(a)
            node.append('=')
            node.append(quote_if_necessary(str(self.attr[a])))
            node.append(', ')
        if flag is True:
            node = node[:-1]
        node.append(']')
        return ''.join(node)

    def __repr__(self):
        '''
        API: __repr__(self)
        Description:
        Returns string representation of node in dot language.
        Return:
            String representation of node.
        '''
        return self.to_string()

Methods

def get_attr(self, attr)

API: get_attr(self, attr) Description: Returns node attribute attr.

Input

attr: Node attribute to get.

Return

Returns Node attribute attr if exists returns None, otherwise.

Expand source code
def get_attr(self, attr):
    '''
    API: get_attr(self, attr)
    Description:
    Returns node attribute attr.
    Input:
        attr: Node attribute to get.
    Return:
        Returns Node attribute attr if exists returns None, otherwise.
    '''
    if attr in self.attr:
        return self.attr[attr]
    else:
        return None
def set_attr(self, attr, value)

API: set_attr(self, attr, value) Description: Sets node attribute attr to value.

Input

attr: Node attribute to set. value: New value of the attribute.

Post

Updates self.attr[attr].

Expand source code
def set_attr(self, attr, value):
    '''
    API: set_attr(self, attr, value)
    Description:
    Sets node attribute attr to value.
    Input:
        attr: Node attribute to set.
        value: New value of the attribute.
    Post:
        Updates self.attr[attr].
    '''
    self.attr[attr] = value
def to_string(self)

API: to_string(self) Description: Returns string representation of node in dot language.

Return

String representation of node.

Expand source code
def to_string(self):
    '''
    API: to_string(self)
    Description:
    Returns string representation of node in dot language.
    Return:
        String representation of node.
    '''
    node = list()
    node.append(quote_if_necessary(str(self.name)))
    node.append(' [')
    flag = False
    for a in self.attr:
        flag = True
        node.append(a)
        node.append('=')
        node.append(quote_if_necessary(str(self.attr[a])))
        node.append(', ')
    if flag is True:
        node = node[:-1]
    node.append(']')
    return ''.join(node)