External: add macholib and altgraph needed to relocate Mach-o binaries on Linux (#12909)
This commit is contained in:
309
lib/spack/external/altgraph/Dot.py
vendored
Normal file
309
lib/spack/external/altgraph/Dot.py
vendored
Normal 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
680
lib/spack/external/altgraph/Graph.py
vendored
Normal 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
166
lib/spack/external/altgraph/GraphAlgo.py
vendored
Normal 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]
|
73
lib/spack/external/altgraph/GraphStat.py
vendored
Normal file
73
lib/spack/external/altgraph/GraphStat.py
vendored
Normal 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
144
lib/spack/external/altgraph/GraphUtil.py
vendored
Normal 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
|
212
lib/spack/external/altgraph/ObjectGraph.py
vendored
Normal file
212
lib/spack/external/altgraph/ObjectGraph.py
vendored
Normal 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
147
lib/spack/external/altgraph/__init__.py
vendored
Normal 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
|
Reference in New Issue
Block a user