Skip to content

Commit

Permalink
Merge branch 'master' into split-api-docs
Browse files Browse the repository at this point in the history
  • Loading branch information
jessica-mitchell committed Aug 8, 2023
2 parents 59e3ba7 + 1a5838e commit 4d09910
Show file tree
Hide file tree
Showing 201 changed files with 4,030 additions and 4,161 deletions.
10 changes: 6 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ set( with-ltdl ON CACHE STRING "Build with ltdl library [default=ON]. To set a s
set( with-gsl ON CACHE STRING "Build with the GSL library [default=ON]. To set a specific library, give the install path." )

# NEST properties
set( with-modelset "full" CACHE STRING "The modelset to include. Sample configurations are in the modelsets directory. This option is mutually exclusive with -Dwith-models. [default=full]." )
set( with-models OFF CACHE STRING "The models to include as a semicolon-separated list of model headers (without the .h extension). This option is mutually exclusive with -Dwith-modelset. [default=OFF]." )
set( tics_per_ms "1000.0" CACHE STRING "Specify elementary unit of time [default=1000 tics per ms]." )
set( tics_per_step "100" CACHE STRING "Specify resolution [default=100 tics per step]." )
set( external-modules OFF CACHE STRING "External NEST modules to be linked in, separated by ';', [default=OFF]." )
Expand Down Expand Up @@ -154,19 +156,19 @@ nest_process_target_bits_split()
nest_process_userdoc()
nest_process_devdoc()

nest_process_models()

# These two function calls must come last, as to prevent unwanted interactions of the newly set flags
# with detection/compilation operations carried out in earlier functions. The optimize/debug flags set
# using these functions should only apply to the compilation of NEST.
# using these functions should only apply to the compilation of NEST, not to that of test programs
# generated by CMake when it tries to detect compiler options or such.
nest_process_with_optimize()
nest_process_with_debug()

nest_get_color_flags()
set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${NEST_C_COLOR_FLAGS}" )
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${NEST_CXX_COLOR_FLAGS}" )

# requires HAVE_LIBNEUROSIM
nest_default_modules()

nest_write_static_module_header( "${PROJECT_BINARY_DIR}/nest/static_modules.h" )

# check additionals
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.4.0-post0.dev0
3.5.0-post0.dev0
340 changes: 340 additions & 0 deletions build_support/generate_modelsmodule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,340 @@
# -*- coding: utf-8 -*-
#
# generate_modelsmodule.py
#
# This file is part of NEST.
#
# Copyright (C) 2004 The NEST Initiative
#
# NEST is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# NEST is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with NEST. If not, see <http://www.gnu.org/licenses/>.

"""Script to generate the modelsmodule implementation file.
This script is called during the run of CMake and generates the file
models/modelsmodule.cpp as well as the list of source files to be
compiled by CMake.
"""

import os
import sys
import argparse

from pathlib import Path
from textwrap import dedent


def parse_commandline():
"""Parse the commandline arguments and put them into variables.
There are three arguments to this script that can be given either as
positional arguments or by their name.
1. srcdir: the path to the top-level NEST source directory
2. blddir: the path to the NEST build directory (-DCMAKE_INSTALL_PREFIX)
3. models: the semicolon-separated list of models to be built in
This function does not return anything, but instead it checks the
commandline arguments and makes them available as global variables
of the script. ``srcdir`` and ``blddir`` are set as they were
given. The model list is split and commented models (i.e. ones
that start with '#') are filtered out. The list is then made
available under the name model_names.
"""

global srcdir, blddir, model_names

description = "Generate the implementation and header files for modelsmodule."
parser = argparse.ArgumentParser(description=description)
parser.add_argument("srcdir", type=str, help="the source directory of NEST")
parser.add_argument("blddir", type=str, help="the build directory of NEST")
parser.add_argument("models", type=str, help="the models to build into NEST")
args = parser.parse_args()

srcdir = args.srcdir
blddir = args.blddir

model_names = [model_file.strip() for model_file in args.models.split(";")]
model_names = [model for model in model_names if model and not model.startswith("#")]


def get_models_from_file(model_file):
"""Extract model information from a given model file.
This function applies a series of simple heuristics to find the
preprocessor guards and the list of models in the file. Guards are
expected to be in the form "#ifdef HAVE_<LIB>" with one guard per
line. For the models, a list of unique pattern is used to infer
the correct model type from the line in the file.
The majority of neuron, device, and connection models are classes
derived from a specific base class (like Node, ArchivingNode, or
Connection) or from another model. The latter can only be detected
if the base model has the same name as the file.
The rate and binary neurons are typedefs for specialized template
classes and multiple of such typedefs may be present in a file.
Parameters
----------
model_file: str
The base file name (i.e. without extension) of the model file
to get the information from.
Returns
-------
tuple with two components:
0: HAVE_* preprocessor guards required for the models in the file
1: a zip of model types and model names found in the file and
that need registering
"""

model_patterns = {
"neuron": "public ArchivingNode",
"stimulator": "public StimulationDevice",
"recorder": "public RecordingDevice",
"devicelike": "public DeviceNode",
"connection": "public Connection",
"node": "public Node",
"clopath": "public ClopathArchivingNode",
"urbanczik": "public UrbanczikArchivingNode",
"binary": "typedef binary_neuron",
"rate": "typedef rate_",
}

fname = Path(srcdir) / "models" / f"{model_file}.h"
if not os.path.exists(fname):
print(f"ERROR: Model with name {model_file}.h does not exist", file=sys.stderr)
sys.exit(128)

guards = []
names = []
types = []
with open(fname, "r") as file:
for line in file:
if line.startswith("#ifdef HAVE_"):
guards.append(line.strip().split()[1])
if line.startswith(f"class {model_file} : "):
for mtype, pattern in model_patterns.items():
if pattern in line:
names.append(model_file)
types.append(mtype)
if line.startswith("class") and line.strip().endswith(f" : public {model_file}"):
names.append(line.split(" ", 2)[1])
# try to infer the type of the derived model from the base model,
# assuming that that was defined earlier in the file
try:
types.append(types[names.index(model_file)])
except (ValueError, KeyError) as e:
types.append("node")
if line.startswith("typedef "):
for mtype, pattern in model_patterns.items():
if pattern in line:
names.append(line.rsplit(" ", 1)[-1].strip()[:-1])
types.append(mtype)

return tuple(guards), zip(types, names)


def get_include_and_model_data():
"""Create data dictionaries for include files and models.
This function creates two nested dictionaries.
The first (`includes`) contains the a mapping from model_type ->
guards -> model_includes and is used in the code generation
function to print all include lines. This basically corresponds to
the list handed to the script as the `models` command line
argument, but is enriched by model type information and the
preprocessor guards needed for the individual include files.
The second (`models`) is a mapping from model_type -> guards ->
model_names and is used to generate the actual model registration
lines. model_names here is a list of models that is potentially
larger than the ones coming in throught the `models` command line
argument, as each file could contain multiple model definitions.
This function does not return anything, but instead sets the
global variables `includes` and `models` to be used by the code
generation function.
"""

global includes, models

includes = {}
models = {}

for model_file in model_names:
guards, model_types_names = get_models_from_file(model_file)
for tp, nm in model_types_names:
# Assemble a nested dictionary for the includes:
fname = model_file + ".h"
if tp in includes:
if guards in includes[tp]:
includes[tp][guards].add(fname)
else:
includes[tp][guards] = set([fname])
else:
includes[tp] = {guards: set([fname])}

if (Path(srcdir) / "models" / f"{model_file}_impl.h").is_file():
includes[tp][guards].add(f"{model_file}_impl.h")

# Assemble a nested dictionary for the models:
if tp in models:
if guards in models[tp]:
models[tp][guards].append(nm)
else:
models[tp][guards] = [nm]
else:
models[tp] = {guards: [nm]}


def start_guard(guards):
"""Print an #ifdef line with preprocessor guards if needed."""

if guards:
guard_str = " && ".join([f"defined( {guard} )" for guard in guards])
return f"#if {guard_str}\n"
else:
return ""


def end_guard(guards):
"""Print an #endif line for the preprocessor guards if needed."""
return "#endif\n" if guards else ""


def generate_modelsmodule():
"""Write the modelsmodule implementation out to file.
This is a very straightforward function that prints several blocks
of C++ code to the file modelsmodule.cpp in the `blddir` handed as
a commandline argument to the script. The blocks in particular are
1. the copyright header.
2. a list of generic NEST includes
3. the list of includes for the models to build into NEST
4. some boilerplate function implementations needed to fulfill the
Module interface
5. the list of model registration lines for the models to build
into NEST
The code is enriched by structured C++ comments as to make
debugging of the code generation process easier in case of errors.
"""

fname = Path(srcdir) / "doc" / "copyright_header.cpp"
with open(fname, "r") as file:
copyright_header = file.read()

fname = "modelsmodule.cpp"
modeldir = Path(blddir) / "models"
modeldir.mkdir(parents=True, exist_ok=True)
with open(modeldir / fname, "w") as file:
file.write(copyright_header.replace("{{file_name}}", fname))
file.write(
dedent(
"""
#include "modelsmodule.h"
// Generated includes
#include "config.h"
// Includes from nestkernel
#include "common_synapse_properties.h"
#include "connector_model_impl.h"
#include "genericmodel.h"
#include "genericmodel_impl.h"
#include "kernel_manager.h"
#include "model_manager_impl.h"
#include "target_identifier.h"
"""
)
)

for model_type, guards_fnames in includes.items():
file.write(f"\n// {model_type.capitalize()} models\n")
for guards, fnames in guards_fnames.items():
file.write(start_guard(guards))
for fname in fnames:
file.write(f'#include "{fname}"\n')
file.write(end_guard(guards))

file.write(
dedent(
"""
nest::ModelsModule::ModelsModule()
{
}
nest::ModelsModule::~ModelsModule()
{
}
const std::string
nest::ModelsModule::name() const
{
return std::string( "NEST standard models module" );
}
void
nest::ModelsModule::init( SLIInterpreter* )
{"""
)
)

conn_reg = ' register_connection_model< {model} >( "{model}" );\n'
node_reg = ' kernel().model_manager.register_node_model< {model} >( "{model}" );\n'

for model_type, guards_mnames in models.items():
file.write(f"\n // {model_type.capitalize()} models\n")
for guards, mnames in guards_mnames.items():
file.write(start_guard(guards))
for mname in mnames:
if model_type == "connection":
file.write(conn_reg.format(model=mname))
else:
file.write(node_reg.format(model=mname))
file.write(end_guard(guards))

file.write("}")


def print_model_sources():
"""Hand back the list of model source files to CMake.
In addition to the header file names handed to the script in the
form of the `models` commandline argument, this function searches
for corresponding implementation files with the extensions `.cpp`
and `_impl.h`. The list of models is printed as a CMake list,
i.e. as a semicolon separated string.
"""

model_sources = []
source_files = os.listdir(Path(srcdir) / "models")
for model_name in model_names:
source_candidates = [model_name + suffix for suffix in (".cpp", ".h", "_impl.h")]
model_sources.extend([f for f in source_files if f in source_candidates])
print(";".join(model_sources), end="")


if __name__ == "__main__":
parse_commandline()
get_include_and_model_data()
generate_modelsmodule()
print_model_sources()
9 changes: 8 additions & 1 deletion cmake/ConfigureSummary.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ function( NEST_PRINT_CONFIG_SUMMARY )
message( "--------------------------------------------------------------------------------" )
message( "NEST Configuration Summary" )
message( "--------------------------------------------------------------------------------" )

message( "" )
if ( CMAKE_BUILD_TYPE )
message( "Build type : ${CMAKE_BUILD_TYPE}" )
Expand All @@ -36,7 +37,13 @@ function( NEST_PRINT_CONFIG_SUMMARY )
message( "Build dynamic : ${BUILD_SHARED_LIBS}" )

message( "" )
message( "Built-in modules : ${SLI_MODULES}" )
if ( with-models )
message( "Built-in models : ${BUILTIN_MODELS}" )
else ()
message( "Built-in modelset : ${with-modelset}" )
endif ()

message( "" )
if ( external-modules )
message( "User modules : ${external-modules}" )
foreach ( mod ${external-modules} )
Expand Down

0 comments on commit 4d09910

Please sign in to comment.