def test_print_statement_with_variables(self): input_path = str( os.path.realpath( os.path.join( os.path.dirname(__file__), os.path.join('resources', 'PrintStatementWithVariables.nestml')))) params = list() params.append('--input_path') params.append(input_path) params.append('--logging_level') params.append('INFO') params.append('--target_path') params.append(self.target_path) params.append('--dev') FrontendConfiguration.parse_config(params) compilation_unit = ModelParser.parse_model(input_path) nestCodeGenerator = NESTCodeGenerator() nestCodeGenerator.generate_code(compilation_unit.get_neuron_list()) with open( str( os.path.realpath( os.path.join( os.path.dirname(__file__), os.path.join(os.pardir, 'target', 'print_test_variables.cpp')))), 'r') as reader: self.assertEqual(reader.read().count('std::cout'), 2)
def create_report_dir(): if not os.path.isdir( os.path.join(FrontendConfiguration.get_target_path(), "..", "report")): os.makedirs( os.path.join(FrontendConfiguration.get_target_path(), "..", "report"))
def test_iaf_cond_alpha_functional(self): path = str( os.path.realpath( os.path.join( os.path.dirname(__file__), os.path.join('..', 'models', 'iaf_cond_alpha.nestml')))) params = list() params.append('-path') params.append(path) # params.append('-dry') params.append('-logging_level') params.append('NO') params.append('-target') params.append('target') params.append('-dev') FrontendConfiguration.config(params) compilation_unit = ModelParser.parse_model(path) iaf_cond_alpha_functional = list() iaf_cond_alpha_functional.append(compilation_unit.get_neuron_list()[0]) generate_nest_module_code(iaf_cond_alpha_functional) analyse_and_generate_neurons(iaf_cond_alpha_functional)
def to_nest(input_path, target_path=None, logging_level='ERROR', module_name=None, store_log=False, suffix="", dev=False): '''Translate NESTML files into their equivalent C++ code for the NEST simulator. Parameters ---------- input_path : str Path to the NESTML file or to a folder containing NESTML files to convert to NEST code. target_path : str, optional (default: append "target" to `input_path`) Path to the generated C++ code and install files. logging_level : str, optional (default: 'ERROR') Sets which level of information should be displayed duing code generation (among 'ERROR', 'WARNING', 'INFO', or 'NO'). module_name : str, optional (default: "nestmlmodule") Name of the module, which will be used to import the model in NEST via `nest.Install(module_name)`. store_log : bool, optional (default: False) Whether the log should be saved to file. suffix : str, optional (default: "") Suffix which will be appended to the model's name (internal use to avoid naming conflicts with existing NEST models). dev : bool, optional (default: False) Enable development mode: code generation is attempted even for models that contain errors, and extra information is rendered in the generated code. ''' # if target_path is not None and not os.path.isabs(target_path): # print('PyNestML: Please provide absolute target path!') # return args = list() args.append(qualifier_input_path_arg) args.append(str(input_path)) if target_path is not None: args.append(qualifier_target_path_arg) args.append(str(target_path)) args.append(qualifier_target_arg) args.append(str("NEST")) args.append(qualifier_logging_level_arg) args.append(str(logging_level)) if module_name is not None: args.append(qualifier_module_name_arg) args.append(str(module_name)) if store_log: args.append(qualifier_store_log_arg) if suffix: args.append(qualifier_suffix_arg) args.append(suffix) if dev: args.append(qualifier_dev_arg) FrontendConfiguration.parse_config(args) if not process() == 0: raise Exception("Error(s) occurred while processing the model")
def main(args): try: FrontendConfiguration.parse_config(args) except InvalidPathException: print('Not a valid path to model or directory: "%s"!' % FrontendConfiguration.get_path()) return # after all argument have been collected, start the actual processing process()
def generate_code(self, neurons: List[ASTNeuron], synapses: List[ASTSynapse] = None) -> None: """ Generate model documentation and index page for each neuron and synapse that is provided. """ if not os.path.isdir(FrontendConfiguration.get_target_path()): os.makedirs(FrontendConfiguration.get_target_path()) self.generate_index(neurons, synapses) self.generate_neurons(neurons) self.generate_synapses(synapses)
def main(): """Returns the process exit code: 0 for success, > 0 for failure""" try: FrontendConfiguration.parse_config(sys.argv[1:]) except InvalidPathException: print('Not a valid path to model or directory: "%s"!' % FrontendConfiguration.get_path()) return 1 # after all argument have been collected, start the actual processing return int(process())
def generate_neuron_code(self, neuron: ASTNeuron) -> None: """ For a handed over neuron, this method generates the corresponding header and implementation file. :param neuron: a single neuron object. """ if not os.path.isdir(FrontendConfiguration.get_target_path()): os.makedirs(FrontendConfiguration.get_target_path()) self.generate_model_h_file(neuron) self.generate_neuron_cpp_file(neuron)
def test_module_name_parsing_input_path_is_file(self): h, path = tempfile.mkstemp(prefix='nestml') basename = os.path.basename(os.path.normpath(path)) params = list() params.append('--input_path') params.append(path) FrontendConfiguration.parse_config(params) assert FrontendConfiguration.module_name == 'nestmlmodule'
def process(): """ Returns ------- errors_occurred : bool Flag indicating whether errors occurred during processing """ errors_occurred = False # init log dir create_report_dir() # The handed over parameters seem to be correct, proceed with the main routine init_predefined() # now proceed to parse all models compilation_units = list() nestml_files = FrontendConfiguration.get_files() if not type(nestml_files) is list: nestml_files = [nestml_files] for nestml_file in nestml_files: parsed_unit = ModelParser.parse_model(nestml_file) if parsed_unit is not None: compilation_units.append(parsed_unit) if len(compilation_units) > 0: # generate a list of all neurons neurons = list() for compilationUnit in compilation_units: neurons.extend(compilationUnit.get_neuron_list()) # check if across two files two neurons with same name have been defined CoCosManager.check_not_two_neurons_across_units(compilation_units) # now exclude those which are broken, i.e. have errors. if not FrontendConfiguration.is_dev: for neuron in neurons: if Logger.has_errors(neuron): code, message = Messages.get_neuron_contains_errors( neuron.get_name()) Logger.log_message( node=neuron, code=code, message=message, error_position=neuron.get_source_position(), log_level=LoggingLevel.INFO) neurons.remove(neuron) errors_occurred = True # perform code generation _codeGenerator = CodeGenerator.from_target_name( FrontendConfiguration.get_target(), options=FrontendConfiguration.get_codegen_opts()) _codeGenerator.generate_code(neurons) for neuron in neurons: if Logger.has_errors(neuron): errors_occurred = True break if FrontendConfiguration.store_log: store_log_to_file() return errors_occurred
def test_module_name_parsing_input_path_is_wrong_dir(self): with pytest.raises(Exception): path = tempfile.mkdtemp(prefix='nestml-') params = list() params.append('--input_path') params.append(path) params.append('--logging_level') params.append('INFO') FrontendConfiguration.parse_config(params)
def generate_neuron_code(self, neuron): # type: (ASTNeuron) -> None """ For a handed over neuron, this method generates the corresponding header and implementation file. :param neuron: a single neuron object. """ if not os.path.isdir(FrontendConfiguration.get_target_path()): os.makedirs(FrontendConfiguration.get_target_path()) self.generate_model_h_file(neuron) self.generate_neuron_cpp_file(neuron)
def test_module_name_parsing_wrong_module_name_specified(self): with pytest.raises(Exception): path = str(os.path.realpath(os.path.join(os.path.dirname(__file__), os.path.join('..', 'models')))) params = list() params.append('--input_path') params.append(path) params.append('--module_name') params.append('xyzzy') FrontendConfiguration.parse_config(params)
def test_module_name_parsing_right_module_name_specified(self): path = str(os.path.realpath(os.path.join(os.path.dirname(__file__), os.path.join('..', 'models')))) params = list() params.append('--input_path') params.append(path) params.append('--module_name') params.append('xyzzymodule') FrontendConfiguration.parse_config(params) assert FrontendConfiguration.module_name == 'xyzzymodule'
def test_module_name_parsing_input_path_is_dir(self): path = tempfile.mkdtemp(prefix='nestml') basename = os.path.basename(os.path.normpath(path)) params = list() params.append('--input_path') params.append(path) params.append('--logging_level') params.append('INFO') FrontendConfiguration.parse_config(params) assert FrontendConfiguration.module_name == basename + 'module'
def main(): """Returns the process exit code: 0 for success, > 0 for failure""" try: FrontendConfiguration.parse_config(sys.argv[1:]) except InvalidPathException: print('Not a valid path to model or directory: "%s"!' % FrontendConfiguration.get_path()) return 1 # the default Python recursion limit is 1000, which might not be enough in practice when running an AST visitor on a deep tree, e.g. containing an automatically generated expression sys.setrecursionlimit(10000) # after all argument have been collected, start the actual processing return int(process())
def generate_neuron_code(self, neuron: ASTNeuron): """ Generate model documentation for neuron model. :param neuron: a single neuron object. """ if not os.path.isdir(FrontendConfiguration.get_target_path()): os.makedirs(FrontendConfiguration.get_target_path()) nestml_model_doc = self._template_nestml_model.render( self.setup_model_generation_helpers(neuron)) with open( str( os.path.join(FrontendConfiguration.get_target_path(), neuron.get_name())) + '.rst', 'w+') as f: f.write(str(nestml_model_doc))
def test_from_objects(self): input_path = os.path.join(os.path.dirname(__file__), 'resources', 'CommentTest.nestml') target_path = os.path.join('target') logging_level = 'INFO' module_name = 'module' store_log = False suffix = '' dev = True to_nest(input_path, target_path, logging_level, module_name, store_log, suffix, dev) self.assertTrue(os.path.isfile(os.path.join(FrontendConfiguration.get_target_path(), 'CMakeLists.txt'))) self.assertTrue(os.path.isfile(os.path.join(FrontendConfiguration.get_target_path(), 'commentTest.cpp'))) self.assertTrue(os.path.isfile(os.path.join(FrontendConfiguration.get_target_path(), 'commentTest.h'))) self.assertTrue(os.path.isfile(os.path.join(FrontendConfiguration.get_target_path(), 'module.cpp'))) self.assertTrue(os.path.isfile(os.path.join(FrontendConfiguration.get_target_path(), 'module.h')))
def test_iaf_psc_alpha_with_codegen_opts(self): input_path = str( os.path.realpath( os.path.join( os.path.dirname(__file__), os.path.join(os.pardir, 'models', 'neurons', 'iaf_psc_alpha.nestml')))) code_opts_path = str( os.path.realpath( os.path.join(os.path.dirname(__file__), os.path.join('resources', 'code_options.json')))) codegen_opts = { "templates": { "path": "point_neuron", "model_templates": { "neuron": ['NeuronClass.cpp.jinja2', 'NeuronHeader.h.jinja2'], "synapse": [] }, "module_templates": [ 'setup/CMakeLists.txt.jinja2', 'setup/ModuleHeader.h.jinja2', 'setup/ModuleClass.cpp.jinja2' ] } } with open(code_opts_path, 'w+') as f: json.dump(codegen_opts, f) params = list() params.append('--input_path') params.append(input_path) params.append('--logging_level') params.append('INFO') params.append('--target_path') params.append(self.target_path) params.append('--dev') params.append('--codegen_opts') params.append(code_opts_path) FrontendConfiguration.parse_config(params) compilation_unit = ModelParser.parse_model(input_path) nestCodeGenerator = NESTCodeGenerator(codegen_opts) nestCodeGenerator.generate_code(compilation_unit.get_neuron_list(), list())
def test_input_path_handling_dir_one_file(self): path = tempfile.mkdtemp(prefix='nestml-') fd, fpath = tempfile.mkstemp(dir=path, suffix='.nestml') with open(fpath, 'w') as f: f.write('neuron foo:\nend\n') os.close(fd) params = list() params.append('--input_path') params.append(path) params.append('--logging_level') params.append('INFO') FrontendConfiguration.parse_config(params) assert len(FrontendConfiguration.paths_to_compilation_units) == 1
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() self.define_solver_type(neuron, namespace) return namespace
def generate_index(self, neurons: Sequence[ASTNeuron], synapses: Sequence[ASTSynapse]): """ Generate model documentation and index page for each neuron and synapse that is provided. """ nestml_models_index = self._template_nestml_models_index.render(self.setup_index_generation_helpers(neurons, synapses)) with open(str(os.path.join(FrontendConfiguration.get_target_path(), 'index.rst')), 'w+') as f: f.write(str(nestml_models_index))
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 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 main() -> int: """ Entry point for the command-line application. Returns ------- The process exit code: 0 for success, > 0 for failure """ try: FrontendConfiguration.parse_config(sys.argv[1:]) except InvalidPathException as e: return 1 # the default Python recursion limit is 1000, which might not be enough in practice when running an AST visitor on a deep tree, e.g. containing an automatically generated expression sys.setrecursionlimit(10000) # after all argument have been collected, start the actual processing return int(process())
def test_expressions(self): input_path = str(os.path.realpath(os.path.join(os.path.dirname(__file__), os.path.join( 'resources', 'ExpressionTypeTest.nestml')))) params = list() params.append('--input_path') params.append(input_path) params.append('--logging_level') params.append('INFO') params.append('--target_path') params.append(self.target_path) params.append('--dev') FrontendConfiguration.parse_config(params) compilation_unit = ModelParser.parse_model(input_path) nestCodeGenerator = NESTCodeGenerator() nestCodeGenerator.generate_code(compilation_unit.get_neuron_list())
def store_transformed_model(self, ast): if FrontendConfiguration.store_log: with open( str( os.path.join(FrontendConfiguration.get_target_path(), '..', 'report', ast.get_name())) + '.txt', 'w+') as f: f.write(str(ast))
def generate_neuron_cpp_file(self, neuron: ASTNeuron) -> None: """ For a handed over neuron, this method generates the corresponding implementation file. :param neuron: a single neuron object. """ neuron_cpp_file = self._template_neuron_cpp_file.render(self.setup_generation_helpers(neuron)) with open(str(os.path.join(FrontendConfiguration.get_target_path(), neuron.get_name())) + '.cpp', 'w+') as f: f.write(str(neuron_cpp_file))
def test_vector_code_generation(self): input_path = str(os.path.realpath(os.path.join(os.path.dirname(__file__), os.path.join( 'valid', 'VectorsDeclarationAndAssignment.nestml')))) params = list() params.append('--input_path') params.append(input_path) params.append('--logging_level') params.append('INFO') params.append('--target_path') params.append(self.target_path) params.append('--dev') FrontendConfiguration.parse_config(params) compilation_unit = ModelParser.parse_model(input_path) nestCodeGenerator = NESTCodeGenerator() nestCodeGenerator.generate_code(compilation_unit.get_neuron_list())
def generate_neuron_cpp_file(self, neuron): # type: (ASTNeuron) -> None """ For a handed over neuron, this method generates the corresponding implementation file. :param neuron: a single neuron object. """ neuron_cpp_file = self._template_neuron_cpp_file.render(self.setup_generation_helpers(neuron)) with open(str(os.path.join(FrontendConfiguration.get_target_path(), neuron.get_name())) + '.cpp', 'w+') as f: f.write(str(neuron_cpp_file))
def generate_synapse_code(self, synapse: ASTSynapse): """ Generate model documentation for synapse model. :param synapse: a single synapse object. """ nestml_model_doc = self._template_synapse_nestml_model.render(self.setup_synapse_model_generation_helpers(synapse)) with open(str(os.path.join(FrontendConfiguration.get_target_path(), synapse.get_name())) + '.rst', 'w+') as f: f.write(str(nestml_model_doc))
def generate_model_h_file(neuron): # type: (ASTNeuron) -> None """ For a handed over neuron, this method generates the corresponding header file. :param neuron: a single neuron object. """ # print("!!!", neuron) neuron_h_file = template_neuron_h_file.render(setup_generation_helpers(neuron)) with open(str(os.path.join(FrontendConfiguration.get_target_path(), neuron.get_name())) + '.h', 'w+') as f: f.write(str(neuron_h_file))
def generate_index(self, neurons: List[ASTNeuron]): """ Generate index (list) of all neuron models with links to their generated documentation. """ nestml_models_index = self._template_nestml_models_index.render( self.setup_index_generation_helpers(neurons)) with open( str( os.path.join(FrontendConfiguration.get_target_path(), 'index.rst')), 'w+') as f: f.write(str(nestml_models_index))
def store_transformed_model(self, ast): if FrontendConfiguration.store_log: with open(str(os.path.join(FrontendConfiguration.get_target_path(), '..', 'report', ast.get_name())) + '.txt', 'w+') as f: f.write(str(ast))
def generate_module_code(self, neurons): # type: (list(ASTNeuron)) -> None """ Generates code that is necessary to integrate neuron models into the NEST infrastructure. :param neurons: a list of neurons :type neurons: list(ASTNeuron) """ namespace = {'neurons': neurons, 'moduleName': FrontendConfiguration.get_module_name(), 'now': datetime.datetime.utcnow()} if not os.path.exists(FrontendConfiguration.get_target_path()): os.makedirs(FrontendConfiguration.get_target_path()) with open(str(os.path.join(FrontendConfiguration.get_target_path(), FrontendConfiguration.get_module_name())) + '.h', 'w+') as f: f.write(str(self._template_module_header.render(namespace))) with open(str(os.path.join(FrontendConfiguration.get_target_path(), FrontendConfiguration.get_module_name())) + '.cpp', 'w+') as f: f.write(str(self._template_module_class.render(namespace))) with open(str(os.path.join(FrontendConfiguration.get_target_path(), 'CMakeLists')) + '.txt', 'w+') as f: f.write(str(self._template_cmakelists.render(namespace))) if not os.path.isdir(os.path.realpath(os.path.join(FrontendConfiguration.get_target_path(), 'sli'))): os.makedirs(os.path.realpath(os.path.join(FrontendConfiguration.get_target_path(), 'sli'))) with open(str(os.path.join(FrontendConfiguration.get_target_path(), 'sli', FrontendConfiguration.get_module_name() + "-init")) + '.sli', 'w+') as f: f.write(str(self._template_sli_init.render(namespace))) code, message = Messages.get_module_generated(FrontendConfiguration.get_target_path()) Logger.log_message(None, code, message, None, LoggingLevel.INFO)