External: add macholib and altgraph needed to relocate Mach-o binaries on Linux (#12909)

This commit is contained in:
Patrick Gartung
2019-09-26 11:48:22 -05:00
committed by GitHub
parent 90236bc9f5
commit 321e956fa9
26 changed files with 5433 additions and 0 deletions

309
lib/spack/external/altgraph/Dot.py vendored Normal file
View File

@@ -0,0 +1,309 @@
'''
altgraph.Dot - Interface to the dot language
============================================
The :py:mod:`~altgraph.Dot` module provides a simple interface to the
file format used in the
`graphviz <http://www.research.att.com/sw/tools/graphviz/>`_
program. The module is intended to offload the most tedious part of the process
(the **dot** file generation) while transparently exposing most of its
features.
To display the graphs or to generate image files the
`graphviz <http://www.research.att.com/sw/tools/graphviz/>`_
package needs to be installed on the system, moreover the :command:`dot` and
:command:`dotty` programs must be accesible in the program path so that they
can be ran from processes spawned within the module.
Example usage
-------------
Here is a typical usage::
from altgraph import Graph, Dot
# create a graph
edges = [ (1,2), (1,3), (3,4), (3,5), (4,5), (5,4) ]
graph = Graph.Graph(edges)
# create a dot representation of the graph
dot = Dot.Dot(graph)
# display the graph
dot.display()
# save the dot representation into the mydot.dot file
dot.save_dot(file_name='mydot.dot')
# save dot file as gif image into the graph.gif file
dot.save_img(file_name='graph', file_type='gif')
Directed graph and non-directed graph
-------------------------------------
Dot class can use for both directed graph and non-directed graph
by passing ``graphtype`` parameter.
Example::
# create directed graph(default)
dot = Dot.Dot(graph, graphtype="digraph")
# create non-directed graph
dot = Dot.Dot(graph, graphtype="graph")
Customizing the output
----------------------
The graph drawing process may be customized by passing
valid :command:`dot` parameters for the nodes and edges. For a list of all
parameters see the `graphviz <http://www.research.att.com/sw/tools/graphviz/>`_
documentation.
Example::
# customizing the way the overall graph is drawn
dot.style(size='10,10', rankdir='RL', page='5, 5' , ranksep=0.75)
# customizing node drawing
dot.node_style(1, label='BASE_NODE',shape='box', color='blue' )
dot.node_style(2, style='filled', fillcolor='red')
# customizing edge drawing
dot.edge_style(1, 2, style='dotted')
dot.edge_style(3, 5, arrowhead='dot', label='binds', labelangle='90')
dot.edge_style(4, 5, arrowsize=2, style='bold')
.. note::
dotty (invoked via :py:func:`~altgraph.Dot.display`) may not be able to
display all graphics styles. To verify the output save it to an image file
and look at it that way.
Valid attributes
----------------
- dot styles, passed via the :py:meth:`Dot.style` method::
rankdir = 'LR' (draws the graph horizontally, left to right)
ranksep = number (rank separation in inches)
- node attributes, passed via the :py:meth:`Dot.node_style` method::
style = 'filled' | 'invisible' | 'diagonals' | 'rounded'
shape = 'box' | 'ellipse' | 'circle' | 'point' | 'triangle'
- edge attributes, passed via the :py:meth:`Dot.edge_style` method::
style = 'dashed' | 'dotted' | 'solid' | 'invis' | 'bold'
arrowhead = 'box' | 'crow' | 'diamond' | 'dot' | 'inv' | 'none'
| 'tee' | 'vee'
weight = number (the larger the number the closer the nodes will be)
- valid `graphviz colors
<http://www.research.att.com/~erg/graphviz/info/colors.html>`_
- for more details on how to control the graph drawing process see the
`graphviz reference
<http://www.research.att.com/sw/tools/graphviz/refs.html>`_.
'''
import os
import warnings
from altgraph import GraphError
class Dot(object):
'''
A class providing a **graphviz** (dot language) representation
allowing a fine grained control over how the graph is being
displayed.
If the :command:`dot` and :command:`dotty` programs are not in the current
system path their location needs to be specified in the contructor.
'''
def __init__(
self, graph=None, nodes=None, edgefn=None, nodevisitor=None,
edgevisitor=None, name="G", dot='dot', dotty='dotty',
neato='neato', graphtype="digraph"):
'''
Initialization.
'''
self.name, self.attr = name, {}
assert graphtype in ['graph', 'digraph']
self.type = graphtype
self.temp_dot = "tmp_dot.dot"
self.temp_neo = "tmp_neo.dot"
self.dot, self.dotty, self.neato = dot, dotty, neato
# self.nodes: node styles
# self.edges: edge styles
self.nodes, self.edges = {}, {}
if graph is not None and nodes is None:
nodes = graph
if graph is not None and edgefn is None:
def edgefn(node, graph=graph):
return graph.out_nbrs(node)
if nodes is None:
nodes = ()
seen = set()
for node in nodes:
if nodevisitor is None:
style = {}
else:
style = nodevisitor(node)
if style is not None:
self.nodes[node] = {}
self.node_style(node, **style)
seen.add(node)
if edgefn is not None:
for head in seen:
for tail in (n for n in edgefn(head) if n in seen):
if edgevisitor is None:
edgestyle = {}
else:
edgestyle = edgevisitor(head, tail)
if edgestyle is not None:
if head not in self.edges:
self.edges[head] = {}
self.edges[head][tail] = {}
self.edge_style(head, tail, **edgestyle)
def style(self, **attr):
'''
Changes the overall style
'''
self.attr = attr
def display(self, mode='dot'):
'''
Displays the current graph via dotty
'''
if mode == 'neato':
self.save_dot(self.temp_neo)
neato_cmd = "%s -o %s %s" % (
self.neato, self.temp_dot, self.temp_neo)
os.system(neato_cmd)
else:
self.save_dot(self.temp_dot)
plot_cmd = "%s %s" % (self.dotty, self.temp_dot)
os.system(plot_cmd)
def node_style(self, node, **kwargs):
'''
Modifies a node style to the dot representation.
'''
if node not in self.edges:
self.edges[node] = {}
self.nodes[node] = kwargs
def all_node_style(self, **kwargs):
'''
Modifies all node styles
'''
for node in self.nodes:
self.node_style(node, **kwargs)
def edge_style(self, head, tail, **kwargs):
'''
Modifies an edge style to the dot representation.
'''
if tail not in self.nodes:
raise GraphError("invalid node %s" % (tail,))
try:
if tail not in self.edges[head]:
self.edges[head][tail] = {}
self.edges[head][tail] = kwargs
except KeyError:
raise GraphError("invalid edge %s -> %s " % (head, tail))
def iterdot(self):
# write graph title
if self.type == 'digraph':
yield 'digraph %s {\n' % (self.name,)
elif self.type == 'graph':
yield 'graph %s {\n' % (self.name,)
else:
raise GraphError("unsupported graphtype %s" % (self.type,))
# write overall graph attributes
for attr_name, attr_value in sorted(self.attr.items()):
yield '%s="%s";' % (attr_name, attr_value)
yield '\n'
# some reusable patterns
cpatt = '%s="%s",' # to separate attributes
epatt = '];\n' # to end attributes
# write node attributes
for node_name, node_attr in sorted(self.nodes.items()):
yield '\t"%s" [' % (node_name,)
for attr_name, attr_value in sorted(node_attr.items()):
yield cpatt % (attr_name, attr_value)
yield epatt
# write edge attributes
for head in sorted(self.edges):
for tail in sorted(self.edges[head]):
if self.type == 'digraph':
yield '\t"%s" -> "%s" [' % (head, tail)
else:
yield '\t"%s" -- "%s" [' % (head, tail)
for attr_name, attr_value in \
sorted(self.edges[head][tail].items()):
yield cpatt % (attr_name, attr_value)
yield epatt
# finish file
yield '}\n'
def __iter__(self):
return self.iterdot()
def save_dot(self, file_name=None):
'''
Saves the current graph representation into a file
'''
if not file_name:
warnings.warn(DeprecationWarning, "always pass a file_name")
file_name = self.temp_dot
with open(file_name, "w") as fp:
for chunk in self.iterdot():
fp.write(chunk)
def save_img(self, file_name=None, file_type="gif", mode='dot'):
'''
Saves the dot file as an image file
'''
if not file_name:
warnings.warn(DeprecationWarning, "always pass a file_name")
file_name = "out"
if mode == 'neato':
self.save_dot(self.temp_neo)
neato_cmd = "%s -o %s %s" % (
self.neato, self.temp_dot, self.temp_neo)
os.system(neato_cmd)
plot_cmd = self.dot
else:
self.save_dot(self.temp_dot)
plot_cmd = self.dot
file_name = "%s.%s" % (file_name, file_type)
create_cmd = "%s -T%s %s -o %s" % (
plot_cmd, file_type, self.temp_dot, file_name)
os.system(create_cmd)

680
lib/spack/external/altgraph/Graph.py vendored Normal file
View File

@@ -0,0 +1,680 @@
"""
altgraph.Graph - Base Graph class
=================================
..
#--Version 2.1
#--Bob Ippolito October, 2004
#--Version 2.0
#--Istvan Albert June, 2004
#--Version 1.0
#--Nathan Denny, May 27, 1999
"""
from altgraph import GraphError
from collections import deque
class Graph(object):
"""
The Graph class represents a directed graph with *N* nodes and *E* edges.
Naming conventions:
- the prefixes such as *out*, *inc* and *all* will refer to methods
that operate on the outgoing, incoming or all edges of that node.
For example: :py:meth:`inc_degree` will refer to the degree of the node
computed over the incoming edges (the number of neighbours linking to
the node).
- the prefixes such as *forw* and *back* will refer to the
orientation of the edges used in the method with respect to the node.
For example: :py:meth:`forw_bfs` will start at the node then use the
outgoing edges to traverse the graph (goes forward).
"""
def __init__(self, edges=None):
"""
Initialization
"""
self.next_edge = 0
self.nodes, self.edges = {}, {}
self.hidden_edges, self.hidden_nodes = {}, {}
if edges is not None:
for item in edges:
if len(item) == 2:
head, tail = item
self.add_edge(head, tail)
elif len(item) == 3:
head, tail, data = item
self.add_edge(head, tail, data)
else:
raise GraphError("Cannot create edge from %s" % (item,))
def __repr__(self):
return '<Graph: %d nodes, %d edges>' % (
self.number_of_nodes(), self.number_of_edges())
def add_node(self, node, node_data=None):
"""
Adds a new node to the graph. Arbitrary data can be attached to the
node via the node_data parameter. Adding the same node twice will be
silently ignored.
The node must be a hashable value.
"""
#
# the nodes will contain tuples that will store incoming edges,
# outgoing edges and data
#
# index 0 -> incoming edges
# index 1 -> outgoing edges
if node in self.hidden_nodes:
# Node is present, but hidden
return
if node not in self.nodes:
self.nodes[node] = ([], [], node_data)
def add_edge(self, head_id, tail_id, edge_data=1, create_nodes=True):
"""
Adds a directed edge going from head_id to tail_id.
Arbitrary data can be attached to the edge via edge_data.
It may create the nodes if adding edges between nonexisting ones.
:param head_id: head node
:param tail_id: tail node
:param edge_data: (optional) data attached to the edge
:param create_nodes: (optional) creates the head_id or tail_id
node in case they did not exist
"""
# shorcut
edge = self.next_edge
# add nodes if on automatic node creation
if create_nodes:
self.add_node(head_id)
self.add_node(tail_id)
# update the corresponding incoming and outgoing lists in the nodes
# index 0 -> incoming edges
# index 1 -> outgoing edges
try:
self.nodes[tail_id][0].append(edge)
self.nodes[head_id][1].append(edge)
except KeyError:
raise GraphError('Invalid nodes %s -> %s' % (head_id, tail_id))
# store edge information
self.edges[edge] = (head_id, tail_id, edge_data)
self.next_edge += 1
def hide_edge(self, edge):
"""
Hides an edge from the graph. The edge may be unhidden at some later
time.
"""
try:
head_id, tail_id, edge_data = \
self.hidden_edges[edge] = self.edges[edge]
self.nodes[tail_id][0].remove(edge)
self.nodes[head_id][1].remove(edge)
del self.edges[edge]
except KeyError:
raise GraphError('Invalid edge %s' % edge)
def hide_node(self, node):
"""
Hides a node from the graph. The incoming and outgoing edges of the
node will also be hidden. The node may be unhidden at some later time.
"""
try:
all_edges = self.all_edges(node)
self.hidden_nodes[node] = (self.nodes[node], all_edges)
for edge in all_edges:
self.hide_edge(edge)
del self.nodes[node]
except KeyError:
raise GraphError('Invalid node %s' % node)
def restore_node(self, node):
"""
Restores a previously hidden node back into the graph and restores
all of its incoming and outgoing edges.
"""
try:
self.nodes[node], all_edges = self.hidden_nodes[node]
for edge in all_edges:
self.restore_edge(edge)
del self.hidden_nodes[node]
except KeyError:
raise GraphError('Invalid node %s' % node)
def restore_edge(self, edge):
"""
Restores a previously hidden edge back into the graph.
"""
try:
head_id, tail_id, data = self.hidden_edges[edge]
self.nodes[tail_id][0].append(edge)
self.nodes[head_id][1].append(edge)
self.edges[edge] = head_id, tail_id, data
del self.hidden_edges[edge]
except KeyError:
raise GraphError('Invalid edge %s' % edge)
def restore_all_edges(self):
"""
Restores all hidden edges.
"""
for edge in list(self.hidden_edges.keys()):
try:
self.restore_edge(edge)
except GraphError:
pass
def restore_all_nodes(self):
"""
Restores all hidden nodes.
"""
for node in list(self.hidden_nodes.keys()):
self.restore_node(node)
def __contains__(self, node):
"""
Test whether a node is in the graph
"""
return node in self.nodes
def edge_by_id(self, edge):
"""
Returns the edge that connects the head_id and tail_id nodes
"""
try:
head, tail, data = self.edges[edge]
except KeyError:
head, tail = None, None
raise GraphError('Invalid edge %s' % edge)
return (head, tail)
def edge_by_node(self, head, tail):
"""
Returns the edge that connects the head_id and tail_id nodes
"""
for edge in self.out_edges(head):
if self.tail(edge) == tail:
return edge
return None
def number_of_nodes(self):
"""
Returns the number of nodes
"""
return len(self.nodes)
def number_of_edges(self):
"""
Returns the number of edges
"""
return len(self.edges)
def __iter__(self):
"""
Iterates over all nodes in the graph
"""
return iter(self.nodes)
def node_list(self):
"""
Return a list of the node ids for all visible nodes in the graph.
"""
return list(self.nodes.keys())
def edge_list(self):
"""
Returns an iterator for all visible nodes in the graph.
"""
return list(self.edges.keys())
def number_of_hidden_edges(self):
"""
Returns the number of hidden edges
"""
return len(self.hidden_edges)
def number_of_hidden_nodes(self):
"""
Returns the number of hidden nodes
"""
return len(self.hidden_nodes)
def hidden_node_list(self):
"""
Returns the list with the hidden nodes
"""
return list(self.hidden_nodes.keys())
def hidden_edge_list(self):
"""
Returns a list with the hidden edges
"""
return list(self.hidden_edges.keys())
def describe_node(self, node):
"""
return node, node data, outgoing edges, incoming edges for node
"""
incoming, outgoing, data = self.nodes[node]
return node, data, outgoing, incoming
def describe_edge(self, edge):
"""
return edge, edge data, head, tail for edge
"""
head, tail, data = self.edges[edge]
return edge, data, head, tail
def node_data(self, node):
"""
Returns the data associated with a node
"""
return self.nodes[node][2]
def edge_data(self, edge):
"""
Returns the data associated with an edge
"""
return self.edges[edge][2]
def update_edge_data(self, edge, edge_data):
"""
Replace the edge data for a specific edge
"""
self.edges[edge] = self.edges[edge][0:2] + (edge_data,)
def head(self, edge):
"""
Returns the node of the head of the edge.
"""
return self.edges[edge][0]
def tail(self, edge):
"""
Returns node of the tail of the edge.
"""
return self.edges[edge][1]
def out_nbrs(self, node):
"""
List of nodes connected by outgoing edges
"""
return [self.tail(n) for n in self.out_edges(node)]
def inc_nbrs(self, node):
"""
List of nodes connected by incoming edges
"""
return [self.head(n) for n in self.inc_edges(node)]
def all_nbrs(self, node):
"""
List of nodes connected by incoming and outgoing edges
"""
return list(dict.fromkeys(self.inc_nbrs(node) + self.out_nbrs(node)))
def out_edges(self, node):
"""
Returns a list of the outgoing edges
"""
try:
return list(self.nodes[node][1])
except KeyError:
raise GraphError('Invalid node %s' % node)
def inc_edges(self, node):
"""
Returns a list of the incoming edges
"""
try:
return list(self.nodes[node][0])
except KeyError:
raise GraphError('Invalid node %s' % node)
def all_edges(self, node):
"""
Returns a list of incoming and outging edges.
"""
return set(self.inc_edges(node) + self.out_edges(node))
def out_degree(self, node):
"""
Returns the number of outgoing edges
"""
return len(self.out_edges(node))
def inc_degree(self, node):
"""
Returns the number of incoming edges
"""
return len(self.inc_edges(node))
def all_degree(self, node):
"""
The total degree of a node
"""
return self.inc_degree(node) + self.out_degree(node)
def _topo_sort(self, forward=True):
"""
Topological sort.
Returns a list of nodes where the successors (based on outgoing and
incoming edges selected by the forward parameter) of any given node
appear in the sequence after that node.
"""
topo_list = []
queue = deque()
indeg = {}
# select the operation that will be performed
if forward:
get_edges = self.out_edges
get_degree = self.inc_degree
get_next = self.tail
else:
get_edges = self.inc_edges
get_degree = self.out_degree
get_next = self.head
for node in self.node_list():
degree = get_degree(node)
if degree:
indeg[node] = degree
else:
queue.append(node)
while queue:
curr_node = queue.popleft()
topo_list.append(curr_node)
for edge in get_edges(curr_node):
tail_id = get_next(edge)
if tail_id in indeg:
indeg[tail_id] -= 1
if indeg[tail_id] == 0:
queue.append(tail_id)
if len(topo_list) == len(self.node_list()):
valid = True
else:
# the graph has cycles, invalid topological sort
valid = False
return (valid, topo_list)
def forw_topo_sort(self):
"""
Topological sort.
Returns a list of nodes where the successors (based on outgoing edges)
of any given node appear in the sequence after that node.
"""
return self._topo_sort(forward=True)
def back_topo_sort(self):
"""
Reverse topological sort.
Returns a list of nodes where the successors (based on incoming edges)
of any given node appear in the sequence after that node.
"""
return self._topo_sort(forward=False)
def _bfs_subgraph(self, start_id, forward=True):
"""
Private method creates a subgraph in a bfs order.
The forward parameter specifies whether it is a forward or backward
traversal.
"""
if forward:
get_bfs = self.forw_bfs
get_nbrs = self.out_nbrs
else:
get_bfs = self.back_bfs
get_nbrs = self.inc_nbrs
g = Graph()
bfs_list = get_bfs(start_id)
for node in bfs_list:
g.add_node(node)
for node in bfs_list:
for nbr_id in get_nbrs(node):
if forward:
g.add_edge(node, nbr_id)
else:
g.add_edge(nbr_id, node)
return g
def forw_bfs_subgraph(self, start_id):
"""
Creates and returns a subgraph consisting of the breadth first
reachable nodes based on their outgoing edges.
"""
return self._bfs_subgraph(start_id, forward=True)
def back_bfs_subgraph(self, start_id):
"""
Creates and returns a subgraph consisting of the breadth first
reachable nodes based on the incoming edges.
"""
return self._bfs_subgraph(start_id, forward=False)
def iterdfs(self, start, end=None, forward=True):
"""
Collecting nodes in some depth first traversal.
The forward parameter specifies whether it is a forward or backward
traversal.
"""
visited, stack = set([start]), deque([start])
if forward:
get_edges = self.out_edges
get_next = self.tail
else:
get_edges = self.inc_edges
get_next = self.head
while stack:
curr_node = stack.pop()
yield curr_node
if curr_node == end:
break
for edge in sorted(get_edges(curr_node)):
tail = get_next(edge)
if tail not in visited:
visited.add(tail)
stack.append(tail)
def iterdata(self, start, end=None, forward=True, condition=None):
"""
Perform a depth-first walk of the graph (as ``iterdfs``)
and yield the item data of every node where condition matches. The
condition callback is only called when node_data is not None.
"""
visited, stack = set([start]), deque([start])
if forward:
get_edges = self.out_edges
get_next = self.tail
else:
get_edges = self.inc_edges
get_next = self.head
get_data = self.node_data
while stack:
curr_node = stack.pop()
curr_data = get_data(curr_node)
if curr_data is not None:
if condition is not None and not condition(curr_data):
continue
yield curr_data
if curr_node == end:
break
for edge in get_edges(curr_node):
tail = get_next(edge)
if tail not in visited:
visited.add(tail)
stack.append(tail)
def _iterbfs(self, start, end=None, forward=True):
"""
The forward parameter specifies whether it is a forward or backward
traversal. Returns a list of tuples where the first value is the hop
value the second value is the node id.
"""
queue, visited = deque([(start, 0)]), set([start])
# the direction of the bfs depends on the edges that are sampled
if forward:
get_edges = self.out_edges
get_next = self.tail
else:
get_edges = self.inc_edges
get_next = self.head
while queue:
curr_node, curr_step = queue.popleft()
yield (curr_node, curr_step)
if curr_node == end:
break
for edge in get_edges(curr_node):
tail = get_next(edge)
if tail not in visited:
visited.add(tail)
queue.append((tail, curr_step + 1))
def forw_bfs(self, start, end=None):
"""
Returns a list of nodes in some forward BFS order.
Starting from the start node the breadth first search proceeds along
outgoing edges.
"""
return [node for node, step in self._iterbfs(start, end, forward=True)]
def back_bfs(self, start, end=None):
"""
Returns a list of nodes in some backward BFS order.
Starting from the start node the breadth first search proceeds along
incoming edges.
"""
return [node for node, _ in self._iterbfs(start, end, forward=False)]
def forw_dfs(self, start, end=None):
"""
Returns a list of nodes in some forward DFS order.
Starting with the start node the depth first search proceeds along
outgoing edges.
"""
return list(self.iterdfs(start, end, forward=True))
def back_dfs(self, start, end=None):
"""
Returns a list of nodes in some backward DFS order.
Starting from the start node the depth first search proceeds along
incoming edges.
"""
return list(self.iterdfs(start, end, forward=False))
def connected(self):
"""
Returns :py:data:`True` if the graph's every node can be reached from
every other node.
"""
node_list = self.node_list()
for node in node_list:
bfs_list = self.forw_bfs(node)
if len(bfs_list) != len(node_list):
return False
return True
def clust_coef(self, node):
"""
Computes and returns the local clustering coefficient of node.
The local cluster coefficient is proportion of the actual number of
edges between neighbours of node and the maximum number of edges
between those neighbours.
See "Local Clustering Coefficient" on
<http://en.wikipedia.org/wiki/Clustering_coefficient>
for a formal definition.
"""
num = 0
nbr_set = set(self.out_nbrs(node))
if node in nbr_set:
nbr_set.remove(node) # loop defense
for nbr in nbr_set:
sec_set = set(self.out_nbrs(nbr))
if nbr in sec_set:
sec_set.remove(nbr) # loop defense
num += len(nbr_set & sec_set)
nbr_num = len(nbr_set)
if nbr_num:
clust_coef = float(num) / (nbr_num * (nbr_num - 1))
else:
clust_coef = 0.0
return clust_coef
def get_hops(self, start, end=None, forward=True):
"""
Computes the hop distance to all nodes centered around a node.
First order neighbours are at hop 1, their neigbours are at hop 2 etc.
Uses :py:meth:`forw_bfs` or :py:meth:`back_bfs` depending on the value
of the forward parameter. If the distance between all neighbouring
nodes is 1 the hop number corresponds to the shortest distance between
the nodes.
:param start: the starting node
:param end: ending node (optional). When not specified will search the
whole graph.
:param forward: directionality parameter (optional).
If C{True} (default) it uses L{forw_bfs} otherwise L{back_bfs}.
:return: returns a list of tuples where each tuple contains the
node and the hop.
Typical usage::
>>> print (graph.get_hops(1, 8))
>>> [(1, 0), (2, 1), (3, 1), (4, 2), (5, 3), (7, 4), (8, 5)]
# node 1 is at 0 hops
# node 2 is at 1 hop
# ...
# node 8 is at 5 hops
"""
if forward:
return list(self._iterbfs(start=start, end=end, forward=True))
else:
return list(self._iterbfs(start=start, end=end, forward=False))

166
lib/spack/external/altgraph/GraphAlgo.py vendored Normal file
View File

@@ -0,0 +1,166 @@
'''
altgraph.GraphAlgo - Graph algorithms
=====================================
'''
from altgraph import GraphError
def dijkstra(graph, start, end=None):
"""
Dijkstra's algorithm for shortest paths
`David Eppstein, UC Irvine, 4 April 2002
<http://www.ics.uci.edu/~eppstein/161/python/>`_
`Python Cookbook Recipe
<http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/119466>`_
Find shortest paths from the start node to all nodes nearer than or
equal to the end node.
Dijkstra's algorithm is only guaranteed to work correctly when all edge
lengths are positive. This code does not verify this property for all
edges (only the edges examined until the end vertex is reached), but will
correctly compute shortest paths even for some graphs with negative edges,
and will raise an exception if it discovers that a negative edge has
caused it to make a mistake.
Adapted to altgraph by Istvan Albert, Pennsylvania State University -
June, 9 2004
"""
D = {} # dictionary of final distances
P = {} # dictionary of predecessors
Q = _priorityDictionary() # estimated distances of non-final vertices
Q[start] = 0
for v in Q:
D[v] = Q[v]
if v == end:
break
for w in graph.out_nbrs(v):
edge_id = graph.edge_by_node(v, w)
vwLength = D[v] + graph.edge_data(edge_id)
if w in D:
if vwLength < D[w]:
raise GraphError(
"Dijkstra: found better path to already-final vertex")
elif w not in Q or vwLength < Q[w]:
Q[w] = vwLength
P[w] = v
return (D, P)
def shortest_path(graph, start, end):
"""
Find a single shortest path from the *start* node to the *end* node.
The input has the same conventions as dijkstra(). The output is a list of
the nodes in order along the shortest path.
**Note that the distances must be stored in the edge data as numeric data**
"""
D, P = dijkstra(graph, start, end)
Path = []
while 1:
Path.append(end)
if end == start:
break
end = P[end]
Path.reverse()
return Path
#
# Utility classes and functions
#
class _priorityDictionary(dict):
'''
Priority dictionary using binary heaps (internal use only)
David Eppstein, UC Irvine, 8 Mar 2002
Implements a data structure that acts almost like a dictionary, with
two modifications:
1. D.smallest() returns the value x minimizing D[x]. For this to
work correctly, all values D[x] stored in the dictionary must be
comparable.
2. iterating "for x in D" finds and removes the items from D in sorted
order. Each item is not removed until the next item is requested,
so D[x] will still return a useful value until the next iteration
of the for-loop. Each operation takes logarithmic amortized time.
'''
def __init__(self):
'''
Initialize priorityDictionary by creating binary heap of pairs
(value,key). Note that changing or removing a dict entry will not
remove the old pair from the heap until it is found by smallest()
or until the heap is rebuilt.
'''
self.__heap = []
dict.__init__(self)
def smallest(self):
'''
Find smallest item after removing deleted items from front of heap.
'''
if len(self) == 0:
raise IndexError("smallest of empty priorityDictionary")
heap = self.__heap
while heap[0][1] not in self or self[heap[0][1]] != heap[0][0]:
lastItem = heap.pop()
insertionPoint = 0
while 1:
smallChild = 2*insertionPoint+1
if smallChild+1 < len(heap) and \
heap[smallChild] > heap[smallChild+1]:
smallChild += 1
if smallChild >= len(heap) or lastItem <= heap[smallChild]:
heap[insertionPoint] = lastItem
break
heap[insertionPoint] = heap[smallChild]
insertionPoint = smallChild
return heap[0][1]
def __iter__(self):
'''
Create destructive sorted iterator of priorityDictionary.
'''
def iterfn():
while len(self) > 0:
x = self.smallest()
yield x
del self[x]
return iterfn()
def __setitem__(self, key, val):
'''
Change value stored in dictionary and add corresponding pair to heap.
Rebuilds the heap if the number of deleted items gets large, to avoid
memory leakage.
'''
dict.__setitem__(self, key, val)
heap = self.__heap
if len(heap) > 2 * len(self):
self.__heap = [(v, k) for k, v in self.items()]
self.__heap.sort()
else:
newPair = (val, key)
insertionPoint = len(heap)
heap.append(None)
while insertionPoint > 0 and newPair < heap[(insertionPoint-1)//2]:
heap[insertionPoint] = heap[(insertionPoint-1)//2]
insertionPoint = (insertionPoint-1)//2
heap[insertionPoint] = newPair
def setdefault(self, key, val):
'''
Reimplement setdefault to pass through our customized __setitem__.
'''
if key not in self:
self[key] = val
return self[key]

View File

@@ -0,0 +1,73 @@
'''
altgraph.GraphStat - Functions providing various graph statistics
=================================================================
'''
def degree_dist(graph, limits=(0, 0), bin_num=10, mode='out'):
'''
Computes the degree distribution for a graph.
Returns a list of tuples where the first element of the tuple is the
center of the bin representing a range of degrees and the second element
of the tuple are the number of nodes with the degree falling in the range.
Example::
....
'''
deg = []
if mode == 'inc':
get_deg = graph.inc_degree
else:
get_deg = graph.out_degree
for node in graph:
deg.append(get_deg(node))
if not deg:
return []
results = _binning(values=deg, limits=limits, bin_num=bin_num)
return results
_EPS = 1.0/(2.0**32)
def _binning(values, limits=(0, 0), bin_num=10):
'''
Bins data that falls between certain limits, if the limits are (0, 0) the
minimum and maximum values are used.
Returns a list of tuples where the first element of the tuple is the
center of the bin and the second element of the tuple are the counts.
'''
if limits == (0, 0):
min_val, max_val = min(values) - _EPS, max(values) + _EPS
else:
min_val, max_val = limits
# get bin size
bin_size = (max_val - min_val)/float(bin_num)
bins = [0] * (bin_num)
# will ignore these outliers for now
for value in values:
try:
if (value - min_val) >= 0:
index = int((value - min_val)/float(bin_size))
bins[index] += 1
except IndexError:
pass
# make it ready for an x,y plot
result = []
center = (bin_size/2) + min_val
for i, y in enumerate(bins):
x = center + bin_size * i
result.append((x, y))
return result

144
lib/spack/external/altgraph/GraphUtil.py vendored Normal file
View File

@@ -0,0 +1,144 @@
'''
altgraph.GraphUtil - Utility classes and functions
==================================================
'''
import random
from collections import deque
from altgraph import Graph
from altgraph import GraphError
def generate_random_graph(
node_num, edge_num, self_loops=False, multi_edges=False):
'''
Generates and returns a :py:class:`~altgraph.Graph.Graph` instance with
*node_num* nodes randomly connected by *edge_num* edges.
'''
g = Graph.Graph()
if not multi_edges:
if self_loops:
max_edges = node_num * node_num
else:
max_edges = node_num * (node_num-1)
if edge_num > max_edges:
raise GraphError(
"inconsistent arguments to 'generate_random_graph'")
nodes = range(node_num)
for node in nodes:
g.add_node(node)
while 1:
head = random.choice(nodes)
tail = random.choice(nodes)
# loop defense
if head == tail and not self_loops:
continue
# multiple edge defense
if g.edge_by_node(head, tail) is not None and not multi_edges:
continue
# add the edge
g.add_edge(head, tail)
if g.number_of_edges() >= edge_num:
break
return g
def generate_scale_free_graph(
steps, growth_num, self_loops=False, multi_edges=False):
'''
Generates and returns a :py:class:`~altgraph.Graph.Graph` instance that
will have *steps* \* *growth_num* nodes and a scale free (powerlaw)
connectivity. Starting with a fully connected graph with *growth_num*
nodes at every step *growth_num* nodes are added to the graph and are
connected to existing nodes with a probability proportional to the degree
of these existing nodes.
'''
# FIXME: The code doesn't seem to do what the documentation claims.
graph = Graph.Graph()
# initialize the graph
store = []
for i in range(growth_num):
for j in range(i + 1, growth_num):
store.append(i)
store.append(j)
graph.add_edge(i, j)
# generate
for node in range(growth_num, steps * growth_num):
graph.add_node(node)
while graph.out_degree(node) < growth_num:
nbr = random.choice(store)
# loop defense
if node == nbr and not self_loops:
continue
# multi edge defense
if graph.edge_by_node(node, nbr) and not multi_edges:
continue
graph.add_edge(node, nbr)
for nbr in graph.out_nbrs(node):
store.append(node)
store.append(nbr)
return graph
def filter_stack(graph, head, filters):
"""
Perform a walk in a depth-first order starting
at *head*.
Returns (visited, removes, orphans).
* visited: the set of visited nodes
* removes: the list of nodes where the node
data does not all *filters*
* orphans: tuples of (last_good, node),
where node is not in removes, is directly
reachable from a node in *removes* and
*last_good* is the closest upstream node that is not
in *removes*.
"""
visited, removes, orphans = set([head]), set(), set()
stack = deque([(head, head)])
get_data = graph.node_data
get_edges = graph.out_edges
get_tail = graph.tail
while stack:
last_good, node = stack.pop()
data = get_data(node)
if data is not None:
for filtfunc in filters:
if not filtfunc(data):
removes.add(node)
break
else:
last_good = node
for edge in get_edges(node):
tail = get_tail(edge)
if last_good is not node:
orphans.add((last_good, tail))
if tail not in visited:
visited.add(tail)
stack.append((last_good, tail))
orphans = [
(lg, tl)
for (lg, tl) in orphans if tl not in removes]
return visited, removes, orphans

View File

@@ -0,0 +1,212 @@
"""
altgraph.ObjectGraph - Graph of objects with an identifier
==========================================================
A graph of objects that have a "graphident" attribute.
graphident is the key for the object in the graph
"""
from altgraph import GraphError
from altgraph.Graph import Graph
from altgraph.GraphUtil import filter_stack
class ObjectGraph(object):
"""
A graph of objects that have a "graphident" attribute.
graphident is the key for the object in the graph
"""
def __init__(self, graph=None, debug=0):
if graph is None:
graph = Graph()
self.graphident = self
self.graph = graph
self.debug = debug
self.indent = 0
graph.add_node(self, None)
def __repr__(self):
return '<%s>' % (type(self).__name__,)
def flatten(self, condition=None, start=None):
"""
Iterate over the subgraph that is entirely reachable by condition
starting from the given start node or the ObjectGraph root
"""
if start is None:
start = self
start = self.getRawIdent(start)
return self.graph.iterdata(start=start, condition=condition)
def nodes(self):
for ident in self.graph:
node = self.graph.node_data(ident)
if node is not None:
yield self.graph.node_data(ident)
def get_edges(self, node):
if node is None:
node = self
start = self.getRawIdent(node)
_, _, outraw, incraw = self.graph.describe_node(start)
def iter_edges(lst, n):
seen = set()
for tpl in (self.graph.describe_edge(e) for e in lst):
ident = tpl[n]
if ident not in seen:
yield self.findNode(ident)
seen.add(ident)
return iter_edges(outraw, 3), iter_edges(incraw, 2)
def edgeData(self, fromNode, toNode):
if fromNode is None:
fromNode = self
start = self.getRawIdent(fromNode)
stop = self.getRawIdent(toNode)
edge = self.graph.edge_by_node(start, stop)
return self.graph.edge_data(edge)
def updateEdgeData(self, fromNode, toNode, edgeData):
if fromNode is None:
fromNode = self
start = self.getRawIdent(fromNode)
stop = self.getRawIdent(toNode)
edge = self.graph.edge_by_node(start, stop)
self.graph.update_edge_data(edge, edgeData)
def filterStack(self, filters):
"""
Filter the ObjectGraph in-place by removing all edges to nodes that
do not match every filter in the given filter list
Returns a tuple containing the number of:
(nodes_visited, nodes_removed, nodes_orphaned)
"""
visited, removes, orphans = filter_stack(self.graph, self, filters)
for last_good, tail in orphans:
self.graph.add_edge(last_good, tail, edge_data='orphan')
for node in removes:
self.graph.hide_node(node)
return len(visited)-1, len(removes), len(orphans)
def removeNode(self, node):
"""
Remove the given node from the graph if it exists
"""
ident = self.getIdent(node)
if ident is not None:
self.graph.hide_node(ident)
def removeReference(self, fromnode, tonode):
"""
Remove all edges from fromnode to tonode
"""
if fromnode is None:
fromnode = self
fromident = self.getIdent(fromnode)
toident = self.getIdent(tonode)
if fromident is not None and toident is not None:
while True:
edge = self.graph.edge_by_node(fromident, toident)
if edge is None:
break
self.graph.hide_edge(edge)
def getIdent(self, node):
"""
Get the graph identifier for a node
"""
ident = self.getRawIdent(node)
if ident is not None:
return ident
node = self.findNode(node)
if node is None:
return None
return node.graphident
def getRawIdent(self, node):
"""
Get the identifier for a node object
"""
if node is self:
return node
ident = getattr(node, 'graphident', None)
return ident
def __contains__(self, node):
return self.findNode(node) is not None
def findNode(self, node):
"""
Find the node on the graph
"""
ident = self.getRawIdent(node)
if ident is None:
ident = node
try:
return self.graph.node_data(ident)
except KeyError:
return None
def addNode(self, node):
"""
Add a node to the graph referenced by the root
"""
self.msg(4, "addNode", node)
try:
self.graph.restore_node(node.graphident)
except GraphError:
self.graph.add_node(node.graphident, node)
def createReference(self, fromnode, tonode, edge_data=None):
"""
Create a reference from fromnode to tonode
"""
if fromnode is None:
fromnode = self
fromident, toident = self.getIdent(fromnode), self.getIdent(tonode)
if fromident is None or toident is None:
return
self.msg(4, "createReference", fromnode, tonode, edge_data)
self.graph.add_edge(fromident, toident, edge_data=edge_data)
def createNode(self, cls, name, *args, **kw):
"""
Add a node of type cls to the graph if it does not already exist
by the given name
"""
m = self.findNode(name)
if m is None:
m = cls(name, *args, **kw)
self.addNode(m)
return m
def msg(self, level, s, *args):
"""
Print a debug message with the given level
"""
if s and level <= self.debug:
print("%s%s %s" % (
" " * self.indent, s, ' '.join(map(repr, args))))
def msgin(self, level, s, *args):
"""
Print a debug message and indent
"""
if level <= self.debug:
self.msg(level, s, *args)
self.indent = self.indent + 1
def msgout(self, level, s, *args):
"""
Dedent and print a debug message
"""
if level <= self.debug:
self.indent = self.indent - 1
self.msg(level, s, *args)

147
lib/spack/external/altgraph/__init__.py vendored Normal file
View File

@@ -0,0 +1,147 @@
'''
altgraph - a python graph library
=================================
altgraph is a fork of `graphlib <http://pygraphlib.sourceforge.net>`_ tailored
to use newer Python 2.3+ features, including additional support used by the
py2app suite (modulegraph and macholib, specifically).
altgraph is a python based graph (network) representation and manipulation
package. It has started out as an extension to the
`graph_lib module
<http://www.ece.arizona.edu/~denny/python_nest/graph_lib_1.0.1.html>`_
written by Nathan Denny it has been significantly optimized and expanded.
The :class:`altgraph.Graph.Graph` class is loosely modeled after the
`LEDA <http://www.algorithmic-solutions.com/enleda.htm>`_
(Library of Efficient Datatypes) representation. The library
includes methods for constructing graphs, BFS and DFS traversals,
topological sort, finding connected components, shortest paths as well as a
number graph statistics functions. The library can also visualize graphs
via `graphviz <http://www.research.att.com/sw/tools/graphviz/>`_.
The package contains the following modules:
- the :py:mod:`altgraph.Graph` module contains the
:class:`~altgraph.Graph.Graph` class that stores the graph data
- the :py:mod:`altgraph.GraphAlgo` module implements graph algorithms
operating on graphs (:py:class:`~altgraph.Graph.Graph`} instances)
- the :py:mod:`altgraph.GraphStat` module contains functions for
computing statistical measures on graphs
- the :py:mod:`altgraph.GraphUtil` module contains functions for
generating, reading and saving graphs
- the :py:mod:`altgraph.Dot` module contains functions for displaying
graphs via `graphviz <http://www.research.att.com/sw/tools/graphviz/>`_
- the :py:mod:`altgraph.ObjectGraph` module implements a graph of
objects with a unique identifier
Installation
------------
Download and unpack the archive then type::
python setup.py install
This will install the library in the default location. For instructions on
how to customize the install procedure read the output of::
python setup.py --help install
To verify that the code works run the test suite::
python setup.py test
Example usage
-------------
Lets assume that we want to analyze the graph below (links to the full picture)
GRAPH_IMG. Our script then might look the following way::
from altgraph import Graph, GraphAlgo, Dot
# these are the edges
edges = [ (1,2), (2,4), (1,3), (2,4), (3,4), (4,5), (6,5),
(6,14), (14,15), (6, 15), (5,7), (7, 8), (7,13), (12,8),
(8,13), (11,12), (11,9), (13,11), (9,13), (13,10) ]
# creates the graph
graph = Graph.Graph()
for head, tail in edges:
graph.add_edge(head, tail)
# do a forward bfs from 1 at most to 20
print(graph.forw_bfs(1))
This will print the nodes in some breadth first order::
[1, 2, 3, 4, 5, 7, 8, 13, 11, 10, 12, 9]
If we wanted to get the hop-distance from node 1 to node 8
we coud write::
print(graph.get_hops(1, 8))
This will print the following::
[(1, 0), (2, 1), (3, 1), (4, 2), (5, 3), (7, 4), (8, 5)]
Node 1 is at 0 hops since it is the starting node, nodes 2,3 are 1 hop away ...
node 8 is 5 hops away. To find the shortest distance between two nodes you
can use::
print(GraphAlgo.shortest_path(graph, 1, 12))
It will print the nodes on one (if there are more) the shortest paths::
[1, 2, 4, 5, 7, 13, 11, 12]
To display the graph we can use the GraphViz backend::
dot = Dot.Dot(graph)
# display the graph on the monitor
dot.display()
# save it in an image file
dot.save_img(file_name='graph', file_type='gif')
..
@author: U{Istvan Albert<http://www.personal.psu.edu/staff/i/u/iua1/>}
@license: MIT License
Copyright (c) 2004 Istvan Albert unless otherwise noted.
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.
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.
@requires: Python 2.3 or higher
@newfield contributor: Contributors:
@contributor: U{Reka Albert <http://www.phys.psu.edu/~ralbert/>}
'''
import pkg_resources
__version__ = pkg_resources.require('altgraph')[0].version
class GraphError(ValueError):
pass