def add_declaration_to_internals(neuron, variable_name, init_expression): # type: (ASTNeuron, str, str) -> ASTNeuron """ Adds the variable as stored in the declaration tuple to the neuron. :param neuron: a single neuron instance :param variable_name: the name of the variable to add :param init_expression: initialization expression :return: the neuron extended by the variable """ try: tmp = ModelParser.parse_expression(init_expression) vector_variable = ASTUtils.get_vectorized_variable( tmp, neuron.get_scope()) declaration_string = variable_name + ' real' + ( '[' + vector_variable.get_vector_parameter() + ']' if vector_variable is not None and vector_variable.has_vector_parameter() else '') + ' = ' + init_expression ast_declaration = ModelParser.parse_declaration(declaration_string) if vector_variable is not None: ast_declaration.set_size_parameter( vector_variable.get_vector_parameter()) neuron.add_to_internal_block(ast_declaration) return neuron except: raise RuntimeError('Must not fail by construction.')
def add_declaration_to_initial_values(neuron, variable, initial_value): # type: (ASTNeuron, str, str) -> ASTNeuron """ Adds a single declaration to the initial values block of the neuron. :param neuron: a neuron :param variable: state variable to add :param initial_value: corresponding initial value :return: a modified neuron """ try: tmp = ModelParser.parse_expression(initial_value) vector_variable = ASTUtils.get_vectorized_variable( tmp, neuron.get_scope()) declaration_string = variable + ' real' + ( '[' + vector_variable.get_vector_parameter() + ']' if vector_variable is not None and vector_variable.has_vector_parameter() else '') + ' = ' + initial_value ast_declaration = ModelParser.parse_declaration(declaration_string) if vector_variable is not None: ast_declaration.set_size_parameter( vector_variable.get_vector_parameter()) neuron.add_to_initial_values_block(ast_declaration) return neuron except: raise RuntimeError('Must not fail by construction.')
def integrate_exact_solution(neuron, exact_solution): # type: (ASTNeuron, map[str, list]) -> ASTNeuron """ Adds a set of instructions to the given neuron as stated in the solver output. :param neuron: a single neuron instance :param exact_solution: exact solution :return: a modified neuron with integrated exact solution and without equations block """ neuron.add_to_internal_block( ModelParser.parse_declaration('__h ms = resolution()')) neuron = add_declarations_to_internals(neuron, exact_solution["propagator"]) state_shape_variables_declarations = compute_state_shape_variables_declarations( exact_solution) neuron = add_declarations_to_initial_values( neuron, state_shape_variables_declarations) state_shape_variables_updates = compute_state_shape_variables_updates( exact_solution) neuron = add_state_updates(state_shape_variables_updates, neuron) neuron = replace_integrate_call(neuron, exact_solution["ode_updates"]) return neuron
def add_declaration_to_internals(neuron: ASTNeuron, variable_name: str, init_expression: str) -> ASTNeuron: """ Adds the variable as stored in the declaration tuple to the neuron. The declared variable is of type real. :param neuron: a single neuron instance :param variable_name: the name of the variable to add :param init_expression: initialization expression :return: the neuron extended by the variable """ tmp = ModelParser.parse_expression(init_expression) vector_variable = ASTUtils.get_vectorized_variable(tmp, neuron.get_scope()) declaration_string = variable_name + ' real' + ( '[' + vector_variable.get_vector_parameter() + ']' if vector_variable is not None and vector_variable.has_vector_parameter() else '') + ' = ' + init_expression ast_declaration = ModelParser.parse_declaration(declaration_string) if vector_variable is not None: ast_declaration.set_size_parameter( vector_variable.get_vector_parameter()) neuron.add_to_internal_block(ast_declaration) ast_declaration.update_scope(neuron.get_internals_blocks().get_scope()) symtable_visitor = ASTSymbolTableVisitor() symtable_visitor.block_type_stack.push(BlockType.INTERNALS) ast_declaration.accept(symtable_visitor) symtable_visitor.block_type_stack.pop() return neuron
def add_declaration_to_state_block(neuron: ASTNeuron, variable: str, initial_value: str) -> ASTNeuron: """ Adds a single declaration to the state block of the neuron. The declared variable is of type real. :param neuron: a neuron :param variable: state variable to add :param initial_value: corresponding initial value :return: a modified neuron """ tmp = ModelParser.parse_expression(initial_value) vector_variable = ASTUtils.get_vectorized_variable(tmp, neuron.get_scope()) declaration_string = variable + ' real' + ( '[' + vector_variable.get_vector_parameter() + ']' if vector_variable is not None and vector_variable.has_vector_parameter() else '') + ' = ' + initial_value ast_declaration = ModelParser.parse_declaration(declaration_string) if vector_variable is not None: ast_declaration.set_size_parameter( vector_variable.get_vector_parameter()) neuron.add_to_state_block(ast_declaration) ast_declaration.update_scope(neuron.get_state_blocks().get_scope()) symtable_visitor = ASTSymbolTableVisitor() symtable_visitor.block_type_stack.push(BlockType.STATE) ast_declaration.accept(symtable_visitor) symtable_visitor.block_type_stack.pop() return neuron
def test_declaration_with_comments(self): declaration = '\n' \ '/*pre*/\n' \ 'test mV = 10mV # in\n' \ '/*post*/\n\n' model = ModelParser.parse_declaration(declaration) model_printer = ASTNestMLPrinter() self.assertEqual(declaration, model_printer.print_node(model))
def add_timestep_symbol(self, neuron): assert neuron.get_initial_value( "__h" ) is None, "\"__h\" is a reserved name, please do not use variables by this name in your NESTML file" assert not "__h" in [ sym.name for sym in neuron.get_internal_symbols() ], "\"__h\" is a reserved name, please do not use variables by this name in your NESTML file" neuron.add_to_internal_block( ModelParser.parse_declaration('__h ms = resolution()'), index=0)
def integrate_delta_solution(equations_block, neuron, shape, shape_to_buffers): def ode_is_lin_const_coeff(ode_symbol, ode_definition, shapes): """ TODO: improve the original code: the function should take a list of shape names, not objects :param ode_symbol string encoding the LHS :param ode_definition string encoding RHS :param shapes A list with shape names :return true iff the ode definition is a linear and constant coefficient ODE """ ode_symbol_sp = parse_expr(ode_symbol) ode_definition_sp = parse_expr(ode_definition) # Check linearity ddvar = diff(diff(ode_definition_sp, ode_symbol_sp), ode_symbol_sp) if simplify(ddvar) != 0: return False # Check coefficients dvar = diff(ode_definition_sp, ode_symbol_sp) for shape in shapes: for symbol in dvar.free_symbols: if shape == str(symbol): return False return True ode_lhs = str(equations_block.get_ode_equations()[0].get_lhs().get_name()) ode_rhs = str(equations_block.get_ode_equations()[0].get_rhs()) shape_name = shape.get_variable().get_name() if not (ode_is_lin_const_coeff(ode_lhs, ode_rhs, [shape_name])): raise Exception("Cannot handle delta shape in a not linear constant coefficient ODE.") ode_lhs = parse_expr(ode_lhs) ode_rhs = parse_expr(ode_rhs) # constant input const_input = simplify(1 / diff(ode_rhs, parse_expr(shape_name)) * (ode_rhs - diff(ode_rhs, ode_lhs) * ode_lhs) - (parse_expr(shape_name))) # ode var factor c1 = diff(ode_rhs, ode_lhs) # The symbol must be declared again. Otherwise, the right hand side will be used for the derivative c2 = diff(ode_rhs, parse_expr(shape_name)) # tau is passed as the second argument of the 'delta' function tau_constant = parse_expr(str(shape.get_expression().get_function_call().get_args()[1])) ode_var_factor = exp(-Symbol('__h') / tau_constant) ode_var_update_instructions = [ str(ode_lhs) + " = " + str(ode_var_factor * ode_lhs), str(ode_lhs) + " += " + str(simplify(c2 / c1 * (exp(Symbol('__h') * c1) - 1)) * const_input)] for k in shape_to_buffers: ode_var_update_instructions.append(str(ode_lhs) + " += " + shape_to_buffers[k]) neuron.add_to_internal_block(ModelParser.parse_declaration('__h ms = resolution()')) replace_integrate_call(neuron, ode_var_update_instructions) return neuron
def add_state_updates(neuron: ASTNeuron, update_expressions: Mapping[str, str]) -> ASTNeuron: """ Adds all update instructions as contained in the solver output to the update block of the neuron. :param neuron: a single neuron :param update_expressions: map of variables to corresponding updates during the update step. :return: a modified version of the neuron """ for variable, update_expression in update_expressions.items(): declaration_statement = variable + '__tmp real = ' + update_expression add_declaration_to_update_block(ModelParser.parse_declaration(declaration_statement), neuron) for variable, update_expression in update_expressions.items(): add_assignment_to_update_block(ModelParser.parse_assignment(variable + ' = ' + variable + '__tmp'), neuron) return neuron
def add_state_updates(state_shape_variables_updates, neuron): # type: (map[str, str], ASTNeuron) -> ASTNeuron """ Adds all update instructions as contained in the solver output to the update block of the neuron. :param state_shape_variables_updates: map of variables to corresponding updates during the update step. :param neuron: a single neuron :return: a modified version of the neuron """ for variable in state_shape_variables_updates: declaration_statement = variable + '__tmp real = ' + state_shape_variables_updates[variable] add_declaration_to_update_block(ModelParser.parse_declaration(declaration_statement), neuron) for variable in state_shape_variables_updates: add_assignment_to_update_block(ModelParser.parse_assignment(variable + ' = ' + variable + '__tmp'), neuron) return neuron
def add_state_updates(state_shape_variables_updates, neuron): # type: (map[str, str], ASTNeuron) -> ASTNeuron """ Adds all update instructions as contained in the solver output to the update block of the neuron. :param state_shape_variables_updates: map of variables to corresponding updates during the update step. :param neuron: a single neuron :return: a modified version of the neuron """ for variable in state_shape_variables_updates: declaration_statement = variable + '__tmp real = ' + state_shape_variables_updates[ variable] add_declaration_to_update_block( ModelParser.parse_declaration(declaration_statement), neuron) for variable in state_shape_variables_updates: add_assignment_to_update_block( ModelParser.parse_assignment(variable + ' = ' + variable + '__tmp'), neuron) return neuron
def add_declaration_to_initial_values(neuron, variable, initial_value): # type: (ASTNeuron, str, str) -> ASTNeuron """ Adds a single declaration to the initial values block of the neuron. :param neuron: a neuron :param variable: state variable to add :param initial_value: corresponding initial value :return: a modified neuron """ tmp = ModelParser.parse_expression(initial_value) vector_variable = ASTUtils.get_vectorized_variable(tmp, neuron.get_scope()) declaration_string = variable + ' real' + ( '[' + vector_variable.get_vector_parameter() + ']' if vector_variable is not None and vector_variable.has_vector_parameter() else '') + ' = ' + initial_value ast_declaration = ModelParser.parse_declaration(declaration_string) if vector_variable is not None: ast_declaration.set_size_parameter(vector_variable.get_vector_parameter()) neuron.add_to_initial_values_block(ast_declaration) return neuron
def add_declaration_to_internals(neuron, variable_name, init_expression): # type: (ASTNeuron, str, str) -> ASTNeuron """ Adds the variable as stored in the declaration tuple to the neuron. :param neuron: a single neuron instance :param variable_name: the name of the variable to add :param init_expression: initialization expression :return: the neuron extended by the variable """ tmp = ModelParser.parse_expression(init_expression) vector_variable = ASTUtils.get_vectorized_variable(tmp, neuron.get_scope()) declaration_string = variable_name + ' real' + ( '[' + vector_variable.get_vector_parameter() + ']' if vector_variable is not None and vector_variable.has_vector_parameter() else '') + ' = ' + init_expression ast_declaration = ModelParser.parse_declaration(declaration_string) if vector_variable is not None: ast_declaration.set_size_parameter(vector_variable.get_vector_parameter()) neuron.add_to_internal_block(ast_declaration) return neuron
def integrate_exact_solution(neuron, exact_solution): # type: (ASTNeuron, map[str, list]) -> ASTNeuron """ Adds a set of instructions to the given neuron as stated in the solver output. :param neuron: a single neuron instance :param exact_solution: exact solution :return: a modified neuron with integrated exact solution and without equations block """ neuron.add_to_internal_block(ModelParser.parse_declaration('__h ms = resolution()')) neuron = add_declarations_to_internals(neuron, exact_solution["propagator"]) state_shape_variables_declarations = compute_state_shape_variables_declarations(exact_solution) neuron = add_declarations_to_initial_values(neuron, state_shape_variables_declarations) state_shape_variables_updates = compute_state_shape_variables_updates(exact_solution) neuron = add_state_updates(state_shape_variables_updates, neuron) neuron = replace_integrate_call(neuron, exact_solution["ode_updates"]) return neuron
def test_declaration_without_comments(self): declaration = 'test mV = 10mV\n' model = ModelParser.parse_declaration(declaration) model_printer = ASTNestMLPrinter() self.assertEqual(declaration, model_printer.print_node(model))
def integrate_delta_solution(equations_block, neuron, shape, shape_to_buffers): def ode_is_lin_const_coeff(ode_symbol, ode_definition, shapes): """ TODO: improve the original code: the function should take a list of shape names, not objects :param ode_symbol string encoding the LHS :param ode_definition string encoding RHS :param shapes A list with shape names :return true iff the ode definition is a linear and constant coefficient ODE """ ode_symbol_sp = parse_expr(ode_symbol) ode_definition_sp = parse_expr(ode_definition) # Check linearity ddvar = diff(diff(ode_definition_sp, ode_symbol_sp), ode_symbol_sp) if simplify(ddvar) != 0: return False # Check coefficients dvar = diff(ode_definition_sp, ode_symbol_sp) for shape in shapes: for symbol in dvar.free_symbols: if shape == str(symbol): return False return True ode_lhs = str(equations_block.get_ode_equations()[0].get_lhs().get_name()) ode_rhs = str(equations_block.get_ode_equations()[0].get_rhs()) shape_name = shape.get_variable().get_name() if not (ode_is_lin_const_coeff(ode_lhs, ode_rhs, [shape_name])): raise Exception( "Cannot handle delta shape in a not linear constant coefficient ODE." ) ode_lhs = parse_expr(ode_lhs) ode_rhs = parse_expr(ode_rhs) # constant input const_input = simplify(1 / diff(ode_rhs, parse_expr(shape_name)) * (ode_rhs - diff(ode_rhs, ode_lhs) * ode_lhs) - (parse_expr(shape_name))) # ode var factor c1 = diff(ode_rhs, ode_lhs) # The symbol must be declared again. Otherwise, the right hand side will be used for the derivative c2 = diff(ode_rhs, parse_expr(shape_name)) # tau is passed as the second argument of the 'delta' function tau_constant = parse_expr( str(shape.get_expression().get_function_call().get_args()[1])) ode_var_factor = exp(-Symbol('__h') / tau_constant) ode_var_update_instructions = [ str(ode_lhs) + " = " + str(ode_var_factor * ode_lhs), str(ode_lhs) + " += " + str(simplify(c2 / c1 * (exp(Symbol('__h') * c1) - 1)) * const_input) ] for k in shape_to_buffers: ode_var_update_instructions.append( str(ode_lhs) + " += " + shape_to_buffers[k]) neuron.add_to_internal_block( ModelParser.parse_declaration('__h ms = resolution()')) replace_integrate_call(neuron, ode_var_update_instructions) return neuron