Source code for msml.model.dag

# region gplv3preamble
# The Medical Simulation Markup Language (MSML) - Simplifying the biomechanical modeling workflow
#
# MSML has been developed in the framework of 'SFB TRR 125 Cognition-Guided Surgery'
#
# If you use this software in academic work, please cite the paper:
#   S. Suwelack, M. Stoll, S. Schalck, N.Schoch, R. Dillmann, R. Bendl, V. Heuveline and S. Speidel,
#   The Medical Simulation Markup Language (MSML) - Simplifying the biomechanical modeling workflow,
#   Medicine Meets Virtual Reality (MMVR) 2014
#
# Copyright (C) 2013-2014 see Authors.txt
#
# If you have any questions please feel free to contact us at suwelack@kit.edu
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# endregion

""" Directed Acyclic Graph for control and information flow.
"""

import networkx as nx

__author__ = "Alexander Weigl <uiduw@student.kit.edu>"

__all__ = ["DiGraph"]

[docs]class DiGraph(nx.MultiDiGraph): """ A networkx.MultiDiGraph but extends this by to methods: * ``self.toporder`` * ``self.show`` """
[docs] def toporder(self): """ Returns the topological order of the graph. Returns: list[set]: a list of buckets (set of nodes), that has only predecessor from the previous bucket Raises: ValueError: iff. the graph is not acyclic """ graph = nx.MultiDiGraph(self) amountnodes = len(self.nodes()) nodes = 0 buckets = [] def independent_nodes(): nodes = graph.nodes() pred = map(graph.predecessors, nodes) return [n for n, p in zip(nodes, pred) if len(p) == 0] i = 0 while nodes < amountnodes and i < amountnodes: inodes = set(independent_nodes()) nodes += len(inodes) buckets.append(inodes) graph.remove_nodes_from(inodes) i += 1 if nodes < amountnodes: raise ValueError('graph is not acyclic') return buckets # def show(self): # """ shows the graph withing matplotlib # """ # # import matplotlib.pyplot as plt # # try: # from networkx import graphviz_layout, write_dot # except ImportError: # raise ImportError("This example needs Graphviz and either PyGraphviz or Pydot") # # G = self # #write_dot(G, '/tmp/test.dot') # pos = nx.graphviz_layout(G, prog='neato', args='') # plt.figure(figsize=(8, 8)) # nx.draw(G, pos, node_size=100, alpha=0.5, node_color="blue", with_labels=True) # plt.axis('equal') # #plt.savefig('circular_tree.png') # #plt.show() # region oldcode
''' class DiGraph(object): """ Directed acyclic graph implementation. """ def __init__(self): """ Construct a new DAG with no nodes or edges. """ self._incoming = {} self._outgoing = {} self._edge_value = {} def add_node(self, node_name): """ Add a node if it does not exist yet, or error out. """ if node_name in self._incoming: raise KeyError('node %s already exists' % node_name) self._incoming[node_name] = set() self._outgoing[node_name] = set() def delete_node(self, node_name): """ Deletes this node and all edges referencing it. """ if node_name not in self._incoming: raise KeyError('node %s does not exist' % node_name) for lis in (self._incoming.iteritems(), self._outgoing.iteritems()): for node , edges in lis: if node_name in edges: edges.remove(node_name) self._incoming.pop(node_name) self._outgoing.pop(node_name) def add_edge(self, fro, to, key = None): """ Add an edge (dependency) between the specified nodes. """ if to not in self._incoming or fro not in self._outgoing: raise KeyError('one or more nodes do not exist in graph') self._outgoing[fro].add(to) self._incoming[to].add(fro) self._edge_value[(fro, to)] = key def delete_edge(self, fro, to): """ Delete an edge from the graph. """ self._outgoing[fro].discard(to) self._incoming[to].discard(fro) def downstream(self, node): """ Returns a list of all nodes this node has edges towards. """ if node not in self._incoming: raise KeyError('node %s is not in graph' % node) return list(self._incoming[node]) def upstream(self, node): if node not in self._outgoing: raise KeyError('node %s is not in graph' % node) return list(self._outgoing[node]) def reset_graph(self): """ Restore the graph to an empty state. """ self._incoming = {} self._outgoing = {} def validate(self): """ Returns (Boolean, message) of whether DAG is valid. """ if len(self.ind_nodes()) == 0: return (False, 'no independent nodes detected') try: self._topological_sort() except ValueError: return (False, 'failed topological sort') return (True, 'valid') def _dependencies(self, target_node, graph = None): """ Returns a list of all nodes from incoming edges. """ if graph is None: graph = self.graph result = set() for node, outgoing_nodes in graph.iteritems(): if target_node in outgoing_nodes: result.add(node) return list(result) def independent_nodes(self): return (node for node, items in self._incoming.items() if len(items) == 0) def toporder(self): """ Returns a topological ordering of the DAG. Raises an error if this is not possible (graph is not valid). """ graph = copy(self) amountnodes = len(self._incoming.keys()) nodes = 0 buckets = [] i = 0 while nodes < amountnodes and i < amountnodes: inodes = set(graph.independent_nodes()) nodes += len(inodes) buckets.append(inodes) for b in inodes: graph.delete_node(b) i += 1 if nodes < amountnodes: raise ValueError('graph is not acyclic') return buckets if __name__ == "__main__": G = DiGraph() a, b, c, d, e = list("ABCDE") G.add_node(a) G.add_node(b) G.add_node(c) G.add_node(d) G.add_node(e) G.add_edge(a, b) G.add_edge(a, c) G.add_edge(c, d) G.add_edge(b, e) G.add_edge(b, d) print(G.toporder()) ''' #endregion