def analyse_neuron(self, neuron): # type: (ASTNeuron) -> None """ Analyse and transform a single neuron. :param neuron: a single neuron. """ code, message = Messages.get_start_processing_neuron(neuron.get_name()) Logger.log_message(neuron, code, message, neuron.get_source_position(), LoggingLevel.INFO) # make normalization # apply spikes to buffers # get rid of convolve, store them and apply then at the end equations_block = neuron.get_equations_block() shape_to_buffers = {} if neuron.get_equations_block() is not None: # extract function names and corresponding incoming buffers convolve_calls = OdeTransformer.get_sum_function_calls( equations_block) for convolve in convolve_calls: shape_to_buffers[str(convolve.get_args()[0])] = str( convolve.get_args()[1]) OdeTransformer.refactor_convolve_call(neuron.get_equations_block()) self.make_functions_self_contained( equations_block.get_ode_functions()) self.replace_functions_through_defining_expressions( equations_block.get_ode_equations(), equations_block.get_ode_functions()) # transform everything into gsl processable (e.g. no functional shapes) or exact form. self.transform_shapes_and_odes(neuron, shape_to_buffers) self.apply_spikes_from_buffers(neuron, shape_to_buffers) # update the symbol table symbol_table_visitor = ASTSymbolTableVisitor() symbol_table_visitor.after_ast_rewrite_ = True # ODE block might have been removed entirely: suppress warnings neuron.accept(symbol_table_visitor)
def analyse_and_generate_neuron(neuron): # type: (ASTNeuron) -> None """ Analysis a single neuron, solves it and generates the corresponding code. :param neuron: a single neuron. """ code, message = Messages.get_start_processing_neuron(neuron.get_name()) Logger.log_message(neuron, code, message, neuron.get_source_position(), LoggingLevel.INFO) # make normalization # apply spikes to buffers # get rid of convolve, store them and apply then at the end equations_block = neuron.get_equations_block() shape_to_buffers = {} if neuron.get_equations_block() is not None: # extract function names and corresponding incoming buffers convolve_calls = OdeTransformer.get_sum_function_calls(equations_block) for convolve in convolve_calls: shape_to_buffers[str(convolve.get_args()[0])] = str(convolve.get_args()[1]) OdeTransformer.refactor_convolve_call(neuron.get_equations_block()) make_functions_self_contained(equations_block.get_ode_functions()) replace_functions_through_defining_expressions(equations_block.get_ode_equations(), equations_block.get_ode_functions()) # transform everything into gsl processable (e.g. no functional shapes) or exact form. transform_shapes_and_odes(neuron, shape_to_buffers) # update the symbol table neuron.accept(ASTSymbolTableVisitor()) generate_nest_code(neuron) # now store the transformed model store_transformed_model(neuron) # at that point all shapes are transformed into the ODE form and spikes can be applied code, message = Messages.get_code_generated(neuron.get_name(), FrontendConfiguration.get_target_path()) Logger.log_message(neuron, code, message, neuron.get_source_position(), LoggingLevel.INFO)
def analyse_neuron(self, neuron): # type: (ASTNeuron) -> None """ Analyse and transform a single neuron. :param neuron: a single neuron. """ code, message = Messages.get_start_processing_neuron(neuron.get_name()) Logger.log_message(neuron, code, message, neuron.get_source_position(), LoggingLevel.INFO) # make normalization # apply spikes to buffers # get rid of convolve, store them and apply then at the end equations_block = neuron.get_equations_block() shape_to_buffers = {} if neuron.get_equations_block() is not None: # extract function names and corresponding incoming buffers convolve_calls = OdeTransformer.get_sum_function_calls(equations_block) for convolve in convolve_calls: shape_to_buffers[str(convolve.get_args()[0])] = str(convolve.get_args()[1]) OdeTransformer.refactor_convolve_call(neuron.get_equations_block()) self.make_functions_self_contained(equations_block.get_ode_functions()) self.replace_functions_through_defining_expressions(equations_block.get_ode_equations(), equations_block.get_ode_functions()) # transform everything into gsl processable (e.g. no functional shapes) or exact form. self.transform_shapes_and_odes(neuron, shape_to_buffers) self.apply_spikes_from_buffers(neuron, shape_to_buffers) # update the symbol table symbol_table_visitor = ASTSymbolTableVisitor() symbol_table_visitor.after_ast_rewrite_ = True # ODE block might have been removed entirely: suppress warnings neuron.accept(symbol_table_visitor)
def setup_model_generation_helpers(self, neuron: ASTNeuron): """ Returns a namespace for Jinja2 neuron model documentation template. :param neuron: a single neuron instance :type neuron: ASTNeuron :return: a map from name to functionality. :rtype: dict """ converter = LatexReferenceConverter() latex_expression_printer = LatexExpressionPrinter(converter) namespace = dict() namespace['now'] = datetime.datetime.utcnow() namespace['neuron'] = neuron namespace['neuronName'] = str(neuron.get_name()) namespace['printer'] = NestPrinter(latex_expression_printer) namespace['assignments'] = NestAssignmentsHelper() namespace['names'] = NestNamesConverter() namespace['declarations'] = NestDeclarationsHelper() namespace['utils'] = ASTUtils() namespace['odeTransformer'] = OdeTransformer() import textwrap pre_comments_bak = neuron.pre_comments neuron.pre_comments = [] namespace['neuron_source_code'] = textwrap.indent( neuron.__str__(), " ") neuron.pre_comments = pre_comments_bak return namespace
def setup_index_generation_helpers(self, neurons: List[ASTNeuron]): """ Returns a namespace for Jinja2 neuron model index page template. :param neurons: a list of neuron instances :type neurons: List[ASTNeuron] :return: a map from name to functionality. :rtype: dict """ converter = LatexReferenceConverter() latex_expression_printer = LatexExpressionPrinter(converter) namespace = dict() namespace['now'] = datetime.datetime.utcnow() namespace['neurons'] = neurons namespace['neuronNames'] = [ str(neuron.get_name()) for neuron in neurons ] namespace['printer'] = NestPrinter(latex_expression_printer) namespace['assignments'] = NestAssignmentsHelper() namespace['names'] = NestNamesConverter() namespace['declarations'] = NestDeclarationsHelper() namespace['utils'] = ASTUtils() namespace['odeTransformer'] = OdeTransformer() return namespace
def generate_kernel_buffers_(self, neuron, equations_block): """ For every occurrence of a convolution of the form `convolve(var, spike_buf)`: add the element `(kernel, spike_buf)` to the set, with `kernel` being the kernel that contains variable `var`. """ kernel_buffers = set() convolve_calls = OdeTransformer.get_convolve_function_calls( equations_block) for convolve in convolve_calls: el = (convolve.get_args()[0], convolve.get_args()[1]) sym = convolve.get_args()[0].get_scope().resolve_to_symbol( convolve.get_args()[0].get_variable().name, SymbolKind.VARIABLE) if sym is None: raise Exception( "No initial value(s) defined for kernel with variable \"" + convolve.get_args()[0].get_variable().get_complete_name() + "\"") if sym.block_type == BlockType.INPUT_BUFFER_SPIKE: el = (el[1], el[0]) # find the corresponding kernel object var = el[0].get_variable() assert var is not None kernel = neuron.get_kernel_by_name(var.get_name()) assert kernel is not None, "In convolution \"convolve(" + str( var.name) + ", " + str( el[1]) + ")\": no kernel by name \"" + var.get_name( ) + "\" found in neuron." el = (kernel, el[1]) kernel_buffers.add(el) return kernel_buffers
def get_delta_factors_(self, neuron, equations_block): r""" For every occurrence of a convolution of the form `x^(n) = a * convolve(kernel, inport) + ...` where `kernel` is a delta function, add the element `(x^(n), inport) --> a` to the set. """ delta_factors = {} for ode_eq in equations_block.get_ode_equations(): var = ode_eq.get_lhs() expr = ode_eq.get_rhs() conv_calls = OdeTransformer.get_convolve_function_calls(expr) for conv_call in conv_calls: assert len( conv_call.args ) == 2, "convolve() function call should have precisely two arguments: kernel and spike buffer" kernel = conv_call.args[0] if is_delta_kernel( neuron.get_kernel_by_name( kernel.get_variable().get_name())): inport = conv_call.args[1].get_variable() expr_str = str(expr) sympy_expr = sympy.parsing.sympy_parser.parse_expr( expr_str) sympy_expr = sympy.expand(sympy_expr) sympy_conv_expr = sympy.parsing.sympy_parser.parse_expr( str(conv_call)) factor_str = [] for term in sympy.Add.make_args(sympy_expr): if term.find(sympy_conv_expr): factor_str.append( str(term.replace(sympy_conv_expr, 1))) factor_str = " + ".join(factor_str) delta_factors[(var, inport)] = factor_str return delta_factors
def apply_incoming_spikes(neuron): """ Adds a set of update instructions to the handed over neuron. :param neuron: a single neuron instance :type neuron: ASTNeuron :return: the modified neuron :rtype: ASTNeuron """ assert (neuron is not None and isinstance(neuron, ASTNeuron)), \ '(PyNestML.Solver.BaseTransformer) No or wrong type of neuron provided (%s)!' % type(neuron) conv_calls = OdeTransformer.get_sum_function_calls(neuron) printer = ExpressionsPrettyPrinter() spikes_updates = list() for convCall in conv_calls: shape = convCall.get_args()[0].get_variable().get_complete_name() buffer = convCall.get_args()[1].get_variable().get_complete_name() initial_values = ( neuron.get_initial_values_blocks().get_declarations() if neuron.get_initial_values_blocks() is not None else list()) for astDeclaration in initial_values: for variable in astDeclaration.get_variables(): if re.match(shape + "[\']*", variable.get_complete_name()) or re.match( shape + '__[\\d]+$', variable.get_complete_name()): spikes_updates.append( ModelParser.parse_assignment( variable.get_complete_name() + " += " + buffer + " * " + printer.print_expression( astDeclaration.get_expression()))) for update in spikes_updates: add_assignment_to_update_block(update, neuron) return neuron
def setup_generation_helpers(neuron): """ Returns a standard namespace with often required functionality. :param neuron: a single neuron instance :type neuron: ASTNeuron :return: a map from name to functionality. :rtype: dict """ gsl_converter = GSLReferenceConverter() gsl_printer = LegacyExpressionPrinter(gsl_converter) # helper classes and objects converter = NESTReferenceConverter(False) legacy_pretty_printer = LegacyExpressionPrinter(converter) namespace = dict() namespace['neuronName'] = neuron.get_name() namespace['neuron'] = neuron namespace['moduleName'] = FrontendConfiguration.get_module_name() namespace['printer'] = NestPrinter(legacy_pretty_printer) namespace['assignments'] = NestAssignmentsHelper() namespace['names'] = NestNamesConverter() namespace['declarations'] = NestDeclarationsHelper() namespace['utils'] = ASTUtils() namespace['idemPrinter'] = LegacyExpressionPrinter() namespace['outputEvent'] = namespace['printer'].print_output_event(neuron.get_body()) namespace['is_spike_input'] = ASTUtils.is_spike_input(neuron.get_body()) namespace['is_current_input'] = ASTUtils.is_current_input(neuron.get_body()) namespace['odeTransformer'] = OdeTransformer() namespace['printerGSL'] = gsl_printer namespace['now'] = datetime.datetime.utcnow() define_solver_type(neuron, namespace) return namespace
def apply_incoming_spikes(neuron): """ Adds a set of update instructions to the handed over neuron. :param neuron: a single neuron instance :type neuron: ASTNeuron :return: the modified neuron :rtype: ASTNeuron """ assert (neuron is not None and isinstance(neuron, ASTNeuron)), \ '(PyNestML.Solver.BaseTransformer) No or wrong type of neuron provided (%s)!' % type(neuron) conv_calls = OdeTransformer.get_sum_function_calls(neuron) printer = ExpressionsPrettyPrinter() spikes_updates = list() for convCall in conv_calls: shape = convCall.get_args()[0].get_variable().get_complete_name() buffer = convCall.get_args()[1].get_variable().get_complete_name() initial_values = ( neuron.get_initial_values_blocks().get_declarations() if neuron.get_initial_values_blocks() is not None else list()) for astDeclaration in initial_values: for variable in astDeclaration.get_variables(): if re.match(shape + "[\']*", variable.get_complete_name()) or re.match(shape + '__[\\d]+$', variable.get_complete_name()): spikes_updates.append(ModelParser.parse_assignment( variable.get_complete_name() + " += " + buffer + " * " + printer.print_expression( astDeclaration.get_expression()))) for update in spikes_updates: add_assignment_to_update_block(update, neuron) return neuron
def transform_functions_json(equations_block): # type: (ASTEquationsBlock) -> list[dict[str, str]] """ Converts AST node to a JSON representation :param equations_block:equations_block :return: json mapping: {odes: [...], shape: [...]} """ equations_block = OdeTransformer.refactor_convolve_call(equations_block) result = [] for fun in equations_block.get_functions(): result.append({"symbol": fun.getVariableName(), "definition": _printer.print_expression(fun.get_expression())}) return result
def transform_functions_json(self, equations_block): # type: (ASTEquationsBlock) -> list[dict[str, str]] """ Converts AST node to a JSON representation :param equations_block:equations_block :return: json mapping: {odes: [...], shape: [...]} """ equations_block = OdeTransformer.refactor_convolve_call(equations_block) result = [] for fun in equations_block.get_functions(): result.append({"symbol": fun.get_variable_name(), "definition": self._printer.print_expression(fun.get_expression())}) return result
def setup_generation_helpers(self, neuron): """ Returns a standard namespace with often required functionality. :param neuron: a single neuron instance :type neuron: ASTNeuron :return: a map from name to functionality. :rtype: dict """ gsl_converter = GSLReferenceConverter() gsl_printer = LegacyExpressionPrinter(gsl_converter) # helper classes and objects converter = NESTReferenceConverter(False) legacy_pretty_printer = LegacyExpressionPrinter(converter) namespace = dict() namespace['neuronName'] = neuron.get_name() namespace['neuron'] = neuron namespace['moduleName'] = FrontendConfiguration.get_module_name() namespace['printer'] = NestPrinter(legacy_pretty_printer) namespace['assignments'] = NestAssignmentsHelper() namespace['names'] = NestNamesConverter() namespace['declarations'] = NestDeclarationsHelper() namespace['utils'] = ASTUtils() namespace['idemPrinter'] = LegacyExpressionPrinter() namespace['outputEvent'] = namespace['printer'].print_output_event(neuron.get_body()) namespace['is_spike_input'] = ASTUtils.is_spike_input(neuron.get_body()) namespace['is_current_input'] = ASTUtils.is_current_input(neuron.get_body()) namespace['odeTransformer'] = OdeTransformer() namespace['printerGSL'] = gsl_printer namespace['now'] = datetime.datetime.utcnow() namespace['tracing'] = FrontendConfiguration.is_dev namespace['PredefinedUnits'] = pynestml.symbols.predefined_units.PredefinedUnits namespace['UnitTypeSymbol'] = pynestml.symbols.unit_type_symbol.UnitTypeSymbol rng_visitor = ASTRandomNumberGeneratorVisitor() neuron.accept(rng_visitor) namespace['norm_rng'] = rng_visitor._norm_rng_is_used self.define_solver_type(neuron, namespace) return namespace
def setup_generation_helpers(self, neuron: ASTNeuron) -> Dict: """ Returns a standard namespace with often required functionality. :param neuron: a single neuron instance :type neuron: ASTNeuron :return: a map from name to functionality. :rtype: dict """ gsl_converter = GSLReferenceConverter() gsl_printer = UnitlessExpressionPrinter(gsl_converter) # helper classes and objects converter = NESTReferenceConverter(False) unitless_pretty_printer = UnitlessExpressionPrinter(converter) namespace = dict() namespace['neuronName'] = neuron.get_name() namespace['neuron'] = neuron namespace['moduleName'] = FrontendConfiguration.get_module_name() namespace['printer'] = NestPrinter(unitless_pretty_printer) namespace['assignments'] = NestAssignmentsHelper() namespace['names'] = NestNamesConverter() namespace['declarations'] = NestDeclarationsHelper() namespace['utils'] = ASTUtils() namespace['idemPrinter'] = UnitlessExpressionPrinter() namespace['outputEvent'] = namespace['printer'].print_output_event( neuron.get_body()) namespace['is_spike_input'] = ASTUtils.is_spike_input( neuron.get_body()) namespace['is_current_input'] = ASTUtils.is_current_input( neuron.get_body()) namespace['odeTransformer'] = OdeTransformer() namespace['printerGSL'] = gsl_printer namespace['now'] = datetime.datetime.utcnow() namespace['tracing'] = FrontendConfiguration.is_dev namespace[ 'PredefinedUnits'] = pynestml.symbols.predefined_units.PredefinedUnits namespace[ 'UnitTypeSymbol'] = pynestml.symbols.unit_type_symbol.UnitTypeSymbol namespace['initial_values'] = {} namespace['uses_analytic_solver'] = neuron.get_name() in self.analytic_solver.keys() \ and self.analytic_solver[neuron.get_name()] is not None if namespace['uses_analytic_solver']: namespace['analytic_state_variables'] = self.analytic_solver[ neuron.get_name()]["state_variables"] namespace['analytic_variable_symbols'] = { sym: neuron.get_equations_block().get_scope().resolve_to_symbol( sym, SymbolKind.VARIABLE) for sym in namespace['analytic_state_variables'] } namespace['update_expressions'] = {} for sym, expr in self.analytic_solver[ neuron.get_name()]["initial_values"].items(): namespace['initial_values'][sym] = expr for sym in namespace['analytic_state_variables']: expr_str = self.analytic_solver[ neuron.get_name()]["update_expressions"][sym] expr_ast = ModelParser.parse_expression(expr_str) # pretend that update expressions are in "equations" block, which should always be present, as differential equations must have been defined to get here expr_ast.update_scope( neuron.get_equations_blocks().get_scope()) expr_ast.accept(ASTSymbolTableVisitor()) namespace['update_expressions'][sym] = expr_ast namespace['propagators'] = self.analytic_solver[ neuron.get_name()]["propagators"] namespace['uses_numeric_solver'] = neuron.get_name() in self.analytic_solver.keys() \ and self.numeric_solver[neuron.get_name()] is not None if namespace['uses_numeric_solver']: namespace['numeric_state_variables'] = self.numeric_solver[ neuron.get_name()]["state_variables"] namespace['numeric_variable_symbols'] = { sym: neuron.get_equations_block().get_scope().resolve_to_symbol( sym, SymbolKind.VARIABLE) for sym in namespace['numeric_state_variables'] } assert not any([ sym is None for sym in namespace['numeric_variable_symbols'].values() ]) namespace['numeric_update_expressions'] = {} for sym, expr in self.numeric_solver[ neuron.get_name()]["initial_values"].items(): namespace['initial_values'][sym] = expr for sym in namespace['numeric_state_variables']: expr_str = self.numeric_solver[ neuron.get_name()]["update_expressions"][sym] expr_ast = ModelParser.parse_expression(expr_str) # pretend that update expressions are in "equations" block, which should always be present, as differential equations must have been defined to get here expr_ast.update_scope( neuron.get_equations_blocks().get_scope()) expr_ast.accept(ASTSymbolTableVisitor()) namespace['numeric_update_expressions'][sym] = expr_ast namespace['useGSL'] = namespace['uses_numeric_solver'] namespace['names'] = GSLNamesConverter() converter = NESTReferenceConverter(True) unitless_pretty_printer = UnitlessExpressionPrinter(converter) namespace['printer'] = NestPrinter(unitless_pretty_printer) namespace["spike_updates"] = neuron.spike_updates rng_visitor = ASTRandomNumberGeneratorVisitor() neuron.accept(rng_visitor) namespace['norm_rng'] = rng_visitor._norm_rng_is_used return namespace