def get_function_return_names(fct): """ Return variable name(s) or return statement of the given function """ lines = inspect.getsourcelines(fct) outputs = None for line in lines[0][::-1]: stripped = line.strip() if 'def' in stripped: # NOTE: This work as def is a reserved keyword which will trigger # invalid syntax if misused msg = 'No return statement found in {}' raise PyungoError(msg.format(fct.__name__)) ast_tree = ast.parse(stripped) for ast_node in ast.walk(ast_tree): if isinstance(ast_node, ast.Return): if isinstance(ast_node.value, ast.Name): outputs = [ast_node.value.id] elif isinstance(ast_node.value, ast.Tuple): outputs = [ elt.id for elt in ast_node.value.elts if isinstance(elt, ast.Name) ] else: name = ast_node.value.__class__.__name__ msg = ('Variable name or Tuple of variable names are ' 'expected, got {}'.format(name)) raise PyungoError(msg) break if outputs: break return outputs
def _process_inputs(self, inputs, is_arg=False, is_kwarg=False): """ converter data passed to Input objects and store them """ # if inputs are None, we inspect the function signature if inputs is None: inputs = list(inspect.signature(self._fct).parameters.keys()) for input_ in inputs: if isinstance(input_, Input): new_input = input_ elif isinstance(input_, str): if is_arg: new_input = Input.arg(input_) elif is_kwarg: new_input = Input.kwarg(input_) else: new_input = Input(input_) elif isinstance(input_, dict): if len(input_) != 1: msg = ('dict inputs should have only one key ' 'and cannot be empty') raise PyungoError(msg) key = next(iter(input_)) value = input_[key] new_input = Input.constant(key, value) else: msg = 'inputs need to be of type Input, str or dict' raise PyungoError(msg) self._inputs.append(new_input)
def _register(self, f, **kwargs): """ check if all needed inputs are there and create a new node """ inputs = kwargs.get('inputs') if not inputs: raise PyungoError('Missing inputs parameter') outputs = kwargs.get('outputs') if not outputs: raise PyungoError('Missing outputs parameters') args_names = kwargs.get('args') kwargs_names = kwargs.get('kwargs') self._create_node(f, inputs, outputs, args_names, kwargs_names)
def check_inputs(self, sim_inputs, sim_outputs, sim_kwargs): """ make sure data inputs provided are good enough """ data_inputs = set(self.inputs.keys()) diff = data_inputs - (data_inputs - set(sim_outputs)) if diff: msg = 'The following inputs are already used in the model: {}' raise PyungoError(msg.format(list(diff))) inputs_to_provide = set(sim_inputs) - set(sim_outputs) diff = inputs_to_provide - data_inputs if diff: msg = 'The following inputs are needed: {}'.format(list(diff)) raise PyungoError(msg) diff = data_inputs - inputs_to_provide - set(sim_kwargs) if diff: msg = 'The following inputs are not used by the model: {}' raise PyungoError(msg.format(list(diff)))
def topological_sort(data): """ Topological sort algorithm Args: data (dict): dictionnary representing dependencies Example: {'a': ['b', 'c']} node id 'a' depends on node id 'b' and 'c' Returns: ordered (list): list of list of node ids Example: [['a'], ['b', 'c'], ['d']] The sequence is representing the order to be run. The nested lists are node ids that can be run in parallel Raises: PyungoError: In case a cyclic dependency exists """ for key in data: data[key] = set(data[key]) for k, v in data.items(): v.discard(k) # ignore self dependencies extra_items_in_deps = reduce(set.union, data.values()) - set(data.keys()) data.update({item: set() for item in extra_items_in_deps}) while True: ordered = set(item for item, dep in data.items() if not dep) if not ordered: break yield sorted(ordered) data = { item: (dep - ordered) for item, dep in data.items() if item not in ordered } if data: raise PyungoError('A cyclic dependency exists amongst {}'.format(data))
def _create_node(self, fct, inputs, outputs, args_names, kwargs_names): """ create a save the node to the graph """ inputs = get_if_exists(inputs, self._inputs) outputs = get_if_exists(outputs, self._outputs) node = Node(fct, inputs, outputs, args_names, kwargs_names) # assume that we cannot have two nodes with the same output names for n in self._nodes.values(): for out_name in n.output_names: if out_name in node.output_names: msg = '{} output already exist'.format(out_name) raise PyungoError(msg) self._nodes[node.id] = node
def set_value_to_input(self, input_name, value): """ set a value to the targeted input name Args: input_name (str): Name of the input value: value to be assigned to the input Raises: PyungoError: In case the input name is unknown """ for input_ in self._inputs: if input_.name == input_name: input_.value = value return msg = 'input "{}" does not exist in this node'.format(input_name) raise PyungoError(msg)