diff --git a/core/build/lib/pygraph/__init__.py b/core/build/lib/pygraph/__init__.py new file mode 100644 index 0000000..8c2ee51 --- /dev/null +++ b/core/build/lib/pygraph/__init__.py @@ -0,0 +1,63 @@ +# Copyright (c) 2007-2012 Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +B{python-graph} + +A library for working with graphs in Python. + +@version: 1.8.2 + +L{Data structure} classes are located at C{pygraph.classes}. + +L{Exception} classes are located at C{pygraph.classes.exceptions}. + +L{Search filters} are located at C{pygraph.algorithms.filters}. + +L{Heuristics} for the A* algorithm are exposed in +C{pygraph.algorithms.heuristics}. + +A quick introductory example: + +>>> # Import the module and instantiate a graph object +>>> from pygraph.classes.graph import graph +>>> from pygraph.algorithms.searching import depth_first_search +>>> gr = graph() +>>> # Add nodes +>>> gr.add_nodes(['X','Y','Z']) +>>> gr.add_nodes(['A','B','C']) +>>> # Add edges +>>> gr.add_edge(('X','Y')) +>>> gr.add_edge(('X','Z')) +>>> gr.add_edge(('A','B')) +>>> gr.add_edge(('A','C')) +>>> gr.add_edge(('Y','B')) +>>> # Depth first search rooted on node X +>>> st, pre, post = depth_first_search(gr, root='X') +>>> # Print the spanning tree +>>> print st +{'A': 'B', 'C': 'A', 'B': 'Y', 'Y': 'X', 'X': None, 'Z': 'X'} +""" + +__import__('pkg_resources').declare_namespace(__name__) diff --git a/core/build/lib/pygraph/algorithms/__init__.py b/core/build/lib/pygraph/algorithms/__init__.py new file mode 100644 index 0000000..f17ac5e --- /dev/null +++ b/core/build/lib/pygraph/algorithms/__init__.py @@ -0,0 +1,31 @@ +# Copyright (c) 2008-2009 Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Algorithms + +This subpackage contains a set of modules, each one of them containing some algorithms. +""" + +__import__('pkg_resources').declare_namespace(__name__) diff --git a/core/build/lib/pygraph/algorithms/accessibility.py b/core/build/lib/pygraph/algorithms/accessibility.py new file mode 100644 index 0000000..3093a4f --- /dev/null +++ b/core/build/lib/pygraph/algorithms/accessibility.py @@ -0,0 +1,347 @@ +# Copyright (c) 2007-2009 Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Accessibility algorithms. + +@sort: accessibility, connected_components, cut_edges, cut_nodes, mutual_accessibility +""" + + +# Imports +from sys import getrecursionlimit, setrecursionlimit + +# Transitive-closure + +def accessibility(graph): + """ + Accessibility matrix (transitive closure). + + @type graph: graph, digraph, hypergraph + @param graph: Graph. + + @rtype: dictionary + @return: Accessibility information for each node. + """ + recursionlimit = getrecursionlimit() + setrecursionlimit(max(len(graph.nodes())*2,recursionlimit)) + + accessibility = {} # Accessibility matrix + + # For each node i, mark each node j if that exists a path from i to j. + for each in graph: + access = {} + # Perform DFS to explore all reachable nodes + _dfs(graph, access, 1, each) + accessibility[each] = list(access.keys()) + + setrecursionlimit(recursionlimit) + return accessibility + + +# Strongly connected components + +def mutual_accessibility(graph): + """ + Mutual-accessibility matrix (strongly connected components). + + @type graph: graph, digraph + @param graph: Graph. + + @rtype: dictionary + @return: Mutual-accessibility information for each node. + """ + recursionlimit = getrecursionlimit() + setrecursionlimit(max(len(graph.nodes())*2,recursionlimit)) + + mutual_access = {} + stack = [] + low = {} + + def visit(node): + if node in low: + return + + num = len(low) + low[node] = num + stack_pos = len(stack) + stack.append(node) + + for successor in graph.neighbors(node): + visit(successor) + low[node] = min(low[node], low[successor]) + + if num == low[node]: + component = stack[stack_pos:] + del stack[stack_pos:] + component.sort() + for each in component: + mutual_access[each] = component + + for item in component: + low[item] = len(graph) + + for node in graph: + visit(node) + + setrecursionlimit(recursionlimit) + return mutual_access + + +# Connected components + +def connected_components(graph): + """ + Connected components. + + @type graph: graph, hypergraph + @param graph: Graph. + + @rtype: dictionary + @return: Pairing that associates each node to its connected component. + """ + recursionlimit = getrecursionlimit() + setrecursionlimit(max(len(graph.nodes())*2,recursionlimit)) + + visited = {} + count = 1 + + # For 'each' node not found to belong to a connected component, find its connected + # component. + for each in graph: + if (each not in visited): + _dfs(graph, visited, count, each) + count = count + 1 + + setrecursionlimit(recursionlimit) + return visited + + +# Limited DFS implementations used by algorithms here + +def _dfs(graph, visited, count, node): + """ + Depth-first search subfunction adapted for accessibility algorithms. + + @type graph: graph, digraph, hypergraph + @param graph: Graph. + + @type visited: dictionary + @param visited: List of nodes (visited nodes are marked non-zero). + + @type count: number + @param count: Counter of connected components. + + @type node: node + @param node: Node to be explored by DFS. + """ + visited[node] = count + # Explore recursively the connected component + for each in graph[node]: + if (each not in visited): + _dfs(graph, visited, count, each) + + +# Cut-Edge and Cut-Vertex identification + +# This works by creating a spanning tree for the graph and keeping track of the preorder number +# of each node in the graph in pre[]. The low[] number for each node tracks the pre[] number of +# the node with lowest pre[] number reachable from the first node. +# +# An edge (u, v) will be a cut-edge low[u] == pre[v]. Suppose v under the spanning subtree with +# root u. This means that, from u, through a path inside this subtree, followed by an backarc, +# one can not get out the subtree. So, (u, v) is the only connection between this subtree and +# the remaining parts of the graph and, when removed, will increase the number of connected +# components. + +# Similarly, a node u will be a cut node if any of the nodes v in the spanning subtree rooted in +# u are so that low[v] > pre[u], which means that there's no path from v to outside this subtree +# without passing through u. + +def cut_edges(graph): + """ + Return the cut-edges of the given graph. + + A cut edge, or bridge, is an edge of a graph whose removal increases the number of connected + components in the graph. + + @type graph: graph, hypergraph + @param graph: Graph. + + @rtype: list + @return: List of cut-edges. + """ + recursionlimit = getrecursionlimit() + setrecursionlimit(max(len(graph.nodes())*2,recursionlimit)) + + # Dispatch if we have a hypergraph + if 'hypergraph' == graph.__class__.__name__: + return _cut_hyperedges(graph) + + pre = {} # Pre-ordering + low = {} # Lowest pre[] reachable from this node going down the spanning tree + one backedge + spanning_tree = {} + reply = [] + pre[None] = 0 + + for each in graph: + if (each not in pre): + spanning_tree[each] = None + _cut_dfs(graph, spanning_tree, pre, low, reply, each) + + setrecursionlimit(recursionlimit) + return reply + + +def _cut_hyperedges(hypergraph): + """ + Return the cut-hyperedges of the given hypergraph. + + @type hypergraph: hypergraph + @param hypergraph: Hypergraph + + @rtype: list + @return: List of cut-nodes. + """ + edges_ = cut_nodes(hypergraph.graph) + edges = [] + + for each in edges_: + if (each[1] == 'h'): + edges.append(each[0]) + + return edges + + +def cut_nodes(graph): + """ + Return the cut-nodes of the given graph. + + A cut node, or articulation point, is a node of a graph whose removal increases the number of + connected components in the graph. + + @type graph: graph, hypergraph + @param graph: Graph. + + @rtype: list + @return: List of cut-nodes. + """ + recursionlimit = getrecursionlimit() + setrecursionlimit(max(len(graph.nodes())*2,recursionlimit)) + + # Dispatch if we have a hypergraph + if 'hypergraph' == graph.__class__.__name__: + return _cut_hypernodes(graph) + + pre = {} # Pre-ordering + low = {} # Lowest pre[] reachable from this node going down the spanning tree + one backedge + reply = {} + spanning_tree = {} + pre[None] = 0 + + # Create spanning trees, calculate pre[], low[] + for each in graph: + if (each not in pre): + spanning_tree[each] = None + _cut_dfs(graph, spanning_tree, pre, low, [], each) + + # Find cuts + for each in graph: + # If node is not a root + if (spanning_tree[each] is not None): + for other in graph[each]: + # If there is no back-edge from descendent to a ancestral of each + if (low[other] >= pre[each] and spanning_tree[other] == each): + reply[each] = 1 + # If node is a root + else: + children = 0 + for other in graph: + if (spanning_tree[other] == each): + children = children + 1 + # root is cut-vertex iff it has two or more children + if (children >= 2): + reply[each] = 1 + + setrecursionlimit(recursionlimit) + return list(reply.keys()) + + +def _cut_hypernodes(hypergraph): + """ + Return the cut-nodes of the given hypergraph. + + @type hypergraph: hypergraph + @param hypergraph: Hypergraph + + @rtype: list + @return: List of cut-nodes. + """ + nodes_ = cut_nodes(hypergraph.graph) + nodes = [] + + for each in nodes_: + if (each[1] == 'n'): + nodes.append(each[0]) + + return nodes + + +def _cut_dfs(graph, spanning_tree, pre, low, reply, node): + """ + Depth first search adapted for identification of cut-edges and cut-nodes. + + @type graph: graph, digraph + @param graph: Graph + + @type spanning_tree: dictionary + @param spanning_tree: Spanning tree being built for the graph by DFS. + + @type pre: dictionary + @param pre: Graph's preordering. + + @type low: dictionary + @param low: Associates to each node, the preordering index of the node of lowest preordering + accessible from the given node. + + @type reply: list + @param reply: List of cut-edges. + + @type node: node + @param node: Node to be explored by DFS. + """ + pre[node] = pre[None] + low[node] = pre[None] + pre[None] = pre[None] + 1 + + for each in graph[node]: + if (each not in pre): + spanning_tree[each] = node + _cut_dfs(graph, spanning_tree, pre, low, reply, each) + if (low[node] > low[each]): + low[node] = low[each] + if (low[each] == pre[each]): + reply.append((node, each)) + elif (low[node] > pre[each] and spanning_tree[node] != each): + low[node] = pre[each] diff --git a/core/build/lib/pygraph/algorithms/critical.py b/core/build/lib/pygraph/algorithms/critical.py new file mode 100644 index 0000000..2e359e6 --- /dev/null +++ b/core/build/lib/pygraph/algorithms/critical.py @@ -0,0 +1,163 @@ +# Copyright (c) 2009 Pedro Matiello +# Tomaz Kovacic +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Critical path algorithms and transitivity detection algorithm. + +@sort: critical_path, transitive_edges +""" + + +# Imports +from pygraph.algorithms.cycles import find_cycle +from pygraph.algorithms.traversal import traversal +from pygraph.algorithms.sorting import topological_sorting + +def _intersection(A,B): + """ + A simple function to find an intersection between two arrays. + + @type A: List + @param A: First List + + @type B: List + @param B: Second List + + @rtype: List + @return: List of Intersections + """ + intersection = [] + for i in A: + if i in B: + intersection.append(i) + return intersection + +def transitive_edges(graph): + """ + Return a list of transitive edges. + + Example of transitivity within graphs: A -> B, B -> C, A -> C + in this case the transitive edge is: A -> C + + @attention: This function is only meaningful for directed acyclic graphs. + + @type graph: digraph + @param graph: Digraph + + @rtype: List + @return: List containing tuples with transitive edges (or an empty array if the digraph + contains a cycle) + """ + #if the graph contains a cycle we return an empty array + if not len(find_cycle(graph)) == 0: + return [] + + tranz_edges = [] # create an empty array that will contain all the tuples + + #run trough all the nodes in the graph + for start in topological_sorting(graph): + #find all the successors on the path for the current node + successors = [] + for a in traversal(graph,start,'pre'): + successors.append(a) + del successors[0] #we need all the nodes in it's path except the start node itself + + for next in successors: + #look for an intersection between all the neighbors of the + #given node and all the neighbors from the given successor + intersect_array = _intersection(graph.neighbors(next), graph.neighbors(start) ) + for a in intersect_array: + if graph.has_edge((start, a)): + ##check for the detected edge and append it to the returned array + tranz_edges.append( (start,a) ) + return tranz_edges # return the final array + + +def critical_path(graph): + """ + Compute and return the critical path in an acyclic directed weighted graph. + + @attention: This function is only meaningful for directed weighted acyclic graphs + + @type graph: digraph + @param graph: Digraph + + @rtype: List + @return: List containing all the nodes in the path (or an empty array if the graph + contains a cycle) + """ + #if the graph contains a cycle we return an empty array + if not len(find_cycle(graph)) == 0: + return [] + + #this empty dictionary will contain a tuple for every single node + #the tuple contains the information about the most costly predecessor + #of the given node and the cost of the path to this node + #(predecessor, cost) + node_tuples = {} + + topological_nodes = topological_sorting(graph) + + #all the tuples must be set to a default value for every node in the graph + for node in topological_nodes: + node_tuples.update( {node :(None, 0)} ) + + #run trough all the nodes in a topological order + for node in topological_nodes: + predecessors =[] + #we must check all the predecessors + for pre in graph.incidents(node): + max_pre = node_tuples[pre][1] + predecessors.append( (pre, graph.edge_weight( (pre, node) ) + max_pre ) ) + + max = 0; max_tuple = (None, 0) + for i in predecessors:#look for the most costly predecessor + if i[1] >= max: + max = i[1] + max_tuple = i + #assign the maximum value to the given node in the node_tuples dictionary + node_tuples[node] = max_tuple + + #find the critical node + max = 0; critical_node = None + for k,v in list(node_tuples.items()): + if v[1] >= max: + max= v[1] + critical_node = k + + + path = [] + #find the critical path with backtracking trought the dictionary + def mid_critical_path(end): + if node_tuples[end][0] != None: + path.append(end) + mid_critical_path(node_tuples[end][0]) + else: + path.append(end) + #call the recursive function + mid_critical_path(critical_node) + + path.reverse() + return path #return the array containing the critical path \ No newline at end of file diff --git a/core/build/lib/pygraph/algorithms/cycles.py b/core/build/lib/pygraph/algorithms/cycles.py new file mode 100644 index 0000000..d2134ef --- /dev/null +++ b/core/build/lib/pygraph/algorithms/cycles.py @@ -0,0 +1,108 @@ +# Copyright (c) 2008-2009 Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Cycle detection algorithms. + +@sort: find_cycle +""" + + +# Imports +from pygraph.classes.exceptions import InvalidGraphType +from pygraph.classes.digraph import digraph as digraph_class +from pygraph.classes.graph import graph as graph_class +from sys import getrecursionlimit, setrecursionlimit + +def find_cycle(graph): + """ + Find a cycle in the given graph. + + This function will return a list of nodes which form a cycle in the graph or an empty list if + no cycle exists. + + @type graph: graph, digraph + @param graph: Graph. + + @rtype: list + @return: List of nodes. + """ + + if (isinstance(graph, graph_class)): + directed = False + elif (isinstance(graph, digraph_class)): + directed = True + else: + raise InvalidGraphType + + def find_cycle_to_ancestor(node, ancestor): + """ + Find a cycle containing both node and ancestor. + """ + path = [] + while (node != ancestor): + if (node is None): + return [] + path.append(node) + node = spanning_tree[node] + path.append(node) + path.reverse() + return path + + def dfs(node): + """ + Depth-first search subfunction. + """ + visited[node] = 1 + # Explore recursively the connected component + for each in graph[node]: + if (cycle): + return + if (each not in visited): + spanning_tree[each] = node + dfs(each) + else: + if (directed or spanning_tree[node] != each): + cycle.extend(find_cycle_to_ancestor(node, each)) + + recursionlimit = getrecursionlimit() + setrecursionlimit(max(len(graph.nodes())*2,recursionlimit)) + + visited = {} # List for marking visited and non-visited nodes + spanning_tree = {} # Spanning tree + cycle = [] + + # Algorithm outer-loop + for each in graph: + # Select a non-visited node + if (each not in visited): + spanning_tree[each] = None + # Explore node's connected component + dfs(each) + if (cycle): + setrecursionlimit(recursionlimit) + return cycle + + setrecursionlimit(recursionlimit) + return [] diff --git a/core/build/lib/pygraph/algorithms/filters/__init__.py b/core/build/lib/pygraph/algorithms/filters/__init__.py new file mode 100644 index 0000000..ad7986e --- /dev/null +++ b/core/build/lib/pygraph/algorithms/filters/__init__.py @@ -0,0 +1,29 @@ +# Copyright (c) 2008-2009 Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Set of searching filters. +""" + +__import__('pkg_resources').declare_namespace(__name__) diff --git a/core/build/lib/pygraph/algorithms/filters/find.py b/core/build/lib/pygraph/algorithms/filters/find.py new file mode 100644 index 0000000..b586d91 --- /dev/null +++ b/core/build/lib/pygraph/algorithms/filters/find.py @@ -0,0 +1,78 @@ +# Copyright (c) 2008-2009 Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Search filter for finding a specific node. +""" + + +class find(object): + """ + Search filter for finding a specific node. + """ + + def __init__(self, target): + """ + Initialize the filter. + + @type target: node + @param target: Target node. + """ + self.graph = None + self.spanning_tree = None + self.target = target + self.done = False + + def configure(self, graph, spanning_tree): + """ + Configure the filter. + + @type graph: graph + @param graph: Graph. + + @type spanning_tree: dictionary + @param spanning_tree: Spanning tree. + """ + self.graph = graph + self.spanning_tree = spanning_tree + + def __call__(self, node, parent): + """ + Decide if the given node should be included in the search process. + + @type node: node + @param node: Given node. + + @type parent: node + @param parent: Given node's parent in the spanning tree. + + @rtype: boolean + @return: Whether the given node should be included in the search process. + """ + if (not self.done): + if (node == self.target): + self.done = True + return True + else: + return False \ No newline at end of file diff --git a/core/build/lib/pygraph/algorithms/filters/null.py b/core/build/lib/pygraph/algorithms/filters/null.py new file mode 100644 index 0000000..6a09a63 --- /dev/null +++ b/core/build/lib/pygraph/algorithms/filters/null.py @@ -0,0 +1,68 @@ +# Copyright (c) 2008-2009 Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Null searching filter. +""" + + +class null(object): + """ + Null search filter. + """ + + def __init__(self): + """ + Initialize the filter. + """ + self.graph = None + self.spanning_tree = None + + def configure(self, graph, spanning_tree): + """ + Configure the filter. + + @type graph: graph + @param graph: Graph. + + @type spanning_tree: dictionary + @param spanning_tree: Spanning tree. + """ + self.graph = graph + self.spanning_tree = spanning_tree + + def __call__(self, node, parent): + """ + Decide if the given node should be included in the search process. + + @type node: node + @param node: Given node. + + @type parent: node + @param parent: Given node's parent in the spanning tree. + + @rtype: boolean + @return: Whether the given node should be included in the search process. + """ + return True \ No newline at end of file diff --git a/core/build/lib/pygraph/algorithms/filters/radius.py b/core/build/lib/pygraph/algorithms/filters/radius.py new file mode 100644 index 0000000..6c9d567 --- /dev/null +++ b/core/build/lib/pygraph/algorithms/filters/radius.py @@ -0,0 +1,96 @@ +# Copyright (c) 2008-2009 Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Radial search filter. +""" + + +class radius(object): + """ + Radial search filter. + + This will keep searching contained inside a specified limit. + """ + + def __init__(self, radius): + """ + Initialize the filter. + + @type radius: number + @param radius: Search radius. + """ + self.graph = None + self.spanning_tree = None + self.radius = radius + self.done = False + + def configure(self, graph, spanning_tree): + """ + Configure the filter. + + @type graph: graph + @param graph: Graph. + + @type spanning_tree: dictionary + @param spanning_tree: Spanning tree. + """ + self.graph = graph + self.spanning_tree = spanning_tree + + def __call__(self, node, parent): + """ + Decide if the given node should be included in the search process. + + @type node: node + @param node: Given node. + + @type parent: node + @param parent: Given node's parent in the spanning tree. + + @rtype: boolean + @return: Whether the given node should be included in the search process. + """ + + def cost_to_root(node): + if (node is not None): + return cost_to_parent(node, st[node]) + cost_to_root(st[node]) + else: + return 0 + + def cost_to_parent(node, parent): + if (parent is not None): + return gr.edge_weight((parent, node)) + else: + return 0 + + gr = self.graph + st = self.spanning_tree + + cost = cost_to_parent(node, parent) + cost_to_root(parent) + + if (cost <= self.radius): + return True + else: + return False \ No newline at end of file diff --git a/core/build/lib/pygraph/algorithms/generators.py b/core/build/lib/pygraph/algorithms/generators.py new file mode 100644 index 0000000..04f92e9 --- /dev/null +++ b/core/build/lib/pygraph/algorithms/generators.py @@ -0,0 +1,132 @@ +# Copyright (c) 2008-2009 Pedro Matiello +# Zsolt Haraszti +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Random graph generators. + +@sort: generate, generate_hypergraph +""" + + +# Imports +from pygraph.classes.graph import graph +from pygraph.classes.digraph import digraph +from pygraph.classes.hypergraph import hypergraph +from random import randint, choice, shuffle #@UnusedImport +from time import time + +# Generator + +def generate(num_nodes, num_edges, directed=False, weight_range=(1, 1)): + """ + Create a random graph. + + @type num_nodes: number + @param num_nodes: Number of nodes. + + @type num_edges: number + @param num_edges: Number of edges. + + @type directed: bool + @param directed: Whether the generated graph should be directed or not. + + @type weight_range: tuple + @param weight_range: tuple of two integers as lower and upper limits on randomly generated + weights (uniform distribution). + """ + # Graph creation + if directed: + random_graph = digraph() + else: + random_graph = graph() + + # Nodes + nodes = list(range(num_nodes)) + random_graph.add_nodes(nodes) + + # Build a list of all possible edges + edges = [] + edges_append = edges.append + for x in nodes: + for y in nodes: + if ((directed and x != y) or (x > y)): + edges_append((x, y)) + + # Randomize the list + shuffle(edges) + + # Add edges to the graph + min_wt = min(weight_range) + max_wt = max(weight_range) + for i in range(num_edges): + each = edges[i] + random_graph.add_edge((each[0], each[1]), wt = randint(min_wt, max_wt)) + + return random_graph + + +def generate_hypergraph(num_nodes, num_edges, r = 0): + """ + Create a random hyper graph. + + @type num_nodes: number + @param num_nodes: Number of nodes. + + @type num_edges: number + @param num_edges: Number of edges. + + @type r: number + @param r: Uniform edges of size r. + """ + # Graph creation + random_graph = hypergraph() + + # Nodes + nodes = list(map(str, list(range(num_nodes)))) + random_graph.add_nodes(nodes) + + # Base edges + edges = list(map(str, list(range(num_nodes, num_nodes+num_edges)))) + random_graph.add_hyperedges(edges) + + # Connect the edges + if 0 == r: + # Add each edge with 50/50 probability + for e in edges: + for n in nodes: + if choice([True, False]): + random_graph.link(n, e) + + else: + # Add only uniform edges + for e in edges: + # First shuffle the nodes + shuffle(nodes) + + # Then take the first r nodes + for i in range(r): + random_graph.link(nodes[i], e) + + return random_graph diff --git a/core/build/lib/pygraph/algorithms/heuristics/__init__.py b/core/build/lib/pygraph/algorithms/heuristics/__init__.py new file mode 100644 index 0000000..2f7ca4d --- /dev/null +++ b/core/build/lib/pygraph/algorithms/heuristics/__init__.py @@ -0,0 +1,32 @@ +# Copyright (c) 2008-2009 Pedro Matiello +# Salim Fadhley +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Set of search heuristics. + +These are to be used with the C{heuristic_search()} function. +""" + +__import__('pkg_resources').declare_namespace(__name__) diff --git a/core/build/lib/pygraph/algorithms/heuristics/chow.py b/core/build/lib/pygraph/algorithms/heuristics/chow.py new file mode 100644 index 0000000..bfd7e14 --- /dev/null +++ b/core/build/lib/pygraph/algorithms/heuristics/chow.py @@ -0,0 +1,77 @@ +# Copyright (c) 2008-2009 Pedro Matiello +# Salim Fadhley +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Edmond Chow's heuristic for A*. +""" + + +# Imports +from pygraph.algorithms.minmax import shortest_path + + +class chow(object): + """ + An implementation of the graph searching heuristic proposed by Edmond Chow. + + Remember to call the C{optimize()} method before the heuristic search. + + For details, check: U{http://www.edmondchow.com/pubs/levdiff-aaai.pdf}. + """ + + def __init__(self, *centers): + """ + Initialize a Chow heuristic object. + """ + self.centers = centers + self.nodes = {} + + def optimize(self, graph): + """ + Build a dictionary mapping each pair of nodes to a number (the distance between them). + + @type graph: graph + @param graph: Graph. + """ + for center in self.centers: + shortest_routes = shortest_path(graph, center)[1] + for node, weight in list(shortest_routes.items()): + self.nodes.setdefault(node, []).append(weight) + + def __call__(self, start, end): + """ + Estimate how far start is from end. + + @type start: node + @param start: Start node. + + @type end: node + @param end: End node. + """ + assert len( list(self.nodes.keys()) ) > 0, "You need to optimize this heuristic for your graph before it can be used to estimate." + + cmp_sequence = list(zip( self.nodes[start], self.nodes[end] )) + chow_number = max( abs( a-b ) for a,b in cmp_sequence ) + return chow_number diff --git a/core/build/lib/pygraph/algorithms/heuristics/euclidean.py b/core/build/lib/pygraph/algorithms/heuristics/euclidean.py new file mode 100644 index 0000000..f0cbc1a --- /dev/null +++ b/core/build/lib/pygraph/algorithms/heuristics/euclidean.py @@ -0,0 +1,97 @@ +# Copyright (c) 2008-2009 Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +A* heuristic for euclidean graphs. +""" + + +# Imports + + +class euclidean(object): + """ + A* heuristic for Euclidean graphs. + + This heuristic has three requirements: + 1. All nodes should have the attribute 'position'; + 2. The weight of all edges should be the euclidean distance between the nodes it links; + 3. The C{optimize()} method should be called before the heuristic search. + + A small example for clarification: + + >>> g = graph.graph() + >>> g.add_nodes(['A','B','C']) + >>> g.add_node_attribute('A', ('position',(0,0))) + >>> g.add_node_attribute('B', ('position',(1,1))) + >>> g.add_node_attribute('C', ('position',(0,2))) + >>> g.add_edge('A','B', wt=2) + >>> g.add_edge('B','C', wt=2) + >>> g.add_edge('A','C', wt=4) + >>> h = graph.heuristics.euclidean() + >>> h.optimize(g) + >>> g.heuristic_search('A', 'C', h) + """ + + def __init__(self): + """ + Initialize the heuristic object. + """ + self.distances = {} + + def optimize(self, graph): + """ + Build a dictionary mapping each pair of nodes to a number (the distance between them). + + @type graph: graph + @param graph: Graph. + """ + for start in graph.nodes(): + for end in graph.nodes(): + for each in graph.node_attributes(start): + if (each[0] == 'position'): + start_attr = each[1] + break + for each in graph.node_attributes(end): + if (each[0] == 'position'): + end_attr = each[1] + break + dist = 0 + for i in range(len(start_attr)): + dist = dist + (float(start_attr[i]) - float(end_attr[i]))**2 + self.distances[(start,end)] = dist + + def __call__(self, start, end): + """ + Estimate how far start is from end. + + @type start: node + @param start: Start node. + + @type end: node + @param end: End node. + """ + assert len(list(self.distances.keys())) > 0, "You need to optimize this heuristic for your graph before it can be used to estimate." + + return self.distances[(start,end)] \ No newline at end of file diff --git a/core/build/lib/pygraph/algorithms/minmax.py b/core/build/lib/pygraph/algorithms/minmax.py new file mode 100644 index 0000000..95d4baa --- /dev/null +++ b/core/build/lib/pygraph/algorithms/minmax.py @@ -0,0 +1,516 @@ +# Copyright (c) 2007-2009 Pedro Matiello +# Peter Sagerson +# Johannes Reinhardt +# Rhys Ulerich +# Roy Smith +# Salim Fadhley +# Tomaz Kovacic +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Minimization and maximization algorithms. + +@sort: heuristic_search, minimal_spanning_tree, shortest_path, +shortest_path_bellman_ford +""" + +from pygraph.algorithms.utils import heappush, heappop +from pygraph.classes.exceptions import NodeUnreachable +from pygraph.classes.exceptions import NegativeWeightCycleError +from pygraph.classes.digraph import digraph +import bisect + +# Minimal spanning tree + +def minimal_spanning_tree(graph, root=None): + """ + Minimal spanning tree. + + @attention: Minimal spanning tree is meaningful only for weighted graphs. + + @type graph: graph + @param graph: Graph. + + @type root: node + @param root: Optional root node (will explore only root's connected component) + + @rtype: dictionary + @return: Generated spanning tree. + """ + visited = [] # List for marking visited and non-visited nodes + spanning_tree = {} # MInimal Spanning tree + + # Initialization + if (root is not None): + visited.append(root) + nroot = root + spanning_tree[root] = None + else: + nroot = 1 + + # Algorithm loop + while (nroot is not None): + ledge = _lightest_edge(graph, visited) + if (ledge == None): + if (root is not None): + break + nroot = _first_unvisited(graph, visited) + if (nroot is not None): + spanning_tree[nroot] = None + visited.append(nroot) + else: + spanning_tree[ledge[1]] = ledge[0] + visited.append(ledge[1]) + + return spanning_tree + + +def _first_unvisited(graph, visited): + """ + Return first unvisited node. + + @type graph: graph + @param graph: Graph. + + @type visited: list + @param visited: List of nodes. + + @rtype: node + @return: First unvisited node. + """ + for each in graph: + if (each not in visited): + return each + return None + + +def _lightest_edge(graph, visited): + """ + Return the lightest edge in graph going from a visited node to an unvisited one. + + @type graph: graph + @param graph: Graph. + + @type visited: list + @param visited: List of nodes. + + @rtype: tuple + @return: Lightest edge in graph going from a visited node to an unvisited one. + """ + lightest_edge = None + weight = None + for each in visited: + for other in graph[each]: + if (other not in visited): + w = graph.edge_weight((each, other)) + if (weight is None or w < weight or weight < 0): + lightest_edge = (each, other) + weight = w + return lightest_edge + + +# Shortest Path + +def shortest_path(graph, source): + """ + Return the shortest path distance between source and all other nodes using Dijkstra's + algorithm. + + @attention: All weights must be nonnegative. + + @see: shortest_path_bellman_ford + + @type graph: graph, digraph + @param graph: Graph. + + @type source: node + @param source: Node from which to start the search. + + @rtype: tuple + @return: A tuple containing two dictionaries, each keyed by target nodes. + 1. Shortest path spanning tree + 2. Shortest distance from given source to each target node + Inaccessible target nodes do not appear in either dictionary. + """ + # Initialization + dist = {source: 0} + previous = {source: None} + + # This is a sorted queue of (dist, node) 2-tuples. The first item in the + # queue is always either a finalized node that we can ignore or the node + # with the smallest estimated distance from the source. Note that we will + # not remove nodes from this list as they are finalized; we just ignore them + # when they come up. + q = [(0, source)] + + # The set of nodes for which we have final distances. + finished = set() + + # Algorithm loop + while len(q) > 0: + du, u = q.pop(0) + + # Process reachable, remaining nodes from u + if u not in finished: + finished.add(u) + for v in graph[u]: + if v not in finished: + alt = du + graph.edge_weight((u, v)) + if (v not in dist) or (alt < dist[v]): + dist[v] = alt + previous[v] = u + bisect.insort(q, (alt, v)) + + return previous, dist + + + +def shortest_path_bellman_ford(graph, source): + """ + Return the shortest path distance between the source node and all other + nodes in the graph using Bellman-Ford's algorithm. + + This algorithm is useful when you have a weighted (and obviously + a directed) graph with negative weights. + + @attention: The algorithm can detect negative weight cycles and will raise + an exception. It's meaningful only for directed weighted graphs. + + @see: shortest_path + + @type graph: digraph + @param graph: Digraph + + @type source: node + @param source: Source node of the graph + + @raise NegativeWeightCycleError: raises if it finds a negative weight cycle. + If this condition is met d(v) > d(u) + W(u, v) then raise the error. + + @rtype: tuple + @return: A tuple containing two dictionaries, each keyed by target nodes. + (same as shortest_path function that implements Dijkstra's algorithm) + 1. Shortest path spanning tree + 2. Shortest distance from given source to each target node + """ + # initialize the required data structures + distance = {source : 0} + predecessor = {source : None} + + # iterate and relax edges + for i in range(1,graph.order()-1): + for src,dst in graph.edges(): + if (src in distance) and (dst not in distance): + distance[dst] = distance[src] + graph.edge_weight((src,dst)) + predecessor[dst] = src + elif (src in distance) and (dst in distance) and \ + distance[src] + graph.edge_weight((src,dst)) < distance[dst]: + distance[dst] = distance[src] + graph.edge_weight((src,dst)) + predecessor[dst] = src + + # detect negative weight cycles + for src,dst in graph.edges(): + if src in distance and \ + dst in distance and \ + distance[src] + graph.edge_weight((src,dst)) < distance[dst]: + raise NegativeWeightCycleError("Detected a negative weight cycle on edge (%s, %s)" % (src,dst)) + + return predecessor, distance + +#Heuristics search + +def heuristic_search(graph, start, goal, heuristic): + """ + A* search algorithm. + + A set of heuristics is available under C{graph.algorithms.heuristics}. User-created heuristics + are allowed too. + + @type graph: graph, digraph + @param graph: Graph + + @type start: node + @param start: Start node + + @type goal: node + @param goal: Goal node + + @type heuristic: function + @param heuristic: Heuristic function + + @rtype: list + @return: Optimized path from start to goal node + """ + + # The queue stores priority, node, cost to reach, and parent. + queue = [ (0, start, 0, None) ] + + # This dictionary maps queued nodes to distance of discovered paths + # and the computed heuristics to goal. We avoid to compute the heuristics + # more than once and to insert too many times the node in the queue. + g = {} + + # This maps explored nodes to parent closest to the start + explored = {} + + while queue: + _, current, dist, parent = heappop(queue) + + if current == goal: + path = [current] + [ n for n in _reconstruct_path( parent, explored ) ] + path.reverse() + return path + + if current in explored: + continue + + explored[current] = parent + + for neighbor in graph[current]: + if neighbor in explored: + continue + + ncost = dist + graph.edge_weight((current, neighbor)) + + if neighbor in g: + qcost, h = g[neighbor] + if qcost <= ncost: + continue + # if ncost < qcost, a longer path to neighbor remains + # g. Removing it would need to filter the whole + # queue, it's better just to leave it there and ignore + # it when we visit the node a second time. + else: + h = heuristic(neighbor, goal) + + g[neighbor] = ncost, h + heappush(queue, (ncost + h, neighbor, ncost, current)) + + raise NodeUnreachable( start, goal ) + +def _reconstruct_path(node, parents): + while node is not None: + yield node + node = parents[node] + +#maximum flow/minimum cut + +def maximum_flow(graph, source, sink, caps = None): + """ + Find a maximum flow and minimum cut of a directed graph by the Edmonds-Karp algorithm. + + @type graph: digraph + @param graph: Graph + + @type source: node + @param source: Source of the flow + + @type sink: node + @param sink: Sink of the flow + + @type caps: dictionary + @param caps: Dictionary specifying a maximum capacity for each edge. If not given, the weight of the edge + will be used as its capacity. Otherwise, for each edge (a,b), caps[(a,b)] should be given. + + @rtype: tuple + @return: A tuple containing two dictionaries + 1. contains the flow through each edge for a maximal flow through the graph + 2. contains to which component of a minimum cut each node belongs + """ + + #handle optional argument, if weights are available, use them, if not, assume one + if caps == None: + caps = {} + for edge in graph.edges(): + caps[edge] = graph.edge_weight((edge[0],edge[1])) + + #data structures to maintain + f = {}.fromkeys(graph.edges(),0) + label = {}.fromkeys(graph.nodes(),[]) + label[source] = ['-',float('Inf')] + u = {}.fromkeys(graph.nodes(),False) + d = {}.fromkeys(graph.nodes(),float('Inf')) + #queue for labelling + q = [source] + + finished = False + while not finished: + #choose first labelled vertex with u == false + for i in range(len(q)): + if not u[q[i]]: + v = q.pop(i) + break + + #find augmenting path + for w in graph.neighbors(v): + if label[w] == [] and f[(v,w)] < caps[(v,w)]: + d[w] = min(caps[(v,w)] - f[(v,w)],d[v]) + label[w] = [v,'+',d[w]] + q.append(w) + for w in graph.incidents(v): + if label[w] == [] and f[(w,v)] > 0: + d[w] = min(f[(w,v)],d[v]) + label[w] = [v,'-',d[w]] + q.append(w) + + u[v] = True + + #extend flow by augmenting path + if label[sink] != []: + delta = label[sink][-1] + w = sink + while w != source: + v = label[w][0] + if label[w][1] == '-': + f[(w,v)] = f[(w,v)] - delta + else: + f[(v,w)] = f[(v,w)] + delta + w = v + #reset labels + label = {}.fromkeys(graph.nodes(),[]) + label[source] = ['-',float('Inf')] + q = [source] + u = {}.fromkeys(graph.nodes(),False) + d = {}.fromkeys(graph.nodes(),float('Inf')) + + #check whether finished + finished = True + for node in graph.nodes(): + if label[node] != [] and u[node] == False: + finished = False + + #find the two components of the cut + cut = {} + for node in graph.nodes(): + if label[node] == []: + cut[node] = 1 + else: + cut[node] = 0 + return (f,cut) + +def cut_value(graph, flow, cut): + """ + Calculate the value of a cut. + + @type graph: digraph + @param graph: Graph + + @type flow: dictionary + @param flow: Dictionary containing a flow for each edge. + + @type cut: dictionary + @param cut: Dictionary mapping each node to a subset index. The function only considers the flow between + nodes with 0 and 1. + + @rtype: float + @return: The value of the flow between the subsets 0 and 1 + """ + #max flow/min cut value calculation + S = [] + T = [] + for node in list(cut.keys()): + if cut[node] == 0: + S.append(node) + elif cut[node] == 1: + T.append(node) + value = 0 + for node in S: + for neigh in graph.neighbors(node): + if neigh in T: + value = value + flow[(node,neigh)] + for inc in graph.incidents(node): + if inc in T: + value = value - flow[(inc,node)] + return value + +def cut_tree(igraph, caps = None): + """ + Construct a Gomory-Hu cut tree by applying the algorithm of Gusfield. + + @type igraph: graph + @param igraph: Graph + + @type caps: dictionary + @param caps: Dictionary specifying a maximum capacity for each edge. If not given, the weight of the edge + will be used as its capacity. Otherwise, for each edge (a,b), caps[(a,b)] should be given. + + @rtype: dictionary + @return: Gomory-Hu cut tree as a dictionary, where each edge is associated with its weight + """ + + #maximum flow needs a digraph, we get a graph + #I think this conversion relies on implementation details outside the api and may break in the future + graph = digraph() + graph.add_graph(igraph) + + #handle optional argument + if not caps: + caps = {} + for edge in graph.edges(): + caps[edge] = igraph.edge_weight(edge) + + #temporary flow variable + f = {} + + #we use a numbering of the nodes for easier handling + n = {} + N = 0 + for node in graph.nodes(): + n[N] = node + N = N + 1 + + #predecessor function + p = {}.fromkeys(list(range(N)),0) + p[0] = None + + for s in range(1,N): + t = p[s] + S = [] + #max flow calculation + (flow,cut) = maximum_flow(graph,n[s],n[t],caps) + for i in range(N): + if cut[n[i]] == 0: + S.append(i) + + value = cut_value(graph,flow,cut) + + f[s] = value + + for i in range(N): + if i == s: + continue + if i in S and p[i] == t: + p[i] = s + if p[t] in S: + p[s] = p[t] + p[t] = s + f[s] = f[t] + f[t] = value + + #cut tree is a dictionary, where each edge is associated with its weight + b = {} + for i in range(1,N): + b[(n[i],n[p[i]])] = f[i] + return b + diff --git a/core/build/lib/pygraph/algorithms/pagerank.py b/core/build/lib/pygraph/algorithms/pagerank.py new file mode 100644 index 0000000..1a72a22 --- /dev/null +++ b/core/build/lib/pygraph/algorithms/pagerank.py @@ -0,0 +1,76 @@ +# Copyright (c) 2010 Pedro Matiello +# Juarez Bochi +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +PageRank algoritm + +@sort: pagerank +""" + +def pagerank(graph, damping_factor=0.85, max_iterations=100, min_delta=0.00001): + """ + Compute and return the PageRank in an directed graph. + + @type graph: digraph + @param graph: Digraph. + + @type damping_factor: number + @param damping_factor: PageRank dumping factor. + + @type max_iterations: number + @param max_iterations: Maximum number of iterations. + + @type min_delta: number + @param min_delta: Smallest variation required to have a new iteration. + + @rtype: Dict + @return: Dict containing all the nodes PageRank. + """ + + nodes = graph.nodes() + graph_size = len(nodes) + if graph_size == 0: + return {} + min_value = (1.0-damping_factor)/graph_size #value for nodes without inbound links + + # itialize the page rank dict with 1/N for all nodes + pagerank = dict.fromkeys(nodes, 1.0/graph_size) + + for i 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 graph.incidents(node): + rank += damping_factor * pagerank[referring_page] / len(graph.neighbors(referring_page)) + + diff += abs(pagerank[node] - rank) + pagerank[node] = rank + + #stop if PageRank has converged + if diff < min_delta: + break + + return pagerank diff --git a/core/build/lib/pygraph/algorithms/searching.py b/core/build/lib/pygraph/algorithms/searching.py new file mode 100644 index 0000000..8cc1dfd --- /dev/null +++ b/core/build/lib/pygraph/algorithms/searching.py @@ -0,0 +1,153 @@ +# Copyright (c) 2007-2009 Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Search algorithms. + +@sort: breadth_first_search, depth_first_search +""" + + +# Imports +from pygraph.algorithms.filters.null import null +from sys import getrecursionlimit, setrecursionlimit + + +# Depth-first search + +def depth_first_search(graph, root=None, filter=null()): + """ + Depth-first search. + + @type graph: graph, digraph + @param graph: Graph. + + @type root: node + @param root: Optional root node (will explore only root's connected component) + + @rtype: tuple + @return: A tupple containing a dictionary and two lists: + 1. Generated spanning tree + 2. Graph's preordering + 3. Graph's postordering + """ + + recursionlimit = getrecursionlimit() + setrecursionlimit(max(len(graph.nodes())*2,recursionlimit)) + + def dfs(node): + """ + Depth-first search subfunction. + """ + visited[node] = 1 + pre.append(node) + # Explore recursively the connected component + for each in graph[node]: + if (each not in visited and list(filter(each, node))): + spanning_tree[each] = node + dfs(each) + post.append(node) + + visited = {} # List for marking visited and non-visited nodes + spanning_tree = {} # Spanning tree + pre = [] # Graph's preordering + post = [] # Graph's postordering + filter.configure(graph, spanning_tree) + + # DFS from one node only + if (root is not None): + if list(filter(root, None)): + spanning_tree[root] = None + dfs(root) + setrecursionlimit(recursionlimit) + return spanning_tree, pre, post + + # Algorithm loop + for each in graph: + # Select a non-visited node + if (each not in visited and list(filter(each, None))): + spanning_tree[each] = None + # Explore node's connected component + dfs(each) + + setrecursionlimit(recursionlimit) + + return (spanning_tree, pre, post) + + +# Breadth-first search + +def breadth_first_search(graph, root=None, filter=null()): + """ + Breadth-first search. + + @type graph: graph, digraph + @param graph: Graph. + + @type root: node + @param root: Optional root node (will explore only root's connected component) + + @rtype: tuple + @return: A tuple containing a dictionary and a list. + 1. Generated spanning tree + 2. Graph's level-based ordering + """ + + def bfs(): + """ + Breadth-first search subfunction. + """ + while (queue != []): + node = queue.pop(0) + + for other in graph[node]: + if (other not in spanning_tree and list(filter(other, node))): + queue.append(other) + ordering.append(other) + spanning_tree[other] = node + + queue = [] # Visiting queue + spanning_tree = {} # Spanning tree + ordering = [] + filter.configure(graph, spanning_tree) + + # BFS from one node only + if (root is not None): + if list(filter(root, None)): + queue.append(root) + ordering.append(root) + spanning_tree[root] = None + bfs() + return spanning_tree, ordering + + # Algorithm + for each in graph: + if (each not in spanning_tree): + if list(filter(each, None)): + queue.append(each) + ordering.append(each) + spanning_tree[each] = None + bfs() + + return spanning_tree, ordering diff --git a/core/build/lib/pygraph/algorithms/sorting.py b/core/build/lib/pygraph/algorithms/sorting.py new file mode 100644 index 0000000..8b97598 --- /dev/null +++ b/core/build/lib/pygraph/algorithms/sorting.py @@ -0,0 +1,51 @@ +# Copyright (c) 2007-2009 Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Sorting algorithms. + +@sort: topological_sorting +""" + + +# Imports +from pygraph.algorithms.searching import depth_first_search + +# Topological sorting +def topological_sorting(graph): + """ + Topological sorting. + + @attention: Topological sorting is meaningful only for directed acyclic graphs. + + @type graph: digraph + @param graph: Graph. + + @rtype: list + @return: Topological sorting for the graph. + """ + # The topological sorting of a DAG is equivalent to its reverse postordering. + order = depth_first_search(graph)[2] + order.reverse() + return order diff --git a/core/build/lib/pygraph/algorithms/traversal.py b/core/build/lib/pygraph/algorithms/traversal.py new file mode 100644 index 0000000..820ffb4 --- /dev/null +++ b/core/build/lib/pygraph/algorithms/traversal.py @@ -0,0 +1,84 @@ +# Copyright (c) 2008-2009 Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Traversal algorithms. + +@sort: traversal +""" + + +# Minimal spanning tree + +def traversal(graph, node, order): + """ + Graph traversal iterator. + + @type graph: graph, digraph + @param graph: Graph. + + @type node: node + @param node: Node. + + @type order: string + @param order: traversal ordering. Possible values are: + 2. 'pre' - Preordering (default) + 1. 'post' - Postordering + + @rtype: iterator + @return: Traversal iterator. + """ + visited = {} + if (order == 'pre'): + pre = 1 + post = 0 + elif (order == 'post'): + pre = 0 + post = 1 + + for each in _dfs(graph, visited, node, pre, post): + yield each + + +def _dfs(graph, visited, node, pre, post): + """ + Depth-first search subfunction for traversals. + + @type graph: graph, digraph + @param graph: Graph. + + @type visited: dictionary + @param visited: List of nodes (visited nodes are marked non-zero). + + @type node: node + @param node: Node to be explored by DFS. + """ + visited[node] = 1 + if (pre): yield node + # Explore recursively the connected component + for each in graph[node]: + if (each not in visited): + for other in _dfs(graph, visited, each, pre, post): + yield other + if (post): yield node diff --git a/core/build/lib/pygraph/algorithms/utils.py b/core/build/lib/pygraph/algorithms/utils.py new file mode 100644 index 0000000..1410f86 --- /dev/null +++ b/core/build/lib/pygraph/algorithms/utils.py @@ -0,0 +1,89 @@ +# Copyright (c) 2008-2009 Pedro Matiello +# Roy Smith +# Salim Fadhley +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Miscellaneous useful stuff. +""" + +# Imports +from heapq import heappush, heappop, heapify + + +# Priority Queue +class priority_queue: + """ + Priority queue. + """ + + def __init__(self, list=[]): + self.heap = [HeapItem(i, 0) for i in list] + heapify(self.heap) + + def __contains__(self, item): + for heap_item in self.heap: + if item == heap_item.item: + return True + return False + + def __len__(self): + return len(self.heap) + + def empty(self): + return len(self.heap) == 0 + + def insert(self, item, priority): + """ + Insert item into the queue, with the given priority. + """ + heappush(self.heap, HeapItem(item, priority)) + + def pop(self): + """ + Return the item with the lowest priority, and remove it from the queue. + """ + return heappop(self.heap).item + + def peek(self): + """ + Return the item with the lowest priority. The queue is unchanged. + """ + return self.heap[0].item + + def discard(self, item): + new_heap = [] + for heap_item in self.heap: + if item != heap_item.item: + new_heap.append(heap_item) + self.heap = new_heap + heapify(self.heap) + +class HeapItem: + def __init__(self, item, priority): + self.item = item + self.priority = priority + + def __cmp__(self, other): + return cmp(self.priority, other.priority) diff --git a/core/build/lib/pygraph/classes/__init__.py b/core/build/lib/pygraph/classes/__init__.py new file mode 100644 index 0000000..ee5fc46 --- /dev/null +++ b/core/build/lib/pygraph/classes/__init__.py @@ -0,0 +1,27 @@ +# Copyright (c) 2008-2009 Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Data structure classes. +""" \ No newline at end of file diff --git a/core/build/lib/pygraph/classes/digraph.py b/core/build/lib/pygraph/classes/digraph.py new file mode 100644 index 0000000..97b3442 --- /dev/null +++ b/core/build/lib/pygraph/classes/digraph.py @@ -0,0 +1,259 @@ +# Copyright (c) 2007-2009 Pedro Matiello +# Christian Muise +# Johannes Reinhardt +# Nathan Davis +# Zsolt Haraszti +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +""" +Digraph class +""" + +# Imports +from pygraph.classes.exceptions import AdditionError +from pygraph.mixins.labeling import labeling +from pygraph.mixins.common import common +from pygraph.mixins.basegraph import basegraph + +class digraph (basegraph, common, labeling): + """ + Digraph class. + + Digraphs are built of nodes and directed edges. + + @sort: __eq__, __init__, __ne__, add_edge, add_node, del_edge, del_node, edges, has_edge, has_node, + incidents, neighbors, node_order, nodes + """ + + DIRECTED = True + + def __init__(self): + """ + Initialize a digraph. + """ + common.__init__(self) + labeling.__init__(self) + self.node_neighbors = {} # Pairing: Node -> Neighbors + self.node_incidence = {} # Pairing: Node -> Incident nodes + + + def nodes(self): + """ + Return node list. + + @rtype: list + @return: Node list. + """ + return list(self.node_neighbors.keys()) + + + def neighbors(self, node): + """ + Return all nodes that are directly accessible from given node. + + @type node: node + @param node: Node identifier + + @rtype: list + @return: List of nodes directly accessible from given node. + """ + return self.node_neighbors[node] + + + def incidents(self, node): + """ + Return all nodes that are incident to the given node. + + @type node: node + @param node: Node identifier + + @rtype: list + @return: List of nodes directly accessible from given node. + """ + return self.node_incidence[node] + + def edges(self): + """ + Return all edges in the graph. + + @rtype: list + @return: List of all edges in the graph. + """ + return [ a for a in self._edges() ] + + def _edges(self): + for n, neighbors in list(self.node_neighbors.items()): + for neighbor in neighbors: + yield (n, neighbor) + + def has_node(self, node): + """ + Return whether the requested node exists. + + @type node: node + @param node: Node identifier + + @rtype: boolean + @return: Truth-value for node existence. + """ + return node in self.node_neighbors + + def add_node(self, node, attrs = None): + """ + Add given node to the graph. + + @attention: While nodes can be of any type, it's strongly recommended to use only + numbers and single-line strings as node identifiers if you intend to use write(). + + @type node: node + @param node: Node identifier. + + @type attrs: list + @param attrs: List of node attributes specified as (attribute, value) tuples. + """ + if attrs is None: + attrs = [] + if (node not in self.node_neighbors): + self.node_neighbors[node] = [] + self.node_incidence[node] = [] + self.node_attr[node] = attrs + else: + raise AdditionError("Node %s already in digraph" % node) + + + def add_edge(self, edge, wt = 1, label="", attrs = []): + """ + Add an directed edge to the graph connecting two nodes. + + An edge, here, is a pair of nodes like C{(n, m)}. + + @type edge: tuple + @param edge: Edge. + + @type wt: number + @param wt: Edge weight. + + @type label: string + @param label: Edge label. + + @type attrs: list + @param attrs: List of node attributes specified as (attribute, value) tuples. + """ + u, v = edge + for n in [u,v]: + if not n in self.node_neighbors: + raise AdditionError( "%s is missing from the node_neighbors table" % n ) + if not n in self.node_incidence: + raise AdditionError( "%s is missing from the node_incidence table" % n ) + + if v in self.node_neighbors[u] and u in self.node_incidence[v]: + raise AdditionError("Edge (%s, %s) already in digraph" % (u, v)) + else: + self.node_neighbors[u].append(v) + self.node_incidence[v].append(u) + self.set_edge_weight((u, v), wt) + self.add_edge_attributes( (u, v), attrs ) + self.set_edge_properties( (u, v), label=label, weight=wt ) + + + def del_node(self, node): + """ + Remove a node from the graph. + + @type node: node + @param node: Node identifier. + """ + for each in list(self.incidents(node)): + # Delete all the edges incident on this node + self.del_edge((each, node)) + + for each in list(self.neighbors(node)): + # Delete all the edges pointing to this node. + self.del_edge((node, each)) + + # Remove this node from the neighbors and incidents tables + del(self.node_neighbors[node]) + del(self.node_incidence[node]) + + # Remove any labeling which may exist. + self.del_node_labeling( node ) + + + def del_edge(self, edge): + """ + Remove an directed edge from the graph. + + @type edge: tuple + @param edge: Edge. + """ + u, v = edge + self.node_neighbors[u].remove(v) + self.node_incidence[v].remove(u) + self.del_edge_labeling( (u,v) ) + + + def has_edge(self, edge): + """ + Return whether an edge exists. + + @type edge: tuple + @param edge: Edge. + + @rtype: boolean + @return: Truth-value for edge existence. + """ + u, v = edge + return (u, v) in self.edge_properties + + + def node_order(self, node): + """ + Return the order of the given node. + + @rtype: number + @return: Order of the given node. + """ + return len(self.neighbors(node)) + + def __eq__(self, other): + """ + Return whether this graph is equal to another one. + + @type other: graph, digraph + @param other: Other graph or digraph + + @rtype: boolean + @return: Whether this graph and the other are equal. + """ + return common.__eq__(self, other) and labeling.__eq__(self, other) + + def __ne__(self, other): + """ + Return whether this graph is not equal to another one. + + @type other: graph, digraph + @param other: Other graph or digraph + + @rtype: boolean + @return: Whether this graph and the other are different. + """ + return not (self == other) diff --git a/core/build/lib/pygraph/classes/exceptions.py b/core/build/lib/pygraph/classes/exceptions.py new file mode 100644 index 0000000..1a3cd5f --- /dev/null +++ b/core/build/lib/pygraph/classes/exceptions.py @@ -0,0 +1,76 @@ +# Copyright (c) 2008-2009 Pedro Matiello +# Salim Fadhley +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Exceptions. +""" + +# Graph errors + +class GraphError(RuntimeError): + """ + A base-class for the various kinds of errors that occur in the the python-graph class. + """ + pass + +class AdditionError(GraphError): + """ + This error is raised when trying to add a node or edge already added to the graph or digraph. + """ + pass + +class NodeUnreachable(GraphError): + """ + Goal could not be reached from start. + """ + def __init__(self, start, goal): + msg = "Node %s could not be reached from node %s" % ( repr(goal), repr(start) ) + InvalidGraphType.__init__(self, msg) + self.start = start + self.goal = goal + +class InvalidGraphType(GraphError): + """ + Invalid graph type. + """ + pass + +# Algorithm errors + +class AlgorithmError(RuntimeError): + """ + A base-class for the various kinds of errors that occur in the the + algorithms package. + """ + pass + +class NegativeWeightCycleError(AlgorithmError): + """ + Algorithms like the Bellman-Ford algorithm can detect and raise an exception + when they encounter a negative weight cycle. + + @see: pygraph.algorithms.shortest_path_bellman_ford + """ + pass diff --git a/core/build/lib/pygraph/classes/graph.py b/core/build/lib/pygraph/classes/graph.py new file mode 100644 index 0000000..8aee642 --- /dev/null +++ b/core/build/lib/pygraph/classes/graph.py @@ -0,0 +1,230 @@ +# Copyright (c) 2007-2009 Pedro Matiello +# Johannes Reinhardt +# Nathan Davis +# Zsolt Haraszti +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Graph class +""" + + +# Imports +from pygraph.classes.exceptions import AdditionError +from pygraph.mixins.labeling import labeling +from pygraph.mixins.common import common +from pygraph.mixins.basegraph import basegraph + + +class graph(basegraph, common, labeling): + """ + Graph class. + + Graphs are built of nodes and edges. + + @sort: __eq__, __init__, __ne__, add_edge, add_node, del_edge, del_node, edges, has_edge, has_node, + neighbors, node_order, nodes + """ + + DIRECTED = False + + + def __init__(self): + """ + Initialize a graph. + """ + common.__init__(self) + labeling.__init__(self) + self.node_neighbors = {} # Pairing: Node -> Neighbors + + def nodes(self): + """ + Return node list. + + @rtype: list + @return: Node list. + """ + return list(self.node_neighbors.keys()) + + + def neighbors(self, node): + """ + Return all nodes that are directly accessible from given node. + + @type node: node + @param node: Node identifier + + @rtype: list + @return: List of nodes directly accessible from given node. + """ + return self.node_neighbors[node] + + def edges(self): + """ + Return all edges in the graph. + + @rtype: list + @return: List of all edges in the graph. + """ + return [ a for a in list(self.edge_properties.keys()) ] + + def has_node(self, node): + """ + Return whether the requested node exists. + + @type node: node + @param node: Node identifier + + @rtype: boolean + @return: Truth-value for node existence. + """ + return node in self.node_neighbors + + + def add_node(self, node, attrs=None): + """ + Add given node to the graph. + + @attention: While nodes can be of any type, it's strongly recommended to use only + numbers and single-line strings as node identifiers if you intend to use write(). + + @type node: node + @param node: Node identifier. + + @type attrs: list + @param attrs: List of node attributes specified as (attribute, value) tuples. + """ + if attrs is None: + attrs = [] + if (not node in self.node_neighbors): + self.node_neighbors[node] = [] + self.node_attr[node] = attrs + else: + raise AdditionError("Node %s already in graph" % node) + + def add_edge(self, edge, wt=1, label='', attrs=[]): + """ + Add an edge to the graph connecting two nodes. + + An edge, here, is a pair of nodes like C{(n, m)}. + + @type edge: tuple + @param edge: Edge. + + @type wt: number + @param wt: Edge weight. + + @type label: string + @param label: Edge label. + + @type attrs: list + @param attrs: List of node attributes specified as (attribute, value) tuples. + """ + u, v = edge + if (v not in self.node_neighbors[u] and u not in self.node_neighbors[v]): + self.node_neighbors[u].append(v) + if (u != v): + self.node_neighbors[v].append(u) + + self.add_edge_attributes((u,v), attrs) + self.set_edge_properties((u, v), label=label, weight=wt) + else: + raise AdditionError("Edge (%s, %s) already in graph" % (u, v)) + + + def del_node(self, node): + """ + Remove a node from the graph. + + @type node: node + @param node: Node identifier. + """ + for each in list(self.neighbors(node)): + if (each != node): + self.del_edge((each, node)) + del(self.node_neighbors[node]) + del(self.node_attr[node]) + + + def del_edge(self, edge): + """ + Remove an edge from the graph. + + @type edge: tuple + @param edge: Edge. + """ + u, v = edge + self.node_neighbors[u].remove(v) + self.del_edge_labeling((u, v)) + if (u != v): + self.node_neighbors[v].remove(u) + self.del_edge_labeling((v, u)) # TODO: This is redundant + + def has_edge(self, edge): + """ + Return whether an edge exists. + + @type edge: tuple + @param edge: Edge. + + @rtype: boolean + @return: Truth-value for edge existence. + """ + u,v = edge + return (u,v) in self.edge_properties and (v,u) in self.edge_properties + + + def node_order(self, node): + """ + Return the order of the graph + + @rtype: number + @return: Order of the given node. + """ + return len(self.neighbors(node)) + + + def __eq__(self, other): + """ + Return whether this graph is equal to another one. + + @type other: graph, digraph + @param other: Other graph or digraph + + @rtype: boolean + @return: Whether this graph and the other are equal. + """ + return common.__eq__(self, other) and labeling.__eq__(self, other) + + def __ne__(self, other): + """ + Return whether this graph is not equal to another one. + + @type other: graph, digraph + @param other: Other graph or digraph + + @rtype: boolean + @return: Whether this graph and the other are different. + """ + return not (self == other) diff --git a/core/build/lib/pygraph/classes/hypergraph.py b/core/build/lib/pygraph/classes/hypergraph.py new file mode 100644 index 0000000..4742247 --- /dev/null +++ b/core/build/lib/pygraph/classes/hypergraph.py @@ -0,0 +1,363 @@ +# Copyright (c) 2008-2009 Pedro Matiello +# Anand Jeyahar +# Christian Muise +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Hypergraph class +""" + + +# Imports +from pygraph.classes.graph import graph +from pygraph.classes.exceptions import AdditionError + +from pygraph.mixins.labeling import labeling +from pygraph.mixins.common import common +from pygraph.mixins.basegraph import basegraph + +class hypergraph (basegraph, common, labeling): + """ + Hypergraph class. + + Hypergraphs are a generalization of graphs where an edge (called hyperedge) can connect more + than two nodes. + + @sort: __init__, __len__, __str__, add_hyperedge, add_hyperedges, add_node, add_nodes, + del_edge, has_node, has_edge, has_hyperedge, hyperedges, link, links, nodes, unlink + """ + + # Technically this isn't directed, but it gives us the right + # behaviour with the parent classes. + DIRECTED = True + + def __init__(self): + """ + Initialize a hypergraph. + """ + common.__init__(self) + labeling.__init__(self) + self.node_links = {} # Pairing: Node -> Hyperedge + self.edge_links = {} # Pairing: Hyperedge -> Node + self.graph = graph() # Ordinary graph + + + def nodes(self): + """ + Return node list. + + @rtype: list + @return: Node list. + """ + return list(self.node_links.keys()) + + + def edges(self): + """ + Return the hyperedge list. + + @rtype: list + @return: List of hyperedges in the graph. + """ + return self.hyperedges() + + + def hyperedges(self): + """ + Return hyperedge list. + + @rtype: list + @return: List of hyperedges in the graph. + """ + return list(self.edge_links.keys()) + + + def has_edge(self, hyperedge): + """ + Return whether the requested node exists. + + @type hyperedge: hyperedge + @param hyperedge: Hyperedge identifier + + @rtype: boolean + @return: Truth-value for hyperedge existence. + """ + return self.has_hyperedge(hyperedge) + + + def has_hyperedge(self, hyperedge): + """ + Return whether the requested node exists. + + @type hyperedge: hyperedge + @param hyperedge: Hyperedge identifier + + @rtype: boolean + @return: Truth-value for hyperedge existence. + """ + return hyperedge in self.edge_links + + + def links(self, obj): + """ + Return all nodes connected by the given hyperedge or all hyperedges + connected to the given hypernode. + + @type obj: hyperedge + @param obj: Object identifier. + + @rtype: list + @return: List of node objects linked to the given hyperedge. + """ + if obj in self.edge_links: + return self.edge_links[obj] + else: + return self.node_links[obj] + + + def neighbors(self, obj): + """ + Return all neighbors adjacent to the given node. + + @type obj: node + @param obj: Object identifier. + + @rtype: list + @return: List of all node objects adjacent to the given node. + """ + neighbors = set([]) + + for e in self.node_links[obj]: + neighbors.update(set(self.edge_links[e])) + + return list(neighbors - set([obj])) + + + def has_node(self, node): + """ + Return whether the requested node exists. + + @type node: node + @param node: Node identifier + + @rtype: boolean + @return: Truth-value for node existence. + """ + return node in self.node_links + + + def add_node(self, node): + """ + Add given node to the hypergraph. + + @attention: While nodes can be of any type, it's strongly recommended to use only numbers + and single-line strings as node identifiers if you intend to use write(). + + @type node: node + @param node: Node identifier. + """ + if (not node in self.node_links): + self.node_links[node] = [] + self.node_attr[node] = [] + self.graph.add_node((node,'n')) + else: + raise AdditionError("Node %s already in graph" % node) + + + def del_node(self, node): + """ + Delete a given node from the hypergraph. + + @type node: node + @param node: Node identifier. + """ + if self.has_node(node): + for e in self.node_links[node]: + self.edge_links[e].remove(node) + + self.node_links.pop(node) + self.graph.del_node((node,'n')) + + + def add_edge(self, hyperedge): + """ + Add given hyperedge to the hypergraph. + + @attention: While hyperedge-nodes can be of any type, it's strongly recommended to use only + numbers and single-line strings as node identifiers if you intend to use write(). + + @type hyperedge: hyperedge + @param hyperedge: Hyperedge identifier. + """ + self.add_hyperedge(hyperedge) + + + def add_hyperedge(self, hyperedge): + """ + Add given hyperedge to the hypergraph. + + @attention: While hyperedge-nodes can be of any type, it's strongly recommended to use only + numbers and single-line strings as node identifiers if you intend to use write(). + + @type hyperedge: hyperedge + @param hyperedge: Hyperedge identifier. + """ + if (not hyperedge in self.edge_links): + self.edge_links[hyperedge] = [] + self.graph.add_node((hyperedge,'h')) + + + def add_edges(self, edgelist): + """ + Add given hyperedges to the hypergraph. + + @attention: While hyperedge-nodes can be of any type, it's strongly recommended to use only + numbers and single-line strings as node identifiers if you intend to use write(). + + @type edgelist: list + @param edgelist: List of hyperedge-nodes to be added to the graph. + """ + self.add_hyperedges(edgelist) + + + def add_hyperedges(self, edgelist): + """ + Add given hyperedges to the hypergraph. + + @attention: While hyperedge-nodes can be of any type, it's strongly recommended to use only + numbers and single-line strings as node identifiers if you intend to use write(). + + @type edgelist: list + @param edgelist: List of hyperedge-nodes to be added to the graph. + """ + for each in edgelist: + self.add_hyperedge(each) + + + def del_edge(self, hyperedge): + """ + Delete the given hyperedge. + + @type hyperedge: hyperedge + @param hyperedge: Hyperedge identifier. + """ + self.del_hyperedge(hyperedge) + + + def del_hyperedge(self, hyperedge): + """ + Delete the given hyperedge. + + @type hyperedge: hyperedge + @param hyperedge: Hyperedge identifier. + """ + if (hyperedge in self.hyperedges()): + for n in self.edge_links[hyperedge]: + self.node_links[n].remove(hyperedge) + + del(self.edge_links[hyperedge]) + self.del_edge_labeling(hyperedge) + self.graph.del_node((hyperedge,'h')) + + + def link(self, node, hyperedge): + """ + Link given node and hyperedge. + + @type node: node + @param node: Node. + + @type hyperedge: node + @param hyperedge: Hyperedge. + """ + if (hyperedge not in self.node_links[node]): + self.edge_links[hyperedge].append(node) + self.node_links[node].append(hyperedge) + self.graph.add_edge(((node,'n'), (hyperedge,'h'))) + else: + raise AdditionError("Link (%s, %s) already in graph" % (node, hyperedge)) + + + def unlink(self, node, hyperedge): + """ + Unlink given node and hyperedge. + + @type node: node + @param node: Node. + + @type hyperedge: hyperedge + @param hyperedge: Hyperedge. + """ + self.node_links[node].remove(hyperedge) + self.edge_links[hyperedge].remove(node) + self.graph.del_edge(((node,'n'), (hyperedge,'h'))) + + + def rank(self): + """ + Return the rank of the given hypergraph. + + @rtype: int + @return: Rank of graph. + """ + max_rank = 0 + + for each in self.hyperedges(): + if len(self.edge_links[each]) > max_rank: + max_rank = len(self.edge_links[each]) + + return max_rank + + def __eq__(self, other): + """ + Return whether this hypergraph is equal to another one. + + @type other: hypergraph + @param other: Other hypergraph + + @rtype: boolean + @return: Whether this hypergraph and the other are equal. + """ + def links_eq(): + for edge in self.edges(): + for link in self.links(edge): + if (link not in other.links(edge)): return False + for edge in other.edges(): + for link in other.links(edge): + if (link not in self.links(edge)): return False + return True + + return common.__eq__(self, other) and links_eq() and labeling.__eq__(self, other) + + def __ne__(self, other): + """ + Return whether this hypergraph is not equal to another one. + + @type other: hypergraph + @param other: Other hypergraph + + @rtype: boolean + @return: Whether this hypergraph and the other are different. + """ + return not (self == other) \ No newline at end of file diff --git a/core/build/lib/pygraph/mixins/__init__.py b/core/build/lib/pygraph/mixins/__init__.py new file mode 100644 index 0000000..54c514e --- /dev/null +++ b/core/build/lib/pygraph/mixins/__init__.py @@ -0,0 +1,33 @@ +# Copyright (c) 2008-2009 Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Mixins. + +Base classes used to compose the the graph classes. + +The classes in this namespace should not be used directly. +""" + +__import__('pkg_resources').declare_namespace(__name__) diff --git a/core/build/lib/pygraph/mixins/basegraph.py b/core/build/lib/pygraph/mixins/basegraph.py new file mode 100644 index 0000000..694d2bc --- /dev/null +++ b/core/build/lib/pygraph/mixins/basegraph.py @@ -0,0 +1,32 @@ +# Copyright (c) 2008-2009 Pedro Matiello +# Salim Fadhley +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +class basegraph( object ): + """ + An abstract class intended as a common ancestor to all graph classes. This allows the user + to test isinstance(X, basegraph) to determine if the object is one of any of the python-graph + main classes. + """ + \ No newline at end of file diff --git a/core/build/lib/pygraph/mixins/common.py b/core/build/lib/pygraph/mixins/common.py new file mode 100644 index 0000000..c989967 --- /dev/null +++ b/core/build/lib/pygraph/mixins/common.py @@ -0,0 +1,215 @@ +# Copyright (c) 2008-2009 Pedro Matiello +# Salim Fadhley +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +class common( object ): + """ + Standard methods common to all graph classes. + + @sort: __eq__, __getitem__, __iter__, __len__, __repr__, __str__, add_graph, add_nodes, + add_spanning_tree, complete, inverse, order, reverse + """ + + def __str__(self): + """ + Return a string representing the graph when requested by str() (or print). + + @rtype: string + @return: String representing the graph. + """ + str_nodes = repr( self.nodes() ) + str_edges = repr( self.edges() ) + return "%s %s" % ( str_nodes, str_edges ) + + def __repr__(self): + """ + Return a string representing the graph when requested by repr() + + @rtype: string + @return: String representing the graph. + """ + return "<%s.%s %s>" % ( self.__class__.__module__, self.__class__.__name__, str(self) ) + + def __iter__(self): + """ + Return a iterator passing through all nodes in the graph. + + @rtype: iterator + @return: Iterator passing through all nodes in the graph. + """ + for n in self.nodes(): + yield n + + def __len__(self): + """ + Return the order of self when requested by len(). + + @rtype: number + @return: Size of the graph. + """ + return self.order() + + def __getitem__(self, node): + """ + Return a iterator passing through all neighbors of the given node. + + @rtype: iterator + @return: Iterator passing through all neighbors of the given node. + """ + for n in self.neighbors( node ): + yield n + + def order(self): + """ + Return the order of self, this is defined as the number of nodes in the graph. + + @rtype: number + @return: Size of the graph. + """ + return len(self.nodes()) + + def add_nodes(self, nodelist): + """ + Add given nodes to the graph. + + @attention: While nodes can be of any type, it's strongly recommended to use only + numbers and single-line strings as node identifiers if you intend to use write(). + Objects used to identify nodes absolutely must be hashable. If you need attach a mutable + or non-hashable node, consider using the labeling feature. + + @type nodelist: list + @param nodelist: List of nodes to be added to the graph. + """ + for each in nodelist: + self.add_node(each) + + def add_graph(self, other): + """ + Add other graph to this graph. + + @attention: Attributes and labels are not preserved. + + @type other: graph + @param other: Graph + """ + self.add_nodes( n for n in other.nodes() if not n in self.nodes() ) + + for each_node in other.nodes(): + for each_edge in other.neighbors(each_node): + if (not self.has_edge((each_node, each_edge))): + self.add_edge((each_node, each_edge)) + + + def add_spanning_tree(self, st): + """ + Add a spanning tree to the graph. + + @type st: dictionary + @param st: Spanning tree. + """ + self.add_nodes(list(st.keys())) + for each in st: + if (st[each] is not None): + self.add_edge((st[each], each)) + + + def complete(self): + """ + Make the graph a complete graph. + + @attention: This will modify the current graph. + """ + for each in self.nodes(): + for other in self.nodes(): + if (each != other and not self.has_edge((each, other))): + self.add_edge((each, other)) + + + def inverse(self): + """ + Return the inverse of the graph. + + @rtype: graph + @return: Complement graph for the graph. + """ + inv = self.__class__() + inv.add_nodes(self.nodes()) + inv.complete() + for each in self.edges(): + if (inv.has_edge(each)): + inv.del_edge(each) + return inv + + def reverse(self): + """ + Generate the reverse of a directed graph, returns an identical graph if not directed. + Attributes & weights are preserved. + + @rtype: digraph + @return: The directed graph that should be reversed. + """ + assert self.DIRECTED, "Undirected graph types such as %s cannot be reversed" % self.__class__.__name__ + + N = self.__class__() + + #- Add the nodes + N.add_nodes( n for n in self.nodes() ) + + #- Add the reversed edges + for (u, v) in self.edges(): + wt = self.edge_weight((u, v)) + label = self.edge_label((u, v)) + attributes = self.edge_attributes((u, v)) + N.add_edge((v, u), wt, label, attributes) + return N + + def __eq__(self, other): + """ + Return whether this graph is equal to another one. + + @type other: graph, digraph + @param other: Other graph or digraph + + @rtype: boolean + @return: Whether this graph and the other are equal. + """ + + def nodes_eq(): + for each in self: + if (not other.has_node(each)): return False + for each in other: + if (not self.has_node(each)): return False + return True + + def edges_eq(): + for edge in self.edges(): + if (not other.has_edge(edge)): return False + for edge in other.edges(): + if (not self.has_edge(edge)): return False + return True + + try: + return nodes_eq() and edges_eq() + except AttributeError: + return False diff --git a/core/build/lib/pygraph/mixins/labeling.py b/core/build/lib/pygraph/mixins/labeling.py new file mode 100644 index 0000000..3340ddd --- /dev/null +++ b/core/build/lib/pygraph/mixins/labeling.py @@ -0,0 +1,227 @@ +# Copyright (c) 2008-2009 Pedro Matiello +# Salim Fadhley +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +class labeling( object ): + """ + Generic labeling support for graphs + + @sort: __eq__, __init__, add_edge_attribute, add_edge_attributes, add_node_attribute, + del_edge_labeling, del_node_labeling, edge_attributes, edge_label, edge_weight, + get_edge_properties, node_attributes, set_edge_label, set_edge_properties, set_edge_weight + """ + WEIGHT_ATTRIBUTE_NAME = "weight" + DEFAULT_WEIGHT = 1 + + LABEL_ATTRIBUTE_NAME = "label" + DEFAULT_LABEL = "" + + def __init__(self): + # Metadata bout edges + self.edge_properties = {} # Mapping: Edge -> Dict mapping, lablel-> str, wt->num + self.edge_attr = {} # Key value pairs: (Edge -> Attributes) + + # Metadata bout nodes + self.node_attr = {} # Pairing: Node -> Attributes + + def del_node_labeling( self, node ): + if node in self.node_attr: + # Since attributes and properties are lazy, they might not exist. + del( self.node_attr[node] ) + + def del_edge_labeling( self, edge ): + + keys = [edge] + if not self.DIRECTED: + keys.append(edge[::-1]) + + for key in keys: + for mapping in [self.edge_properties, self.edge_attr ]: + try: + del ( mapping[key] ) + except KeyError: + pass + + def edge_weight(self, edge): + """ + Get the weight of an edge. + + @type edge: edge + @param edge: One edge. + + @rtype: number + @return: Edge weight. + """ + return self.get_edge_properties( edge ).setdefault( self.WEIGHT_ATTRIBUTE_NAME, self.DEFAULT_WEIGHT ) + + + def set_edge_weight(self, edge, wt): + """ + Set the weight of an edge. + + @type edge: edge + @param edge: One edge. + + @type wt: number + @param wt: Edge weight. + """ + self.set_edge_properties(edge, weight=wt ) + if not self.DIRECTED: + self.set_edge_properties((edge[1], edge[0]) , weight=wt ) + + + def edge_label(self, edge): + """ + Get the label of an edge. + + @type edge: edge + @param edge: One edge. + + @rtype: string + @return: Edge label + """ + return self.get_edge_properties( edge ).setdefault( self.LABEL_ATTRIBUTE_NAME, self.DEFAULT_LABEL ) + + def set_edge_label(self, edge, label): + """ + Set the label of an edge. + + @type edge: edge + @param edge: One edge. + + @type label: string + @param label: Edge label. + """ + self.set_edge_properties(edge, label=label ) + if not self.DIRECTED: + self.set_edge_properties((edge[1], edge[0]) , label=label ) + + def set_edge_properties(self, edge, **properties ): + self.edge_properties.setdefault( edge, {} ).update( properties ) + if (not self.DIRECTED and edge[0] != edge[1]): + self.edge_properties.setdefault((edge[1], edge[0]), {}).update( properties ) + + def get_edge_properties(self, edge): + return self.edge_properties.setdefault( edge, {} ) + + def add_edge_attribute(self, edge, attr): + """ + Add attribute to the given edge. + + @type edge: edge + @param edge: One edge. + + @type attr: tuple + @param attr: Node attribute specified as a tuple in the form (attribute, value). + """ + self.edge_attr[edge] = self.edge_attributes(edge) + [attr] + + if (not self.DIRECTED and edge[0] != edge[1]): + self.edge_attr[(edge[1],edge[0])] = self.edge_attributes((edge[1], edge[0])) + [attr] + + def add_edge_attributes(self, edge, attrs): + """ + Append a sequence of attributes to the given edge + + @type edge: edge + @param edge: One edge. + + @type attrs: tuple + @param attrs: Node attributes specified as a sequence of tuples in the form (attribute, value). + """ + for attr in attrs: + self.add_edge_attribute(edge, attr) + + + def add_node_attribute(self, node, attr): + """ + Add attribute to the given node. + + @type node: node + @param node: Node identifier + + @type attr: tuple + @param attr: Node attribute specified as a tuple in the form (attribute, value). + """ + self.node_attr[node] = self.node_attr[node] + [attr] + + + def node_attributes(self, node): + """ + Return the attributes of the given node. + + @type node: node + @param node: Node identifier + + @rtype: list + @return: List of attributes specified tuples in the form (attribute, value). + """ + return self.node_attr[node] + + + def edge_attributes(self, edge): + """ + Return the attributes of the given edge. + + @type edge: edge + @param edge: One edge. + + @rtype: list + @return: List of attributes specified tuples in the form (attribute, value). + """ + try: + return self.edge_attr[edge] + except KeyError: + return [] + + def __eq__(self, other): + """ + Return whether this graph is equal to another one. + + @type other: graph, digraph + @param other: Other graph or digraph + + @rtype: boolean + @return: Whether this graph and the other are equal. + """ + def attrs_eq(list1, list2): + for each in list1: + if (each not in list2): return False + for each in list2: + if (each not in list1): return False + return True + + def edges_eq(): + for edge in self.edges(): + if (self.edge_weight(edge) != other.edge_weight(edge)): return False + if (self.edge_label(edge) != other.edge_label(edge)): return False + if (not attrs_eq(self.edge_attributes(edge), other.edge_attributes(edge))): return False + return True + + def nodes_eq(): + for node in self: + if (not attrs_eq(self.node_attributes(node), other.node_attributes(node))): return False + return True + + return nodes_eq() and edges_eq() \ No newline at end of file diff --git a/core/build/lib/pygraph/readwrite/__init__.py b/core/build/lib/pygraph/readwrite/__init__.py new file mode 100644 index 0000000..01815a4 --- /dev/null +++ b/core/build/lib/pygraph/readwrite/__init__.py @@ -0,0 +1,31 @@ +# Copyright (c) 2008-2009 Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Readwrite algorithms. + +Algorithms for reading and writing graphs. +""" + +__import__('pkg_resources').declare_namespace(__name__) \ No newline at end of file diff --git a/core/build/lib/pygraph/readwrite/markup.py b/core/build/lib/pygraph/readwrite/markup.py new file mode 100644 index 0000000..c6e2455 --- /dev/null +++ b/core/build/lib/pygraph/readwrite/markup.py @@ -0,0 +1,195 @@ +# Copyright (c) 2007-2009 Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Functions for reading and writing graphs in a XML markup. + +@sort: read, read_hypergraph, write, write_hypergraph +""" + + +# Imports +from pygraph.classes.digraph import digraph +from pygraph.classes.exceptions import InvalidGraphType +from pygraph.classes.graph import graph +from pygraph.classes.hypergraph import hypergraph +from xml.dom.minidom import Document, parseString + + +def write(G): + """ + Return a string specifying the given graph as a XML document. + + @type G: graph + @param G: Graph. + + @rtype: string + @return: String specifying the graph as a XML document. + """ + + # Document root + grxml = Document() + if (type(G) == graph): + grxmlr = grxml.createElement('graph') + elif (type(G) == digraph ): + grxmlr = grxml.createElement('digraph') + elif (type(G) == hypergraph ): + return write_hypergraph(G) + else: + raise InvalidGraphType + grxml.appendChild(grxmlr) + + # Each node... + for each_node in G.nodes(): + node = grxml.createElement('node') + node.setAttribute('id', str(each_node)) + grxmlr.appendChild(node) + for each_attr in G.node_attributes(each_node): + attr = grxml.createElement('attribute') + attr.setAttribute('attr', each_attr[0]) + attr.setAttribute('value', each_attr[1]) + node.appendChild(attr) + + # Each edge... + for edge_from, edge_to in G.edges(): + edge = grxml.createElement('edge') + edge.setAttribute('from', str(edge_from)) + edge.setAttribute('to', str(edge_to)) + edge.setAttribute('wt', str(G.edge_weight((edge_from, edge_to)))) + edge.setAttribute('label', str(G.edge_label((edge_from, edge_to)))) + grxmlr.appendChild(edge) + for attr_name, attr_value in G.edge_attributes((edge_from, edge_to)): + attr = grxml.createElement('attribute') + attr.setAttribute('attr', attr_name) + attr.setAttribute('value', attr_value) + edge.appendChild(attr) + + return grxml.toprettyxml() + + +def read(string): + """ + Read a graph from a XML document and return it. Nodes and edges specified in the input will + be added to the current graph. + + @type string: string + @param string: Input string in XML format specifying a graph. + + @rtype: graph + @return: Graph + """ + dom = parseString(string) + if dom.getElementsByTagName("graph"): + G = graph() + elif dom.getElementsByTagName("digraph"): + G = digraph() + elif dom.getElementsByTagName("hypergraph"): + return read_hypergraph(string) + else: + raise InvalidGraphType + + # Read nodes... + for each_node in dom.getElementsByTagName("node"): + G.add_node(each_node.getAttribute('id')) + for each_attr in each_node.getElementsByTagName("attribute"): + G.add_node_attribute(each_node.getAttribute('id'), + (each_attr.getAttribute('attr'), + each_attr.getAttribute('value'))) + + # Read edges... + for each_edge in dom.getElementsByTagName("edge"): + if (not G.has_edge((each_edge.getAttribute('from'), each_edge.getAttribute('to')))): + G.add_edge((each_edge.getAttribute('from'), each_edge.getAttribute('to')), \ + wt = float(each_edge.getAttribute('wt')), label = each_edge.getAttribute('label')) + for each_attr in each_edge.getElementsByTagName("attribute"): + attr_tuple = (each_attr.getAttribute('attr'), each_attr.getAttribute('value')) + if (attr_tuple not in G.edge_attributes((each_edge.getAttribute('from'), \ + each_edge.getAttribute('to')))): + G.add_edge_attribute((each_edge.getAttribute('from'), \ + each_edge.getAttribute('to')), attr_tuple) + + return G + + +def write_hypergraph(hgr): + """ + Return a string specifying the given hypergraph as a XML document. + + @type hgr: hypergraph + @param hgr: Hypergraph. + + @rtype: string + @return: String specifying the graph as a XML document. + """ + + # Document root + grxml = Document() + grxmlr = grxml.createElement('hypergraph') + grxml.appendChild(grxmlr) + + # Each node... + nodes = hgr.nodes() + hyperedges = hgr.hyperedges() + for each_node in (nodes + hyperedges): + if (each_node in nodes): + node = grxml.createElement('node') + else: + node = grxml.createElement('hyperedge') + node.setAttribute('id', str(each_node)) + grxmlr.appendChild(node) + + # and its outgoing edge + if each_node in nodes: + for each_edge in hgr.links(each_node): + edge = grxml.createElement('link') + edge.setAttribute('to', str(each_edge)) + node.appendChild(edge) + + return grxml.toprettyxml() + + +def read_hypergraph(string): + """ + Read a graph from a XML document. Nodes and hyperedges specified in the input will be added + to the current graph. + + @type string: string + @param string: Input string in XML format specifying a graph. + + @rtype: hypergraph + @return: Hypergraph + """ + + hgr = hypergraph() + + dom = parseString(string) + for each_node in dom.getElementsByTagName("node"): + hgr.add_node(each_node.getAttribute('id')) + for each_node in dom.getElementsByTagName("hyperedge"): + hgr.add_hyperedge(each_node.getAttribute('id')) + dom = parseString(string) + for each_node in dom.getElementsByTagName("node"): + for each_edge in each_node.getElementsByTagName("link"): + hgr.link(str(each_node.getAttribute('id')), str(each_edge.getAttribute('to'))) + return hgr diff --git a/core/dist/python_graph_core-1.8.2-py3.5.egg b/core/dist/python_graph_core-1.8.2-py3.5.egg new file mode 100644 index 0000000..f9fe0f2 Binary files /dev/null and b/core/dist/python_graph_core-1.8.2-py3.5.egg differ diff --git a/core/distribute_setup.py b/core/distribute_setup.py index f6af757..1a26663 100644 --- a/core/distribute_setup.py +++ b/core/distribute_setup.py @@ -182,7 +182,7 @@ def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, try: from urllib.request import urlopen except ImportError: - from urllib2 import urlopen + from urllib.request import urlopen tgz_name = "distribute-%s.tar.gz" % version url = download_base + tgz_name saveto = os.path.join(to_dir, tgz_name) diff --git a/core/distribute_setup.py.bak b/core/distribute_setup.py.bak new file mode 100644 index 0000000..f6af757 --- /dev/null +++ b/core/distribute_setup.py.bak @@ -0,0 +1,487 @@ +#!python +"""Bootstrap distribute installation + +If you want to use setuptools in your package's setup.py, just include this +file in the same directory with it, and add this to the top of your setup.py:: + + from distribute_setup import use_setuptools + use_setuptools() + +If you want to require a specific version of setuptools, set a download +mirror, or use an alternate download directory, you can do so by supplying +the appropriate options to ``use_setuptools()``. + +This file can also be run as a script to install or upgrade setuptools. +""" +import os +import sys +import time +import fnmatch +import tempfile +import tarfile +from distutils import log + +try: + from site import USER_SITE +except ImportError: + USER_SITE = None + +try: + import subprocess + + def _python_cmd(*args): + args = (sys.executable,) + args + return subprocess.call(args) == 0 + +except ImportError: + # will be used for python 2.3 + def _python_cmd(*args): + args = (sys.executable,) + args + # quoting arguments if windows + if sys.platform == 'win32': + def quote(arg): + if ' ' in arg: + return '"%s"' % arg + return arg + args = [quote(arg) for arg in args] + return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 + +DEFAULT_VERSION = "0.6.24" +DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" +SETUPTOOLS_FAKED_VERSION = "0.6c11" + +SETUPTOOLS_PKG_INFO = """\ +Metadata-Version: 1.0 +Name: setuptools +Version: %s +Summary: xxxx +Home-page: xxx +Author: xxx +Author-email: xxx +License: xxx +Description: xxx +""" % SETUPTOOLS_FAKED_VERSION + + +def _install(tarball): + # extracting the tarball + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + tar = tarfile.open(tarball) + _extractall(tar) + tar.close() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) + + # installing + log.warn('Installing Distribute') + if not _python_cmd('setup.py', 'install'): + log.warn('Something went wrong during the installation.') + log.warn('See the error message above.') + finally: + os.chdir(old_wd) + + +def _build_egg(egg, tarball, to_dir): + # extracting the tarball + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + tar = tarfile.open(tarball) + _extractall(tar) + tar.close() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) + + # building an egg + log.warn('Building a Distribute egg in %s', to_dir) + _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) + + finally: + os.chdir(old_wd) + # returning the result + log.warn(egg) + if not os.path.exists(egg): + raise IOError('Could not build the egg.') + + +def _do_download(version, download_base, to_dir, download_delay): + egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' + % (version, sys.version_info[0], sys.version_info[1])) + if not os.path.exists(egg): + tarball = download_setuptools(version, download_base, + to_dir, download_delay) + _build_egg(egg, tarball, to_dir) + sys.path.insert(0, egg) + import setuptools + setuptools.bootstrap_install_from = egg + + +def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, download_delay=15, no_fake=True): + # making sure we use the absolute path + to_dir = os.path.abspath(to_dir) + was_imported = 'pkg_resources' in sys.modules or \ + 'setuptools' in sys.modules + try: + try: + import pkg_resources + if not hasattr(pkg_resources, '_distribute'): + if not no_fake: + _fake_setuptools() + raise ImportError + except ImportError: + return _do_download(version, download_base, to_dir, download_delay) + try: + pkg_resources.require("distribute>="+version) + return + except pkg_resources.VersionConflict: + e = sys.exc_info()[1] + if was_imported: + sys.stderr.write( + "The required version of distribute (>=%s) is not available,\n" + "and can't be installed while this script is running. Please\n" + "install a more recent version first, using\n" + "'easy_install -U distribute'." + "\n\n(Currently using %r)\n" % (version, e.args[0])) + sys.exit(2) + else: + del pkg_resources, sys.modules['pkg_resources'] # reload ok + return _do_download(version, download_base, to_dir, + download_delay) + except pkg_resources.DistributionNotFound: + return _do_download(version, download_base, to_dir, + download_delay) + finally: + if not no_fake: + _create_fake_setuptools_pkg_info(to_dir) + +def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, delay=15): + """Download distribute from a specified location and return its filename + + `version` should be a valid distribute version number that is available + as an egg for download under the `download_base` URL (which should end + with a '/'). `to_dir` is the directory where the egg will be downloaded. + `delay` is the number of seconds to pause before an actual download + attempt. + """ + # making sure we use the absolute path + to_dir = os.path.abspath(to_dir) + try: + from urllib.request import urlopen + except ImportError: + from urllib2 import urlopen + tgz_name = "distribute-%s.tar.gz" % version + url = download_base + tgz_name + saveto = os.path.join(to_dir, tgz_name) + src = dst = None + if not os.path.exists(saveto): # Avoid repeated downloads + try: + log.warn("Downloading %s", url) + src = urlopen(url) + # Read/write all in one block, so we don't create a corrupt file + # if the download is interrupted. + data = src.read() + dst = open(saveto, "wb") + dst.write(data) + finally: + if src: + src.close() + if dst: + dst.close() + return os.path.realpath(saveto) + +def _no_sandbox(function): + def __no_sandbox(*args, **kw): + try: + from setuptools.sandbox import DirectorySandbox + if not hasattr(DirectorySandbox, '_old'): + def violation(*args): + pass + DirectorySandbox._old = DirectorySandbox._violation + DirectorySandbox._violation = violation + patched = True + else: + patched = False + except ImportError: + patched = False + + try: + return function(*args, **kw) + finally: + if patched: + DirectorySandbox._violation = DirectorySandbox._old + del DirectorySandbox._old + + return __no_sandbox + +def _patch_file(path, content): + """Will backup the file then patch it""" + existing_content = open(path).read() + if existing_content == content: + # already patched + log.warn('Already patched.') + return False + log.warn('Patching...') + _rename_path(path) + f = open(path, 'w') + try: + f.write(content) + finally: + f.close() + return True + +_patch_file = _no_sandbox(_patch_file) + +def _same_content(path, content): + return open(path).read() == content + +def _rename_path(path): + new_name = path + '.OLD.%s' % time.time() + log.warn('Renaming %s into %s', path, new_name) + os.rename(path, new_name) + return new_name + +def _remove_flat_installation(placeholder): + if not os.path.isdir(placeholder): + log.warn('Unkown installation at %s', placeholder) + return False + found = False + for file in os.listdir(placeholder): + if fnmatch.fnmatch(file, 'setuptools*.egg-info'): + found = True + break + if not found: + log.warn('Could not locate setuptools*.egg-info') + return + + log.warn('Removing elements out of the way...') + pkg_info = os.path.join(placeholder, file) + if os.path.isdir(pkg_info): + patched = _patch_egg_dir(pkg_info) + else: + patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO) + + if not patched: + log.warn('%s already patched.', pkg_info) + return False + # now let's move the files out of the way + for element in ('setuptools', 'pkg_resources.py', 'site.py'): + element = os.path.join(placeholder, element) + if os.path.exists(element): + _rename_path(element) + else: + log.warn('Could not find the %s element of the ' + 'Setuptools distribution', element) + return True + +_remove_flat_installation = _no_sandbox(_remove_flat_installation) + +def _after_install(dist): + log.warn('After install bootstrap.') + placeholder = dist.get_command_obj('install').install_purelib + _create_fake_setuptools_pkg_info(placeholder) + +def _create_fake_setuptools_pkg_info(placeholder): + if not placeholder or not os.path.exists(placeholder): + log.warn('Could not find the install location') + return + pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) + setuptools_file = 'setuptools-%s-py%s.egg-info' % \ + (SETUPTOOLS_FAKED_VERSION, pyver) + pkg_info = os.path.join(placeholder, setuptools_file) + if os.path.exists(pkg_info): + log.warn('%s already exists', pkg_info) + return + + log.warn('Creating %s', pkg_info) + f = open(pkg_info, 'w') + try: + f.write(SETUPTOOLS_PKG_INFO) + finally: + f.close() + + pth_file = os.path.join(placeholder, 'setuptools.pth') + log.warn('Creating %s', pth_file) + f = open(pth_file, 'w') + try: + f.write(os.path.join(os.curdir, setuptools_file)) + finally: + f.close() + +_create_fake_setuptools_pkg_info = _no_sandbox(_create_fake_setuptools_pkg_info) + +def _patch_egg_dir(path): + # let's check if it's already patched + pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') + if os.path.exists(pkg_info): + if _same_content(pkg_info, SETUPTOOLS_PKG_INFO): + log.warn('%s already patched.', pkg_info) + return False + _rename_path(path) + os.mkdir(path) + os.mkdir(os.path.join(path, 'EGG-INFO')) + pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') + f = open(pkg_info, 'w') + try: + f.write(SETUPTOOLS_PKG_INFO) + finally: + f.close() + return True + +_patch_egg_dir = _no_sandbox(_patch_egg_dir) + +def _before_install(): + log.warn('Before install bootstrap.') + _fake_setuptools() + + +def _under_prefix(location): + if 'install' not in sys.argv: + return True + args = sys.argv[sys.argv.index('install')+1:] + for index, arg in enumerate(args): + for option in ('--root', '--prefix'): + if arg.startswith('%s=' % option): + top_dir = arg.split('root=')[-1] + return location.startswith(top_dir) + elif arg == option: + if len(args) > index: + top_dir = args[index+1] + return location.startswith(top_dir) + if arg == '--user' and USER_SITE is not None: + return location.startswith(USER_SITE) + return True + + +def _fake_setuptools(): + log.warn('Scanning installed packages') + try: + import pkg_resources + except ImportError: + # we're cool + log.warn('Setuptools or Distribute does not seem to be installed.') + return + ws = pkg_resources.working_set + try: + setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools', + replacement=False)) + except TypeError: + # old distribute API + setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools')) + + if setuptools_dist is None: + log.warn('No setuptools distribution found') + return + # detecting if it was already faked + setuptools_location = setuptools_dist.location + log.warn('Setuptools installation detected at %s', setuptools_location) + + # if --root or --preix was provided, and if + # setuptools is not located in them, we don't patch it + if not _under_prefix(setuptools_location): + log.warn('Not patching, --root or --prefix is installing Distribute' + ' in another location') + return + + # let's see if its an egg + if not setuptools_location.endswith('.egg'): + log.warn('Non-egg installation') + res = _remove_flat_installation(setuptools_location) + if not res: + return + else: + log.warn('Egg installation') + pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') + if (os.path.exists(pkg_info) and + _same_content(pkg_info, SETUPTOOLS_PKG_INFO)): + log.warn('Already patched.') + return + log.warn('Patching...') + # let's create a fake egg replacing setuptools one + res = _patch_egg_dir(setuptools_location) + if not res: + return + log.warn('Patched done.') + _relaunch() + + +def _relaunch(): + log.warn('Relaunching...') + # we have to relaunch the process + # pip marker to avoid a relaunch bug + if sys.argv[:3] == ['-c', 'install', '--single-version-externally-managed']: + sys.argv[0] = 'setup.py' + args = [sys.executable] + sys.argv + sys.exit(subprocess.call(args)) + + +def _extractall(self, path=".", members=None): + """Extract all members from the archive to the current working + directory and set owner, modification time and permissions on + directories afterwards. `path' specifies a different directory + to extract to. `members' is optional and must be a subset of the + list returned by getmembers(). + """ + import copy + import operator + from tarfile import ExtractError + directories = [] + + if members is None: + members = self + + for tarinfo in members: + if tarinfo.isdir(): + # Extract directories with a safe mode. + directories.append(tarinfo) + tarinfo = copy.copy(tarinfo) + tarinfo.mode = 448 # decimal for oct 0700 + self.extract(tarinfo, path) + + # Reverse sort directories. + if sys.version_info < (2, 4): + def sorter(dir1, dir2): + return cmp(dir1.name, dir2.name) + directories.sort(sorter) + directories.reverse() + else: + directories.sort(key=operator.attrgetter('name'), reverse=True) + + # Set correct owner, mtime and filemode on directories. + for tarinfo in directories: + dirpath = os.path.join(path, tarinfo.name) + try: + self.chown(tarinfo, dirpath) + self.utime(tarinfo, dirpath) + self.chmod(tarinfo, dirpath) + except ExtractError: + e = sys.exc_info()[1] + if self.errorlevel > 1: + raise + else: + self._dbg(1, "tarfile: %s" % e) + + +def main(argv, version=DEFAULT_VERSION): + """Install or upgrade setuptools and EasyInstall""" + tarball = download_setuptools() + _install(tarball) + + +if __name__ == '__main__': + import os + os.environ.set("HTTP_HOST",r"http//ldnproxy.ldn.emea.cib:9090") + main(sys.argv[1:]) diff --git a/core/pygraph/algorithms/generators.py b/core/pygraph/algorithms/generators.py index 75d6baa..04f92e9 100644 --- a/core/pygraph/algorithms/generators.py +++ b/core/pygraph/algorithms/generators.py @@ -63,7 +63,7 @@ def generate(num_nodes, num_edges, directed=False, weight_range=(1, 1)): random_graph = graph() # Nodes - nodes = range(num_nodes) + nodes = list(range(num_nodes)) random_graph.add_nodes(nodes) # Build a list of all possible edges diff --git a/core/pygraph/algorithms/generators.py.bak b/core/pygraph/algorithms/generators.py.bak new file mode 100644 index 0000000..75d6baa --- /dev/null +++ b/core/pygraph/algorithms/generators.py.bak @@ -0,0 +1,132 @@ +# Copyright (c) 2008-2009 Pedro Matiello +# Zsolt Haraszti +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Random graph generators. + +@sort: generate, generate_hypergraph +""" + + +# Imports +from pygraph.classes.graph import graph +from pygraph.classes.digraph import digraph +from pygraph.classes.hypergraph import hypergraph +from random import randint, choice, shuffle #@UnusedImport +from time import time + +# Generator + +def generate(num_nodes, num_edges, directed=False, weight_range=(1, 1)): + """ + Create a random graph. + + @type num_nodes: number + @param num_nodes: Number of nodes. + + @type num_edges: number + @param num_edges: Number of edges. + + @type directed: bool + @param directed: Whether the generated graph should be directed or not. + + @type weight_range: tuple + @param weight_range: tuple of two integers as lower and upper limits on randomly generated + weights (uniform distribution). + """ + # Graph creation + if directed: + random_graph = digraph() + else: + random_graph = graph() + + # Nodes + nodes = range(num_nodes) + random_graph.add_nodes(nodes) + + # Build a list of all possible edges + edges = [] + edges_append = edges.append + for x in nodes: + for y in nodes: + if ((directed and x != y) or (x > y)): + edges_append((x, y)) + + # Randomize the list + shuffle(edges) + + # Add edges to the graph + min_wt = min(weight_range) + max_wt = max(weight_range) + for i in range(num_edges): + each = edges[i] + random_graph.add_edge((each[0], each[1]), wt = randint(min_wt, max_wt)) + + return random_graph + + +def generate_hypergraph(num_nodes, num_edges, r = 0): + """ + Create a random hyper graph. + + @type num_nodes: number + @param num_nodes: Number of nodes. + + @type num_edges: number + @param num_edges: Number of edges. + + @type r: number + @param r: Uniform edges of size r. + """ + # Graph creation + random_graph = hypergraph() + + # Nodes + nodes = list(map(str, list(range(num_nodes)))) + random_graph.add_nodes(nodes) + + # Base edges + edges = list(map(str, list(range(num_nodes, num_nodes+num_edges)))) + random_graph.add_hyperedges(edges) + + # Connect the edges + if 0 == r: + # Add each edge with 50/50 probability + for e in edges: + for n in nodes: + if choice([True, False]): + random_graph.link(n, e) + + else: + # Add only uniform edges + for e in edges: + # First shuffle the nodes + shuffle(nodes) + + # Then take the first r nodes + for i in range(r): + random_graph.link(nodes[i], e) + + return random_graph diff --git a/core/pygraph/algorithms/minmax.py b/core/pygraph/algorithms/minmax.py index eada083..95d4baa 100644 --- a/core/pygraph/algorithms/minmax.py +++ b/core/pygraph/algorithms/minmax.py @@ -429,7 +429,7 @@ def cut_value(graph, flow, cut): #max flow/min cut value calculation S = [] T = [] - for node in cut.keys(): + for node in list(cut.keys()): if cut[node] == 0: S.append(node) elif cut[node] == 1: @@ -481,7 +481,7 @@ def cut_tree(igraph, caps = None): N = N + 1 #predecessor function - p = {}.fromkeys(range(N),0) + p = {}.fromkeys(list(range(N)),0) p[0] = None for s in range(1,N): diff --git a/core/pygraph/algorithms/minmax.py.bak b/core/pygraph/algorithms/minmax.py.bak new file mode 100644 index 0000000..eada083 --- /dev/null +++ b/core/pygraph/algorithms/minmax.py.bak @@ -0,0 +1,516 @@ +# Copyright (c) 2007-2009 Pedro Matiello +# Peter Sagerson +# Johannes Reinhardt +# Rhys Ulerich +# Roy Smith +# Salim Fadhley +# Tomaz Kovacic +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Minimization and maximization algorithms. + +@sort: heuristic_search, minimal_spanning_tree, shortest_path, +shortest_path_bellman_ford +""" + +from pygraph.algorithms.utils import heappush, heappop +from pygraph.classes.exceptions import NodeUnreachable +from pygraph.classes.exceptions import NegativeWeightCycleError +from pygraph.classes.digraph import digraph +import bisect + +# Minimal spanning tree + +def minimal_spanning_tree(graph, root=None): + """ + Minimal spanning tree. + + @attention: Minimal spanning tree is meaningful only for weighted graphs. + + @type graph: graph + @param graph: Graph. + + @type root: node + @param root: Optional root node (will explore only root's connected component) + + @rtype: dictionary + @return: Generated spanning tree. + """ + visited = [] # List for marking visited and non-visited nodes + spanning_tree = {} # MInimal Spanning tree + + # Initialization + if (root is not None): + visited.append(root) + nroot = root + spanning_tree[root] = None + else: + nroot = 1 + + # Algorithm loop + while (nroot is not None): + ledge = _lightest_edge(graph, visited) + if (ledge == None): + if (root is not None): + break + nroot = _first_unvisited(graph, visited) + if (nroot is not None): + spanning_tree[nroot] = None + visited.append(nroot) + else: + spanning_tree[ledge[1]] = ledge[0] + visited.append(ledge[1]) + + return spanning_tree + + +def _first_unvisited(graph, visited): + """ + Return first unvisited node. + + @type graph: graph + @param graph: Graph. + + @type visited: list + @param visited: List of nodes. + + @rtype: node + @return: First unvisited node. + """ + for each in graph: + if (each not in visited): + return each + return None + + +def _lightest_edge(graph, visited): + """ + Return the lightest edge in graph going from a visited node to an unvisited one. + + @type graph: graph + @param graph: Graph. + + @type visited: list + @param visited: List of nodes. + + @rtype: tuple + @return: Lightest edge in graph going from a visited node to an unvisited one. + """ + lightest_edge = None + weight = None + for each in visited: + for other in graph[each]: + if (other not in visited): + w = graph.edge_weight((each, other)) + if (weight is None or w < weight or weight < 0): + lightest_edge = (each, other) + weight = w + return lightest_edge + + +# Shortest Path + +def shortest_path(graph, source): + """ + Return the shortest path distance between source and all other nodes using Dijkstra's + algorithm. + + @attention: All weights must be nonnegative. + + @see: shortest_path_bellman_ford + + @type graph: graph, digraph + @param graph: Graph. + + @type source: node + @param source: Node from which to start the search. + + @rtype: tuple + @return: A tuple containing two dictionaries, each keyed by target nodes. + 1. Shortest path spanning tree + 2. Shortest distance from given source to each target node + Inaccessible target nodes do not appear in either dictionary. + """ + # Initialization + dist = {source: 0} + previous = {source: None} + + # This is a sorted queue of (dist, node) 2-tuples. The first item in the + # queue is always either a finalized node that we can ignore or the node + # with the smallest estimated distance from the source. Note that we will + # not remove nodes from this list as they are finalized; we just ignore them + # when they come up. + q = [(0, source)] + + # The set of nodes for which we have final distances. + finished = set() + + # Algorithm loop + while len(q) > 0: + du, u = q.pop(0) + + # Process reachable, remaining nodes from u + if u not in finished: + finished.add(u) + for v in graph[u]: + if v not in finished: + alt = du + graph.edge_weight((u, v)) + if (v not in dist) or (alt < dist[v]): + dist[v] = alt + previous[v] = u + bisect.insort(q, (alt, v)) + + return previous, dist + + + +def shortest_path_bellman_ford(graph, source): + """ + Return the shortest path distance between the source node and all other + nodes in the graph using Bellman-Ford's algorithm. + + This algorithm is useful when you have a weighted (and obviously + a directed) graph with negative weights. + + @attention: The algorithm can detect negative weight cycles and will raise + an exception. It's meaningful only for directed weighted graphs. + + @see: shortest_path + + @type graph: digraph + @param graph: Digraph + + @type source: node + @param source: Source node of the graph + + @raise NegativeWeightCycleError: raises if it finds a negative weight cycle. + If this condition is met d(v) > d(u) + W(u, v) then raise the error. + + @rtype: tuple + @return: A tuple containing two dictionaries, each keyed by target nodes. + (same as shortest_path function that implements Dijkstra's algorithm) + 1. Shortest path spanning tree + 2. Shortest distance from given source to each target node + """ + # initialize the required data structures + distance = {source : 0} + predecessor = {source : None} + + # iterate and relax edges + for i in range(1,graph.order()-1): + for src,dst in graph.edges(): + if (src in distance) and (dst not in distance): + distance[dst] = distance[src] + graph.edge_weight((src,dst)) + predecessor[dst] = src + elif (src in distance) and (dst in distance) and \ + distance[src] + graph.edge_weight((src,dst)) < distance[dst]: + distance[dst] = distance[src] + graph.edge_weight((src,dst)) + predecessor[dst] = src + + # detect negative weight cycles + for src,dst in graph.edges(): + if src in distance and \ + dst in distance and \ + distance[src] + graph.edge_weight((src,dst)) < distance[dst]: + raise NegativeWeightCycleError("Detected a negative weight cycle on edge (%s, %s)" % (src,dst)) + + return predecessor, distance + +#Heuristics search + +def heuristic_search(graph, start, goal, heuristic): + """ + A* search algorithm. + + A set of heuristics is available under C{graph.algorithms.heuristics}. User-created heuristics + are allowed too. + + @type graph: graph, digraph + @param graph: Graph + + @type start: node + @param start: Start node + + @type goal: node + @param goal: Goal node + + @type heuristic: function + @param heuristic: Heuristic function + + @rtype: list + @return: Optimized path from start to goal node + """ + + # The queue stores priority, node, cost to reach, and parent. + queue = [ (0, start, 0, None) ] + + # This dictionary maps queued nodes to distance of discovered paths + # and the computed heuristics to goal. We avoid to compute the heuristics + # more than once and to insert too many times the node in the queue. + g = {} + + # This maps explored nodes to parent closest to the start + explored = {} + + while queue: + _, current, dist, parent = heappop(queue) + + if current == goal: + path = [current] + [ n for n in _reconstruct_path( parent, explored ) ] + path.reverse() + return path + + if current in explored: + continue + + explored[current] = parent + + for neighbor in graph[current]: + if neighbor in explored: + continue + + ncost = dist + graph.edge_weight((current, neighbor)) + + if neighbor in g: + qcost, h = g[neighbor] + if qcost <= ncost: + continue + # if ncost < qcost, a longer path to neighbor remains + # g. Removing it would need to filter the whole + # queue, it's better just to leave it there and ignore + # it when we visit the node a second time. + else: + h = heuristic(neighbor, goal) + + g[neighbor] = ncost, h + heappush(queue, (ncost + h, neighbor, ncost, current)) + + raise NodeUnreachable( start, goal ) + +def _reconstruct_path(node, parents): + while node is not None: + yield node + node = parents[node] + +#maximum flow/minimum cut + +def maximum_flow(graph, source, sink, caps = None): + """ + Find a maximum flow and minimum cut of a directed graph by the Edmonds-Karp algorithm. + + @type graph: digraph + @param graph: Graph + + @type source: node + @param source: Source of the flow + + @type sink: node + @param sink: Sink of the flow + + @type caps: dictionary + @param caps: Dictionary specifying a maximum capacity for each edge. If not given, the weight of the edge + will be used as its capacity. Otherwise, for each edge (a,b), caps[(a,b)] should be given. + + @rtype: tuple + @return: A tuple containing two dictionaries + 1. contains the flow through each edge for a maximal flow through the graph + 2. contains to which component of a minimum cut each node belongs + """ + + #handle optional argument, if weights are available, use them, if not, assume one + if caps == None: + caps = {} + for edge in graph.edges(): + caps[edge] = graph.edge_weight((edge[0],edge[1])) + + #data structures to maintain + f = {}.fromkeys(graph.edges(),0) + label = {}.fromkeys(graph.nodes(),[]) + label[source] = ['-',float('Inf')] + u = {}.fromkeys(graph.nodes(),False) + d = {}.fromkeys(graph.nodes(),float('Inf')) + #queue for labelling + q = [source] + + finished = False + while not finished: + #choose first labelled vertex with u == false + for i in range(len(q)): + if not u[q[i]]: + v = q.pop(i) + break + + #find augmenting path + for w in graph.neighbors(v): + if label[w] == [] and f[(v,w)] < caps[(v,w)]: + d[w] = min(caps[(v,w)] - f[(v,w)],d[v]) + label[w] = [v,'+',d[w]] + q.append(w) + for w in graph.incidents(v): + if label[w] == [] and f[(w,v)] > 0: + d[w] = min(f[(w,v)],d[v]) + label[w] = [v,'-',d[w]] + q.append(w) + + u[v] = True + + #extend flow by augmenting path + if label[sink] != []: + delta = label[sink][-1] + w = sink + while w != source: + v = label[w][0] + if label[w][1] == '-': + f[(w,v)] = f[(w,v)] - delta + else: + f[(v,w)] = f[(v,w)] + delta + w = v + #reset labels + label = {}.fromkeys(graph.nodes(),[]) + label[source] = ['-',float('Inf')] + q = [source] + u = {}.fromkeys(graph.nodes(),False) + d = {}.fromkeys(graph.nodes(),float('Inf')) + + #check whether finished + finished = True + for node in graph.nodes(): + if label[node] != [] and u[node] == False: + finished = False + + #find the two components of the cut + cut = {} + for node in graph.nodes(): + if label[node] == []: + cut[node] = 1 + else: + cut[node] = 0 + return (f,cut) + +def cut_value(graph, flow, cut): + """ + Calculate the value of a cut. + + @type graph: digraph + @param graph: Graph + + @type flow: dictionary + @param flow: Dictionary containing a flow for each edge. + + @type cut: dictionary + @param cut: Dictionary mapping each node to a subset index. The function only considers the flow between + nodes with 0 and 1. + + @rtype: float + @return: The value of the flow between the subsets 0 and 1 + """ + #max flow/min cut value calculation + S = [] + T = [] + for node in cut.keys(): + if cut[node] == 0: + S.append(node) + elif cut[node] == 1: + T.append(node) + value = 0 + for node in S: + for neigh in graph.neighbors(node): + if neigh in T: + value = value + flow[(node,neigh)] + for inc in graph.incidents(node): + if inc in T: + value = value - flow[(inc,node)] + return value + +def cut_tree(igraph, caps = None): + """ + Construct a Gomory-Hu cut tree by applying the algorithm of Gusfield. + + @type igraph: graph + @param igraph: Graph + + @type caps: dictionary + @param caps: Dictionary specifying a maximum capacity for each edge. If not given, the weight of the edge + will be used as its capacity. Otherwise, for each edge (a,b), caps[(a,b)] should be given. + + @rtype: dictionary + @return: Gomory-Hu cut tree as a dictionary, where each edge is associated with its weight + """ + + #maximum flow needs a digraph, we get a graph + #I think this conversion relies on implementation details outside the api and may break in the future + graph = digraph() + graph.add_graph(igraph) + + #handle optional argument + if not caps: + caps = {} + for edge in graph.edges(): + caps[edge] = igraph.edge_weight(edge) + + #temporary flow variable + f = {} + + #we use a numbering of the nodes for easier handling + n = {} + N = 0 + for node in graph.nodes(): + n[N] = node + N = N + 1 + + #predecessor function + p = {}.fromkeys(range(N),0) + p[0] = None + + for s in range(1,N): + t = p[s] + S = [] + #max flow calculation + (flow,cut) = maximum_flow(graph,n[s],n[t],caps) + for i in range(N): + if cut[n[i]] == 0: + S.append(i) + + value = cut_value(graph,flow,cut) + + f[s] = value + + for i in range(N): + if i == s: + continue + if i in S and p[i] == t: + p[i] = s + if p[t] in S: + p[s] = p[t] + p[t] = s + f[s] = f[t] + f[t] = value + + #cut tree is a dictionary, where each edge is associated with its weight + b = {} + for i in range(1,N): + b[(n[i],n[p[i]])] = f[i] + return b + diff --git a/core/pygraph/algorithms/searching.py b/core/pygraph/algorithms/searching.py index 64d1f1e..8cc1dfd 100644 --- a/core/pygraph/algorithms/searching.py +++ b/core/pygraph/algorithms/searching.py @@ -64,7 +64,7 @@ def dfs(node): pre.append(node) # Explore recursively the connected component for each in graph[node]: - if (each not in visited and filter(each, node)): + if (each not in visited and list(filter(each, node))): spanning_tree[each] = node dfs(each) post.append(node) @@ -77,7 +77,7 @@ def dfs(node): # DFS from one node only if (root is not None): - if filter(root, None): + if list(filter(root, None)): spanning_tree[root] = None dfs(root) setrecursionlimit(recursionlimit) @@ -86,7 +86,7 @@ def dfs(node): # Algorithm loop for each in graph: # Select a non-visited node - if (each not in visited and filter(each, None)): + if (each not in visited and list(filter(each, None))): spanning_tree[each] = None # Explore node's connected component dfs(each) @@ -122,7 +122,7 @@ def bfs(): node = queue.pop(0) for other in graph[node]: - if (other not in spanning_tree and filter(other, node)): + if (other not in spanning_tree and list(filter(other, node))): queue.append(other) ordering.append(other) spanning_tree[other] = node @@ -134,7 +134,7 @@ def bfs(): # BFS from one node only if (root is not None): - if filter(root, None): + if list(filter(root, None)): queue.append(root) ordering.append(root) spanning_tree[root] = None @@ -144,7 +144,7 @@ def bfs(): # Algorithm for each in graph: if (each not in spanning_tree): - if filter(each, None): + if list(filter(each, None)): queue.append(each) ordering.append(each) spanning_tree[each] = None diff --git a/core/pygraph/algorithms/searching.py.bak b/core/pygraph/algorithms/searching.py.bak new file mode 100644 index 0000000..64d1f1e --- /dev/null +++ b/core/pygraph/algorithms/searching.py.bak @@ -0,0 +1,153 @@ +# Copyright (c) 2007-2009 Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Search algorithms. + +@sort: breadth_first_search, depth_first_search +""" + + +# Imports +from pygraph.algorithms.filters.null import null +from sys import getrecursionlimit, setrecursionlimit + + +# Depth-first search + +def depth_first_search(graph, root=None, filter=null()): + """ + Depth-first search. + + @type graph: graph, digraph + @param graph: Graph. + + @type root: node + @param root: Optional root node (will explore only root's connected component) + + @rtype: tuple + @return: A tupple containing a dictionary and two lists: + 1. Generated spanning tree + 2. Graph's preordering + 3. Graph's postordering + """ + + recursionlimit = getrecursionlimit() + setrecursionlimit(max(len(graph.nodes())*2,recursionlimit)) + + def dfs(node): + """ + Depth-first search subfunction. + """ + visited[node] = 1 + pre.append(node) + # Explore recursively the connected component + for each in graph[node]: + if (each not in visited and filter(each, node)): + spanning_tree[each] = node + dfs(each) + post.append(node) + + visited = {} # List for marking visited and non-visited nodes + spanning_tree = {} # Spanning tree + pre = [] # Graph's preordering + post = [] # Graph's postordering + filter.configure(graph, spanning_tree) + + # DFS from one node only + if (root is not None): + if filter(root, None): + spanning_tree[root] = None + dfs(root) + setrecursionlimit(recursionlimit) + return spanning_tree, pre, post + + # Algorithm loop + for each in graph: + # Select a non-visited node + if (each not in visited and filter(each, None)): + spanning_tree[each] = None + # Explore node's connected component + dfs(each) + + setrecursionlimit(recursionlimit) + + return (spanning_tree, pre, post) + + +# Breadth-first search + +def breadth_first_search(graph, root=None, filter=null()): + """ + Breadth-first search. + + @type graph: graph, digraph + @param graph: Graph. + + @type root: node + @param root: Optional root node (will explore only root's connected component) + + @rtype: tuple + @return: A tuple containing a dictionary and a list. + 1. Generated spanning tree + 2. Graph's level-based ordering + """ + + def bfs(): + """ + Breadth-first search subfunction. + """ + while (queue != []): + node = queue.pop(0) + + for other in graph[node]: + if (other not in spanning_tree and filter(other, node)): + queue.append(other) + ordering.append(other) + spanning_tree[other] = node + + queue = [] # Visiting queue + spanning_tree = {} # Spanning tree + ordering = [] + filter.configure(graph, spanning_tree) + + # BFS from one node only + if (root is not None): + if filter(root, None): + queue.append(root) + ordering.append(root) + spanning_tree[root] = None + bfs() + return spanning_tree, ordering + + # Algorithm + for each in graph: + if (each not in spanning_tree): + if filter(each, None): + queue.append(each) + ordering.append(each) + spanning_tree[each] = None + bfs() + + return spanning_tree, ordering diff --git a/core/pygraph/classes/digraph.py b/core/pygraph/classes/digraph.py index 6b4715f..97b3442 100644 --- a/core/pygraph/classes/digraph.py +++ b/core/pygraph/classes/digraph.py @@ -101,7 +101,7 @@ def edges(self): return [ a for a in self._edges() ] def _edges(self): - for n, neighbors in self.node_neighbors.items(): + for n, neighbors in list(self.node_neighbors.items()): for neighbor in neighbors: yield (n, neighbor) diff --git a/core/pygraph/classes/digraph.py.bak b/core/pygraph/classes/digraph.py.bak new file mode 100644 index 0000000..6b4715f --- /dev/null +++ b/core/pygraph/classes/digraph.py.bak @@ -0,0 +1,259 @@ +# Copyright (c) 2007-2009 Pedro Matiello +# Christian Muise +# Johannes Reinhardt +# Nathan Davis +# Zsolt Haraszti +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +""" +Digraph class +""" + +# Imports +from pygraph.classes.exceptions import AdditionError +from pygraph.mixins.labeling import labeling +from pygraph.mixins.common import common +from pygraph.mixins.basegraph import basegraph + +class digraph (basegraph, common, labeling): + """ + Digraph class. + + Digraphs are built of nodes and directed edges. + + @sort: __eq__, __init__, __ne__, add_edge, add_node, del_edge, del_node, edges, has_edge, has_node, + incidents, neighbors, node_order, nodes + """ + + DIRECTED = True + + def __init__(self): + """ + Initialize a digraph. + """ + common.__init__(self) + labeling.__init__(self) + self.node_neighbors = {} # Pairing: Node -> Neighbors + self.node_incidence = {} # Pairing: Node -> Incident nodes + + + def nodes(self): + """ + Return node list. + + @rtype: list + @return: Node list. + """ + return list(self.node_neighbors.keys()) + + + def neighbors(self, node): + """ + Return all nodes that are directly accessible from given node. + + @type node: node + @param node: Node identifier + + @rtype: list + @return: List of nodes directly accessible from given node. + """ + return self.node_neighbors[node] + + + def incidents(self, node): + """ + Return all nodes that are incident to the given node. + + @type node: node + @param node: Node identifier + + @rtype: list + @return: List of nodes directly accessible from given node. + """ + return self.node_incidence[node] + + def edges(self): + """ + Return all edges in the graph. + + @rtype: list + @return: List of all edges in the graph. + """ + return [ a for a in self._edges() ] + + def _edges(self): + for n, neighbors in self.node_neighbors.items(): + for neighbor in neighbors: + yield (n, neighbor) + + def has_node(self, node): + """ + Return whether the requested node exists. + + @type node: node + @param node: Node identifier + + @rtype: boolean + @return: Truth-value for node existence. + """ + return node in self.node_neighbors + + def add_node(self, node, attrs = None): + """ + Add given node to the graph. + + @attention: While nodes can be of any type, it's strongly recommended to use only + numbers and single-line strings as node identifiers if you intend to use write(). + + @type node: node + @param node: Node identifier. + + @type attrs: list + @param attrs: List of node attributes specified as (attribute, value) tuples. + """ + if attrs is None: + attrs = [] + if (node not in self.node_neighbors): + self.node_neighbors[node] = [] + self.node_incidence[node] = [] + self.node_attr[node] = attrs + else: + raise AdditionError("Node %s already in digraph" % node) + + + def add_edge(self, edge, wt = 1, label="", attrs = []): + """ + Add an directed edge to the graph connecting two nodes. + + An edge, here, is a pair of nodes like C{(n, m)}. + + @type edge: tuple + @param edge: Edge. + + @type wt: number + @param wt: Edge weight. + + @type label: string + @param label: Edge label. + + @type attrs: list + @param attrs: List of node attributes specified as (attribute, value) tuples. + """ + u, v = edge + for n in [u,v]: + if not n in self.node_neighbors: + raise AdditionError( "%s is missing from the node_neighbors table" % n ) + if not n in self.node_incidence: + raise AdditionError( "%s is missing from the node_incidence table" % n ) + + if v in self.node_neighbors[u] and u in self.node_incidence[v]: + raise AdditionError("Edge (%s, %s) already in digraph" % (u, v)) + else: + self.node_neighbors[u].append(v) + self.node_incidence[v].append(u) + self.set_edge_weight((u, v), wt) + self.add_edge_attributes( (u, v), attrs ) + self.set_edge_properties( (u, v), label=label, weight=wt ) + + + def del_node(self, node): + """ + Remove a node from the graph. + + @type node: node + @param node: Node identifier. + """ + for each in list(self.incidents(node)): + # Delete all the edges incident on this node + self.del_edge((each, node)) + + for each in list(self.neighbors(node)): + # Delete all the edges pointing to this node. + self.del_edge((node, each)) + + # Remove this node from the neighbors and incidents tables + del(self.node_neighbors[node]) + del(self.node_incidence[node]) + + # Remove any labeling which may exist. + self.del_node_labeling( node ) + + + def del_edge(self, edge): + """ + Remove an directed edge from the graph. + + @type edge: tuple + @param edge: Edge. + """ + u, v = edge + self.node_neighbors[u].remove(v) + self.node_incidence[v].remove(u) + self.del_edge_labeling( (u,v) ) + + + def has_edge(self, edge): + """ + Return whether an edge exists. + + @type edge: tuple + @param edge: Edge. + + @rtype: boolean + @return: Truth-value for edge existence. + """ + u, v = edge + return (u, v) in self.edge_properties + + + def node_order(self, node): + """ + Return the order of the given node. + + @rtype: number + @return: Order of the given node. + """ + return len(self.neighbors(node)) + + def __eq__(self, other): + """ + Return whether this graph is equal to another one. + + @type other: graph, digraph + @param other: Other graph or digraph + + @rtype: boolean + @return: Whether this graph and the other are equal. + """ + return common.__eq__(self, other) and labeling.__eq__(self, other) + + def __ne__(self, other): + """ + Return whether this graph is not equal to another one. + + @type other: graph, digraph + @param other: Other graph or digraph + + @rtype: boolean + @return: Whether this graph and the other are different. + """ + return not (self == other) diff --git a/core/pygraph/classes/graph.py b/core/pygraph/classes/graph.py index fc20e88..8aee642 100644 --- a/core/pygraph/classes/graph.py +++ b/core/pygraph/classes/graph.py @@ -87,7 +87,7 @@ def edges(self): @rtype: list @return: List of all edges in the graph. """ - return [ a for a in self.edge_properties.keys() ] + return [ a for a in list(self.edge_properties.keys()) ] def has_node(self, node): """ diff --git a/core/pygraph/classes/graph.py.bak b/core/pygraph/classes/graph.py.bak new file mode 100644 index 0000000..fc20e88 --- /dev/null +++ b/core/pygraph/classes/graph.py.bak @@ -0,0 +1,230 @@ +# Copyright (c) 2007-2009 Pedro Matiello +# Johannes Reinhardt +# Nathan Davis +# Zsolt Haraszti +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Graph class +""" + + +# Imports +from pygraph.classes.exceptions import AdditionError +from pygraph.mixins.labeling import labeling +from pygraph.mixins.common import common +from pygraph.mixins.basegraph import basegraph + + +class graph(basegraph, common, labeling): + """ + Graph class. + + Graphs are built of nodes and edges. + + @sort: __eq__, __init__, __ne__, add_edge, add_node, del_edge, del_node, edges, has_edge, has_node, + neighbors, node_order, nodes + """ + + DIRECTED = False + + + def __init__(self): + """ + Initialize a graph. + """ + common.__init__(self) + labeling.__init__(self) + self.node_neighbors = {} # Pairing: Node -> Neighbors + + def nodes(self): + """ + Return node list. + + @rtype: list + @return: Node list. + """ + return list(self.node_neighbors.keys()) + + + def neighbors(self, node): + """ + Return all nodes that are directly accessible from given node. + + @type node: node + @param node: Node identifier + + @rtype: list + @return: List of nodes directly accessible from given node. + """ + return self.node_neighbors[node] + + def edges(self): + """ + Return all edges in the graph. + + @rtype: list + @return: List of all edges in the graph. + """ + return [ a for a in self.edge_properties.keys() ] + + def has_node(self, node): + """ + Return whether the requested node exists. + + @type node: node + @param node: Node identifier + + @rtype: boolean + @return: Truth-value for node existence. + """ + return node in self.node_neighbors + + + def add_node(self, node, attrs=None): + """ + Add given node to the graph. + + @attention: While nodes can be of any type, it's strongly recommended to use only + numbers and single-line strings as node identifiers if you intend to use write(). + + @type node: node + @param node: Node identifier. + + @type attrs: list + @param attrs: List of node attributes specified as (attribute, value) tuples. + """ + if attrs is None: + attrs = [] + if (not node in self.node_neighbors): + self.node_neighbors[node] = [] + self.node_attr[node] = attrs + else: + raise AdditionError("Node %s already in graph" % node) + + def add_edge(self, edge, wt=1, label='', attrs=[]): + """ + Add an edge to the graph connecting two nodes. + + An edge, here, is a pair of nodes like C{(n, m)}. + + @type edge: tuple + @param edge: Edge. + + @type wt: number + @param wt: Edge weight. + + @type label: string + @param label: Edge label. + + @type attrs: list + @param attrs: List of node attributes specified as (attribute, value) tuples. + """ + u, v = edge + if (v not in self.node_neighbors[u] and u not in self.node_neighbors[v]): + self.node_neighbors[u].append(v) + if (u != v): + self.node_neighbors[v].append(u) + + self.add_edge_attributes((u,v), attrs) + self.set_edge_properties((u, v), label=label, weight=wt) + else: + raise AdditionError("Edge (%s, %s) already in graph" % (u, v)) + + + def del_node(self, node): + """ + Remove a node from the graph. + + @type node: node + @param node: Node identifier. + """ + for each in list(self.neighbors(node)): + if (each != node): + self.del_edge((each, node)) + del(self.node_neighbors[node]) + del(self.node_attr[node]) + + + def del_edge(self, edge): + """ + Remove an edge from the graph. + + @type edge: tuple + @param edge: Edge. + """ + u, v = edge + self.node_neighbors[u].remove(v) + self.del_edge_labeling((u, v)) + if (u != v): + self.node_neighbors[v].remove(u) + self.del_edge_labeling((v, u)) # TODO: This is redundant + + def has_edge(self, edge): + """ + Return whether an edge exists. + + @type edge: tuple + @param edge: Edge. + + @rtype: boolean + @return: Truth-value for edge existence. + """ + u,v = edge + return (u,v) in self.edge_properties and (v,u) in self.edge_properties + + + def node_order(self, node): + """ + Return the order of the graph + + @rtype: number + @return: Order of the given node. + """ + return len(self.neighbors(node)) + + + def __eq__(self, other): + """ + Return whether this graph is equal to another one. + + @type other: graph, digraph + @param other: Other graph or digraph + + @rtype: boolean + @return: Whether this graph and the other are equal. + """ + return common.__eq__(self, other) and labeling.__eq__(self, other) + + def __ne__(self, other): + """ + Return whether this graph is not equal to another one. + + @type other: graph, digraph + @param other: Other graph or digraph + + @rtype: boolean + @return: Whether this graph and the other are different. + """ + return not (self == other) diff --git a/core/python_graph_core.egg-info/PKG-INFO b/core/python_graph_core.egg-info/PKG-INFO new file mode 100644 index 0000000..327a232 --- /dev/null +++ b/core/python_graph_core.egg-info/PKG-INFO @@ -0,0 +1,13 @@ +Metadata-Version: 1.1 +Name: python-graph-core +Version: 1.8.2 +Summary: A library for working with graphs in Python +Home-page: http://code.google.com/p/python-graph/ +Author: Pedro Matiello +Author-email: pmatiello@gmail.com +License: MIT +Description: python-graph is a library for working with graphs in Python. This software provides a suitable data structure for representing graphs and a whole set of important algorithms. +Keywords: python graphs hypergraphs networks library algorithms +Platform: UNKNOWN +Classifier: License :: OSI Approved :: MIT License +Classifier: Topic :: Software Development :: Libraries :: Python Modules diff --git a/core/python_graph_core.egg-info/SOURCES.txt b/core/python_graph_core.egg-info/SOURCES.txt new file mode 100644 index 0000000..a58e146 --- /dev/null +++ b/core/python_graph_core.egg-info/SOURCES.txt @@ -0,0 +1,36 @@ +setup.py +pygraph/__init__.py +pygraph/algorithms/__init__.py +pygraph/algorithms/accessibility.py +pygraph/algorithms/critical.py +pygraph/algorithms/cycles.py +pygraph/algorithms/generators.py +pygraph/algorithms/minmax.py +pygraph/algorithms/pagerank.py +pygraph/algorithms/searching.py +pygraph/algorithms/sorting.py +pygraph/algorithms/traversal.py +pygraph/algorithms/utils.py +pygraph/algorithms/filters/__init__.py +pygraph/algorithms/filters/find.py +pygraph/algorithms/filters/null.py +pygraph/algorithms/filters/radius.py +pygraph/algorithms/heuristics/__init__.py +pygraph/algorithms/heuristics/chow.py +pygraph/algorithms/heuristics/euclidean.py +pygraph/classes/__init__.py +pygraph/classes/digraph.py +pygraph/classes/exceptions.py +pygraph/classes/graph.py +pygraph/classes/hypergraph.py +pygraph/mixins/__init__.py +pygraph/mixins/basegraph.py +pygraph/mixins/common.py +pygraph/mixins/labeling.py +pygraph/readwrite/__init__.py +pygraph/readwrite/markup.py +python_graph_core.egg-info/PKG-INFO +python_graph_core.egg-info/SOURCES.txt +python_graph_core.egg-info/dependency_links.txt +python_graph_core.egg-info/namespace_packages.txt +python_graph_core.egg-info/top_level.txt \ No newline at end of file diff --git a/core/python_graph_core.egg-info/dependency_links.txt b/core/python_graph_core.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/core/python_graph_core.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/core/python_graph_core.egg-info/namespace_packages.txt b/core/python_graph_core.egg-info/namespace_packages.txt new file mode 100644 index 0000000..ba98b63 --- /dev/null +++ b/core/python_graph_core.egg-info/namespace_packages.txt @@ -0,0 +1 @@ +pygraph diff --git a/core/python_graph_core.egg-info/top_level.txt b/core/python_graph_core.egg-info/top_level.txt new file mode 100644 index 0000000..2f4d8a8 --- /dev/null +++ b/core/python_graph_core.egg-info/top_level.txt @@ -0,0 +1,5 @@ +pygraph +pygraph\algorithms +pygraph\classes +pygraph\mixins +pygraph\readwrite diff --git a/dot/build/lib/pygraph/__init__.py b/dot/build/lib/pygraph/__init__.py new file mode 100644 index 0000000..de40ea7 --- /dev/null +++ b/dot/build/lib/pygraph/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) diff --git a/dot/build/lib/pygraph/readwrite/__init__.py b/dot/build/lib/pygraph/readwrite/__init__.py new file mode 100644 index 0000000..b0d6433 --- /dev/null +++ b/dot/build/lib/pygraph/readwrite/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) \ No newline at end of file diff --git a/dot/build/lib/pygraph/readwrite/dot.py b/dot/build/lib/pygraph/readwrite/dot.py new file mode 100644 index 0000000..a6964e2 --- /dev/null +++ b/dot/build/lib/pygraph/readwrite/dot.py @@ -0,0 +1,263 @@ +# Copyright (c) 2007-2009 Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Functions for reading and writing graphs in Dot language. + +@sort: read, read_hypergraph, write, write_hypergraph +""" + + +# Imports +from pygraph.classes.digraph import digraph +from pygraph.classes.exceptions import InvalidGraphType +from pygraph.classes.graph import graph +from pygraph.classes.hypergraph import hypergraph +import pydot + +# Values +colors = ['aquamarine4', 'blue4', 'brown4', 'cornflowerblue', 'cyan4', + 'darkgreen', 'darkorange3', 'darkorchid4', 'darkseagreen4', 'darkslategray', + 'deeppink4', 'deepskyblue4', 'firebrick3', 'hotpink3', 'indianred3', + 'indigo', 'lightblue4', 'lightseagreen', 'lightskyblue4', 'magenta4', + 'maroon', 'palevioletred3', 'steelblue', 'violetred3'] + + +def read(string): + """ + Read a graph from a string in Dot language and return it. Nodes and edges specified in the + input will be added to the current graph. + + @type string: string + @param string: Input string in Dot format specifying a graph. + + @rtype: graph + @return: Graph + """ + + dotG = pydot.graph_from_dot_data(string) + + if (dotG.get_type() == "graph"): + G = graph() + elif (dotG.get_type() == "digraph"): + G = digraph() + elif (dotG.get_type() == "hypergraph"): + return read_hypergraph(string) + else: + raise InvalidGraphType + + # Read nodes... + # Note: If the nodes aren't explicitly listed, they need to be + for each_node in dotG.get_nodes(): + G.add_node(each_node.get_name()) + for each_attr_key, each_attr_val in list(each_node.get_attributes().items()): + G.add_node_attribute(each_node.get_name(), (each_attr_key, each_attr_val)) + + # Read edges... + for each_edge in dotG.get_edges(): + # Check if the nodes have been added + if not G.has_node(each_edge.get_source()): + G.add_node(each_edge.get_source()) + if not G.has_node(each_edge.get_destination()): + G.add_node(each_edge.get_destination()) + + # See if there's a weight + if 'weight' in list(each_edge.get_attributes().keys()): + _wt = each_edge.get_attributes()['weight'] + else: + _wt = 1 + + # See if there is a label + if 'label' in list(each_edge.get_attributes().keys()): + _label = each_edge.get_attributes()['label'] + else: + _label = '' + + G.add_edge((each_edge.get_source(), each_edge.get_destination()), wt = _wt, label = _label) + + for each_attr_key, each_attr_val in list(each_edge.get_attributes().items()): + if not each_attr_key in ['weight', 'label']: + G.add_edge_attribute((each_edge.get_source(), each_edge.get_destination()), \ + (each_attr_key, each_attr_val)) + + return G + + +def write(G, weighted=False): + """ + Return a string specifying the given graph in Dot language. + + @type G: graph + @param G: Graph. + + @type weighted: boolean + @param weighted: Whether edges should be labelled with their weight. + + @rtype: string + @return: String specifying the graph in Dot Language. + """ + dotG = pydot.Dot() + + if not 'name' in dir(G): + dotG.set_name('graphname') + else: + dotG.set_name(G.name) + + if (isinstance(G, graph)): + dotG.set_type('graph') + directed = False + elif (isinstance(G, digraph)): + dotG.set_type('digraph') + directed = True + elif (isinstance(G, hypergraph)): + return write_hypergraph(G) + else: + raise InvalidGraphType("Expected graph or digraph, got %s" % repr(G) ) + + for node in G.nodes(): + attr_list = {} + for attr in G.node_attributes(node): + attr_list[str(attr[0])] = str(attr[1]) + + newNode = pydot.Node(str(node), **attr_list) + + dotG.add_node(newNode) + + # Pydot doesn't work properly with the get_edge, so we use + # our own set to keep track of what's been added or not. + seen_edges = set([]) + for edge_from, edge_to in G.edges(): + if (str(edge_from) + "-" + str(edge_to)) in seen_edges: + continue + + if (not directed) and (str(edge_to) + "-" + str(edge_from)) in seen_edges: + continue + + attr_list = {} + for attr in G.edge_attributes((edge_from, edge_to)): + attr_list[str(attr[0])] = str(attr[1]) + + if str(G.edge_label((edge_from, edge_to))): + attr_list['label'] = str(G.edge_label((edge_from, edge_to))) + + elif weighted: + attr_list['label'] = str(G.edge_weight((edge_from, edge_to))) + + if weighted: + attr_list['weight'] = str(G.edge_weight((edge_from, edge_to))) + + newEdge = pydot.Edge(str(edge_from), str(edge_to), **attr_list) + + dotG.add_edge(newEdge) + + seen_edges.add(str(edge_from) + "-" + str(edge_to)) + + return dotG.to_string() + + +def read_hypergraph(string): + """ + Read a hypergraph from a string in dot format. Nodes and edges specified in the input will be + added to the current hypergraph. + + @type string: string + @param string: Input string in dot format specifying a graph. + + @rtype: hypergraph + @return: Hypergraph + """ + hgr = hypergraph() + dotG = pydot.graph_from_dot_data(string) + + # Read the hypernode nodes... + # Note 1: We need to assume that all of the nodes are listed since we need to know if they + # are a hyperedge or a normal node + # Note 2: We should read in all of the nodes before putting in the links + for each_node in dotG.get_nodes(): + if 'hypernode' == each_node.get('hyper_node_type'): + hgr.add_node(each_node.get_name()) + elif 'hyperedge' == each_node.get('hyper_node_type'): + hgr.add_hyperedge(each_node.get_name()) + + # Now read in the links to connect the hyperedges + for each_link in dotG.get_edges(): + if hgr.has_node(each_link.get_source()): + link_hypernode = each_link.get_source() + link_hyperedge = each_link.get_destination() + elif hgr.has_node(each_link.get_destination()): + link_hypernode = each_link.get_destination() + link_hyperedge = each_link.get_source() + hgr.link(link_hypernode, link_hyperedge) + + return hgr + + +def write_hypergraph(hgr, colored = False): + """ + Return a string specifying the given hypergraph in DOT Language. + + @type hgr: hypergraph + @param hgr: Hypergraph. + + @type colored: boolean + @param colored: Whether hyperedges should be colored. + + @rtype: string + @return: String specifying the hypergraph in DOT Language. + """ + dotG = pydot.Dot() + + if not 'name' in dir(hgr): + dotG.set_name('hypergraph') + else: + dotG.set_name(hgr.name) + + colortable = {} + colorcount = 0 + + # Add all of the nodes first + for node in hgr.nodes(): + newNode = pydot.Node(str(node), hyper_node_type = 'hypernode') + + dotG.add_node(newNode) + + for hyperedge in hgr.hyperedges(): + + if (colored): + colortable[hyperedge] = colors[colorcount % len(colors)] + colorcount += 1 + + newNode = pydot.Node(str(hyperedge), hyper_node_type = 'hyperedge', \ + color = str(colortable[hyperedge]), \ + shape = 'point') + else: + newNode = pydot.Node(str(hyperedge), hyper_node_type = 'hyperedge') + + dotG.add_node(newNode) + + for link in hgr.links(hyperedge): + newEdge = pydot.Edge(str(hyperedge), str(link)) + dotG.add_edge(newEdge) + + return dotG.to_string() diff --git a/dot/dist/python_graph_dot-1.8.2-py3.5.egg b/dot/dist/python_graph_dot-1.8.2-py3.5.egg new file mode 100644 index 0000000..34cc60b Binary files /dev/null and b/dot/dist/python_graph_dot-1.8.2-py3.5.egg differ diff --git a/dot/distribute_setup.py b/dot/distribute_setup.py index f6af757..1a26663 100644 --- a/dot/distribute_setup.py +++ b/dot/distribute_setup.py @@ -182,7 +182,7 @@ def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, try: from urllib.request import urlopen except ImportError: - from urllib2 import urlopen + from urllib.request import urlopen tgz_name = "distribute-%s.tar.gz" % version url = download_base + tgz_name saveto = os.path.join(to_dir, tgz_name) diff --git a/dot/distribute_setup.py.bak b/dot/distribute_setup.py.bak new file mode 100644 index 0000000..f6af757 --- /dev/null +++ b/dot/distribute_setup.py.bak @@ -0,0 +1,487 @@ +#!python +"""Bootstrap distribute installation + +If you want to use setuptools in your package's setup.py, just include this +file in the same directory with it, and add this to the top of your setup.py:: + + from distribute_setup import use_setuptools + use_setuptools() + +If you want to require a specific version of setuptools, set a download +mirror, or use an alternate download directory, you can do so by supplying +the appropriate options to ``use_setuptools()``. + +This file can also be run as a script to install or upgrade setuptools. +""" +import os +import sys +import time +import fnmatch +import tempfile +import tarfile +from distutils import log + +try: + from site import USER_SITE +except ImportError: + USER_SITE = None + +try: + import subprocess + + def _python_cmd(*args): + args = (sys.executable,) + args + return subprocess.call(args) == 0 + +except ImportError: + # will be used for python 2.3 + def _python_cmd(*args): + args = (sys.executable,) + args + # quoting arguments if windows + if sys.platform == 'win32': + def quote(arg): + if ' ' in arg: + return '"%s"' % arg + return arg + args = [quote(arg) for arg in args] + return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 + +DEFAULT_VERSION = "0.6.24" +DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" +SETUPTOOLS_FAKED_VERSION = "0.6c11" + +SETUPTOOLS_PKG_INFO = """\ +Metadata-Version: 1.0 +Name: setuptools +Version: %s +Summary: xxxx +Home-page: xxx +Author: xxx +Author-email: xxx +License: xxx +Description: xxx +""" % SETUPTOOLS_FAKED_VERSION + + +def _install(tarball): + # extracting the tarball + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + tar = tarfile.open(tarball) + _extractall(tar) + tar.close() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) + + # installing + log.warn('Installing Distribute') + if not _python_cmd('setup.py', 'install'): + log.warn('Something went wrong during the installation.') + log.warn('See the error message above.') + finally: + os.chdir(old_wd) + + +def _build_egg(egg, tarball, to_dir): + # extracting the tarball + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + tar = tarfile.open(tarball) + _extractall(tar) + tar.close() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) + + # building an egg + log.warn('Building a Distribute egg in %s', to_dir) + _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) + + finally: + os.chdir(old_wd) + # returning the result + log.warn(egg) + if not os.path.exists(egg): + raise IOError('Could not build the egg.') + + +def _do_download(version, download_base, to_dir, download_delay): + egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' + % (version, sys.version_info[0], sys.version_info[1])) + if not os.path.exists(egg): + tarball = download_setuptools(version, download_base, + to_dir, download_delay) + _build_egg(egg, tarball, to_dir) + sys.path.insert(0, egg) + import setuptools + setuptools.bootstrap_install_from = egg + + +def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, download_delay=15, no_fake=True): + # making sure we use the absolute path + to_dir = os.path.abspath(to_dir) + was_imported = 'pkg_resources' in sys.modules or \ + 'setuptools' in sys.modules + try: + try: + import pkg_resources + if not hasattr(pkg_resources, '_distribute'): + if not no_fake: + _fake_setuptools() + raise ImportError + except ImportError: + return _do_download(version, download_base, to_dir, download_delay) + try: + pkg_resources.require("distribute>="+version) + return + except pkg_resources.VersionConflict: + e = sys.exc_info()[1] + if was_imported: + sys.stderr.write( + "The required version of distribute (>=%s) is not available,\n" + "and can't be installed while this script is running. Please\n" + "install a more recent version first, using\n" + "'easy_install -U distribute'." + "\n\n(Currently using %r)\n" % (version, e.args[0])) + sys.exit(2) + else: + del pkg_resources, sys.modules['pkg_resources'] # reload ok + return _do_download(version, download_base, to_dir, + download_delay) + except pkg_resources.DistributionNotFound: + return _do_download(version, download_base, to_dir, + download_delay) + finally: + if not no_fake: + _create_fake_setuptools_pkg_info(to_dir) + +def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, delay=15): + """Download distribute from a specified location and return its filename + + `version` should be a valid distribute version number that is available + as an egg for download under the `download_base` URL (which should end + with a '/'). `to_dir` is the directory where the egg will be downloaded. + `delay` is the number of seconds to pause before an actual download + attempt. + """ + # making sure we use the absolute path + to_dir = os.path.abspath(to_dir) + try: + from urllib.request import urlopen + except ImportError: + from urllib2 import urlopen + tgz_name = "distribute-%s.tar.gz" % version + url = download_base + tgz_name + saveto = os.path.join(to_dir, tgz_name) + src = dst = None + if not os.path.exists(saveto): # Avoid repeated downloads + try: + log.warn("Downloading %s", url) + src = urlopen(url) + # Read/write all in one block, so we don't create a corrupt file + # if the download is interrupted. + data = src.read() + dst = open(saveto, "wb") + dst.write(data) + finally: + if src: + src.close() + if dst: + dst.close() + return os.path.realpath(saveto) + +def _no_sandbox(function): + def __no_sandbox(*args, **kw): + try: + from setuptools.sandbox import DirectorySandbox + if not hasattr(DirectorySandbox, '_old'): + def violation(*args): + pass + DirectorySandbox._old = DirectorySandbox._violation + DirectorySandbox._violation = violation + patched = True + else: + patched = False + except ImportError: + patched = False + + try: + return function(*args, **kw) + finally: + if patched: + DirectorySandbox._violation = DirectorySandbox._old + del DirectorySandbox._old + + return __no_sandbox + +def _patch_file(path, content): + """Will backup the file then patch it""" + existing_content = open(path).read() + if existing_content == content: + # already patched + log.warn('Already patched.') + return False + log.warn('Patching...') + _rename_path(path) + f = open(path, 'w') + try: + f.write(content) + finally: + f.close() + return True + +_patch_file = _no_sandbox(_patch_file) + +def _same_content(path, content): + return open(path).read() == content + +def _rename_path(path): + new_name = path + '.OLD.%s' % time.time() + log.warn('Renaming %s into %s', path, new_name) + os.rename(path, new_name) + return new_name + +def _remove_flat_installation(placeholder): + if not os.path.isdir(placeholder): + log.warn('Unkown installation at %s', placeholder) + return False + found = False + for file in os.listdir(placeholder): + if fnmatch.fnmatch(file, 'setuptools*.egg-info'): + found = True + break + if not found: + log.warn('Could not locate setuptools*.egg-info') + return + + log.warn('Removing elements out of the way...') + pkg_info = os.path.join(placeholder, file) + if os.path.isdir(pkg_info): + patched = _patch_egg_dir(pkg_info) + else: + patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO) + + if not patched: + log.warn('%s already patched.', pkg_info) + return False + # now let's move the files out of the way + for element in ('setuptools', 'pkg_resources.py', 'site.py'): + element = os.path.join(placeholder, element) + if os.path.exists(element): + _rename_path(element) + else: + log.warn('Could not find the %s element of the ' + 'Setuptools distribution', element) + return True + +_remove_flat_installation = _no_sandbox(_remove_flat_installation) + +def _after_install(dist): + log.warn('After install bootstrap.') + placeholder = dist.get_command_obj('install').install_purelib + _create_fake_setuptools_pkg_info(placeholder) + +def _create_fake_setuptools_pkg_info(placeholder): + if not placeholder or not os.path.exists(placeholder): + log.warn('Could not find the install location') + return + pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) + setuptools_file = 'setuptools-%s-py%s.egg-info' % \ + (SETUPTOOLS_FAKED_VERSION, pyver) + pkg_info = os.path.join(placeholder, setuptools_file) + if os.path.exists(pkg_info): + log.warn('%s already exists', pkg_info) + return + + log.warn('Creating %s', pkg_info) + f = open(pkg_info, 'w') + try: + f.write(SETUPTOOLS_PKG_INFO) + finally: + f.close() + + pth_file = os.path.join(placeholder, 'setuptools.pth') + log.warn('Creating %s', pth_file) + f = open(pth_file, 'w') + try: + f.write(os.path.join(os.curdir, setuptools_file)) + finally: + f.close() + +_create_fake_setuptools_pkg_info = _no_sandbox(_create_fake_setuptools_pkg_info) + +def _patch_egg_dir(path): + # let's check if it's already patched + pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') + if os.path.exists(pkg_info): + if _same_content(pkg_info, SETUPTOOLS_PKG_INFO): + log.warn('%s already patched.', pkg_info) + return False + _rename_path(path) + os.mkdir(path) + os.mkdir(os.path.join(path, 'EGG-INFO')) + pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') + f = open(pkg_info, 'w') + try: + f.write(SETUPTOOLS_PKG_INFO) + finally: + f.close() + return True + +_patch_egg_dir = _no_sandbox(_patch_egg_dir) + +def _before_install(): + log.warn('Before install bootstrap.') + _fake_setuptools() + + +def _under_prefix(location): + if 'install' not in sys.argv: + return True + args = sys.argv[sys.argv.index('install')+1:] + for index, arg in enumerate(args): + for option in ('--root', '--prefix'): + if arg.startswith('%s=' % option): + top_dir = arg.split('root=')[-1] + return location.startswith(top_dir) + elif arg == option: + if len(args) > index: + top_dir = args[index+1] + return location.startswith(top_dir) + if arg == '--user' and USER_SITE is not None: + return location.startswith(USER_SITE) + return True + + +def _fake_setuptools(): + log.warn('Scanning installed packages') + try: + import pkg_resources + except ImportError: + # we're cool + log.warn('Setuptools or Distribute does not seem to be installed.') + return + ws = pkg_resources.working_set + try: + setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools', + replacement=False)) + except TypeError: + # old distribute API + setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools')) + + if setuptools_dist is None: + log.warn('No setuptools distribution found') + return + # detecting if it was already faked + setuptools_location = setuptools_dist.location + log.warn('Setuptools installation detected at %s', setuptools_location) + + # if --root or --preix was provided, and if + # setuptools is not located in them, we don't patch it + if not _under_prefix(setuptools_location): + log.warn('Not patching, --root or --prefix is installing Distribute' + ' in another location') + return + + # let's see if its an egg + if not setuptools_location.endswith('.egg'): + log.warn('Non-egg installation') + res = _remove_flat_installation(setuptools_location) + if not res: + return + else: + log.warn('Egg installation') + pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') + if (os.path.exists(pkg_info) and + _same_content(pkg_info, SETUPTOOLS_PKG_INFO)): + log.warn('Already patched.') + return + log.warn('Patching...') + # let's create a fake egg replacing setuptools one + res = _patch_egg_dir(setuptools_location) + if not res: + return + log.warn('Patched done.') + _relaunch() + + +def _relaunch(): + log.warn('Relaunching...') + # we have to relaunch the process + # pip marker to avoid a relaunch bug + if sys.argv[:3] == ['-c', 'install', '--single-version-externally-managed']: + sys.argv[0] = 'setup.py' + args = [sys.executable] + sys.argv + sys.exit(subprocess.call(args)) + + +def _extractall(self, path=".", members=None): + """Extract all members from the archive to the current working + directory and set owner, modification time and permissions on + directories afterwards. `path' specifies a different directory + to extract to. `members' is optional and must be a subset of the + list returned by getmembers(). + """ + import copy + import operator + from tarfile import ExtractError + directories = [] + + if members is None: + members = self + + for tarinfo in members: + if tarinfo.isdir(): + # Extract directories with a safe mode. + directories.append(tarinfo) + tarinfo = copy.copy(tarinfo) + tarinfo.mode = 448 # decimal for oct 0700 + self.extract(tarinfo, path) + + # Reverse sort directories. + if sys.version_info < (2, 4): + def sorter(dir1, dir2): + return cmp(dir1.name, dir2.name) + directories.sort(sorter) + directories.reverse() + else: + directories.sort(key=operator.attrgetter('name'), reverse=True) + + # Set correct owner, mtime and filemode on directories. + for tarinfo in directories: + dirpath = os.path.join(path, tarinfo.name) + try: + self.chown(tarinfo, dirpath) + self.utime(tarinfo, dirpath) + self.chmod(tarinfo, dirpath) + except ExtractError: + e = sys.exc_info()[1] + if self.errorlevel > 1: + raise + else: + self._dbg(1, "tarfile: %s" % e) + + +def main(argv, version=DEFAULT_VERSION): + """Install or upgrade setuptools and EasyInstall""" + tarball = download_setuptools() + _install(tarball) + + +if __name__ == '__main__': + import os + os.environ.set("HTTP_HOST",r"http//ldnproxy.ldn.emea.cib:9090") + main(sys.argv[1:]) diff --git a/dot/pygraph/readwrite/dot.py b/dot/pygraph/readwrite/dot.py index 2bdfa60..a6964e2 100644 --- a/dot/pygraph/readwrite/dot.py +++ b/dot/pygraph/readwrite/dot.py @@ -71,7 +71,7 @@ def read(string): # Note: If the nodes aren't explicitly listed, they need to be for each_node in dotG.get_nodes(): G.add_node(each_node.get_name()) - for each_attr_key, each_attr_val in each_node.get_attributes().items(): + for each_attr_key, each_attr_val in list(each_node.get_attributes().items()): G.add_node_attribute(each_node.get_name(), (each_attr_key, each_attr_val)) # Read edges... @@ -83,20 +83,20 @@ def read(string): G.add_node(each_edge.get_destination()) # See if there's a weight - if 'weight' in each_edge.get_attributes().keys(): + if 'weight' in list(each_edge.get_attributes().keys()): _wt = each_edge.get_attributes()['weight'] else: _wt = 1 # See if there is a label - if 'label' in each_edge.get_attributes().keys(): + if 'label' in list(each_edge.get_attributes().keys()): _label = each_edge.get_attributes()['label'] else: _label = '' G.add_edge((each_edge.get_source(), each_edge.get_destination()), wt = _wt, label = _label) - for each_attr_key, each_attr_val in each_edge.get_attributes().items(): + for each_attr_key, each_attr_val in list(each_edge.get_attributes().items()): if not each_attr_key in ['weight', 'label']: G.add_edge_attribute((each_edge.get_source(), each_edge.get_destination()), \ (each_attr_key, each_attr_val)) diff --git a/dot/pygraph/readwrite/dot.py.bak b/dot/pygraph/readwrite/dot.py.bak new file mode 100644 index 0000000..2bdfa60 --- /dev/null +++ b/dot/pygraph/readwrite/dot.py.bak @@ -0,0 +1,263 @@ +# Copyright (c) 2007-2009 Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Functions for reading and writing graphs in Dot language. + +@sort: read, read_hypergraph, write, write_hypergraph +""" + + +# Imports +from pygraph.classes.digraph import digraph +from pygraph.classes.exceptions import InvalidGraphType +from pygraph.classes.graph import graph +from pygraph.classes.hypergraph import hypergraph +import pydot + +# Values +colors = ['aquamarine4', 'blue4', 'brown4', 'cornflowerblue', 'cyan4', + 'darkgreen', 'darkorange3', 'darkorchid4', 'darkseagreen4', 'darkslategray', + 'deeppink4', 'deepskyblue4', 'firebrick3', 'hotpink3', 'indianred3', + 'indigo', 'lightblue4', 'lightseagreen', 'lightskyblue4', 'magenta4', + 'maroon', 'palevioletred3', 'steelblue', 'violetred3'] + + +def read(string): + """ + Read a graph from a string in Dot language and return it. Nodes and edges specified in the + input will be added to the current graph. + + @type string: string + @param string: Input string in Dot format specifying a graph. + + @rtype: graph + @return: Graph + """ + + dotG = pydot.graph_from_dot_data(string) + + if (dotG.get_type() == "graph"): + G = graph() + elif (dotG.get_type() == "digraph"): + G = digraph() + elif (dotG.get_type() == "hypergraph"): + return read_hypergraph(string) + else: + raise InvalidGraphType + + # Read nodes... + # Note: If the nodes aren't explicitly listed, they need to be + for each_node in dotG.get_nodes(): + G.add_node(each_node.get_name()) + for each_attr_key, each_attr_val in each_node.get_attributes().items(): + G.add_node_attribute(each_node.get_name(), (each_attr_key, each_attr_val)) + + # Read edges... + for each_edge in dotG.get_edges(): + # Check if the nodes have been added + if not G.has_node(each_edge.get_source()): + G.add_node(each_edge.get_source()) + if not G.has_node(each_edge.get_destination()): + G.add_node(each_edge.get_destination()) + + # See if there's a weight + if 'weight' in each_edge.get_attributes().keys(): + _wt = each_edge.get_attributes()['weight'] + else: + _wt = 1 + + # See if there is a label + if 'label' in each_edge.get_attributes().keys(): + _label = each_edge.get_attributes()['label'] + else: + _label = '' + + G.add_edge((each_edge.get_source(), each_edge.get_destination()), wt = _wt, label = _label) + + for each_attr_key, each_attr_val in each_edge.get_attributes().items(): + if not each_attr_key in ['weight', 'label']: + G.add_edge_attribute((each_edge.get_source(), each_edge.get_destination()), \ + (each_attr_key, each_attr_val)) + + return G + + +def write(G, weighted=False): + """ + Return a string specifying the given graph in Dot language. + + @type G: graph + @param G: Graph. + + @type weighted: boolean + @param weighted: Whether edges should be labelled with their weight. + + @rtype: string + @return: String specifying the graph in Dot Language. + """ + dotG = pydot.Dot() + + if not 'name' in dir(G): + dotG.set_name('graphname') + else: + dotG.set_name(G.name) + + if (isinstance(G, graph)): + dotG.set_type('graph') + directed = False + elif (isinstance(G, digraph)): + dotG.set_type('digraph') + directed = True + elif (isinstance(G, hypergraph)): + return write_hypergraph(G) + else: + raise InvalidGraphType("Expected graph or digraph, got %s" % repr(G) ) + + for node in G.nodes(): + attr_list = {} + for attr in G.node_attributes(node): + attr_list[str(attr[0])] = str(attr[1]) + + newNode = pydot.Node(str(node), **attr_list) + + dotG.add_node(newNode) + + # Pydot doesn't work properly with the get_edge, so we use + # our own set to keep track of what's been added or not. + seen_edges = set([]) + for edge_from, edge_to in G.edges(): + if (str(edge_from) + "-" + str(edge_to)) in seen_edges: + continue + + if (not directed) and (str(edge_to) + "-" + str(edge_from)) in seen_edges: + continue + + attr_list = {} + for attr in G.edge_attributes((edge_from, edge_to)): + attr_list[str(attr[0])] = str(attr[1]) + + if str(G.edge_label((edge_from, edge_to))): + attr_list['label'] = str(G.edge_label((edge_from, edge_to))) + + elif weighted: + attr_list['label'] = str(G.edge_weight((edge_from, edge_to))) + + if weighted: + attr_list['weight'] = str(G.edge_weight((edge_from, edge_to))) + + newEdge = pydot.Edge(str(edge_from), str(edge_to), **attr_list) + + dotG.add_edge(newEdge) + + seen_edges.add(str(edge_from) + "-" + str(edge_to)) + + return dotG.to_string() + + +def read_hypergraph(string): + """ + Read a hypergraph from a string in dot format. Nodes and edges specified in the input will be + added to the current hypergraph. + + @type string: string + @param string: Input string in dot format specifying a graph. + + @rtype: hypergraph + @return: Hypergraph + """ + hgr = hypergraph() + dotG = pydot.graph_from_dot_data(string) + + # Read the hypernode nodes... + # Note 1: We need to assume that all of the nodes are listed since we need to know if they + # are a hyperedge or a normal node + # Note 2: We should read in all of the nodes before putting in the links + for each_node in dotG.get_nodes(): + if 'hypernode' == each_node.get('hyper_node_type'): + hgr.add_node(each_node.get_name()) + elif 'hyperedge' == each_node.get('hyper_node_type'): + hgr.add_hyperedge(each_node.get_name()) + + # Now read in the links to connect the hyperedges + for each_link in dotG.get_edges(): + if hgr.has_node(each_link.get_source()): + link_hypernode = each_link.get_source() + link_hyperedge = each_link.get_destination() + elif hgr.has_node(each_link.get_destination()): + link_hypernode = each_link.get_destination() + link_hyperedge = each_link.get_source() + hgr.link(link_hypernode, link_hyperedge) + + return hgr + + +def write_hypergraph(hgr, colored = False): + """ + Return a string specifying the given hypergraph in DOT Language. + + @type hgr: hypergraph + @param hgr: Hypergraph. + + @type colored: boolean + @param colored: Whether hyperedges should be colored. + + @rtype: string + @return: String specifying the hypergraph in DOT Language. + """ + dotG = pydot.Dot() + + if not 'name' in dir(hgr): + dotG.set_name('hypergraph') + else: + dotG.set_name(hgr.name) + + colortable = {} + colorcount = 0 + + # Add all of the nodes first + for node in hgr.nodes(): + newNode = pydot.Node(str(node), hyper_node_type = 'hypernode') + + dotG.add_node(newNode) + + for hyperedge in hgr.hyperedges(): + + if (colored): + colortable[hyperedge] = colors[colorcount % len(colors)] + colorcount += 1 + + newNode = pydot.Node(str(hyperedge), hyper_node_type = 'hyperedge', \ + color = str(colortable[hyperedge]), \ + shape = 'point') + else: + newNode = pydot.Node(str(hyperedge), hyper_node_type = 'hyperedge') + + dotG.add_node(newNode) + + for link in hgr.links(hyperedge): + newEdge = pydot.Edge(str(hyperedge), str(link)) + dotG.add_edge(newEdge) + + return dotG.to_string() diff --git a/dot/python_graph_dot.egg-info/PKG-INFO b/dot/python_graph_dot.egg-info/PKG-INFO new file mode 100644 index 0000000..a06ae30 --- /dev/null +++ b/dot/python_graph_dot.egg-info/PKG-INFO @@ -0,0 +1,13 @@ +Metadata-Version: 1.1 +Name: python-graph-dot +Version: 1.8.2 +Summary: DOT support for python-graph +Home-page: http://code.google.com/p/python-graph/ +Author: Pedro Matiello +Author-email: pmatiello@gmail.com +License: MIT +Description: python-graph is a library for working with graphs in Python. This software provides a suitable data structure for representing graphs and a whole set of important algorithms. +Keywords: python graphs hypergraphs networks library algorithms +Platform: UNKNOWN +Classifier: License :: OSI Approved :: MIT License +Classifier: Topic :: Software Development :: Libraries :: Python Modules diff --git a/dot/python_graph_dot.egg-info/SOURCES.txt b/dot/python_graph_dot.egg-info/SOURCES.txt new file mode 100644 index 0000000..0598f6a --- /dev/null +++ b/dot/python_graph_dot.egg-info/SOURCES.txt @@ -0,0 +1,10 @@ +setup.py +pygraph/__init__.py +pygraph/readwrite/__init__.py +pygraph/readwrite/dot.py +python_graph_dot.egg-info/PKG-INFO +python_graph_dot.egg-info/SOURCES.txt +python_graph_dot.egg-info/dependency_links.txt +python_graph_dot.egg-info/namespace_packages.txt +python_graph_dot.egg-info/requires.txt +python_graph_dot.egg-info/top_level.txt \ No newline at end of file diff --git a/dot/python_graph_dot.egg-info/dependency_links.txt b/dot/python_graph_dot.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/dot/python_graph_dot.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/dot/python_graph_dot.egg-info/namespace_packages.txt b/dot/python_graph_dot.egg-info/namespace_packages.txt new file mode 100644 index 0000000..ba98b63 --- /dev/null +++ b/dot/python_graph_dot.egg-info/namespace_packages.txt @@ -0,0 +1 @@ +pygraph diff --git a/dot/python_graph_dot.egg-info/requires.txt b/dot/python_graph_dot.egg-info/requires.txt new file mode 100644 index 0000000..802f36d --- /dev/null +++ b/dot/python_graph_dot.egg-info/requires.txt @@ -0,0 +1,2 @@ +python-graph-core==1.8.2 +pydot diff --git a/dot/python_graph_dot.egg-info/top_level.txt b/dot/python_graph_dot.egg-info/top_level.txt new file mode 100644 index 0000000..07af956 --- /dev/null +++ b/dot/python_graph_dot.egg-info/top_level.txt @@ -0,0 +1,2 @@ +pygraph +pygraph\readwrite diff --git a/tests/testlib.py b/tests/testlib.py index 66f734f..d7fa5c8 100644 --- a/tests/testlib.py +++ b/tests/testlib.py @@ -49,8 +49,8 @@ # Init try: if (argv[0] != 'testrunner.py'): - print - print ("Random seed: %s" % random_seed) + print() + print(("Random seed: %s" % random_seed)) except: pass diff --git a/tests/testlib.py.bak b/tests/testlib.py.bak new file mode 100644 index 0000000..66f734f --- /dev/null +++ b/tests/testlib.py.bak @@ -0,0 +1,72 @@ +# Copyright (c) 2007-2009 Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Helper functions for unit-tests. +""" + + +# Imports +from pygraph.algorithms.generators import generate, generate_hypergraph +from random import seed +from time import time +from sys import argv + +# Configuration +random_seed = int(time()) +num_nodes = { 'small': 10, + 'medium': 25, + 'sparse': 40 + } +num_edges = { 'small': 18, + 'medium': 120, + 'sparse': 200 + } +sizes = ['small', 'medium', 'sparse'] +use_size = 'medium' + +# Init +try: + if (argv[0] != 'testrunner.py'): + print + print ("Random seed: %s" % random_seed) +except: + pass + + +def new_graph(wt_range=(1, 1)): + seed(random_seed) + return generate(num_nodes[use_size], num_edges[use_size], directed=False, weight_range=wt_range) + +def new_digraph(wt_range=(1, 1)): + seed(random_seed) + return generate(num_nodes[use_size], num_edges[use_size], directed=True, weight_range=wt_range) + +def new_hypergraph(): + seed(random_seed) + return generate_hypergraph(num_nodes[use_size], num_edges[use_size]) + +def new_uniform_hypergraph(_r): + seed(random_seed) + return generate_hypergraph(num_nodes[use_size], num_edges[use_size], r = _r) diff --git a/tests/testrunner.py b/tests/testrunner.py index f2a40a4..8307653 100644 --- a/tests/testrunner.py +++ b/tests/testrunner.py @@ -26,7 +26,7 @@ sys.path.append('..') import pygraph import unittest -import testlib +from . import testlib import logging from os import listdir @@ -41,7 +41,7 @@ def test_modules(): def run_tests(): for each_size in testlib.sizes: - print ("Testing with %s graphs" % each_size) + print(("Testing with %s graphs" % each_size)) suite = unittest.TestSuite() testlib.use_size = each_size @@ -66,7 +66,7 @@ def main(): print ("") print ("--------------------------------------------------") print ("python-graph unit-tests") - print ("Random seed: %s" % testlib.random_seed) + print(("Random seed: %s" % testlib.random_seed)) print ("--------------------------------------------------") print ("") run_tests() diff --git a/tests/testrunner.py.bak b/tests/testrunner.py.bak new file mode 100644 index 0000000..f2a40a4 --- /dev/null +++ b/tests/testrunner.py.bak @@ -0,0 +1,76 @@ +# Copyright (c) 2007-2009 Pedro Matiello +# Salim Fadhley +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +import sys +sys.path.append('..') +import pygraph +import unittest +import testlib +import logging +from os import listdir + +log = logging.getLogger(__name__) + +def test_modules(): + modlist = [] + for each in listdir('.'): + if (each[0:9] == "unittests" and each[-3:] == ".py"): + modlist.append(each[0:-3]) + return modlist + +def run_tests(): + for each_size in testlib.sizes: + print ("Testing with %s graphs" % each_size) + + suite = unittest.TestSuite() + testlib.use_size = each_size + + for each_module in test_modules(): + try: + suite.addTests(unittest.TestLoader().loadTestsFromName(each_module)) + except ImportError as ie: + log.exception(ie) + continue + + tr = unittest.TextTestRunner(verbosity=2) + result = tr.run(suite) + del suite + +def main(): + try: + rseed = sys.argv[1] + testlib.random_seed = int(rseed) + except: + pass + print ("") + print ("--------------------------------------------------") + print ("python-graph unit-tests") + print ("Random seed: %s" % testlib.random_seed) + print ("--------------------------------------------------") + print ("") + run_tests() + +if __name__ == "__main__": + main() + \ No newline at end of file diff --git a/tests/unittests-accessibility.py b/tests/unittests-accessibility.py index 8af5d6d..ecb88eb 100644 --- a/tests/unittests-accessibility.py +++ b/tests/unittests-accessibility.py @@ -38,7 +38,7 @@ from pygraph.classes.hypergraph import hypergraph from copy import deepcopy from sys import getrecursionlimit -import testlib +from . import testlib def number_of_connected_components(cc): n = 0 @@ -86,7 +86,7 @@ def test_accessibility_in_digraph(self): def test_accessibility_on_very_deep_graph(self): gr = pygraph.classes.graph.graph() - gr.add_nodes(range(0,2001)) + gr.add_nodes(list(range(0,2001))) for i in range(0,2000): gr.add_edge((i,i+1)) recursionlimit = getrecursionlimit() @@ -110,7 +110,7 @@ def test_mutual_accessibility_in_graph(self): def test_mutual_accessibility_on_very_deep_graph(self): gr = pygraph.classes.graph.graph() - gr.add_nodes(range(0,5001)) + gr.add_nodes(list(range(0,5001))) for i in range(0,5000): gr.add_edge((i,i+1)) recursionlimit = getrecursionlimit() @@ -149,7 +149,7 @@ def test_connected_components_in_graph(self): def test_connected_components_on_very_deep_graph(self): gr = pygraph.classes.graph.graph() - gr.add_nodes(range(0,5001)) + gr.add_nodes(list(range(0,5001))) for i in range(0,5000): gr.add_edge((i,i+1)) recursionlimit = getrecursionlimit() @@ -174,7 +174,7 @@ def test_cut_nodes_in_graph(self): def test_cut_nodes_on_very_deep_graph(self): gr = pygraph.classes.graph.graph() - gr.add_nodes(range(0,5001)) + gr.add_nodes(list(range(0,5001))) for i in range(0,5000): gr.add_edge((i,i+1)) recursionlimit = getrecursionlimit() @@ -199,7 +199,7 @@ def test_cut_edges_in_graph(self): def test_cut_edges_on_very_deep_graph(self): gr = pygraph.classes.graph.graph() - gr.add_nodes(range(0,5001)) + gr.add_nodes(list(range(0,5001))) for i in range(0,5000): gr.add_edge((i,i+1)) recursionlimit = getrecursionlimit() @@ -210,7 +210,7 @@ def test_accessibility_hypergraph(self): gr = hypergraph() # Add some nodes / edges - gr.add_nodes(range(8)) + gr.add_nodes(list(range(8))) gr.add_hyperedges(['a', 'b', 'c']) # Connect the 9 nodes with three size-3 hyperedges @@ -232,7 +232,7 @@ def test_connected_components_hypergraph(self): gr = hypergraph() # Add some nodes / edges - gr.add_nodes(range(9)) + gr.add_nodes(list(range(9))) gr.add_hyperedges(['a', 'b', 'c']) # Connect the 9 nodes with three size-3 hyperedges @@ -251,7 +251,7 @@ def test_connected_components_hypergraph(self): # Do it again with two components and more than one edge for each gr = hypergraph() - gr.add_nodes(range(9)) + gr.add_nodes(list(range(9))) gr.add_hyperedges(['a', 'b', 'c', 'd']) for node_set in [['a',0,1,2], ['b',2,3,4], ['c',5,6,7], ['d',6,7,8]]: @@ -274,7 +274,7 @@ def test_cut_nodes_in_hypergraph(self): gr = hypergraph() # Add some nodes / edges - gr.add_nodes(range(9)) + gr.add_nodes(list(range(9))) gr.add_hyperedges(['a', 'b', 'c']) # Connect the 9 nodes with three size-3 hyperedges @@ -301,7 +301,7 @@ def test_cut_edges_in_hypergraph(self): gr = hypergraph() # Add some nodes / edges - gr.add_nodes(range(9)) + gr.add_nodes(list(range(9))) gr.add_hyperedges(['a1', 'b1', 'c1']) gr.add_hyperedges(['a2', 'b2', 'c2']) diff --git a/tests/unittests-accessibility.py.bak b/tests/unittests-accessibility.py.bak new file mode 100644 index 0000000..8af5d6d --- /dev/null +++ b/tests/unittests-accessibility.py.bak @@ -0,0 +1,327 @@ +# Copyright (c) Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Unittests for graph.algorithms.accessibility +""" + + +import unittest +import pygraph +from pygraph.algorithms.searching import depth_first_search +from pygraph.algorithms.accessibility import accessibility +from pygraph.algorithms.accessibility import mutual_accessibility +from pygraph.algorithms.accessibility import connected_components +from pygraph.algorithms.accessibility import cut_nodes +from pygraph.algorithms.accessibility import cut_edges +from pygraph.classes.hypergraph import hypergraph +from copy import deepcopy +from sys import getrecursionlimit +import testlib + +def number_of_connected_components(cc): + n = 0 + for each in cc: + if cc[each] > n: + n = cc[each] + return n + +class test_accessibility(unittest.TestCase): + + def setUp(self): + pass + + def test_accessibility_in_graph(self): + gr = testlib.new_graph() + gr.add_nodes(['a','b','c']) + gr.add_edge(('a','b')) + gr.add_edge(('a','c')) + + ac = accessibility(gr) + + for n in gr: + for m in gr: + if (m in ac[n]): + assert m in depth_first_search(gr, n)[0] + assert n in depth_first_search(gr, m)[0] + else: + assert m not in depth_first_search(gr, n)[0] + + def test_accessibility_in_digraph(self): + gr = testlib.new_digraph() + gr.add_nodes(['a','b','c']) + gr.add_edge(('a','b')) + gr.add_edge(('a','c')) + + ac = accessibility(gr) + + for n in gr: + for m in gr: + if (m in ac[n]): + assert m in depth_first_search(gr, n)[0] + else: + assert m not in depth_first_search(gr, n)[0] + + + def test_accessibility_on_very_deep_graph(self): + gr = pygraph.classes.graph.graph() + gr.add_nodes(range(0,2001)) + for i in range(0,2000): + gr.add_edge((i,i+1)) + recursionlimit = getrecursionlimit() + accessibility(gr) + assert getrecursionlimit() == recursionlimit + + def test_mutual_accessibility_in_graph(self): + gr = testlib.new_graph() + gr.add_nodes(['a','b','c']) + gr.add_edge(('a','b')) + gr.add_edge(('a','c')) + + ma = mutual_accessibility(gr) + for n in gr: + for m in gr: + if (m in ma[n]): + assert m in depth_first_search(gr, n)[0] + assert n in depth_first_search(gr, m)[0] + else: + assert m not in depth_first_search(gr, n)[0] or n not in depth_first_search(gr, m)[0] + + def test_mutual_accessibility_on_very_deep_graph(self): + gr = pygraph.classes.graph.graph() + gr.add_nodes(range(0,5001)) + for i in range(0,5000): + gr.add_edge((i,i+1)) + recursionlimit = getrecursionlimit() + mutual_accessibility(gr) + assert getrecursionlimit() == recursionlimit + + def test_mutual_accessibility_in_digraph(self): + gr = testlib.new_digraph() + gr.add_nodes(['a','b','c']) + gr.add_edge(('a','b')) + gr.add_edge(('b','a')) + gr.add_edge(('a','c')) + + ma = mutual_accessibility(gr) + for n in gr: + for m in gr: + if (m in ma[n]): + assert m in depth_first_search(gr, n)[0] + assert n in depth_first_search(gr, m)[0] + else: + assert m not in depth_first_search(gr, n)[0] or n not in depth_first_search(gr, m)[0] + + def test_connected_components_in_graph(self): + gr = testlib.new_graph() + gr.add_nodes(['a','b','c']) + gr.add_edge(('a','b')) + + cc = connected_components(gr) + + for n in gr: + for m in gr: + if (cc[n] == cc[m]): + assert m in depth_first_search(gr, n)[0] + else: + assert m not in depth_first_search(gr, n)[0] + + def test_connected_components_on_very_deep_graph(self): + gr = pygraph.classes.graph.graph() + gr.add_nodes(range(0,5001)) + for i in range(0,5000): + gr.add_edge((i,i+1)) + recursionlimit = getrecursionlimit() + connected_components(gr) + assert getrecursionlimit() == recursionlimit + + def test_cut_nodes_in_graph(self): + gr = testlib.new_graph() + gr.add_nodes(['x','y']) + gr.add_edge(('x','y')) + gr.add_edge(('x',0)) + + gr_copy = deepcopy(gr) + + cn = cut_nodes(gr) + + for each in cn: + before = number_of_connected_components(connected_components(gr)) + gr.del_node(each) + number_of_connected_components(connected_components(gr)) > before + gr = gr_copy + + def test_cut_nodes_on_very_deep_graph(self): + gr = pygraph.classes.graph.graph() + gr.add_nodes(range(0,5001)) + for i in range(0,5000): + gr.add_edge((i,i+1)) + recursionlimit = getrecursionlimit() + cut_nodes(gr) + assert getrecursionlimit() == recursionlimit + + def test_cut_edges_in_graph(self): + gr = testlib.new_graph() + gr.add_nodes(['x','y']) + gr.add_edge(('x','y')) + gr.add_edge(('x',0)) + + gr_copy = deepcopy(gr) + + ce = cut_edges(gr) + + for each in ce: + before = number_of_connected_components(connected_components(gr)) + gr.del_edge(each) + number_of_connected_components(connected_components(gr)) > before + gr = gr_copy + + def test_cut_edges_on_very_deep_graph(self): + gr = pygraph.classes.graph.graph() + gr.add_nodes(range(0,5001)) + for i in range(0,5000): + gr.add_edge((i,i+1)) + recursionlimit = getrecursionlimit() + cut_edges(gr) + assert getrecursionlimit() == recursionlimit + + def test_accessibility_hypergraph(self): + gr = hypergraph() + + # Add some nodes / edges + gr.add_nodes(range(8)) + gr.add_hyperedges(['a', 'b', 'c']) + + # Connect the 9 nodes with three size-3 hyperedges + for node_set in [['a',0,1,2], ['b',2,3,4], ['c',5,6,7]]: + for node in node_set[1:]: + gr.link(node, node_set[0]) + + access = accessibility(gr) + + assert 8 == len(access) + + for i in range(5): + assert set(access[i]) == set(range(5)) + + for i in range(5,8): + assert set(access[i]) == set(range(5,8)) + + def test_connected_components_hypergraph(self): + gr = hypergraph() + + # Add some nodes / edges + gr.add_nodes(range(9)) + gr.add_hyperedges(['a', 'b', 'c']) + + # Connect the 9 nodes with three size-3 hyperedges + for node_set in [['a',0,1,2], ['b',3,4,5], ['c',6,7,8]]: + for node in node_set[1:]: + gr.link(node, node_set[0]) + + cc = connected_components(gr) + + assert 3 == len(set(cc.values())) + + assert cc[0] == cc[1] and cc[1] == cc[2] + assert cc[3] == cc[4] and cc[4] == cc[5] + assert cc[6] == cc[7] and cc[7] == cc[8] + + + # Do it again with two components and more than one edge for each + gr = hypergraph() + gr.add_nodes(range(9)) + gr.add_hyperedges(['a', 'b', 'c', 'd']) + + for node_set in [['a',0,1,2], ['b',2,3,4], ['c',5,6,7], ['d',6,7,8]]: + for node in node_set[1:]: + gr.link(node, node_set[0]) + + cc = connected_components(gr) + + assert 2 == len(set(cc.values())) + + for i in [0,1,2,3]: + assert cc[i] == cc[i+1] + + for i in [5,6,7]: + assert cc[i] == cc[i+1] + + assert cc[4] != cc[5] + + def test_cut_nodes_in_hypergraph(self): + gr = hypergraph() + + # Add some nodes / edges + gr.add_nodes(range(9)) + gr.add_hyperedges(['a', 'b', 'c']) + + # Connect the 9 nodes with three size-3 hyperedges + for node_set in [['a',0,1,2], ['b',3,4,5], ['c',6,7,8]]: + for node in node_set[1:]: + gr.link(node, node_set[0]) + + # Connect the groups + gr.add_hyperedges(['l1','l2']) + gr.link(0, 'l1') + gr.link(3, 'l1') + gr.link(5, 'l2') + gr.link(8, 'l2') + + cn = cut_nodes(gr); + + assert 0 in cn + assert 3 in cn + assert 5 in cn + assert 8 in cn + assert len(cn) == 4 + + def test_cut_edges_in_hypergraph(self): + gr = hypergraph() + + # Add some nodes / edges + gr.add_nodes(range(9)) + gr.add_hyperedges(['a1', 'b1', 'c1']) + gr.add_hyperedges(['a2', 'b2', 'c2']) + + # Connect the 9 nodes with three size-3 hyperedges + for node_set in [['a1',0,1,2], ['b1',3,4,5], ['c1',6,7,8], ['a2',0,1,2], ['b2',3,4,5], ['c2',6,7,8]]: + for node in node_set[1:]: + gr.link(node, node_set[0]) + + # Connect the groups + gr.add_hyperedges(['l1','l2']) + gr.link(0, 'l1') + gr.link(3, 'l1') + gr.link(5, 'l2') + gr.link(8, 'l2') + + ce = cut_edges(gr) + + assert 'l1' in ce + assert 'l2' in ce + assert len(ce) == 2 + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unittests-cycles.py b/tests/unittests-cycles.py index 9c7eea0..75ea725 100644 --- a/tests/unittests-cycles.py +++ b/tests/unittests-cycles.py @@ -34,7 +34,7 @@ from pygraph.classes.digraph import digraph from pygraph.classes.graph import graph from sys import getrecursionlimit -import testlib +from . import testlib def verify_cycle(graph, cycle): @@ -84,7 +84,7 @@ def test_find_small_cycle_on_digraph(self): def test_find_cycle_on_very_deep_graph(self): gr = pygraph.classes.graph.graph() - gr.add_nodes(range(0,20001)) + gr.add_nodes(list(range(0,20001))) for i in range(0,20000): gr.add_edge((i,i+1)) recursionlimit = getrecursionlimit() diff --git a/tests/unittests-cycles.py.bak b/tests/unittests-cycles.py.bak new file mode 100644 index 0000000..9c7eea0 --- /dev/null +++ b/tests/unittests-cycles.py.bak @@ -0,0 +1,108 @@ +# Copyright (c) Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Unittests for graph.algorithms.cycles +""" + + +import unittest +import pygraph +from pygraph.algorithms.cycles import find_cycle +from pygraph.algorithms.searching import depth_first_search +from pygraph.classes.digraph import digraph +from pygraph.classes.graph import graph +from sys import getrecursionlimit +import testlib + + +def verify_cycle(graph, cycle): + for i in range(len(cycle)): + assert graph.has_edge((cycle[i],cycle[(i+1)%len(cycle)])) + +class test_find_cycle(unittest.TestCase): + + # Graph + + def test_find_cycle_on_graph(self): + gr = testlib.new_graph() + cycle = find_cycle(gr) + verify_cycle(gr, cycle) + + def test_find_cycle_on_graph_withot_cycles(self): + gr = testlib.new_graph() + st, pre, post = depth_first_search(gr) + gr = graph() + gr.add_spanning_tree(st) + assert find_cycle(gr) == [] + + # Digraph + + def test_find_cycle_on_digraph(self): + gr = testlib.new_digraph() + cycle = find_cycle(gr) + verify_cycle(gr, cycle) + + def test_find_cycle_on_digraph_without_cycles(self): + gr = testlib.new_digraph() + st, pre, post = depth_first_search(gr) + gr = digraph() + gr.add_spanning_tree(st) + assert find_cycle(gr) == [] + + def test_find_small_cycle_on_digraph(self): + gr = digraph() + gr.add_nodes([1, 2, 3, 4, 5]) + gr.add_edge((1, 2)) + gr.add_edge((2, 3)) + gr.add_edge((2, 4)) + gr.add_edge((4, 5)) + gr.add_edge((2, 1)) + # Cycle: 1-2 + assert find_cycle(gr) == [1,2] + + def test_find_cycle_on_very_deep_graph(self): + gr = pygraph.classes.graph.graph() + gr.add_nodes(range(0,20001)) + for i in range(0,20000): + gr.add_edge((i,i+1)) + recursionlimit = getrecursionlimit() + find_cycle(gr) + assert getrecursionlimit() == recursionlimit + + # Regression + + def test_regression1(self): + G = digraph() + G.add_nodes([1, 2, 3, 4, 5]) + G.add_edge((1, 2)) + G.add_edge((2, 3)) + G.add_edge((2, 4)) + G.add_edge((4, 5)) + G.add_edge((3, 5)) + G.add_edge((3, 1)) + assert find_cycle(G) == [1, 2, 3] + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/tests/unittests-heuristics.py b/tests/unittests-heuristics.py index f63c5d3..c34fe72 100644 --- a/tests/unittests-heuristics.py +++ b/tests/unittests-heuristics.py @@ -35,7 +35,7 @@ from pygraph.algorithms.heuristics.chow import chow from pygraph.classes import exceptions -from test_data import nations_of_the_world +from .test_data import nations_of_the_world class test_chow(unittest.TestCase): diff --git a/tests/unittests-heuristics.py.bak b/tests/unittests-heuristics.py.bak new file mode 100644 index 0000000..f63c5d3 --- /dev/null +++ b/tests/unittests-heuristics.py.bak @@ -0,0 +1,94 @@ +# Copyright (c) Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Unittests for graph.algorithms.heuristics +""" + + +import unittest +import pygraph +from pygraph.classes.graph import graph +from pygraph.classes.digraph import digraph +from pygraph.algorithms.heuristics.euclidean import euclidean +from pygraph.algorithms.heuristics.chow import chow +from pygraph.classes import exceptions + +from test_data import nations_of_the_world + + +class test_chow(unittest.TestCase): + + def setUp(self): + self.G = graph() + nations_of_the_world(self.G) + + def test_basic(self): + """ + Test some very basic functionality + """ + englands_neighbors = self.G.neighbors("England") + assert set(['Wales', 'Scotland', 'France', 'Ireland']) == set( englands_neighbors ) + + def test_chow(self): + heuristic = chow( "Wales", "North Korea", "Russia" ) + heuristic.optimize(self.G) + result = pygraph.algorithms.minmax.heuristic_search( self.G, "England", "India", heuristic ) + + def test_chow_unreachable(self): + heuristic = chow( "Wales", "North Korea", "Russia" ) + self.G.add_node("Sealand") + self.G.add_edge(("England", "Sealand")) + heuristic.optimize(self.G) + self.G.del_edge(("England", "Sealand")) + + try: + result = pygraph.algorithms.minmax.heuristic_search( self.G, "England", "Sealand" , heuristic ) + except exceptions.NodeUnreachable as _: + return + + assert False, "This test should raise an unreachable error." + + +class test_euclidean(unittest.TestCase): + + def setUp(self): + self.G = pygraph.classes.graph.graph() + self.G.add_node('A', [('position',[0,0])]) + self.G.add_node('B', [('position',[2,0])]) + self.G.add_node('C', [('position',[2,3])]) + self.G.add_node('D', [('position',[1,2])]) + self.G.add_edge(('A', 'B'), wt=4) + self.G.add_edge(('A', 'D'), wt=5) + self.G.add_edge(('B', 'C'), wt=9) + self.G.add_edge(('D', 'C'), wt=2) + + def test_euclidean(self): + heuristic = euclidean() + heuristic.optimize(self.G) + result = pygraph.algorithms.minmax.heuristic_search(self.G, 'A', 'C', heuristic ) + assert result == ['A', 'D', 'C'] + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/tests/unittests-hypergraph.py b/tests/unittests-hypergraph.py index 0d82a0c..6195f52 100644 --- a/tests/unittests-hypergraph.py +++ b/tests/unittests-hypergraph.py @@ -32,7 +32,7 @@ from pygraph.algorithms.generators import generate from pygraph.classes.exceptions import AdditionError from pygraph.classes.hypergraph import hypergraph -import testlib +from . import testlib from copy import copy, deepcopy class test_hypergraph(unittest.TestCase): diff --git a/tests/unittests-hypergraph.py.bak b/tests/unittests-hypergraph.py.bak new file mode 100644 index 0000000..0d82a0c --- /dev/null +++ b/tests/unittests-hypergraph.py.bak @@ -0,0 +1,339 @@ +# Copyright (c) Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Unittests for graph.classes.hypergraph +""" + + +import unittest +import pygraph +from pygraph.algorithms.generators import generate +from pygraph.classes.exceptions import AdditionError +from pygraph.classes.hypergraph import hypergraph +import testlib +from copy import copy, deepcopy + +class test_hypergraph(unittest.TestCase): + + # Add/Remove nodes and edges + + def test_raise_exception_on_duplicate_node_addition(self): + gr = hypergraph() + gr.add_node('a_node') + try: + gr.add_node('a_node') + except AdditionError: + pass + else: + fail() + + def test_raise_exception_on_duplicate_edge_link(self): + gr = hypergraph() + gr.add_node('a node') + gr.add_hyperedge('an edge') + gr.link('a node', 'an edge') + try: + gr.link('a node', 'an edge') + except AdditionError: + pass + else: + fail() + + def test_raise_exception_on_non_existing_link_removal(self): + gr = hypergraph() + gr.add_node(0) + gr.add_hyperedge(1) + try: + gr.unlink(0, 1) + except ValueError: + pass + else: + fail() + + def test_raise_exception_when_edge_added_from_non_existing_node(self): + gr = hypergraph() + gr.add_nodes([0,1]) + try: + gr.link(3,0) + except KeyError: + pass + else: + fail() + assert gr.neighbors(0) == [] + + def test_raise_exception_when_edge_added_to_non_existing_node(self): + gr = hypergraph() + gr.add_nodes([0,1]) + try: + gr.link(0,3) + except KeyError: + pass + else: + fail() + assert gr.neighbors(0) == [] + + def test_remove_node(self): + gr = testlib.new_hypergraph() + gr.del_node(0) + self.assertTrue(0 not in gr.nodes()) + for e in gr.hyperedges(): + for n in gr.links(e): + self.assertTrue(n in gr.nodes()) + + def test_remove_edge(self): + h = hypergraph() + h.add_nodes([1,2]) + h.add_edges(['a', 'b']) + + h.link(1,'a') + h.link(2,'a') + h.link(1,'b') + h.link(2,'b') + + # Delete an edge + h.del_edge('a') + + assert 1 == len(h.hyperedges()) + + gr = testlib.new_hypergraph() + edge_no = len(gr.nodes())+1 + gr.del_hyperedge(edge_no) + self.assertTrue(edge_no not in gr.hyperedges()) + + def test_remove_link_from_node_to_same_node(self): + gr = hypergraph() + gr.add_node(0) + gr.add_hyperedge(0) + gr.link(0, 0) + gr.unlink(0, 0) + + def test_remove_node_with_edge_to_itself(self): + gr = hypergraph() + gr.add_node(0) + gr.add_hyperedge(0) + gr.link(0, 0) + gr.del_node(0) + + def test_check_add_node_s(self): + gr = hypergraph() + nodes = [1,2,3] + gr.add_nodes(nodes) + gr.add_node(0) + + for n in [0] + nodes: + assert n in gr + assert gr.has_node(n) + + def test_rank(self): + # Uniform case + gr = testlib.new_uniform_hypergraph(3) + assert 3 == gr.rank() + + # Non-uniform case + gr = testlib.new_hypergraph() + num = max([len(gr.links(e)) for e in gr.hyperedges()]) + assert num == gr.rank() + + def test_repr(self): + """ + Validate the repr string + """ + gr = testlib.new_hypergraph() + gr_repr = repr(gr) + assert isinstance(gr_repr, str ) + assert gr.__class__.__name__ in gr_repr + + def test_order_len_equivlance(self): + """ + Verify the behavior of G.order() + """ + gr = testlib.new_hypergraph() + assert len(gr) == gr.order() + assert gr.order() == len( gr.node_links ) + + def test_hypergraph_equality_nodes(self): + """ + Hyperaph equality test. This one checks node equality. + """ + gr = hypergraph() + gr.add_nodes([0,1,2,3,4,5]) + + gr2 = deepcopy(gr) + + gr3 = deepcopy(gr) + gr3.del_node(5) + + gr4 = deepcopy(gr) + gr4.add_node(6) + gr4.del_node(0) + + assert gr == gr2 + assert gr2 == gr + assert gr != gr3 + assert gr3 != gr + assert gr != gr4 + assert gr4 != gr + + def test_hypergraph_equality_edges(self): + """ + Hyperaph equality test. This one checks edge equality. + """ + gr = hypergraph() + gr.add_nodes([0,1,2,3]) + gr.add_edge('e1') + gr.add_edge('e2') + gr.link(0, 'e1') + gr.link(1, 'e1') + gr.link(1, 'e2') + gr.link(2, 'e2') + + gr2 = deepcopy(gr) + + gr3 = deepcopy(gr) + gr3.del_edge('e2') + + gr4 = deepcopy(gr) + gr4.unlink(1, 'e2') + + assert gr == gr2 + assert gr2 == gr + assert gr != gr3 + assert gr3 != gr + assert gr != gr4 + assert gr4 != gr + + def test_hypergraph_equality_labels(self): + """ + Hyperaph equality test. This one checks edge equality. + """ + gr = hypergraph() + gr.add_nodes([0,1,2,3]) + gr.add_edge('e1') + gr.add_edge('e2') + gr.add_edge('e3') + gr.set_edge_label('e1', 'l1') + gr.set_edge_label('e2', 'l2') + + gr2 = deepcopy(gr) + + gr3 = deepcopy(gr) + gr3.set_edge_label('e3', 'l3') + + gr4 = deepcopy(gr) + gr4.set_edge_label('e1', 'lx') + + gr5 = deepcopy(gr) + gr5.del_edge('e1') + gr5.add_edge('e1') + + assert gr == gr2 + assert gr2 == gr + assert gr != gr3 + assert gr3 != gr + assert gr != gr4 + assert gr4 != gr + assert gr != gr5 + assert gr5 != gr + + def test_hypergraph_equality_attributes(self): + """ + Hyperaph equality test. This one checks edge equality. + """ + gr = hypergraph() + gr.add_nodes([0,1]) + gr.add_edge('e1') + gr.add_edge('e2') + gr.add_node_attribute(0, ('a',0)) + gr.add_edge_attribute('e1', ('b',1)) + + gr2 = deepcopy(gr) + + gr3 = deepcopy(gr) + gr3.add_node_attribute(0, ('x','y')) + + gr4 = deepcopy(gr) + gr4.add_edge_attribute('e1', ('u','v')) + + gr5 = deepcopy(gr) + gr5.del_edge('e1') + gr5.add_edge('e1') + + gr6 = deepcopy(gr) + gr6.del_node(0) + gr6.add_node(0) + + assert gr == gr2 + assert gr2 == gr + assert gr != gr3 + assert gr3 != gr + assert gr != gr4 + assert gr4 != gr + assert gr != gr5 + assert gr5 != gr + assert gr != gr6 + assert gr6 != gr + + def test_hypergraph_equality_weight(self): + """ + Hyperaph equality test. This one checks edge equality. + """ + gr = hypergraph() + gr.add_nodes([0,1,2,3]) + gr.add_edge('e1') + gr.add_edge('e2') + gr.add_edge('e3') + gr.set_edge_weight('e1', 2) + + gr2 = deepcopy(gr) + + gr3 = deepcopy(gr) + gr3.set_edge_weight('e3', 2) + + gr4 = deepcopy(gr) + gr4.set_edge_weight('e1', 1) + + assert gr == gr2 + assert gr2 == gr + assert gr != gr3 + assert gr3 != gr + assert gr != gr4 + assert gr4 != gr + + def test_hypergraph_link_unlink_link(self): + """ + Hypergraph link-unlink-link test. It makes sure that unlink cleans + everything properly. No AdditionError should occur. + """ + h = hypergraph() + h.add_nodes([1,2]) + h.add_edges(['e1']) + + h.link(1, 'e1') + h.unlink(1, 'e1') + h.link(1,'e1') + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unittests-minmax.py b/tests/unittests-minmax.py index 8442af1..a887848 100644 --- a/tests/unittests-minmax.py +++ b/tests/unittests-minmax.py @@ -28,7 +28,7 @@ """ import unittest -import testlib +from . import testlib from pygraph.classes.graph import graph from pygraph.classes.digraph import digraph diff --git a/tests/unittests-minmax.py.bak b/tests/unittests-minmax.py.bak new file mode 100644 index 0000000..8442af1 --- /dev/null +++ b/tests/unittests-minmax.py.bak @@ -0,0 +1,230 @@ +# Copyright (c) Pedro Matiello +# Johannes Reinhardt +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Unittests for graph.algorithms.searching +""" + +import unittest +import testlib + +from pygraph.classes.graph import graph +from pygraph.classes.digraph import digraph + +from pygraph.algorithms.searching import depth_first_search +from pygraph.algorithms.minmax import minimal_spanning_tree,\ +shortest_path, heuristic_search, shortest_path_bellman_ford, maximum_flow, cut_tree +from pygraph.algorithms.heuristics.chow import chow +from pygraph.classes.exceptions import NegativeWeightCycleError + +from copy import deepcopy + +# helpers + +def tree_weight(gr, tree): + sum = 0; + for each in tree: + sum = sum + gr.edge_weight((each, tree[each])) + return sum + +def add_spanning_tree(gr, st): + # A very tolerant implementation. + gr.add_nodes(list(st.keys())) + for each in st: + if ((st[each] is not None) and (not gr.has_edge((st[each], each)))): # Accepts invalid STs + gr.add_edge((st[each], each)) + +def bf_path(gr, root, target, remainder): + if (remainder <= 0): return True + if (root == target): return False + for each in gr[root]: + if (not bf_path(gr, each, target, remainder - gr.edge_weight((root, each)))): + return False + return True + +def generate_fixture_digraph(): + #helper for bellman-ford algorithm + G = digraph() + G.add_nodes([1,2,3,4,5]) + G.add_edge((1,2), 6) + G.add_edge((1,4), 7) + G.add_edge((2,4), 8) + G.add_edge((3,2), -2) + G.add_edge((4,3), -3) + G.add_edge((2,5), -4) + G.add_edge((4,5), 9) + G.add_edge((5,1), 2) + G.add_edge((5,3), 7) + return G + +def generate_fixture_digraph_neg_weight_cycle(): + #graph with a neg. weight cycle + G = generate_fixture_digraph() + G.del_edge((2,4)) + G.add_edge((2,4), 2)#changed + + G.add_nodes([100,200]) #unconnected part + G.add_edge((100,200),2) + return G + +def generate_fixture_digraph_unconnected(): + G = generate_fixture_digraph() + G.add_nodes([100,200]) + G.add_edge((100,200),2) + return G + +# minimal spanning tree tests + +class test_minimal_spanning_tree(unittest.TestCase): + + def test_minimal_spanning_tree_on_graph(self): + gr = testlib.new_graph(wt_range=(1,10)) + mst = minimal_spanning_tree(gr, root=0) + wt = tree_weight(gr, mst) + len_dfs = len(depth_first_search(gr, root=0)[0]) + for each in mst: + if (mst[each] != None): + mst_copy = deepcopy(mst) + del(mst_copy[each]) + for other in gr[each]: + mst_copy[each] = other + if (tree_weight(gr, mst_copy) < wt): + gr2 = graph() + add_spanning_tree(gr2, mst_copy) + assert len(depth_first_search(gr2, root=0)[0]) < len_dfs + + +# shortest path tests + +class test_shortest_path(unittest.TestCase): + + def test_shortest_path_on_graph(self): + gr = testlib.new_graph(wt_range=(1,10)) + st, dist = shortest_path(gr, 0) + for each in gr: + if (each in dist): + assert bf_path(gr, 0, each, dist[each]) + + def test_shortest_path_on_digraph(self): + # Test stub: not checking for correctness yet + gr = testlib.new_digraph(wt_range=(1,10)) + st, dist = shortest_path(gr, 0) + for each in gr: + if (each in dist): + assert bf_path(gr, 0, each, dist[each]) + + def test_shortest_path_should_fail_if_source_does_not_exist(self): + gr = testlib.new_graph() + try: + shortest_path(gr, 'invalid') + assert False + except (KeyError): + pass + +class test_shortest_path_bellman_ford(unittest.TestCase): + + def test_shortest_path_BF_on_empty_digraph(self): + pre, dist = shortest_path_bellman_ford(digraph(), 1) + assert pre == {1:None} and dist == {1:0} + + def test_shortest_path_BF_on_digraph(self): + #testing correctness on the fixture + gr = generate_fixture_digraph() + pre,dist = shortest_path_bellman_ford(gr, 1) + assert pre == {1: None, 2: 3, 3: 4, 4: 1, 5: 2} \ + and dist == {1: 0, 2: 2, 3: 4, 4: 7, 5: -2} + + def test_shortest_path_BF_on_digraph_with_negwcycle(self): + #test negative weight cycle detection + gr = generate_fixture_digraph_neg_weight_cycle() + self.assertRaises(NegativeWeightCycleError, + shortest_path_bellman_ford, gr, 1) + + def test_shortest_path_BF_on_unconnected_graph(self): + gr = generate_fixture_digraph_unconnected() + pre,dist = shortest_path_bellman_ford(gr, 100) + assert pre == {200: 100, 100: None} and \ + dist == {200: 2, 100: 0} + +class test_maxflow_mincut(unittest.TestCase): + + def test_trivial_maxflow(self): + gr = digraph() + gr.add_nodes([0,1,2,3]) + gr.add_edge((0,1), wt=5) + gr.add_edge((1,2), wt=3) + gr.add_edge((2,3), wt=7) + flows, cuts = maximum_flow(gr, 0, 3) + assert flows[(0,1)] == 3 + assert flows[(1,2)] == 3 + assert flows[(2,3)] == 3 + + def test_random_maxflow(self): + gr = testlib.new_digraph(wt_range=(1,20)) + flows, cuts = maximum_flow(gr, 0, 1) + # Sanity test + for each in flows: + assert gr.edge_weight(each) >= flows[each] + +# Tests for heuristic search are not necessary here as it's tested +# in unittests-heuristics.py + +class test_cut_tree(unittest.TestCase): + + def test_cut_tree(self): + #set up the graph (see example on wikipedia page for Gomory-Hu tree) + gr = graph() + gr.add_nodes([0,1,2,3,4,5]) + gr.add_edge((0,1), wt=1) + gr.add_edge((0,2), wt=7) + gr.add_edge((1,3), wt=3) + gr.add_edge((1,2), wt=1) + gr.add_edge((1,4), wt=2) + gr.add_edge((2,4), wt=4) + gr.add_edge((3,4), wt=1) + gr.add_edge((3,5), wt=6) + gr.add_edge((4,5), wt=2) + + ct = cut_tree(gr) + + #check ct + assert ct[(2,0)] == 8 + assert ct[(4,2)] == 6 + assert ct[(1,4)] == 7 + assert ct[(3,1)] == 6 + assert ct[(5,3)] == 8 + + def test_cut_tree_with_empty_graph(self): + gr = graph() + ct = cut_tree(gr) + assert ct == {} + + def test_cut_tree_with_random_graph(self): + gr = testlib.new_graph() + ct = cut_tree(gr) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unittests-pagerank.py b/tests/unittests-pagerank.py index 8d68318..41c150e 100644 --- a/tests/unittests-pagerank.py +++ b/tests/unittests-pagerank.py @@ -30,7 +30,7 @@ import unittest from pygraph.classes.digraph import digraph from pygraph.algorithms.pagerank import pagerank -import testlib +from . import testlib class test_pagerank(unittest.TestCase): diff --git a/tests/unittests-pagerank.py.bak b/tests/unittests-pagerank.py.bak new file mode 100644 index 0000000..8d68318 --- /dev/null +++ b/tests/unittests-pagerank.py.bak @@ -0,0 +1,103 @@ +# Copyright (c) 2010 Pedro Matiello +# Juarez Bochi +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Unittests for pygraph.algorithms.pagerank +""" + +import unittest +from pygraph.classes.digraph import digraph +from pygraph.algorithms.pagerank import pagerank +import testlib + +class test_pagerank(unittest.TestCase): + + # pagerank algorithm + + def test_pagerank_empty(self): + #Test if an empty dict is returned for an empty graph + G = digraph() + self.assertEqual(pagerank(G), {}) + + def test_pagerank_cycle(self): + #Test if all nodes in a cycle graph have the same value + G = digraph() + G.add_nodes([1, 2, 3, 4, 5]) + G.add_edge((1, 2)) + G.add_edge((2, 3)) + G.add_edge((3, 4)) + G.add_edge((4, 5)) + G.add_edge((5, 1)) + self.assertEqual(pagerank(G), {1: 0.2, 2: 0.2, 3: 0.2, 4: 0.2, 5: 0.2}) + + def test_pagerank(self): + #Test example from wikipedia: http://en.wikipedia.org/wiki/File:Linkstruct3.svg + G = digraph() + G.add_nodes([1, 2, 3, 4, 5, 6, 7]) + G.add_edge((1, 2)) + G.add_edge((1, 3)) + G.add_edge((1, 4)) + G.add_edge((1, 5)) + G.add_edge((1, 7)) + G.add_edge((2, 1)) + G.add_edge((3, 1)) + G.add_edge((3, 2)) + G.add_edge((4, 2)) + G.add_edge((4, 3)) + G.add_edge((4, 5)) + G.add_edge((5, 1)) + G.add_edge((5, 3)) + G.add_edge((5, 4)) + G.add_edge((5, 6)) + G.add_edge((6, 1)) + G.add_edge((6, 5)) + G.add_edge((7, 5)) + expected_pagerank = { + 1: 0.280, + 2: 0.159, + 3: 0.139, + 4: 0.108, + 5: 0.184, + 6: 0.061, + 7: 0.069, + } + pr = pagerank(G) + for k in pr: + self.assertAlmostEqual(pr[k], expected_pagerank[k], places=3) + + def test_pagerank_random(self): + G = testlib.new_digraph() + md = 0.00001 + df = 0.85 + pr = pagerank(G, damping_factor=df, min_delta=md) + min_value = (1.0-df)/len(G) + for node in G: + expected = min_value + for each in G.incidents(node): + expected += (df * pr[each] / len(G.neighbors(each))) + assert abs(pr[node] - expected) < md + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unittests-readwrite.py b/tests/unittests-readwrite.py index 59e1fd7..1107315 100644 --- a/tests/unittests-readwrite.py +++ b/tests/unittests-readwrite.py @@ -30,7 +30,7 @@ import unittest import pygraph from pygraph.readwrite import dot, markup -import testlib +from . import testlib def graph_equality(gr1, gr2): for each in gr1.nodes(): diff --git a/tests/unittests-readwrite.py.bak b/tests/unittests-readwrite.py.bak new file mode 100644 index 0000000..59e1fd7 --- /dev/null +++ b/tests/unittests-readwrite.py.bak @@ -0,0 +1,117 @@ +# Copyright (c) Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Unittests for graph.algorithms.readwrite +""" + + +import unittest +import pygraph +from pygraph.readwrite import dot, markup +import testlib + +def graph_equality(gr1, gr2): + for each in gr1.nodes(): + assert each in gr2.nodes() + for each in gr2.nodes(): + assert each in gr1.nodes() + for each in gr1.edges(): + assert each in gr2.edges() + for each in gr2.edges(): + assert each in gr1.edges() + +class test_readwrite_dot(unittest.TestCase): + + def test_dot_for_graph(self): + gr = testlib.new_graph() + dotstr = dot.write(gr) + gr1 = dot.read(dotstr) + dotstr = dot.write(gr1) + gr2 = dot.read(dotstr) + graph_equality(gr1, gr2) + assert len(gr.nodes()) == len(gr1.nodes()) + assert len(gr.edges()) == len(gr1.edges()) + + def test_dot_for_digraph(self): + gr = testlib.new_digraph() + dotstr = dot.write(gr) + gr1 = dot.read(dotstr) + dotstr = dot.write(gr1) + gr2 = dot.read(dotstr) + graph_equality(gr1, gr2) + assert len(gr.nodes()) == len(gr1.nodes()) + assert len(gr.edges()) == len(gr1.edges()) + + def test_dot_for_hypergraph(self): + gr = testlib.new_hypergraph() + dotstr = dot.write(gr) + gr1 = dot.read_hypergraph(dotstr) + dotstr = dot.write(gr1) + gr2 = dot.read_hypergraph(dotstr) + graph_equality(gr1, gr2) + + def test_output_names_in_dot(self): + gr1 = testlib.new_graph() + gr1.name = "Some name 1" + gr2 = testlib.new_digraph() + gr2.name = "Some name 2" + gr3 = testlib.new_hypergraph() + gr3.name = "Some name 3" + assert "Some name 1" in dot.write(gr1) + assert "Some name 2" in dot.write(gr2) + assert "Some name 3" in dot.write(gr3) + +class test_readwrite_markup(unittest.TestCase): + + def test_xml_for_graph(self): + gr = testlib.new_graph() + dotstr = markup.write(gr) + gr1 = markup.read(dotstr) + dotstr = markup.write(gr1) + gr2 = markup.read(dotstr) + graph_equality(gr1, gr2) + assert len(gr.nodes()) == len(gr1.nodes()) + assert len(gr.edges()) == len(gr1.edges()) + + def test_xml_digraph(self): + gr = testlib.new_digraph() + dotstr = markup.write(gr) + gr1 = markup.read(dotstr) + dotstr = markup.write(gr1) + gr2 = markup.read(dotstr) + graph_equality(gr1, gr2) + assert len(gr.nodes()) == len(gr1.nodes()) + assert len(gr.edges()) == len(gr1.edges()) + + def test_xml_hypergraph(self): + gr = testlib.new_hypergraph() + dotstr = markup.write(gr) + gr1 = markup.read(dotstr) + dotstr = markup.write(gr1) + gr2 = markup.read(dotstr) + graph_equality(gr1, gr2) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unittests-searching.py b/tests/unittests-searching.py index fbb5b2e..a6e53e3 100644 --- a/tests/unittests-searching.py +++ b/tests/unittests-searching.py @@ -33,7 +33,7 @@ import pygraph.classes from pygraph.algorithms.searching import depth_first_search, breadth_first_search from sys import getrecursionlimit -import testlib +from . import testlib class test_depth_first_search(unittest.TestCase): @@ -74,7 +74,7 @@ def test_dfs_in_digraph(self): def test_dfs_very_deep_graph(self): gr = pygraph.classes.graph.graph() - gr.add_nodes(range(0,20001)) + gr.add_nodes(list(range(0,20001))) for i in range(0,20000): gr.add_edge((i,i+1)) recursionlimit = getrecursionlimit() diff --git a/tests/unittests-searching.py.bak b/tests/unittests-searching.py.bak new file mode 100644 index 0000000..fbb5b2e --- /dev/null +++ b/tests/unittests-searching.py.bak @@ -0,0 +1,118 @@ +# Copyright (c) Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Unittests for graph.algorithms.searching +""" + + +# Imports +import unittest +import pygraph +import pygraph.classes +from pygraph.algorithms.searching import depth_first_search, breadth_first_search +from sys import getrecursionlimit +import testlib + + +class test_depth_first_search(unittest.TestCase): + + def test_dfs_in_empty_graph(self): + gr = pygraph.classes.graph.graph() + st, pre, post = depth_first_search(gr) + assert st == {} + assert pre == [] + assert post == [] + + def test_dfs_in_graph(self): + gr = testlib.new_graph() + st, pre, post = depth_first_search(gr) + for each in gr: + if (st[each] != None): + assert pre.index(each) > pre.index(st[each]) + assert post.index(each) < post.index(st[each]) + for node in st: + assert gr.has_edge((st[node], node)) or st[node] == None + + def test_dfs_in_empty_digraph(self): + gr = pygraph.classes.digraph.digraph() + st, pre, post = depth_first_search(gr) + assert st == {} + assert pre == [] + assert post == [] + + def test_dfs_in_digraph(self): + gr = testlib.new_digraph() + st, pre, post = depth_first_search(gr) + for each in gr: + if (st[each] != None): + assert pre.index(each) > pre.index(st[each]) + assert post.index(each) < post.index(st[each]) + for node in st: + assert gr.has_edge((st[node], node)) or st[node] == None + + def test_dfs_very_deep_graph(self): + gr = pygraph.classes.graph.graph() + gr.add_nodes(range(0,20001)) + for i in range(0,20000): + gr.add_edge((i,i+1)) + recursionlimit = getrecursionlimit() + depth_first_search(gr, 0) + assert getrecursionlimit() == recursionlimit + +class test_breadth_first_search(unittest.TestCase): + + def test_bfs_in_empty_graph(self): + gr = pygraph.classes.graph.graph() + st, lo = breadth_first_search(gr) + assert st == {} + assert lo == [] + + def test_bfs_in_graph(self): + gr = pygraph.classes.graph.graph() + gr = testlib.new_digraph() + st, lo = breadth_first_search(gr) + for each in gr: + if (st[each] != None): + assert lo.index(each) > lo.index(st[each]) + for node in st: + assert gr.has_edge((st[node], node)) or st[node] == None + + def test_bfs_in_empty_digraph(self): + gr = pygraph.classes.digraph.digraph() + st, lo = breadth_first_search(gr) + assert st == {} + assert lo == [] + + def test_bfs_in_digraph(self): + gr = testlib.new_digraph() + st, lo = breadth_first_search(gr) + for each in gr: + if (st[each] != None): + assert lo.index(each) > lo.index(st[each]) + for node in st: + assert gr.has_edge((st[node], node)) or st[node] == None + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/tests/unittests-sorting.py b/tests/unittests-sorting.py index 24bedbc..0038657 100644 --- a/tests/unittests-sorting.py +++ b/tests/unittests-sorting.py @@ -32,7 +32,7 @@ from pygraph.algorithms.sorting import topological_sorting from pygraph.algorithms.searching import depth_first_search from sys import getrecursionlimit -import testlib +from . import testlib class test_topological_sorting(unittest.TestCase): @@ -79,7 +79,7 @@ def is_ordered(node, list): def test_topological_sort_on_very_deep_graph(self): gr = pygraph.classes.graph.graph() - gr.add_nodes(range(0,20001)) + gr.add_nodes(list(range(0,20001))) for i in range(0,20000): gr.add_edge((i,i+1)) recursionlimit = getrecursionlimit() diff --git a/tests/unittests-sorting.py.bak b/tests/unittests-sorting.py.bak new file mode 100644 index 0000000..24bedbc --- /dev/null +++ b/tests/unittests-sorting.py.bak @@ -0,0 +1,90 @@ +# Copyright (c) Pedro Matiello +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +""" +Unittests for graph.algorithms.sorting +""" + + +import unittest +import pygraph.classes +from pygraph.algorithms.sorting import topological_sorting +from pygraph.algorithms.searching import depth_first_search +from sys import getrecursionlimit +import testlib + + +class test_topological_sorting(unittest.TestCase): + + def test_topological_sorting_on_tree(self): + gr = testlib.new_graph() + st, pre, post = depth_first_search(gr) + tree = pygraph.classes.digraph.digraph() + + + for each in st: + if st[each]: + if (each not in tree.nodes()): + tree.add_node(each) + if (st[each] not in tree.nodes()): + tree.add_node(st[each]) + tree.add_edge((st[each], each)) + + ts = topological_sorting(tree) + for each in ts: + if (st[each]): + assert ts.index(each) > ts.index(st[each]) + + def test_topological_sorting_on_digraph(self): + + def is_ordered(node, list): + # Has parent on list + for each in list: + if gr.has_edge((each, node)): + return True + # Has no possible ancestors on list + st, pre, post = depth_first_search(gr, node) + for each in list: + if (each in st): + return False + return True + + gr = testlib.new_digraph() + ts = topological_sorting(gr) + + while (ts): + x = ts.pop() + assert is_ordered(x, ts) + + def test_topological_sort_on_very_deep_graph(self): + gr = pygraph.classes.graph.graph() + gr.add_nodes(range(0,20001)) + for i in range(0,20000): + gr.add_edge((i,i+1)) + recursionlimit = getrecursionlimit() + topological_sorting(gr) + assert getrecursionlimit() == recursionlimit + +if __name__ == "__main__": + unittest.main() \ No newline at end of file