def write_adl(graph): """ Export graph as adjacency list (ADL) .. note:: This format does not store graph, node, or edge data. :param graph: Graph object to export :type graph: :graphit:Graph :return: Graph object :rtype: :py:str """ # Create empty file buffer string_buffer = StringIO() # Process nodes with adjacency adjacency = graph.adjacency() adj_done = [] for node, adj in adjacency.items(): if len(adj): string_buffer.write('{0} {1}\n'.format(node, ' '.join([str(n) for n in adj]))) adj_done.extend(adj) # Process isolated nodes for node, adj in adjacency.items(): if not len(adj) and node not in adj_done: string_buffer.write('{0}\n'.format(node)) logger.info('Graph {0} exported in Adjacency list format'.format(repr(graph))) # Reset buffer cursor string_buffer.seek(0) return string_buffer.read()
def write_p2g(graph, graph_name_label='name'): """ Export a graphit graph to P2G format :param graph: Graph object to export :type graph: :graphit:Graph :param graph_name_label: graph.data attribute label for the graph name :type graph_name_label: :py:str :return: P2G graph representation :rtype: :py:str """ # Create empty file buffer string_buffer = StringIO() # Write graph meta-data string_buffer.write('{0}\n'.format( graph.data.get(graph_name_label, 'graph'))) string_buffer.write('{0} {1}\n'.format(len(graph.nodes), len(graph.edges))) # Write nodes and edges key_tag = graph.data['key_tag'] nodes = sorted(graph.nodes.keys()) for node in nodes: string_buffer.write('{0}\n'.format(graph[node].get(key_tag))) string_buffer.write('{0}\n'.format(' '.join( [str(nodes.index(n)) for n in graph.adjacency[node]]))) # Reset buffer cursor string_buffer.seek(0) return string_buffer.read()
def write_pgf(graph, pickle_graph=False): """ Export graph as Graph Python Format file PGF format is the modules own file format consisting of a serialized graph data, nodes and edges dictionaries. Exports either as plain text serialized dictionary or pickled graph object. The format is feature rich with good performance but is not portable. :param graph: Graph object to export :type graph: :graphit:Graph :param pickle_graph: serialize the Graph using Python pickle :type pickle_graph: :py:bool :return: Graph in GPF graph representation :rtype: :py:str """ # Pickle or not if pickle_graph: return pickle.dumps(graph) # Export graph as serialized Graph Python Format pp = pprint.PrettyPrinter(indent=2) string_buffer = StringIO() string_buffer.write('{') # Export graph data dictionary string_buffer.write('"data": {0},\n'.format( pp.pformat(graph.data.to_dict()))) # Export nodes as dictionary string_buffer.write('"nodes": {0},\n'.format( pp.pformat(graph.nodes.to_dict()))) # Export edges as dictionary string_buffer.write('"edges": {0},\n'.format( pp.pformat(graph.edges.to_dict()))) string_buffer.write('}') logger.info('Graph {0} exported in PGF format'.format(repr(graph))) # Reset buffer cursor string_buffer.seek(0) return string_buffer.read()
def write_flattened(graph, sep='.', default=None, allow_none=False, **kwargs): # No nodes, return empty dict if graph.empty(): logging.info('Graph is empty: {0}'.format(repr(graph))) return {} # Graph should be of type GraphAxis with a root node nid defined if not isinstance(graph, GraphAxis): raise TypeError('Unsupported graph type {0}'.format(type(graph))) if graph.root is None: raise GraphitException('No graph root node defines') # Set current NodeTools aside and register new one curr_nt = graph.node_tools graph.origin.node_tools = NodeAxisTools # Create empty file buffer string_buffer = StringIO() value_tag = graph.data.value_tag for leaf in node_leaves(graph): node = graph.getnodes(leaf) value = node.get(value_tag, default=default) if value is None and not allow_none: continue path = '{0}{1}{2}\n'.format(node.path(sep=sep, **kwargs), sep, value) string_buffer.write(path) # Restore original NodeTools graph.origin.node_tools = curr_nt # Reset buffer cursor string_buffer.seek(0) return string_buffer.read()
def write_tgf(graph, key_tag=None): """ Export a graph in Trivial Graph Format .. note:: TGF graph export uses the Graph iternodes and iteredges methods to retrieve nodes and edges and 'get' the data labels. The behaviour of this process is determined by the single node/edge mixin classes and the ORM mapper. :param graph: Graph object to export :type graph: :graphit:Graph :param key_tag: node/edge data key :type key_tag: :py:str :return: TGF graph representation :rtype: :py:str """ # Define node and edge data tags to export key_tag = key_tag or graph.key_tag # Create empty file buffer string_buffer = StringIO() # Export nodes for node in graph.iternodes(): string_buffer.write('{0} {1}\n'.format(node.nid, node.get(key_tag, default=''))) # Export edges string_buffer.write('#\n') for edge in graph.iteredges(): e1, e2 = edge.nid string_buffer.write('{0} {1} {2}\n'.format( e1, e2, edge.get(key_tag, default=''))) # Reset buffer cursor string_buffer.seek(0) return string_buffer.read()
def write_dot(graph, graph_name='graph', dot_directives=None): """ DOT graphs are either directional (digraph) or undirectional, mixed mode is not supported. Basic types for node and edge attributes are supported. :param graph: Graph object to export :type graph: :graphit:Graph :param graph_name: graph name to include :type graph_name: :py:str :param dot_directives: special DOT format rendering directives :type dot_directives: :py:dict :return: DOT graph representation :rtype: :py:str """ indent = ' ' * 4 link = '->' if graph.directed else '--' # Create empty file buffer string_buffer = StringIO() # Write header comment and graph container string_buffer.write('//Created by {0} version {1}\n'.format(__module__, __version__)) string_buffer.write('{0} "{1}" {2}\n'.format('digraph' if graph.directed else 'graph', graph_name, '{')) # Write special DOT directives if isinstance(dot_directives, dict): for directive, value in dot_directives.items(): string_buffer.write('{0}{1}={2}\n'.format(indent, directive, value)) # Export nodes string_buffer.write('{0}//nodes\n'.format(indent)) for node in graph.iternodes(): attr = ['{0}={1}'.format(k, json.dumps(v)) for k,v in node.nodes[node.nid].items() if isinstance(v, PY_PRIMITIVES) and not k.startswith('$')] if attr: string_buffer.write('{0}{1} [{2}];\n'.format(indent, node.nid, ','.join(attr))) # Export adjacency string_buffer.write('{0}//edges\n'.format(indent)) done = [] for node, adj in graph.adjacency.items(): for a in adj: edges = [(node, a), (a, node)] if all([e not in done for e in edges]): attr = {} for edge in edges: attr.update(graph.edges.get(edge, {})) attr = ['{0}={1}'.format(k, json.dumps(v)) for k, v in attr.items() if isinstance(v, PY_PRIMITIVES) and not k.startswith('$')] if attr: string_buffer.write('{0}{1} {2} {3} [{4}];\n'.format(indent, node, link, a, ','.join(attr))) else: string_buffer.write('{0}{1} {2} {3};\n'.format(indent, node, link, a)) done.extend(edges) # Closing curly brace string_buffer.write('}\n') # Reset buffer cursor string_buffer.seek(0) return string_buffer.read()
def write_gml(graph, node_tools=None, edge_tools=None): """ Export a graphit graph to GML format Export graphit Graph data, nodes and edges in Graph Modelling Language (GML) format. The function replaces the graph NodeTools and EdgeTools with a custom version exposing a `serialize` method responsible for serializing the node/edge attributes in a GML format. The NodeTools class is also used to export Graph.data attributes. Custom serializers may be introduced as custom NodeTools or EdgeTools classes using the `node_tools` and/or `edge_tools` attributes. In addition, the graph ORM may be used to inject tailored `serialize` methods in specific nodes or edges. :param graph: Graph object to export :type graph: :graphit:Graph :param node_tools: NodeTools class with node serialize method :type node_tools: :graphit:NodeTools :param edge_tools: EdgeTools class with edge serialize method :type edge_tools: :graphit:EdgeTools :return: GML graph representation :rtype: :py:str """ # Set current node and edge tools aside and register GML ones for export curr_nt = graph.node_tools curr_et = graph.edge_tools if node_tools and not issubclass(node_tools, NodeTools): raise GraphitException('Node_tools ({0}) needs to inherit from the NodeTools class'.format(type(node_tools))) graph.node_tools = node_tools or type('GMLNodeTools', (GMLTools, NodeTools), {}) if edge_tools and not issubclass(edge_tools, EdgeTools): raise GraphitException('Edge_tools ({0}) needs to inherit from the EdgeTools class'.format(type(edge_tools))) graph.edge_tools = edge_tools or type('GMLEdgeTools', (GMLTools, EdgeTools), {}) # Create empty file buffer string_buffer = StringIO() # Serialize main graph instance gs = graph.node_tools() string_buffer.write('graph [\n') gs.serialize(graph.data.to_dict(), string_buffer, indent=2) # Serialize nodes for node in graph.iternodes(sort_key=int): node.serialize(node.nodes[node.nid], string_buffer, indent=2, class_name='node') # Serialize edges for edge in graph.iteredges(): edge.serialize(edge.edges[edge.nid], string_buffer, indent=2, class_name='edge') string_buffer.write(']\n') # Restore original node and edge tools graph.node_tools = curr_nt graph.edge_tools = curr_et logger.info('Graph {0} exported in GML format'.format(repr(graph))) # Reset buffer cursor string_buffer.seek(0) return string_buffer.read()
def write_web(graph, orm_data_tag='haddock_type', indent=2, root_nid=None): """ Export a graph in Spyder .web format Empty data blocks or Python None values are not exported. .. note:: Web graph export uses the Graph iternodes and iteredges methods to retrieve nodes and edges and 'get' the data labels. The behaviour of this process is determined by the single node/edge mixin classes and the ORM mapper. :param graph: Graph object to export :type graph: :graphit:Graph :param orm_data_tag: data key to use for .web data identifier :type orm_data_tag: :py:str :param indent: .web file white space indentation level :type indent: :py:int :param root_nid: Root node ID in graph hierarchy :return: Spyder .web graph representation :rtype: :py:str """ # Build ORM with format specific conversion classes weborm = GraphORM() weborm.node_mapping.add( RestraintsInterface, lambda x: x.get(graph.data.key_tag) == 'activereslist') weborm.node_mapping.add( RestraintsInterface, lambda x: x.get(graph.data.key_tag) == 'passivereslist') # Resolve the root node (if any) for hierarchical data structures if root_nid and root_nid not in graph.nodes: raise GraphitException( 'Root node ID {0} not in graph'.format(root_nid)) else: root_nid = resolve_root_node(graph) if root_nid is None: raise GraphitException('Unable to resolve root node ID') # Set current NodeTools aside and register new one curr_nt = graph.node_tools graph.node_tools = WebNodeTools # Set current ORM aside and register new one. curr_orm = graph.orm graph.orm = weborm # Create empty file buffer string_buffer = StringIO() # Traverse node hierarchy def _walk_dict(node, indent_level): # First, collect all leaf nodes and write. Sort according to 'key' for leaf in sorted( [n for n in node.children(include_self=True) if n.isleaf], key=lambda obj: obj.key): # Do not export nodes that have no data or None but do export # empty data blocks (has orm_data_tag) if leaf.get(graph.data.value_tag, None) is None: if leaf.get(orm_data_tag): string_buffer.write('{0}{1} = {2} (\n'.format( ' ' * indent_level, leaf.get(graph.data.key_tag), leaf.get(orm_data_tag))) string_buffer.write('{0}),\n'.format(' ' * indent_level)) continue # Format 'Array' types when they are list style leaf nodes if leaf.get('is_array', False) or leaf.get('type') == 'array': string_buffer.write('{0}{1} = {2} (\n'.format( ' ' * indent_level, leaf.get(graph.data.key_tag), leaf.get(orm_data_tag))) array_indent = indent_level + indent for array_type in leaf.get(graph.data.value_tag, default=[]): string_buffer.write('{0}{1},\n'.format( ' ' * array_indent, array_type)) string_buffer.write('{0}),\n'.format(' ' * indent_level)) # Format key, value pairs else: string_buffer.write('{0}{1} = {2},\n'.format( ' ' * indent_level, leaf.get(graph.data.key_tag), leaf.get(graph.data.value_tag, default=''))) # Second, process child non-leaf nodes for child in [n for n in node.children() if not n.isleaf]: # Write block header key = '' if not child.get('is_array', False) or child.get('type') == 'array': key = '{0} = '.format(child.get(graph.data.key_tag)) string_buffer.write('{0}{1}{2} (\n'.format( ' ' * indent_level, key, child.get(orm_data_tag))) # Indent new data block one level down and walk children indent_level += indent _walk_dict(child, indent_level) # Close data block and indent one level up indent_level -= indent string_buffer.write('{0}),\n'.format(' ' * indent_level)) # Build adjacency only once with graph.adjacency as adj: rootnode = graph.getnodes(root_nid) if rootnode.isleaf: _walk_dict(rootnode, 0) else: string_buffer.write('{0} (\n'.format(rootnode.get(orm_data_tag))) _walk_dict(rootnode, indent) string_buffer.write(')\n') # Restore original ORM and NodeTools graph.node_tools = curr_nt graph.orm = curr_orm logger.info('Graph {0} exported in WEB format'.format(repr(graph))) # Reset buffer cursor string_buffer.seek(0) return string_buffer.read()
def write_dot(graph, graph_name=None): """ DOT graphs are either directional (digraph) or undirectional, mixed mode is not supported. Nodes and edges are all exported separably, short hand notations are not supported. Grouping and supgraphs are not supported. Graph attributes in graph.data, graph.edges and graph.nodes will be exported as DOT directives regardless if they are official GraphVis DOT graph directives as listed in the reference documentation: https://www.graphviz.org/doc/info/attrs.html Dot reserved rendering keywords part of the graphs global attributes in graph.data or part of the node and edge attributes are exported as part of the DOT graph. :param graph: Graph object to export :type graph: :graphit:Graph :param graph_name: name of the 'graph' or 'digraph'. Uses the 'title' attribute in graph.data by default, else graph_name :type graph_name: :py:str :return: DOT graph representation :rtype: :py:str """ indent = ' ' * 4 link = '->' if graph.directed else '--' # Create empty file buffer string_buffer = StringIO() # Write header comment and graph container graph_name = graph.data.get('title', graph_name or 'graph') string_buffer.write('//Created by {0} version {1}\n'.format(__module__, version())) string_buffer.write('{0} "{1}" {2}\n'.format('digraph' if graph.directed else 'graph', graph_name, '{')) # Write global DOT directives for dot_key, dot_value in graph.data.items(): if isinstance(dot_value, PY_PRIMITIVES) and not dot_key.startswith('$'): string_buffer.write('{0}{1}={2}\n'.format(indent, dot_key, json.dumps(dot_value))) # Export nodes string_buffer.write('{0}//nodes\n'.format(indent)) for node in graph.iternodes(): attr = ['{0}={1}'.format(k, json.dumps(v)) for k, v in node.nodes[node.nid].items() if isinstance(v, PY_PRIMITIVES) and not k.startswith('$')] if attr: string_buffer.write('{0}{1} [{2}];\n'.format(indent, node.nid, ','.join(attr))) # Export adjacency string_buffer.write('{0}//edges\n'.format(indent)) done = [] for edge in graph.edges: if edge not in done: edges = sorted([edge, edge[::-1]]) attr = [] for e in edges: attr.extend(['{0}={1}'.format(k, json.dumps(v)) for k, v in graph.edges[e].items() if isinstance(v, PY_PRIMITIVES) and not k.startswith('$')]) start, end = edges[0] if attr: string_buffer.write('{0}{1} {2} {3} [{4}];\n'.format(indent, start, link, end, ','.join(attr))) else: string_buffer.write('{0}{1} {2} {3};\n'.format(indent, start, link, end)) done.extend(edges) # Closing curly brace string_buffer.write('}\n') # Reset buffer cursor string_buffer.seek(0) return string_buffer.read()
def write_lgf(graph): """ Write graph in LEMON Graph Format (LGF) :param graph: Graph object to import LGF data in :type graph: :graphit:Graph :return: Graph object :rtype: :graphit:Graph """ # Create empty file buffer string_buffer = StringIO() # Export meta-data string_buffer.write('# Graphit graph exported in LEMON Graph Format\n') string_buffer.write('# Graphit version: {0}, date: {1}\n'.format( version(), time.ctime())) # Export nodes if len(graph.nodes): string_buffer.write('\n@nodes\n') table = ExportTable(graph.nodes) string_buffer.write('id {0}\n'.format(table.header)) for node, attr in graph.nodes.items(): string_buffer.write('{0} {1}\n'.format(node, table.export_row(attr))) # Export edges if len(graph.edges): string_buffer.write('\n@argcs\n' if graph.directed else '\n@edges\n') table = ExportTable(graph.edges) string_buffer.write(' {0}\n'.format(table.header or '_')) for edge, attr in graph.edges.items(): string_buffer.write('{0} {1} {2}\n'.format( edge[0], edge[1], table.export_row(attr))) logger.info('Graph {0} exported in LEMON graph format'.format(repr(graph))) # Reset buffer cursor string_buffer.seek(0) return string_buffer.read()
def write_lgr(graph, node_key=None, edge_key=None, node_data_type='string', edge_data_type='void'): """ Export a graph to an LGR data format The LEDA format allows for export of only one node or edge data type (as: |{data type}|). For nodes this is usually the node label and for edges any arbitrary data key,value pair. In both cases the data type is required to be of either: string, int, float or bool. Nodes and edges are exported by iterating over them using `iternodes` and `iteredges`. Iteration uses the graphit Object Relations Mapper (ORM) allowing full control over the data export by overriding the `get` method globally in the 'NodeTools' or 'EdgeTools' classes or using custom classes registered with the ORM. Data returned by the `get` method will be serialized regardless the return type. The node and edge data types are registered globally in the LENA file using `node_data_type` and `edge_data_type` set to 'void' (no data) by default. :param graph: Graph to export :type graph: :graphit:Graph :param node_key: key name of node data to export :type node_key: :py:str :param edge_key: key name of edge data to export :type edge_key: :py:str :param node_data_type: primitive data type of exported node data :type node_data_type: :py:str :param edge_data_type: primitive data type of exported edge data :type edge_data_type: :py:str :return: Graph exported as LGR format :rtype: :py:str :raises: GraphitException """ # Default node_key to graph.data.key_tag if node_key is None: node_key = graph.data.key_tag # If export of node/edge data corresponding data types need to be defined if (node_key is not None and node_data_type == 'void') or (edge_key is not None and edge_data_type == 'void'): raise GraphitException('Define node_data_type and/or edge_data_type') # Create empty file buffer string_buffer = StringIO() # Print header string_buffer.write('#header section\nLEDA.GRAPH\n{0}\n{1}\n'.format( node_data_type, edge_data_type)) string_buffer.write('{0}\n'.format(-1 if graph.directed else -2)) # Print nodes string_buffer.write('#nodes section\n{0}\n'.format(len(graph.nodes))) node_mapping = {} for i, node in enumerate(graph.iternodes(), start=1): string_buffer.write('|{{{0}}}|\n'.format( str(node.get(node_key, default='')))) node_mapping[node.nid] = i # Print edges string_buffer.write('#edges section\n{0}\n'.format(len(graph.edges))) for edge in graph.iteredges(): source, target = edge.nid string_buffer.write('{0} {1} 0 |{{{2}}}|\n'.format( node_mapping[source], node_mapping[target], str(edge.get(edge_key, default='')))) logger.info('Graph {0} exported in LEDA format'.format(repr(graph))) # Reset buffer cursor string_buffer.seek(0) return string_buffer.read()