summaryrefslogtreecommitdiff
path: root/grc/python/base/FlowGraph.py
diff options
context:
space:
mode:
Diffstat (limited to 'grc/python/base/FlowGraph.py')
-rw-r--r--grc/python/base/FlowGraph.py482
1 files changed, 482 insertions, 0 deletions
diff --git a/grc/python/base/FlowGraph.py b/grc/python/base/FlowGraph.py
new file mode 100644
index 0000000000..42faab6a16
--- /dev/null
+++ b/grc/python/base/FlowGraph.py
@@ -0,0 +1,482 @@
+"""
+Copyright 2008-2011 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion 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 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion 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, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+"""
+
+import time
+from operator import methodcaller
+from itertools import ifilter
+
+from grc.gui import Messages
+
+from . import odict
+from .Element import Element
+from .Constants import FLOW_GRAPH_FILE_FORMAT_VERSION
+
+
+class FlowGraph(Element):
+
+ def __init__(self, platform):
+ """
+ Make a flow graph from the arguments.
+
+ Args:
+ platform: a platforms with blocks and contrcutors
+
+ Returns:
+ the flow graph object
+ """
+ #initialize
+ Element.__init__(self, platform)
+ self._elements = []
+ self._timestamp = time.ctime()
+ #inital blank import
+ self.import_data()
+
+ def _get_unique_id(self, base_id=''):
+ """
+ Get a unique id starting with the base id.
+
+ Args:
+ base_id: the id starts with this and appends a count
+
+ Returns:
+ a unique id
+ """
+ index = 0
+ while True:
+ id = '%s_%d' % (base_id, index)
+ index += 1
+ #make sure that the id is not used by another block
+ if not filter(lambda b: b.get_id() == id, self.get_blocks()): return id
+
+ def __str__(self):
+ return 'FlowGraph - %s(%s)' % (self.get_option('title'), self.get_option('id'))
+
+ def get_complexity(self):
+ """
+ Determines the complexity of a flowgraph
+ """
+ dbal = 0
+ block_list = self.get_blocks()
+ for block in block_list:
+ # Skip options block
+ if block.get_key() == 'options':
+ continue
+
+ # Don't worry about optional sinks?
+ sink_list = filter(lambda c: not c.get_optional(), block.get_sinks())
+ source_list = filter(lambda c: not c.get_optional(), block.get_sources())
+ sinks = float(len(sink_list))
+ sources = float(len(source_list))
+ base = max(min(sinks, sources), 1)
+
+ # Port ratio multiplier
+ if min(sinks, sources) > 0:
+ multi = sinks / sources
+ multi = (1 / multi) if multi > 1 else multi
+ else:
+ multi = 1
+
+ # Connection ratio multiplier
+ sink_multi = max(float(sum(map(lambda c: len(c.get_connections()), sink_list)) / max(sinks, 1.0)), 1.0)
+ source_multi = max(float(sum(map(lambda c: len(c.get_connections()), source_list)) / max(sources, 1.0)), 1.0)
+ dbal = dbal + (base * multi * sink_multi * source_multi)
+
+ elements = float(len(self.get_elements()))
+ connections = float(len(self.get_connections()))
+ disabled_connections = len(filter(lambda c: not c.get_enabled(), self.get_connections()))
+ blocks = float(len(block_list))
+ variables = elements - blocks - connections
+ enabled = float(len(self.get_enabled_blocks()))
+
+ # Disabled multiplier
+ if enabled > 0:
+ disabled_multi = 1 / (max(1 - ((blocks - enabled) / max(blocks, 1)), 0.05))
+ else:
+ disabled_multi = 1
+
+ # Connection multiplier (How many connections )
+ if (connections - disabled_connections) > 0:
+ conn_multi = 1 / (max(1 - (disabled_connections / max(connections, 1)), 0.05))
+ else:
+ conn_multi = 1
+
+ final = round(max((dbal - 1) * disabled_multi * conn_multi * connections, 0.0) / 1000000, 6)
+ return final
+
+ def rewrite(self):
+ def refactor_bus_structure():
+
+ for block in self.get_blocks():
+ for direc in ['source', 'sink']:
+ if direc == 'source':
+ get_p = block.get_sources;
+ get_p_gui = block.get_sources_gui;
+ bus_structure = block.form_bus_structure('source');
+ else:
+ get_p = block.get_sinks;
+ get_p_gui = block.get_sinks_gui
+ bus_structure = block.form_bus_structure('sink');
+
+ if 'bus' in map(lambda a: a.get_type(), get_p_gui()):
+ if len(get_p_gui()) > len(bus_structure):
+ times = range(len(bus_structure), len(get_p_gui()));
+ for i in times:
+ for connect in get_p_gui()[-1].get_connections():
+ block.get_parent().remove_element(connect);
+ get_p().remove(get_p_gui()[-1]);
+ elif len(get_p_gui()) < len(bus_structure):
+ n = {'name':'bus','type':'bus'};
+ if True in map(lambda a: isinstance(a.get_nports(), int), get_p()):
+ n['nports'] = str(1);
+
+ times = range(len(get_p_gui()), len(bus_structure));
+
+ for i in times:
+ n['key'] = str(len(get_p()));
+ n = odict(n);
+ port = block.get_parent().get_parent().Port(block=block, n=n, dir=direc);
+ get_p().append(port);
+
+ for child in self.get_children(): child.rewrite()
+ refactor_bus_structure()
+
+ def get_option(self, key):
+ """
+ Get the option for a given key.
+ The option comes from the special options block.
+
+ Args:
+ key: the param key for the options block
+
+ Returns:
+ the value held by that param
+ """
+ return self._options_block.get_param(key).get_evaluated()
+
+ def is_flow_graph(self): return True
+
+ ##############################################
+ ## Access Elements
+ ##############################################
+ def get_block(self, id):
+ for block in self.iter_blocks():
+ if block.get_id() == id:
+ return block
+ raise KeyError('No block with ID {0!r}'.format(id))
+
+ def iter_blocks(self):
+ return ifilter(methodcaller('is_block'), self.get_elements())
+
+ def get_blocks(self):
+ return list(self.iter_blocks())
+
+ def iter_connections(self):
+ return ifilter(methodcaller('is_connection'), self.get_elements())
+
+ def get_connections(self):
+ return list(self.iter_connections())
+
+ def get_elements(self):
+ """
+ Get a list of all the elements.
+ Always ensure that the options block is in the list (only once).
+
+ Returns:
+ the element list
+ """
+ options_block_count = self._elements.count(self._options_block)
+ if not options_block_count:
+ self._elements.append(self._options_block)
+ for i in range(options_block_count-1):
+ self._elements.remove(self._options_block)
+ return self._elements
+
+ get_children = get_elements
+
+ def iter_enabled_blocks(self):
+ """
+ Get an iterator of all blocks that are enabled and not bypassed.
+ """
+ return ifilter(methodcaller('get_enabled'), self.iter_blocks())
+
+ def get_enabled_blocks(self):
+ """
+ Get a list of all blocks that are enabled and not bypassed.
+
+ Returns:
+ a list of blocks
+ """
+ return list(self.iter_enabled_blocks())
+
+ def get_bypassed_blocks(self):
+ """
+ Get a list of all blocks that are bypassed.
+
+ Returns:
+ a list of blocks
+ """
+ return filter(methodcaller('get_bypassed'), self.iter_blocks())
+
+ def get_enabled_connections(self):
+ """
+ Get a list of all connections that are enabled.
+
+ Returns:
+ a list of connections
+ """
+ return filter(methodcaller('get_enabled'), self.get_connections())
+
+ def get_new_block(self, key):
+ """
+ Get a new block of the specified key.
+ Add the block to the list of elements.
+
+ Args:
+ key: the block key
+
+ Returns:
+ the new block or None if not found
+ """
+ if key not in self.get_parent().get_block_keys(): return None
+ block = self.get_parent().get_new_block(self, key)
+ self.get_elements().append(block);
+ if block._bussify_sink:
+ block.bussify({'name':'bus','type':'bus'}, 'sink')
+ if block._bussify_source:
+ block.bussify({'name':'bus','type':'bus'}, 'source')
+ return block;
+
+ def connect(self, porta, portb):
+ """
+ Create a connection between porta and portb.
+
+ Args:
+ porta: a port
+ portb: another port
+ @throw Exception bad connection
+
+ Returns:
+ the new connection
+ """
+ connection = self.get_parent().Connection(flow_graph=self, porta=porta, portb=portb)
+ self.get_elements().append(connection)
+ return connection
+
+ def remove_element(self, element):
+ """
+ Remove the element from the list of elements.
+ If the element is a port, remove the whole block.
+ If the element is a block, remove its connections.
+ If the element is a connection, just remove the connection.
+ """
+ if element not in self.get_elements(): return
+ #found a port, set to parent signal block
+ if element.is_port():
+ element = element.get_parent()
+ #remove block, remove all involved connections
+ if element.is_block():
+ for port in element.get_ports():
+ map(self.remove_element, port.get_connections())
+ if element.is_connection():
+ if element.is_bus():
+ cons_list = []
+ for i in map(lambda a: a.get_connections(), element.get_source().get_associated_ports()):
+ cons_list.extend(i);
+ map(self.remove_element, cons_list);
+ self.get_elements().remove(element)
+
+ def evaluate(self, expr):
+ """
+ Evaluate the expression.
+
+ Args:
+ expr: the string expression
+ @throw NotImplementedError
+ """
+ raise NotImplementedError
+
+ ##############################################
+ ## Import/Export Methods
+ ##############################################
+ def export_data(self):
+ """
+ Export this flow graph to nested data.
+ Export all block and connection data.
+
+ Returns:
+ a nested data odict
+ """
+ # sort blocks and connections for nicer diffs
+ blocks = sorted(self.iter_blocks(), key=lambda b: (
+ b.get_key() != 'options', # options to the front
+ not b.get_key().startswith('variable'), # then vars
+ str(b)
+ ))
+ connections = sorted(self.get_connections(), key=str)
+ n = odict()
+ n['timestamp'] = self._timestamp
+ n['block'] = [b.export_data() for b in blocks]
+ n['connection'] = [c.export_data() for c in connections]
+ instructions = odict({
+ 'created': self.get_parent().get_version_short(),
+ 'format': FLOW_GRAPH_FILE_FORMAT_VERSION,
+ })
+ return odict({'flow_graph': n, '_instructions': instructions})
+
+ def import_data(self, n=None):
+ """
+ Import blocks and connections into this flow graph.
+ Clear this flowgraph of all previous blocks and connections.
+ Any blocks or connections in error will be ignored.
+
+ Args:
+ n: the nested data odict
+ """
+ errors = False
+ self._elements = list() # remove previous elements
+ # set file format
+ try:
+ instructions = n.find('_instructions') or {}
+ file_format = int(instructions.get('format', '0')) or _guess_file_format_1(n)
+ except:
+ file_format = 0
+
+ fg_n = n and n.find('flow_graph') or odict() # use blank data if none provided
+ self._timestamp = fg_n.find('timestamp') or time.ctime()
+
+ # build the blocks
+ self._options_block = self.get_parent().get_new_block(self, 'options')
+ for block_n in fg_n.findall('block'):
+ key = block_n.find('key')
+ block = self._options_block if key == 'options' else self.get_new_block(key)
+
+ if not block: # looks like this block key cannot be found
+ # create a dummy block instead
+ block = self.get_new_block('dummy_block')
+ # Ugly ugly ugly
+ _initialize_dummy_block(block, block_n)
+ Messages.send_error_msg_load('Block key "%s" not found' % key)
+
+ block.import_data(block_n)
+
+ # build the connections
+ def verify_and_get_port(key, block, dir):
+ ports = block.get_sinks() if dir == 'sink' else block.get_sources()
+ for port in ports:
+ if key == port.get_key():
+ break
+ if not key.isdigit() and port.get_type() == '' and key == port.get_name():
+ break
+ else:
+ if block.is_dummy_block():
+ port = _dummy_block_add_port(block, key, dir)
+ else:
+ raise LookupError('%s key %r not in %s block keys' % (dir, key, dir))
+ return port
+
+ for connection_n in fg_n.findall('connection'):
+ # get the block ids and port keys
+ source_block_id = connection_n.find('source_block_id')
+ sink_block_id = connection_n.find('sink_block_id')
+ source_key = connection_n.find('source_key')
+ sink_key = connection_n.find('sink_key')
+ try:
+ source_block = self.get_block(source_block_id)
+ sink_block = self.get_block(sink_block_id)
+
+ # fix old, numeric message ports keys
+ if file_format < 1:
+ source_key, sink_key = _update_old_message_port_keys(
+ source_key, sink_key, source_block, sink_block)
+
+ # build the connection
+ source_port = verify_and_get_port(source_key, source_block, 'source')
+ sink_port = verify_and_get_port(sink_key, sink_block, 'sink')
+ self.connect(source_port, sink_port)
+ except LookupError as e:
+ Messages.send_error_load(
+ 'Connection between %s(%s) and %s(%s) could not be made.\n\t%s' % (
+ source_block_id, source_key, sink_block_id, sink_key, e))
+ errors = True
+
+ self.rewrite() # global rewrite
+ return errors
+
+
+def _update_old_message_port_keys(source_key, sink_key, source_block, sink_block):
+ """Backward compatibility for message port keys
+
+ Message ports use their names as key (like in the 'connect' method).
+ Flowgraph files from former versions still have numeric keys stored for
+ message connections. These have to be replaced by the name of the
+ respective port. The correct message port is deduced from the integer
+ value of the key (assuming the order has not changed).
+
+ The connection ends are updated only if both ends translate into a
+ message port.
+ """
+ try:
+ # get ports using the "old way" (assuming liner indexed keys)
+ source_port = source_block.get_sources()[int(source_key)]
+ sink_port = sink_block.get_sinks()[int(sink_key)]
+ if source_port.get_type() == "message" and sink_port.get_type() == "message":
+ source_key, sink_key = source_port.get_key(), sink_port.get_key()
+ except (ValueError, IndexError):
+ pass
+ return source_key, sink_key # do nothing
+
+
+def _guess_file_format_1(n):
+ """Try to guess the file format for flow-graph files without version tag"""
+ try:
+ has_non_numeric_message_keys = any(not (
+ connection_n.find('source_key').isdigit() and
+ connection_n.find('sink_key').isdigit()
+ ) for connection_n in n.find('flow_graph').findall('connection'))
+ if has_non_numeric_message_keys:
+ return 1
+ except:
+ pass
+ return 0
+
+
+def _initialize_dummy_block(block, block_n):
+ """This is so ugly... dummy-fy a block
+
+ Modify block object to get the behaviour for a missing block
+ """
+ block._key = block_n.find('key')
+ block.is_dummy_block = lambda: True
+ block.is_valid = lambda: False
+ block.get_enabled = lambda: False
+ for param_n in block_n.findall('param'):
+ if param_n['key'] not in block.get_param_keys():
+ new_param_n = odict({'key': param_n['key'], 'name': param_n['key'], 'type': 'string'})
+ block.get_params().append(block.get_parent().get_parent().Param(block=block, n=new_param_n))
+
+
+def _dummy_block_add_port(block, key, dir):
+ """This is so ugly... Add a port to a dummy-field block"""
+ port_n = odict({'name': '?', 'key': key, 'type': ''})
+ port = block.get_parent().get_parent().Port(block=block, n=port_n, dir=dir)
+ if port.is_source():
+ block.get_sources().append(port)
+ else:
+ block.get_sinks().append(port)
+ return port