def ode_toolbox_analysis(self, neuron: ASTNeuron, kernel_buffers: Mapping[ASTKernel, ASTInputPort]): """ Prepare data for ODE-toolbox input format, invoke ODE-toolbox analysis via its API, and return the output. """ assert isinstance( neuron.get_equations_blocks(), ASTEquationsBlock), "only one equation block should be present" equations_block = neuron.get_equations_block() if len(equations_block.get_kernels()) == 0 and len( equations_block.get_ode_equations()) == 0: # no equations defined -> no changes to the neuron return None, None code, message = Messages.get_neuron_analyzed(neuron.get_name()) Logger.log_message(neuron, code, message, neuron.get_source_position(), LoggingLevel.INFO) parameters_block = neuron.get_parameter_blocks() odetoolbox_indict = self.transform_ode_and_kernels_to_json( neuron, parameters_block, kernel_buffers) odetoolbox_indict["options"] = {} odetoolbox_indict["options"]["output_timestep_symbol"] = "__h" solver_result = analysis( odetoolbox_indict, disable_stiffness_check=True, debug=FrontendConfiguration.logging_level == "DEBUG") analytic_solver = None analytic_solvers = [ x for x in solver_result if x["solver"] == "analytical" ] assert len( analytic_solvers ) <= 1, "More than one analytic solver not presently supported" if len(analytic_solvers) > 0: analytic_solver = analytic_solvers[0] # if numeric solver is required, generate a stepping function that includes each state variable numeric_solver = None numeric_solvers = [ x for x in solver_result if x["solver"].startswith("numeric") ] if numeric_solvers: solver_result = analysis( odetoolbox_indict, disable_stiffness_check=True, disable_analytic_solver=True, debug=FrontendConfiguration.logging_level == "DEBUG") numeric_solvers = [ x for x in solver_result if x["solver"].startswith("numeric") ] assert len( numeric_solvers ) <= 1, "More than one numeric solver not presently supported" if len(numeric_solvers) > 0: numeric_solver = numeric_solvers[0] return analytic_solver, numeric_solver
def update_initial_values_for_odes(cls, neuron: ASTNeuron, solver_dicts: List[dict]) -> None: """ Update initial values for original ODE declarations (e.g. V_m', g_ahp'') that are present in the model before ODE-toolbox processing, with the formatted variable names and initial values returned by ODE-toolbox. """ assert isinstance( neuron.get_equations_blocks(), ASTEquationsBlock), "only one equation block should be present" if neuron.get_state_blocks() is None: return for iv_decl in neuron.get_state_blocks().get_declarations(): for var in iv_decl.get_variables(): var_name = var.get_complete_name() if cls.is_ode_variable(var.get_name(), neuron): assert cls.variable_in_solver( cls.to_ode_toolbox_processed_name(var_name), solver_dicts) # replace the left-hand side variable name by the ode-toolbox format var.set_name( cls.to_ode_toolbox_processed_name( var.get_complete_name())) var.set_differential_order(0) # replace the defining expression by the ode-toolbox result iv_expr = cls.get_initial_value_from_ode_toolbox_result( cls.to_ode_toolbox_processed_name(var_name), solver_dicts) assert iv_expr is not None iv_expr = ModelParser.parse_expression(iv_expr) iv_expr.update_scope(neuron.get_state_blocks().get_scope()) iv_decl.set_expression(iv_expr)
def remove_initial_values_for_kernels(cls, neuron: ASTNeuron) -> None: """ Remove initial values for original declarations (e.g. g_in, g_in', V_m); these might conflict with the initial value expressions returned from ODE-toolbox. """ assert isinstance( neuron.get_equations_blocks(), ASTEquationsBlock), "only one equation block should be present" equations_block = neuron.get_equations_block() symbols_to_remove = set() for kernel in equations_block.get_kernels(): for kernel_var in kernel.get_variables(): kernel_var_order = kernel_var.get_differential_order() for order in range(kernel_var_order): symbol_name = kernel_var.get_name() + "'" * order symbols_to_remove.add(symbol_name) decl_to_remove = set() for symbol_name in symbols_to_remove: for decl in neuron.get_state_blocks().get_declarations(): if len(decl.get_variables()) == 1: if decl.get_variables()[0].get_name() == symbol_name: decl_to_remove.add(decl) else: for var in decl.get_variables(): if var.get_name() == symbol_name: decl.variables.remove(var) for decl in decl_to_remove: neuron.get_state_blocks().get_declarations().remove(decl)
def is_ode_variable(cls, var_base_name: str, neuron: ASTNeuron) -> bool: """ Checks if the variable is present in an ODE """ equations_block = neuron.get_equations_blocks() for ode_eq in equations_block.get_ode_equations(): var = ode_eq.get_lhs() if var.get_name() == var_base_name: return True return False
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
def analyse_neuron(self, neuron: ASTNeuron) -> List[ASTAssignment]: """ Analyse and transform a single neuron. :param neuron: a single neuron. :return: spike_updates: list of spike updates, see documentation for get_spike_update_expressions() for more information. """ code, message = Messages.get_start_processing_neuron(neuron.get_name()) Logger.log_message(neuron, code, message, neuron.get_source_position(), LoggingLevel.INFO) equations_block = neuron.get_equations_block() if equations_block is None: # add all declared state variables as none of them are used in equations block self.non_equations_state_variables[neuron.get_name()] = [] self.non_equations_state_variables[neuron.get_name()].extend(ASTUtils.all_variables_defined_in_block(neuron.get_initial_values_blocks())) self.non_equations_state_variables[neuron.get_name()].extend(ASTUtils.all_variables_defined_in_block(neuron.get_state_blocks())) return [] delta_factors = self.get_delta_factors_(neuron, equations_block) kernel_buffers = self.generate_kernel_buffers_(neuron, equations_block) self.replace_convolve_calls_with_buffers_(neuron, equations_block, kernel_buffers) self.make_inline_expressions_self_contained(equations_block.get_inline_expressions()) self.replace_inline_expressions_through_defining_expressions( equations_block.get_ode_equations(), equations_block.get_inline_expressions()) analytic_solver, numeric_solver = self.ode_toolbox_analysis(neuron, kernel_buffers) self.analytic_solver[neuron.get_name()] = analytic_solver self.numeric_solver[neuron.get_name()] = numeric_solver self.non_equations_state_variables[neuron.get_name()] = [] for decl in neuron.get_initial_values_blocks().get_declarations(): for var in decl.get_variables(): # check if this variable is not in equations if not neuron.get_equations_blocks(): self.non_equations_state_variables[neuron.get_name()].append(var) continue used_in_eq = False for ode_eq in neuron.get_equations_blocks().get_ode_equations(): if ode_eq.get_lhs().get_name() == var.get_name(): used_in_eq = True break for kern in neuron.get_equations_blocks().get_kernels(): for kern_var in kern.get_variables(): if kern_var.get_name() == var.get_name(): used_in_eq = True break if not used_in_eq: self.non_equations_state_variables[neuron.get_name()].append(var) self.remove_initial_values_for_kernels(neuron) kernels = self.remove_kernel_definitions_from_equations_block(neuron) self.update_initial_values_for_odes(neuron, [analytic_solver, numeric_solver], kernels) self.remove_ode_definitions_from_equations_block(neuron) self.create_initial_values_for_kernels(neuron, [analytic_solver, numeric_solver], kernels) self.replace_variable_names_in_expressions(neuron, [analytic_solver, numeric_solver]) self.add_timestep_symbol(neuron) if self.analytic_solver[neuron.get_name()] is not None: neuron = add_declarations_to_internals(neuron, self.analytic_solver[neuron.get_name()]["propagators"]) self.update_symbol_table(neuron, kernel_buffers) spike_updates = self.get_spike_update_expressions( neuron, kernel_buffers, [analytic_solver, numeric_solver], delta_factors) return spike_updates