diff --git a/.gitignore b/.gitignore index b710bef2b72c6b9f6eea87de726bf180c4f3a107..ff2696ed75fe48bd54e9d654d2c139a2d9fa1c6b 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,8 @@ *.swo *.swn *.swq +*.swm +*.swl # tar files *.tar @@ -65,6 +67,7 @@ external/tmp mechanisms/*.hpp # build path -build +build* commit.msg + diff --git a/CMakeLists.txt b/CMakeLists.txt index 37f1cfe80e468fa7d8c50a7f57ab7de7b01a2f51..8da46487de7eb56194909cbcb81a1262ff2a6444 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,7 +51,7 @@ if(WITH_MPI) endif() # Profiler support -set(WITH_PROFILING OFF CACHE BOOL "use built in profiling of miniapp" ) +set(WITH_PROFILING OFF CACHE BOOL "use built-in profiling of miniapp" ) if(WITH_PROFILING) add_definitions(-DWITH_PROFILING) endif() @@ -62,18 +62,45 @@ if(SYSTEM_CRAY) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -dynamic") endif() -# targets for extermal dependencies -include(ExternalProject) -externalproject_add(modparser - PREFIX ${CMAKE_BINARY_DIR}/external - CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/external" - "-DCMAKE_CXX_FLAGS=${SAVED_CXX_FLAGS}" - "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" - BINARY_DIR "${CMAKE_BINARY_DIR}/external/modparser" - STAMP_DIR "${CMAKE_BINARY_DIR}/external/" - TMP_DIR "${CMAKE_BINARY_DIR}/external/tmp" - SOURCE_DIR "${CMAKE_SOURCE_DIR}/external/modparser" +# vectorization target +set(VECTORIZE_TARGET "none" CACHE STRING "CPU target for vectorization {KNL,AVX,AVX2}") + +if(VECTORIZE_TARGET STREQUAL "KNL") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXXOPT_KNL}") +elseif(VECTORIZE_TARGET STREQUAL "AVX") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXXOPT_AVX}") +elseif(VECTORIZE_TARGET STREQUAL "AVX2") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXXOPT_AVX2}") +endif() + +# whether to generate optimized kernels from NMODL +set(USE_OPTIMIZED_KERNELS OFF CACHE BOOL "generate optimized code that vectorizes with the Intel compiler") + +# Only build modcc if it has not already been installed. +# This is useful if cross compiling for KNL, when it is not desirable to compile +# modcc with the same flags that are used for the KNL target. +find_program(MODCC_BIN modcc) +set(modcc "${MODCC_BIN}") +set(use_external_modcc OFF BOOL) + +# the modcc executable was not found, so build our own copy +if(MODCC_BIN STREQUAL "MODCC_BIN-NOTFOUND") + include(ExternalProject) + externalproject_add(modparser + PREFIX ${CMAKE_BINARY_DIR}/external + CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/external" + "-DCMAKE_CXX_FLAGS=${SAVED_CXX_FLAGS}" + "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + BINARY_DIR "${CMAKE_BINARY_DIR}/external/modparser" + STAMP_DIR "${CMAKE_BINARY_DIR}/external/" + TMP_DIR "${CMAKE_BINARY_DIR}/external/tmp" + SOURCE_DIR "${CMAKE_SOURCE_DIR}/external/modparser" ) + # Set up environment to use the version of modcc that is compiled + # as the ExternalProject above. + set(use_external_modcc ON) + set(modcc "${CMAKE_BINARY_DIR}/external/bin/modcc") +endif() include_directories(${CMAKE_SOURCE_DIR}/external) @@ -82,7 +109,6 @@ include_directories(${CMAKE_SOURCE_DIR}/include) include_directories(${CMAKE_SOURCE_DIR}/src) include_directories(${CMAKE_SOURCE_DIR}/miniapp) include_directories(${CMAKE_SOURCE_DIR}) - if( "${WITH_TBB}" STREQUAL "ON" ) include_directories(${TBB_INCLUDE_DIRS}) endif() diff --git a/README.md b/README.md index dc2e7d720fa5452c15f2db6b2e7ad6001928cb85..32091a9364a52a70985abe288b50bfcc47d53d54 100644 --- a/README.md +++ b/README.md @@ -77,3 +77,147 @@ cmake <path to CMakeLists.txt> -DWITH_TBB=ON -DSYSTEM_CRAY=ON cmake <path to CMakeLists.txt> -DWITH_TBB=ON -DWITH_MPI=ON -DSYSTEM_CRAY=ON ``` + +# targetting KNL + +## build modparser + +The source to source compiler "modparser" that generates the C++/CUDA kernels for the ion channels and synapses is in a separate repository. +It is included in our project as a git submodule, and by default it will be built with the same compiler and flags that are used to build the miniapp and tests. + +This can cause problems if we are cross compiling, e.g. for KNL, because the modparser compiler might not be runnable on the compilation node. +CMake will look for the source to source compiler executable, `modcc`, in the `PATH` environment variable, and will use the version if finds instead of building its own. + +Modparser requires a C++11 compiler, and has been tested on GCC, Intel, and Clang compilers + - if the default compiler on your is some ancient version of gcc you might need to load a module/set the CC and CXX environment variables. + +```bash +git clone git@github.com:eth-cscs/modparser.git +cd modparser + +# example of setting a C++11 compiler +export CXX=`which gcc-4.8` + +cmake . +make -j + +# set path and test that you can see modcc +export PATH=`pwd`/bin:$PATH +which modcc +``` + +## set up environment + +- source the intel compilers +- source the TBB vars +- I have only tested with the latest stable version from online, not the version that comes installed sometimes with the Intel compilers. + +## build miniapp + +```bash +# clone the repo and set up the submodules +git clone TODO +cd cell_algorithms +git submodule init +git submodule update + +# make a path for out of source build +mkdir build_knl +cd build_knl + +## build miniapp + +# clone the repo and set up the submodules +git clone https://github.com/bcumming/cell_algorithms.git +cd cell_algorithms + +# checkout the knl branch +git checkout knl + +# setup submodules +git submodule init +git submodule update + +# make a path for out of source build +mkdir build_knl +cd build_knl + +# run cmake with all the magic flags +export CC=`which icc` +export CXX=`which icpc` +cmake .. -DCMAKE_BUILD_TYPE=release -DWITH_TBB=ON -DWITH_PROFILING=ON -DVECTORIZE_TARGET=KNL -DUSE_OPTIMIZED_KERNELS=ON +make -j +``` + +The flags passed into cmake are described: + - `-DCMAKE_BUILD_TYPE=release` : build in release mode with `-O3`. + - `-WITH_TBB=ON` : use TBB for threading on multicore + - `-DWITH_PROFILING=ON` : use internal profilers that print profiling report at end + - `-DVECTORIZE_TARGET=KNL` : generate AVX512 instructions, alternatively you can use: + - `AVX2` for Haswell & Broadwell + - `AVX` for Sandy Bridge and Ivy Bridge + - `-DUSE_OPTIMIZED_KERNELS=ON` : tell the source to source compiler to generate optimized kernels that use Intel extensions + - without these vectorized code will not be generated. + +## run tests + +Run some unit tests +```bash +cd tests +./test.exe +cd .. +``` + +## run miniapp + +The miniapp is the target for benchmarking. +First, we can run a small problem to check the build. +For the small test run, the parameters have the following meaning + - `-n 1000` : 1000 cells + - `-s 200` : 200 synapses per cell + - `-t 20` : simulated for 20ms + - `-p 0` : no file output of voltage traces + +The number of cells is the number of discrete tasks that are distributed to the threads in each large time integration period. +The number of synapses per cell is the amount of computational work per cell/task. +Realistic cells have anywhere in the range of 1,000-10,000 synapses per cell. + +```bash +cd miniapp + +# a small run to check that everything works +./miniapp.exe -n 1000 -s 200 -t 20 -p 0 + +# a larger run for generating meaninful benchmarks +./miniapp.exe -n 2000 -s 2000 -t 100 -p 0 +``` + +This generates the following profiler output (some reformatting to make the table work): + +``` + --------------------------------------- + | small | large | +-------------|-------------------|-------------------| +total | 0.791 100.0 | 38.593 100.0 | + stepping | 0.738 93.3 | 36.978 95.8 | + matrix | 0.406 51.3 | 6.034 15.6 | + solve | 0.308 38.9 | 4.534 11.7 | + setup | 0.082 10.4 | 1.260 3.3 | + other | 0.016 2.0 | 0.240 0.6 | + state | 0.194 24.5 | 23.235 60.2 | + expsyn | 0.158 20.0 | 22.679 58.8 | + hh | 0.014 1.7 | 0.215 0.6 | + pas | 0.003 0.4 | 0.053 0.1 | + other | 0.019 2.4 | 0.287 0.7 | + current | 0.107 13.5 | 7.106 18.4 | + expsyn | 0.047 5.9 | 6.118 15.9 | + pas | 0.028 3.5 | 0.476 1.2 | + hh | 0.006 0.7 | 0.096 0.2 | + other | 0.026 3.3 | 0.415 1.1 | + events | 0.005 0.6 | 0.125 0.3 | + sampling | 0.003 0.4 | 0.051 0.1 | + other | 0.024 3.0 | 0.428 1.1 | + other | 0.053 6.7 | 1.614 4.2 | +----------------------------------------------------- +``` + diff --git a/cmake/CompilerOptions.cmake b/cmake/CompilerOptions.cmake index 167ae04818f6c2eb791704de08d7bd99adb449fb..c92c14c45eea9f6969c6395bc67964fa37f16b30 100644 --- a/cmake/CompilerOptions.cmake +++ b/cmake/CompilerOptions.cmake @@ -13,10 +13,24 @@ if(${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") set(CXXOPT_WALL "${CXXOPT_WALL} -Wno-missing-braces") endif() +if(${CMAKE_CXX_COMPILER_ID} MATCHES "GNU") + + # compiler flags for generating KNL-specific AVX512 instructions + # supported in gcc 4.9.x and later + set(CXXOPT_KNL "-march=knl") + set(CXXOPT_AVX "-mavx") + set(CXXOPT_AVX2 "-march=core-avx2") +endif() + if(${CMAKE_CXX_COMPILER_ID} MATCHES "Intel") # Disable warning for unused template parameter # this is raised by a templated function in the json library set(CXXOPT_WALL "${CXXOPT_WALL} -wd488") + + # compiler flags for generating KNL-specific AVX512 instructions + set(CXXOPT_KNL "-xMIC-AVX512") + set(CXXOPT_AVX "-mavx") + set(CXXOPT_AVX2 "-march=core-avx2") endif() diff --git a/data/cells_small.json b/data/cells_small.json index 01dc71bd84eb623c4a682d45e9665732d36ad57e..93c8c2f3dc3078471f337b0bb2eebc231d78364e 100644 --- a/data/cells_small.json +++ b/data/cells_small.json @@ -60,7 +60,7 @@ "indexes": [1,3,5,6,7,9,11,13,15,17,19,21,23,24,25,27,28,29,30,31,32,33,35,36,37,38,39,41,43,45,47,49,50,51,53,54,55,57,58,59,60,61,63,64,65,67,68,69,71,73], "name": "pas" }], - "parent_index": [0,0,1,0,3,0,5,6,7,0,9,2,11,10,13,10,15,12,17,14,19,14,21,16,23,24,25,16,27,28,29,30,31,32,33,20,35,36,37,38,39,20,41,26,43,26,45,42,47,42,49,50,51,48,53,54,55,48,57,58,59,60,61,62,63,64,65,62,67,68,69,70,71,70,73] + "parent_index": [0,0,1,0,3,0,5,6,7,0,9,0,11,10,13,10,15,0,17,14,19,14,21,16,23,24,25,16,27,28,29,30,31,32,33,20,35,36,37,38,39,20,41,26,43,26,45,42,47,42,49,50,51,48,53,54,55,48,57,58,59,60,61,62,63,64,65,62,67,68,69,70,71,70,73] },{ "mechanisms": [{ "indexes": [1,11,17], @@ -123,7 +123,7 @@ "indexes": [1,3,5,6,7,9,11,13,15,17,19,21,23,24,25,27,28,29,30,31,32,33,35,36,37,38,39,41,43,45,47,49,50,51,53,54,55,57,58,59,60,61,63,64,65,67,68,69,71,73], "name": "pas" }], - "parent_index": [0,0,1,0,3,0,5,6,7,0,9,2,11,10,13,10,15,12,17,14,19,14,21,16,23,24,25,16,27,28,29,30,31,32,33,20,35,36,37,38,39,20,41,26,43,26,45,42,47,42,49,50,51,48,53,54,55,48,57,58,59,60,61,62,63,64,65,62,67,68,69,70,71,70,73] + "parent_index": [0,0,1,0,3,0,5,6,7,0,9,0,11,10,13,10,15,0,17,14,19,14,21,16,23,24,25,16,27,28,29,30,31,32,33,20,35,36,37,38,39,20,41,26,43,26,45,42,47,42,49,50,51,48,53,54,55,48,57,58,59,60,61,62,63,64,65,62,67,68,69,70,71,70,73] },{ "mechanisms": [{ "indexes": [1,11,17], @@ -186,7 +186,7 @@ "indexes": [1,3,5,6,7,9,11,13,15,17,19,21,23,24,25,27,28,29,30,31,32,33,35,36,37,38,39,41,43,45,47,49,50,51,53,54,55,57,58,59,60,61,63,64,65,67,68,69,71,73], "name": "pas" }], - "parent_index": [0,0,1,0,3,0,5,6,7,0,9,2,11,10,13,10,15,12,17,14,19,14,21,16,23,24,25,16,27,28,29,30,31,32,33,20,35,36,37,38,39,20,41,26,43,26,45,42,47,42,49,50,51,48,53,54,55,48,57,58,59,60,61,62,63,64,65,62,67,68,69,70,71,70,73] + "parent_index": [0,0,1,0,3,0,5,6,7,0,9,0,11,10,13,10,15,0,17,14,19,14,21,16,23,24,25,16,27,28,29,30,31,32,33,20,35,36,37,38,39,20,41,26,43,26,45,42,47,42,49,50,51,48,53,54,55,48,57,58,59,60,61,62,63,64,65,62,67,68,69,70,71,70,73] },{ "mechanisms": [{ "indexes": [1,11,17], @@ -249,7 +249,7 @@ "indexes": [1,3,5,6,7,9,11,13,15,17,19,21,23,24,25,27,28,29,30,31,32,33,35,36,37,38,39,41,43,45,47,49,50,51,53,54,55,57,58,59,60,61,63,64,65,67,68,69,71,73], "name": "pas" }], - "parent_index": [0,0,1,0,3,0,5,6,7,0,9,2,11,10,13,10,15,12,17,14,19,14,21,16,23,24,25,16,27,28,29,30,31,32,33,20,35,36,37,38,39,20,41,26,43,26,45,42,47,42,49,50,51,48,53,54,55,48,57,58,59,60,61,62,63,64,65,62,67,68,69,70,71,70,73] + "parent_index": [0,0,1,0,3,0,5,6,7,0,9,0,11,10,13,10,15,0,17,14,19,14,21,16,23,24,25,16,27,28,29,30,31,32,33,20,35,36,37,38,39,20,41,26,43,26,45,42,47,42,49,50,51,48,53,54,55,48,57,58,59,60,61,62,63,64,65,62,67,68,69,70,71,70,73] },{ "mechanisms": [{ "indexes": [1,23,59], @@ -312,7 +312,7 @@ "indexes": [1,3,5,7,9,10,11,13,14,15,16,17,18,19,21,23,25,26,27,29,31,33,35,37,39,41,42,43,44,45,47,49,50,51,52,53,54,55,56,57,59,61,62,63,65,67,68,69,70,71,73,75,77,79,80,81,83,84,85,87,89,90,91,92,93,95,97,98,99,101,103,104,105,107,109,111,113,115,117,118,119,121,123,125,127,128,129,130,131,133,134,135,136,137,139,140,141,143,144,145,147,148,149,150,151,153,154,155,157,158,159,160,161,163,165,166,167,168,169,170,171,173,174,175,176,177,179,180,181,183,185,186,187,188,189,191,193,194,195,196,197,199,201,202,203,204,205,206,207,209,210,211,212,213,214,215,217,218,219,220,221,222,223,225,226,227,229,230,231,233,234,235,237,239,240,241,243,244,245,246,247,249,250,251,253,254,255,256,257,258,259,261,262,263,264,265,267,268,269,270,271,272,273,274,275,277,278,279,281,282,283,284,285,287,289,290,291,293,294,295,296,297,299,300,301,303,304,305,307,309,311,312,313,315,316,317,318,319,321,323,325,326,327,329,331,332,333,335,336,337,338,339,340,341,343,344,345,346,347,348,349,351,352,353,355,356,357,359,360,361,363], "name": "pas" }], - "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,0,13,14,15,16,17,18,19,0,21,2,23,4,25,26,27,4,29,6,31,6,33,8,35,8,37,12,39,12,41,42,43,44,45,22,47,22,49,50,51,52,53,54,55,56,57,24,59,28,61,62,63,28,65,30,67,68,69,70,71,30,73,32,75,32,77,34,79,80,81,34,83,84,85,36,87,36,89,90,91,92,93,38,95,38,97,98,99,40,101,40,103,104,105,48,107,48,109,74,111,74,113,76,115,76,117,118,119,78,121,78,123,88,125,88,127,128,129,130,131,96,133,134,135,136,137,96,139,140,141,100,143,144,145,100,147,148,149,150,151,108,153,154,155,108,157,158,159,160,161,110,163,110,165,166,167,168,169,170,171,112,173,174,175,176,177,112,179,180,181,114,183,114,185,186,187,188,189,116,191,116,193,194,195,196,197,122,199,122,201,202,203,204,205,206,207,124,209,210,211,212,213,214,215,124,217,218,219,220,221,222,223,126,225,226,227,126,229,230,231,142,233,234,235,142,237,164,239,240,241,164,243,244,245,246,247,184,249,250,251,184,253,254,255,256,257,258,259,190,261,262,263,264,265,190,267,268,269,270,271,272,273,274,275,192,277,278,279,192,281,282,283,284,285,200,287,200,289,290,291,276,293,294,295,296,297,276,299,300,301,280,303,304,305,280,307,288,309,288,311,312,313,298,315,316,317,318,319,298,321,302,323,302,325,326,327,322,329,322,331,332,333,324,335,336,337,338,339,340,341,324,343,344,345,346,347,348,349,328,351,352,353,328,355,356,357,330,359,360,361,330,363] + "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,0,13,14,15,16,17,18,19,0,21,0,23,4,25,26,27,4,29,6,31,6,33,8,35,8,37,12,39,12,41,42,43,44,45,22,47,22,49,50,51,52,53,54,55,56,57,0,59,28,61,62,63,28,65,30,67,68,69,70,71,30,73,32,75,32,77,34,79,80,81,34,83,84,85,36,87,36,89,90,91,92,93,38,95,38,97,98,99,40,101,40,103,104,105,48,107,48,109,74,111,74,113,76,115,76,117,118,119,78,121,78,123,88,125,88,127,128,129,130,131,96,133,134,135,136,137,96,139,140,141,100,143,144,145,100,147,148,149,150,151,108,153,154,155,108,157,158,159,160,161,110,163,110,165,166,167,168,169,170,171,112,173,174,175,176,177,112,179,180,181,114,183,114,185,186,187,188,189,116,191,116,193,194,195,196,197,122,199,122,201,202,203,204,205,206,207,124,209,210,211,212,213,214,215,124,217,218,219,220,221,222,223,126,225,226,227,126,229,230,231,142,233,234,235,142,237,164,239,240,241,164,243,244,245,246,247,184,249,250,251,184,253,254,255,256,257,258,259,190,261,262,263,264,265,190,267,268,269,270,271,272,273,274,275,192,277,278,279,192,281,282,283,284,285,200,287,200,289,290,291,276,293,294,295,296,297,276,299,300,301,280,303,304,305,280,307,288,309,288,311,312,313,298,315,316,317,318,319,298,321,302,323,302,325,326,327,322,329,322,331,332,333,324,335,336,337,338,339,340,341,324,343,344,345,346,347,348,349,328,351,352,353,328,355,356,357,330,359,360,361,330,363] },{ "mechanisms": [{ "indexes": [1,23,59], @@ -375,7 +375,7 @@ "indexes": [1,3,5,7,9,10,11,13,14,15,16,17,18,19,21,23,25,26,27,29,31,33,35,37,39,41,42,43,44,45,47,49,50,51,52,53,54,55,56,57,59,61,62,63,65,67,68,69,70,71,73,75,77,79,80,81,83,84,85,87,89,90,91,92,93,95,97,98,99,101,103,104,105,107,109,111,113,115,117,118,119,121,123,125,127,128,129,130,131,133,134,135,136,137,139,140,141,143,144,145,147,148,149,150,151,153,154,155,157,158,159,160,161,163,165,166,167,168,169,170,171,173,174,175,176,177,179,180,181,183,185,186,187,188,189,191,193,194,195,196,197,199,201,202,203,204,205,206,207,209,210,211,212,213,214,215,217,218,219,220,221,222,223,225,226,227,229,230,231,233,234,235,237,239,240,241,243,244,245,246,247,249,250,251,253,254,255,256,257,258,259,261,262,263,264,265,267,268,269,270,271,272,273,274,275,277,278,279,281,282,283,284,285,287,289,290,291,293,294,295,296,297,299,300,301,303,304,305,307,309,311,312,313,315,316,317,318,319,321,323,325,326,327,329,331,332,333,335,336,337,338,339,340,341,343,344,345,346,347,348,349,351,352,353,355,356,357,359,360,361,363], "name": "pas" }], - "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,0,13,14,15,16,17,18,19,0,21,2,23,4,25,26,27,4,29,6,31,6,33,8,35,8,37,12,39,12,41,42,43,44,45,22,47,22,49,50,51,52,53,54,55,56,57,24,59,28,61,62,63,28,65,30,67,68,69,70,71,30,73,32,75,32,77,34,79,80,81,34,83,84,85,36,87,36,89,90,91,92,93,38,95,38,97,98,99,40,101,40,103,104,105,48,107,48,109,74,111,74,113,76,115,76,117,118,119,78,121,78,123,88,125,88,127,128,129,130,131,96,133,134,135,136,137,96,139,140,141,100,143,144,145,100,147,148,149,150,151,108,153,154,155,108,157,158,159,160,161,110,163,110,165,166,167,168,169,170,171,112,173,174,175,176,177,112,179,180,181,114,183,114,185,186,187,188,189,116,191,116,193,194,195,196,197,122,199,122,201,202,203,204,205,206,207,124,209,210,211,212,213,214,215,124,217,218,219,220,221,222,223,126,225,226,227,126,229,230,231,142,233,234,235,142,237,164,239,240,241,164,243,244,245,246,247,184,249,250,251,184,253,254,255,256,257,258,259,190,261,262,263,264,265,190,267,268,269,270,271,272,273,274,275,192,277,278,279,192,281,282,283,284,285,200,287,200,289,290,291,276,293,294,295,296,297,276,299,300,301,280,303,304,305,280,307,288,309,288,311,312,313,298,315,316,317,318,319,298,321,302,323,302,325,326,327,322,329,322,331,332,333,324,335,336,337,338,339,340,341,324,343,344,345,346,347,348,349,328,351,352,353,328,355,356,357,330,359,360,361,330,363] + "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,0,13,14,15,16,17,18,19,0,21,0,23,4,25,26,27,4,29,6,31,6,33,8,35,8,37,12,39,12,41,42,43,44,45,22,47,22,49,50,51,52,53,54,55,56,57,0,59,28,61,62,63,28,65,30,67,68,69,70,71,30,73,32,75,32,77,34,79,80,81,34,83,84,85,36,87,36,89,90,91,92,93,38,95,38,97,98,99,40,101,40,103,104,105,48,107,48,109,74,111,74,113,76,115,76,117,118,119,78,121,78,123,88,125,88,127,128,129,130,131,96,133,134,135,136,137,96,139,140,141,100,143,144,145,100,147,148,149,150,151,108,153,154,155,108,157,158,159,160,161,110,163,110,165,166,167,168,169,170,171,112,173,174,175,176,177,112,179,180,181,114,183,114,185,186,187,188,189,116,191,116,193,194,195,196,197,122,199,122,201,202,203,204,205,206,207,124,209,210,211,212,213,214,215,124,217,218,219,220,221,222,223,126,225,226,227,126,229,230,231,142,233,234,235,142,237,164,239,240,241,164,243,244,245,246,247,184,249,250,251,184,253,254,255,256,257,258,259,190,261,262,263,264,265,190,267,268,269,270,271,272,273,274,275,192,277,278,279,192,281,282,283,284,285,200,287,200,289,290,291,276,293,294,295,296,297,276,299,300,301,280,303,304,305,280,307,288,309,288,311,312,313,298,315,316,317,318,319,298,321,302,323,302,325,326,327,322,329,322,331,332,333,324,335,336,337,338,339,340,341,324,343,344,345,346,347,348,349,328,351,352,353,328,355,356,357,330,359,360,361,330,363] },{ "mechanisms": [{ "indexes": [1,23,59], @@ -438,7 +438,7 @@ "indexes": [1,3,5,7,9,10,11,13,14,15,16,17,18,19,21,23,25,26,27,29,31,33,35,37,39,41,42,43,44,45,47,49,50,51,52,53,54,55,56,57,59,61,62,63,65,67,68,69,70,71,73,75,77,79,80,81,83,84,85,87,89,90,91,92,93,95,97,98,99,101,103,104,105,107,109,111,113,115,117,118,119,121,123,125,127,128,129,130,131,133,134,135,136,137,139,140,141,143,144,145,147,148,149,150,151,153,154,155,157,158,159,160,161,163,165,166,167,168,169,170,171,173,174,175,176,177,179,180,181,183,185,186,187,188,189,191,193,194,195,196,197,199,201,202,203,204,205,206,207,209,210,211,212,213,214,215,217,218,219,220,221,222,223,225,226,227,229,230,231,233,234,235,237,239,240,241,243,244,245,246,247,249,250,251,253,254,255,256,257,258,259,261,262,263,264,265,267,268,269,270,271,272,273,274,275,277,278,279,281,282,283,284,285,287,289,290,291,293,294,295,296,297,299,300,301,303,304,305,307,309,311,312,313,315,316,317,318,319,321,323,325,326,327,329,331,332,333,335,336,337,338,339,340,341,343,344,345,346,347,348,349,351,352,353,355,356,357,359,360,361,363], "name": "pas" }], - "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,0,13,14,15,16,17,18,19,0,21,2,23,4,25,26,27,4,29,6,31,6,33,8,35,8,37,12,39,12,41,42,43,44,45,22,47,22,49,50,51,52,53,54,55,56,57,24,59,28,61,62,63,28,65,30,67,68,69,70,71,30,73,32,75,32,77,34,79,80,81,34,83,84,85,36,87,36,89,90,91,92,93,38,95,38,97,98,99,40,101,40,103,104,105,48,107,48,109,74,111,74,113,76,115,76,117,118,119,78,121,78,123,88,125,88,127,128,129,130,131,96,133,134,135,136,137,96,139,140,141,100,143,144,145,100,147,148,149,150,151,108,153,154,155,108,157,158,159,160,161,110,163,110,165,166,167,168,169,170,171,112,173,174,175,176,177,112,179,180,181,114,183,114,185,186,187,188,189,116,191,116,193,194,195,196,197,122,199,122,201,202,203,204,205,206,207,124,209,210,211,212,213,214,215,124,217,218,219,220,221,222,223,126,225,226,227,126,229,230,231,142,233,234,235,142,237,164,239,240,241,164,243,244,245,246,247,184,249,250,251,184,253,254,255,256,257,258,259,190,261,262,263,264,265,190,267,268,269,270,271,272,273,274,275,192,277,278,279,192,281,282,283,284,285,200,287,200,289,290,291,276,293,294,295,296,297,276,299,300,301,280,303,304,305,280,307,288,309,288,311,312,313,298,315,316,317,318,319,298,321,302,323,302,325,326,327,322,329,322,331,332,333,324,335,336,337,338,339,340,341,324,343,344,345,346,347,348,349,328,351,352,353,328,355,356,357,330,359,360,361,330,363] + "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,0,13,14,15,16,17,18,19,0,21,0,23,4,25,26,27,4,29,6,31,6,33,8,35,8,37,12,39,12,41,42,43,44,45,22,47,22,49,50,51,52,53,54,55,56,57,0,59,28,61,62,63,28,65,30,67,68,69,70,71,30,73,32,75,32,77,34,79,80,81,34,83,84,85,36,87,36,89,90,91,92,93,38,95,38,97,98,99,40,101,40,103,104,105,48,107,48,109,74,111,74,113,76,115,76,117,118,119,78,121,78,123,88,125,88,127,128,129,130,131,96,133,134,135,136,137,96,139,140,141,100,143,144,145,100,147,148,149,150,151,108,153,154,155,108,157,158,159,160,161,110,163,110,165,166,167,168,169,170,171,112,173,174,175,176,177,112,179,180,181,114,183,114,185,186,187,188,189,116,191,116,193,194,195,196,197,122,199,122,201,202,203,204,205,206,207,124,209,210,211,212,213,214,215,124,217,218,219,220,221,222,223,126,225,226,227,126,229,230,231,142,233,234,235,142,237,164,239,240,241,164,243,244,245,246,247,184,249,250,251,184,253,254,255,256,257,258,259,190,261,262,263,264,265,190,267,268,269,270,271,272,273,274,275,192,277,278,279,192,281,282,283,284,285,200,287,200,289,290,291,276,293,294,295,296,297,276,299,300,301,280,303,304,305,280,307,288,309,288,311,312,313,298,315,316,317,318,319,298,321,302,323,302,325,326,327,322,329,322,331,332,333,324,335,336,337,338,339,340,341,324,343,344,345,346,347,348,349,328,351,352,353,328,355,356,357,330,359,360,361,330,363] },{ "mechanisms": [{ "indexes": [1,23,59], @@ -501,7 +501,7 @@ "indexes": [1,3,5,7,9,10,11,13,14,15,16,17,18,19,21,23,25,26,27,29,31,33,35,37,39,41,42,43,44,45,47,49,50,51,52,53,54,55,56,57,59,61,62,63,65,67,68,69,70,71,73,75,77,79,80,81,83,84,85,87,89,90,91,92,93,95,97,98,99,101,103,104,105,107,109,111,113,115,117,118,119,121,123,125,127,128,129,130,131,133,134,135,136,137,139,140,141,143,144,145,147,148,149,150,151,153,154,155,157,158,159,160,161,163,165,166,167,168,169,170,171,173,174,175,176,177,179,180,181,183,185,186,187,188,189,191,193,194,195,196,197,199,201,202,203,204,205,206,207,209,210,211,212,213,214,215,217,218,219,220,221,222,223,225,226,227,229,230,231,233,234,235,237,239,240,241,243,244,245,246,247,249,250,251,253,254,255,256,257,258,259,261,262,263,264,265,267,268,269,270,271,272,273,274,275,277,278,279,281,282,283,284,285,287,289,290,291,293,294,295,296,297,299,300,301,303,304,305,307,309,311,312,313,315,316,317,318,319,321,323,325,326,327,329,331,332,333,335,336,337,338,339,340,341,343,344,345,346,347,348,349,351,352,353,355,356,357,359,360,361,363], "name": "pas" }], - "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,0,13,14,15,16,17,18,19,0,21,2,23,4,25,26,27,4,29,6,31,6,33,8,35,8,37,12,39,12,41,42,43,44,45,22,47,22,49,50,51,52,53,54,55,56,57,24,59,28,61,62,63,28,65,30,67,68,69,70,71,30,73,32,75,32,77,34,79,80,81,34,83,84,85,36,87,36,89,90,91,92,93,38,95,38,97,98,99,40,101,40,103,104,105,48,107,48,109,74,111,74,113,76,115,76,117,118,119,78,121,78,123,88,125,88,127,128,129,130,131,96,133,134,135,136,137,96,139,140,141,100,143,144,145,100,147,148,149,150,151,108,153,154,155,108,157,158,159,160,161,110,163,110,165,166,167,168,169,170,171,112,173,174,175,176,177,112,179,180,181,114,183,114,185,186,187,188,189,116,191,116,193,194,195,196,197,122,199,122,201,202,203,204,205,206,207,124,209,210,211,212,213,214,215,124,217,218,219,220,221,222,223,126,225,226,227,126,229,230,231,142,233,234,235,142,237,164,239,240,241,164,243,244,245,246,247,184,249,250,251,184,253,254,255,256,257,258,259,190,261,262,263,264,265,190,267,268,269,270,271,272,273,274,275,192,277,278,279,192,281,282,283,284,285,200,287,200,289,290,291,276,293,294,295,296,297,276,299,300,301,280,303,304,305,280,307,288,309,288,311,312,313,298,315,316,317,318,319,298,321,302,323,302,325,326,327,322,329,322,331,332,333,324,335,336,337,338,339,340,341,324,343,344,345,346,347,348,349,328,351,352,353,328,355,356,357,330,359,360,361,330,363] + "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,0,13,14,15,16,17,18,19,0,21,0,23,4,25,26,27,4,29,6,31,6,33,8,35,8,37,12,39,12,41,42,43,44,45,22,47,22,49,50,51,52,53,54,55,56,57,0,59,28,61,62,63,28,65,30,67,68,69,70,71,30,73,32,75,32,77,34,79,80,81,34,83,84,85,36,87,36,89,90,91,92,93,38,95,38,97,98,99,40,101,40,103,104,105,48,107,48,109,74,111,74,113,76,115,76,117,118,119,78,121,78,123,88,125,88,127,128,129,130,131,96,133,134,135,136,137,96,139,140,141,100,143,144,145,100,147,148,149,150,151,108,153,154,155,108,157,158,159,160,161,110,163,110,165,166,167,168,169,170,171,112,173,174,175,176,177,112,179,180,181,114,183,114,185,186,187,188,189,116,191,116,193,194,195,196,197,122,199,122,201,202,203,204,205,206,207,124,209,210,211,212,213,214,215,124,217,218,219,220,221,222,223,126,225,226,227,126,229,230,231,142,233,234,235,142,237,164,239,240,241,164,243,244,245,246,247,184,249,250,251,184,253,254,255,256,257,258,259,190,261,262,263,264,265,190,267,268,269,270,271,272,273,274,275,192,277,278,279,192,281,282,283,284,285,200,287,200,289,290,291,276,293,294,295,296,297,276,299,300,301,280,303,304,305,280,307,288,309,288,311,312,313,298,315,316,317,318,319,298,321,302,323,302,325,326,327,322,329,322,331,332,333,324,335,336,337,338,339,340,341,324,343,344,345,346,347,348,349,328,351,352,353,328,355,356,357,330,359,360,361,330,363] },{ "mechanisms": [{ "indexes": [1,19,57], @@ -564,7 +564,7 @@ "indexes": [1,3,5,7,9,10,11,12,13,15,17,19,21,22,23,25,27,29,31,33,34,35,37,39,41,42,43,45,47,48,49,50,51,52,53,55,57,59,60,61,63,65,66,67,69,71,73,74,75,77,78,79,81,83,85,86,87,88,89,91,93,95,97,99,100,101,102,103,105,107,109,110,111,113,115,116,117,119,121,123,124,125,127,128,129,131,133,135,137,138,139,141,143,144,145,147,149,151,153,154,155,157,159,161,162,163,165,166,167,169,170,171,173,175,176,177,179,180,181,182,183,185,186,187,189,191,192,193,194,195,197,198,199,201,203,205,207,208,209,211,212,213,215,216,217,219,220,221,223,225,226,227,229,230,231,232,233,235,237,239,241,243,244,245,247,249,251,253,254,255,256,257,259,261,262,263,264,265,267,269,271,273,274,275,277,279,281,283,285,286,287,289,291,292,293,295,297,299,300,301,302,303,305,306,307,308,309,310,311,313,314,315,317,318,319,320,321,322,323,324,325,327,328,329,331,332,333,335,337,338,339,341,343,345,346,347,348,349,351,353,355,357,359,360,361], "name": "pas" }], - "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,12,13,0,15,0,17,2,19,4,21,22,23,4,25,6,27,6,29,8,31,8,33,34,35,14,37,14,39,16,41,42,43,16,45,18,47,48,49,50,51,52,53,18,55,20,57,24,59,60,61,24,63,26,65,66,67,26,69,28,71,28,73,74,75,30,77,78,79,30,81,32,83,32,85,86,87,88,89,36,91,36,93,46,95,46,97,56,99,100,101,102,103,56,105,64,107,64,109,110,111,68,113,68,115,116,117,70,119,70,121,72,123,124,125,72,127,128,129,76,131,76,133,80,135,80,137,138,139,82,141,82,143,144,145,92,147,92,149,94,151,94,153,154,155,96,157,96,159,104,161,162,163,104,165,166,167,106,169,170,171,106,173,120,175,176,177,120,179,180,181,182,183,122,185,186,187,122,189,126,191,192,193,194,195,126,197,198,199,132,201,132,203,140,205,140,207,208,209,142,211,212,213,142,215,216,217,146,219,220,221,146,223,152,225,226,227,152,229,230,231,232,233,160,235,160,237,172,239,172,241,174,243,244,245,174,247,188,249,188,251,190,253,254,255,256,257,190,259,218,261,262,263,264,265,218,267,224,269,224,271,246,273,274,275,246,277,248,279,248,281,260,283,260,285,286,287,278,289,278,291,292,293,282,295,282,297,288,299,300,301,302,303,288,305,306,307,308,309,310,311,304,313,314,315,304,317,318,319,320,321,322,323,324,325,312,327,328,329,312,331,332,333,334,335,334,337,338,339,340,341,340,343,342,345,346,347,348,349,342,351,344,353,344,355,356,357,356,359,360,361] + "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,12,13,0,15,0,17,0,19,4,21,22,23,4,25,6,27,6,29,8,31,8,33,34,35,14,37,14,39,16,41,42,43,16,45,18,47,48,49,50,51,52,53,18,55,0,57,24,59,60,61,24,63,26,65,66,67,26,69,28,71,28,73,74,75,30,77,78,79,30,81,32,83,32,85,86,87,88,89,36,91,36,93,46,95,46,97,56,99,100,101,102,103,56,105,64,107,64,109,110,111,68,113,68,115,116,117,70,119,70,121,72,123,124,125,72,127,128,129,76,131,76,133,80,135,80,137,138,139,82,141,82,143,144,145,92,147,92,149,94,151,94,153,154,155,96,157,96,159,104,161,162,163,104,165,166,167,106,169,170,171,106,173,120,175,176,177,120,179,180,181,182,183,122,185,186,187,122,189,126,191,192,193,194,195,126,197,198,199,132,201,132,203,140,205,140,207,208,209,142,211,212,213,142,215,216,217,146,219,220,221,146,223,152,225,226,227,152,229,230,231,232,233,160,235,160,237,172,239,172,241,174,243,244,245,174,247,188,249,188,251,190,253,254,255,256,257,190,259,218,261,262,263,264,265,218,267,224,269,224,271,246,273,274,275,246,277,248,279,248,281,260,283,260,285,286,287,278,289,278,291,292,293,282,295,282,297,288,299,300,301,302,303,288,305,306,307,308,309,310,311,304,313,314,315,304,317,318,319,320,321,322,323,324,325,312,327,328,329,312,331,332,333,334,335,334,337,338,339,340,341,340,343,342,345,346,347,348,349,342,351,344,353,344,355,356,357,356,359,360,361] },{ "mechanisms": [{ "indexes": [1,19,57], @@ -627,7 +627,7 @@ "indexes": [1,3,5,7,9,10,11,12,13,15,17,19,21,22,23,25,27,29,31,33,34,35,37,39,41,42,43,45,47,48,49,50,51,52,53,55,57,59,60,61,63,65,66,67,69,71,73,74,75,77,78,79,81,83,85,86,87,88,89,91,93,95,97,99,100,101,102,103,105,107,109,110,111,113,115,116,117,119,121,123,124,125,127,128,129,131,133,135,137,138,139,141,143,144,145,147,149,151,153,154,155,157,159,161,162,163,165,166,167,169,170,171,173,175,176,177,179,180,181,182,183,185,186,187,189,191,192,193,194,195,197,198,199,201,203,205,207,208,209,211,212,213,215,216,217,219,220,221,223,225,226,227,229,230,231,232,233,235,237,239,241,243,244,245,247,249,251,253,254,255,256,257,259,261,262,263,264,265,267,269,271,273,274,275,277,279,281,283,285,286,287,289,291,292,293,295,297,299,300,301,302,303,305,306,307,308,309,310,311,313,314,315,317,318,319,320,321,322,323,324,325,327,328,329,331,332,333,335,337,338,339,341,343,345,346,347,348,349,351,353,355,357,359,360,361], "name": "pas" }], - "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,12,13,0,15,0,17,2,19,4,21,22,23,4,25,6,27,6,29,8,31,8,33,34,35,14,37,14,39,16,41,42,43,16,45,18,47,48,49,50,51,52,53,18,55,20,57,24,59,60,61,24,63,26,65,66,67,26,69,28,71,28,73,74,75,30,77,78,79,30,81,32,83,32,85,86,87,88,89,36,91,36,93,46,95,46,97,56,99,100,101,102,103,56,105,64,107,64,109,110,111,68,113,68,115,116,117,70,119,70,121,72,123,124,125,72,127,128,129,76,131,76,133,80,135,80,137,138,139,82,141,82,143,144,145,92,147,92,149,94,151,94,153,154,155,96,157,96,159,104,161,162,163,104,165,166,167,106,169,170,171,106,173,120,175,176,177,120,179,180,181,182,183,122,185,186,187,122,189,126,191,192,193,194,195,126,197,198,199,132,201,132,203,140,205,140,207,208,209,142,211,212,213,142,215,216,217,146,219,220,221,146,223,152,225,226,227,152,229,230,231,232,233,160,235,160,237,172,239,172,241,174,243,244,245,174,247,188,249,188,251,190,253,254,255,256,257,190,259,218,261,262,263,264,265,218,267,224,269,224,271,246,273,274,275,246,277,248,279,248,281,260,283,260,285,286,287,278,289,278,291,292,293,282,295,282,297,288,299,300,301,302,303,288,305,306,307,308,309,310,311,304,313,314,315,304,317,318,319,320,321,322,323,324,325,312,327,328,329,312,331,332,333,334,335,334,337,338,339,340,341,340,343,342,345,346,347,348,349,342,351,344,353,344,355,356,357,356,359,360,361] + "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,12,13,0,15,0,17,0,19,4,21,22,23,4,25,6,27,6,29,8,31,8,33,34,35,14,37,14,39,16,41,42,43,16,45,18,47,48,49,50,51,52,53,18,55,0,57,24,59,60,61,24,63,26,65,66,67,26,69,28,71,28,73,74,75,30,77,78,79,30,81,32,83,32,85,86,87,88,89,36,91,36,93,46,95,46,97,56,99,100,101,102,103,56,105,64,107,64,109,110,111,68,113,68,115,116,117,70,119,70,121,72,123,124,125,72,127,128,129,76,131,76,133,80,135,80,137,138,139,82,141,82,143,144,145,92,147,92,149,94,151,94,153,154,155,96,157,96,159,104,161,162,163,104,165,166,167,106,169,170,171,106,173,120,175,176,177,120,179,180,181,182,183,122,185,186,187,122,189,126,191,192,193,194,195,126,197,198,199,132,201,132,203,140,205,140,207,208,209,142,211,212,213,142,215,216,217,146,219,220,221,146,223,152,225,226,227,152,229,230,231,232,233,160,235,160,237,172,239,172,241,174,243,244,245,174,247,188,249,188,251,190,253,254,255,256,257,190,259,218,261,262,263,264,265,218,267,224,269,224,271,246,273,274,275,246,277,248,279,248,281,260,283,260,285,286,287,278,289,278,291,292,293,282,295,282,297,288,299,300,301,302,303,288,305,306,307,308,309,310,311,304,313,314,315,304,317,318,319,320,321,322,323,324,325,312,327,328,329,312,331,332,333,334,335,334,337,338,339,340,341,340,343,342,345,346,347,348,349,342,351,344,353,344,355,356,357,356,359,360,361] },{ "mechanisms": [{ "indexes": [1,19,57], @@ -690,7 +690,7 @@ "indexes": [1,3,5,7,9,10,11,12,13,15,17,19,21,22,23,25,27,29,31,33,34,35,37,39,41,42,43,45,47,48,49,50,51,52,53,55,57,59,60,61,63,65,66,67,69,71,73,74,75,77,78,79,81,83,85,86,87,88,89,91,93,95,97,99,100,101,102,103,105,107,109,110,111,113,115,116,117,119,121,123,124,125,127,128,129,131,133,135,137,138,139,141,143,144,145,147,149,151,153,154,155,157,159,161,162,163,165,166,167,169,170,171,173,175,176,177,179,180,181,182,183,185,186,187,189,191,192,193,194,195,197,198,199,201,203,205,207,208,209,211,212,213,215,216,217,219,220,221,223,225,226,227,229,230,231,232,233,235,237,239,241,243,244,245,247,249,251,253,254,255,256,257,259,261,262,263,264,265,267,269,271,273,274,275,277,279,281,283,285,286,287,289,291,292,293,295,297,299,300,301,302,303,305,306,307,308,309,310,311,313,314,315,317,318,319,320,321,322,323,324,325,327,328,329,331,332,333,335,337,338,339,341,343,345,346,347,348,349,351,353,355,357,359,360,361], "name": "pas" }], - "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,12,13,0,15,0,17,2,19,4,21,22,23,4,25,6,27,6,29,8,31,8,33,34,35,14,37,14,39,16,41,42,43,16,45,18,47,48,49,50,51,52,53,18,55,20,57,24,59,60,61,24,63,26,65,66,67,26,69,28,71,28,73,74,75,30,77,78,79,30,81,32,83,32,85,86,87,88,89,36,91,36,93,46,95,46,97,56,99,100,101,102,103,56,105,64,107,64,109,110,111,68,113,68,115,116,117,70,119,70,121,72,123,124,125,72,127,128,129,76,131,76,133,80,135,80,137,138,139,82,141,82,143,144,145,92,147,92,149,94,151,94,153,154,155,96,157,96,159,104,161,162,163,104,165,166,167,106,169,170,171,106,173,120,175,176,177,120,179,180,181,182,183,122,185,186,187,122,189,126,191,192,193,194,195,126,197,198,199,132,201,132,203,140,205,140,207,208,209,142,211,212,213,142,215,216,217,146,219,220,221,146,223,152,225,226,227,152,229,230,231,232,233,160,235,160,237,172,239,172,241,174,243,244,245,174,247,188,249,188,251,190,253,254,255,256,257,190,259,218,261,262,263,264,265,218,267,224,269,224,271,246,273,274,275,246,277,248,279,248,281,260,283,260,285,286,287,278,289,278,291,292,293,282,295,282,297,288,299,300,301,302,303,288,305,306,307,308,309,310,311,304,313,314,315,304,317,318,319,320,321,322,323,324,325,312,327,328,329,312,331,332,333,334,335,334,337,338,339,340,341,340,343,342,345,346,347,348,349,342,351,344,353,344,355,356,357,356,359,360,361] + "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,12,13,0,15,0,17,0,19,4,21,22,23,4,25,6,27,6,29,8,31,8,33,34,35,14,37,14,39,16,41,42,43,16,45,18,47,48,49,50,51,52,53,18,55,0,57,24,59,60,61,24,63,26,65,66,67,26,69,28,71,28,73,74,75,30,77,78,79,30,81,32,83,32,85,86,87,88,89,36,91,36,93,46,95,46,97,56,99,100,101,102,103,56,105,64,107,64,109,110,111,68,113,68,115,116,117,70,119,70,121,72,123,124,125,72,127,128,129,76,131,76,133,80,135,80,137,138,139,82,141,82,143,144,145,92,147,92,149,94,151,94,153,154,155,96,157,96,159,104,161,162,163,104,165,166,167,106,169,170,171,106,173,120,175,176,177,120,179,180,181,182,183,122,185,186,187,122,189,126,191,192,193,194,195,126,197,198,199,132,201,132,203,140,205,140,207,208,209,142,211,212,213,142,215,216,217,146,219,220,221,146,223,152,225,226,227,152,229,230,231,232,233,160,235,160,237,172,239,172,241,174,243,244,245,174,247,188,249,188,251,190,253,254,255,256,257,190,259,218,261,262,263,264,265,218,267,224,269,224,271,246,273,274,275,246,277,248,279,248,281,260,283,260,285,286,287,278,289,278,291,292,293,282,295,282,297,288,299,300,301,302,303,288,305,306,307,308,309,310,311,304,313,314,315,304,317,318,319,320,321,322,323,324,325,312,327,328,329,312,331,332,333,334,335,334,337,338,339,340,341,340,343,342,345,346,347,348,349,342,351,344,353,344,355,356,357,356,359,360,361] },{ "mechanisms": [{ "indexes": [1,19,57], @@ -753,7 +753,7 @@ "indexes": [1,3,5,7,9,10,11,12,13,15,17,19,21,22,23,25,27,29,31,33,34,35,37,39,41,42,43,45,47,48,49,50,51,52,53,55,57,59,60,61,63,65,66,67,69,71,73,74,75,77,78,79,81,83,85,86,87,88,89,91,93,95,97,99,100,101,102,103,105,107,109,110,111,113,115,116,117,119,121,123,124,125,127,128,129,131,133,135,137,138,139,141,143,144,145,147,149,151,153,154,155,157,159,161,162,163,165,166,167,169,170,171,173,175,176,177,179,180,181,182,183,185,186,187,189,191,192,193,194,195,197,198,199,201,203,205,207,208,209,211,212,213,215,216,217,219,220,221,223,225,226,227,229,230,231,232,233,235,237,239,241,243,244,245,247,249,251,253,254,255,256,257,259,261,262,263,264,265,267,269,271,273,274,275,277,279,281,283,285,286,287,289,291,292,293,295,297,299,300,301,302,303,305,306,307,308,309,310,311,313,314,315,317,318,319,320,321,322,323,324,325,327,328,329,331,332,333,335,337,338,339,341,343,345,346,347,348,349,351,353,355,357,359,360,361], "name": "pas" }], - "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,12,13,0,15,0,17,2,19,4,21,22,23,4,25,6,27,6,29,8,31,8,33,34,35,14,37,14,39,16,41,42,43,16,45,18,47,48,49,50,51,52,53,18,55,20,57,24,59,60,61,24,63,26,65,66,67,26,69,28,71,28,73,74,75,30,77,78,79,30,81,32,83,32,85,86,87,88,89,36,91,36,93,46,95,46,97,56,99,100,101,102,103,56,105,64,107,64,109,110,111,68,113,68,115,116,117,70,119,70,121,72,123,124,125,72,127,128,129,76,131,76,133,80,135,80,137,138,139,82,141,82,143,144,145,92,147,92,149,94,151,94,153,154,155,96,157,96,159,104,161,162,163,104,165,166,167,106,169,170,171,106,173,120,175,176,177,120,179,180,181,182,183,122,185,186,187,122,189,126,191,192,193,194,195,126,197,198,199,132,201,132,203,140,205,140,207,208,209,142,211,212,213,142,215,216,217,146,219,220,221,146,223,152,225,226,227,152,229,230,231,232,233,160,235,160,237,172,239,172,241,174,243,244,245,174,247,188,249,188,251,190,253,254,255,256,257,190,259,218,261,262,263,264,265,218,267,224,269,224,271,246,273,274,275,246,277,248,279,248,281,260,283,260,285,286,287,278,289,278,291,292,293,282,295,282,297,288,299,300,301,302,303,288,305,306,307,308,309,310,311,304,313,314,315,304,317,318,319,320,321,322,323,324,325,312,327,328,329,312,331,332,333,334,335,334,337,338,339,340,341,340,343,342,345,346,347,348,349,342,351,344,353,344,355,356,357,356,359,360,361] + "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,12,13,0,15,0,17,0,19,4,21,22,23,4,25,6,27,6,29,8,31,8,33,34,35,14,37,14,39,16,41,42,43,16,45,18,47,48,49,50,51,52,53,18,55,0,57,24,59,60,61,24,63,26,65,66,67,26,69,28,71,28,73,74,75,30,77,78,79,30,81,32,83,32,85,86,87,88,89,36,91,36,93,46,95,46,97,56,99,100,101,102,103,56,105,64,107,64,109,110,111,68,113,68,115,116,117,70,119,70,121,72,123,124,125,72,127,128,129,76,131,76,133,80,135,80,137,138,139,82,141,82,143,144,145,92,147,92,149,94,151,94,153,154,155,96,157,96,159,104,161,162,163,104,165,166,167,106,169,170,171,106,173,120,175,176,177,120,179,180,181,182,183,122,185,186,187,122,189,126,191,192,193,194,195,126,197,198,199,132,201,132,203,140,205,140,207,208,209,142,211,212,213,142,215,216,217,146,219,220,221,146,223,152,225,226,227,152,229,230,231,232,233,160,235,160,237,172,239,172,241,174,243,244,245,174,247,188,249,188,251,190,253,254,255,256,257,190,259,218,261,262,263,264,265,218,267,224,269,224,271,246,273,274,275,246,277,248,279,248,281,260,283,260,285,286,287,278,289,278,291,292,293,282,295,282,297,288,299,300,301,302,303,288,305,306,307,308,309,310,311,304,313,314,315,304,317,318,319,320,321,322,323,324,325,312,327,328,329,312,331,332,333,334,335,334,337,338,339,340,341,340,343,342,345,346,347,348,349,342,351,344,353,344,355,356,357,356,359,360,361] },{ "mechanisms": [{ "indexes": [1,27,67], @@ -816,7 +816,7 @@ "indexes": [1,3,5,7,9,10,11,12,13,14,15,17,19,20,21,22,23,24,25,27,29,31,33,34,35,36,37,38,39,41,43,44,45,46,47,49,50,51,52,53,55,56,57,58,59,60,61,62,63,65,67,69,71,73,74,75,76,77,78,79,81,82,83,84,85,87,88,89,90,91,93,95,96,97,98,99,100,101,103,104,105,106,107,108,109,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,143,144,145,147,149,150,151,152,153,154,155,157,159,160,161,162,163,165,166,167,168,169,170,171,172,173,175,176,177,178,179,180,181,183,185,187,188,189,190,191,193,194,195], "name": "pas" }], - "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,12,13,14,15,0,17,0,19,20,21,22,23,24,25,2,27,4,29,4,31,6,33,34,35,36,37,38,39,6,41,8,43,44,45,46,47,8,49,50,51,52,53,18,55,56,57,58,59,60,61,62,63,18,65,28,67,30,69,30,71,32,73,74,75,76,77,78,79,32,81,82,83,84,85,42,87,88,89,90,91,42,93,66,95,96,97,98,99,100,101,66,103,104,105,106,107,108,109,70,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,70,143,144,145,72,147,72,149,150,151,152,153,154,155,80,157,80,159,160,161,162,163,142,165,166,167,168,169,170,171,172,173,142,175,176,177,178,179,180,181,146,183,146,185,148,187,188,189,190,191,148,193,194,195] + "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,12,13,14,15,0,17,0,19,20,21,22,23,24,25,0,27,4,29,4,31,6,33,34,35,36,37,38,39,6,41,8,43,44,45,46,47,8,49,50,51,52,53,18,55,56,57,58,59,60,61,62,63,18,65,0,67,30,69,30,71,32,73,74,75,76,77,78,79,32,81,82,83,84,85,42,87,88,89,90,91,42,93,66,95,96,97,98,99,100,101,66,103,104,105,106,107,108,109,70,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,70,143,144,145,72,147,72,149,150,151,152,153,154,155,80,157,80,159,160,161,162,163,142,165,166,167,168,169,170,171,172,173,142,175,176,177,178,179,180,181,146,183,146,185,148,187,188,189,190,191,148,193,194,195] },{ "mechanisms": [{ "indexes": [1,27,67], @@ -879,7 +879,7 @@ "indexes": [1,3,5,7,9,10,11,12,13,14,15,17,19,20,21,22,23,24,25,27,29,31,33,34,35,36,37,38,39,41,43,44,45,46,47,49,50,51,52,53,55,56,57,58,59,60,61,62,63,65,67,69,71,73,74,75,76,77,78,79,81,82,83,84,85,87,88,89,90,91,93,95,96,97,98,99,100,101,103,104,105,106,107,108,109,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,143,144,145,147,149,150,151,152,153,154,155,157,159,160,161,162,163,165,166,167,168,169,170,171,172,173,175,176,177,178,179,180,181,183,185,187,188,189,190,191,193,194,195], "name": "pas" }], - "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,12,13,14,15,0,17,0,19,20,21,22,23,24,25,2,27,4,29,4,31,6,33,34,35,36,37,38,39,6,41,8,43,44,45,46,47,8,49,50,51,52,53,18,55,56,57,58,59,60,61,62,63,18,65,28,67,30,69,30,71,32,73,74,75,76,77,78,79,32,81,82,83,84,85,42,87,88,89,90,91,42,93,66,95,96,97,98,99,100,101,66,103,104,105,106,107,108,109,70,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,70,143,144,145,72,147,72,149,150,151,152,153,154,155,80,157,80,159,160,161,162,163,142,165,166,167,168,169,170,171,172,173,142,175,176,177,178,179,180,181,146,183,146,185,148,187,188,189,190,191,148,193,194,195] + "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,12,13,14,15,0,17,0,19,20,21,22,23,24,25,0,27,4,29,4,31,6,33,34,35,36,37,38,39,6,41,8,43,44,45,46,47,8,49,50,51,52,53,18,55,56,57,58,59,60,61,62,63,18,65,0,67,30,69,30,71,32,73,74,75,76,77,78,79,32,81,82,83,84,85,42,87,88,89,90,91,42,93,66,95,96,97,98,99,100,101,66,103,104,105,106,107,108,109,70,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,70,143,144,145,72,147,72,149,150,151,152,153,154,155,80,157,80,159,160,161,162,163,142,165,166,167,168,169,170,171,172,173,142,175,176,177,178,179,180,181,146,183,146,185,148,187,188,189,190,191,148,193,194,195] },{ "mechanisms": [{ "indexes": [1,27,67], @@ -942,7 +942,7 @@ "indexes": [1,3,5,7,9,10,11,12,13,14,15,17,19,20,21,22,23,24,25,27,29,31,33,34,35,36,37,38,39,41,43,44,45,46,47,49,50,51,52,53,55,56,57,58,59,60,61,62,63,65,67,69,71,73,74,75,76,77,78,79,81,82,83,84,85,87,88,89,90,91,93,95,96,97,98,99,100,101,103,104,105,106,107,108,109,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,143,144,145,147,149,150,151,152,153,154,155,157,159,160,161,162,163,165,166,167,168,169,170,171,172,173,175,176,177,178,179,180,181,183,185,187,188,189,190,191,193,194,195], "name": "pas" }], - "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,12,13,14,15,0,17,0,19,20,21,22,23,24,25,2,27,4,29,4,31,6,33,34,35,36,37,38,39,6,41,8,43,44,45,46,47,8,49,50,51,52,53,18,55,56,57,58,59,60,61,62,63,18,65,28,67,30,69,30,71,32,73,74,75,76,77,78,79,32,81,82,83,84,85,42,87,88,89,90,91,42,93,66,95,96,97,98,99,100,101,66,103,104,105,106,107,108,109,70,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,70,143,144,145,72,147,72,149,150,151,152,153,154,155,80,157,80,159,160,161,162,163,142,165,166,167,168,169,170,171,172,173,142,175,176,177,178,179,180,181,146,183,146,185,148,187,188,189,190,191,148,193,194,195] + "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,12,13,14,15,0,17,0,19,20,21,22,23,24,25,0,27,4,29,4,31,6,33,34,35,36,37,38,39,6,41,8,43,44,45,46,47,8,49,50,51,52,53,18,55,56,57,58,59,60,61,62,63,18,65,0,67,30,69,30,71,32,73,74,75,76,77,78,79,32,81,82,83,84,85,42,87,88,89,90,91,42,93,66,95,96,97,98,99,100,101,66,103,104,105,106,107,108,109,70,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,70,143,144,145,72,147,72,149,150,151,152,153,154,155,80,157,80,159,160,161,162,163,142,165,166,167,168,169,170,171,172,173,142,175,176,177,178,179,180,181,146,183,146,185,148,187,188,189,190,191,148,193,194,195] },{ "mechanisms": [{ "indexes": [1,27,67], @@ -1005,7 +1005,7 @@ "indexes": [1,3,5,7,9,10,11,12,13,14,15,17,19,20,21,22,23,24,25,27,29,31,33,34,35,36,37,38,39,41,43,44,45,46,47,49,50,51,52,53,55,56,57,58,59,60,61,62,63,65,67,69,71,73,74,75,76,77,78,79,81,82,83,84,85,87,88,89,90,91,93,95,96,97,98,99,100,101,103,104,105,106,107,108,109,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,143,144,145,147,149,150,151,152,153,154,155,157,159,160,161,162,163,165,166,167,168,169,170,171,172,173,175,176,177,178,179,180,181,183,185,187,188,189,190,191,193,194,195], "name": "pas" }], - "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,12,13,14,15,0,17,0,19,20,21,22,23,24,25,2,27,4,29,4,31,6,33,34,35,36,37,38,39,6,41,8,43,44,45,46,47,8,49,50,51,52,53,18,55,56,57,58,59,60,61,62,63,18,65,28,67,30,69,30,71,32,73,74,75,76,77,78,79,32,81,82,83,84,85,42,87,88,89,90,91,42,93,66,95,96,97,98,99,100,101,66,103,104,105,106,107,108,109,70,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,70,143,144,145,72,147,72,149,150,151,152,153,154,155,80,157,80,159,160,161,162,163,142,165,166,167,168,169,170,171,172,173,142,175,176,177,178,179,180,181,146,183,146,185,148,187,188,189,190,191,148,193,194,195] + "parent_index": [0,0,1,0,3,0,5,0,7,0,9,10,11,12,13,14,15,0,17,0,19,20,21,22,23,24,25,0,27,4,29,4,31,6,33,34,35,36,37,38,39,6,41,8,43,44,45,46,47,8,49,50,51,52,53,18,55,56,57,58,59,60,61,62,63,18,65,0,67,30,69,30,71,32,73,74,75,76,77,78,79,32,81,82,83,84,85,42,87,88,89,90,91,42,93,66,95,96,97,98,99,100,101,66,103,104,105,106,107,108,109,70,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,70,143,144,145,72,147,72,149,150,151,152,153,154,155,80,157,80,159,160,161,162,163,142,165,166,167,168,169,170,171,172,173,142,175,176,177,178,179,180,181,146,183,146,185,148,187,188,189,190,191,148,193,194,195] },{ "mechanisms": [{ "indexes": [1,19,57], @@ -1068,7 +1068,7 @@ "indexes": [1,3,5,7,9,11,12,13,14,15,17,19,21,23,25,26,27,28,29,31,32,33,34,35,37,39,40,41,43,45,46,47,48,49,50,51,53,55,57,59,61,63,64,65,66,67,69,70,71,73,75,77,79,81,83,84,85,87,89,91,92,93,94,95,97,98,99,100,101,103,105,107,108,109,110,111,113,114,115,116,117,119,120,121,122,123,125,126,127,128,129,131,133,134,135,137,138,139,140,141,142,143,145,147,148,149,150,151,153,154,155,157,159,160,161,163,164,165,166,167,168,169,170,171,173,174,175,177,178,179,180,181,183,184,185,186,187,189,190,191,193,194,195,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,217,218,219,221,222,223,225,226,227,228,229,231,233,234,235,237,238,239,241,243,245,247,249], "name": "pas" }], - "parent_index": [0,0,1,0,3,0,5,0,7,0,9,0,11,12,13,14,15,0,17,2,19,4,21,4,23,6,25,26,27,28,29,6,31,32,33,34,35,8,37,8,39,40,41,10,43,10,45,46,47,48,49,50,51,18,53,18,55,20,57,22,59,22,61,24,63,64,65,66,67,24,69,70,71,30,73,30,75,36,77,36,79,38,81,38,83,84,85,42,87,42,89,44,91,92,93,94,95,44,97,98,99,100,101,52,103,52,105,54,107,108,109,110,111,54,113,114,115,116,117,56,119,120,121,122,123,56,125,126,127,128,129,60,131,60,133,134,135,62,137,138,139,140,141,142,143,62,145,82,147,148,149,150,151,82,153,154,155,90,157,90,159,160,161,132,163,164,165,166,167,168,169,170,171,132,173,174,175,136,177,178,179,180,181,136,183,184,185,186,187,146,189,190,191,146,193,194,195,172,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,172,217,218,219,176,221,222,223,176,225,226,227,228,229,216,231,216,233,234,235,220,237,238,239,220,241,232,243,232,245,236,247,236,249] + "parent_index": [0,0,1,0,3,0,5,0,7,0,9,0,11,12,13,14,15,0,17,0,19,4,21,4,23,6,25,26,27,28,29,6,31,32,33,34,35,8,37,8,39,40,41,10,43,10,45,46,47,48,49,50,51,18,53,18,55,0,57,22,59,22,61,24,63,64,65,66,67,24,69,70,71,30,73,30,75,36,77,36,79,38,81,38,83,84,85,42,87,42,89,44,91,92,93,94,95,44,97,98,99,100,101,52,103,52,105,54,107,108,109,110,111,54,113,114,115,116,117,56,119,120,121,122,123,56,125,126,127,128,129,60,131,60,133,134,135,62,137,138,139,140,141,142,143,62,145,82,147,148,149,150,151,82,153,154,155,90,157,90,159,160,161,132,163,164,165,166,167,168,169,170,171,132,173,174,175,136,177,178,179,180,181,136,183,184,185,186,187,146,189,190,191,146,193,194,195,172,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,172,217,218,219,176,221,222,223,176,225,226,227,228,229,216,231,216,233,234,235,220,237,238,239,220,241,232,243,232,245,236,247,236,249] },{ "mechanisms": [{ "indexes": [1,19,57], @@ -1131,7 +1131,7 @@ "indexes": [1,3,5,7,9,11,12,13,14,15,17,19,21,23,25,26,27,28,29,31,32,33,34,35,37,39,40,41,43,45,46,47,48,49,50,51,53,55,57,59,61,63,64,65,66,67,69,70,71,73,75,77,79,81,83,84,85,87,89,91,92,93,94,95,97,98,99,100,101,103,105,107,108,109,110,111,113,114,115,116,117,119,120,121,122,123,125,126,127,128,129,131,133,134,135,137,138,139,140,141,142,143,145,147,148,149,150,151,153,154,155,157,159,160,161,163,164,165,166,167,168,169,170,171,173,174,175,177,178,179,180,181,183,184,185,186,187,189,190,191,193,194,195,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,217,218,219,221,222,223,225,226,227,228,229,231,233,234,235,237,238,239,241,243,245,247,249], "name": "pas" }], - "parent_index": [0,0,1,0,3,0,5,0,7,0,9,0,11,12,13,14,15,0,17,2,19,4,21,4,23,6,25,26,27,28,29,6,31,32,33,34,35,8,37,8,39,40,41,10,43,10,45,46,47,48,49,50,51,18,53,18,55,20,57,22,59,22,61,24,63,64,65,66,67,24,69,70,71,30,73,30,75,36,77,36,79,38,81,38,83,84,85,42,87,42,89,44,91,92,93,94,95,44,97,98,99,100,101,52,103,52,105,54,107,108,109,110,111,54,113,114,115,116,117,56,119,120,121,122,123,56,125,126,127,128,129,60,131,60,133,134,135,62,137,138,139,140,141,142,143,62,145,82,147,148,149,150,151,82,153,154,155,90,157,90,159,160,161,132,163,164,165,166,167,168,169,170,171,132,173,174,175,136,177,178,179,180,181,136,183,184,185,186,187,146,189,190,191,146,193,194,195,172,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,172,217,218,219,176,221,222,223,176,225,226,227,228,229,216,231,216,233,234,235,220,237,238,239,220,241,232,243,232,245,236,247,236,249] + "parent_index": [0,0,1,0,3,0,5,0,7,0,9,0,11,12,13,14,15,0,17,0,19,4,21,4,23,6,25,26,27,28,29,6,31,32,33,34,35,8,37,8,39,40,41,10,43,10,45,46,47,48,49,50,51,18,53,18,55,0,57,22,59,22,61,24,63,64,65,66,67,24,69,70,71,30,73,30,75,36,77,36,79,38,81,38,83,84,85,42,87,42,89,44,91,92,93,94,95,44,97,98,99,100,101,52,103,52,105,54,107,108,109,110,111,54,113,114,115,116,117,56,119,120,121,122,123,56,125,126,127,128,129,60,131,60,133,134,135,62,137,138,139,140,141,142,143,62,145,82,147,148,149,150,151,82,153,154,155,90,157,90,159,160,161,132,163,164,165,166,167,168,169,170,171,132,173,174,175,136,177,178,179,180,181,136,183,184,185,186,187,146,189,190,191,146,193,194,195,172,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,172,217,218,219,176,221,222,223,176,225,226,227,228,229,216,231,216,233,234,235,220,237,238,239,220,241,232,243,232,245,236,247,236,249] },{ "mechanisms": [{ "indexes": [1,19,57], @@ -1194,7 +1194,7 @@ "indexes": [1,3,5,7,9,11,12,13,14,15,17,19,21,23,25,26,27,28,29,31,32,33,34,35,37,39,40,41,43,45,46,47,48,49,50,51,53,55,57,59,61,63,64,65,66,67,69,70,71,73,75,77,79,81,83,84,85,87,89,91,92,93,94,95,97,98,99,100,101,103,105,107,108,109,110,111,113,114,115,116,117,119,120,121,122,123,125,126,127,128,129,131,133,134,135,137,138,139,140,141,142,143,145,147,148,149,150,151,153,154,155,157,159,160,161,163,164,165,166,167,168,169,170,171,173,174,175,177,178,179,180,181,183,184,185,186,187,189,190,191,193,194,195,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,217,218,219,221,222,223,225,226,227,228,229,231,233,234,235,237,238,239,241,243,245,247,249], "name": "pas" }], - "parent_index": [0,0,1,0,3,0,5,0,7,0,9,0,11,12,13,14,15,0,17,2,19,4,21,4,23,6,25,26,27,28,29,6,31,32,33,34,35,8,37,8,39,40,41,10,43,10,45,46,47,48,49,50,51,18,53,18,55,20,57,22,59,22,61,24,63,64,65,66,67,24,69,70,71,30,73,30,75,36,77,36,79,38,81,38,83,84,85,42,87,42,89,44,91,92,93,94,95,44,97,98,99,100,101,52,103,52,105,54,107,108,109,110,111,54,113,114,115,116,117,56,119,120,121,122,123,56,125,126,127,128,129,60,131,60,133,134,135,62,137,138,139,140,141,142,143,62,145,82,147,148,149,150,151,82,153,154,155,90,157,90,159,160,161,132,163,164,165,166,167,168,169,170,171,132,173,174,175,136,177,178,179,180,181,136,183,184,185,186,187,146,189,190,191,146,193,194,195,172,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,172,217,218,219,176,221,222,223,176,225,226,227,228,229,216,231,216,233,234,235,220,237,238,239,220,241,232,243,232,245,236,247,236,249] + "parent_index": [0,0,1,0,3,0,5,0,7,0,9,0,11,12,13,14,15,0,17,0,19,4,21,4,23,6,25,26,27,28,29,6,31,32,33,34,35,8,37,8,39,40,41,10,43,10,45,46,47,48,49,50,51,18,53,18,55,0,57,22,59,22,61,24,63,64,65,66,67,24,69,70,71,30,73,30,75,36,77,36,79,38,81,38,83,84,85,42,87,42,89,44,91,92,93,94,95,44,97,98,99,100,101,52,103,52,105,54,107,108,109,110,111,54,113,114,115,116,117,56,119,120,121,122,123,56,125,126,127,128,129,60,131,60,133,134,135,62,137,138,139,140,141,142,143,62,145,82,147,148,149,150,151,82,153,154,155,90,157,90,159,160,161,132,163,164,165,166,167,168,169,170,171,132,173,174,175,136,177,178,179,180,181,136,183,184,185,186,187,146,189,190,191,146,193,194,195,172,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,172,217,218,219,176,221,222,223,176,225,226,227,228,229,216,231,216,233,234,235,220,237,238,239,220,241,232,243,232,245,236,247,236,249] },{ "mechanisms": [{ "indexes": [1,19,57], @@ -1257,5 +1257,5 @@ "indexes": [1,3,5,7,9,11,12,13,14,15,17,19,21,23,25,26,27,28,29,31,32,33,34,35,37,39,40,41,43,45,46,47,48,49,50,51,53,55,57,59,61,63,64,65,66,67,69,70,71,73,75,77,79,81,83,84,85,87,89,91,92,93,94,95,97,98,99,100,101,103,105,107,108,109,110,111,113,114,115,116,117,119,120,121,122,123,125,126,127,128,129,131,133,134,135,137,138,139,140,141,142,143,145,147,148,149,150,151,153,154,155,157,159,160,161,163,164,165,166,167,168,169,170,171,173,174,175,177,178,179,180,181,183,184,185,186,187,189,190,191,193,194,195,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,217,218,219,221,222,223,225,226,227,228,229,231,233,234,235,237,238,239,241,243,245,247,249], "name": "pas" }], - "parent_index": [0,0,1,0,3,0,5,0,7,0,9,0,11,12,13,14,15,0,17,2,19,4,21,4,23,6,25,26,27,28,29,6,31,32,33,34,35,8,37,8,39,40,41,10,43,10,45,46,47,48,49,50,51,18,53,18,55,20,57,22,59,22,61,24,63,64,65,66,67,24,69,70,71,30,73,30,75,36,77,36,79,38,81,38,83,84,85,42,87,42,89,44,91,92,93,94,95,44,97,98,99,100,101,52,103,52,105,54,107,108,109,110,111,54,113,114,115,116,117,56,119,120,121,122,123,56,125,126,127,128,129,60,131,60,133,134,135,62,137,138,139,140,141,142,143,62,145,82,147,148,149,150,151,82,153,154,155,90,157,90,159,160,161,132,163,164,165,166,167,168,169,170,171,132,173,174,175,136,177,178,179,180,181,136,183,184,185,186,187,146,189,190,191,146,193,194,195,172,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,172,217,218,219,176,221,222,223,176,225,226,227,228,229,216,231,216,233,234,235,220,237,238,239,220,241,232,243,232,245,236,247,236,249] + "parent_index": [0,0,1,0,3,0,5,0,7,0,9,0,11,12,13,14,15,0,17,0,19,4,21,4,23,6,25,26,27,28,29,6,31,32,33,34,35,8,37,8,39,40,41,10,43,10,45,46,47,48,49,50,51,18,53,18,55,0,57,22,59,22,61,24,63,64,65,66,67,24,69,70,71,30,73,30,75,36,77,36,79,38,81,38,83,84,85,42,87,42,89,44,91,92,93,94,95,44,97,98,99,100,101,52,103,52,105,54,107,108,109,110,111,54,113,114,115,116,117,56,119,120,121,122,123,56,125,126,127,128,129,60,131,60,133,134,135,62,137,138,139,140,141,142,143,62,145,82,147,148,149,150,151,82,153,154,155,90,157,90,159,160,161,132,163,164,165,166,167,168,169,170,171,132,173,174,175,136,177,178,179,180,181,136,183,184,185,186,187,146,189,190,191,146,193,194,195,172,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,172,217,218,219,176,221,222,223,176,225,226,227,228,229,216,231,216,233,234,235,220,237,238,239,220,241,232,243,232,245,236,247,236,249] }] diff --git a/external/modparser b/external/modparser index 588ca1a5ea28ef04d17b318e754d479e5489eb9a..b200bf6376a2dc30edea98fcc2375fc9be095135 160000 --- a/external/modparser +++ b/external/modparser @@ -1 +1 @@ -Subproject commit 588ca1a5ea28ef04d17b318e754d479e5489eb9a +Subproject commit b200bf6376a2dc30edea98fcc2375fc9be095135 diff --git a/external/vector b/external/vector index 8284611f05b0fbe21a1f84630e2726015cb1d96d..c8678e80660cd63b293ade80ece8eed2249c1b06 160000 --- a/external/vector +++ b/external/vector @@ -1 +1 @@ -Subproject commit 8284611f05b0fbe21a1f84630e2726015cb1d96d +Subproject commit c8678e80660cd63b293ade80ece8eed2249c1b06 diff --git a/mechanisms/CMakeLists.txt b/mechanisms/CMakeLists.txt index cf183d75545c011dbb5fbb3436221ab7ab4d0933..80925e24320fe3a01d0c2eee24e5494e58ecd14a 100644 --- a/mechanisms/CMakeLists.txt +++ b/mechanisms/CMakeLists.txt @@ -1,14 +1,31 @@ +# the list of built-in mechanisms to be provided by default set(mechanisms pas hh expsyn exp2syn) -set(modcc "${CMAKE_BINARY_DIR}/external/bin/modcc") +# set the flags for the modcc compiler that converts NMODL +# files to C++/CUDA source. +set(modcc_flags "-t cpu") +if(USE_OPTIMIZED_KERNELS) # generate optimized kernels + set(modcc_flags ${modcc_flags} -O) +endif() +# generate source for each mechanism foreach(mech ${mechanisms}) set(mod "${CMAKE_CURRENT_SOURCE_DIR}/mod/${mech}.mod") set(hpp "${CMAKE_CURRENT_SOURCE_DIR}/${mech}.hpp") - add_custom_command(OUTPUT "${hpp}" - DEPENDS modparser "${mod}" - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - COMMAND "${modcc}" -t cpu -o "${hpp}" "${mod}") + if(use_external_modcc) + add_custom_command( + OUTPUT "${hpp}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMAND ${modcc} ${modcc_flags} ${mod} -o ${hpp} + ) + else() + add_custom_command( + OUTPUT "${hpp}" + DEPENDS modparser "${mod}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMAND ${modcc} ${modcc_flags} ${mod} -o ${hpp} + ) + endif() set_source_files_properties("${hpp}" PROPERTIES GENERATED TRUE) list(APPEND all_mod_hpps "${hpp}") endforeach() diff --git a/mechanisms/generate.sh b/mechanisms/generate.sh index 1fcd49beb79603e6d79cf35e1e4b9f699fcb2f49..89dfd5e277f9811bd91cdc363a61f41a1e81b706 100755 --- a/mechanisms/generate.sh +++ b/mechanisms/generate.sh @@ -1,3 +1,5 @@ +#!/usr/bin/env bash + #flags="-t cpu -O" flags="-t cpu" diff --git a/miniapp/CMakeLists.txt b/miniapp/CMakeLists.txt index a5b9561198875a6515b4e8a69f7685ea75c19803..682adefe1d04b057cb330dc9468485b276b797b2 100644 --- a/miniapp/CMakeLists.txt +++ b/miniapp/CMakeLists.txt @@ -1,9 +1,9 @@ set(HEADERS ) set(MINIAPP_SOURCES - # mpi.cpp io.cpp miniapp.cpp + miniapp_recipes.cpp ) add_executable(miniapp.exe ${MINIAPP_SOURCES} ${HEADERS}) diff --git a/miniapp/io.cpp b/miniapp/io.cpp index 52594b6bcf872bc3eb22c0bc7376f2d98b35c060..344fd56135b232bef3563d6a3cc61b570868a360 100644 --- a/miniapp/io.cpp +++ b/miniapp/io.cpp @@ -1,125 +1,310 @@ -#include <fstream> +#include <algorithm> #include <exception> +#include <fstream> +#include <istream> +#include <type_traits> #include <tclap/CmdLine.h> +#include <json/src/json.hpp> + +#include <util/meta.hpp> +#include <util/optional.hpp> #include "io.hpp" +// Let TCLAP understand value arguments that are of an optional type. + +namespace TCLAP { + template <typename V> + struct ArgTraits<nest::mc::util::optional<V>> { + using ValueCategory = ValueLike; + }; +} // namespace TCLAP + namespace nest { namespace mc { + +namespace util { + // Using static here because we do not want external linkage for this operator. + template <typename V> + static std::istream& operator>>(std::istream& I, optional<V>& v) { + V u; + if (I >> u) { + v = u; + } + return I; + } +} + namespace io { -/// read simulation options from json file with name fname -/// if file name is empty or if file is not a valid json file a default -/// set of parameters is returned : -/// 1000 cells, 500 synapses per cell, 100 compartments per segment +// Override annoying parameters listed back-to-front behaviour. +// +// TCLAP argument creation _prepends_ its arguments to the internal +// list (_argList), where standard options --help etc. are already +// pre-inserted. +// +// reorder_arguments() reverses the arguments to restore ordering, +// and moves the standard options to the end. +class CustomCmdLine: public TCLAP::CmdLine { +public: + CustomCmdLine(const std::string &message, const std::string &version = "none"): + TCLAP::CmdLine(message, ' ', version, true) + {} + + void reorder_arguments() { + _argList.reverse(); + for (auto opt: {"help", "version", "ignore_rest"}) { + auto i = std::find_if( + _argList.begin(), _argList.end(), + [&opt](TCLAP::Arg* a) { return a->getName()==opt; }); + + if (i!=_argList.end()) { + auto a = *i; + _argList.erase(i); + _argList.push_back(a); + } + } + } +}; + +// Update an option value from command line argument if set. +template < + typename T, + typename Arg, + typename = util::enable_if_t<std::is_base_of<TCLAP::Arg, Arg>::value> +> +static void update_option(T& opt, Arg& arg) { + if (arg.isSet()) { + opt = arg.getValue(); + } +} + +// Update an option value from json object if key present. +template <typename T> +static void update_option(T& opt, const nlohmann::json& j, const std::string& key) { + if (j.count(key)) { + opt = j[key]; + } +} + +// --- special case for string due to ambiguous overloading in json library. +static void update_option(std::string& opt, const nlohmann::json& j, const std::string& key) { + if (j.count(key)) { + opt = j[key].get<std::string>(); + } +} + +// --- special case for optional values. +template <typename T> +static void update_option(util::optional<T>& opt, const nlohmann::json& j, const std::string& key) { + if (j.count(key)) { + auto value = j[key]; + if (value.is_null()) { + opt = util::nothing; + } + else { + opt = value; + } + } +} + +// Read options from (optional) json file and command line arguments. cl_options read_options(int argc, char** argv) { - // set default options - const cl_options default_options{"", 1000, 500, "expsyn", 100, 100., 0.025, false}; + // Default options: + const cl_options defopts{ + 1000, // number of cells + 500, // synapses_per_cell + "expsyn", // synapse type + 100, // compartments_per_segment + 100., // tfinal + 0.025, // dt + false, // all_to_all + false, // probe_soma_only + 0.0, // probe_ratio + "trace_", // trace_prefix + util::nothing, // trace_max_gid + + // spike_output_parameters: + false, // spike output + false, // single_file_per_simulation + true, // Overwrite outputfile if exists + "./", // output path + "spikes", // file name + "gdf" // file extension + }; cl_options options; - // parse command line arguments + std::string save_file = ""; + + // Parse command line arguments. try { - TCLAP::CmdLine cmd("mod2c performance benchmark harness", ' ', "0.1"); + CustomCmdLine cmd("nest mc miniapp harness", "0.1"); + TCLAP::ValueArg<std::string> ifile_arg( + "i", "ifile", + "read parameters from json-formatted file <file name>", + false, "","file name", cmd); + TCLAP::ValueArg<std::string> ofile_arg( + "o", "ofile", + "save parameters to json-formatted file <file name>", + false, "","file name", cmd); TCLAP::ValueArg<uint32_t> ncells_arg( "n", "ncells", "total number of cells in the model", - false, 1000, "non negative integer"); + false, defopts.cells, "integer", cmd); TCLAP::ValueArg<uint32_t> nsynapses_arg( "s", "nsynapses", "number of synapses per cell", - false, 500, "non negative integer"); + false, defopts.synapses_per_cell, "integer", cmd); TCLAP::ValueArg<std::string> syntype_arg( - "S", "syntype", "type of synapse (expsyn or exp2syn)", - false, "expsyn", "synapse type"); + "S", "syntype", "specify synapse type: expsyn or exp2syn", + false, defopts.syn_type, "string", cmd); TCLAP::ValueArg<uint32_t> ncompartments_arg( "c", "ncompartments", "number of compartments per segment", - false, 100, "non negative integer"); - TCLAP::ValueArg<std::string> ifile_arg( - "i", "ifile", "json file with model parameters", - false, "","file name string"); + false, defopts.compartments_per_segment, "integer", cmd); TCLAP::ValueArg<double> tfinal_arg( - "t", "tfinal", "time to simulate in ms", - false, 100., "positive real number"); + "t", "tfinal", "run simulation to <time> ms", + false, defopts.tfinal, "time", cmd); TCLAP::ValueArg<double> dt_arg( - "d", "dt", "time step size in ms", - false, 0.025, "positive real number"); + "d", "dt", "set simulation time step to <time> ms", + false, defopts.dt, "time", cmd); TCLAP::SwitchArg all_to_all_arg( - "a","alltoall","all to all network", cmd, false); - - cmd.add(ncells_arg); - cmd.add(nsynapses_arg); - cmd.add(syntype_arg); - cmd.add(ncompartments_arg); - cmd.add(ifile_arg); - cmd.add(dt_arg); - cmd.add(tfinal_arg); + "m","alltoall","all to all network", cmd, false); + TCLAP::ValueArg<double> probe_ratio_arg( + "p", "probe-ratio", "proportion between 0 and 1 of cells to probe", + false, defopts.probe_ratio, "proportion", cmd); + TCLAP::SwitchArg probe_soma_only_arg( + "X", "probe-soma-only", "only probe cell somas, not dendrites", cmd, false); + TCLAP::ValueArg<std::string> trace_prefix_arg( + "P", "prefix", "write traces to files with prefix <prefix>", + false, defopts.trace_prefix, "stringr", cmd); + TCLAP::ValueArg<util::optional<unsigned>> trace_max_gid_arg( + "T", "trace-max-gid", "only trace probes on cells up to and including <gid>", + false, defopts.trace_max_gid, "gid", cmd); + TCLAP::SwitchArg spike_output_arg( + "f","spike_file_output","save spikes to file", cmd, false); + cmd.reorder_arguments(); cmd.parse(argc, argv); - options.cells = ncells_arg.getValue(); - options.synapses_per_cell = nsynapses_arg.getValue(); - options.syn_type = syntype_arg.getValue(); - options.compartments_per_segment = ncompartments_arg.getValue(); - options.ifname = ifile_arg.getValue(); - options.tfinal = tfinal_arg.getValue(); - options.dt = dt_arg.getValue(); - options.all_to_all = all_to_all_arg.getValue(); + options = defopts; + + std::string ifile_name = ifile_arg.getValue(); + if (ifile_name != "") { + // Read parameters from specified JSON file first, to allow + // overriding arguments on the command line. + std::ifstream fid(ifile_name); + if (fid) { + try { + nlohmann::json fopts; + fid >> fopts; + + update_option(options.cells, fopts, "cells"); + update_option(options.synapses_per_cell, fopts, "synapses"); + update_option(options.syn_type, fopts, "syn_type"); + update_option(options.compartments_per_segment, fopts, "compartments"); + update_option(options.dt, fopts, "dt"); + update_option(options.tfinal, fopts, "tfinal"); + update_option(options.all_to_all, fopts, "all_to_all"); + update_option(options.probe_ratio, fopts, "probe_ratio"); + update_option(options.probe_soma_only, fopts, "probe_soma_only"); + update_option(options.trace_prefix, fopts, "trace_prefix"); + update_option(options.trace_max_gid, fopts, "trace_max_gid"); + + // Parameters for spike output + update_option(options.spike_file_output, fopts, "spike_file_output"); + if (options.spike_file_output) { + update_option(options.single_file_per_rank, fopts, "single_file_per_rank"); + update_option(options.over_write, fopts, "over_write"); + update_option(options.output_path, fopts, "output_path"); + update_option(options.file_name, fopts, "file_name"); + update_option(options.file_extension, fopts, "file_extension"); + } + + } + catch (std::exception& e) { + throw model_description_error( + "unable to parse parameters in "+ifile_name+": "+e.what()); + } + } + else { + throw usage_error("unable to open model parameter file "+ifile_name); + } + } + + update_option(options.cells, ncells_arg); + update_option(options.synapses_per_cell, nsynapses_arg); + update_option(options.syn_type, syntype_arg); + update_option(options.compartments_per_segment, ncompartments_arg); + update_option(options.tfinal, tfinal_arg); + update_option(options.dt, dt_arg); + update_option(options.all_to_all, all_to_all_arg); + update_option(options.probe_ratio, probe_ratio_arg); + update_option(options.probe_soma_only, probe_soma_only_arg); + update_option(options.trace_prefix, trace_prefix_arg); + update_option(options.trace_max_gid, trace_max_gid_arg); + update_option(options.spike_file_output, spike_output_arg); + + save_file = ofile_arg.getValue(); } - // catch any exceptions in command line handling - catch(TCLAP::ArgException &e) { - std::cerr << "error: parsing command line arguments:\n " - << e.error() << " for arg " << e.argId() << "\n"; - exit(1); + catch (TCLAP::ArgException& e) { + throw usage_error("error parsing command line argument "+e.argId()+": "+e.error()); } - if(options.ifname == "") { - options.check_and_normalize(); - return options; - } - else { - std::ifstream fid(options.ifname, std::ifstream::in); + // Save option values if requested. + if (save_file != "") { + std::ofstream fid(save_file); + if (fid) { + try { + nlohmann::json fopts; - if(fid.is_open()) { - // read json data in input file - nlohmann::json fopts; - fid >> fopts; + fopts["cells"] = options.cells; + fopts["synapses"] = options.synapses_per_cell; + fopts["syn_type"] = options.syn_type; + fopts["compartments"] = options.compartments_per_segment; + fopts["dt"] = options.dt; + fopts["tfinal"] = options.tfinal; + fopts["all_to_all"] = options.all_to_all; + fopts["probe_ratio"] = options.probe_ratio; + fopts["probe_soma_only"] = options.probe_soma_only; + fopts["trace_prefix"] = options.trace_prefix; + if (options.trace_max_gid) { + fopts["trace_max_gid"] = options.trace_max_gid.get(); + } + else { + fopts["trace_max_gid"] = nullptr; + } + fid << std::setw(3) << fopts << "\n"; - try { - options.cells = fopts["cells"]; - options.synapses_per_cell = fopts["synapses"]; - options.compartments_per_segment = fopts["compartments"]; - options.dt = fopts["dt"]; - options.tfinal = fopts["tfinal"]; - options.all_to_all = fopts["all_to_all"]; - } - catch(std::domain_error e) { - std::cerr << "error: unable to open parameters in " - << options.ifname << " : " << e.what() << "\n"; - exit(1); } - catch(std::exception e) { - std::cerr << "error: unable to open parameters in " - << options.ifname << "\n"; - exit(1); + catch (std::exception& e) { + throw model_description_error( + "unable to save parameters in "+save_file+": "+e.what()); } - options.check_and_normalize(); - return options; + } + else { + throw usage_error("unable to write to model parameter file "+save_file); } } - - return default_options; + return options; } std::ostream& operator<<(std::ostream& o, const cl_options& options) { - o << "simultion options:\n"; + o << "simulation options:\n"; o << " cells : " << options.cells << "\n"; o << " compartments/segment : " << options.compartments_per_segment << "\n"; o << " synapses/cell : " << options.synapses_per_cell << "\n"; o << " simulation time : " << options.tfinal << "\n"; o << " dt : " << options.dt << "\n"; o << " all to all network : " << (options.all_to_all ? "yes" : "no") << "\n"; - o << " input file name : " << options.ifname << "\n"; + o << " probe ratio : " << options.probe_ratio << "\n"; + o << " probe soma only : " << (options.probe_soma_only ? "yes" : "no") << "\n"; + o << " trace prefix : " << options.trace_prefix << "\n"; + o << " trace max gid : "; + if (options.trace_max_gid) { + o << *options.trace_max_gid; + } + o << "\n"; return o; } diff --git a/miniapp/io.hpp b/miniapp/io.hpp index 1f530d9150c69b01cfa9f6cac8127180ff12ec70..c18e688c33ceff0c3966b5d66ae8eb1680bc48a1 100644 --- a/miniapp/io.hpp +++ b/miniapp/io.hpp @@ -1,6 +1,12 @@ #pragma once -#include <json/src/json.hpp> +#include <string> +#include <cstdint> +#include <iosfwd> +#include <stdexcept> +#include <utility> + +#include <util/optional.hpp> namespace nest { namespace mc { @@ -8,7 +14,6 @@ namespace io { // holds the options for a simulation run struct cl_options { - std::string ifname; uint32_t cells; uint32_t synapses_per_cell; std::string syn_type; @@ -16,20 +21,37 @@ struct cl_options { double tfinal; double dt; bool all_to_all; + bool probe_soma_only; + double probe_ratio; + std::string trace_prefix; + util::optional<unsigned> trace_max_gid; + + // Parameters for spike output + bool spike_file_output; + bool single_file_per_rank; + bool over_write; + std::string output_path; + std::string file_name; + std::string file_extension; +}; - // TODO the normalize bit should be moved to the model_parameters when - // we start having more models - void check_and_normalize() { - if(all_to_all) { - synapses_per_cell = cells - 1; - } - } +class usage_error: public std::runtime_error { +public: + template <typename S> + usage_error(S&& whatmsg): std::runtime_error(std::forward<S>(whatmsg)) {} +}; + +class model_description_error: public std::runtime_error { +public: + template <typename S> + model_description_error(S&& whatmsg): std::runtime_error(std::forward<S>(whatmsg)) {} }; std::ostream& operator<<(std::ostream& o, const cl_options& opt); cl_options read_options(int argc, char** argv); + } // namespace io } // namespace mc } // namespace nest diff --git a/miniapp/miniapp.cpp b/miniapp/miniapp.cpp index fec0e995d56903f50a6b743b63df9eb01e428011..bc4133cb15ceecc5c96b8b7649f286980acb232f 100644 --- a/miniapp/miniapp.cpp +++ b/miniapp/miniapp.cpp @@ -1,449 +1,230 @@ +#include <cmath> +#include <exception> #include <iostream> #include <fstream> -#include <sstream> +#include <memory> +#include <vector> +#include <json/src/json.hpp> + +#include <common_types.hpp> #include <cell.hpp> #include <cell_group.hpp> +#include <communication/communicator.hpp> +#include <communication/global_policy.hpp> #include <fvm_cell.hpp> -#include <mechanism_interface.hpp> +#include <io/exporter_spike_file.hpp> +#include <mechanism_catalogue.hpp> +#include <model.hpp> +#include <profiling/profiler.hpp> +#include <threading/threading.hpp> +#include <util/ioutil.hpp> +#include <util/nop.hpp> +#include <util/optional.hpp> #include "io.hpp" -#include "threading/threading.hpp" -#include "profiling/profiler.hpp" -#include "communication/communicator.hpp" -#include "communication/global_policy.hpp" -#include "util/optional.hpp" +#include "miniapp_recipes.hpp" +#include "trace_sampler.hpp" + +using namespace nest::mc; + +using global_policy = communication::global_policy; +using lowered_cell = fvm::fvm_cell<double, cell_local_size_type>; +using model_type = model<lowered_cell>; +using time_type = model_type::time_type; +using sample_trace_type = sample_trace<time_type, model_type::value_type>; +using file_export_type = io::exporter_spike_file<time_type, global_policy>; +void banner(); +std::unique_ptr<recipe> make_recipe(const io::cl_options&, const probe_distribution&); +std::unique_ptr<sample_trace_type> make_trace(cell_member_type probe_id, probe_spec probe); +std::pair<cell_gid_type, cell_gid_type> distribute_cells(cell_size_type ncells); +using communicator_type = communication::communicator<time_type, communication::global_policy>; +using spike_type = typename communicator_type::spike_type; + +void write_trace_json(const sample_trace_type& trace, const std::string& prefix = "trace_"); -using namespace nest; +int main(int argc, char** argv) { + nest::mc::communication::global_policy_guard global_guard(argc, argv); -using real_type = double; -using index_type = int; -using id_type = uint32_t; -using numeric_cell = mc::fvm::fvm_cell<real_type, index_type>; -using cell_group = mc::cell_group<numeric_cell>; + try { + std::cout << util::mask_stream(global_policy::id()==0); + banner(); -using global_policy = nest::mc::communication::global_policy; -using communicator_type = - mc::communication::communicator<global_policy>; + // read parameters + io::cl_options options = io::read_options(argc, argv); + std::cout << options << "\n"; + std::cout << "\n"; + std::cout << ":: simulation to " << options.tfinal << " ms in " + << std::ceil(options.tfinal / options.dt) << " steps of " + << options.dt << " ms" << std::endl; -using nest::mc::util::optional; + // determine what to attach probes to + probe_distribution pdist; + pdist.proportion = options.probe_ratio; + pdist.all_segments = !options.probe_soma_only; -struct model { - communicator_type communicator; - std::vector<cell_group> cell_groups; + auto recipe = make_recipe(options, pdist); + auto cell_range = distribute_cells(recipe->num_cells()); - unsigned num_groups() const { - return cell_groups.size(); - } - void run(double tfinal, double dt) { - auto t = 0.; - auto delta = std::min(double(communicator.min_delay()), tfinal); - while (t<tfinal) { - mc::threading::parallel_for::apply( - 0, num_groups(), - [&](int i) { - mc::util::profiler_enter("stepping","events"); - cell_groups[i].enqueue_events(communicator.queue(i)); - mc::util::profiler_leave(); - - cell_groups[i].advance(std::min(t+delta, tfinal), dt); - - mc::util::profiler_enter("events"); - communicator.add_spikes(cell_groups[i].spikes()); - cell_groups[i].clear_spikes(); - mc::util::profiler_leave(2); - } - ); - - mc::util::profiler_enter("stepping", "exchange"); - communicator.exchange(); - mc::util::profiler_leave(2); - - t += delta; - } - } + model_type m(*recipe, cell_range.first, cell_range.second); - void init_communicator() { - mc::util::profiler_enter("setup", "communicator"); + auto register_exporter = [] (const io::cl_options& options) { + return + util::make_unique<file_export_type>( + options.file_name, options.output_path, + options.file_extension, options.over_write); + }; - // calculate the source and synapse distribution serially - std::vector<id_type> target_counts(num_groups()); - std::vector<id_type> source_counts(num_groups()); - for (auto i=0u; i<num_groups(); ++i) { - target_counts[i] = cell_groups[i].cell().synapses()->size(); - source_counts[i] = cell_groups[i].spike_sources().size(); + // File output is depending on the input arguments + std::unique_ptr<file_export_type> file_exporter; + if (options.spike_file_output) { + if (options.single_file_per_rank) { + file_exporter = register_exporter(options); + m.set_local_spike_callback( + [&](const std::vector<spike_type>& spikes) { + file_exporter->output(spikes); + }); + } + else if(communication::global_policy::id()==0) { + file_exporter = register_exporter(options); + m.set_global_spike_callback( + [&](const std::vector<spike_type>& spikes) { + file_exporter->output(spikes); + }); + } } - target_map = mc::algorithms::make_index(target_counts); - source_map = mc::algorithms::make_index(source_counts); - - // create connections - communicator = communicator_type(num_groups(), target_counts); + // inject some artificial spikes, 1 per 20 neurons. + std::vector<cell_gid_type> local_sources; + cell_gid_type first_spike_cell = 20*((cell_range.first+19)/20); + for (auto c=first_spike_cell; c<cell_range.second; c+=20) { + local_sources.push_back(c); + m.add_artificial_spike({c, 0}); + } - mc::util::profiler_leave(2); - } + // attach samplers to all probes + std::vector<std::unique_ptr<sample_trace_type>> traces; + const model_type::time_type sample_dt = 0.1; + for (auto probe: m.probes()) { + if (options.trace_max_gid && probe.id.gid>*options.trace_max_gid) { + continue; + } - void update_gids() { - mc::util::profiler_enter("setup", "globalize"); - auto com_policy = communicator.communication_policy(); - auto global_source_map = com_policy.make_map(source_map.back()); - auto domain_idx = communicator.domain_id(); - for (auto i=0u; i<num_groups(); ++i) { - cell_groups[i].set_source_gids( - source_map[i]+global_source_map[domain_idx] - ); - cell_groups[i].set_target_gids( - target_map[i]+communicator.target_gid_from_group_lid(0) - ); + traces.push_back(make_trace(probe.id, probe.probe)); + m.attach_sampler(probe.id, make_trace_sampler(traces.back().get(), sample_dt)); } - mc::util::profiler_leave(2); - } - // TODO : only stored here because init_communicator() and update_gids() are split - std::vector<id_type> source_map; - std::vector<id_type> target_map; + // dummy run of the model for one step to ensure that profiling is consistent + m.run(options.dt, options.dt); - // traces from probes - struct trace_data { - struct sample_type { - float time; - double value; - }; - std::string name; - std::string units; - index_type id; - std::vector<sample_type> samples; - }; - - // different traces may be written to by different threads; - // during simulation, each trace_sampler will be responsible for its - // corresponding element in the traces vector. - - std::vector<trace_data> traces; - - // make a sampler that records to traces - struct simple_sampler_functor { - std::vector<trace_data> &traces_; - size_t trace_index_ = 0; - float requested_sample_time_ = 0; - float dt_ = 0; - - simple_sampler_functor(std::vector<trace_data> &traces, size_t index, float dt) : - traces_(traces), trace_index_(index), dt_(dt) - {} - - optional<float> operator()(float t, double v) { - traces_[trace_index_].samples.push_back({t,v}); - return requested_sample_time_ += dt_; + // reset the model + m.reset(); + // rest the source spikes + for (auto source : local_sources) { + m.add_artificial_spike({source, 0}); } - }; - mc::sampler make_simple_sampler( - index_type probe_gid, const std::string& name, const std::string& units, index_type id, float dt) - { - traces.push_back(trace_data{name, units, id}); - return {probe_gid, simple_sampler_functor(traces, traces.size()-1, dt)}; - } + // run model + m.run(options.tfinal, options.dt); - void reset_traces() { - // do not call during simulation: thread-unsafe access to traces. - traces.clear(); - } + // output profile and diagnostic feedback + auto const num_steps = options.tfinal / options.dt; + util::profiler_output(0.001, m.num_cells()*num_steps); + std::cout << "there were " << m.num_spikes() << " spikes\n"; - void dump_traces() { - // do not call during simulation: thread-unsafe access to traces. + // save traces for (const auto& trace: traces) { - auto path = "trace_" + std::to_string(trace.id) - + "_" + trace.name + ".json"; - - nlohmann::json jrep; - jrep["name"] = trace.name; - jrep["units"] = trace.units; - jrep["id"] = trace.id; - - auto& jt = jrep["data"]["time"]; - auto& jy = jrep["data"][trace.name]; - - for (const auto& sample: trace.samples) { - jt.push_back(sample.time); - jy.push_back(sample.value); - } - std::ofstream file(path); - file << std::setw(1) << jrep << std::endl; + write_trace_json(*trace.get(), options.trace_prefix); } } -}; - -// define some global model parameters -namespace parameters { -namespace synapses { - // synapse delay - constexpr float delay = 20.0; // ms - - // connection weight - constexpr double weight_per_cell = 0.3; // uS -} -} - -/////////////////////////////////////// -// prototypes -/////////////////////////////////////// - -/// make a single abstract cell -mc::cell make_cell(int compartments_per_segment, int num_synapses, const std::string& syn_type); - -/// do basic setup (initialize global state, print banner, etc) -void setup(); - -/// helper function for initializing cells -cell_group make_lowered_cell(int cell_index, const mc::cell& c); - -/// models -void all_to_all_model(nest::mc::io::cl_options& opt, model& m); - -/////////////////////////////////////// -// main -/////////////////////////////////////// -int main(int argc, char** argv) { - nest::mc::communication::global_policy_guard global_guard(argc, argv); - - setup(); - - // read parameters - mc::io::cl_options options; - try { - options = mc::io::read_options(argc, argv); - if (global_policy::id()==0) { - std::cout << options << "\n"; - } + catch (io::usage_error& e) { + // only print usage/startup errors on master + std::cerr << util::mask_stream(global_policy::id()==0); + std::cerr << e.what() << "\n"; + return 1; } catch (std::exception& e) { - std::cerr << e.what() << std::endl; - exit(1); - } - - model m; - all_to_all_model(options, m); - - // - // time stepping - // - auto tfinal = options.tfinal; - auto dt = options.dt; - - auto id = m.communicator.domain_id(); - - if (id==0) { - // use std::endl to force flush of output on cluster jobs - std::cout << "\n"; - std::cout << ":: simulation to " << tfinal << " ms in " - << std::ceil(tfinal / dt) << " steps of " - << dt << " ms" << std::endl; - } - - // add some spikes to the system to start it - auto first = m.communicator.group_gid_first(id); - if(first%20) { - first += 20 - (first%20); // round up to multiple of 20 - } - auto last = m.communicator.group_gid_first(id+1); - for (auto i=first; i<last; i+=20) { - m.communicator.add_spike({i, 0}); + std::cerr << e.what() << "\n"; + return 2; } + return 0; +} - m.run(tfinal, dt); +std::pair<cell_gid_type, cell_gid_type> distribute_cells(cell_size_type num_cells) { + // Crude load balancing: + // divide [0, num_cells) into num_domains non-overlapping, contiguous blocks + // of size as close to equal as possible. - mc::util::profiler_output(0.001); + auto num_domains = communication::global_policy::size(); + auto domain_id = communication::global_policy::id(); - if (id==0) { - std::cout << "there were " << m.communicator.num_spikes() << " spikes\n"; - } + cell_gid_type cell_from = (cell_gid_type)(num_cells*(domain_id/(double)num_domains)); + cell_gid_type cell_to = (cell_gid_type)(num_cells*((domain_id+1)/(double)num_domains)); - m.dump_traces(); + return {cell_from, cell_to}; } -/////////////////////////////////////// -// models -/////////////////////////////////////// - -void all_to_all_model(nest::mc::io::cl_options& options, model& m) { - // - // make cells - // - - auto synapses_per_cell = options.synapses_per_cell; - auto is_all_to_all = options.all_to_all; +void banner() { + std::cout << "====================\n"; + std::cout << " starting miniapp\n"; + std::cout << " - " << threading::description() << " threading support\n"; + std::cout << " - communication policy: " << global_policy::name() << "\n"; + std::cout << "====================\n"; +} - // make a basic cell - auto basic_cell = - make_cell(options.compartments_per_segment, synapses_per_cell, options.syn_type); +std::unique_ptr<recipe> make_recipe(const io::cl_options& options, const probe_distribution& pdist) { + basic_recipe_param p; - auto num_domains = global_policy::size(); - auto domain_id = global_policy::id(); + p.num_compartments = options.compartments_per_segment; + p.num_synapses = options.all_to_all? options.cells-1: options.synapses_per_cell; + p.synapse_type = options.syn_type; - // make a vector for storing all of the cells - id_type ncell_global = options.cells; - id_type ncell_local = ncell_global / num_domains; - int remainder = ncell_global - (ncell_local*num_domains); - if (domain_id<remainder) { - ncell_local++; + if (options.all_to_all) { + return make_basic_kgraph_recipe(options.cells, p, pdist); } - - m.cell_groups = std::vector<cell_group>(ncell_local); - - // initialize the cells in parallel - mc::threading::parallel_for::apply( - 0, ncell_local, - [&](int i) { - mc::util::profiler_enter("setup", "cells"); - m.cell_groups[i] = make_lowered_cell(i, basic_cell); - mc::util::profiler_leave(2); - } - ); - - // - // network creation - // - m.init_communicator(); - - mc::util::profiler_enter("setup", "connections"); - - // RNG distributions for connection delays and source cell ids - auto weight_distribution = std::exponential_distribution<float>(0.75); - auto source_distribution = - std::uniform_int_distribution<uint32_t>(0u, options.cells-1); - - // calculate the weight of synaptic connections, which is chosen so that - // the sum of all synaptic weights on a cell is - // parameters::synapses::weight_per_cell - float weight = parameters::synapses::weight_per_cell / synapses_per_cell; - - // loop over each local cell and build the list of synapse connections - // that terminate on the cell - for (auto lid=0u; lid<ncell_local; ++lid) { - auto target = m.communicator.target_gid_from_group_lid(lid); - auto gid = m.communicator.group_gid_from_group_lid(lid); - auto gen = std::mt19937(gid); // seed with cell gid for reproducability - // add synapses to cell - auto i = 0u; - auto cells_added = 0u; - while (cells_added < synapses_per_cell) { - auto source = is_all_to_all ? i : source_distribution(gen); - if (gid!=source) { - m.communicator.add_connection({ - source, target++, weight, - parameters::synapses::delay + weight_distribution(gen) - }); - cells_added++; - } - ++i; - } + else { + return make_basic_rgraph_recipe(options.cells, p, pdist); } - - m.communicator.construct(); - - //for (auto con : m.communicator.connections()) std::cout << con << "\n"; - - m.update_gids(); - - // - // setup probes - // - - mc::util::profiler_leave(); - mc::util::profiler_enter("probes"); - - // monitor soma and dendrite on a few cells - float sample_dt = 0.1; - index_type monitor_group_gids[] = { 0, 1, 2 }; - for (auto gid : monitor_group_gids) { - if (!m.communicator.is_local_group(gid)) { - continue; - } - - auto lid = m.communicator.group_lid(gid); - auto probe_soma = m.cell_groups[lid].probe_gid_range().first; - auto probe_dend = probe_soma+1; - auto probe_dend_current = probe_soma+2; - - m.cell_groups[lid].add_sampler( - m.make_simple_sampler(probe_soma, "vsoma", "mV", gid, sample_dt) - ); - m.cell_groups[lid].add_sampler( - m.make_simple_sampler(probe_dend, "vdend", "mV", gid, sample_dt) - ); - m.cell_groups[lid].add_sampler( - m.make_simple_sampler(probe_dend_current, "idend", "mA/cm²", gid, sample_dt) - ); - } - - mc::util::profiler_leave(2); } -/////////////////////////////////////// -// function definitions -/////////////////////////////////////// - -void setup() { - // print banner - if (global_policy::id()==0) { - std::cout << "====================\n"; - std::cout << " starting miniapp\n"; - std::cout << " - " << mc::threading::description() << " threading support\n"; - std::cout << " - communication policy: " << global_policy::name() << "\n"; - std::cout << "====================\n"; +std::unique_ptr<sample_trace_type> make_trace(cell_member_type probe_id, probe_spec probe) { + std::string name = ""; + std::string units = ""; + + switch (probe.kind) { + case probeKind::membrane_voltage: + name = "v"; + units = "mV"; + break; + case probeKind::membrane_current: + name = "i"; + units = "mA/cm²"; + break; + default: ; } + name += probe.location.segment? "dend" : "soma"; - // setup global state for the mechanisms - mc::mechanisms::setup_mechanism_helpers(); + return util::make_unique<sample_trace_type>(probe_id, name, units); } -// make a high level cell description for use in simulation -mc::cell make_cell(int compartments_per_segment, int num_synapses, const std::string& syn_type) { - nest::mc::cell cell; +void write_trace_json(const sample_trace_type& trace, const std::string& prefix) { + auto path = prefix + std::to_string(trace.probe_id.gid) + + "." + std::to_string(trace.probe_id.index) + "_" + trace.name + ".json"; - // Soma with diameter 12.6157 um and HH channel - auto soma = cell.add_soma(12.6157/2.0); - soma->add_mechanism(mc::hh_parameters()); + nlohmann::json jrep; + jrep["name"] = trace.name; + jrep["units"] = trace.units; + jrep["cell"] = trace.probe_id.gid; + jrep["probe"] = trace.probe_id.index; - // add dendrite of length 200 um and diameter 1 um with passive channel - std::vector<mc::cable_segment*> dendrites; - dendrites.push_back(cell.add_cable(0, mc::segmentKind::dendrite, 0.5, 0.5, 200)); - dendrites.push_back(cell.add_cable(1, mc::segmentKind::dendrite, 0.5, 0.25,100)); - dendrites.push_back(cell.add_cable(1, mc::segmentKind::dendrite, 0.5, 0.25,100)); + auto& jt = jrep["data"]["time"]; + auto& jy = jrep["data"][trace.name]; - for (auto d : dendrites) { - d->add_mechanism(mc::pas_parameters()); - d->set_compartments(compartments_per_segment); - d->mechanism("membrane").set("r_L", 100); + for (const auto& sample: trace.samples) { + jt.push_back(sample.time); + jy.push_back(sample.value); } - - cell.add_detector({0,0}, 20); - - auto gen = std::mt19937(); - auto distribution = std::uniform_real_distribution<float>(0.f, 1.0f); - // distribute the synapses at random locations the terminal dendrites in a - // round robin manner - nest::mc::parameter_list syn_default(syn_type); - for (auto i=0; i<num_synapses; ++i) { - cell.add_synapse({2+(i%2), distribution(gen)}, syn_default); - } - - // add probes: - auto probe_soma = cell.add_probe({0, 0}, mc::probeKind::membrane_voltage); - auto probe_dendrite = cell.add_probe({1, 0.5}, mc::probeKind::membrane_voltage); - auto probe_dendrite_current = cell.add_probe({1, 0.5}, mc::probeKind::membrane_current); - - EXPECTS(probe_soma==0); - EXPECTS(probe_dendrite==1); - EXPECTS(probe_dendrite_current==2); - (void)probe_soma, (void)probe_dendrite, (void)probe_dendrite_current; - - return cell; -} - -cell_group make_lowered_cell(int cell_index, const mc::cell& c) { - return cell_group(c); + std::ofstream file(path); + file << std::setw(1) << jrep << std::endl; } - diff --git a/miniapp/miniapp_recipes.cpp b/miniapp/miniapp_recipes.cpp new file mode 100644 index 0000000000000000000000000000000000000000..742951dff6df83cb13c2f13af6e2d1ace2ae97eb --- /dev/null +++ b/miniapp/miniapp_recipes.cpp @@ -0,0 +1,236 @@ +#include <cmath> +#include <random> +#include <vector> +#include <utility> + +#include <cell.hpp> +#include <util/debug.hpp> + +#include "miniapp_recipes.hpp" + +namespace nest { +namespace mc { + +// TODO: split cell description into separate morphology, stimulus, probes, mechanisms etc. +// description for greater data reuse. + +template <typename RNG> +cell make_basic_cell( + unsigned compartments_per_segment, + unsigned num_synapses, + const std::string& syn_type, + RNG& rng) +{ + nest::mc::cell cell; + + // Soma with diameter 12.6157 um and HH channel + auto soma = cell.add_soma(12.6157/2.0); + soma->add_mechanism(mc::hh_parameters()); + + // add dendrite of length 200 um and diameter 1 um with passive channel + std::vector<mc::cable_segment*> dendrites; + dendrites.push_back(cell.add_cable(0, mc::segmentKind::dendrite, 0.5, 0.5, 200)); + dendrites.push_back(cell.add_cable(1, mc::segmentKind::dendrite, 0.5, 0.25,100)); + dendrites.push_back(cell.add_cable(1, mc::segmentKind::dendrite, 0.5, 0.25,100)); + + for (auto d : dendrites) { + d->add_mechanism(mc::pas_parameters()); + d->set_compartments(compartments_per_segment); + d->mechanism("membrane").set("r_L", 100); + } + + cell.add_detector({0,0}, 20); + + auto distribution = std::uniform_real_distribution<float>(0.f, 1.0f); + // distribute the synapses at random locations the terminal dendrites in a + // round robin manner; the terminal dendrites in this cell have indices 2 and 3. + nest::mc::parameter_list syn_default(syn_type); + for (unsigned i=0; i<num_synapses; ++i) { + cell.add_synapse({2+(i%2), distribution(rng)}, syn_default); + } + + return cell; +} + +class basic_cell_recipe: public recipe { +public: + basic_cell_recipe(cell_gid_type ncell, basic_recipe_param param, probe_distribution pdist): + ncell_(ncell), param_(std::move(param)), pdist_(std::move(pdist)) + { + delay_distribution_param = exp_param{param_.mean_connection_delay_ms + - param_.min_connection_delay_ms}; + } + + cell_size_type num_cells() const override { return ncell_; } + + cell get_cell(cell_gid_type i) const override { + auto gen = std::mt19937(i); // TODO: replace this with hashing generator... + + auto cc = get_cell_count_info(i); + auto cell = make_basic_cell(param_.num_compartments, cc.num_targets, + param_.synapse_type, gen); + + EXPECTS(cell.num_segments()==basic_cell_segments); + EXPECTS(cell.probes().size()==0); + EXPECTS(cell.synapses().size()==cc.num_targets); + EXPECTS(cell.detectors().size()==cc.num_sources); + + // add probes + if (cc.num_probes) { + unsigned n_probe_segs = pdist_.all_segments? basic_cell_segments: 1u; + for (unsigned i = 0; i<n_probe_segs; ++i) { + if (pdist_.membrane_voltage) { + cell.add_probe({{i, i? 0.5: 0.0}, mc::probeKind::membrane_voltage}); + } + if (pdist_.membrane_current) { + cell.add_probe({{i, i? 0.5: 0.0}, mc::probeKind::membrane_current}); + } + } + } + EXPECTS(cell.probes().size()==cc.num_probes); + return cell; + } + + cell_count_info get_cell_count_info(cell_gid_type i) const override { + cell_count_info cc = {1, param_.num_synapses, 0 }; + + // probe this cell? + if (std::floor(i*pdist_.proportion)!=std::floor((i-1.0)*pdist_.proportion)) { + std::size_t np = pdist_.membrane_voltage + pdist_.membrane_current; + if (pdist_.all_segments) { + np *= basic_cell_segments; + } + + cc.num_probes = np; + } + + return cc; + } + +protected: + template <typename RNG> + cell_connection draw_connection_params(RNG& rng) const { + std::exponential_distribution<float> delay_dist(delay_distribution_param); + float delay = param_.min_connection_delay_ms + delay_dist(rng); + float weight = param_.syn_weight_per_cell/param_.num_synapses; + return cell_connection{{0, 0}, {0, 0}, weight, delay}; + } + + cell_gid_type ncell_; + basic_recipe_param param_; + probe_distribution pdist_; + static constexpr int basic_cell_segments = 4; + + using exp_param = std::exponential_distribution<float>::param_type; + exp_param delay_distribution_param; +}; + +class basic_ring_recipe: public basic_cell_recipe { +public: + basic_ring_recipe(cell_gid_type ncell, + basic_recipe_param param, + probe_distribution pdist = probe_distribution{}): + basic_cell_recipe(ncell, std::move(param), std::move(pdist)) {} + + std::vector<cell_connection> connections_on(cell_gid_type i) const override { + std::vector<cell_connection> conns; + auto gen = std::mt19937(i); // TODO: replace this with hashing generator... + + cell_gid_type prev = i==0? ncell_-1: i-1; + for (unsigned t=0; t<param_.num_synapses; ++t) { + cell_connection cc = draw_connection_params(gen); + cc.source = {prev, 0}; + cc.dest = {i, t}; + conns.push_back(cc); + } + + return conns; + } +}; + +std::unique_ptr<recipe> make_basic_ring_recipe( + cell_gid_type ncell, + basic_recipe_param param, + probe_distribution pdist) +{ + return std::unique_ptr<recipe>(new basic_ring_recipe(ncell, param, pdist)); +} + + +class basic_rgraph_recipe: public basic_cell_recipe { +public: + basic_rgraph_recipe(cell_gid_type ncell, + basic_recipe_param param, + probe_distribution pdist = probe_distribution{}): + basic_cell_recipe(ncell, std::move(param), std::move(pdist)) {} + + std::vector<cell_connection> connections_on(cell_gid_type i) const override { + std::vector<cell_connection> conns; + auto conn_param_gen = std::mt19937(i); // TODO: replace this with hashing generator... + auto source_gen = std::mt19937(i*123+457); // ditto + + std::uniform_int_distribution<cell_gid_type> source_distribution(0, ncell_-2); + + for (unsigned t=0; t<param_.num_synapses; ++t) { + auto source = source_distribution(source_gen); + if (source>=i) ++source; + + cell_connection cc = draw_connection_params(conn_param_gen); + cc.source = {source, 0}; + cc.dest = {i, t}; + conns.push_back(cc); + } + + return conns; + } +}; + +std::unique_ptr<recipe> make_basic_rgraph_recipe( + cell_gid_type ncell, + basic_recipe_param param, + probe_distribution pdist) +{ + return std::unique_ptr<recipe>(new basic_rgraph_recipe(ncell, param, pdist)); +} + +class basic_kgraph_recipe: public basic_cell_recipe { +public: + basic_kgraph_recipe(cell_gid_type ncell, + basic_recipe_param param, + probe_distribution pdist = probe_distribution{}): + basic_cell_recipe(ncell, std::move(param), std::move(pdist)) + { + if (std::size_t(param.num_synapses) != ncell-1) { + throw invalid_recipe_error("number of synapses per cell must equal number " + "of cells minus one in complete graph model"); + } + } + + std::vector<cell_connection> connections_on(cell_gid_type i) const override { + std::vector<cell_connection> conns; + auto conn_param_gen = std::mt19937(i); // TODO: replace this with hashing generator... + + for (unsigned t=0; t<param_.num_synapses; ++t) { + cell_gid_type source = t>=i? t+1: t; + EXPECTS(source<ncell_); + + cell_connection cc = draw_connection_params(conn_param_gen); + cc.source = {source, 0}; + cc.dest = {i, t}; + conns.push_back(cc); + } + + return conns; + } +}; + +std::unique_ptr<recipe> make_basic_kgraph_recipe( + cell_gid_type ncell, + basic_recipe_param param, + probe_distribution pdist) +{ + return std::unique_ptr<recipe>(new basic_kgraph_recipe(ncell, param, pdist)); +} + +} // namespace mc +} // namespace nest diff --git a/miniapp/miniapp_recipes.hpp b/miniapp/miniapp_recipes.hpp new file mode 100644 index 0000000000000000000000000000000000000000..2e519bd1b492e532db86175acbd11520d1bd7a8b --- /dev/null +++ b/miniapp/miniapp_recipes.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include <cstddef> +#include <memory> +#include <stdexcept> + +#include "recipe.hpp" + +// miniapp-specific recipes + +namespace nest { +namespace mc { + +struct probe_distribution { + float proportion = 1.f; // what proportion of cells should get probes? + bool all_segments = true; // false => soma only + bool membrane_voltage = true; + bool membrane_current = true; +}; + +struct basic_recipe_param { + unsigned num_compartments = 1; + unsigned num_synapses = 1; + std::string synapse_type = "expsyn"; + float min_connection_delay_ms = 20.0; + float mean_connection_delay_ms = 20.75; + float syn_weight_per_cell = 0.3; +}; + +std::unique_ptr<recipe> make_basic_ring_recipe( + cell_gid_type ncell, + basic_recipe_param param, + probe_distribution pdist = probe_distribution{}); + +std::unique_ptr<recipe> make_basic_kgraph_recipe( + cell_gid_type ncell, + basic_recipe_param param, + probe_distribution pdist = probe_distribution{}); + +std::unique_ptr<recipe> make_basic_rgraph_recipe( + cell_gid_type ncell, + basic_recipe_param param, + probe_distribution pdist = probe_distribution{}); + +} // namespace mc +} // namespace nest diff --git a/miniapp/trace_sampler.hpp b/miniapp/trace_sampler.hpp new file mode 100644 index 0000000000000000000000000000000000000000..26db0a491444db32a49653f861b18dce464c103d --- /dev/null +++ b/miniapp/trace_sampler.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include <cstdlib> +#include <vector> + +#include <common_types.hpp> +#include <cell.hpp> +#include <util/optional.hpp> + +#include <iostream> + +namespace nest { +namespace mc { + +template <typename Time=float, typename Value=double> +struct sample_trace { + using time_type = Time; + using value_type = Value; + + struct sample_type { + time_type time; + value_type value; + }; + + std::string name; + std::string units; + cell_member_type probe_id; + std::vector<sample_type> samples; + + sample_trace() =default; + sample_trace(cell_member_type probe_id, const std::string& name, const std::string& units): + name(name), units(units), probe_id(probe_id) + {} +}; + +template <typename Time=float, typename Value=double> +struct trace_sampler { + using time_type = Time; + using value_type = Value; + + float next_sample_t() const { return t_next_sample_; } + + util::optional<time_type> operator()(time_type t, value_type v) { + if (t<t_next_sample_) { + return t_next_sample_; + } + + trace_->samples.push_back({t,v}); + return t_next_sample_+=sample_dt_; + } + + trace_sampler(sample_trace<time_type, value_type> *trace, time_type sample_dt, time_type tfrom=0): + trace_(trace), sample_dt_(sample_dt), t_next_sample_(tfrom) + {} + +private: + sample_trace<time_type, value_type> *trace_; + + time_type sample_dt_; + time_type t_next_sample_; +}; + +// with type deduction ... +template <typename Time, typename Value> +trace_sampler<Time, Value> make_trace_sampler(sample_trace<Time, Value> *trace, Time sample_dt, Time tfrom=0) { + return trace_sampler<Time, Value>(trace, sample_dt, tfrom); +} + +} // namespace mc +} // namespace nest diff --git a/nrn/generate_validation.sh b/nrn/generate_validation.sh index 532a7cd738d53a1a0f8342d07f09c016b5e24857..15149cddc022aa47d2f94149fc4efc6c833e9ecd 100755 --- a/nrn/generate_validation.sh +++ b/nrn/generate_validation.sh @@ -1,5 +1,7 @@ -python2.7 ./soma.py -python2.7 ./ball_and_stick.py -python2.7 ./ball_and_3stick.py -python2.7 ./simple_synapse.py --synapse exp2 -python2.7 ./simple_synapse.py --synapse exp +#!/usr/bin/env bash + +python2 ./soma.py +python2 ./ball_and_stick.py +python2 ./ball_and_3stick.py +python2 ./simple_synapse.py --synapse exp2 +python2 ./simple_synapse.py --synapse exp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7f4a588d933a5ea74342c70dc8b6ac5df95aca8b..8e0db8876fadf3d030b65ccac9a653eedc1c8d37 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,8 +2,8 @@ set(HEADERS swcio.hpp ) set(BASE_SOURCES + common_types_io.cpp cell.cpp - mechanism_interface.cpp parameter_list.cpp profiling/profiler.cpp swcio.cpp diff --git a/src/algorithms.hpp b/src/algorithms.hpp index e89f06f539f51b37f7ce95ca8ff2de18b3709d62..37eeaf370acd54ef326f70cc4ae17e46f1c6f2b0 100644 --- a/src/algorithms.hpp +++ b/src/algorithms.hpp @@ -90,12 +90,12 @@ bool is_minimal_degree(C const& c) "is_minimal_degree only applies to integral types" ); - if(c.size()==0u) { + if (c.size()==0u) { return true; } using value_type = typename C::value_type; - if(c[0] != value_type(0)) { + if (c[0] != value_type(0)) { return false; } auto i = value_type(1); @@ -121,49 +121,43 @@ bool is_positive(C const& c) } template<typename C> -bool has_contiguous_segments(const C &parent_index) +std::vector<typename C::value_type> child_count(const C& parent_index) { static_assert( std::is_integral<typename C::value_type>::value, "integral type required" ); - if (!is_minimal_degree(parent_index)) { - return false; - } - - int n = parent_index.size(); - std::vector<bool> is_leaf(n, false); - - for(auto i=1; i<n; ++i) { - auto p = parent_index[i]; - if(is_leaf[p]) { - return false; - } - - if(p != i-1) { - // we have a branch and i-1 is a leaf node - is_leaf[i-1] = true; - } + std::vector<typename C::value_type> count(parent_index.size(), 0); + for (auto i = 1u; i < parent_index.size(); ++i) { + ++count[parent_index[i]]; } - return true; + return count; } template<typename C> -std::vector<typename C::value_type> child_count(const C &parent_index) +bool has_contiguous_compartments(const C& parent_index) { + using value_type = typename C::value_type; static_assert( - std::is_integral<typename C::value_type>::value, + std::is_integral<value_type>::value, "integral type required" ); - std::vector<typename C::value_type> count(parent_index.size(), 0); - for (std::size_t i = 1; i < parent_index.size(); ++i) { - ++count[parent_index[i]]; + if (!is_minimal_degree(parent_index)) { + return false; } - return count; + auto num_child = child_count(parent_index); + for (auto i=1u; i < parent_index.size(); ++i) { + auto p = parent_index[i]; + if (num_child[p]==1 && p!=value_type(i-1)) { + return false; + } + } + + return true; } template<typename C> @@ -174,7 +168,7 @@ std::vector<typename C::value_type> branches(const C& parent_index) "integral type required" ); - EXPECTS(has_contiguous_segments(parent_index)); + EXPECTS(has_contiguous_compartments(parent_index)); std::vector<typename C::value_type> branch_index; if (parent_index.empty()) { @@ -229,7 +223,7 @@ typename C::value_type find_branch(const C& branch_index, auto it = std::find_if( branch_index.begin(), branch_index.end(), - [nid](const value_type &v) { return v > nid; } + [nid](const value_type& v) { return v > nid; } ); return it - branch_index.begin() - 1; @@ -249,8 +243,8 @@ std::vector<typename C::value_type> make_parent_index( return {}; } - EXPECTS(parent_index.size() == unsigned(branch_index.back())); - EXPECTS(has_contiguous_segments(parent_index)); + EXPECTS(parent_index.size() == branch_index.back()); + EXPECTS(has_contiguous_compartments(parent_index)); EXPECTS(is_strictly_monotonic_increasing(branch_index)); // expand the branch index diff --git a/src/cell.cpp b/src/cell.cpp index 01d681a7e080c8c0444fe0299820181d5154028e..e96cae69aca834d61d81c9c53c9488532a3b5b69 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -26,7 +26,7 @@ cell::cell() parents_.push_back(0); } -int cell::num_segments() const +cell::size_type cell::num_segments() const { return segments_.size(); } @@ -75,9 +75,9 @@ cable_segment* cell::add_cable(cell::index_type parent, segment_ptr&& cable) return segments_.back()->as_cable(); } -segment* cell::segment(int index) +segment* cell::segment(index_type index) { - if(index<0 || index>=num_segments()) { + if (index>=num_segments()) { throw std::out_of_range( "attempt to access a segment with invalid index" ); @@ -85,9 +85,9 @@ segment* cell::segment(int index) return segments_[index].get(); } -segment const* cell::segment(int index) const +segment const* cell::segment(index_type index) const { - if(index<0 || index>=num_segments()) { + if (index>=num_segments()) { throw std::out_of_range( "attempt to access a segment with invalid index" ); @@ -109,7 +109,7 @@ soma_segment* cell::soma() return nullptr; } -cable_segment* cell::cable(int index) +cable_segment* cell::cable(index_type index) { if(index>0 && index<num_segments()) { return segment(index)->as_cable(); @@ -146,9 +146,9 @@ std::vector<segment_ptr> const& cell::segments() const return segments_; } -std::vector<int> cell::compartment_counts() const +std::vector<cell::size_type> cell::compartment_counts() const { - std::vector<int> comp_count; + std::vector<size_type> comp_count; comp_count.reserve(num_segments()); for(auto const& s : segments()) { comp_count.push_back(s->num_compartments()); @@ -156,10 +156,10 @@ std::vector<int> cell::compartment_counts() const return comp_count; } -size_t cell::num_compartments() const +cell::size_type cell::num_compartments() const { auto n = 0u; - for(auto &s : segments_) { + for(auto& s : segments_) { n += s->num_compartments(); } return n; @@ -196,7 +196,7 @@ void cell::add_detector(segment_location loc, double threshold) spike_detectors_.push_back({loc, threshold}); } -std::vector<int> const& cell::segment_parents() const +std::vector<cell::index_type> const& cell::segment_parents() const { return parents_; } @@ -212,24 +212,22 @@ std::vector<int> const& cell::segment_parents() const // - number of compartments in each segment bool cell_basic_equality(cell const& lhs, cell const& rhs) { - if(lhs.num_segments() != rhs.num_segments()) { + if (lhs.num_segments() != rhs.num_segments()) { return false; } - if(lhs.segment_parents() != rhs.segment_parents()) { + if (lhs.segment_parents() != rhs.segment_parents()) { return false; } - for(auto i=0; i<lhs.num_segments(); ++i) { + for (cell::index_type i=0; i<lhs.num_segments(); ++i) { // a quick and dirty test auto& l = *lhs.segment(i); auto& r = *rhs.segment(i); - if(l.kind() != r.kind()) return false; - if(l.area() != r.area()) return false; - if(l.volume() != r.volume()) return false; - if(l.as_cable()) { - if( l.as_cable()->num_compartments() - != r.as_cable()->num_compartments()) - { + if (l.kind() != r.kind()) return false; + if (l.area() != r.area()) return false; + if (l.volume() != r.volume()) return false; + if (l.as_cable()) { + if (l.as_cable()->num_compartments() != r.as_cable()->num_compartments()) { return false; } } diff --git a/src/cell.hpp b/src/cell.hpp index 1cb9b9f65dedbdcc4476c2b447eed1d1dea243ca..746549289f1b718433388e92f5f1ca9d13d433c1 100644 --- a/src/cell.hpp +++ b/src/cell.hpp @@ -5,8 +5,9 @@ #include <thread> #include <vector> -#include "segment.hpp" +#include "common_types.hpp" #include "cell_tree.hpp" +#include "segment.hpp" #include "stimulus.hpp" #include "util/debug.hpp" @@ -17,12 +18,12 @@ namespace mc { /// description struct compartment_model { cell_tree tree; - std::vector<int> parent_index; - std::vector<int> segment_index; + std::vector<cell_tree::int_type> parent_index; + std::vector<cell_tree::int_type> segment_index; }; struct segment_location { - segment_location(int s, double l) + segment_location(cell_lid_type s, double l) : segment(s), position(l) { EXPECTS(position>=0. && position<=1.); @@ -30,7 +31,7 @@ struct segment_location { friend bool operator==(segment_location l, segment_location r) { return l.segment==r.segment && l.position==r.position; } - int segment; + cell_lid_type segment; double position; }; @@ -44,27 +45,29 @@ enum class probeKind { membrane_current }; +struct probe_spec { + segment_location location; + probeKind kind; +}; + /// high-level abstract representation of a cell and its segments class cell { public: - - // types - using index_type = int; + using index_type = cell_lid_type; + using size_type = cell_local_size_type; using value_type = double; using point_type = point<value_type>; - + struct synapse_instance { segment_location location; parameter_list mechanism; }; - struct probe_instance { - segment_location location; - probeKind kind; - }; + struct stimulus_instance { segment_location location; i_clamp clamp; }; + struct detector_instance { segment_location location; double threshold; @@ -89,12 +92,12 @@ public: cable_segment* add_cable(index_type parent, Args ...args); /// the number of segments in the cell - int num_segments() const; + size_type num_segments() const; bool has_soma() const; - class segment* segment(int index); - class segment const* segment(int index) const; + class segment* segment(index_type index); + class segment const* segment(index_type index) const; /// access pointer to the soma /// returns nullptr if the cell has no soma @@ -103,7 +106,7 @@ public: /// access pointer to a cable segment /// will throw an std::out_of_range exception if /// the cable index is not valid - cable_segment* cable(int index); + cable_segment* cable(index_type index); /// the volume of the cell value_type volume() const; @@ -112,16 +115,16 @@ public: value_type area() const; /// the total number of compartments over all segments - size_t num_compartments() const; + size_type num_compartments() const; std::vector<segment_ptr> const& segments() const; /// return reference to array that enumerates the index of the parent of /// each segment - std::vector<int> const& segment_parents() const; + std::vector<index_type> const& segment_parents() const; /// return a vector with the compartment count for each segment in the cell - std::vector<int> compartment_counts() const; + std::vector<size_type> compartment_counts() const; compartment_model model() const; @@ -169,12 +172,12 @@ public: ////////////////// // probes ////////////////// - index_type add_probe(segment_location loc, probeKind kind) { - probes_.push_back({loc, kind}); + index_type add_probe(probe_spec p) { + probes_.push_back(p); return probes_.size()-1; } - const std::vector<probe_instance>& + const std::vector<probe_spec>& probes() const { return probes_; } private: @@ -195,7 +198,7 @@ private: std::vector<detector_instance> spike_detectors_; // the probes - std::vector<probe_instance> probes_; + std::vector<probe_spec> probes_; }; // Checks that two cells have the same diff --git a/src/cell_group.hpp b/src/cell_group.hpp index ff146bd1110eb873d5d83c35969b981a48579b5f..77276f49e52f12dc2b8205a7d24113d7409365f4 100644 --- a/src/cell_group.hpp +++ b/src/cell_group.hpp @@ -1,104 +1,100 @@ #pragma once #include <cstdint> +#include <functional> #include <vector> #include <cell.hpp> +#include <common_types.hpp> #include <event_queue.hpp> -#include <communication/spike.hpp> -#include <communication/spike_source.hpp> +#include <spike.hpp> +#include <spike_source.hpp> +#include <util/singleton.hpp> #include <profiling/profiler.hpp> namespace nest { namespace mc { -// samplers take a time and sample value, and return an optional time -// for the next desired sample. - -struct sampler { - using index_type = int; - using time_type = float; - using value_type = double; - - index_type probe_gid; // samplers are attached to probes - std::function<util::optional<time_type>(time_type, value_type)> sample; -}; - -template <typename Cell> +template <typename LoweredCell> class cell_group { public: - using index_type = uint32_t; - using cell_type = Cell; - using value_type = typename cell_type::value_type; - using size_type = typename cell_type::value_type; - using spike_detector_type = spike_detector<Cell>; + using index_type = cell_gid_type; + using lowered_cell_type = LoweredCell; + using value_type = typename lowered_cell_type::value_type; + using size_type = typename lowered_cell_type::value_type; + using spike_detector_type = spike_detector<lowered_cell_type>; + using source_id_type = cell_member_type; + + using time_type = float; + using sampler_function = std::function<util::optional<time_type>(time_type, double)>; struct spike_source_type { - index_type index; + source_id_type source_id; spike_detector_type source; }; cell_group() = default; - cell_group(const cell& c) : - cell_{c} + cell_group(cell_gid_type gid, const cell& c) : + gid_base_{gid} { - cell_.voltage()(memory::all) = -65.; - cell_.initialize(); + detector_handles_.resize(c.detectors().size()); + target_handles_.resize(c.synapses().size()); + probe_handles_.resize(c.probes().size()); - for (auto& d : c.detectors()) { - spike_sources_.push_back( { - 0u, spike_detector_type(cell_, d.location, d.threshold, 0.f) - }); - } - } + cell_.initialize(util::singleton_view(c), detector_handles_, target_handles_, probe_handles_); - void set_source_gids(index_type gid) { - for (auto& s : spike_sources_) { - s.index = gid++; - } - } + // Create spike detectors and associate them with globally unique source ids, + // as specified by cell gid and cell-local zero-based index. - void set_target_gids(index_type lid) { - first_target_gid_ = lid; - } + cell_gid_type source_gid = gid_base_; + cell_lid_type source_lid = 0u; - index_type num_probes() const { - return cell_.num_probes(); - } + unsigned i = 0; + for (auto& d : c.detectors()) { + cell_member_type source_id{source_gid, source_lid++}; - void set_probe_gids(index_type gid) { - first_probe_gid_ = gid; + spike_sources_.push_back({ + source_id, spike_detector_type(cell_, detector_handles_[i], d.threshold, 0.f) + }); + } } - std::pair<index_type, index_type> probe_gid_range() const { - return { first_probe_gid_, first_probe_gid_+cell_.num_probes() }; + void reset() { + clear_spikes(); + clear_events(); + reset_samplers(); + //initialize_cells(); + for (auto& spike_source: spike_sources_) { + spike_source.source.reset(cell_, 0.f); + } } - void advance(double tfinal, double dt) { + void advance(time_type tfinal, time_type dt) { while (cell_.time()<tfinal) { // take any pending samples - float cell_time = cell_.time(); + time_type cell_time = cell_.time(); - nest::mc::util::profiler_enter("sampling"); + PE("sampling"); while (auto m = sample_events_.pop_if_before(cell_time)) { - auto& sampler = samplers_[m->sampler_index]; - EXPECTS((bool)sampler.sample); + auto& s = samplers_[m->sampler_index]; + EXPECTS((bool)s.sampler); + auto next = s.sampler(cell_.time(), cell_.probe(s.handle)); - index_type probe_index = sampler.probe_gid-first_probe_gid_; - auto next = sampler.sample(cell_.time(), cell_.probe(probe_index)); if (next) { m->time = std::max(*next, cell_time); sample_events_.push(*m); } } - nest::mc::util::profiler_leave(); + PL(); // look for events in the next time step - auto tstep = std::min(tfinal, cell_.time()+dt); + time_type tstep = cell_.time()+dt; + tstep = std::min(tstep, tfinal); + auto next = events_.pop_if_before(tstep); - auto tnext = next ? next->time: tstep; + time_type tnext = next ? next->time: tstep; // integrate cell state cell_.advance(tnext - cell_.time()); @@ -106,25 +102,27 @@ public: std::cerr << "warning: solution out of bounds\n"; } - nest::mc::util::profiler_enter("events"); + PE("events"); // check for new spikes for (auto& s : spike_sources_) { if (auto spike = s.source.test(cell_, cell_.time())) { - spikes_.push_back({s.index, spike.get()}); + spikes_.push_back({s.source_id, spike.get()}); } } // apply events if (next) { - cell_.apply_event(next.get()); + auto handle = target_handles_[next->target.index]; + cell_.deliver_event(handle, next->weight); // apply events that are due within some epsilon of the current // time step. This should be a parameter. e.g. with for variable // order time stepping, use the minimum possible time step size. while(auto e = events_.pop_if_before(cell_.time()+dt/10.)) { - cell_.apply_event(e.get()); + auto handle = target_handles_[e->target.index]; + cell_.deliver_event(handle, e->weight); } } - nest::mc::util::profiler_leave(); + PL(); } } @@ -132,18 +130,12 @@ public: template <typename R> void enqueue_events(const R& events) { for (auto e : events) { - e.target -= first_target_gid_; events_.push(e); } } - const std::vector<communication::spike<index_type>>& - spikes() const { - return spikes_; - } - - cell_type& cell() { return cell_; } - const cell_type& cell() const { return cell_; } + const std::vector<spike<source_id_type, time_type>>& + spikes() const { return spikes_; } const std::vector<spike_source_type>& spike_sources() const { @@ -154,38 +146,76 @@ public: spikes_.clear(); } - void add_sampler(const sampler& s, float start_time = 0) { + void clear_events() { + events_.clear(); + } + + void add_sampler(cell_member_type probe_id, sampler_function s, time_type start_time = 0) { + EXPECTS(probe_id.gid==gid_base_); auto sampler_index = uint32_t(samplers_.size()); - samplers_.push_back(s); + samplers_.push_back({probe_handles_[probe_id.index], s}); + sampler_start_times_.push_back(start_time); sample_events_.push({sampler_index, start_time}); } -private: + void remove_samplers() { + sample_events_.clear(); + samplers_.clear(); + sampler_start_times_.clear(); + } + + void reset_samplers() { + // clear all pending sample events and reset to start at time 0 + sample_events_.clear(); + for(uint32_t i=0u; i<samplers_.size(); ++i) { + sample_events_.push({i, sampler_start_times_[i]}); + } + } + value_type probe(cell_member_type probe_id) const { + return cell_.probe(probe_handles_[probe_id.index]); + } + +private: + /// gid of first cell in group + cell_gid_type gid_base_; /// the lowered cell state (e.g. FVM) of the cell - cell_type cell_; + lowered_cell_type cell_; /// spike detectors attached to the cell std::vector<spike_source_type> spike_sources_; - //. spikes that are generated - std::vector<communication::spike<index_type>> spikes_; + /// spikes that are generated + std::vector<spike<source_id_type, time_type>> spikes_; /// pending events to be delivered - event_queue<postsynaptic_spike_event> events_; + event_queue<postsynaptic_spike_event<time_type>> events_; /// pending samples to be taken - event_queue<sample_event> sample_events_; + event_queue<sample_event<time_type>> sample_events_; + std::vector<time_type> sampler_start_times_; /// the global id of the first target (e.g. a synapse) in this group index_type first_target_gid_; - - /// the global id of the first probe in this group - index_type first_probe_gid_; + + /// handles for accessing lowered cell + using detector_handle = typename lowered_cell_type::detector_handle; + std::vector<detector_handle> detector_handles_; + + using target_handle = typename lowered_cell_type::target_handle; + std::vector<target_handle> target_handles_; + + using probe_handle = typename lowered_cell_type::probe_handle; + std::vector<probe_handle> probe_handles_; + + struct sampler_entry { + typename lowered_cell_type::probe_handle handle; + sampler_function sampler; + }; /// collection of samplers to be run against probes in this group - std::vector<sampler> samplers_; + std::vector<sampler_entry> samplers_; }; } // namespace mc diff --git a/src/cell_tree.hpp b/src/cell_tree.hpp index 4d9f70b1a934ab3b5bdb840d6bb0bd1b8419dfec..ce06d69a8b8ac7427b991e7e54ac2bc764a052e7 100644 --- a/src/cell_tree.hpp +++ b/src/cell_tree.hpp @@ -10,6 +10,8 @@ #include <vector> #include <vector/include/Vector.hpp> + +#include "common_types.hpp" #include "tree.hpp" #include "util.hpp" @@ -29,38 +31,42 @@ namespace mc { /// flexibility in choosing the root. class cell_tree { using range = memory::Range; -public : - // use a signed 16-bit integer for storage of indexes, which is reasonable given - // that typical cells have at most 1000-2000 segments - using int_type = int; + +public: + using int_type = cell_lid_type; + using size_type = cell_local_size_type; + using index_type = memory::HostVector<int_type>; using view_type = index_type::view_type; using const_view_type = index_type::const_view_type; + using tree = nest::mc::tree<int_type, size_type>; + static constexpr int_type no_parent = tree::no_parent; + /// default empty constructor cell_tree() = default; /// construct from a parent index - cell_tree(std::vector<int> const& parent_index) + cell_tree(std::vector<int_type> const& parent_index) { // handle the case of an empty parent list, which implies a single-compartment model if(parent_index.size()>0) { tree_ = tree(parent_index); } else { - tree_ = tree(std::vector<int>({0})); + tree_ = tree(std::vector<int_type>({0})); } } /// construct from a tree // copy constructor - cell_tree(tree const& t, int s) + cell_tree(tree const& t, int_type s) : tree_(t), soma_(s) { } // move constructor - cell_tree(tree&& t, int s) + cell_tree(tree&& t, int_type s) : tree_(std::move(t)), soma_(s) { } @@ -129,12 +135,12 @@ public : } /// returns the number of child segments of segment b - size_t num_children(size_t b) const { + size_type num_children(int_type b) const { return tree_.num_children(b); } /// returns a list of the children of segment b - const_view_type children(size_t b) const { + const_view_type children(int_type b) const { return tree_.children(b); } @@ -162,7 +168,7 @@ public : index_type depth_from_leaf() { tree::index_type depth(num_segments()); - depth_from_leaf(depth, 0); + depth_from_leaf(depth, int_type{0}); return depth; } @@ -170,7 +176,7 @@ public : { tree::index_type depth(num_segments()); depth[0] = 0; - depth_from_root(depth, 1); + depth_from_root(depth, int_type{1}); return depth; } @@ -179,12 +185,11 @@ private : /// helper type for sub-tree computation /// use in balance() struct sub_tree { - sub_tree(int r, int diam, int dpth) - : root(r), diameter(diam), depth(dpth) + sub_tree(int_type r, int_type diam, int_type dpth): + root(r), diameter(diam), depth(dpth) {} - void set(int r, int diam, int dpth) - { + void set(int r, int diam, int dpth) { root = r; diameter = diam; depth = dpth; @@ -198,15 +203,15 @@ private : "]"; } - int root; - int diameter; - int depth; + int_type root; + int_type diameter; + int_type depth; }; /// returns the index of the segment that would minimise the depth of the /// tree if used as the root segment int_type find_minimum_root() { - if(num_segments()==1) { + if (num_segments()==1) { return 0; } @@ -229,14 +234,14 @@ private : // walk has been completed to the root node, the node that has been // selected will be the root of the sub-tree with the largest diameter. sub_tree max_sub_tree(0, 0, 0); - auto distance_from_max_leaf = 1; + int_type distance_from_max_leaf = 1; auto pnt = max_leaf; auto pos = parent(max_leaf); - while(pos != -1) { + while(pos != no_parent) { for(auto c : children(pos)) { if(c!=pnt) { auto diameter = depth[c] + 1 + distance_from_max_leaf; - if(diameter>max_sub_tree.diameter) { + if (diameter>max_sub_tree.diameter) { max_sub_tree.set(pos, diameter, distance_from_max_leaf); } } @@ -266,7 +271,7 @@ private : return new_root; } - int_type depth_from_leaf(index_type& depth, int segment) + int_type depth_from_leaf(index_type& depth, int_type segment) { int_type max_depth = 0; for(auto c : children(segment)) { @@ -276,7 +281,7 @@ private : return max_depth+1; } - void depth_from_root(index_type& depth, int segment) + void depth_from_root(index_type& depth, int_type segment) { auto d = depth[parent(segment)] + 1; depth[segment] = d; diff --git a/src/common_types.hpp b/src/common_types.hpp new file mode 100644 index 0000000000000000000000000000000000000000..da81ed5d65368af60095d8b9dab40aeede29467a --- /dev/null +++ b/src/common_types.hpp @@ -0,0 +1,55 @@ +#pragma once + +/* + * Common definitions for index types etc. across prototype simulator + * library. (Expect analogues in future versions to be template parameters?) + */ + +#include <iosfwd> +#include <type_traits> + +#include <util/lexcmp_def.hpp> + +namespace nest { +namespace mc { + +// For identifying cells globally. + +using cell_gid_type = std::uint32_t; + +// For sizes of collections of cells. + +using cell_size_type = typename std::make_unsigned<cell_gid_type>::type; + +// For indexes into cell-local data. +// +// Local indices for items within a particular cell-local collection should be +// zero-based and numbered contiguously. + +using cell_lid_type = std::uint32_t; + +// For counts of cell-local data. + +using cell_local_size_type = typename std::make_unsigned<cell_lid_type>::type; + +// For global identification of an item of cell local data. +// +// Items of cell_member_type must: +// +// * be associated with a unique cell, identified by the member `gid` +// (see: cell_gid_type); +// +// * identify an item within a cell-local collection by the member `index` +// (see: cell_lid_type). + +struct cell_member_type { + cell_gid_type gid; + cell_lid_type index; +}; + +DEFINE_LEXICOGRAPHIC_ORDERING(cell_member_type,(a.gid,a.index),(b.gid,b.index)) + +} // namespace mc +} // namespace nest + +std::ostream& operator<<(std::ostream& O, nest::mc::cell_member_type m); diff --git a/src/common_types_io.cpp b/src/common_types_io.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ad6ca540b80e1ce296c8ba2f4eceb52cd9ebfd63 --- /dev/null +++ b/src/common_types_io.cpp @@ -0,0 +1,8 @@ +#include <iostream> + +#include <common_types.hpp> + +std::ostream& operator<<(std::ostream& O, nest::mc::cell_member_type m) { + return O << m.gid << ':' << m.index; +} + diff --git a/src/communication/communicator.hpp b/src/communication/communicator.hpp index 34185ac0fbd795294211ced7190d8b84a3e70874..13d4a54303da74aefbcab0bc4d0ecc3636d7f472 100644 --- a/src/communication/communicator.hpp +++ b/src/communication/communicator.hpp @@ -4,13 +4,15 @@ #include <iostream> #include <vector> #include <random> +#include <functional> -#include <communication/spike.hpp> -#include <threading/threading.hpp> #include <algorithms.hpp> +#include <connection.hpp> +#include <communication/gathered_vector.hpp> #include <event_queue.hpp> - -#include "connection.hpp" +#include <spike.hpp> +#include <util/debug.hpp> +#include <util/double_buffer.hpp> namespace nest { namespace mc { @@ -26,90 +28,55 @@ namespace communication { // Once all connections have been specified, the construct() method can be used // to build the data structures required for efficient spike communication and // event generation. -template <typename CommunicationPolicy> +template <typename Time, typename CommunicationPolicy> class communicator { public: - using id_type = uint32_t; using communication_policy_type = CommunicationPolicy; + using id_type = cell_gid_type; + using time_type = Time; + using spike_type = spike<cell_member_type, time_type>; + using connection_type = connection<time_type>; - using spike_type = spike<id_type>; + /// per-cell group lists of events to be delivered + using event_queue = + std::vector<postsynaptic_spike_event<time_type>>; communicator() = default; - communicator(id_type n_groups, std::vector<id_type> target_counts) : - num_groups_local_(n_groups), - num_targets_local_(target_counts.size()) - { - target_map_ = nest::mc::algorithms::make_index(target_counts); - num_targets_local_ = target_map_.back(); - - // create an event queue for each target group - events_.resize(num_groups_local_); - - // make maps for converting lid to gid - target_gid_map_ = communication_policy_.make_map(num_targets_local_); - group_gid_map_ = communication_policy_.make_map(num_groups_local_); + // TODO + // for now, still assuming one-to-one association cells <-> groups, + // so that 'group' gids as represented by their first cell gid are + // contiguous. + communicator(id_type cell_from, id_type cell_to): + cell_gid_from_(cell_from), cell_gid_to_(cell_to) + {} - // transform the target ids from lid to gid - auto first_target = target_gid_map_[domain_id()]; - for (auto &id : target_map_) { - id += first_target; - } - } - - id_type target_gid_from_group_lid(id_type lid) const { - EXPECTS(lid<num_groups_local_); - return target_map_[lid]; - } - - id_type group_gid_from_group_lid(id_type lid) const { - EXPECTS(lid<num_groups_local_); - return group_gid_map_[domain_id()] + lid; + cell_local_size_type num_groups_local() const + { + return cell_gid_to_-cell_gid_from_; } - void add_connection(connection con) { - EXPECTS(is_local_target(con.destination())); + void add_connection(connection_type con) { + EXPECTS(is_local_cell(con.destination().gid)); connections_.push_back(con); } - bool is_local_target(id_type gid) { - return gid>=target_gid_map_[domain_id()] - && gid<target_gid_map_[domain_id()+1]; - } - - bool is_local_group(id_type gid) { - return gid>=group_gid_map_[domain_id()] - && gid<group_gid_map_[domain_id()+1]; - } - - /// return the global id of the first group in domain d - /// the groups in domain d are in the contiguous half open range - /// [domain_first_group(d), domain_first_group(d+1)) - id_type group_gid_first(int d) const { - return group_gid_map_[d]; - } - - id_type target_lid(id_type gid) { - EXPECTS(is_local_group(gid)); - - return gid - target_gid_map_[domain_id()]; - } - - id_type group_lid(id_type gid) { - EXPECTS(is_local_group(gid)); - - return gid - group_gid_map_[domain_id()]; + /// returns true if the cell with gid is on the domain of the caller + bool is_local_cell(id_type gid) const { + return gid>=cell_gid_from_ && gid<cell_gid_to_; } - // builds the optimized data structure + /// builds the optimized data structure + /// must be called after all connections have been added void construct() { if (!std::is_sorted(connections_.begin(), connections_.end())) { - std::sort(connections_.begin(), connections_.end()); + threading::sort(connections_); } } - float min_delay() { - auto local_min = std::numeric_limits<float>::max(); + /// the minimum delay of all connections in the global network. + time_type min_delay() { + auto local_min = std::numeric_limits<time_type>::max(); for (auto& con : connections_) { local_min = std::min(local_min, con.delay()); } @@ -117,88 +84,44 @@ public: return communication_policy_.min(local_min); } - // return the local group index of the group which hosts the target with - // global id gid - id_type local_group_from_global_target(id_type gid) { - // assert that gid is in range - EXPECTS(is_local_target(gid)); - - return - std::distance( - target_map_.begin(), - std::upper_bound(target_map_.begin(), target_map_.end(), gid) - ) - 1; - } - - void add_spike(spike_type s) { - thread_spikes().push_back(s); - } - - void add_spikes(const std::vector<spike_type>& s) { - auto& v = thread_spikes(); - v.insert(v.end(), s.begin(), s.end()); - } - - std::vector<spike_type>& thread_spikes() { - return thread_spikes_.local(); - } - - void exchange() { - - // global all-to-all to gather a local copy of the global spike list - // on each node - //profiler_.enter("global exchange"); - auto global_spikes = communication_policy_.gather_spikes(local_spikes()); + /// Perform exchange of spikes. + /// + /// Takes as input the list of local_spikes that were generated on the calling domain. + /// Returns the full global set of vectors, along with meta data about their partition + gathered_vector<spike_type> exchange(const std::vector<spike_type>& local_spikes) { + // global all-to-all to gather a local copy of the global spike list on each node. + auto global_spikes = communication_policy_.gather_spikes( local_spikes ); num_spikes_ += global_spikes.size(); - clear_thread_spike_buffers(); - //profiler_.leave(); - - for (auto& q : events_) { - q.clear(); - } - - //profiler_.enter("events"); - - //profiler_.enter("make events"); - // check all global spikes to see if they will generate local events - for (auto spike : global_spikes) { + return global_spikes; + } + + /// Check each global spike in turn to see it generates local events. + /// If so, make the events and insert them into the appropriate event list. + /// Return a vector that contains the event queues for each local cell group. + /// + /// Returns a vector of event queues, with one queue for each local cell group. The + /// events in each queue are all events that must be delivered to targets in that cell + /// group as a result of the global spike exchange. + std::vector<event_queue> make_event_queues(const gathered_vector<spike_type>& global_spikes) { + auto queues = std::vector<event_queue>(num_groups_local()); + for (auto spike : global_spikes.values()) { // search for targets - auto targets = - std::equal_range( - connections_.begin(), connections_.end(), spike.source - ); + auto targets = std::equal_range(connections_.begin(), connections_.end(), spike.source); // generate an event for each target for (auto it=targets.first; it!=targets.second; ++it) { - auto gidx = local_group_from_global_target(it->destination()); - - events_[gidx].push_back(it->make_event(spike)); + auto gidx = cell_group_index(it->destination().gid); + queues[gidx].push_back(it->make_event(spike)); } } - //profiler_.leave(); // make events - - //profiler_.leave(); // event generation + return queues; } - uint64_t num_spikes() const - { - return num_spikes_; - } + /// Returns the total number of global spikes over the duration of the simulation + uint64_t num_spikes() const { return num_spikes_; } - int domain_id() const { - return communication_policy_.id(); - } - - int num_domains() const { - return communication_policy_.size(); - } - - const std::vector<postsynaptic_spike_event>& queue(int i) const { - return events_[i]; - } - - const std::vector<connection>& connections() const { + const std::vector<connection_type>& connections() const { return connections_; } @@ -206,66 +129,24 @@ public: return communication_policy_; } - const std::vector<id_type>& local_target_map() const { - return target_map_; - } - - std::vector<spike_type> local_spikes() { - std::vector<spike_type> spikes; - for (auto& v : thread_spikes_) { - spikes.insert(spikes.end(), v.begin(), v.end()); - } - return spikes; - } - - void clear_thread_spike_buffers() { - for (auto& v : thread_spikes_) { - v.clear(); - } + void reset() { + num_spikes_ = 0; } private: + std::size_t cell_group_index(cell_gid_type cell_gid) const { + // this will be more elaborate when there is more than one cell per cell group + EXPECTS(cell_gid>=cell_gid_from_ && cell_gid<cell_gid_to_); + return cell_gid-cell_gid_from_; + } - // - // both of these can be fixed with double buffering - // - // FIXME : race condition on the thread_spikes_ buffers when exchange() modifies/access them - // ... other threads will be pushing to them simultaneously - // FIXME : race condition on the group-specific event queues when exchange pushes to them - // ... other threads will be accessing them to update their event queues - - // thread private storage for accumulating spikes - using local_spike_store_type = - nest::mc::threading::enumerable_thread_specific<std::vector<spike_type>>; - local_spike_store_type thread_spikes_; - - std::vector<connection> connections_; - std::vector<std::vector<postsynaptic_spike_event>> events_; - - // local target group i has targets in the half open range - // [target_map_[i], target_map_[i+1]) - std::vector<id_type> target_map_; - - // for keeping track of how time is spent where - //util::Profiler profiler_; - - // the number of groups and targets handled by this communicator - id_type num_groups_local_; - id_type num_targets_local_; - - // index maps for the global distribution of groups and targets - - // communicator i has the groups in the half open range : - // [group_gid_map_[i], group_gid_map_[i+1]) - std::vector<id_type> group_gid_map_; - - // communicator i has the targets in the half open range : - // [target_gid_map_[i], target_gid_map_[i+1]) - std::vector<id_type> target_gid_map_; + std::vector<connection_type> connections_; communication_policy_type communication_policy_; uint64_t num_spikes_ = 0u; + id_type cell_gid_from_; + id_type cell_gid_to_; }; } // namespace communication diff --git a/src/communication/connection.hpp b/src/communication/connection.hpp deleted file mode 100644 index ee1acfd72dc21cffe01f28239bca29526366df31..0000000000000000000000000000000000000000 --- a/src/communication/connection.hpp +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once - -#include <cstdint> - -#include <event_queue.hpp> -#include <communication/spike.hpp> - -namespace nest { -namespace mc { -namespace communication { - -class connection { -public: - using id_type = uint32_t; - connection(id_type src, id_type dest, float w, float d) : - source_(src), - destination_(dest), - weight_(w), - delay_(d) - {} - - float weight() const { return weight_; } - float delay() const { return delay_; } - - id_type source() const { return source_; } - id_type destination() const { return destination_; } - - postsynaptic_spike_event make_event(spike<id_type> s) { - return {destination_, s.time + delay_, weight_}; - } - -private: - - id_type source_; - id_type destination_; - float weight_; - float delay_; -}; - -// connections are sorted by source id -// these operators make for easy interopability with STL algorithms - -static inline -bool operator< (connection lhs, connection rhs) { - return lhs.source() < rhs.source(); -} - -static inline -bool operator< (connection lhs, connection::id_type rhs) { - return lhs.source() < rhs; -} - -static inline -bool operator< (connection::id_type lhs, connection rhs) { - return lhs < rhs.source(); -} - -} // namespace communication -} // namespace mc -} // namespace nest - -static inline -std::ostream& operator<<(std::ostream& o, nest::mc::communication::connection const& con) { - char buff[512]; - snprintf( - buff, sizeof(buff), "con [%10u -> %10u : weight %8.4f, delay %8.4f]", - con.source(), con.destination(), con.weight(), con.delay() - ); - return o << buff; -} diff --git a/src/communication/gathered_vector.hpp b/src/communication/gathered_vector.hpp new file mode 100644 index 0000000000000000000000000000000000000000..ceb5c16cde44a872e9ce08d3b4c65c58f9f73c1a --- /dev/null +++ b/src/communication/gathered_vector.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include <cstdint> +#include <numeric> +#include <vector> + +#include <algorithms.hpp> + +namespace nest { +namespace mc { + +template <typename T> +class gathered_vector { +public: + using value_type = T; + using count_type = unsigned; + + gathered_vector(std::vector<value_type>&& v, std::vector<count_type>&& p) : + values_(std::move(v)), + partition_(std::move(p)) + { + EXPECTS(std::is_sorted(partition_.begin(), partition_.end())); + EXPECTS(std::size_t(partition_.back()) == values_.size()); + } + + /// the partition of distribution + const std::vector<count_type>& partition() const { + return partition_; + } + + /// the number of entries in the gathered vector in partiion i + count_type count(std::size_t i) const { + return partition_[i+1] - partition_[i]; + } + + /// the values in the gathered vector + const std::vector<value_type>& values() const { + return values_; + } + + /// the size of the gathered vector + std::size_t size() const { + return values_.size(); + } + +private: + std::vector<value_type> values_; + std::vector<count_type> partition_; +}; + +} // namespace mc +} // namespace nest diff --git a/src/communication/mpi.hpp b/src/communication/mpi.hpp index 5be71b6381bf8311bcab85d5396d64ba2c3eb26b..9ebb0ca412d83dbb600471417beb299fd1a55a8b 100644 --- a/src/communication/mpi.hpp +++ b/src/communication/mpi.hpp @@ -10,6 +10,7 @@ #include <mpi.h> #include <algorithms.hpp> +#include <communication/gathered_vector.hpp> namespace nest { namespace mc { @@ -121,6 +122,39 @@ namespace mpi { return buffer; } + /// Gather all of a distributed vector + /// Retains the meta data (i.e. vector partition) + template <typename T> + gathered_vector<T> gather_all_with_partition(const std::vector<T>& values) { + using gathered_type = gathered_vector<T>; + using count_type = typename gathered_vector<T>::count_type; + using traits = mpi_traits<T>; + + // We have to use int for the count and displs vectors instead + // of count_type because these are used as arguments to MPI_Allgatherv + // which expects int arguments. + auto counts = gather_all(int(values.size())); + for (auto& c : counts) { + c *= traits::count(); + } + auto displs = algorithms::make_index(counts); + + std::vector<T> buffer(displs.back()/traits::count()); + + MPI_Allgatherv( + // send buffer + values.data(), counts[rank()], traits::mpi_type(), + // receive buffer + buffer.data(), counts.data(), displs.data(), traits::mpi_type(), + MPI_COMM_WORLD + ); + + return gathered_type( + std::move(buffer), + std::vector<count_type>(displs.begin(), displs.end()) + ); + } + template <typename T> T reduce(T value, MPI_Op op, int root) { using traits = mpi_traits<T>; diff --git a/src/communication/mpi_global_policy.hpp b/src/communication/mpi_global_policy.hpp index 7409585447ae888eec2aa47ddc1fa0f137df7d6e..d12beb6372abb01526186162247b45db6ddf75b9 100644 --- a/src/communication/mpi_global_policy.hpp +++ b/src/communication/mpi_global_policy.hpp @@ -4,25 +4,25 @@ #error "mpi_global_policy.hpp should only be compiled in a WITH_MPI build" #endif +#include <cstdint> #include <type_traits> #include <vector> -#include <cstdint> - -#include <communication/spike.hpp> -#include <communication/mpi.hpp> #include <algorithms.hpp> +#include <common_types.hpp> +#include <communication/gathered_vector.hpp> +#include <communication/mpi.hpp> +#include <spike.hpp> namespace nest { namespace mc { namespace communication { struct mpi_global_policy { - using id_type = uint32_t; - - std::vector<spike<id_type>> - static gather_spikes(const std::vector<spike<id_type>>& local_spikes) { - return mpi::gather_all(local_spikes); + template <typename Spike> + static gathered_vector<Spike> + gather_spikes(const std::vector<Spike>& local_spikes) { + return mpi::gather_all_with_partition(local_spikes); } static int id() { return mpi::rank(); } @@ -39,6 +39,11 @@ struct mpi_global_policy { return nest::mc::mpi::reduce(value, MPI_MAX); } + template <typename T> + static T sum(T value) { + return nest::mc::mpi::reduce(value, MPI_SUM); + } + template < typename T, typename = typename std::enable_if<std::is_integral<T>::value> diff --git a/src/communication/serial_global_policy.hpp b/src/communication/serial_global_policy.hpp index a1c7fee9671cb79768e255a710e0082386302e8a..486266cadcacde9b9db4c7393efad9540a5c74f8 100644 --- a/src/communication/serial_global_policy.hpp +++ b/src/communication/serial_global_policy.hpp @@ -1,22 +1,25 @@ #pragma once +#include <cstdint> #include <type_traits> #include <vector> -#include <cstdint> - -#include <communication/spike.hpp> +#include <communication/gathered_vector.hpp> +#include <spike.hpp> namespace nest { namespace mc { namespace communication { struct serial_global_policy { - using id_type = uint32_t; - - std::vector<spike<id_type>> const - static gather_spikes(const std::vector<spike<id_type>>& local_spikes) { - return local_spikes; + template <typename Spike> + static gathered_vector<Spike> + gather_spikes(const std::vector<Spike>& local_spikes) { + using count_type = typename gathered_vector<Spike>::count_type; + return gathered_vector<Spike>( + std::vector<Spike>(local_spikes), + {0u, static_cast<count_type>(local_spikes.size())} + ); } static int id() { @@ -37,6 +40,11 @@ struct serial_global_policy { return value; } + template <typename T> + static T sum(T value) { + return value; + } + template < typename T, typename = typename std::enable_if<std::is_integral<T>::value> diff --git a/src/compartment.hpp b/src/compartment.hpp index b92360a560ba5de30a036505d50d457b7b5a5a9c..da6bbcf57298593a60f7510822832060c7a89846 100644 --- a/src/compartment.hpp +++ b/src/compartment.hpp @@ -3,6 +3,8 @@ #include <iterator> #include <utility> +#include "common_types.hpp" + namespace nest { namespace mc { @@ -10,7 +12,7 @@ namespace mc { /// The compartment is a conic frustrum struct compartment { using value_type = double; - using size_type = int; + using size_type = cell_local_size_type; using value_pair = std::pair<value_type, value_type>; compartment() = delete; @@ -103,8 +105,7 @@ class compartment_iterator : }; class compartment_range { - public: - +public: using size_type = compartment_iterator::size_type; using real_type = compartment_iterator::real_type; diff --git a/src/connection.hpp b/src/connection.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b9e40fb5baeb83ad69c87c219f65d9f98acc4456 --- /dev/null +++ b/src/connection.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include <cstdint> + +#include <common_types.hpp> +#include <event_queue.hpp> +#include <spike.hpp> + +namespace nest { +namespace mc { + +template <typename Time> +class connection { +public: + using id_type = cell_member_type; + using time_type = Time; + + connection(id_type src, id_type dest, float w, time_type d) : + source_(src), + destination_(dest), + weight_(w), + delay_(d) + {} + + float weight() const { return weight_; } + float delay() const { return delay_; } + + id_type source() const { return source_; } + id_type destination() const { return destination_; } + + postsynaptic_spike_event<time_type> make_event(spike<id_type, time_type> s) { + return {destination_, s.time + delay_, weight_}; + } + +private: + id_type source_; + id_type destination_; + float weight_; + time_type delay_; +}; + +// connections are sorted by source id +// these operators make for easy interopability with STL algorithms + +template <typename T> +static inline bool operator<(connection<T> lhs, connection<T> rhs) { + return lhs.source() < rhs.source(); +} + +template <typename T> +static inline bool operator<(connection<T> lhs, typename connection<T>::id_type rhs) { + return lhs.source() < rhs; +} + +template <typename T> +static inline bool operator<(typename connection<T>::id_type lhs, connection<T> rhs) { + return lhs < rhs.source(); +} + +} // namespace mc +} // namespace nest + +template <typename T> +static inline std::ostream& operator<<(std::ostream& o, nest::mc::connection<T> const& con) { + return o << "con [" << con.source() << " -> " << con.destination() + << " : weight " << con.weight() + << ", delay " << con.delay() << "]"; +} diff --git a/src/event_queue.hpp b/src/event_queue.hpp index 7f3034f3f08bbb2235bf457c95e8eedfe8d0895d..31f6a5a10cf6b0c9ff9c0ff049909b801cd25b34 100644 --- a/src/event_queue.hpp +++ b/src/event_queue.hpp @@ -4,35 +4,49 @@ #include <ostream> #include <queue> +#include "common_types.hpp" #include "util/optional.hpp" namespace nest { namespace mc { +/* An event class Event must comply with the following conventions: + * Typedefs: + * time_type floating point type used to represent event times + * Member functions: + * time_type when() const return time value associated with event + */ + +template <typename Time> struct postsynaptic_spike_event { - uint32_t target; - float time; + using time_type = Time; + + cell_member_type target; + time_type time; float weight; -}; -inline float event_time(const postsynaptic_spike_event &ev) { return ev.time; } + time_type when() const { return time; } +}; +template <typename Time> struct sample_event { - uint32_t sampler_index; - float time; -}; + using time_type = Time; -inline float event_time(const sample_event &ev) { return ev.time; } + std::uint32_t sampler_index; + time_type time; + + time_type when() const { return time; } +}; /* Event objects must have a method event_time() which returns a value * from a type with a total ordering with respect to <, >, etc. - */ + */ template <typename Event> class event_queue { public : using value_type = Event; - using time_type = decltype(event_time(std::declval<Event>())); + using time_type = typename Event::time_type; // create event_queue() {} @@ -46,7 +60,7 @@ public : } // push thing - void push(const value_type &e) { + void push(const value_type& e) { queue_.push(e); } @@ -56,7 +70,7 @@ public : // pop until util::optional<value_type> pop_if_before(time_type t_until) { - if (!queue_.empty() && event_time(queue_.top()) < t_until) { + if (!queue_.empty() && queue_.top().when() < t_until) { auto ev = queue_.top(); queue_.pop(); return ev; @@ -66,10 +80,15 @@ public : } } + // clear everything + void clear() { + queue_ = decltype(queue_){}; + } + private: struct event_greater { - bool operator()(const Event &a, const Event &b) { - return event_time(a) > event_time(b); + bool operator()(const Event& a, const Event& b) { + return a.when() > b.when(); } }; @@ -83,8 +102,8 @@ private: } // namespace nest } // namespace mc -inline -std::ostream& operator<< (std::ostream& o, const nest::mc::postsynaptic_spike_event& e) +template <typename T> +inline std::ostream& operator<<(std::ostream& o, const nest::mc::postsynaptic_spike_event<T>& e) { return o << "event[" << e.target << "," << e.time << "," << e.weight << "]"; } diff --git a/src/fvm_cell.hpp b/src/fvm_cell.hpp index 2fa86fd9d6326b3ffc1692f7c08c0fde00edf163..83fd8b769974f8447b3930d8f2477a0732d139fe 100644 --- a/src/fvm_cell.hpp +++ b/src/fvm_cell.hpp @@ -1,6 +1,7 @@ #pragma once #include <algorithm> +#include <iterator> #include <map> #include <set> #include <string> @@ -13,28 +14,156 @@ #include <math.hpp> #include <matrix.hpp> #include <mechanism.hpp> -#include <mechanism_interface.hpp> +#include <mechanism_catalogue.hpp> +#include <profiling/profiler.hpp> #include <segment.hpp> #include <stimulus.hpp> #include <util.hpp> -#include <profiling/profiler.hpp> +#include <util/meta.hpp> #include <vector/include/Vector.hpp> +/* + * Lowered cell implementation based on finite volume method. + * + * TODO: Move following description of internal API + * to a better location or document. + * + * Lowered cells are managed by the `cell_group` class. + * A `cell_group` manages one lowered cell instance, which + * may in turn simulate one or more cells. + * + * The outward facing interface for `cell_group` is defined + * in terms of cell gids and member indices; the interface + * between `cell_group` and a lowered cell is described below. + * + * The motivation for the following interface is to + * 1. Provide more flexibility in lowered cell implementation + * 2. Save memory and duplicated index maps by associating + * externally visible objects with opaque handles. + * + * In the following, `lowered_cell` represents the lowered + * cell class, and `lowered` an instance thereof. + * + * `lowered_cell::detector_handle` + * Type of handles used by spike detectors to query + * membrane voltage. + * + * `lowered_cell::probe_handle` + * Type of handle used to query the value of a probe. + * + * `lowered_cell::target_handle` + * Type of handle used to identify the target of a + * postsynaptic spike event. + * + * `lowered_cell::value_type` + * Floating point type used for internal states + * and simulation time. + * + * `lowered_cell()` + * Default constructor; performs no cell-specific + * initialization. + * + * `lowered.initialize(const Cells& cells, ...)` + * Allocate and initalize data structures to simulate + * the cells described in the collection `cells`, + * where each item is of type `nest::mc::cell`. + * + * Remaining arguments consist of references to + * collections for storing: + * 1. Detector handles + * 2. Target handles + * 3. Probe handles + * + * Handles are written in the same order as they + * appear in the provided cell descriptions. + * + * `lowered.reset()` + * + * Resets state to initial conditiions and sets + * internal simulation time to 0. + * + * `lowered.advance(value_type dt)` + * + * Advanece simulation state by `dt` (value in + * milliseconds). For `fvm_cell` at least, + * this corresponds to one integration step. + * + * `lowered.deliver_event(target_handle target, value_type weight)` + * + * Update target-specifc state based on the arrival + * of a postsynaptic spike event with given weight. + * + * `lowered.detect_voltage(detector_handle)` + * + * Return membrane voltage at detector site as specified + * by handle. + * + * `lowered.probe(probe_handle)` + * + * Return value of corresponding probe. + * + * `lowered.resting_potential(value_type potential)` + * + * Set the steady-state membrane voltage used for the + * cell initial condition. (Defaults to -65 mV currently.) + */ + namespace nest { namespace mc { namespace fvm { -template <typename T, typename I> +template <typename Value, typename Index> class fvm_cell { public: - fvm_cell() = default; /// the real number type - using value_type = T; + using value_type = Value; + /// the integral index type - using size_type = I; + using size_type = Index; + + /// the container used for indexes + using index_type = memory::HostVector<size_type>; + + /// the container used for values + using vector_type = memory::HostVector<value_type>; + + /// API for cell_group (see above): + + using detector_handle = size_type; + using target_handle = std::pair<size_type, size_type>; + using probe_handle = std::pair<const vector_type fvm_cell::*, size_type>; + + void resting_potential(value_type potential_mV) { + resting_potential_ = potential_mV; + } + + template <typename Cells, typename Detectors, typename Targets, typename Probes> + void initialize( + const Cells& cells, // collection of nest::mc::cell descriptions + Detectors& detector_handles, // (write) where to store detector handles + Targets& target_handles, // (write) where to store target handles + Probes& probe_handles); // (write) where to store probe handles + + void reset(); + + void deliver_event(target_handle h, value_type weight) { + mechanisms_[synapse_base_+h.first]->net_receive(h.second, weight); + } + + value_type detector_voltage(detector_handle h) const { + return voltage_[h]; // detector_handle is just the compartment index + } + + value_type probe(probe_handle h) const { + return (this->*h.first)[h.second]; + } + + void advance(value_type dt); + + /// Following types and methods are public only for testing: /// the type used to store matrix information using matrix_type = matrix<value_type, size_type>; @@ -46,21 +175,14 @@ public: /// ion species storage using ion_type = mechanisms::ion<value_type, size_type>; - /// the container used for indexes - using index_type = memory::HostVector<size_type>; /// view into index container using index_view = typename index_type::view_type; using const_index_view = typename index_type::const_view_type; - /// the container used for values - using vector_type = memory::HostVector<value_type>; /// view into value container using vector_view = typename vector_type::view_type; using const_vector_view = typename vector_type::const_view_type; - /// constructor - fvm_cell(nest::mc::cell const& cell); - /// build the matrix for a given time step void setup_matrix(value_type dt); @@ -88,6 +210,10 @@ public: vector_view voltage() { return voltage_; } const_vector_view voltage() const { return voltage_; } + /// return the current in each CV + vector_view current() { return current_; } + const_vector_view current() const { return current_; } + std::size_t size() const { return matrix_.size(); } /// return reference to in iterable container of the mechanisms @@ -109,27 +235,6 @@ public: ion_type& ion_k() { return ions_[mechanisms::ionKind::k]; } ion_type const& ion_k() const { return ions_[mechanisms::ionKind::k]; } - /// make a time step - void advance(value_type dt); - - /// pass an event to the appropriate synapse and call net_receive - void apply_event(postsynaptic_spike_event e) { - mechanisms_[synapse_index_]->net_receive(e.target, e.weight); - } - - mechanism_type& synapses() { - return mechanisms_[synapse_index_]; - } - - /// set initial states - void initialize(); - - /// returns the compartment index of a segment location - int compartment_index(segment_location loc) const; - - /// returns voltage at a segment location - value_type voltage(segment_location loc) const; - /// flags if solution is physically realistic. /// here we define physically realistic as the voltage being within reasonable bounds. /// use a simple test of the voltage at the soma is reasonable, i.e. in the range @@ -139,51 +244,43 @@ public: return (v>-1000.) && (v<1000.); } - /// returns current at a segment location - value_type current(segment_location loc) const; - - value_type time() const { return t_; } - value_type probe(uint32_t i) const { - auto p = probes_[i]; - return (this->*p.first)[p.second]; - } - std::size_t num_probes() const { return probes_.size(); } private: - - /// current time + /// current time [ms] value_type t_ = value_type{0}; + /// resting potential (initial voltage condition) + value_type resting_potential_ = -65; + /// the linear system for implicit time stepping of cell state matrix_type matrix_; /// index for fast lookup of compartment index ranges of segments index_type segment_index_; - /// cv_areas_[i] is the surface area of CV i + /// cv_areas_[i] is the surface area of CV i [µm^2] vector_type cv_areas_; /// alpha_[i] is the following value at the CV face between /// CV i and its parent, required when constructing linear system /// face_alpha_[i] = area_face / (c_m * r_L * delta_x); - vector_type face_alpha_; + vector_type face_alpha_; // [µm·m^2/cm/s ≡ 10^5 µm^2/ms] - /// cv_capacitance_[i] is the capacitance of CV i per unit area (i.e. c_m) + /// cv_capacitance_[i] is the capacitance of CV i per unit area (i.e. c_m) [F/m^2] vector_type cv_capacitance_; - /// the average current over the surface of each CV + /// the average current density over the surface of each CV [mA/cm^2] /// current_ = i_m - i_e - /// so the total current over the surface of CV i is - /// current_[i] * cv_areas_ vector_type current_; - /// the potential in mV in each CV + /// the potential in each CV [mV] vector_type voltage_; - std::size_t synapse_index_; // synapses at the end of mechanisms_, from here + /// Where point mechanisms start in the mechanisms_ list. + std::size_t synapse_base_; /// the set of mechanisms present in the cell std::vector<mechanism_type> mechanisms_; @@ -194,6 +291,9 @@ private: std::vector<std::pair<uint32_t, i_clamp>> stimulii_; std::vector<std::pair<const vector_type fvm_cell::*, uint32_t>> probes_; + + // mechanism factory + using mechanism_catalogue = nest::mc::mechanisms::catalogue<value_type, size_type>; }; //////////////////////////////////////////////////////////////////////////////// @@ -201,23 +301,35 @@ private: //////////////////////////////////////////////////////////////////////////////// template <typename T, typename I> -fvm_cell<T, I>::fvm_cell(nest::mc::cell const& cell) -: cv_areas_ {cell.num_compartments(), T(0)} -, face_alpha_ {cell.num_compartments(), T(0)} -, cv_capacitance_{cell.num_compartments(), T(0)} -, current_ {cell.num_compartments(), T(0)} -, voltage_ {cell.num_compartments(), T(0)} +template <typename Cells, typename Detectors, typename Targets, typename Probes> +void fvm_cell<T, I>::initialize( + const Cells& cells, + Detectors& detector_handles, + Targets& target_handles, + Probes& probe_handles) { + if (util::size(cells)!=1u) { + throw std::invalid_argument("fvm_cell accepts only one cell"); + } + + const nest::mc::cell& cell = *(std::begin(cells)); + size_type ncomp = cell.num_compartments(); + + // confirm write-parameters have enough room to store handles + EXPECTS(util::size(detector_handles)==cell.detectors().size()); + EXPECTS(util::size(target_handles)==cell.synapses().size()); + EXPECTS(util::size(probe_handles)==cell.probes().size()); + + // initialize storage + cv_areas_ = vector_type{ncomp, T{0}}; + face_alpha_ = vector_type{ncomp, T{0}}; + cv_capacitance_ = vector_type{ncomp, T{0}}; + current_ = vector_type{ncomp, T{0}}; + voltage_ = vector_type{ncomp, T{resting_potential_}}; + using util::left; using util::right; - // TODO: potential code stink - // matrix_ is a member, but it is not initialized with the other members - // above because it requires the parent_index, which is calculated - // "on the fly" by cell.model(). - // cell.model() is quite expensive, and the information it calculates is - // used elsewhere, so we defer the intialization to inside the constructor - // body. const auto graph = cell.model(); matrix_ = matrix_type(graph.parent_index); @@ -291,10 +403,10 @@ fvm_cell<T, I>::fvm_cell(nest::mc::cell const& cell) // for each mechanism in the cell record the indexes of the segments that // contain the mechanism - std::map<std::string, std::vector<int>> mech_map; + std::map<std::string, std::vector<unsigned>> mech_map; - for(auto i=0; i<cell.num_segments(); ++i) { - for(const auto& mech : cell.segment(i)->mechanisms()) { + for (unsigned i=0; i<cell.num_segments(); ++i) { + for (const auto& mech : cell.segment(i)->mechanisms()) { // FIXME : Membrane has to be a proper mechanism, // because it is exposed via the public interface. // This if statement is bad @@ -308,12 +420,10 @@ fvm_cell<T, I>::fvm_cell(nest::mc::cell const& cell) // instance. // TODO : this works well for density mechanisms (e.g. ion channels), but // does it work for point processes (e.g. synapses)? - for(auto& mech : mech_map) { - auto& helper = nest::mc::mechanisms::get_mechanism_helper(mech.first); - + for (auto& mech : mech_map) { // calculate the number of compartments that contain the mechanism auto num_comp = 0u; - for(auto seg : mech.second) { + for (auto seg : mech.second) { num_comp += segment_index_[seg+1] - segment_index_[seg]; } @@ -321,7 +431,7 @@ fvm_cell<T, I>::fvm_cell(nest::mc::cell const& cell) // the mechanism index_type compartment_index(num_comp); auto pos = 0u; - for(auto seg : mech.second) { + for (auto seg : mech.second) { auto seg_size = segment_index_[seg+1] - segment_index_[seg]; std::iota( compartment_index.data() + pos, @@ -333,24 +443,39 @@ fvm_cell<T, I>::fvm_cell(nest::mc::cell const& cell) // instantiate the mechanism mechanisms_.push_back( - helper->new_mechanism(voltage_, current_, compartment_index) + mechanism_catalogue::make(mech.first, voltage_, current_, compartment_index) ); } - synapse_index_ = mechanisms_.size(); + synapse_base_ = mechanisms_.size(); + + // Create the synapse mechanism implementations together with the target handles + std::vector<std::vector<cell_lid_type>> syn_mech_map; + std::map<std::string, std::size_t> syn_mech_indices; + auto target_hi = target_handles.begin(); - std::map<std::string, std::vector<int>> syn_map; for (const auto& syn : cell.synapses()) { - syn_map[syn.mechanism.name()].push_back(find_compartment_index(syn.location, graph)); + const auto& name = syn.mechanism.name(); + std::size_t index = 0; + if (syn_mech_indices.count(name)==0) { + index = syn_mech_map.size(); + syn_mech_indices[name] = index; + syn_mech_map.push_back(std::vector<cell_lid_type>{}); + } + else { + index = syn_mech_indices[name]; + } + + size_type comp = find_compartment_index(syn.location, graph); + *target_hi++ = target_handle{index, syn_mech_map[index].size()}; + syn_mech_map[index].push_back(comp); } - for (const auto &syni : syn_map) { + for (const auto& syni : syn_mech_indices) { const auto& mech_name = syni.first; - auto& helper = nest::mc::mechanisms::get_mechanism_helper(mech_name); - - index_type compartment_index(syni.second); - auto mech = helper->new_mechanism(voltage_, current_, compartment_index); + index_type compartment_index(syn_mech_map[syni.second]); + auto mech = mechanism_catalogue::make(mech_name, voltage_, current_, compartment_index); mech->set_areas(cv_areas_); mechanisms_.push_back(std::move(mech)); } @@ -370,7 +495,7 @@ fvm_cell<T, I>::fvm_cell(nest::mc::cell const& cell) } } } - std::vector<int> indexes(index_set.begin(), index_set.end()); + std::vector<cell_lid_type> indexes(index_set.begin(), index_set.end()); // create the ion state if(indexes.size()) { @@ -413,24 +538,35 @@ fvm_cell<T, I>::fvm_cell(nest::mc::cell const& cell) } // record probe locations by index into corresponding state vector + auto probe_hi = probe_handles.begin(); for (auto probe : cell.probes()) { - uint32_t comp = find_compartment_index(probe.location, graph); + auto comp = find_compartment_index(probe.location, graph); switch (probe.kind) { - case probeKind::membrane_voltage: - probes_.push_back({&fvm_cell::voltage_, comp}); - break; - case probeKind::membrane_current: - probes_.push_back({&fvm_cell::current_, comp}); - break; - default: - throw std::logic_error("unrecognized probeKind"); + case probeKind::membrane_voltage: + *probe_hi = {&fvm_cell::voltage_, comp}; + break; + case probeKind::membrane_current: + *probe_hi = {&fvm_cell::current_, comp}; + break; + default: + throw std::logic_error("unrecognized probeKind"); } + ++probe_hi; + } + + // detector handles are just their corresponding compartment indices + auto detector_hi = detector_handles.begin(); + for (auto detector : cell.detectors()) { + auto comp = find_compartment_index(detector.location, graph); + *detector_hi++ = comp; } + + // initialise mechanism and voltage state + reset(); } template <typename T, typename I> -void fvm_cell<T, I>::setup_matrix(T dt) -{ +void fvm_cell<T, I>::setup_matrix(T dt) { using memory::all; // convenience accesors to matrix storage @@ -452,9 +588,9 @@ void fvm_cell<T, I>::setup_matrix(T dt) // . . . // l[i] . . d[i] // - //d(all) = 1.0; - d(all) = cv_areas_; - for(auto i=1u; i<d.size(); ++i) { + + d(all) = cv_areas_; // [µm^2] + for (auto i=1u; i<d.size(); ++i) { auto a = 1e5*dt * face_alpha_[i]; d[i] += a; @@ -467,90 +603,64 @@ void fvm_cell<T, I>::setup_matrix(T dt) // the RHS of the linear system is // V[i] - dt/cm*(im - ie) - auto factor = 10.*dt; + auto factor = 10.*dt; // units: 10·ms/(F/m^2)·(mA/cm^2) ≡ mV for(auto i=0u; i<d.size(); ++i) { - //rhs[i] = voltage_[i] - factor/cv_capacitance_[i]*current_[i]; rhs[i] = cv_areas_[i]*(voltage_[i] - factor/cv_capacitance_[i]*current_[i]); } } -template <typename T, typename I> -int fvm_cell<T, I>::compartment_index(segment_location loc) const -{ - EXPECTS(unsigned(loc.segment) < segment_index_.size()); - - const auto seg = loc.segment; - - auto first = segment_index_[seg]; - auto n = segment_index_[seg+1] - first; - auto index = std::floor(n*loc.position); - return index<n ? first+index : first+n-1; -} template <typename T, typename I> -T fvm_cell<T, I>::voltage(segment_location loc) const -{ - return voltage_[compartment_index(loc)]; -} - -template <typename T, typename I> -T fvm_cell<T, I>::current(segment_location loc) const -{ - return current_[compartment_index(loc)]; -} - -template <typename T, typename I> -void fvm_cell<T, I>::initialize() -{ +void fvm_cell<T, I>::reset() { + voltage_(memory::all) = resting_potential_; t_ = 0.; - - for(auto& m : mechanisms_) { + for (auto& m : mechanisms_) { m->nrn_init(); } } template <typename T, typename I> -void fvm_cell<T, I>::advance(T dt) -{ +void fvm_cell<T, I>::advance(T dt) { using memory::all; - mc::util::profiler_enter("current"); + PE("current"); current_(all) = 0.; // update currents from ion channels for(auto& m : mechanisms_) { - mc::util::profiler_enter(m->name().c_str()); + PE(m->name().c_str()); m->set_params(t_, dt); m->nrn_current(); - mc::util::profiler_leave(); + PL(); } // add current contributions from stimulii - for(auto& stim : stimulii_) { - auto ie = stim.second.amplitude(t_); + for (auto& stim : stimulii_) { + auto ie = stim.second.amplitude(t_); // [nA] auto loc = stim.first; - // the factor of 100 scales the injected current to 10^2.nA - current_[loc] -= 100.*ie/cv_areas_[loc]; + // note: current_ in [mA/cm^2], ie in [nA], cv_areas_ in [µm^2]. + // unit scale factor: [nA/µm^2]/[mA/cm^2] = 100 + current_[loc] -= 100*ie/cv_areas_[loc]; } - mc::util::profiler_leave(); + PL(); - mc::util::profiler_enter("matrix", "setup"); // solve the linear system + PE("matrix", "setup"); setup_matrix(dt); - mc::util::profiler_leave(); mc::util::profiler_enter("solve"); + PL(); PE("solve"); matrix_.solve(); - mc::util::profiler_leave(); + PL(); voltage_(all) = matrix_.rhs(); - mc::util::profiler_leave(); + PL(); - mc::util::profiler_enter("state"); // integrate state of gating variables etc. + PE("state"); for(auto& m : mechanisms_) { - mc::util::profiler_enter(m->name().c_str()); + PE(m->name().c_str()); m->nrn_state(); - mc::util::profiler_leave(); + PL(); } - mc::util::profiler_leave(); + PL(); t_ += dt; } @@ -559,4 +669,3 @@ void fvm_cell<T, I>::advance(T dt) } // namespace mc } // namespace nest - diff --git a/src/io/exporter.hpp b/src/io/exporter.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a5885b7b10841593059402a491ccf21148b78df3 --- /dev/null +++ b/src/io/exporter.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include <random> +#include <string> + +#include <common_types.hpp> +#include <spike.hpp> + +namespace nest { +namespace mc { +namespace io { + +// interface for exporters. +// Exposes one virtual functions: +// do_export(vector<type>) receiving a vector of parameters to export + +template <typename Time, typename CommunicationPolicy> +class exporter { + +public: + using time_type = Time; + using spike_type = spike<cell_member_type, time_type>; + + // Performs the export of the data + virtual void output(const std::vector<spike_type>&) = 0; + + // Returns the status of the exporter + virtual bool good() const = 0; +}; + +} //communication +} // namespace mc +} // namespace nest diff --git a/src/io/exporter_spike_file.hpp b/src/io/exporter_spike_file.hpp new file mode 100644 index 0000000000000000000000000000000000000000..67c714209becbe140d2244a8a7e28552c41d7b8c --- /dev/null +++ b/src/io/exporter_spike_file.hpp @@ -0,0 +1,102 @@ +#pragma once + +#include <fstream> +#include <iomanip> +#include <memory> +#include <random> +#include <stdexcept> +#include <vector> + +#include <cstring> +#include <cstdio> + +#include <common_types.hpp> +#include <io/exporter.hpp> +#include <spike.hpp> +#include <util.hpp> + +namespace nest { +namespace mc { +namespace io { + +template <typename Time, typename CommunicationPolicy> +class exporter_spike_file : public exporter<Time, CommunicationPolicy> { +public: + using time_type = Time; + using spike_type = spike<cell_member_type, time_type>; + using communication_policy_type = CommunicationPolicy; + + // Constructor + // over_write if true will overwrite the specified output file (default = true) + // output_path relative or absolute path + // file_name will be appended with "_x" with x the rank number + // file_extension a seperator will be added automatically + exporter_spike_file( + const std::string& file_name, + const std::string& path, + const std::string& file_extension, + bool over_write=true) + { + file_path_ = + create_output_file_path( + file_name, path, file_extension, communication_policy_.id()); + + //test if the file exist and depending on over_write throw or delete + if (!over_write && file_exists(file_path_)) { + throw std::runtime_error( + "Tried opening file for writing but it exists and over_write is false: " + file_path_); + } + + file_handle_.open(file_path_); + } + + // Performs the a export of the spikes to file + // one id and spike time with 4 decimals after the comma on a + // line space separated + void output(const std::vector<spike_type>& spikes) override { + for (auto spike : spikes) { + char linebuf[45]; + auto n = + std::snprintf( + linebuf, sizeof(linebuf), "%u %.4f\n", + unsigned{spike.source.gid}, float(spike.time)); + file_handle_.write(linebuf, n); + } + } + + bool good() const override { + return file_handle_.good(); + } + + // Creates an indexed filename + static std::string create_output_file_path( + const std::string& file_name, + const std::string& path, + const std::string& file_extension, + unsigned index) + { + return path + file_name + "_" + std::to_string(index) + "." + file_extension; + } + + // The name of the output path and file name. + // May be either relative or absolute path. + const std::string& file_path() const { + return file_path_; + } + +private: + bool file_exists(const std::string& file_path) { + std::ifstream fid(file_path); + return fid.good(); + } + + // Handle to opened file handle + std::ofstream file_handle_; + std::string file_path_; + + communication_policy_type communication_policy_; +}; + +} //communication +} // namespace mc +} // namespace nest diff --git a/src/matrix.hpp b/src/matrix.hpp index d0ddb816adc5cb9bfb6c3c88562e0a41835920ec..79b7218bc97fa0b16469ac50dbb5a91dc58c0db6 100644 --- a/src/matrix.hpp +++ b/src/matrix.hpp @@ -164,7 +164,7 @@ class matrix { auto const ncells = num_cells(); // loop over submatrices - for(auto m=0; m<ncells; ++m) { + for (size_type m=0; m<ncells; ++m) { auto first = cell_index_[m]; auto last = cell_index_[m+1]; diff --git a/src/mechanism_catalogue.hpp b/src/mechanism_catalogue.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c9d3e6de2771a1b18fa05f104dd257b8c7fe6dd9 --- /dev/null +++ b/src/mechanism_catalogue.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include <map> +#include <stdexcept> +#include <string> + +#include <mechanism.hpp> +#include <mechanisms/hh.hpp> +#include <mechanisms/pas.hpp> +#include <mechanisms/expsyn.hpp> +#include <mechanisms/exp2syn.hpp> + +namespace nest { +namespace mc { +namespace mechanisms { + +template <typename T, typename I> +struct catalogue { + using view_type = typename mechanism<T, I>::view_type; + using index_view = typename mechanism<T, I>::index_view; + + static mechanism_ptr<T, I> make( + const std::string& name, + view_type vec_v, + view_type vec_i, + index_view node_indices) + { + auto entry = mech_map.find(name); + if (entry==mech_map.end()) { + throw std::out_of_range("no such mechanism"); + } + + return entry->second(vec_v, vec_i, node_indices); + } + + static bool has(const std::string& name) { + return mech_map.count(name)>0; + } + +private: + using maker_type = mechanism_ptr<T, I> (*)(view_type, view_type, index_view); + static const std::map<std::string, maker_type> mech_map; + + template <template <typename, typename> class mech> + static mechanism_ptr<T, I> maker(view_type vec_v, view_type vec_i, index_view node_indices) { + return make_mechanism<mech<T, I>>(vec_v, vec_i, node_indices); + } +}; + +template <typename T, typename I> +const std::map<std::string, typename catalogue<T, I>::maker_type> catalogue<T, I>::mech_map = { + { "pas", maker<pas::mechanism_pas> }, + { "hh", maker<hh::mechanism_hh> }, + { "expsyn", maker<expsyn::mechanism_expsyn> }, + { "exp2syn", maker<exp2syn::mechanism_exp2syn> } +}; + + +} // namespace mechanisms +} // namespace mc +} // namespace nest diff --git a/src/mechanism_interface.cpp b/src/mechanism_interface.cpp deleted file mode 100644 index 680e8739d11ee0dad16890bb1088a1a7d4b774b5..0000000000000000000000000000000000000000 --- a/src/mechanism_interface.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "mechanism_interface.hpp" - -// -// include the mechanisms -// - -#include <mechanisms/hh.hpp> -#include <mechanisms/pas.hpp> -#include <mechanisms/expsyn.hpp> -#include <mechanisms/exp2syn.hpp> - - -namespace nest { -namespace mc { -namespace mechanisms { - -std::map<std::string, mechanism_helper_ptr<value_type, index_type>> mechanism_helpers; - -void setup_mechanism_helpers() { - mechanism_helpers["pas"] = - make_mechanism_helper< - mechanisms::pas::helper<value_type, index_type> - >(); - - mechanism_helpers["hh"] = - make_mechanism_helper< - mechanisms::hh::helper<value_type, index_type> - >(); - - mechanism_helpers["expsyn"] = - make_mechanism_helper< - mechanisms::expsyn::helper<value_type, index_type> - >(); - - mechanism_helpers["exp2syn"] = - make_mechanism_helper< - mechanisms::exp2syn::helper<value_type, index_type> - >(); -} - -mechanism_helper_ptr<value_type, index_type>& -get_mechanism_helper(const std::string& name) -{ - auto helper = mechanism_helpers.find(name); - if(helper==mechanism_helpers.end()) { - throw std::out_of_range( - nest::mc::util::pprintf("there is no mechanism named \'%\'", name) - ); - } - - return helper->second; -} - -} // namespace mechanisms -} // namespace nest -} // namespace mc - diff --git a/src/mechanism_interface.hpp b/src/mechanism_interface.hpp index c59caaad6c655d6a69c5206b956b1cb42959cfdf..5e5360e6602761ae2923c6c2afec958279235dca 100644 --- a/src/mechanism_interface.hpp +++ b/src/mechanism_interface.hpp @@ -1,7 +1,6 @@ #pragma once -#include <map> -#include <string> +// just for compatibility with current version of modparser... #include "mechanism.hpp" #include "parameter_list.hpp" @@ -10,11 +9,6 @@ namespace nest { namespace mc { namespace mechanisms { -using value_type = double; -using index_type = int; - -/// helper type for building mechanisms -/// the use of abstract base classes everywhere is a bit ugly template <typename T, typename I> struct mechanism_helper { using value_type = T; @@ -25,34 +19,10 @@ struct mechanism_helper { using view_type = typename mechanism<T,I>::view_type; virtual std::string name() const = 0; - virtual mechanism_ptr<T,I> new_mechanism(view_type, view_type, index_view) const = 0; - virtual void set_parameters(mechanism_ptr_type&, parameter_list const&) const = 0; }; -template <typename T, typename I> -using mechanism_helper_ptr = - std::unique_ptr<mechanism_helper<T,I>>; - -template <typename M> -mechanism_helper_ptr<typename M::value_type, typename M::size_type> -make_mechanism_helper() -{ - return util::make_unique<M>(); -} - -// for now use a global variable for the map of mechanism helpers -extern std::map< - std::string, - mechanism_helper_ptr<value_type, index_type> -> mechanism_helpers; - -void setup_mechanism_helpers(); - -mechanism_helper_ptr<value_type, index_type>& -get_mechanism_helper(const std::string& name); - } // namespace mechanisms } // namespace mc } // namespace nest diff --git a/src/model.hpp b/src/model.hpp new file mode 100644 index 0000000000000000000000000000000000000000..5d0ce4fd8bcbe010cec3629e4b5d1adde38bcd96 --- /dev/null +++ b/src/model.hpp @@ -0,0 +1,263 @@ +#pragma once + +#include <memory> +#include <vector> + +#include <cstdlib> + +#include <common_types.hpp> +#include <cell.hpp> +#include <cell_group.hpp> +#include <communication/communicator.hpp> +#include <communication/global_policy.hpp> +#include <fvm_cell.hpp> +#include <profiling/profiler.hpp> +#include <recipe.hpp> +#include <thread_private_spike_store.hpp> +#include <util/nop.hpp> + +#include "trace_sampler.hpp" + +namespace nest { +namespace mc { + +template <typename Cell> +class model { +public: + using cell_group_type = cell_group<Cell>; + using time_type = typename cell_group_type::time_type; + using value_type = typename cell_group_type::value_type; + using communicator_type = communication::communicator<time_type, communication::global_policy>; + using sampler_function = typename cell_group_type::sampler_function; + using spike_type = typename communicator_type::spike_type; + using spike_export_function = std::function<void(const std::vector<spike_type>&)>; + + struct probe_record { + cell_member_type id; + probe_spec probe; + }; + + model(const recipe& rec, cell_gid_type cell_from, cell_gid_type cell_to): + cell_from_(cell_from), + cell_to_(cell_to), + communicator_(cell_from, cell_to) + { + // generate the cell groups in parallel, with one task per cell group + cell_groups_ = std::vector<cell_group_type>{cell_to_-cell_from_}; + threading::parallel_vector<probe_record> probes; + + threading::parallel_for::apply(cell_from_, cell_to_, + [&](cell_gid_type i) { + PE("setup", "cells"); + auto cell = rec.get_cell(i); + auto idx = i-cell_from_; + cell_groups_[idx] = cell_group_type(i, cell); + + cell_lid_type j = 0; + for (const auto& probe: cell.probes()) { + cell_member_type probe_id{i,j++}; + probes.push_back({probe_id, probe}); + } + PL(2); + }); + + // insert probes + probes_.assign(probes.begin(), probes.end()); + + // generate the network connections + for (cell_gid_type i=cell_from_; i<cell_to_; ++i) { + for (const auto& cc: rec.connections_on(i)) { + connection<time_type> conn{cc.source, cc.dest, cc.weight, cc.delay}; + communicator_.add_connection(conn); + } + } + communicator_.construct(); + + // Allocate an empty queue buffer for each cell group + // These must be set initially to ensure that a queue is available for each + // cell group for the first time step. + current_events().resize(num_groups()); + future_events().resize(num_groups()); + } + + void reset() { + t_ = 0.; + for (auto& group: cell_groups_) { + group.reset(); + } + + communicator_.reset(); + + for(auto& q : current_events()) { + q.clear(); + } + for(auto& q : future_events()) { + q.clear(); + } + + current_spikes().clear(); + previous_spikes().clear(); + + util::profilers_restart(); + } + + time_type run(time_type tfinal, time_type dt) { + // Calculate the size of the largest possible time integration interval + // before communication of spikes is required. + // If spike exchange and cell update are serialized, this is the + // minimum delay of the network, however we use half this period + // to overlap communication and computation. + time_type t_interval = communicator_.min_delay()/2; + + while (t_<tfinal) { + auto tuntil = std::min(t_+t_interval, tfinal); + + event_queues_.exchange(); + local_spikes_.exchange(); + + // empty the spike buffers for the current integration period. + // these buffers will store the new spikes generated in update_cells. + current_spikes().clear(); + + // task that updates cell state in parallel. + auto update_cells = [&] () { + threading::parallel_for::apply( + 0u, cell_groups_.size(), + [&](unsigned i) { + auto &group = cell_groups_[i]; + + PE("stepping","events"); + group.enqueue_events(current_events()[i]); + PL(); + + group.advance(tuntil, dt); + + PE("events"); + current_spikes().insert(group.spikes()); + group.clear_spikes(); + PL(2); + }); + }; + + // task that performs spike exchange with the spikes generated in + // the previous integration period, generating the postsynaptic + // events that must be delivered at the start of the next + // integration period at the latest. + auto exchange = [&] () { + PE("stepping", "communciation"); + + PE("exchange"); + auto local_spikes = previous_spikes().gather(); + auto global_spikes = communicator_.exchange(local_spikes); + PL(); + + PE("spike output"); + local_export_callback_(local_spikes); + global_export_callback_(global_spikes.values()); + PL(); + + PE("events"); + future_events() = communicator_.make_event_queues(global_spikes); + PL(); + + PL(2); + }; + + // run the tasks, overlapping if the threading model and number of + // available threads permits it. + threading::task_group g; + g.run(exchange); + g.run(update_cells); + g.wait(); + + t_ = tuntil; + } + + return t_; + } + + // only thread safe if called outside the run() method + void add_artificial_spike(cell_member_type source) { + add_artificial_spike(source, t_); + } + + // only thread safe if called outside the run() method + void add_artificial_spike(cell_member_type source, time_type tspike) { + current_spikes().get().push_back({source, tspike}); + } + + void attach_sampler(cell_member_type probe_id, sampler_function f, time_type tfrom = 0) { + // TODO: translate probe_id.gid to appropriate group, but for now 1-1. + if (probe_id.gid<cell_from_ || probe_id.gid>=cell_to_) { + return; + } + cell_groups_[probe_id.gid-cell_from_].add_sampler(probe_id, f, tfrom); + } + + const std::vector<probe_record>& probes() const { return probes_; } + + std::size_t num_spikes() const { + return communicator_.num_spikes(); + } + std::size_t num_groups() const { + return cell_groups_.size(); + } + std::size_t num_cells() const { + // TODO: fix when the assumption that there is one cell per cell group is no longer valid + return num_groups(); + } + + // register a callback that will perform a export of the global + // spike vector + void set_global_spike_callback(spike_export_function export_callback) { + global_export_callback_ = export_callback; + } + + // register a callback that will perform a export of the rank local + // spike vector + void set_local_spike_callback(spike_export_function export_callback) { + local_export_callback_ = export_callback; + } + +private: + cell_gid_type cell_from_; + cell_gid_type cell_to_; + time_type t_ = 0.; + std::vector<cell_group_type> cell_groups_; + communicator_type communicator_; + std::vector<probe_record> probes_; + + using event_queue_type = typename communicator_type::event_queue; + util::double_buffer< std::vector<event_queue_type> > event_queues_; + + using local_spike_store_type = thread_private_spike_store<time_type>; + util::double_buffer< local_spike_store_type > local_spikes_; + + spike_export_function global_export_callback_ = util::nop_function; + spike_export_function local_export_callback_ = util::nop_function; + + // Convenience functions that map the spike buffers and event queues onto + // the appropriate integration interval. + // + // To overlap communication and computation, integration intervals of + // size Delta/2 are used, where Delta is the minimum delay in the global + // system. + // From the frame of reference of the current integration period we + // define three intervals: previous, current and future + // Then we define the following : + // current_spikes : spikes generated in the current interval + // previous_spikes: spikes generated in the preceding interval + // current_events : events to be delivered at the start of + // the current interval + // future_events : events to be delivered at the start of + // the next interval + + local_spike_store_type& current_spikes() { return local_spikes_.get(); } + local_spike_store_type& previous_spikes() { return local_spikes_.other(); } + + std::vector<event_queue_type>& current_events() { return event_queues_.get(); } + std::vector<event_queue_type>& future_events() { return event_queues_.other(); } +}; + +} // namespace mc +} // namespace nest diff --git a/src/profiling/profiler.cpp b/src/profiling/profiler.cpp index b5d2ad0d8d7bd9efe7fd8e55e5f6720ab051b3f1..83c15d78fa7f4c5d6a076d4ff534ef77fc891a57 100644 --- a/src/profiling/profiler.cpp +++ b/src/profiling/profiler.cpp @@ -1,9 +1,9 @@ #include <numeric> -#include "profiler.hpp" -#include "util/debug.hpp" - +#include <common_types.hpp> #include <communication/global_policy.hpp> +#include <profiling/profiler.hpp> +#include <util/debug.hpp> namespace nest { namespace mc { @@ -66,7 +66,7 @@ void profiler_node::print_sub( if (print_children) { auto other = 0.; - for (auto &n : children) { + for (auto& n : children) { if (n.value<threshold || n.name=="other") { other += n.value; } @@ -162,7 +162,7 @@ double region_type::subregion_contributions() const { profiler_node region_type::populate_performance_tree() const { profiler_node tree(total(), name()); - for (auto &it : subregions_) { + for (auto& it : subregions_) { tree.children.push_back(it.second->populate_performance_tree()); } @@ -241,6 +241,17 @@ void profiler::stop() { deactivate(); } +void profiler::restart() { + if (!is_activated()) { + start(); + return; + } + deactivate(); + root_region_.clear(); + start(); +} + + profiler_node profiler::performance_tree() { if (is_activated()) { stop(); @@ -280,15 +291,22 @@ void profiler_leave(int nlevels) { get_profiler().leave(nlevels); } -// iterate over all profilers and ensure that they have the same start stop times -void stop_profilers() { +/// iterate over all profilers and ensure that they have the same start stop times +void profilers_stop() { for (auto& p : data::profilers_) { p.stop(); } } -void profiler_output(double threshold) { - stop_profilers(); +/// iterate over all profilers and reset +void profilers_restart() { + for (auto& p : data::profilers_) { + p.restart(); + } +} + +void profiler_output(double threshold, std::size_t num_local_work_items) { + profilers_stop(); // Find the earliest start time and latest stop time over all profilers // This can be used to calculate the wall time for this communicator. @@ -323,6 +341,11 @@ void profiler_output(double threshold) { auto ncomms = communication::global_policy::size(); auto comm_rank = communication::global_policy::id(); bool print = comm_rank==0 ? true : false; + + // calculate the throughput in terms of work items per second + auto local_throughput = num_local_work_items / wall_time; + auto global_throughput = communication::global_policy::sum(local_throughput); + if(print) { std::cout << " ---------------------------------------------------- \n"; std::cout << "| profiler |\n"; @@ -335,7 +358,6 @@ void profiler_output(double threshold) { std::snprintf( line, sizeof(line), "%-18s%10d\n", "communicators", int(ncomms)); - std::cout << line; std::snprintf( line, sizeof(line), "%-18s%10d\n", "threads", int(nthreads)); @@ -345,6 +367,15 @@ void profiler_output(double threshold) { "thread efficiency", float(efficiency)); std::cout << line << "\n"; p.print(std::cout, threshold); + std::cout << "\n"; + std::snprintf( + line, sizeof(line), "%-18s%10s%10s\n", + "", "local", "global"); + std::cout << line; + std::snprintf( + line, sizeof(line), "%-18s%10d%10d\n", + "throughput", int(local_throughput), int(global_throughput)); + std::cout << line; std::cout << "\n\n"; } @@ -354,6 +385,7 @@ void profiler_output(double threshold) { as_json["threads"] = nthreads; as_json["efficiency"] = efficiency; as_json["communicators"] = ncomms; + as_json["throughput"] = unsigned(local_throughput); as_json["rank"] = comm_rank; as_json["regions"] = p.as_json(); @@ -368,8 +400,9 @@ void profiler_stop() {} void profiler_enter(const char*) {} void profiler_leave() {} void profiler_leave(int) {} -void stop_profilers() {} -void profiler_output(double threshold) {} +void profilers_stop() {} +void profiler_output(double threshold, std::size_t num_local_work_items) {} +void profilers_restart() {}; #endif } // namespace util diff --git a/src/profiling/profiler.hpp b/src/profiling/profiler.hpp index 7230697baab21f914fa3b92f4ffbed4382b062b6..a905416c49d295706bed32b7e0149948fa8f47d6 100644 --- a/src/profiling/profiler.hpp +++ b/src/profiling/profiler.hpp @@ -121,6 +121,11 @@ public: bool has_subregions() const { return subregions_.size() > 0; } + void clear() { + subregions_.clear(); + start_time(); + } + size_t hash() const { return hash_; } region_type* subregion(const char* n); @@ -170,6 +175,10 @@ public: /// stop (deactivate) the profiler void stop(); + /// restart the profiler + /// remove all trace information and restart timer for the root region + void restart(); + /// the time stamp at which the profiler was started (avtivated) timer_type::time_point start_time() const { return start_time_; } @@ -226,15 +235,24 @@ void profiler_enter(const char* n, Args... args) { /// move up one level in the profiler void profiler_leave(); + /// move up multiple profiler levels in one call void profiler_leave(int nlevels); /// iterate and stop them -void stop_profilers(); +void profilers_stop(); + +/// reset profilers +void profilers_restart(); /// print the collated profiler to std::cout -void profiler_output(double threshold); +void profiler_output(double threshold, std::size_t num_local_work_items); } // namespace util } // namespace mc } // namespace nest + +// define some helper macros to make instrumentation of the source code with calls +// to the profiler a little less visually distracting +#define PE nest::mc::util::profiler_enter +#define PL nest::mc::util::profiler_leave diff --git a/src/recipe.hpp b/src/recipe.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b451a1a1cffbd2139707e8f8d8a60fa6e2f4e037 --- /dev/null +++ b/src/recipe.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include <cstddef> +#include <memory> +#include <stdexcept> + +namespace nest { +namespace mc { + +struct cell_count_info { + cell_size_type num_sources; + cell_size_type num_targets; + cell_size_type num_probes; +}; + +class invalid_recipe_error: public std::runtime_error { +public: + invalid_recipe_error(std::string whatstr): std::runtime_error(std::move(whatstr)) {} +}; + +/* Recipe descriptions are cell-oriented: in order that the building + * phase can be done distributedly and in order that the recipe + * description can be built indepdently of any runtime execution + * environment, connection end-points are represented by pairs + * (cell index, source/target index on cell). + */ + +using cell_connection_endpoint = cell_member_type; + +// Note: `cell_connection` and `connection` have essentially the same data +// and represent the same thing conceptually. `cell_connection` objects +// are notionally described in terms of external cell identifiers instead +// of internal gids, but we are not making the distinction between the +// two in the current code. These two types could well be merged. + +struct cell_connection { + cell_connection_endpoint source; + cell_connection_endpoint dest; + + float weight; + float delay; +}; + +class recipe { +public: + virtual cell_size_type num_cells() const =0; + + virtual cell get_cell(cell_gid_type) const =0; + virtual cell_count_info get_cell_count_info(cell_gid_type) const =0; + virtual std::vector<cell_connection> connections_on(cell_gid_type) const =0; +}; + +} // namespace mc +} // namespace nest diff --git a/src/segment.hpp b/src/segment.hpp index c6c49b26a946d36fdc969d9473fa07105d30d4af..248306a6c3a976d123b9d412b93c09156305086d 100644 --- a/src/segment.hpp +++ b/src/segment.hpp @@ -4,6 +4,7 @@ #include <vector> #include "algorithms.hpp" +#include "common_types.hpp" #include "compartment.hpp" #include "math.hpp" #include "parameter_list.hpp" @@ -36,6 +37,7 @@ class segment { public: using value_type = double; + using size_type = cell_local_size_type; using point_type = point<value_type>; segmentKind kind() const { @@ -57,8 +59,8 @@ class segment { return kind_==segmentKind::axon; } - virtual int num_compartments() const = 0; - virtual void set_compartments(int) = 0; + virtual size_type num_compartments() const = 0; + virtual void set_compartments(size_type) = 0; virtual value_type volume() const = 0; virtual value_type area() const = 0; @@ -153,13 +155,12 @@ class segment { std::vector<parameter_list> mechanisms_; }; -class placeholder_segment : public segment -{ - public: - +class placeholder_segment : public segment { +public: using base = segment; using base::kind_; using base::value_type; + using base::size_type; placeholder_segment() { @@ -181,23 +182,22 @@ class placeholder_segment : public segment return true; } - int num_compartments() const override + size_type num_compartments() const override { return 0; } - virtual void set_compartments(int) override + virtual void set_compartments(size_type) override { } }; -class soma_segment : public segment -{ - public : - +class soma_segment : public segment { +public: using base = segment; using base::kind_; using base::value_type; using base::point_type; + using base::size_type; soma_segment() = delete; @@ -208,7 +208,7 @@ class soma_segment : public segment mechanisms_.push_back(membrane_parameters()); } - soma_segment(value_type r, point_type const &c) + soma_segment(value_type r, point_type const& c) : soma_segment(r) { center_ = c; @@ -245,12 +245,12 @@ class soma_segment : public segment } /// soma has one and one only compartments - int num_compartments() const override + size_type num_compartments() const override { return 1; } - void set_compartments(int n) override + void set_compartments(size_type n) override { } private : @@ -261,10 +261,8 @@ class soma_segment : public segment point_type center_; }; -class cable_segment : public segment -{ - public : - +class cable_segment : public segment { +public: using base = segment; using base::kind_; using base::value_type; @@ -332,7 +330,7 @@ class cable_segment : public segment value_type volume() const override { auto sum = value_type{0}; - for(auto i=0; i<num_sub_segments(); ++i) { + for (auto i=0u; i<num_sub_segments(); ++i) { sum += math::volume_frustrum(lengths_[i], radii_[i], radii_[i+1]); } return sum; @@ -341,7 +339,7 @@ class cable_segment : public segment value_type area() const override { auto sum = value_type{0}; - for(auto i=0; i<num_sub_segments(); ++i) { + for (auto i=0u; i<num_sub_segments(); ++i) { sum += math::area_frustrum(lengths_[i], radii_[i], radii_[i+1]); } return sum; @@ -358,7 +356,7 @@ class cable_segment : public segment } // the number sub-segments that define the cable segment - int num_sub_segments() const + size_type num_sub_segments() const { return radii_.size()-1; } @@ -378,12 +376,12 @@ class cable_segment : public segment return this; } - int num_compartments() const override + size_type num_compartments() const override { return num_compartments_; } - void set_compartments(int n) override + void set_compartments(size_type n) override { if(n<1) { throw std::out_of_range( @@ -406,8 +404,8 @@ class cable_segment : public segment // we find ourselves having to do it over and over again. // The time to cache it might be when update_lengths() is called. auto sum = value_type(0); - auto i=0; - for(i=0; i<num_sub_segments(); ++i) { + size_type i = 0; + for (i = 0; i<num_sub_segments(); ++i) { if(sum+lengths_[i]>pos) { break; } @@ -425,19 +423,18 @@ class cable_segment : public segment return {num_compartments(), radii_.front(), radii_.back(), length()}; } - private : - +private: void update_lengths() { - if(locations_.size()) { + if (locations_.size()) { lengths_.resize(num_sub_segments()); - for(auto i=0; i<num_sub_segments(); ++i) { + for (size_type i=0; i<num_sub_segments(); ++i) { lengths_[i] = norm(locations_[i] - locations_[i+1]); } } } - int num_compartments_ = 1; + size_type num_compartments_ = 1; std::vector<value_type> lengths_; std::vector<value_type> radii_; std::vector<point_type> locations_; diff --git a/src/communication/spike.hpp b/src/spike.hpp similarity index 53% rename from src/communication/spike.hpp rename to src/spike.hpp index 03b5ed75def7a2fb68bd491ae7f1208d4b230b8a..d3ea02551456d2408712886dcff8125b314630e9 100644 --- a/src/communication/spike.hpp +++ b/src/spike.hpp @@ -1,48 +1,39 @@ #pragma once -#include <type_traits> #include <ostream> +#include <type_traits> namespace nest { namespace mc { -namespace communication { -template < - typename I, - typename = typename std::enable_if<std::is_integral<I>::value> -> +template <typename I, typename Time> struct spike { using id_type = I; - id_type source = 0; - float time = -1.; + using time_type = Time; + + id_type source = id_type{}; + time_type time = -1.; spike() = default; - spike(id_type s, float t) : + spike(id_type s, time_type t) : source(s), time(t) {} }; } // namespace mc } // namespace nest -} // namespace communication /// custom stream operator for printing nest::mc::spike<> values -template <typename I> -std::ostream& operator<<( - std::ostream& o, - nest::mc::communication::spike<I> s) -{ +template <typename I, typename T> +std::ostream& operator<<(std::ostream& o, nest::mc::spike<I, T> s) { return o << "spike[t " << s.time << ", src " << s.source << "]"; } /// less than comparison operator for nest::mc::spike<> values /// spikes are ordered by spike time, for use in sorting and queueing -template <typename I> -bool operator<( - nest::mc::communication::spike<I> lhs, - nest::mc::communication::spike<I> rhs) -{ +template <typename I, typename T> +bool operator<(nest::mc::spike<I, T> lhs, nest::mc::spike<I, T> rhs) { return lhs.time < rhs.time; } diff --git a/src/communication/spike_source.hpp b/src/spike_source.hpp similarity index 73% rename from src/communication/spike_source.hpp rename to src/spike_source.hpp index 95099cb547de24e76386c2253960b8fffe4bf569..fd043a3a6252410d89fd5d64c0d9d57816f14987 100644 --- a/src/communication/spike_source.hpp +++ b/src/spike_source.hpp @@ -8,23 +8,20 @@ namespace mc { // spike detector for a lowered cell template <typename Cell> -class spike_detector -{ +class spike_detector { public: using cell_type = Cell; - spike_detector( const cell_type& cell, segment_location loc, double thresh, float t_init) : - location_(loc), - threshold_(thresh), - previous_t_(t_init) + spike_detector(const cell_type& cell, typename Cell::detector_handle h, double thresh, float t_init) : + handle_(h), + threshold_(thresh) { - previous_v_ = cell.voltage(location_); - is_spiking_ = previous_v_ >= thresh ? true : false; + reset(cell, t_init); } util::optional<float> test(const cell_type& cell, float t) { util::optional<float> result = util::nothing; - auto v = cell.voltage(location_); + auto v = cell.detector_voltage(handle_); // these if statements could be simplified, but I keep them like // this to clearly reflect the finite state machine @@ -52,16 +49,19 @@ public: bool is_spiking() const { return is_spiking_; } - segment_location location() const { return location_; } - float t() const { return previous_t_; } float v() const { return previous_v_; } -private: + void reset(const cell_type& cell, float t_init) { + previous_t_ = t_init; + previous_v_ = cell.detector_voltage(handle_); + is_spiking_ = previous_v_ >= threshold_; + } +private: // parameters/data - segment_location location_; + typename cell_type::detector_handle handle_; double threshold_; // state diff --git a/src/stimulus.hpp b/src/stimulus.hpp index f7c7538cf398a7154588126620d97b7c054953a4..f3c587a412eb2aba5a0dc1c127c262b86ca018e7 100644 --- a/src/stimulus.hpp +++ b/src/stimulus.hpp @@ -45,9 +45,9 @@ class i_clamp { private: - value_type delay_ = 0; - value_type duration_ = 0; - value_type amplitude_ = 0; + value_type delay_ = 0; // [ms] + value_type duration_ = 0; // [ms] + value_type amplitude_ = 0; // [nA] }; } // namespace mc diff --git a/src/swcio.cpp b/src/swcio.cpp index 3f38f8e791308819ac2fe3eeecde4c78ab585799..db8001be40447864ce5c6c5974f274fb516afcf8 100644 --- a/src/swcio.cpp +++ b/src/swcio.cpp @@ -261,7 +261,7 @@ swc_record_range_clean::swc_record_range_clean(std::istream& is) parent_list.push_back(records_[i].parent()); } - if (!nest::mc::algorithms::has_contiguous_segments(parent_list)) { + if (!nest::mc::algorithms::has_contiguous_compartments(parent_list)) { throw swc_parse_error("branches are not contiguously numbered", 0); } } diff --git a/src/thread_private_spike_store.hpp b/src/thread_private_spike_store.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3f320fdd93709c622696e889f80c16588eb5ad66 --- /dev/null +++ b/src/thread_private_spike_store.hpp @@ -0,0 +1,87 @@ +#pragma once + +#include <vector> + +#include <common_types.hpp> +#include <spike.hpp> +#include <threading/threading.hpp> + +namespace nest { +namespace mc { + +/// Handles the complexity of managing thread private buffers of spikes. +/// Internally stores one thread private buffer of spikes for each hardware thread. +/// This can be accessed directly using the get() method, which returns a reference to +/// The thread private buffer of the calling thread. +/// The insert() and gather() methods add a vector of spikes to the buffer, +/// and collate all of the buffers into a single vector respectively. +template <typename Time> +class thread_private_spike_store { +public : + using id_type = cell_gid_type; + using time_type = Time; + using spike_type = spike<cell_member_type, time_type>; + + /// Collate all of the individual buffers into a single vector of spikes. + /// Does not modify the buffer contents. + std::vector<spike_type> gather() const { + std::vector<spike_type> spikes; + unsigned num_spikes = 0u; + for (auto& b : buffers_) { + num_spikes += b.size(); + } + spikes.reserve(num_spikes); + + for (auto& b : buffers_) { + spikes.insert(spikes.begin(), b.begin(), b.end()); + } + + return spikes; + } + + /// Return a reference to the thread private buffer of the calling thread + std::vector<spike_type>& get() { + return buffers_.local(); + } + + /// Return a reference to the thread private buffer of the calling thread + const std::vector<spike_type>& get() const { + return buffers_.local(); + } + + /// Clear all of the thread private buffers + void clear() { + for (auto& b : buffers_) { + b.clear(); + } + } + + /// Append the passed spikes to the end of the thread private buffer of the + /// calling thread + void insert(const std::vector<spike_type>& spikes) { + auto& buff = get(); + buff.insert(buff.end(), spikes.begin(), spikes.end()); + } + +private : + /// thread private storage for accumulating spikes + using local_spike_store_type = + threading::enumerable_thread_specific<std::vector<spike_type>>; + + local_spike_store_type buffers_; + +public : + using iterator = typename local_spike_store_type::iterator; + using const_iterator = typename local_spike_store_type::const_iterator; + + // make the container iterable + // we iterate of threads, not individual containers + + iterator begin() { return buffers_.begin(); } + iterator end() { return buffers_.begin(); } + const_iterator begin() const { return buffers_.begin(); } + const_iterator end() const { return buffers_.begin(); } +}; + +} // namespace mc +} // namespace nest diff --git a/src/threading/serial.hpp b/src/threading/serial.hpp index fb66e6e2ff78ec7eb638091ded2ac874583227e6..de9e3180271da68440b600918081e155fc34a1fb 100644 --- a/src/threading/serial.hpp +++ b/src/threading/serial.hpp @@ -4,9 +4,11 @@ #error "this header can only be loaded if WITH_SERIAL is set" #endif +#include <algorithm> #include <array> #include <chrono> #include <string> +#include <vector> namespace nest { namespace mc { @@ -19,7 +21,9 @@ template <typename T> class enumerable_thread_specific { std::array<T, 1> data; - public : +public : + using iterator = typename std::array<T, 1>::iterator; + using const_iterator = typename std::array<T, 1>::const_iterator; enumerable_thread_specific() = default; @@ -36,11 +40,14 @@ class enumerable_thread_specific { auto size() -> decltype(data.size()) const { return data.size(); } - auto begin() -> decltype(data.begin()) { return data.begin(); } - auto end() -> decltype(data.end()) { return data.end(); } + iterator begin() { return data.begin(); } + iterator end() { return data.end(); } - auto cbegin() -> decltype(data.cbegin()) const { return data.cbegin(); } - auto cend() -> decltype(data.cend()) const { return data.cend(); } + const_iterator begin() const { return data.begin(); } + const_iterator end() const { return data.end(); } + + const_iterator cbegin() const { return data.cbegin(); } + const_iterator cend() const { return data.cend(); } }; @@ -56,6 +63,24 @@ struct parallel_for { } }; +template <typename RandomIt> +void sort(RandomIt begin, RandomIt end) { + std::sort(begin, end); +} + +template <typename RandomIt, typename Compare> +void sort(RandomIt begin, RandomIt end, Compare comp) { + std::sort(begin, end, comp); +} + +template <typename Container> +void sort(Container& c) { + std::sort(c.begin(), c.end()); +} + +template <typename T> +using parallel_vector = std::vector<T>; + inline std::string description() { return "serial"; } @@ -78,6 +103,34 @@ struct timer { constexpr bool multithreaded() { return false; } +/// Proxy for tbb task group. +/// The tbb version launches tasks asynchronously, returning control to the +/// caller. The serial version implemented here simply runs the task, before +/// returning control, effectively serializing all asynchronous calls. +class task_group { +public: + task_group() = default; + + template<typename Func> + void run(const Func& f) { + f(); + } + + template<typename Func> + void run_and_wait(const Func& f) { + f(); + } + + void wait() + {} + + bool is_canceling() { + return false; + } + + void cancel() + {} +}; } // threading } // mc diff --git a/src/threading/tbb.hpp b/src/threading/tbb.hpp index eae6b112bdb75b697709379b41f00cfbc249a3b1..91a7b59b44ed6eaa8394da7fd5f4fa6586556a84 100644 --- a/src/threading/tbb.hpp +++ b/src/threading/tbb.hpp @@ -46,6 +46,26 @@ struct timer { constexpr bool multithreaded() { return true; } +template <typename T> +using parallel_vector = tbb::concurrent_vector<T>; + +using task_group = tbb::task_group; + +template <typename RandomIt> +void sort(RandomIt begin, RandomIt end) { + tbb::parallel_sort(begin, end); +} + +template <typename RandomIt, typename Compare> +void sort(RandomIt begin, RandomIt end, Compare comp) { + tbb::parallel_sort(begin, end, comp); +} + +template <typename Container> +void sort(Container& c) { + tbb::parallel_sort(c.begin(), c.end()); +} + } // threading } // mc } // nest diff --git a/src/tree.hpp b/src/tree.hpp index 98ff9aa43656f9d68b879297a6ffd5399348b6f0..cd59537411e90c7b5efa696ebe8d5baed9c5ea1a 100644 --- a/src/tree.hpp +++ b/src/tree.hpp @@ -12,16 +12,18 @@ namespace nest { namespace mc { +template <typename Int, typename Size = std::size_t> class tree { using range = memory::Range; - public : - - using int_type = int; +public: + using int_type = Int; + using size_type = Size; using index_type = memory::HostVector<int_type>; - using view_type = index_type::view_type; - using const_view_type = index_type::const_view_type; + using view_type = typename index_type::view_type; + using const_view_type = typename index_type::const_view_type; + static constexpr int_type no_parent = (int_type)-1; tree() = default; @@ -67,12 +69,12 @@ class tree { init(new_parent_index.size()); parents_(memory::all) = new_parent_index; - parents_[0] = -1; + parents_[0] = no_parent; child_index_(memory::all) = algorithms::make_index(algorithms::child_count(parents_)); - std::vector<int> pos(parents_.size(), 0); + std::vector<int_type> pos(parents_.size(), 0); for (auto i = 1u; i < parents_.size(); ++i) { auto p = parents_[i]; children_[child_index_[p] + pos[p]] = i; @@ -80,16 +82,18 @@ class tree { } } - size_t num_children() const { - return children_.size(); + size_type num_children() const { + return static_cast<size_type>(children_.size()); } - size_t num_children(size_t b) const { + + size_type num_children(size_t b) const { return child_index_[b+1] - child_index_[b]; } - size_t num_nodes() const { + + size_type num_nodes() const { // the number of nodes is the size of the child index minus 1 // ... except for the case of an empty tree - auto sz = child_index_.size(); + auto sz = static_cast<size_type>(child_index_.size()); return sz ? sz - 1 : 0; } @@ -104,7 +108,7 @@ class tree { } /// return the list of all children of branch b - const_view_type children(size_t b) const { + const_view_type children(size_type b) const { return children_(child_index_[b], child_index_[b+1]); } @@ -122,7 +126,7 @@ class tree { } /// memory used to store tree (in bytes) - size_t memory() const { + std::size_t memory() const { return sizeof(int_type)*data_.size() + sizeof(tree); } @@ -164,17 +168,16 @@ class tree { return p; } - private : - - void init(int nnode) { - auto nchild = nnode -1; +private: + void init(size_type nnode) { + auto nchild = nnode - 1; data_ = index_type(nchild + (nnode + 1) + nnode); set_ranges(nnode); } - void set_ranges(int nnode) { - if(nnode) { + void set_ranges(size_type nnode) { + if (nnode) { auto nchild = nnode - 1; // data_ is partitioned as follows: // data_ = [children_[nchild], child_index_[nnode+1], parents_[nnode]] @@ -215,17 +218,17 @@ class tree { /// new_node /// p : permutation vector, p[i] is the new index of node i in the old /// tree - int add_children( - int new_node, - int old_node, - int parent_node, + int_type add_children( + int_type new_node, + int_type old_node, + int_type parent_node, view_type p, tree const& old_tree ) { - // check for the senitel that indicates that the old root has + // check for the sentinel that indicates that the old root has // been processed - if(old_node==-1) { + if (old_node==no_parent) { return new_node; } @@ -237,7 +240,7 @@ class tree { auto this_node = new_node; auto pos = child_index_[this_node]; - auto add_parent_as_child = parent_node>=0 && old_node>0; + auto add_parent_as_child = parent_node!=no_parent && old_node>0; // // STEP 1 : add the child indexes for this_node // @@ -259,12 +262,12 @@ class tree { // STEP 2 : recursively add each child's children // new_node++; - for(auto b : old_children) { - if(b != parent_node) { - new_node = add_children(new_node, b, -1, p, old_tree); + for (auto b : old_children) { + if (b != parent_node) { + new_node = add_children(new_node, b, no_parent, p, old_tree); } } - if(add_parent_as_child) { + if (add_parent_as_child) { new_node = add_children( new_node, old_tree.parent(old_node), old_node, p, old_tree @@ -286,38 +289,38 @@ class tree { view_type parents_ = data_(0, 0); }; -template <typename C> -std::vector<int> make_parent_index(tree const& t, C const& counts) +template <typename IntT, typename SizeT, typename C> +std::vector<IntT> make_parent_index(tree<IntT, SizeT> const& t, C const& counts) { using range = memory::Range; + using int_type = typename tree<IntT, SizeT>::int_type; + constexpr auto no_parent = tree<IntT, SizeT>::no_parent; - if( !algorithms::is_positive(counts) - || counts.size() != t.num_nodes() ) - { + if (!algorithms::is_positive(counts) || counts.size() != t.num_nodes()) { throw std::domain_error( "make_parent_index requires one non-zero count per segment" ); } auto index = algorithms::make_index(counts); auto num_compartments = index.back(); - std::vector<int> parent_index(num_compartments); - auto pos = 0; - for(int i : range(0, t.num_nodes())) { + std::vector<int_type> parent_index(num_compartments); + int_type pos = 0; + for (int_type i : range(0, t.num_nodes())) { // get the parent of this segment // taking care for the case where the root node has -1 as its parent auto parent = t.parent(i); - parent = parent>=0 ? parent : 0; + parent = parent!=no_parent ? parent : 0; // the index of the first compartment in the segment // is calculated differently for the root (i.e when i==parent) - if(i!=parent) { + if (i!=parent) { parent_index[pos++] = index[parent+1]-1; } else { parent_index[pos++] = parent; } // number the remaining compartments in the segment consecutively - while(pos<index[i+1]) { + while (pos<index[i+1]) { parent_index[pos] = pos-1; pos++; } diff --git a/src/util.hpp b/src/util.hpp index 29a6b28a9ec09cce13fcced0db20ae30aa3d803f..243987e628685ee4d4414394b054bebc6e7646a0 100644 --- a/src/util.hpp +++ b/src/util.hpp @@ -18,7 +18,7 @@ using memory::util::cyan; template <typename T> std::ostream& -operator << (std::ostream &o, std::vector<T>const& v) +operator << (std::ostream& o, std::vector<T>const& v) { o << "["; for(auto const& i: v) { @@ -29,7 +29,7 @@ operator << (std::ostream &o, std::vector<T>const& v) } template <typename T> -std::ostream& print(std::ostream &o, std::vector<T>const& v) +std::ostream& print(std::ostream& o, std::vector<T>const& v) { o << "["; for(auto const& i: v) { diff --git a/src/util/counter.hpp b/src/util/counter.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d071752807f53fc72324367d4e4ea87e0dc6c7be --- /dev/null +++ b/src/util/counter.hpp @@ -0,0 +1,95 @@ +#pragma once + +/* Present an integral value as an iterator, for integral-range 'containers' */ + +#include <cstddef> +#include <iterator> +#include <type_traits> + +namespace nest { +namespace mc { +namespace util { + +template <typename V, typename = typename std::enable_if<std::is_integral<V>::value>::type> +struct counter { + using difference_type = V; + using value_type = V; + using pointer = const V*; + using reference = const V&; + using iterator_category = std::random_access_iterator_tag; + + counter(): v_{} {} + counter(V v): v_{v} {} + + counter(const counter&) = default; + counter(counter&&) = default; + + counter& operator++() { + ++v_; + return *this; + } + + counter operator++(int) { + counter c(*this); + ++v_; + return c; + } + + counter& operator--() { + --v_; + return *this; + } + + counter operator--(int) { + counter c(*this); + --v_; + return c; + } + + counter& operator+=(difference_type n) { + v_ += n; + return *this; + } + + counter& operator-=(difference_type n) { + v_ -= n; + return *this; + } + + counter operator+(difference_type n) { + return counter(v_+n); + } + + friend counter operator+(difference_type n, counter x) { + return counter(n+x.v_); + } + + counter operator-(difference_type n) { + return counter(v_-n); + } + + difference_type operator-(counter x) const { + return v_-x.v_; + } + + value_type operator*() const { return v_; } + + value_type operator[](difference_type n) const { return v_+n; } + + bool operator==(counter x) const { return v_==x.v_; } + bool operator!=(counter x) const { return v_!=x.v_; } + bool operator<=(counter x) const { return v_<=x.v_; } + bool operator>=(counter x) const { return v_>=x.v_; } + bool operator<(counter x) const { return v_<x.v_; } + bool operator>(counter x) const { return v_>x.v_; } + + counter& operator=(const counter&) = default; + counter& operator=(counter&&) = default; + +private: + V v_; +}; + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/debug.cpp b/src/util/debug.cpp index 51c646bbcb38b1722e40766f4886a957094bd1bb..f2f039708a860c7fcb9ec65db7345b608a019dd9 100644 --- a/src/util/debug.cpp +++ b/src/util/debug.cpp @@ -14,8 +14,11 @@ namespace util { std::mutex global_debug_cerr_mutex; -bool failed_assertion(const char* assertion, const char* file, - int line, const char* func) +bool abort_on_failed_assertion( + const char* assertion, + const char* file, + int line, + const char* func) { // Explicit flush, as we can't assume default buffering semantics on stderr/cerr, // and abort() might not flush streams. @@ -26,8 +29,13 @@ bool failed_assertion(const char* assertion, const char* file, return false; } -std::ostream& debug_emit_trace_leader(std::ostream& out, const char* file, - int line, const char* varlist) +failed_assertion_handler_t global_failed_assertion_handler = abort_on_failed_assertion; + +std::ostream& debug_emit_trace_leader( + std::ostream& out, + const char* file, + int line, + const char* varlist) { iosfmt_guard guard(out); diff --git a/src/util/debug.hpp b/src/util/debug.hpp index 1d0ef63346068876f8366a0d423a8887fe4a7470..be2b3d51e4d7bf9b5d4c5fb9b7c78855c5e4b4c5 100644 --- a/src/util/debug.hpp +++ b/src/util/debug.hpp @@ -10,8 +10,18 @@ namespace nest { namespace mc { namespace util { -bool failed_assertion(const char* assertion, const char* file, int line, const char* func); -std::ostream &debug_emit_trace_leader(std::ostream& out, const char* file, int line, const char* varlist); +using failed_assertion_handler_t = + bool (*)(const char* assertion, const char* file, int line, const char* func); + +bool abort_on_failed_assertion(const char* assertion, const char* file, int line, const char* func); +inline bool ignore_failed_assertion(const char*, const char*, int, const char*) { + return false; +} + +// defaults to abort_on_failed_assertion; +extern failed_assertion_handler_t global_failed_assertion_handler; + +std::ostream& debug_emit_trace_leader(std::ostream& out, const char* file, int line, const char* varlist); inline void debug_emit(std::ostream& out) { out << "\n"; @@ -66,7 +76,7 @@ void debug_emit_trace(const char* file, int line, const char* varlist, const Arg #define EXPECTS(condition) \ (void)((condition) || \ - nest::mc::util::failed_assertion(#condition, __FILE__, __LINE__, DEBUG_FUNCTION_NAME)) + nest::mc::util::global_failed_assertion_handler(#condition, __FILE__, __LINE__, DEBUG_FUNCTION_NAME)) #else #define EXPECTS(condition) #endif // def WITH_ASSERTIONS diff --git a/src/util/double_buffer.hpp b/src/util/double_buffer.hpp new file mode 100644 index 0000000000000000000000000000000000000000..31cffa1babef2ced0b843b4d3e375678612528b2 --- /dev/null +++ b/src/util/double_buffer.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include <array> +#include <atomic> + +#include <util/debug.hpp> + +namespace nest { +namespace mc { +namespace util { + +/// double buffer with thread safe exchange/flip operation. +template <typename T> +class double_buffer { +private: + std::atomic<int> index_; + std::array<T, 2> buffers_; + + int other_index() { + return index_ ? 0 : 1; + } + +public: + using value_type = T; + + double_buffer() : + index_(0) + {} + + /// remove the copy and move constructors which won't work with std::atomic + double_buffer(double_buffer&&) = delete; + double_buffer(const double_buffer&) = delete; + double_buffer& operator=(const double_buffer&) = delete; + double_buffer& operator=(double_buffer&&) = delete; + + /// flip the buffers in a thread safe manner + /// n calls to exchange will always result in n flips + void exchange() { + // use operator^= which is overloaded by std::atomic<> + index_ ^= 1; + } + + /// get the current/front buffer + value_type& get() { + return buffers_[index_]; + } + + /// get the current/front buffer + const value_type& get() const { + return buffers_[index_]; + } + + /// get the back buffer + value_type& other() { + return buffers_[other_index()]; + } + + /// get the back buffer + const value_type& other() const { + return buffers_[other_index()]; + } +}; + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/either.hpp b/src/util/either.hpp new file mode 100644 index 0000000000000000000000000000000000000000..36932c96e8be340638c98eb582d06ffa93450828 --- /dev/null +++ b/src/util/either.hpp @@ -0,0 +1,387 @@ +#pragma once + +/* + * A type-safe discriminated union of two members. + * + * Returns true in a bool context if the first of the two types holds a value. + */ + +#include <cstdlib> +#include <type_traits> +#include <stdexcept> +#include <utility> + +#include "util/meta.hpp" +#include "util/uninitialized.hpp" + +namespace nest { +namespace mc { +namespace util { + +struct either_invalid_access: std::runtime_error { + explicit either_invalid_access(const std::string& what_str) + : std::runtime_error(what_str) + {} + + either_invalid_access() + : std::runtime_error("access of unconstructed value in either") + {} +}; + +namespace detail { + template <typename A, typename B> + struct either_data { + union { + uninitialized<A> ua; + uninitialized<B> ub; + }; + + either_data() = default; + + either_data(const either_data&) = delete; + either_data(either_data&&) = delete; + either_data& operator=(const either_data&) = delete; + either_data& operator=(either_data&&) = delete; + }; + + template <std::size_t, typename A, typename B> struct either_select; + + template <typename A, typename B> + struct either_select<0, A, B> { + using type = uninitialized<A>; + static type& field(either_data<A, B>& data) { return data.ua; } + static const type& field(const either_data<A, B>& data) { return data.ua; } + }; + + template <typename A, typename B> + struct either_select<1, A, B> { + using type = uninitialized<B>; + static type& field(either_data<A, B>& data) { return data.ub; } + static const type& field(const either_data<A, B>& data) { return data.ub; } + }; + + template <std::size_t I, typename A, typename B> + struct either_get: either_select<I, A, B> { + using typename either_select<I, A, B>::type; + using either_select<I, A, B>::field; + + static typename type::reference unsafe_get(either_data<A, B>& data) { + return field(data).ref(); + } + + static typename type::const_reference unsafe_get(const either_data<A, B>& data) { + return field(data).cref(); + } + + static typename type::reference unsafe_get(char which, either_data<A, B>& data) { + if (I!=which) { + throw either_invalid_access(); + } + return field(data).ref(); + } + + static typename type::const_reference unsafe_get(char which, const either_data<A, B>& data) { + if (I!=which) { + throw either_invalid_access(); + } + return field(data).cref(); + } + + static typename type::pointer ptr(char which, either_data<A, B>& data) { + return I==which? field(data).ptr(): nullptr; + } + + static typename type::const_pointer ptr(char which, const either_data<A, B>& data) { + return I==which? field(data).cptr(): nullptr; + } + }; +} // namespace detail + +constexpr std::size_t variant_npos = -1; // emulating C++17 variant type + +template <typename A, typename B> +class either: public detail::either_data<A, B> { + using base = detail::either_data<A, B>; + using base::ua; + using base::ub; + + template <std::size_t I> + using getter = detail::either_get<I, A, B>; + + unsigned char which; + +public: + // default ctor if A is default-constructible or A is not and B is. + template < + typename A_ = A, + bool a_ = std::is_default_constructible<A_>::value, + bool b_ = std::is_default_constructible<B>::value, + typename = enable_if_t<a_ || (!a_ && b_)>, + std::size_t w_ = a_? 0: 1 + > + either() noexcept(std::is_nothrow_default_constructible<typename getter<w_>::type>::value): + which(w_) + { + getter<w_>::field(*this).construct(); + } + + // implicit constructors from A and B values by copy or move + either(const A& a) noexcept(std::is_nothrow_copy_constructible<A>::value): which(0) { + getter<0>::field(*this).construct(a); + } + + template < + typename B_ = B, + typename = enable_if_t<!std::is_same<A, B_>::value> + > + either(const B& b) noexcept(std::is_nothrow_copy_constructible<B>::value): which(1) { + getter<1>::field(*this).construct(b); + } + + either(A&& a) noexcept(std::is_nothrow_move_constructible<A>::value): which(0) { + getter<0>::field(*this).construct(std::move(a)); + } + + template < + typename B_ = B, + typename = enable_if_t<!std::is_same<A, B_>::value> + > + either(B&& b) noexcept(std::is_nothrow_move_constructible<B>::value): which(1) { + getter<1>::field(*this).construct(std::move(b)); + } + + // copy constructor + either(const either& x) + noexcept(std::is_nothrow_copy_constructible<A>::value && + std::is_nothrow_copy_constructible<B>::value): + which(x.which) + { + if (which==0) { + getter<0>::field(*this).construct(x.unsafe_get<0>()); + } + else if (which==1) { + getter<1>::field(*this).construct(x.unsafe_get<1>()); + } + } + + // move constructor + either(either&& x) + noexcept(std::is_nothrow_move_constructible<A>::value && + std::is_nothrow_move_constructible<B>::value): + which(x.which) + { + if (which==0) { + getter<0>::field(*this).construct(std::move(x.unsafe_get<0>())); + } + else if (which==1) { + getter<1>::field(*this).construct(std::move(x.unsafe_get<1>())); + } + } + + // copy assignment + either& operator=(const either& x) { + if (this==&x) { + return *this; + } + + switch (which) { + case 0: + if (x.which==0) { + unsafe_get<0>() = x.unsafe_get<0>(); + } + else { + if (x.which==1) { + B b_tmp(x.unsafe_get<1>()); + getter<0>::field(*this).destruct(); + which = (unsigned char)variant_npos; + getter<1>::field(*this).construct(std::move(b_tmp)); + which = 1; + } + else { + getter<0>::field(*this).destruct(); + which = (unsigned char)variant_npos; + } + } + break; + case 1: + if (x.which==1) { + unsafe_get<1>() = x.unsafe_get<1>(); + } + else { + if (x.which==0) { + A a_tmp(x.unsafe_get<0>()); + getter<1>::field(*this).destruct(); + which = (unsigned char)variant_npos; + getter<0>::field(*this).construct(std::move(a_tmp)); + which = 0; + } + else { + getter<1>::field(*this).destruct(); + which = (unsigned char)variant_npos; + } + } + break; + default: // variant_npos + if (x.which==0) { + getter<0>::field(*this).construct(x.unsafe_get<0>()); + } + else if (x.which==1) { + getter<1>::field(*this).construct(x.unsafe_get<1>()); + } + break; + } + return *this; + } + + // move assignment + either& operator=(either&& x) { + if (this==&x) { + return *this; + } + + switch (which) { + case 0: + if (x.which==0) { + unsafe_get<0>() = std::move(x.unsafe_get<0>()); + } + else { + which = (unsigned char)variant_npos; + getter<0>::field(*this).destruct(); + if (x.which==1) { + getter<1>::field(*this).construct(std::move(x.unsafe_get<1>())); + which = 1; + } + } + break; + case 1: + if (x.which==1) { + unsafe_get<1>() = std::move(x.unsafe_get<1>()); + } + else { + which = (unsigned char)variant_npos; + getter<1>::field(*this).destruct(); + if (x.which==0) { + getter<0>::field(*this).construct(std::move(x.unsafe_get<0>())); + which = 0; + } + } + break; + default: // variant_npos + if (x.which==0) { + getter<0>::field(*this).construct(std::move(x.unsafe_get<0>())); + } + else if (x.which==1) { + getter<1>::field(*this).construct(std::move(x.unsafe_get<1>())); + } + break; + } + return *this; + } + + // unchecked element access + template <std::size_t I> + typename getter<I>::type::reference unsafe_get() { + return getter<I>::unsafe_get(*this); + } + + template <std::size_t I> + typename getter<I>::type::const_reference unsafe_get() const { + return getter<I>::unsafe_get(*this); + } + + // checked element access + template <std::size_t I> + typename getter<I>::type::reference get() { + return getter<I>::unsafe_get(which, *this); + } + + template <std::size_t I> + typename getter<I>::type::const_reference get() const { + return getter<I>::unsafe_get(which, *this); + } + + // convenience getter aliases + typename getter<0>::type::reference first() { return get<0>(); } + typename getter<0>::type::const_reference first() const { return get<0>(); } + + typename getter<1>::type::reference second() { return get<1>(); } + typename getter<1>::type::const_reference second() const { return get<1>(); } + + // pointer to element access: return nullptr if it does not hold this item + template <std::size_t I> + auto ptr() -> decltype(getter<I>::ptr(which, *this)) { + return getter<I>::ptr(which, *this); + } + + template <std::size_t I> + auto ptr() const -> decltype(getter<I>::ptr(which, *this)) { + return getter<I>::ptr(which, *this); + } + + // true in bool context if holds first alternative + constexpr operator bool() const { return which==0; } + + constexpr bool valueless_by_exception() const noexcept { + return which==(unsigned char)variant_npos; + } + + constexpr std::size_t index() const noexcept { + return which; + } + + ~either() { + if (which==0) { + getter<0>::field(*this).destruct(); + } + else if (which==1) { + getter<1>::field(*this).destruct(); + } + } + + // comparison operators follow C++17 variant semantics + bool operator==(const either& x) const { + return index()==x.index() && + index()==0? unsafe_get<0>()==x.unsafe_get<0>(): + index()==1? unsafe_get<1>()==x.unsafe_get<1>(): + true; + } + + bool operator!=(const either& x) const { + return index()!=x.index() || + index()==0? unsafe_get<0>()!=x.unsafe_get<0>(): + index()==1? unsafe_get<1>()!=x.unsafe_get<1>(): + false; + } + + bool operator<(const either& x) const { + return !x.valueless_by_exception() && + index()==0? (x.index()==1 || unsafe_get<0>()<x.unsafe_get<0>()): + index()==1? (x.index()!=0 && unsafe_get<1>()<x.unsafe_get<1>()): + true; + } + + bool operator>=(const either& x) const { + return x.valueless_by_exception() || + index()==0? (x.index()!=1 && unsafe_get<0>()>=x.unsafe_get<0>()): + index()==1? (x.index()==0 || unsafe_get<1>()>=x.unsafe_get<1>()): + false; + } + + bool operator<=(const either& x) const { + return valueless_by_exception() || + x.index()==0? (index()!=1 && unsafe_get<0>()<=x.unsafe_get<0>()): + x.index()==1? (index()==0 || unsafe_get<1>()<=x.unsafe_get<1>()): + false; + } + + bool operator>(const either& x) const { + return !valueless_by_exception() && + x.index()==0? (index()==1 || unsafe_get<0>()>x.unsafe_get<0>()): + x.index()==1? (index()!=0 && unsafe_get<1>()>x.unsafe_get<1>()): + true; + } +}; + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/ioutil.hpp b/src/util/ioutil.hpp index da73bd800e48c66364b0fd884e643a15797f1c8f..2feab394df71d730bde904d041ebbeeb85bf9a44 100644 --- a/src/util/ioutil.hpp +++ b/src/util/ioutil.hpp @@ -1,6 +1,6 @@ #pragma once -#include <ios> +#include <iostream> namespace nest { namespace mc { @@ -24,6 +24,75 @@ private: }; +template <typename charT, typename traitsT = std::char_traits<charT> > +class basic_null_streambuf: public std::basic_streambuf<charT, traitsT> { +private: + typedef typename std::basic_streambuf<charT, traitsT> streambuf_type; + +public: + typedef typename streambuf_type::char_type char_type; + typedef typename streambuf_type::int_type int_type; + typedef typename streambuf_type::pos_type pos_type; + typedef typename streambuf_type::off_type off_type; + typedef typename streambuf_type::traits_type traits_type; + + virtual ~basic_null_streambuf() {} + +protected: + std::streamsize xsputn(const char_type* s, std::streamsize count) override { + return count; + } + + int_type overflow(int_type c) override { + return traits_type::not_eof(c); + } +}; + +class mask_stream { +public: + explicit mask_stream(bool mask): mask_(mask) {} + + operator bool() const { return mask_; } + + template <typename charT, typename traitsT> + friend std::basic_ostream<charT, traitsT>& + operator<<(std::basic_ostream<charT, traitsT>& O, const mask_stream& F) { + int xindex = get_xindex(); + + std::basic_streambuf<charT, traitsT>* saved_streambuf = + static_cast<std::basic_streambuf<charT, traitsT>*>(O.pword(xindex)); + + if (F.mask_ && saved_streambuf) { + // re-enable by restoring saved streambuf + O.pword(xindex) = 0; + O.rdbuf(saved_streambuf); + } + else if (!F.mask_ && !saved_streambuf) { + // disable stream but save old streambuf + O.pword(xindex) = O.rdbuf(); + O.rdbuf(get_null_streambuf<charT, traitsT>()); + } + + return O; + } + +private: + // our key for retrieve saved streambufs. + static int get_xindex() { + static int xindex = std::ios_base::xalloc(); + return xindex; + } + + template <typename charT, typename traitsT> + static std::basic_streambuf<charT, traitsT>* get_null_streambuf() { + static basic_null_streambuf<charT, traitsT> the_null_streambuf; + return &the_null_streambuf; + } + + // true => do not filter + bool mask_; +}; + } // namespace util } // namespace mc } // namespace nest diff --git a/src/util/iterutil.hpp b/src/util/iterutil.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6a9fd159ffe9f75eddf57f2adb5c4d42cb9e7861 --- /dev/null +++ b/src/util/iterutil.hpp @@ -0,0 +1,178 @@ +#pragma once + +/* + * Utilities and base classes to help with + * implementing iterators and iterator adaptors. + */ + +#include <iterator> +#include <memory> +#include <type_traits> +#include <utility> + +#include <util/meta.hpp> + +namespace nest { +namespace mc { +namespace util { + +/* + * Return the iterator reachable from iter such that + * std::next(iter)==end + * + * Two implementations: the first applies generally, while the + * second is used when we can just return std::prev(end). + */ +template <typename I, typename E> +enable_if_t< + is_forward_iterator<I>::value && + (!is_bidirectional_iterator<E>::value || !std::is_constructible<I, E>::value), + I> +upto(I iter, E end) { + I j = iter; + while (j!=end) { + iter = j; + ++j; + } + return iter; +} + +template <typename I, typename E> +enable_if_t<is_bidirectional_iterator<E>::value && std::is_constructible<I, E>::value, I> +upto(I iter, E end) { + return iter==I{end}? iter: I{std::prev(end)}; +} + +/* + * Provide a proxy object for operator->() for iterator adaptors that + * present rvalues on dereference. + */ +template <typename V> +struct pointer_proxy: public V { + pointer_proxy(const V& v): V{v} {} + pointer_proxy(V&& v): V{std::move(v)} {} + const V* operator->() const { return this; } +}; + +/* + * Base class (using CRTP) for iterator adaptors that + * perform a transformation or present a proxy for + * an underlying iterator. + * + * Supplies default implementations for iterator concepts + * in terms of the derived class' methods and the + * inner iterator. + * + * Derived class must provide implementations for: + * operator*() + * operator[](difference_type) + * inner() // provides access to wrapped iterator + */ + +template <typename Derived, typename I> +class iterator_adaptor { +protected: + Derived& derived() { return static_cast<Derived&>(*this); } + const Derived& derived() const { return static_cast<const Derived&>(*this); } + +private: + // Access to inner iterator provided by derived class. + I& inner() { return derived().inner(); } + const I& inner() const { return derived().inner(); } + +public: + using value_type = typename std::iterator_traits<I>::value_type; + using difference_type = typename std::iterator_traits<I>::difference_type; + using iterator_category = typename std::iterator_traits<I>::iterator_category; + using pointer = typename std::iterator_traits<I>::pointer; + using reference = typename std::iterator_traits<I>::reference; + + iterator_adaptor() = default; + + // forward and input iterator requirements + + I operator->() { return inner(); } + I operator->() const { return inner(); } + + Derived& operator++() { + ++inner(); + return derived(); + } + + Derived operator++(int) { + Derived c(derived()); + ++derived(); + return c; + } + + bool operator==(const Derived& x) const { + return inner()==x.inner(); + } + + bool operator!=(const Derived& x) const { + return !(derived()==x); + } + + // bidirectional iterator requirements + + Derived& operator--() { + --inner(); + return derived(); + } + + Derived operator--(int) { + Derived c(derived()); + --derived(); + return c; + } + + // random access iterator requirements + + Derived& operator+=(difference_type n) { + inner() += n; + return derived(); + } + + Derived operator+(difference_type n) const { + Derived c(derived()); + return c += n; + } + + friend Derived operator+(difference_type n, const Derived& x) { + return x+n; + } + + Derived& operator-=(difference_type n) { + inner() -= n; + return *this; + } + + Derived operator-(difference_type n) const { + Derived c(derived()); + return c -= n; + } + + difference_type operator-(const Derived& x) const { + return inner()-x.inner(); + } + + bool operator<(const Derived& x) const { + return inner()<x.inner(); + } + + bool operator<=(const Derived& x) const { + return derived()<x || derived()==x; + } + + bool operator>=(const Derived& x) const { + return !(derived()<x); + } + + bool operator>(const Derived& x) const { + return !(derived()<=x); + } +}; + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/lexcmp_def.hpp b/src/util/lexcmp_def.hpp new file mode 100644 index 0000000000000000000000000000000000000000..cfb297b1d16d3dbd7023bb7f300271eda0af1e41 --- /dev/null +++ b/src/util/lexcmp_def.hpp @@ -0,0 +1,40 @@ +#pragma once + +/* + * Macro definitions for defining comparison operators for + * record-like types. + * + * Use: + * + * To define comparison operations for a record type xyzzy + * with fields foo, bar and baz: + * + * DEFINE_LEXICOGRAPHIC_ORDERING(xyzzy,(a.foo,a.bar,a.baz),(b.foo,b.bar,b.baz)) + * + * The explicit use of 'a' and 'b' in the second and third parameters + * is needed only to save a heroic amount of preprocessor macro + * deep magic. + * + */ + +#include <tuple> + +#define DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(proxy,op,type,a_fields,b_fields) \ +inline bool operator op(const type& a,const type& b) { return proxy a_fields op proxy b_fields; } + +#define DEFINE_LEXICOGRAPHIC_ORDERING(type,a_fields,b_fields) \ +DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::tie,<,type,a_fields,b_fields) \ +DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::tie,>,type,a_fields,b_fields) \ +DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::tie,<=,type,a_fields,b_fields) \ +DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::tie,>=,type,a_fields,b_fields) \ +DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::tie,!=,type,a_fields,b_fields) \ +DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::tie,==,type,a_fields,b_fields) + +#define DEFINE_LEXICOGRAPHIC_ORDERING_BY_VALUE(type,a_fields,b_fields) \ +DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::make_tuple,<,type,a_fields,b_fields) \ +DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::make_tuple,>,type,a_fields,b_fields) \ +DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::make_tuple,<=,type,a_fields,b_fields) \ +DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::make_tuple,>=,type,a_fields,b_fields) \ +DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::make_tuple,!=,type,a_fields,b_fields) \ +DEFINE_LEXICOGRAPHIC_ORDERING_IMPL_(std::make_tuple,==,type,a_fields,b_fields) + diff --git a/src/util/meta.hpp b/src/util/meta.hpp index 2e22e31ceda0ff83588625486ec7a36161569733..40f08d481f23199e8e06a47fc41acdd58fdab8c9 100644 --- a/src/util/meta.hpp +++ b/src/util/meta.hpp @@ -2,6 +2,8 @@ /* Type utilities and convenience expressions. */ +#include <cstddef> +#include <iterator> #include <type_traits> namespace nest { @@ -13,8 +15,45 @@ namespace util { template <typename T> using result_of_t = typename std::result_of<T>::type; -template <bool V> -using enable_if_t = typename std::enable_if<V>::type; +template <bool V, typename R = void> +using enable_if_t = typename std::enable_if<V, R>::type; + +template <class...> +using void_t = void; + +template <typename T> +using decay_t = typename std::decay<T>::type; + +template <typename X> +std::size_t size(const X& x) { return x.size(); } + +template <typename X, std::size_t N> +constexpr std::size_t size(X (&)[N]) { return N; } + +template <typename T> +constexpr auto cbegin(const T& c) -> decltype(std::begin(c)) { + return std::begin(c); +} + +template <typename T> +constexpr auto cend(const T& c) -> decltype(std::end(c)) { + return std::end(c); +} + +// Types associated with a container or sequence + +template <typename Seq> +struct sequence_traits { + using iterator = decltype(std::begin(std::declval<Seq&>())); + using const_iterator = decltype(cbegin(std::declval<Seq&>())); + using value_type = typename std::iterator_traits<iterator>::value_type; + using reference = typename std::iterator_traits<iterator>::reference; + using difference_type = typename std::iterator_traits<iterator>::difference_type; + using size_type = decltype(size(std::declval<Seq&>())); + // for use with heterogeneous ranges + using sentinel = decltype(std::end(std::declval<Seq&>())); + using const_sentinel = decltype(cend(std::declval<Seq&>())); +}; // Convenience short cuts @@ -26,10 +65,91 @@ template <typename T> using enable_if_move_constructible_t = enable_if_t<std::is_move_constructible<T>::value>; +template <typename T> +using enable_if_default_constructible_t = + enable_if_t<std::is_default_constructible<T>::value>; + template <typename... T> using enable_if_constructible_t = enable_if_t<std::is_constructible<T...>::value>; +template <typename T> +using enable_if_copy_assignable_t = + enable_if_t<std::is_copy_assignable<T>::value>; + +template <typename T> +using enable_if_move_assignable_t = + enable_if_t<std::is_move_assignable<T>::value>; + +// Iterator class test +// (might not be portable before C++17) + +template <typename T, typename = void> +struct is_iterator: public std::false_type {}; + +template <typename T> +struct is_iterator<T, void_t<typename std::iterator_traits<T>::iterator_category>>: + public std::true_type {}; + +template <typename T> +using is_iterator_t = typename is_iterator<T>::type; + +// Random access iterator test + +template <typename T, typename = void> +struct is_random_access_iterator: public std::false_type {}; + +template <typename T> +struct is_random_access_iterator<T, enable_if_t< + std::is_same< + std::random_access_iterator_tag, + typename std::iterator_traits<T>::iterator_category>::value + >> : public std::true_type {}; + +template <typename T> +using is_random_access_iterator_t = typename is_random_access_iterator<T>::type; + +// Bidirectional iterator test + +template <typename T, typename = void> +struct is_bidirectional_iterator: public std::false_type {}; + +template <typename T> +struct is_bidirectional_iterator<T, enable_if_t< + std::is_same< + std::random_access_iterator_tag, + typename std::iterator_traits<T>::iterator_category>::value + || + std::is_same< + std::bidirectional_iterator_tag, + typename std::iterator_traits<T>::iterator_category>::value + >> : public std::true_type {}; + +template <typename T> +using is_bidirectional_iterator_t = typename is_bidirectional_iterator<T>::type; + +// Forward iterator test + +template <typename T, typename = void> +struct is_forward_iterator: public std::false_type {}; + +template <typename T> +struct is_forward_iterator<T, enable_if_t< + std::is_same< + std::random_access_iterator_tag, + typename std::iterator_traits<T>::iterator_category>::value + || + std::is_same< + std::bidirectional_iterator_tag, + typename std::iterator_traits<T>::iterator_category>::value + || + std::is_same< + std::forward_iterator_tag, + typename std::iterator_traits<T>::iterator_category>::value + >> : public std::true_type {}; + +template <typename T> +using is_forward_iterator_t = typename is_forward_iterator<T>::type; } // namespace util } // namespace mc diff --git a/src/util/nop.hpp b/src/util/nop.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6ec568b9d5fe9bfe4904b23ea6babcdf8b4ca84e --- /dev/null +++ b/src/util/nop.hpp @@ -0,0 +1,35 @@ +#pragma once + +/* + * Provide object that implicitly converts to + * a std::function object that does nothing but return a + * default-constructed type or void. + */ + +#include <functional> + +namespace nest { +namespace mc { +namespace util { + +struct nop_function_t { + template <typename R, typename... Args> + operator std::function<R (Args...)>() const { + return [](Args...) { return R{}; }; + } + + template <typename... Args> + operator std::function<void (Args...)>() const { + return [](Args...) { }; + } + + // keep clang happy: see CWG issue #253, + // http://open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#253 + constexpr nop_function_t() {} +}; + +static constexpr nop_function_t nop_function; + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/optional.hpp b/src/util/optional.hpp index 92191dfff7adf9af084a2984b19210565da761e4..40b40d7c3ff27352638c789575172a5cd5fee8d4 100644 --- a/src/util/optional.hpp +++ b/src/util/optional.hpp @@ -29,7 +29,7 @@ namespace util { template <typename X> struct optional; struct optional_unset_error: std::runtime_error { - explicit optional_unset_error(const std::string &what_str) + explicit optional_unset_error(const std::string& what_str) : std::runtime_error(what_str) {} @@ -38,16 +38,6 @@ struct optional_unset_error: std::runtime_error { {} }; -struct optional_invalid_dereference: std::runtime_error { - explicit optional_invalid_dereference(const std::string &what_str) - : std::runtime_error(what_str) - {} - - optional_invalid_dereference() - : std::runtime_error("derefernce of optional<void> value") - {} -}; - struct nothing_t {}; constexpr nothing_t nothing{}; @@ -68,21 +58,21 @@ namespace detail { struct optional_tag {}; template <typename X> - using is_optional = std::is_base_of<optional_tag, typename std::decay<X>::type>; + using is_optional = std::is_base_of<optional_tag, decay_t<X>>; - template <typename D,typename X> + template <typename D, typename X> struct wrapped_type_impl { using type = X; }; - template <typename D,typename X> - struct wrapped_type_impl<optional<D>,X> { + template <typename D, typename X> + struct wrapped_type_impl<optional<D>, X> { using type = D; }; template <typename X> struct wrapped_type { - using type = typename wrapped_type_impl<typename std::decay<X>::type,X>::type; + using type = typename wrapped_type_impl<decay_t<X>, X>::type; }; template <typename X> @@ -96,10 +86,10 @@ namespace detail { using data_type = util::uninitialized<X>; public: - using reference_type = typename data_type::reference_type; - using const_reference_type = typename data_type::const_reference_type; - using pointer_type = typename data_type::pointer_type; - using const_pointer_type = typename data_type::const_pointer_type; + using reference = typename data_type::reference; + using const_reference = typename data_type::const_reference; + using pointer = typename data_type::pointer; + using const_pointer = typename data_type::const_pointer; protected: bool set; @@ -114,8 +104,8 @@ namespace detail { } } - reference_type ref() { return data.ref(); } - const_reference_type ref() const { return data.cref(); } + reference ref() { return data.ref(); } + const_reference ref() const { return data.cref(); } public: ~optional_base() { @@ -124,28 +114,24 @@ namespace detail { } } - const_pointer_type operator->() const { return data.ptr(); } - pointer_type operator->() { return data.ptr(); } + pointer operator->() { return data.ptr(); } + const_pointer operator->() const { return data.ptr(); } - const_reference_type operator*() const { return ref(); } - reference_type operator*() { return ref(); } + reference operator*() { return ref(); } + const_reference operator*() const { return ref(); } - reference_type get() { - if (set) { - return ref(); - } - else { + reference get() { + if (!set) { throw optional_unset_error(); } + return ref(); } - const_reference_type get() const { - if (set) { - return ref(); - } - else { + const_reference get() const { + if (!set) { throw optional_unset_error(); } + return ref(); } explicit operator bool() const { return set; } @@ -206,16 +192,16 @@ namespace detail { private: template <typename R, bool F_void_return> struct bind_impl { - template <typename DT,typename F> - static R bind(DT& d,F&& f) { + template <typename DT, typename F> + static R bind(DT& d, F&& f) { return R(d.apply(std::forward<F>(f))); } }; template <typename R> - struct bind_impl<R,true> { - template <typename DT,typename F> - static R bind(DT& d,F&& f) { + struct bind_impl<R, true> { + template <typename DT, typename F> + static R bind(DT& d, F&& f) { d.apply(std::forward<F>(f)); return R(true); } @@ -243,30 +229,32 @@ struct optional: detail::optional_base<X> { using base::reset; using base::data; - optional(): base() {} - optional(nothing_t): base() {} + optional() noexcept: base() {} + optional(nothing_t) noexcept: base() {} - template < - typename Y = X, - typename = enable_if_copy_constructible_t<Y> - > - optional(const X& x): base(true, x) {} + optional(const X& x) + noexcept(std::is_nothrow_copy_constructible<X>::value): base(true, x) {} - template < - typename Y = X, - typename = enable_if_move_constructible_t<Y> - > - optional(X&& x): base(true, std::move(x)) {} + optional(X&& x) + noexcept(std::is_nothrow_move_constructible<X>::value): base(true, std::move(x)) {} optional(const optional& ot): base(ot.set, ot.ref()) {} template <typename T> - optional(const optional<T>& ot): base(ot.set, ot.ref()) {} + optional(const optional<T>& ot) + noexcept(std::is_nothrow_constructible<X, T>::value): base(ot.set, ot.ref()) {} - optional(optional&& ot): base(ot.set, std::move(ot.ref())) {} + optional(optional&& ot) + noexcept(std::is_nothrow_move_constructible<X>::value): base(ot.set, std::move(ot.ref())) {} template <typename T> - optional(optional<T>&& ot): base(ot.set, std::move(ot.ref())) {} + optional(optional<T>&& ot) + noexcept(std::is_nothrow_constructible<X, T&&>::value): base(ot.set, std::move(ot.ref())) {} + + optional& operator=(nothing_t) { + reset(); + return *this; + } template < typename Y, @@ -331,13 +319,19 @@ struct optional<X&>: detail::optional_base<X&> { using base::set; using base::ref; using base::data; + using base::reset; - optional(): base() {} - optional(nothing_t): base() {} - optional(X&x): base(true,x) {} + optional() noexcept: base() {} + optional(nothing_t) noexcept: base() {} + optional(X& x) noexcept: base(true, x) {} template <typename T> - optional(optional<T&>& ot): base(ot.set,ot.ref()) {} + optional(optional<T&>& ot) noexcept: base(ot.set, ot.ref()) {} + + optional& operator=(nothing_t) { + reset(); + return *this; + } template <typename Y> optional& operator=(Y& y) { @@ -364,14 +358,20 @@ template <> struct optional<void>: detail::optional_base<void> { using base = detail::optional_base<void>; using base::set; + using base::reset; optional(): base() {} template <typename T> - optional(T): base(true,true) {} + optional(T): base(true, true) {} template <typename T> - optional(const optional<T>& o): base(o.set,true) {} + optional(const optional<T>& o): base(o.set, true) {} + + optional& operator=(nothing_t) { + reset(); + return *this; + } template <typename T> optional& operator=(T) { @@ -394,7 +394,7 @@ struct optional<void>: detail::optional_base<void> { }; -template <typename A,typename B> +template <typename A, typename B> typename std::enable_if< detail::is_optional<A>::value || detail::is_optional<B>::value, optional< @@ -404,16 +404,16 @@ typename std::enable_if< >::type > >::type -operator|(A&& a,B&& b) { +operator|(A&& a, B&& b) { return detail::decay_bool(a) ? a : b; } -template <typename A,typename B> +template <typename A, typename B> typename std::enable_if< detail::is_optional<A>::value || detail::is_optional<B>::value, optional<detail::wrapped_type_t<B>> >::type -operator&(A&& a,B&& b) { +operator&(A&& a, B&& b) { using result_type = optional<detail::wrapped_type_t<B>>; return a ? b: result_type(); } diff --git a/src/util/partition.hpp b/src/util/partition.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b35f81de8fa1f40217e331dd96e09a11eba1c174 --- /dev/null +++ b/src/util/partition.hpp @@ -0,0 +1,165 @@ +#pragma once + +#include <iterator> +#include <stdexcept> +#include <type_traits> + +#include <util/either.hpp> +#include <util/meta.hpp> +#include <util/partition_iterator.hpp> +#include <util/range.hpp> + +namespace nest { +namespace mc { +namespace util { + +struct invalid_partition: std::runtime_error { + explicit invalid_partition(const std::string& what): std::runtime_error(what) {} + explicit invalid_partition(const char* what): std::runtime_error(what) {} +}; + +/* + * Present a sequence with monotically increasing values as a partition, + */ +template <typename I> +class partition_range: public range<partition_iterator<I>> { + using base = range<partition_iterator<I>>; + using inner_value_type = typename std::iterator_traits<I>::value_type; + +public: + using typename base::iterator; + using typename base::value_type; + using base::left; + using base::right; + using base::front; + using base::back; + using base::empty; + + template <typename Seq> + partition_range(const Seq& s): base{std::begin(s), upto(std::begin(s), std::end(s))} { + EXPECTS(is_valid()); + } + + // explicitly check invariants + void validate() const { + auto ok = is_valid(); + if (!ok) { + throw invalid_partition(ok.second()); + } + } + + // find half-open sub-interval containing x + iterator find(const inner_value_type& x) const { + if (empty()) { + return right; + } + + auto divs = divisions(); + auto i = std::upper_bound(divs.left, divs.right, x); + if (i==divs.left || i==divs.right) { + return right; + } + return iterator{std::prev(i)}; + } + + // access to underlying divisions + range<I> divisions() const { + return {left.get(), std::next(right.get())}; + } + + // global upper and lower bounds of partition + value_type bounds() const { + return {front().first, back().second}; + } + +private: + either<bool, std::string> is_valid() const { + if (!std::is_sorted(left.get(), right.get())) { + return std::string("offsets are not monotonically increasing"); + } + else { + return true; + } + } +}; + + +template < + typename Seq, + typename SeqIter = typename sequence_traits<Seq>::const_iterator, + typename = enable_if_t<is_forward_iterator<SeqIter>::value> +> +partition_range<SeqIter> partition_view(const Seq& r) { + return partition_range<SeqIter>(r); +} + +/* + * Construct a monotonically increasing sequence in a provided + * container representing a partition from a sequence of subset sizes. + * + * If the first parameter is `partition_in_place`, the provided + * container `divisions` will not be resized, and the partition will + * be of length `util::size(divisions)-1` or zero if `divisions` is + * empty. + * + * Otherwise, `divisions` will be be resized to `util::size(sizes)+1` + * and represent a partition of length `util::size(sizes)`. + * + * Returns a partition view over `divisions`. + */ + +struct partition_in_place_t { + constexpr partition_in_place_t() {} +}; + +constexpr partition_in_place_t partition_in_place; + +template < + typename Part, + typename Sizes, + typename T = typename sequence_traits<Part>::value_type +> +partition_range<typename sequence_traits<Part>::const_iterator> +make_partition(partition_in_place_t, Part& divisions, const Sizes& sizes, T from=T{}) { + auto pi = std::begin(divisions); + auto pe = std::end(divisions); + auto si = std::begin(sizes); + auto se = std::end(sizes); + + if (pi!=pe) { + *pi++ = from; + while (pi!=pe && si!=se) { + from += *si++; + *pi++ = from; + } + while (pi!=pe) { + *pi++ = from; + } + } + return partition_view(divisions); +} + +template < + typename Part, + typename Sizes, + typename T = typename sequence_traits<Part>::value_type +> +partition_range<typename sequence_traits<Part>::const_iterator> +make_partition(Part& divisions, const Sizes& sizes, T from=T{}) { + divisions.resize(size(sizes)+1); + + // (would use std::inclusive_scan in C++17) + auto pi = std::begin(divisions); + for (const auto& s: sizes) { + *pi++ = from; + from += s; + } + *pi = from; + + return partition_view(divisions); +} + + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/partition_iterator.hpp b/src/util/partition_iterator.hpp new file mode 100644 index 0000000000000000000000000000000000000000..f13b82f25d43c604680ff4e96b3efd6936e98aea --- /dev/null +++ b/src/util/partition_iterator.hpp @@ -0,0 +1,66 @@ +#pragma once + +/* + * Present a monotonically increasing sequence of values given by an iterator + * as a sequence of pairs representing half-open intervals. + * + * Implementation is a thin wrapper over underlying iterator. + */ + +#include <iterator> +#include <memory> +#include <type_traits> +#include <utility> + +#include <util/iterutil.hpp> +#include <util/meta.hpp> + +namespace nest { +namespace mc { +namespace util { + +template <typename I> +class partition_iterator: public iterator_adaptor<partition_iterator<I>, I> { + using base = iterator_adaptor<partition_iterator<I>, I>; + friend class iterator_adaptor<partition_iterator<I>, I>; + I inner_; + + // provides access to inner iterator for adaptor. + const I& inner() const { return inner_; } + I& inner() { return inner_; } + + using inner_value_type = decay_t<decltype(*inner_)>; + +public: + using typename base::difference_type; + using value_type = std::pair<inner_value_type, inner_value_type>; + using pointer = const value_type*; + using reference = const value_type&; + + template < + typename J, + typename = enable_if_t<!std::is_same<decay_t<J>, partition_iterator>::value> + > + explicit partition_iterator(J&& c): inner_{std::forward<J>(c)} {} + + // forward and input iterator requirements + + value_type operator*() const { + return {*inner_, *std::next(inner_)}; + } + + util::pointer_proxy<value_type> operator->() const { + return **this; + } + + value_type operator[](difference_type n) const { + return *(*this+n); + } + + // public access to inner iterator + const I& get() const { return inner_; } +}; + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/range.hpp b/src/util/range.hpp new file mode 100644 index 0000000000000000000000000000000000000000..2439ca74ee785fac1f909e92b3189bbfcd6f5da2 --- /dev/null +++ b/src/util/range.hpp @@ -0,0 +1,332 @@ +#pragma once + +/* Present a pair of iterators as a non-owning collection. + * + * Two public member fields, `left` and `right`, describe + * the half-open interval [`left`, `right`). + * + * Constness of the range object only affects mutability + * of the iterators, and does not relate to the constness + * of the data to which the iterators refer. + * + * The `right` field may differ in type from the `left` field, + * in which case it is regarded as a sentinel type; the end of + * the interval is then marked by the first successor `i` of + * `left` that satisfies `i==right`. + * + * For an iterator `i` and sentinel `s`, it is expected that + * the tests `i==s` and `i!=s` are well defined, with the + * corresponding semantics. + */ + +#include <cstddef> +#include <iterator> +#include <limits> +#include <stdexcept> +#include <type_traits> +#include <utility> + +#ifdef WITH_TBB +#include <tbb/tbb_stddef.h> +#endif + +#include <util/counter.hpp> +#include <util/debug.hpp> +#include <util/either.hpp> +#include <util/iterutil.hpp> +#include <util/meta.hpp> + +namespace nest { +namespace mc { +namespace util { + +template <typename U, typename S = U> +struct range { + using iterator = U; + using sentinel = S; + using const_iterator = iterator; + using difference_type = typename std::iterator_traits<iterator>::difference_type; + using size_type = typename std::make_unsigned<difference_type>::type; + using value_type = typename std::iterator_traits<iterator>::value_type; + using reference = typename std::iterator_traits<iterator>::reference; + using const_reference = const value_type&; + + iterator left; + sentinel right; + + range() = default; + range(const range&) = default; + range(range&&) = default; + + template <typename U1, typename U2> + range(U1&& l, U2&& r): + left(std::forward<U1>(l)), right(std::forward<U2>(r)) + {} + + range& operator=(const range&) = default; + range& operator=(range&&) = default; + + bool empty() const { return left==right; } + + iterator begin() const { return left; } + const_iterator cbegin() const { return left; } + + sentinel end() const { return right; } + sentinel cend() const { return right; } + + template <typename V = iterator> + enable_if_t<is_forward_iterator<V>::value, size_type> + size() const { + return std::distance(begin(), end()); + } + + constexpr size_type max_size() const { return std::numeric_limits<size_type>::max(); } + + void swap(range& other) { + std::swap(left, other.left); + std::swap(right, other.right); + } + + auto front() const -> decltype(*left) { return *left; } + + auto back() const -> decltype(*left) { return *upto(left, right); } + + template <typename V = iterator> + enable_if_t<is_random_access_iterator<V>::value, decltype(*left)> + operator[](difference_type n) const { + return *std::next(begin(), n); + } + + template <typename V = iterator> + enable_if_t<is_random_access_iterator<V>::value, decltype(*left)> + at(difference_type n) const { + if (size_type(n) >= size()) { + throw std::out_of_range("out of range in range"); + } + return (*this)[n]; + } + +#ifdef WITH_TBB + template < + typename V = iterator, + typename = enable_if_t<is_forward_iterator<V>::value> + > + range(range& r, tbb::split): + left(r.left), right(r.right) + { + std::advance(left, r.size()/2u); + r.right = left; + } + + template < + typename V = iterator, + typename = enable_if_t<is_forward_iterator<V>::value> + > + range(range& r, tbb::proportional_split p): + left(r.left), right(r.right) + { + size_type i = (r.size()*p.left())/(p.left()+p.right()); + if (i<1) { + i = 1; + } + std::advance(left, i); + r.right = left; + } + + bool is_divisible() const { + return is_forward_iterator<U>::value && left != right && std::next(left) != right; + } + + static const bool is_splittable_in_proportion() { + return is_forward_iterator<U>::value; + } +#endif +}; + +template <typename U, typename V> +range<U, V> make_range(const U& left, const V& right) { + return range<U, V>(left, right); +} + +/* + * Use a proxy iterator to present a range as having the same begin and + * end types, for use with e.g. pre-C++17 ranged-for loops or STL + * algorithms. + */ +template <typename I, typename S> +class sentinel_iterator { + nest::mc::util::either<I, S> e_; + + bool is_sentinel() const { return e_.index()!=0; } + + I& iter() { + EXPECTS(!is_sentinel()); + return e_.template unsafe_get<0>(); + } + + const I& iter() const { + EXPECTS(!is_sentinel()); + return e_.template unsafe_get<0>(); + } + + S& sentinel() { + EXPECTS(is_sentinel()); + return e_.template unsafe_get<1>(); + } + + const S& sentinel() const { + EXPECTS(is_sentinel()); + return e_.template unsafe_get<1>(); + } + +public: + using difference_type = typename std::iterator_traits<I>::difference_type; + using value_type = typename std::iterator_traits<I>::value_type; + using pointer = typename std::iterator_traits<I>::pointer; + using reference = typename std::iterator_traits<I>::reference; + using iterator_category = typename std::iterator_traits<I>::iterator_category; + + sentinel_iterator(I i): e_(i) {} + + template <typename V = S, typename = enable_if_t<!std::is_same<I, V>::value>> + sentinel_iterator(S i): e_(i) {} + + sentinel_iterator() = default; + sentinel_iterator(const sentinel_iterator&) = default; + sentinel_iterator(sentinel_iterator&&) = default; + + sentinel_iterator& operator=(const sentinel_iterator&) = default; + sentinel_iterator& operator=(sentinel_iterator&&) = default; + + // forward and input iterator requirements + + auto operator*() const -> decltype(*(this->iter())) { return *iter(); } + + I operator->() const { return e_.template ptr<0>(); } + + sentinel_iterator& operator++() { + ++iter(); + return *this; + } + + sentinel_iterator operator++(int) { + sentinel_iterator c(*this); + ++*this; + return c; + } + + bool operator==(const sentinel_iterator& x) const { + if (is_sentinel()) { + return x.is_sentinel() || x.iter()==sentinel(); + } + else { + return x.is_sentinel()? iter()==x.sentinel(): iter()==x.iter(); + } + } + + bool operator!=(const sentinel_iterator& x) const { + return !(*this==x); + } + + // bidirectional iterator requirements + + sentinel_iterator& operator--() { + --iter(); + return *this; + } + + sentinel_iterator operator--(int) { + sentinel_iterator c(*this); + --*this; + return c; + } + + // random access iterator requirements + + sentinel_iterator &operator+=(difference_type n) { + iter() += n; + return *this; + } + + sentinel_iterator operator+(difference_type n) const { + sentinel_iterator c(*this); + return c += n; + } + + friend sentinel_iterator operator+(difference_type n, sentinel_iterator x) { + return x+n; + } + + sentinel_iterator& operator-=(difference_type n) { + iter() -= n; + return *this; + } + + sentinel_iterator operator-(difference_type n) const { + sentinel_iterator c(*this); + return c -= n; + } + + difference_type operator-(sentinel_iterator x) const { + return iter()-x.iter(); + } + + auto operator[](difference_type n) const -> decltype(*(this->iter())) { + return *(iter()+n); + } + + bool operator<=(const sentinel_iterator& x) const { + return x.is_sentinel() || (!is_sentinel() && iter()<=x.iter()); + } + + bool operator<(const sentinel_iterator& x) const { + return !is_sentinel() && (x.is_sentinel() || iter()<=x.iter()); + } + + bool operator>=(const sentinel_iterator& x) const { + return !(x<*this); + } + + bool operator>(const sentinel_iterator& x) const { + return !(x<=*this); + } +}; + +template <typename I, typename S> +using sentinel_iterator_t = + typename std::conditional<std::is_same<I, S>::value, I, sentinel_iterator<I, S>>::type; + +template <typename I, typename S> +sentinel_iterator_t<I, S> make_sentinel_iterator(const I& i, const S& s) { + return sentinel_iterator_t<I, S>(i); +} + +template <typename I, typename S> +sentinel_iterator_t<I, S> make_sentinel_end(const I& i, const S& s) { + return sentinel_iterator_t<I, S>(s); +} + +template <typename Seq> +auto canonical_view(const Seq& s) -> + range<sentinel_iterator_t<decltype(std::begin(s)), decltype(std::end(s))>> +{ + return {make_sentinel_iterator(std::begin(s), std::end(s)), make_sentinel_end(std::begin(s), std::end(s))}; +} + +/* + * Present a single item as a range + */ + +template <typename T> +range<T*> singleton_view(T& item) { + return {&item, &item+1}; +} + +template <typename T> +range<const T*> singleton_view(const T& item) { + return {&item, &item+1}; +} + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/singleton.hpp b/src/util/singleton.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c718eef3624af2088daa9d84abb00401ed1bd6d4 --- /dev/null +++ b/src/util/singleton.hpp @@ -0,0 +1,63 @@ +#pragma once + +/* + * Present a single object as a (non-owning) container with one + * element. + * + * (Will be subsumed by range/view code.) + */ + +#include <algorithm> + +namespace nest { +namespace mc { +namespace util { + +template <typename X> +struct singleton_adaptor { + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using value_type = X; + using reference = X&; + using const_reference = const X&; + using iterator = X*; + using const_iterator = const X*; + + X* xp; + singleton_adaptor(X& x): xp(&x) {} + + const X* cbegin() const { return xp; } + const X* begin() const { return xp; } + X* begin() { return xp; } + + const X* cend() const { return xp+1; } + const X* end() const { return xp+1; } + X* end() { return xp+1; } + + std::size_t size() const { return 1u; } + + bool empty() const { return false; } + + const X* front() const { return *xp; } + X* front() { return *xp; } + + const X* back() const { return *xp; } + X* back() { return *xp; } + + const X* operator[](difference_type) const { return *xp; } + X* operator[](difference_type) { return *xp; } + + void swap(singleton_adaptor& s) { std::swap(xp, s.xp); } + friend void swap(singleton_adaptor& r, singleton_adaptor& s) { + r.swap(s); + } +}; + +template <typename X> +singleton_adaptor<X> singleton_view(X& x) { + return singleton_adaptor<X>(x); +} + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/span.hpp b/src/util/span.hpp new file mode 100644 index 0000000000000000000000000000000000000000..06cdfa36924324c589af8a16412d8c167fb0429f --- /dev/null +++ b/src/util/span.hpp @@ -0,0 +1,33 @@ +#pragma once + +/* + * Presents a half-open interval [a,b) of integral values as a container. + */ + +#include <type_traits> +#include <utility> + +#include <util/counter.hpp> +#include <util/range.hpp> + +namespace nest { +namespace mc { +namespace util { + +template <typename I> +using span = range<counter<I>>; + +template <typename I, typename J> +span<typename std::common_type<I, J>::type> make_span(I left, J right) { + return span<typename std::common_type<I, J>::type>(left, right); +} + +template <typename I, typename J> +span<typename std::common_type<I, J>::type> make_span(std::pair<I, J> interval) { + return span<typename std::common_type<I, J>::type>(interval.first, interval.second); +} + + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/transform.hpp b/src/util/transform.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d6c930dbdc2cb653e94433ff0424fdc5612cbe3f --- /dev/null +++ b/src/util/transform.hpp @@ -0,0 +1,109 @@ +#pragma once + +/* + * An iterator adaptor that presents the values from an underlying + * iterator after applying a provided functor. + */ + +#include <iterator> +#include <memory> +#include <type_traits> + +#include <util/iterutil.hpp> +#include <util/meta.hpp> +#include <util/range.hpp> + +namespace nest { +namespace mc { +namespace util { + +template <typename I, typename F> +class transform_iterator: public iterator_adaptor<transform_iterator<I, F>, I> { + using base = iterator_adaptor<transform_iterator<I, F>, I>; + friend class iterator_adaptor<transform_iterator<I, F>, I>; + + I inner_; + F f_; + + // provides access to inner iterator for adaptor. + const I& inner() const { return inner_; } + I& inner() { return inner_; } + + using inner_value_type = util::decay_t<decltype(*inner_)>; + +public: + using typename base::difference_type; + using value_type = decltype(f_(*inner_)); + using pointer = const value_type*; + using reference = const value_type&; + + template <typename J, typename G> + transform_iterator(J&& c, G&& g): inner_(std::forward<J>(c)), f_(std::forward<G>(g)) {} + + transform_iterator(const transform_iterator&) = default; + transform_iterator(transform_iterator&&) = default; + transform_iterator& operator=(const transform_iterator&) = default; + transform_iterator& operator=(transform_iterator&&) = default; + + // forward and input iterator requirements + + value_type operator*() const { + return f_(*inner_); + } + + util::pointer_proxy<value_type> operator->() const { + return **this; + } + + value_type operator[](difference_type n) const { + return *(*this+n); + } + + // public access to inner iterator + const I& get() const { return inner_; } + + bool operator==(const transform_iterator& x) const { return inner_==x.inner_; } + bool operator!=(const transform_iterator& x) const { return inner_!=x.inner_; } + + // expose inner iterator for testing against a sentinel + template <typename Sentinel> + bool operator==(const Sentinel& s) const { return inner_==s; } + + template <typename Sentinel> + bool operator!=(const Sentinel& s) const { return !(inner_==s); } +}; + +template <typename I, typename F> +transform_iterator<I, util::decay_t<F>> make_transform_iterator(const I& i, const F& f) { + return transform_iterator<I, util::decay_t<F>>(i, f); +} + +template < + typename Seq, + typename F, + typename seq_citer = typename sequence_traits<Seq>::const_iterator, + typename seq_csent = typename sequence_traits<Seq>::const_sentinel, + typename = enable_if_t<std::is_same<seq_citer, seq_csent>::value> +> +range<transform_iterator<seq_citer, util::decay_t<F>>> +transform_view(const Seq& s, const F& f) { + return {make_transform_iterator(cbegin(s), f), make_transform_iterator(cend(s), f)}; +} + + +template < + typename Seq, + typename F, + typename seq_citer = typename sequence_traits<Seq>::const_iterator, + typename seq_csent = typename sequence_traits<Seq>::const_sentinel, + typename = enable_if_t<!std::is_same<seq_citer, seq_csent>::value> +> +range<transform_iterator<seq_citer, util::decay_t<F>>, seq_csent> +transform_view(const Seq& s, const F& f) { + return {make_transform_iterator(cbegin(s), f), cend(s)}; +} + + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/uninitialized.hpp b/src/util/uninitialized.hpp index 846e46d5a2451fe1535f026ee4b801e5303a6f0c..8e3613cd6b342b40c7f2cd76edfd4e7acec5c582 100644 --- a/src/util/uninitialized.hpp +++ b/src/util/uninitialized.hpp @@ -18,25 +18,26 @@ namespace nest { namespace mc { namespace util { -/* Maintains storage for a value of type X, with explicit +/* + * Maintains storage for a value of type X, with explicit * construction and destruction. */ template <typename X> -struct uninitialized { +class uninitialized { private: typename std::aligned_storage<sizeof(X), alignof(X)>::type data; public: - using pointer_type = X*; - using const_pointer_type = const X*; - using reference_type = X&; - using const_reference_type = const X&; + using pointer = X*; + using const_pointer = const X*; + using reference = X&; + using const_reference= const X&; - pointer_type ptr() { return reinterpret_cast<X*>(&data); } - const_pointer_type cptr() const { return reinterpret_cast<const X*>(&data); } + pointer ptr() { return reinterpret_cast<X*>(&data); } + const_pointer cptr() const { return reinterpret_cast<const X*>(&data); } - reference_type ref() { return *reinterpret_cast<X*>(&data); } - const_reference_type cref() const { return *reinterpret_cast<const X*>(&data); } + reference ref() { return *reinterpret_cast<X*>(&data); } + const_reference cref() const { return *reinterpret_cast<const X*>(&data); } // Copy construct the value. template < @@ -60,45 +61,46 @@ public: // Apply the one-parameter functor F to the value by reference. template <typename F> - result_of_t<F(reference_type)> apply(F&& f) { return f(ref()); } + result_of_t<F(reference)> apply(F&& f) { return f(ref()); } // Apply the one-parameter functor F to the value by const reference. template <typename F> - result_of_t<F(const_reference_type)> apply(F&& f) const { return f(cref()); } + result_of_t<F(const_reference)> apply(F&& f) const { return f(cref()); } }; -/* Maintains storage for a pointer of type X, representing +/* + * Maintains storage for a pointer of type X, representing * a possibly uninitialized reference. */ template <typename X> -struct uninitialized<X&> { +class uninitialized<X&> { private: X *data; public: - using pointer_type = X*; - using const_pointer_type = const X*; - using reference_type = X&; - using const_reference_type = const X&; + using pointer = X*; + using const_pointer = const X*; + using reference = X&; + using const_reference = const X&; - pointer_type ptr() { return data; } - const_pointer_type cptr() const { return data; } + pointer ptr() { return data; } + const_pointer cptr() const { return data; } - reference_type ref() { return *data; } - const_reference_type cref() const { return *data; } + reference ref() { return *data; } + const_reference cref() const { return *data; } void construct(X& x) { data = &x; } void destruct() {} // Apply the one-parameter functor F to the value by reference. template <typename F> - result_of_t<F(reference_type)> apply(F&& f) { + result_of_t<F(reference)> apply(F&& f) { return f(ref()); } // Apply the one-parameter functor F to the value by const reference. template <typename F> - result_of_t<F(const_reference_type)> apply(F&& f) const { + result_of_t<F(const_reference)> apply(F&& f) const { return f(cref()); } }; @@ -108,17 +110,18 @@ public: * Allows the use of uninitialized<X> for void X, for generic applications. */ template <> -struct uninitialized<void> { - using pointer_type = void*; - using const_pointer_type = const void*; - using reference_type = void; - using const_reference_type = void; +class uninitialized<void> { +public: + using pointer = void*; + using const_pointer = const void*; + using reference = void; + using const_reference = void; - pointer_type ptr() { return nullptr; } - const_pointer_type cptr() const { return nullptr; } + pointer ptr() { return nullptr; } + const_pointer cptr() const { return nullptr; } - reference_type ref() {} - const_reference_type cref() const {} + reference ref() {} + const_reference cref() const {} // No operation. void construct(...) {} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 10fb65efd052c1d9d7d7fdd5f50bdec0f93cb5a5..c39e22a80f408f824af43f31f1d5e965b1f37ff5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -14,6 +14,9 @@ add_subdirectory(validation) # Test for the internode communication (eg. mpi) add_subdirectory(global_communication) +# Tests for performance: This could include stand alone tests. These do not necessarily be run automatically +add_subdirectory(performance) + # Proposed additional test types: @@ -21,7 +24,6 @@ add_subdirectory(global_communication) # Test to check integration between components -# Tests for performance: This could include stand alone tests. These do not necessarily be run automatically # Numbered tests based on bugs in the tracker diff --git a/tests/global_communication/CMakeLists.txt b/tests/global_communication/CMakeLists.txt index f7d000157e4cfd130399d1f718b67fbc5eb2f86d..cc1e1af8b6567a2fc887815fa4eaeb25c561841e 100644 --- a/tests/global_communication/CMakeLists.txt +++ b/tests/global_communication/CMakeLists.txt @@ -1 +1,34 @@ -# Nothing to be done yet +set(HEADERS + ${PROJECT_SOURCE_DIR}/src/swcio.hpp +) +set(COMMUNICATION_SOURCES + test_exporter_spike_file.cpp + test_communicator.cpp + + # unit test driver + test.cpp +) + +add_executable(global_communication.exe ${COMMUNICATION_SOURCES} ${HEADERS}) + +set(TARGETS global_communication.exe) + +foreach(target ${TARGETS}) + target_link_libraries(${target} LINK_PUBLIC cellalgo gtest) + + if(WITH_TBB) + target_link_libraries(${target} LINK_PUBLIC ${TBB_LIBRARIES}) + endif() + + if(WITH_MPI) + target_link_libraries(${target} LINK_PUBLIC ${MPI_C_LIBRARIES}) + set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS "${MPI_C_LINK_FLAGS}") + endif() + + set_target_properties( + ${target} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests" + ) +endforeach() + diff --git a/tests/global_communication/mpi_listener.hpp b/tests/global_communication/mpi_listener.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e33aaebb5cea2168baead70e3418567c10bcdd6e --- /dev/null +++ b/tests/global_communication/mpi_listener.hpp @@ -0,0 +1,155 @@ +#pragma once + +#include <cstdio> +#include <ostream> +#include <stdexcept> + +#include <communication/global_policy.hpp> + +#include "gtest.h" + +/// A specialized listener desinged for printing test results with MPI. +/// +/// When tests are run with MPI, one instance of each test is run on +/// each rank. The default behavior of Google Test is for each test +/// instance to print to stdout. With more than one MPI rank, this creates +/// the usual MPI mess of output. +/// +/// This specialization has the first rank (rank 0) print to stdout, and all MPI +/// ranks print their output to separate text files. +/// For each test a message is printed showing +/// - detailed messages about errors on rank 0 +/// - a head count of errors that occured on other MPI ranks + +class mpi_listener : public testing::EmptyTestEventListener { +private: + using UnitTest = testing::UnitTest; + using TestCase = testing::TestCase; + using TestInfo = testing::TestInfo; + using TestPartResult = testing::TestPartResult; + + int rank_; + int size_; + std::ofstream fid_; + char buffer_[1024]; + int test_case_failures_; + int test_case_tests_; + int test_failures_; + + bool does_print() const { + return rank_==0; + } + + void print(const char* s) { + if (fid_) { + fid_ << s; + } + if (does_print()) { + std::cout << s; + } + } + + void print(const std::string& s) { + print(s.c_str()); + } + + /// convenience function that handles the logic of using snprintf + /// and forwarding the results to file and/or stdout. + /// + /// TODO : it might be an idea to use a resizeable buffer + template <typename... Args> + void printf_helper(const char* s, Args&&... args) { + snprintf(buffer_, sizeof(buffer_), s, std::forward<Args>(args)...); + print(buffer_); + } + +public: + mpi_listener(std::string f_base="") { + rank_ = nest::mc::communication::global_policy::id(); + size_ = nest::mc::communication::global_policy::size(); + + if (f_base.empty()) { + return; + } + std::string fname = f_base + "_" + std::to_string(rank_) + ".txt"; + fid_.open(fname); + if (!fid_) { + throw std::runtime_error("could not open file " + fname + " for test output"); + } + } + + /// Messages that are printed at the start and end of the test program. + /// i.e. once only. + virtual void OnTestProgramStart(const UnitTest&) override { + printf_helper("*** test output for rank %d of %d\n\n", rank_, size_); + } + virtual void OnTestProgramEnd(const UnitTest&) override { + printf_helper("*** end test output for rank %d of %d\n", rank_, size_); + } + + /// Messages that are printed at the start and end of each test case. + /// On startup a counter that counts the number of tests that fail in + /// this test case is initialized to zero, and will be incremented for each + /// test that fails. + virtual void OnTestCaseStart(const TestCase& test_case) override { + test_case_failures_ = 0; + test_case_tests_ = 0; + } + virtual void OnTestCaseEnd(const TestCase& test_case) override { + printf_helper( + "[PASSED %3d; FAILED %3d] of %3d tests in %s\n\n", + test_case_tests_-test_case_failures_, + test_case_failures_, + test_case_tests_, + test_case.name() + ); + } + + // Called before a test starts. + virtual void OnTestStart(const TestInfo& test_info) override { + printf_helper( " TEST %s::%s\n", test_info.test_case_name(), test_info.name()); + test_failures_ = 0; + } + + // Called after a failed assertion or a SUCCEED() invocation. + virtual void OnTestPartResult(const TestPartResult& test_part_result) override { + const char* banner = "--------------------------------------------------------------------------------"; + + // indent all lines in the summary by 4 spaces + std::string summary = " " + std::string(test_part_result.summary()); + auto pos = summary.find("\n"); + while (pos!=summary.size() && pos!=std::string::npos) { + summary.replace(pos, 1, "\n "); + pos = summary.find("\n", pos+1); + } + + printf_helper( + " LOCAL_%s\n %s\n %s:%d\n%s\n %s\n", + test_part_result.failed() ? "FAIL" : "SUCCESS", + banner, + test_part_result.file_name(), + test_part_result.line_number(), + summary.c_str(), + banner + ); + + // note that there was a failure in this test case + if (test_part_result.failed()) { + test_failures_++; + } + } + + // Called after a test ends. + virtual void OnTestEnd(const TestInfo& test_info) override { + test_case_tests_++; + + // count the number of ranks that had errors + int global_errors = + nest::mc::communication::global_policy::sum(test_failures_>0 ? 1 : 0); + if (global_errors>0) { + test_case_failures_++; + printf_helper(" GLOBAL_FAIL on %d ranks\n", global_errors); + } + } +}; + diff --git a/tests/global_communication/test.cpp b/tests/global_communication/test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1ddb6d41baf5d84cefc5b7f4e542a7cf79d2598c --- /dev/null +++ b/tests/global_communication/test.cpp @@ -0,0 +1,39 @@ +#include <iostream> +#include <fstream> +#include <numeric> +#include <vector> + +#include "gtest.h" + +#include "mpi_listener.hpp" + +#include <communication/communicator.hpp> +#include <communication/global_policy.hpp> +#include <util/ioutil.hpp> + +using namespace nest::mc; + +int main(int argc, char **argv) { + // We need to set the communicator policy at the top level + // this allows us to build multiple communicators in the tests + communication::global_policy_guard global_guard(argc, argv); + + // initialize google test environment + testing::InitGoogleTest(&argc, argv); + + // set up a custom listener that prints messages in an MPI-friendly way + auto& listeners = testing::UnitTest::GetInstance()->listeners(); + // first delete the original printer + delete listeners.Release(listeners.default_result_printer()); + // now add our custom printer + listeners.Append(new mpi_listener("results_global_communication")); + + // record the local return value for tests run on this mpi rank + // 0 : success + // 1 : failure + auto result = RUN_ALL_TESTS(); + + // perform global collective, to ensure that all ranks return + // the same exit code + return communication::global_policy::max(result); +} diff --git a/tests/global_communication/test_communicator.cpp b/tests/global_communication/test_communicator.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f79d4e2d6e42b382bc4b54e16d29187ea531b7c0 --- /dev/null +++ b/tests/global_communication/test_communicator.cpp @@ -0,0 +1,35 @@ +#include "gtest.h" + +#include <cstdio> +#include <fstream> +#include <iostream> +#include <string> +#include <vector> + +#include <communication/communicator.hpp> +#include <communication/global_policy.hpp> + +using namespace nest::mc; + +using time_type = float; +using communicator_type = communication::communicator<time_type, communication::global_policy>; + +TEST(communicator, setup) { + /* + using policy = communication::global_policy; + + auto num_domains = policy::size(); + auto rank = policy::id(); + + auto counts = policy.gather_all(1); + EXPECT_EQ(counts.size(), unsigned(num_domains)); + for(auto i : counts) { + EXPECT_EQ(i, 1); + } + + auto part = util::parition_view(counts); + for(auto p : part) { + EXPECT_EQ(p.second-p.first, 1); + } + */ +} diff --git a/tests/global_communication/test_exporter_spike_file.cpp b/tests/global_communication/test_exporter_spike_file.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4a85b0784801df6f91c72db70b44b69b36337f2b --- /dev/null +++ b/tests/global_communication/test_exporter_spike_file.cpp @@ -0,0 +1,115 @@ +#include "gtest.h" + +#include <cstdio> +#include <fstream> +#include <iostream> +#include <string> +#include <vector> + +#include <communication/communicator.hpp> +#include <communication/global_policy.hpp> +#include <io/exporter_spike_file.hpp> + +class exporter_spike_file_fixture : public ::testing::Test { +protected: + using time_type = float; + using communicator_type = nest::mc::communication::global_policy; + + using exporter_type = + nest::mc::io::exporter_spike_file<time_type, communicator_type>; + using spike_type = exporter_type::spike_type; + + std::string file_name_; + std::string path_; + std::string extension_; + unsigned index_; + + exporter_spike_file_fixture() : + file_name_("spikes_exporter_spike_file_fixture"), + path_("./"), + extension_("gdf"), + index_(communicator_type::id()) + {} + + std::string get_standard_file_name() { + return exporter_type::create_output_file_path(file_name_, path_, extension_, index_); + } + + void SetUp() { + // code here will execute just before the test ensues + } + + void TearDown() { + // delete the start create file + std::remove(get_standard_file_name().c_str()); + } + + ~exporter_spike_file_fixture() + {} +}; + +TEST_F(exporter_spike_file_fixture, constructor) { + exporter_type exporter(file_name_, path_, extension_, true); + + //test if the file exist and depending on over_write throw or delete + std::ifstream f(get_standard_file_name()); + EXPECT_TRUE(f.good()); + + // We now know the file exists, so create a new exporter with overwrite false + try { + exporter_type exporter1(file_name_, path_, extension_, false); + FAIL() << "expected a file already exists error"; + } + catch (const std::runtime_error& err) { + EXPECT_EQ( + err.what(), + "Tried opening file for writing but it exists and over_write is false: " + + get_standard_file_name() + ); + } + catch (...) { + FAIL() << "expected a file already exists error"; + } +} + +TEST_F(exporter_spike_file_fixture, create_output_file_path) { + // Create some random paths, no need for fancy tests here + std::string produced_filename = + exporter_type::create_output_file_path("spikes", "./", "gdf", 0); + EXPECT_STREQ(produced_filename.c_str(), "./spikes_0.gdf"); + + produced_filename = + exporter_type::create_output_file_path("a_name", "../../", "txt", 5); + EXPECT_STREQ(produced_filename.c_str(), "../../a_name_5.txt"); +} + +TEST_F(exporter_spike_file_fixture, do_export) { + { + exporter_type exporter(file_name_, path_, extension_); + + // Create some spikes + std::vector<spike_type> spikes; + spikes.push_back({ { 0, 0 }, 0.0 }); + spikes.push_back({ { 0, 0 }, 0.1 }); + spikes.push_back({ { 1, 0 }, 1.0 }); + spikes.push_back({ { 1, 0 }, 1.1 }); + + // now do the export + exporter.output(spikes); + } + + // Test if we have spikes in the file? + std::ifstream f(get_standard_file_name()); + EXPECT_TRUE(f.good()); + + std::string line; + + EXPECT_TRUE(std::getline(f, line)); + EXPECT_STREQ(line.c_str(), "0 0.0000"); + EXPECT_TRUE(std::getline(f, line)); + EXPECT_STREQ(line.c_str(), "0 0.1000"); + EXPECT_TRUE(std::getline(f, line)); + EXPECT_STREQ(line.c_str(), "1 1.0000"); + EXPECT_TRUE(std::getline(f, line)); + EXPECT_STREQ(line.c_str(), "1 1.1000"); +} diff --git a/tests/performance/CMakeLists.txt b/tests/performance/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..351be926371b61bcf130d43efc42e5fb0ff53eee --- /dev/null +++ b/tests/performance/CMakeLists.txt @@ -0,0 +1,2 @@ +# Unit tests +add_subdirectory(io) diff --git a/tests/performance/io/CMakeLists.txt b/tests/performance/io/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..5bfe045ebf782c298702bd107201c9c343a2a297 --- /dev/null +++ b/tests/performance/io/CMakeLists.txt @@ -0,0 +1,22 @@ +set(HEADERS +) + +set(DISK_IO_SOURCES + disk_io.cpp +) + +add_executable(disk_io.exe ${DISK_IO_SOURCES} ${HEADERS}) + +target_link_libraries(disk_io.exe LINK_PUBLIC cellalgo) + +if(WITH_TBB) + target_link_libraries(disk_io.exe LINK_PUBLIC ${TBB_LIBRARIES}) +endif() + +if(WITH_MPI) + target_link_libraries(disk_io.exe LINK_PUBLIC ${MPI_C_LIBRARIES}) + set_property(TARGET disk_io.exe APPEND_STRING PROPERTY LINK_FLAGS "${MPI_C_LINK_FLAGS}") +endif() + +# Copy the python file that drives the performance tests and produces the output +file(COPY disk_io.py DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/tests/performance/io/disk_io.cpp b/tests/performance/io/disk_io.cpp new file mode 100644 index 0000000000000000000000000000000000000000..526263bf395a81eb36ed050b96cfe1923e04cd25 --- /dev/null +++ b/tests/performance/io/disk_io.cpp @@ -0,0 +1,160 @@ +#include <stdio.h> + +#include <fstream> +#include <iostream> +#include <numeric> + +#include <cell.hpp> +#include <cell_group.hpp> +#include <common_types.hpp> +#include <communication/communicator.hpp> +#include <communication/global_policy.hpp> +#include <fvm_cell.hpp> +#include <io/exporter_spike_file.hpp> +#include <profiling/profiler.hpp> + +using namespace nest::mc; + +using global_policy = communication::global_policy; +using lowered_cell = fvm::fvm_cell<double, cell_local_size_type>; +using cell_group_type = cell_group<lowered_cell>; +using time_type = typename cell_group_type::time_type; +using spike_type = io::exporter_spike_file<time_type, global_policy>::spike_type; +using timer = util::timer_type; + +int main(int argc, char** argv) { + + //Setup the possible mpi environment + communication::global_policy_guard global_guard(argc, argv); + + // very simple command line parsing + if (argc < 3) { + std::cout << "disk_io <int nrspikes> <int nr_repeats> [simple_output (false|true)]\n" + << " Simple performance test runner for the exporter manager\n" + << " It exports nrspikes nr_repeats using the export_manager and will produce\n" + << " the total, mean and std of the time needed to perform the output to disk\n\n" + + << " <file_per_rank> true will produce a single file per mpi rank\n" + << " <simple_output> true will produce a simplyfied comma seperated output for automatic parsing\n\n" + + << " The application can be started with mpi support and will produce output on a single rank\n" + << " if nrspikes is not a multiple of the nr of mpi rank, floor is take\n" ; + return 1; + } + auto nr_spikes = atoi(argv[1]); + + if (nr_spikes == 0) { + std::cout << "disk_io <nrspikes>\n"; + std::cout << " nrspikes should be a valid integer higher then zero\n"; + + return 1; + } + auto nr_repeats = atoi(argv[2]); + + if (nr_repeats == 0) { + std::cout << "disk_io <nrspikes>\n"; + std::cout << " nr_repeats should be a valid integer higher then zero\n"; + return 1; + } + + auto simple_stats = false; + if (argc == 4) { + std::string simple(argv[3]); + if (simple == std::string("true")) + { + simple_stats = true; + } + } + + // Create the sut + io::exporter_spike_file<time_type, global_policy> exporter( + "spikes", "./", "gdf", true); + + // We need the nr of ranks to calculate the nr of spikes to produce per + // rank + global_policy communication_policy; + + auto nr_ranks = unsigned( communication_policy.size() ); + auto spikes_per_rank = nr_spikes / nr_ranks; + + // Create a set of spikes + std::vector<spike_type> spikes; + + // ********************************************************************* + // To have a somewhat realworld data set we calculate from the nr of spikes + // (assuming 20 hz average) the number of nr of 'simulated' neurons, + // and create idxs using this value. The number of chars in the number + // influences the size of the output and thus the speed + // Also taken that we have only a single second of simulated time + // all spike times should be between 0.0 and 1.0: + auto simulated_neurons = spikes_per_rank / 20; + for (auto idx = unsigned{ 0 }; idx < spikes_per_rank; ++idx) { + + spikes.push_back({ + {idx % simulated_neurons, 0 }, // correct idx + 0.0f + 1 / (0.05f + idx % 20) + }); // semi random float + } + + double timings_arr[nr_repeats]; + double time_total = 0; + + // now output to disk nr_repeats times, while keeping track of the times + for (auto idx = 0; idx < nr_repeats; ++idx) { + auto time_start = timer::tic(); + exporter.output(spikes); + auto run_time = timer::toc(time_start); + + time_total += run_time; + timings_arr[idx] = run_time; + } + + // create the vector here to prevent changes on the heap influencing the + // timeing + std::vector<double> timings; + for (auto idx = 0; idx < nr_repeats; ++idx) { + timings.push_back(timings_arr[idx]); + } + + + // Calculate some statistics + auto sum = std::accumulate(timings.begin(), timings.end(), 0.0); + auto mean = sum / timings.size(); + + std::vector<double> diff(timings.size()); + std::transform( + timings.begin(), timings.end(), diff.begin(), + std::bind2nd(std::minus<double>(), mean) + ); + auto sq_sum = std::inner_product( + diff.begin(), diff.end(), diff.begin(), + 0.0 + ); + auto stdev = std::sqrt(sq_sum / timings.size()); + + auto min = *std::min_element(timings.begin(), timings.end()); + auto max = *std::max_element(timings.begin(), timings.end()); + + + if (communication_policy.id() != 0) { + return 0; + } + + // and output + if (simple_stats) { + std::cout << time_total<< "," + << mean << "," + << stdev << "," + << min << "," + << max << std::endl; + } + else { + std::cout << "total time (ms): " << time_total << std::endl; + std::cout << "mean time (ms): " << mean << std::endl; + std::cout << "stdev time (ms): " << std::endl; + std::cout << "min time (ms): " << min << std::endl; + std::cout << "max time (ms): " << max << std::endl; + } + + return 0; +} diff --git a/tests/performance/io/disk_io.py b/tests/performance/io/disk_io.py new file mode 100644 index 0000000000000000000000000000000000000000..ea42304b23e2da851058b8b6d7625f8185ec0db3 --- /dev/null +++ b/tests/performance/io/disk_io.py @@ -0,0 +1,49 @@ +import matplotlib.pyplot as plt +import subprocess +import os + + + +current_script_dir = os.path.dirname(os.path.abspath(__file__)) + +spikes_to_save = 1000000 + +print ( "Simple performance runner for spike output to file. \n" + + str(spikes_to_save) + " spikes will be written to a file and the duration of this \n" + + "operation measured for different number of ranks\n" ) + + +range_nr_rank = [1, 2, 4, 8, 16, 24, 32, 48, 64] +mean = [] +std = [] +min = [] +max = [] +for n_rank in range_nr_rank: + # open the disk_io executable + p1 = subprocess.Popen(["mpirun", "-n",str(n_rank), + os.path.join(current_script_dir, "disk_io.exe"), + str(spikes_to_save), str(10), "true"], + stdout=subprocess.PIPE) + + #and grab the raw stats + stats = p1.communicate()[0] + + # convert into list + stats = stats.split(",") + + mean.append(float(stats[1])) + std.append(float(stats[2])) + min.append(float(stats[3])) + max.append(float(stats[4])) + + print ("performed test for n_rank= " + str(n_rank)) + +print (range_nr_rank) +print (mean) +print (std) + +plt.errorbar(range_nr_rank, mean, yerr=std, fmt='-o', label="mean (std)") +plt.errorbar(range_nr_rank, min, fmt='-', label="min") +plt.errorbar(range_nr_rank, max, fmt='-', label="max") +plt.legend() +plt.show() diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index a3a2d55333f6316b133bff3982ec58e1b03d525d..6b34d70125bb4d9792f8abb6b68daf411dfe78d8 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -11,23 +11,34 @@ set(HEADERS set(TEST_SOURCES # unit tests test_algorithms.cpp + test_double_buffer.cpp test_cell.cpp test_compartments.cpp + test_counter.cpp + test_either.cpp test_event_queue.cpp test_fvm.cpp test_cell_group.cpp + test_lexcmp.cpp + test_mask_stream.cpp test_matrix.cpp test_mechanisms.cpp + test_nop.cpp test_optional.cpp test_parameters.cpp + test_partition.cpp test_point.cpp test_probe.cpp test_segment.cpp + test_range.cpp + test_span.cpp test_spikes.cpp + test_spike_store.cpp test_stimulus.cpp test_swcio.cpp test_synapses.cpp test_tree.cpp + test_transform.cpp test_uninitialized.cpp # unit test driver @@ -43,12 +54,12 @@ foreach(target ${TARGETS}) target_link_libraries(${target} LINK_PUBLIC cellalgo gtest) if(WITH_TBB) - target_link_libraries(${target} LINK_PUBLIC ${TBB_LIBRARIES}) + target_link_libraries(${target} LINK_PUBLIC ${TBB_LIBRARIES}) endif() if(WITH_MPI) - target_link_libraries(${target} LINK_PUBLIC ${MPI_C_LIBRARIES}) - set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS "${MPI_C_LINK_FLAGS}") + target_link_libraries(${target} LINK_PUBLIC ${MPI_C_LIBRARIES}) + set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS "${MPI_C_LINK_FLAGS}") endif() set_target_properties(${target} diff --git a/tests/unit/test_algorithms.cpp b/tests/unit/test_algorithms.cpp index badaabffb605986578cbc15b9ed40b597a0e95ff..7c4f9a12a717cd9929fa7ec3de14157b9c88affb 100644 --- a/tests/unit/test_algorithms.cpp +++ b/tests/unit/test_algorithms.cpp @@ -1,3 +1,4 @@ +#include <random> #include <vector> #include "gtest.h" @@ -6,6 +7,28 @@ #include "../test_util.hpp" #include "util/debug.hpp" +/// tests the sort implementation in threading +/// is only parallel if TBB is being used +TEST(algorithms, parallel_sort) +{ + auto n = 10000; + std::vector<int> v(n); + std::iota(v.begin(), v.end(), 1); + + // intialize with the default random seed + std::shuffle(v.begin(), v.end(), std::mt19937()); + + // assert that the original vector has in fact been permuted + EXPECT_FALSE(std::is_sorted(v.begin(), v.end())); + + nest::mc::threading::sort(v); + + EXPECT_TRUE(std::is_sorted(v.begin(), v.end())); + for(auto i=0; i<n; ++i) { + EXPECT_EQ(i+1, v[i]); + } +} + TEST(algorithms, sum) { @@ -14,9 +37,12 @@ TEST(algorithms, sum) EXPECT_EQ(10*2, nest::mc::algorithms::sum(v1)); // make an array 1:20 and sum it up using formula for arithmetic sequence - std::vector<int> v2(20); - std::iota(v2.begin(), v2.end(), 1); auto n = 20; + std::vector<int> v2(n); + // can't use iota because the Intel compiler optimizes it out, despite + // the result being required in EXPECT_EQ + // std::iota(v2.begin(), v2.end(), 1); + for(auto i=0; i<n; ++i) { v2[i] = i+1; } EXPECT_EQ((n+1)*n/2, nest::mc::algorithms::sum(v2)); } @@ -172,7 +198,7 @@ TEST(algorithms, is_positive) ); } -TEST(algorithms, has_contiguous_segments) +TEST(algorithms, has_contiguous_compartments) { // // 0 @@ -186,7 +212,7 @@ TEST(algorithms, has_contiguous_segments) // 5 6 // EXPECT_FALSE( - nest::mc::algorithms::has_contiguous_segments( + nest::mc::algorithms::has_contiguous_compartments( std::vector<int>{0, 0, 1, 2, 2, 3, 4, 2} ) ); @@ -203,7 +229,7 @@ TEST(algorithms, has_contiguous_segments) // 4 7 // EXPECT_FALSE( - nest::mc::algorithms::has_contiguous_segments( + nest::mc::algorithms::has_contiguous_compartments( std::vector<int>{0, 0, 1, 2, 3, 2, 2, 5} ) ); @@ -220,7 +246,7 @@ TEST(algorithms, has_contiguous_segments) // 4 6 // EXPECT_TRUE( - nest::mc::algorithms::has_contiguous_segments( + nest::mc::algorithms::has_contiguous_compartments( std::vector<int>{0, 0, 1, 2, 3, 2, 5, 2} ) ); @@ -237,21 +263,34 @@ TEST(algorithms, has_contiguous_segments) // 4 6 // EXPECT_TRUE( - nest::mc::algorithms::has_contiguous_segments( + nest::mc::algorithms::has_contiguous_compartments( std::vector<int>{0, 0, 1, 2, 3, 2, 5, 1} ) ); + // + // 0 + // / \. + // 1 2 + // / \. + // 3 4 + // + EXPECT_TRUE( + nest::mc::algorithms::has_contiguous_compartments( + std::vector<int>{0, 0, 0, 1, 1} + ) + ); + // Soma-only list EXPECT_TRUE( - nest::mc::algorithms::has_contiguous_segments( + nest::mc::algorithms::has_contiguous_compartments( std::vector<int>{0} ) ); // Empty list EXPECT_TRUE( - nest::mc::algorithms::has_contiguous_segments( + nest::mc::algorithms::has_contiguous_compartments( std::vector<int>{} ) ); diff --git a/tests/unit/test_cell.cpp b/tests/unit/test_cell.cpp index 88c8dcaced167d4030529adb8a348a116158c17f..3102d97435d3095b4ef91b885b040ac084f37a5f 100644 --- a/tests/unit/test_cell.cpp +++ b/tests/unit/test_cell.cpp @@ -1,6 +1,6 @@ #include "gtest.h" -#include "../src/cell.hpp" +#include "cell.hpp" TEST(cell_type, soma) { @@ -59,7 +59,7 @@ TEST(cell_type, add_segment) ); c.add_cable(0, std::move(seg)); - EXPECT_EQ(c.num_segments(), 2); + EXPECT_EQ(c.num_segments(), 2u); } // add segment on the fly @@ -78,7 +78,7 @@ TEST(cell_type, add_segment) segmentKind::dendrite, cable_radius, cable_radius, cable_length ); - EXPECT_EQ(c.num_segments(), 2); + EXPECT_EQ(c.num_segments(), 2u); } { cell c; @@ -97,7 +97,7 @@ TEST(cell_type, add_segment) std::vector<double>{cable_length, cable_length, cable_length} ); - EXPECT_EQ(c.num_segments(), 2); + EXPECT_EQ(c.num_segments(), 2u); } } @@ -137,7 +137,7 @@ TEST(cell_type, multiple_cables) c.add_cable(1, seg(segmentKind::dendrite)); c.add_cable(1, seg(segmentKind::dendrite)); - EXPECT_EQ(c.num_segments(), 5); + EXPECT_EQ(c.num_segments(), 5u); // each of the 5 segments has volume 1 by design EXPECT_EQ(c.volume(), 5.); // each of the 4 cables has volume 2., and the soma has an awkward area @@ -148,12 +148,14 @@ TEST(cell_type, multiple_cables) const auto model = c.model(); auto const& con = model.tree; + auto no_parent = cell_tree::no_parent; + EXPECT_EQ(con.num_segments(), 5u); - EXPECT_EQ(con.parent(0), -1); - EXPECT_EQ(con.parent(1), 0); - EXPECT_EQ(con.parent(2), 0); - EXPECT_EQ(con.parent(3), 1); - EXPECT_EQ(con.parent(4), 1); + EXPECT_EQ(con.parent(0), no_parent); + EXPECT_EQ(con.parent(1), 0u); + EXPECT_EQ(con.parent(2), 0u); + EXPECT_EQ(con.parent(3), 1u); + EXPECT_EQ(con.parent(4), 1u); EXPECT_EQ(con.num_children(0), 2u); EXPECT_EQ(con.num_children(1), 2u); EXPECT_EQ(con.num_children(2), 0u); diff --git a/tests/unit/test_cell_group.cpp b/tests/unit/test_cell_group.cpp index d9787a1935657391be82f2e674b13bab9ef908ed..3aed222029f4535a85d9713849cff184a3684531 100644 --- a/tests/unit/test_cell_group.cpp +++ b/tests/unit/test_cell_group.cpp @@ -1,16 +1,12 @@ -#include <limits> - #include "gtest.h" +#include <common_types.hpp> #include <fvm_cell.hpp> #include <cell_group.hpp> nest::mc::cell make_cell() { using namespace nest::mc; - // setup global state for the mechanisms - mechanisms::setup_mechanism_helpers(); - nest::mc::cell cell; // Soma with diameter 12.6157 um and HH channel @@ -24,10 +20,8 @@ nest::mc::cell make_cell() { dendrite->mechanism("membrane").set("r_L", 100); - // add stimulus - cell.add_stimulus({1,1}, {5., 80., 0.3}); - - cell.add_detector({0,0}, 0); + cell.add_detector({0, 0}, 0); + cell.add_stimulus({1, 1}, {5., 80., 0.3}); return cell; } @@ -36,13 +30,45 @@ TEST(cell_group, test) { using namespace nest::mc; - using cell_type = cell_group<fvm::fvm_cell<double, int>>; - - auto cell = cell_type{make_cell()}; + using cell_group_type = cell_group<fvm::fvm_cell<double, cell_local_size_type>>; + auto group = cell_group_type{0, make_cell()}; - cell.advance(50, 0.01); + group.advance(50, 0.01); // a bit lame... - EXPECT_EQ(cell.spikes().size(), 4u); + EXPECT_EQ(group.spikes().size(), 4u); } +TEST(cell_group, sources) +{ + using namespace nest::mc; + + // TODO: extend to multi-cell cell groups when the time comes + + using cell_group_type = cell_group<fvm::fvm_cell<double, cell_local_size_type>>; + + auto cell = make_cell(); + EXPECT_EQ(cell.detectors().size(), 1u); + // add another detector on the cell to make things more interesting + cell.add_detector({1, 0.3}, 2.3); + + cell_gid_type first_gid = 37u; + auto group = cell_group_type{first_gid, cell}; + + // expect group sources to be lexicographically sorted by source id + // with gids in cell group's range and indices starting from zero + + const auto& sources = group.spike_sources(); + for (unsigned i = 0; i<sources.size(); ++i) { + auto id = sources[i].source_id; + if (i==0) { + EXPECT_EQ(id.gid, first_gid); + EXPECT_EQ(id.index, 0u); + } + else { + auto prev = sources[i-1].source_id; + EXPECT_GT(id, prev); + EXPECT_EQ(id.index, id.gid==prev.gid? prev.index+1: 0u); + } + } +} diff --git a/tests/unit/test_compartments.cpp b/tests/unit/test_compartments.cpp index c109f9cda9a335ce32f695f85ccd0295446a5d92..c167b16cce5585a43efc13fc9f136025b21f7811 100644 --- a/tests/unit/test_compartments.cpp +++ b/tests/unit/test_compartments.cpp @@ -15,13 +15,13 @@ TEST(compartments, compartment) { nest::mc::compartment c(100, 1.2, 2.1, 2.2); - EXPECT_EQ(c.index, 100); + EXPECT_EQ(c.index, 100u); EXPECT_EQ(c.length, 1.2); EXPECT_EQ(left(c.radius), 2.1); EXPECT_EQ(right(c.radius), 2.2); auto c2 = c; - EXPECT_EQ(c2.index, 100); + EXPECT_EQ(c2.index, 100u); EXPECT_EQ(c2.length, 1.2); EXPECT_EQ(left(c2.radius), 2.1); EXPECT_EQ(right(c2.radius), 2.2); @@ -29,7 +29,7 @@ TEST(compartments, compartment) { nest::mc::compartment c{100, 1, 2, 3}; - EXPECT_EQ(c.index, 100); + EXPECT_EQ(c.index, 100u); EXPECT_EQ(c.length, 1.); EXPECT_EQ(left(c.radius), 2.); EXPECT_EQ(right(c.radius), 3.); @@ -54,7 +54,7 @@ TEST(compartments, compartment_iterator) ++it; { auto c = *it; - EXPECT_EQ(c.index, 1); + EXPECT_EQ(c.index, 1u); EXPECT_EQ(left(c.radius), 3.0); EXPECT_EQ(right(c.radius), 5.0); EXPECT_EQ(c.length, 2.5); @@ -65,7 +65,7 @@ TEST(compartments, compartment_iterator) // returned iterator should be unchanged { auto c = *(it++); - EXPECT_EQ(c.index, 1); + EXPECT_EQ(c.index, 1u); EXPECT_EQ(left(c.radius), 3.0); EXPECT_EQ(right(c.radius), 5.0); EXPECT_EQ(c.length, 2.5); @@ -73,7 +73,7 @@ TEST(compartments, compartment_iterator) // while the iterator itself was updated { auto c = *it; - EXPECT_EQ(c.index, 2); + EXPECT_EQ(c.index, 2u); EXPECT_EQ(left(c.radius), 5.0); EXPECT_EQ(right(c.radius), 7.0); EXPECT_EQ(c.length, 2.5); @@ -84,7 +84,7 @@ TEST(compartments, compartment_iterator) // copy iterator auto it2 = it; auto c = *it2; - EXPECT_EQ(c.index, 2); + EXPECT_EQ(c.index, 2u); EXPECT_EQ(left(c.radius), 5.0); EXPECT_EQ(right(c.radius), 7.0); EXPECT_EQ(c.length, 2.5); @@ -96,7 +96,7 @@ TEST(compartments, compartment_iterator) // check the copy has updated correctly when incremented c= *it2; - EXPECT_EQ(c.index, 3); + EXPECT_EQ(c.index, 3u); EXPECT_EQ(left(c.radius), 7.0); EXPECT_EQ(right(c.radius), 9.0); EXPECT_EQ(c.length, 2.5); @@ -108,11 +108,11 @@ TEST(compartments, compartment_range) { nest::mc::compartment_range rng(10, 1.0, 2.0, 10.); - EXPECT_EQ((*rng.begin()).index, 0); - EXPECT_EQ((*rng.end()).index, 10); + EXPECT_EQ((*rng.begin()).index, 0u); + EXPECT_EQ((*rng.end()).index, 10u); EXPECT_NE(rng.begin(), rng.end()); - auto count = 0; + unsigned count = 0; for(auto c : rng) { EXPECT_EQ(c.index, count); auto er = 1.0 + double(count)/10.; @@ -121,7 +121,7 @@ TEST(compartments, compartment_range) EXPECT_EQ(c.length, 1.0); ++count; } - EXPECT_EQ(count, 10); + EXPECT_EQ(count, 10u); } // test case of zero length range diff --git a/tests/unit/test_counter.cpp b/tests/unit/test_counter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7c97f2277e08a865ebb0be0a7e7b9d4db18f7360 --- /dev/null +++ b/tests/unit/test_counter.cpp @@ -0,0 +1,131 @@ +#include "gtest.h" + +#include <iterator> +#include <type_traits> + +#include <util/counter.hpp> + +using namespace nest::mc; + +template <typename V> +class counter_test: public ::testing::Test {}; + +TYPED_TEST_CASE_P(counter_test); + +TYPED_TEST_P(counter_test, value) { + using int_type = TypeParam; + using counter = util::counter<int_type>; + + counter c0; + EXPECT_EQ(int_type{0}, *c0); + + counter c1{int_type{1}}; + counter c2{int_type{2}}; + + EXPECT_EQ(int_type{1}, *c1); + EXPECT_EQ(int_type{2}, *c2); + + c2 = c1; + EXPECT_EQ(int_type{1}, *c2); + + c2 = int_type{2}; + EXPECT_EQ(int_type{2}, *c2); +} + +TYPED_TEST_P(counter_test, compare) { + using int_type = TypeParam; + using counter = util::counter<int_type>; + + counter c1{int_type{1}}; + counter c2{int_type{2}}; + + EXPECT_LT(c1, c2); + EXPECT_LE(c1, c2); + EXPECT_NE(c1, c2); + EXPECT_GE(c2, c1); + EXPECT_GT(c2, c1); + + counter c1bis{int_type{1}}; + + EXPECT_LE(c1, c1bis); + EXPECT_EQ(c1, c1bis); + EXPECT_GE(c1, c1bis); +} + +TYPED_TEST_P(counter_test, arithmetic) { + using int_type = TypeParam; + using counter = util::counter<int_type>; + + counter c1{int_type{1}}; + counter c2{int_type{10}}; + int_type nine{9}; + + EXPECT_EQ(nine, c2-c1); + EXPECT_EQ(c2, c1+nine); + EXPECT_EQ(c2, nine+c1); + EXPECT_EQ(c1, c2-nine); + + counter c3 = c1; + counter c4 = (c3 += nine); + + EXPECT_EQ(c2, c3); + EXPECT_EQ(c3, c4); + + c3 = c2; + c4 = (c3 -= nine); + + EXPECT_EQ(c1, c3); + EXPECT_EQ(c3, c4); + + c3 = c1; + EXPECT_EQ(counter{2}, ++c3); + EXPECT_EQ(counter{3}, ++c3); + EXPECT_EQ(counter{2}, --c3); + EXPECT_EQ(counter{1}, --c3); + + c3 = c1; + EXPECT_EQ(counter{1}, c3++); + EXPECT_EQ(counter{2}, c3++); + EXPECT_EQ(counter{3}, c3--); + EXPECT_EQ(counter{2}, c3--); + + EXPECT_EQ(int_type{10}, c2[0]); + EXPECT_EQ(int_type{4}, c2[-6]); + EXPECT_EQ(int_type{19}, c2[9]); +} + +TYPED_TEST_P(counter_test, iterator_traits) { + using int_type = TypeParam; + using counter = util::counter<int_type>; + using traits = std::iterator_traits<counter>; + + typename traits::reference r = *counter{int_type{3}}; + EXPECT_EQ(r, int_type{3}); + + typename traits::difference_type d = counter{int_type{4}} - counter{int_type{7}}; + EXPECT_EQ(typename traits::difference_type(-3), d); + + EXPECT_TRUE((std::is_same<std::random_access_iterator_tag, typename traits::iterator_category>::value)); +} + +TYPED_TEST_P(counter_test, iterator_functions) { + using int_type = TypeParam; + using counter = util::counter<int_type>; + + counter c1{int_type{1}}; + counter c2{int_type{10}}; + + EXPECT_EQ(int_type{9}, std::distance(c1,c2)); + counter c3{c1}; + std::advance(c3, int_type{9}); + EXPECT_EQ(c2, c3); + + EXPECT_EQ(counter{int_type{2}}, std::next(c1)); + EXPECT_EQ(counter{int_type{9}}, std::prev(c2)); +} + +REGISTER_TYPED_TEST_CASE_P(counter_test, value, compare, arithmetic, iterator_traits, iterator_functions); + +using int_types = ::testing::Types<signed char, unsigned char, short, unsigned short, int, unsigned, std::size_t, std::ptrdiff_t>; +INSTANTIATE_TYPED_TEST_CASE_P(int_types, counter_test, int_types); + diff --git a/tests/unit/test_double_buffer.cpp b/tests/unit/test_double_buffer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1689cc2faac17331694c78830c22cd1ea57777fe --- /dev/null +++ b/tests/unit/test_double_buffer.cpp @@ -0,0 +1,58 @@ +#include "gtest.h" + +#include <util/double_buffer.hpp> + +// not much to test here: just test that values passed into the constructor +// are correctly stored in members +TEST(double_buffer, exchange_and_get) +{ + using namespace nest::mc::util; + + double_buffer<int> buf; + + buf.get() = 2134; + buf.exchange(); + buf.get() = 8990; + buf.exchange(); + + EXPECT_EQ(buf.get(), 2134); + EXPECT_EQ(buf.other(), 8990); + buf.exchange(); + EXPECT_EQ(buf.get(), 8990); + EXPECT_EQ(buf.other(), 2134); + buf.exchange(); + EXPECT_EQ(buf.get(), 2134); + EXPECT_EQ(buf.other(), 8990); +} + +TEST(double_buffer, assign_get_other) +{ + using namespace nest::mc::util; + + double_buffer<std::string> buf; + + buf.get() = "1"; + buf.other() = "2"; + + EXPECT_EQ(buf.get(), "1"); + EXPECT_EQ(buf.other(), "2"); +} + +TEST(double_buffer, non_pod) +{ + using namespace nest::mc::util; + + double_buffer<std::string> buf; + + buf.get() = "1"; + buf.other() = "2"; + + EXPECT_EQ(buf.get(), "1"); + EXPECT_EQ(buf.other(), "2"); + buf.exchange(); + EXPECT_EQ(buf.get(), "2"); + EXPECT_EQ(buf.other(), "1"); + buf.exchange(); + EXPECT_EQ(buf.get(), "1"); + EXPECT_EQ(buf.other(), "2"); +} diff --git a/tests/unit/test_either.cpp b/tests/unit/test_either.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4d185d8bf5eea7c7f5d0e75395d6f7e6d0f46f53 --- /dev/null +++ b/tests/unit/test_either.cpp @@ -0,0 +1,64 @@ +#include <typeinfo> +#include <array> +#include <algorithm> + +#include "gtest.h" +#include "util/either.hpp" + +// TODO: coverage! + +using namespace nest::mc::util; + +TEST(either, basic) { + either<int, std::string> e0(17); + + EXPECT_TRUE(e0); + EXPECT_EQ(17, e0.get<0>()); + EXPECT_EQ(e0.unsafe_get<0>(), e0.get<0>()); + EXPECT_EQ(e0.unsafe_get<0>(), e0.first()); + EXPECT_THROW(e0.get<1>(), either_invalid_access); + either<int, std::string> e1("seventeen"); + + EXPECT_FALSE(e1); + EXPECT_EQ("seventeen", e1.get<1>()); + EXPECT_EQ(e1.unsafe_get<1>(), e1.get<1>()); + EXPECT_EQ(e1.unsafe_get<1>(), e1.second()); + EXPECT_THROW(e1.get<0>(), either_invalid_access); + + e0 = e1; + EXPECT_EQ("seventeen", e0.get<1>()); + EXPECT_THROW(e0.get<0>(), either_invalid_access); + + e0 = 19; + EXPECT_EQ(19, e0.get<0>()); +} + +struct no_copy { + int value; + + no_copy(): value(23) {} + explicit no_copy(int v): value(v) {} + no_copy(const no_copy&) = delete; + no_copy(no_copy&&) = default; + + no_copy& operator=(const no_copy&) = delete; + no_copy& operator=(no_copy&&) = default; +}; + +TEST(either, no_copy) { + either<no_copy, std::string> e0(no_copy{17}); + + EXPECT_TRUE(e0); + + either<no_copy, std::string> e1(std::move(e0)); + + EXPECT_TRUE(e1); + + either<no_copy, std::string> e2; + EXPECT_TRUE(e2); + EXPECT_EQ(23, e2.get<0>().value); + + e2 = std::move(e1); + EXPECT_TRUE(e2); + EXPECT_EQ(17, e2.get<0>().value); +} diff --git a/tests/unit/test_event_queue.cpp b/tests/unit/test_event_queue.cpp index 5ac4c75747d761638b0bbd4b90a9b6fed6fa3c18..f4e31b26715fa2aa3cb690720442cc68f2b18fe2 100644 --- a/tests/unit/test_event_queue.cpp +++ b/tests/unit/test_event_queue.cpp @@ -7,14 +7,14 @@ TEST(event_queue, push) { using namespace nest::mc; - using ps_event_queue = event_queue<postsynaptic_spike_event>; + using ps_event_queue = event_queue<postsynaptic_spike_event<float>>; ps_event_queue q; - q.push({1u, 2.f, 2.f}); - q.push({4u, 1.f, 2.f}); - q.push({8u, 20.f, 2.f}); - q.push({2u, 8.f, 2.f}); + q.push({{1u, 0u}, 2.f, 2.f}); + q.push({{4u, 1u}, 1.f, 2.f}); + q.push({{8u, 2u}, 20.f, 2.f}); + q.push({{2u, 3u}, 8.f, 2.f}); std::vector<float> times; while(q.size()) { @@ -31,13 +31,13 @@ TEST(event_queue, push) TEST(event_queue, push_range) { using namespace nest::mc; - using ps_event_queue = event_queue<postsynaptic_spike_event>; + using ps_event_queue = event_queue<postsynaptic_spike_event<float>>; - postsynaptic_spike_event events[] = { - {1u, 2.f, 2.f}, - {4u, 1.f, 2.f}, - {8u, 20.f, 2.f}, - {2u, 8.f, 2.f} + postsynaptic_spike_event<float> events[] = { + {{1u, 0u}, 2.f, 2.f}, + {{4u, 1u}, 1.f, 2.f}, + {{8u, 2u}, 20.f, 2.f}, + {{2u, 3u}, 8.f, 2.f} }; ps_event_queue q; @@ -56,13 +56,20 @@ TEST(event_queue, push_range) TEST(event_queue, pop_if_before) { using namespace nest::mc; - using ps_event_queue = event_queue<postsynaptic_spike_event>; + using ps_event_queue = event_queue<postsynaptic_spike_event<float>>; - postsynaptic_spike_event events[] = { - {1u, 1.f, 2.f}, - {2u, 2.f, 2.f}, - {3u, 3.f, 2.f}, - {4u, 4.f, 2.f} + cell_member_type target[4] = { + {1u, 0u}, + {4u, 1u}, + {8u, 2u}, + {2u, 3u} + }; + + postsynaptic_spike_event<float> events[] = { + {target[0], 1.f, 2.f}, + {target[1], 2.f, 2.f}, + {target[2], 3.f, 2.f}, + {target[3], 4.f, 2.f} }; ps_event_queue q; @@ -76,12 +83,12 @@ TEST(event_queue, pop_if_before) auto e2 = q.pop_if_before(5.); EXPECT_TRUE(e2); - EXPECT_EQ(e2->target, 1u); + EXPECT_EQ(e2->target, target[0]); EXPECT_EQ(q.size(), 3u); auto e3 = q.pop_if_before(5.); EXPECT_TRUE(e3); - EXPECT_EQ(e3->target, 2u); + EXPECT_EQ(e3->target, target[1]); EXPECT_EQ(q.size(), 2u); auto e4 = q.pop_if_before(2.5); @@ -90,7 +97,7 @@ TEST(event_queue, pop_if_before) auto e5 = q.pop_if_before(5.); EXPECT_TRUE(e5); - EXPECT_EQ(e5->target, 3u); + EXPECT_EQ(e5->target, target[2]); EXPECT_EQ(q.size(), 1u); q.pop_if_before(5.); diff --git a/tests/unit/test_fvm.cpp b/tests/unit/test_fvm.cpp index 1383167aafec9b0c0ae36f2bd6ab8661803d2fe5..39fdb6c837f71820c3fd6981f9be2bc38cee91c8 100644 --- a/tests/unit/test_fvm.cpp +++ b/tests/unit/test_fvm.cpp @@ -1,10 +1,13 @@ #include <fstream> #include "gtest.h" -#include "../test_util.hpp" +#include <common_types.hpp> #include <cell.hpp> #include <fvm_cell.hpp> +#include <util/singleton.hpp> + +#include "../test_util.hpp" TEST(fvm, cable) { @@ -12,9 +15,6 @@ TEST(fvm, cable) nest::mc::cell cell; - // setup global state for the mechanisms - nest::mc::mechanisms::setup_mechanism_helpers(); - cell.add_soma(6e-4); // 6um in cm // 1um radius and 4mm long, all in cm @@ -42,8 +42,15 @@ TEST(fvm, cable) cell.segment(1)->set_compartments(4); cell.segment(2)->set_compartments(4); - using fvm_cell = fvm::fvm_cell<double, int>; - fvm_cell fvcell(cell); + using fvm_cell = fvm::fvm_cell<double, cell_lid_type>; + + std::vector<fvm_cell::target_handle> targets; + std::vector<fvm_cell::detector_handle> detectors; + std::vector<fvm_cell::probe_handle> probes; + + fvm_cell fvcell; + fvcell.initialize(util::singleton_view(cell), detectors, targets, probes); + auto& J = fvcell.jacobian(); EXPECT_EQ(cell.num_compartments(), 9u); @@ -64,9 +71,6 @@ TEST(fvm, init) nest::mc::cell cell; - // setup global state for the mechanisms - nest::mc::mechanisms::setup_mechanism_helpers(); - cell.add_soma(12.6157/2.0); //auto& props = cell.soma()->properties; @@ -95,8 +99,14 @@ TEST(fvm, init) cell.segment(1)->set_compartments(10); - using fvm_cell = fvm::fvm_cell<double, int>; - fvm_cell fvcell(cell); + using fvm_cell = fvm::fvm_cell<double, cell_lid_type>; + std::vector<fvm_cell::target_handle> targets; + std::vector<fvm_cell::detector_handle> detectors; + std::vector<fvm_cell::probe_handle> probes; + + fvm_cell fvcell; + fvcell.initialize(util::singleton_view(cell), detectors, targets, probes); + auto& J = fvcell.jacobian(); EXPECT_EQ(J.size(), 11u); diff --git a/tests/unit/test_lexcmp.cpp b/tests/unit/test_lexcmp.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e2b5c476d7983f7726349953d15e34509aab031c --- /dev/null +++ b/tests/unit/test_lexcmp.cpp @@ -0,0 +1,110 @@ +#include "gtest.h" + +#include <util/lexcmp_def.hpp> + +struct lexcmp_test_one { + int foo; +}; + +DEFINE_LEXICOGRAPHIC_ORDERING(lexcmp_test_one, (a.foo), (b.foo)) + +TEST(lexcmp_def,one) { + lexcmp_test_one p{3}, q{4}, r{4}; + + EXPECT_LE(p,q); + EXPECT_LT(p,q); + EXPECT_NE(p,q); + EXPECT_GE(q,p); + EXPECT_GT(q,p); + + EXPECT_LE(q,r); + EXPECT_GE(q,r); + EXPECT_EQ(q,r); +} + +struct lexcmp_test_three { + int x; + std::string y; + double z; +}; + +// test fields in reverse order: z, y, x +DEFINE_LEXICOGRAPHIC_ORDERING(lexcmp_test_three, (a.z,a.y,a.x), (b.z,b.y,b.x)) + +TEST(lexcmp_def,three) { + lexcmp_test_three p{1,"foo",2}; + lexcmp_test_three q{1,"foo",3}; + lexcmp_test_three r{1,"bar",2}; + lexcmp_test_three s{5,"foo",2}; + + EXPECT_LE(p,q); + EXPECT_LT(p,q); + EXPECT_NE(p,q); + EXPECT_GE(q,p); + EXPECT_GT(q,p); + + EXPECT_LE(r,p); + EXPECT_LT(r,p); + EXPECT_NE(p,r); + EXPECT_GE(p,r); + EXPECT_GT(p,r); + + EXPECT_LE(p,s); + EXPECT_LT(p,s); + EXPECT_NE(p,s); + EXPECT_GE(s,p); + EXPECT_GT(s,p); +} + +// test fields accessed by reference-returning member function + +class lexcmp_test_refmemfn { +public: + explicit lexcmp_test_refmemfn(int foo): foo_(foo) {} + + const int& foo() const { return foo_; } + int& foo() { return foo_; } + +private: + int foo_; +}; + +DEFINE_LEXICOGRAPHIC_ORDERING(lexcmp_test_refmemfn, (a.foo()), (b.foo())) + +TEST(lexcmp_def,refmemfn) { + lexcmp_test_refmemfn p{3}; + const lexcmp_test_refmemfn q{4}; + + EXPECT_LE(p,q); + EXPECT_LT(p,q); + EXPECT_NE(p,q); + EXPECT_GE(q,p); + EXPECT_GT(q,p); +} + +// test comparison via proxy tuple object + +class lexcmp_test_valmemfn { +public: + explicit lexcmp_test_valmemfn(int foo, int bar): foo_(foo), bar_(bar) {} + int foo() const { return foo_; } + int bar() const { return bar_; } + +private: + int foo_; + int bar_; +}; + +DEFINE_LEXICOGRAPHIC_ORDERING_BY_VALUE(lexcmp_test_valmemfn, (a.foo(),a.bar()), (b.foo(),b.bar())) + +TEST(lexcmp_def,proxy) { + lexcmp_test_valmemfn p{3,2}, q{3,4}; + + EXPECT_LE(p,q); + EXPECT_LT(p,q); + EXPECT_NE(p,q); + EXPECT_GE(q,p); + EXPECT_GT(q,p); +} + + diff --git a/tests/unit/test_mask_stream.cpp b/tests/unit/test_mask_stream.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8ca8089eab1f7486204ce0df70fb953000b301a0 --- /dev/null +++ b/tests/unit/test_mask_stream.cpp @@ -0,0 +1,68 @@ +#include "gtest.h" + +#include <sstream> + +#include <util/ioutil.hpp> + +using namespace nest::mc::util; + +TEST(mask_stream,nomask) { + // expect mask_stream(true) on a new stream not to change rdbuf. + std::ostringstream s; + auto sbuf = s.rdbuf(); + s << mask_stream(true); + EXPECT_EQ(sbuf, s.rdbuf()); +} + +TEST(mask_stream,mask) { + // masked stream should produce no ouptut + std::ostringstream s; + s << "one"; + s << mask_stream(false); + + s << "two"; + EXPECT_EQ(s.str(), "one"); + + s << mask_stream(true); + s << "three"; + EXPECT_EQ(s.str(), "onethree"); +} + +TEST(mask_stream,mask_multi) { + // mark_stream(false) should be idempotent + + std::ostringstream s; + auto sbuf1 = s.rdbuf(); + + s << "foo"; + s << mask_stream(false); + auto sbuf2 = s.rdbuf(); + + s << "bar"; + s << mask_stream(false); + auto sbuf3 = s.rdbuf(); + EXPECT_EQ(sbuf2, sbuf3); + + s << "baz"; + s << mask_stream(true); + auto sbuf4 = s.rdbuf(); + EXPECT_EQ(sbuf1, sbuf4); + + s << "xyzzy"; + EXPECT_EQ(s.str(), "fooxyzzy"); +} + +TEST(mask_stream,fmt) { + // expect formatting to be preserved across masks. + + std::ostringstream s; + s.precision(1); + + s << mask_stream(false); + EXPECT_EQ(s.precision(), 1); + s.precision(2); + + s << mask_stream(true); + EXPECT_EQ(s.precision(), 2); +} + diff --git a/tests/unit/test_mechanisms.cpp b/tests/unit/test_mechanisms.cpp index 2e31f1139d25cb3e36bc1ff0b32a3ec8a4316a4b..377f683fa8ff0831b0cdff3f94be96710d966d0c 100644 --- a/tests/unit/test_mechanisms.cpp +++ b/tests/unit/test_mechanisms.cpp @@ -1,25 +1,17 @@ #include "gtest.h" -#include "../src/mechanism_interface.hpp" -#include "../src/matrix.hpp" +//#include "../src/mechanism_interface.hpp" +#include "mechanism_catalogue.hpp" +#include "matrix.hpp" TEST(mechanisms, helpers) { - nest::mc::mechanisms::setup_mechanism_helpers(); - - EXPECT_EQ(nest::mc::mechanisms::mechanism_helpers.size(), 4u); + using namespace nest::mc; + using catalogue = mechanisms::catalogue<double, int>; // verify that the hh and pas channels are available - EXPECT_EQ(nest::mc::mechanisms::get_mechanism_helper("hh")->name(), "hh"); - EXPECT_EQ(nest::mc::mechanisms::get_mechanism_helper("pas")->name(), "pas"); - - // check that an out_of_range exception is thrown if an invalid mechanism is - // requested - ASSERT_THROW( - nest::mc::mechanisms::get_mechanism_helper("dachshund"), - std::out_of_range - ); + EXPECT_TRUE(catalogue::has("hh")); + EXPECT_TRUE(catalogue::has("pas")); - //0 1 2 3 4 5 6 7 8 9 std::vector<int> parent_index = {0,0,1,2,3,4,0,6,7,8}; memory::HostVector<int> node_indices = std::vector<int>{0,6,7,8,9}; auto n = node_indices.size(); @@ -28,10 +20,17 @@ TEST(mechanisms, helpers) { memory::HostVector<double> vec_i(n, 0.); memory::HostVector<double> vec_v(n, 0.); - auto& helper = nest::mc::mechanisms::get_mechanism_helper("hh"); - auto mech = helper->new_mechanism(vec_v, vec_i, node_indices); + auto mech = catalogue::make("hh", vec_v, vec_i, node_indices); EXPECT_EQ(mech->name(), "hh"); EXPECT_EQ(mech->size(), 5u); //EXPECT_EQ(mech->matrix_, &matrix); + + // check that an out_of_range exception is thrown if an invalid mechanism is + // requested + ASSERT_THROW( + catalogue::make("dachshund", vec_v, vec_i, node_indices), + std::out_of_range + ); + //0 1 2 3 4 5 6 7 8 9 } diff --git a/tests/unit/test_nop.cpp b/tests/unit/test_nop.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0ee03db24fb4fccfba67dea587cc3ad83c2e3a30 --- /dev/null +++ b/tests/unit/test_nop.cpp @@ -0,0 +1,75 @@ +#include "gtest.h" +#include "util/nop.hpp" + +using namespace nest::mc::util; + +TEST(nop, void_fn) { + std::function<void ()> f(nop_function); + + EXPECT_TRUE(f); + f(); // should do nothing + + bool flag = false; + f = [&]() { flag = true; }; + f(); + EXPECT_TRUE(flag); + + flag = false; + f = nop_function; + f(); + EXPECT_FALSE(flag); + + // with some arguments + std::function<void (int, int)> g(nop_function); + EXPECT_TRUE(g); + g(2, 3); // should do nothing + + int sum = 0; + g = [&](int a, int b) { sum = a+b; }; + g(2, 3); + EXPECT_EQ(5, sum); + + sum = 0; + g = nop_function; + g(2, 3); + EXPECT_EQ(0, sum); +} + +struct check_default { + int value = 100; + + check_default() = default; + explicit check_default(int n): value(n) {} +}; + +TEST(nop, default_return_fn) { + std::function<check_default ()> f(nop_function); + + EXPECT_TRUE(f); + auto result = f(); + EXPECT_EQ(result.value, 100); + + f = []() { return check_default(17); }; + result = f(); + EXPECT_EQ(result.value, 17); + + f = nop_function; + result = f(); + EXPECT_EQ(result.value, 100); + + std::function<check_default (double, double)> g(nop_function); + + EXPECT_TRUE(g); + result = g(1.4, 1.5); + EXPECT_EQ(result.value, 100); + + g = [](double x, double y) { return check_default{(int)(x*y)}; }; + result = g(1.4, 1.5); + EXPECT_EQ(result.value, 2); + + g = nop_function; + result = g(1.4, 1.5); + EXPECT_EQ(result.value, 100); + +} + diff --git a/tests/unit/test_optional.cpp b/tests/unit/test_optional.cpp index 35b74a470cea3e82a230592454126d04ce092da9..0c7758bcf84708cc873827bad5d33cceceabbf18 100644 --- a/tests/unit/test_optional.cpp +++ b/tests/unit/test_optional.cpp @@ -24,24 +24,30 @@ TEST(optionalm,unset_throw) { optional<int> a; int check=10; - try { a.get(); } - catch(optional_unset_error &e) { + try { + a.get(); + } + catch (optional_unset_error& e) { ++check; } EXPECT_EQ(11,check); check=20; a=2; - try { a.get(); } - catch(optional_unset_error &e) { + try { + a.get(); + } + catch (optional_unset_error& e) { ++check; } EXPECT_EQ(20,check); check=30; a.reset(); - try { a.get(); } - catch(optional_unset_error &e) { + try { + a.get(); + } + catch (optional_unset_error& e) { ++check; } EXPECT_EQ(31,check); @@ -66,13 +72,13 @@ TEST(optionalm,ctor_conv) { TEST(optionalm,ctor_ref) { int v=10; - optional<int &> a(v); + optional<int&> a(v); EXPECT_EQ(10,a.get()); v=20; EXPECT_EQ(20,a.get()); - optional<int &> b(a),c=b,d=v; + optional<int&> b(a),c=b,d=v; EXPECT_EQ(&(a.get()),&(b.get())); EXPECT_EQ(&(a.get()),&(c.get())); EXPECT_EQ(&(a.get()),&(d.get())); @@ -86,6 +92,18 @@ TEST(optionalm,assign_returns) { auto bp=&(a=4); EXPECT_EQ(&a,bp); + + auto b2=(a=optional<int>(10)); + EXPECT_EQ(typeid(optional<int>),typeid(b2)); + + auto bp2=&(a=4); + EXPECT_EQ(&a,bp2); + + auto b3=(a=nothing); + EXPECT_EQ(typeid(optional<int>),typeid(b3)); + + auto bp3=&(a=4); + EXPECT_EQ(&a,bp3); } TEST(optionalm,assign_reference) { @@ -98,11 +116,16 @@ TEST(optionalm,assign_reference) { *ar = 5.0; EXPECT_EQ(5.0, a); - br = ar; + auto& check_rval=(br=ar); EXPECT_TRUE(br); + EXPECT_EQ(&br, &check_rval); *br = 7.0; EXPECT_EQ(7.0, a); + + auto& check_rval2=(br=nothing); + EXPECT_FALSE(br); + EXPECT_EQ(&br, &check_rval2); } struct nomove { @@ -110,13 +133,13 @@ struct nomove { nomove(): value(0) {} nomove(int i): value(i) {} - nomove(const nomove &n): value(n.value) {} - nomove(nomove &&n) = delete; + nomove(const nomove& n): value(n.value) {} + nomove(nomove&& n) = delete; - nomove &operator=(const nomove &n) { value=n.value; return *this; } + nomove& operator=(const nomove& n) { value=n.value; return *this; } - bool operator==(const nomove &them) const { return them.value==value; } - bool operator!=(const nomove &them) const { return !(*this==them); } + bool operator==(const nomove& them) const { return them.value==value; } + bool operator!=(const nomove& them) const { return !(*this==them); } }; TEST(optionalm,ctor_nomove) { @@ -136,21 +159,21 @@ struct nocopy { nocopy(): value(0) {} nocopy(int i): value(i) {} - nocopy(const nocopy &n) = delete; - nocopy(nocopy &&n) { + nocopy(const nocopy& n) = delete; + nocopy(nocopy&& n) { value=n.value; n.value=0; } - nocopy &operator=(const nocopy &n) = delete; - nocopy &operator=(nocopy &&n) { + nocopy& operator=(const nocopy& n) = delete; + nocopy& operator=(nocopy&& n) { value=n.value; n.value=-1; return *this; } - bool operator==(const nocopy &them) const { return them.value==value; } - bool operator!=(const nocopy &them) const { return !(*this==them); } + bool operator==(const nocopy& them) const { return them.value==value; } + bool operator!=(const nocopy& them) const { return !(*this==them); } }; TEST(optionalm,ctor_nocopy) { @@ -206,6 +229,10 @@ TEST(optionalm,void) { x=b >> []() { return 1; }; EXPECT_TRUE((bool)x); EXPECT_EQ(1,x.get()); + + auto& check_rval=(b=nothing); + EXPECT_FALSE((bool)b); + EXPECT_EQ(&b,&check_rval); } TEST(optionalm,bind_to_void) { @@ -251,13 +278,13 @@ TEST(optionalm,bind_to_optional_void) { TEST(optionalm,bind_with_ref) { optional<int> a=10; - a >> [](int &v) {++v; }; + a >> [](int& v) { ++v; }; EXPECT_EQ(11,*a); } struct check_cref { - int operator()(const int &) { return 10; } - int operator()(int &) { return 11; } + int operator()(const int&) { return 10; } + int operator()(int&) { return 11; } }; TEST(optionalm,bind_constness) { diff --git a/tests/unit/test_parameters.cpp b/tests/unit/test_parameters.cpp index 0781640489725a100b3e4ede3be40dfe168b1cef..a573e07cd9fce4581d2bd6b026e1499f94336d1d 100644 --- a/tests/unit/test_parameters.cpp +++ b/tests/unit/test_parameters.cpp @@ -28,7 +28,7 @@ TEST(parameters, setting) EXPECT_FALSE(list.add_parameter({"b", -3.0})); EXPECT_EQ(list.num_parameters(), 2); - auto &parms = list.parameters(); + auto& parms = list.parameters(); EXPECT_EQ(parms[0].name, "a"); EXPECT_EQ(parms[0].value, 0.12); EXPECT_EQ(parms[0].range.min, 0); diff --git a/tests/unit/test_partition.cpp b/tests/unit/test_partition.cpp new file mode 100644 index 0000000000000000000000000000000000000000..edfb95d1021ea452c1701d9a84ef2341ed17e48f --- /dev/null +++ b/tests/unit/test_partition.cpp @@ -0,0 +1,150 @@ +#include "gtest.h" + +#include <array> +#include <forward_list> +#include <string> +#include <vector> + +#include <util/debug.hpp> +#include <util/nop.hpp> +#include <util/partition.hpp> + +using namespace nest::mc; + +TEST(partition, partition_view) { + std::forward_list<int> fl = {1, 4, 6, 8, 10 }; + + auto p1 = util::partition_view(fl); + EXPECT_EQ(std::make_pair(1,4), p1.front()); + EXPECT_EQ(std::make_pair(8,10), p1.back()); + EXPECT_EQ(std::make_pair(1,10), p1.bounds()); + EXPECT_EQ(4u, p1.size()); + + std::vector<double> v = {2.0, 3.6, 7.5}; + + auto p2 = util::partition_view(v); + EXPECT_EQ(2u, p2.size()); + + std::vector<double> ends; + std::vector<double> ends_expected = { 2.0, 3.6, 3.6, 7.5 }; + for (auto b: p2) { + ends.push_back(b.first); + ends.push_back(b.second); + } + EXPECT_EQ(ends_expected, ends); +} + +TEST(partition, short_partition_view) { + int two_divs[] = {10, 15}; + EXPECT_EQ(1u, util::partition_view(two_divs).size()); + + int one_div[] = {10}; + EXPECT_EQ(0u, util::partition_view(one_div).size()); + + std::array<int, 0> zero_divs; + EXPECT_EQ(0u, util::partition_view(zero_divs).size()); +} + +TEST(partition, check_monotonicity) { + // override any EXPECTS checks in partition + util::global_failed_assertion_handler = util::ignore_failed_assertion; + + int divs_ok[] = {1, 2, 2, 3, 3}; + EXPECT_NO_THROW(util::partition_view(divs_ok).validate()); + + int divs_bad[] = {3, 2, 1}; + EXPECT_THROW(util::partition_view(divs_bad).validate(), util::invalid_partition); +} + +TEST(partition, partition_view_find) { + std::vector<double> divs = { 1, 2.5, 3, 5.5 }; + double eps = 0.1; + auto p = util::partition_view(divs); + + EXPECT_EQ(p.end(), p.find(divs.front()-eps)); + EXPECT_NE(p.end(), p.find(divs.front())); + EXPECT_EQ(divs.front(), p.find(divs.front())->first); + + EXPECT_NE(p.end(), p.find(divs.back()-eps)); + EXPECT_EQ(divs.back(), p.find(divs.back()-eps)->second); + EXPECT_EQ(p.end(), p.find(divs.back())); + EXPECT_EQ(p.end(), p.find(divs.back()+eps)); + + EXPECT_EQ(divs[1], p.find(divs[1]+eps)->first); + EXPECT_EQ(divs[2], p.find(divs[1]+eps)->second); +} + +TEST(partition, partition_view_non_numeric) { + std::string divs[] = { "a", "dictionary", "of", "sorted", "words" }; + auto p = util::partition_view(divs); + + EXPECT_EQ("dictionary", p.find("elephant")->first); +} + +TEST(partition, make_partition_in_place) { + unsigned sizes[] = { 7, 3, 0, 2 }; + unsigned part_store[util::size(sizes)+1]; + + auto p = util::make_partition(util::partition_in_place, part_store, sizes, 10u); + ASSERT_EQ(4u, p.size()); + EXPECT_EQ(std::make_pair(10u, 17u), p[0]); + EXPECT_EQ(std::make_pair(17u, 20u), p[1]); + EXPECT_EQ(std::make_pair(20u, 20u), p[2]); + EXPECT_EQ(std::make_pair(20u, 22u), p[3]); + + // with short sizes sequence + unsigned short_sizes[] = { 1, 2 }; + p = util::make_partition(util::partition_in_place, part_store, short_sizes, 0u); + ASSERT_EQ(4u, p.size()); + EXPECT_EQ(std::make_pair(0u, 1u), p[0]); + EXPECT_EQ(std::make_pair(1u, 3u), p[1]); + EXPECT_EQ(std::make_pair(3u, 3u), p[2]); + EXPECT_EQ(std::make_pair(3u, 3u), p[3]); + + // with longer sizes sequence + unsigned long_sizes[] = {1, 2, 3, 4, 5, 6}; + p = util::make_partition(util::partition_in_place, part_store, long_sizes, 0u); + ASSERT_EQ(4u, p.size()); + EXPECT_EQ(std::make_pair(0u, 1u), p[0]); + EXPECT_EQ(std::make_pair(1u, 3u), p[1]); + EXPECT_EQ(std::make_pair(3u, 6u), p[2]); + EXPECT_EQ(std::make_pair(6u, 10u), p[3]); + + // with empty sizes sequence + std::array<unsigned, 0> no_sizes; + p = util::make_partition(util::partition_in_place, part_store, no_sizes, 17u); + ASSERT_EQ(4u, p.size()); + EXPECT_EQ(std::make_pair(17u, 17u), p[0]); + EXPECT_EQ(std::make_pair(17u, 17u), p[1]); + EXPECT_EQ(std::make_pair(17u, 17u), p[2]); + EXPECT_EQ(std::make_pair(17u, 17u), p[3]); + + // with short partition containers + unsigned part_store_one[1]; + p = util::make_partition(util::partition_in_place, part_store_one, sizes, 10u); + ASSERT_EQ(0u, p.size()); + ASSERT_TRUE(p.empty()); + + std::array<unsigned,0> part_store_zero; + p = util::make_partition(util::partition_in_place, part_store_zero, sizes, 10u); + ASSERT_EQ(0u, p.size()); + ASSERT_TRUE(p.empty()); +} + +TEST(partition, make_partition) { + // (also tests differing types for sizes and divisiosn) + unsigned sizes[] = { 7, 3, 0, 2 }; + std::forward_list<double> part_store = { 100.3 }; + + auto p = util::make_partition(part_store, sizes, 10.0); + ASSERT_EQ(4u, p.size()); + + auto pi = p.begin(); + EXPECT_EQ(10.0, pi++->first); + EXPECT_EQ(17.0, pi++->first); + EXPECT_EQ(20.0, pi++->first); + EXPECT_EQ(20.0, pi->first); + EXPECT_EQ(22.0, pi->second); + + EXPECT_EQ(p.end(), ++pi); +} diff --git a/tests/unit/test_probe.cpp b/tests/unit/test_probe.cpp index 52ac4e7f62171bc556df5b0ba4e25f05b3304fa5..13a12ad6bd067ed91b37f2867f22b0f6405ba89a 100644 --- a/tests/unit/test_probe.cpp +++ b/tests/unit/test_probe.cpp @@ -1,7 +1,9 @@ #include "gtest.h" -#include "cell.hpp" -#include "fvm_cell.hpp" +#include <common_types.hpp> +#include <cell.hpp> +#include <fvm_cell.hpp> +#include <util/singleton.hpp> TEST(probe, instantiation) { @@ -12,13 +14,13 @@ TEST(probe, instantiation) segment_location loc1{0, 0}; segment_location loc2{1, 0.6}; - auto p1 = c1.add_probe(loc1, probeKind::membrane_voltage); - auto p2 = c1.add_probe(loc2, probeKind::membrane_current); + auto p1 = c1.add_probe({loc1, probeKind::membrane_voltage}); + auto p2 = c1.add_probe({loc2, probeKind::membrane_current}); // expect locally provided probe ids to be numbered sequentially from zero. - - EXPECT_EQ(0, p1); - EXPECT_EQ(1, p2); + + EXPECT_EQ(0u, p1); + EXPECT_EQ(1u, p2); // expect the probes() return to be a collection with these two probes. @@ -48,31 +50,35 @@ TEST(probe, fvm_cell) segment_location loc1{1, 1}; segment_location loc2{1, 0.5}; - auto pv0 = bs.add_probe(loc0, probeKind::membrane_voltage); - auto pv1 = bs.add_probe(loc1, probeKind::membrane_voltage); - auto pi2 = bs.add_probe(loc2, probeKind::membrane_current); - + bs.add_probe({loc0, probeKind::membrane_voltage}); + bs.add_probe({loc1, probeKind::membrane_voltage}); + bs.add_probe({loc2, probeKind::membrane_current}); + i_clamp stim(0, 100, 0.3); bs.add_stimulus({1, 1}, stim); - fvm::fvm_cell<double, int> lcell(bs); - lcell.setup_matrix(0.01); - lcell.initialize(); + using fvm_cell = fvm::fvm_cell<double, cell_lid_type>; + std::vector<fvm_cell::target_handle> targets; + std::vector<fvm_cell::detector_handle> detectors; + std::vector<fvm_cell::probe_handle> probes{3}; - EXPECT_EQ(3u, lcell.num_probes()); + fvm_cell lcell; + lcell.initialize(util::singleton_view(bs), detectors, targets, probes); - // expect probe values and direct queries of voltage and current + // Know from implementation that probe_handle.second + // is a compartment index: expect probe values and + // direct queries of voltage and current // to be equal in fvm cell - EXPECT_EQ(lcell.voltage(loc0), lcell.probe(pv0)); - EXPECT_EQ(lcell.voltage(loc1), lcell.probe(pv1)); - EXPECT_EQ(lcell.current(loc2), lcell.probe(pi2)); + EXPECT_EQ(lcell.voltage()[probes[0].second], lcell.probe(probes[0])); + EXPECT_EQ(lcell.voltage()[probes[1].second], lcell.probe(probes[1])); + EXPECT_EQ(lcell.current()[probes[2].second], lcell.probe(probes[2])); lcell.advance(0.05); - EXPECT_EQ(lcell.voltage(loc0), lcell.probe(pv0)); - EXPECT_EQ(lcell.voltage(loc1), lcell.probe(pv1)); - EXPECT_EQ(lcell.current(loc2), lcell.probe(pi2)); + EXPECT_EQ(lcell.voltage()[probes[0].second], lcell.probe(probes[0])); + EXPECT_EQ(lcell.voltage()[probes[1].second], lcell.probe(probes[1])); + EXPECT_EQ(lcell.current()[probes[2].second], lcell.probe(probes[2])); } diff --git a/tests/unit/test_range.cpp b/tests/unit/test_range.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7847212e3f0b0546c1f974dbdebab3303a15ea1d --- /dev/null +++ b/tests/unit/test_range.cpp @@ -0,0 +1,171 @@ +#include "gtest.h" + +#include <algorithm> +#include <iterator> +#include <sstream> +#include <list> +#include <numeric> +#include <type_traits> + +#ifdef WITH_TBB +#include <tbb/tbb_stddef.h> +#endif + +#include <util/range.hpp> + +using namespace nest::mc; + +TEST(range, list_iterator) { + std::list<int> l = { 2, 4, 6, 8, 10 }; + + auto s = util::make_range(l.begin(), l.end()); + + EXPECT_EQ(s.left, l.begin()); + EXPECT_EQ(s.right, l.end()); + + EXPECT_EQ(s.begin(), l.begin()); + EXPECT_EQ(s.end(), l.end()); + + EXPECT_EQ(s.size(), l.size()); + EXPECT_EQ(s.front(), *l.begin()); + EXPECT_EQ(s.back(), *std::prev(l.end())); + + int check = std::accumulate(l.begin(), l.end(), 0); + int sum = 0; + for (auto i: s) { + sum += i; + } + + EXPECT_EQ(check, sum); + + auto sum2 = std::accumulate(s.begin(), s.end(), 0); + EXPECT_EQ(check, sum2); +} + +TEST(range, pointer) { + int xs[] = { 10, 11, 12, 13, 14, 15, 16 }; + int l = 2; + int r = 5; + + util::range<int *> s(&xs[l], &xs[r]); + auto s_deduced = util::make_range(xs+l, xs+r); + + EXPECT_TRUE((std::is_same<decltype(s), decltype(s_deduced)>::value)); + EXPECT_EQ(s.left, s_deduced.left); + EXPECT_EQ(s.right, s_deduced.right); + + EXPECT_EQ(3u, s.size()); + + EXPECT_EQ(xs[l], *s.left); + EXPECT_EQ(xs[l], *s.begin()); + EXPECT_EQ(xs[l], s[0]); + EXPECT_EQ(xs[l], s.front()); + + EXPECT_EQ(xs[r], *s.right); + EXPECT_EQ(xs[r], *s.end()); + EXPECT_THROW(s.at(r-l), std::out_of_range); + + EXPECT_EQ(r-l, std::distance(s.begin(), s.end())); + + EXPECT_TRUE(std::equal(s.begin(), s.end(), &xs[l])); +} + +TEST(range, input_iterator) { + int nums[] = { 10, 9, 8, 7, 6 }; + std::istringstream sin("10 9 8 7 6"); + auto s = util::make_range(std::istream_iterator<int>(sin), std::istream_iterator<int>()); + + EXPECT_TRUE(std::equal(s.begin(), s.end(), &nums[0])); +} + +TEST(range, const_iterator) { + std::vector<int> xs = { 1, 2, 3, 4, 5 }; + auto r = util::make_range(xs.begin(), xs.end()); + EXPECT_TRUE((std::is_same<int&, decltype(r.front())>::value)); + + const auto& xs_const = xs; + auto r_const = util::make_range(xs_const.begin(), xs_const.end()); + EXPECT_TRUE((std::is_same<const int&, decltype(r_const.front())>::value)); +} + +struct null_terminated_t { + bool operator==(const char *p) const { return !*p; } + bool operator!=(const char *p) const { return !!*p; } + + friend bool operator==(const char *p, null_terminated_t x) { + return x==p; + } + + friend bool operator!=(const char *p, null_terminated_t x) { + return x!=p; + } + + constexpr null_terminated_t() {} +}; + +constexpr null_terminated_t null_terminated; + +TEST(range, sentinel) { + const char *cstr = "hello world"; + std::string s; + + auto cstr_range = util::make_range(cstr, null_terminated); + for (auto i=cstr_range.begin(); i!=cstr_range.end(); ++i) { + s += *i; + } + + EXPECT_EQ(s, std::string(cstr)); + + s.clear(); + for (auto c: canonical_view(cstr_range)) { + s += c; + } + + EXPECT_EQ(s, std::string(cstr)); +} + +#ifdef WITH_TBB + +TEST(range, tbb_split) { + constexpr std::size_t N = 20; + int xs[N]; + + for (unsigned i = 0; i<N; ++i) { + xs[i] = i; + } + + auto s = util::make_range(&xs[0], &xs[0]+N); + + while (s.size()>1) { + auto ssize = s.size(); + auto r = decltype(s){s, tbb::split{}}; + EXPECT_GT(r.size(), 0u); + EXPECT_GT(s.size(), 0u); + EXPECT_EQ(ssize, r.size()+s.size()); + EXPECT_EQ(s.end(), r.begin()); + + EXPECT_TRUE(r.size()>1 || !r.is_divisible()); + EXPECT_TRUE(s.size()>1 || !s.is_divisible()); + } + + for (unsigned i = 1; i<N-1; ++i) { + s = util::make_range(&xs[0], &xs[0]+N); + // expect exact splitting by proportion in this instance + + auto r = decltype(s){s, tbb::proportional_split{i, N-i}}; + EXPECT_EQ(&xs[0], s.left); + EXPECT_EQ(&xs[0]+i, s.right); + EXPECT_EQ(&xs[0]+i, r.left); + EXPECT_EQ(&xs[0]+N, r.right); + } +} + +TEST(range, tbb_no_split) { + std::istringstream sin("10 9 8 7 6"); + auto s = util::make_range(std::istream_iterator<int>(sin), std::istream_iterator<int>()); + + EXPECT_FALSE(decltype(s)::is_splittable_in_proportion()); + EXPECT_FALSE(s.is_divisible()); +} + +#endif diff --git a/tests/unit/test_span.cpp b/tests/unit/test_span.cpp new file mode 100644 index 0000000000000000000000000000000000000000..edb55f7d65e7f0321add82de6e0879143050fede --- /dev/null +++ b/tests/unit/test_span.cpp @@ -0,0 +1,93 @@ +#include "gtest.h" + +#include <algorithm> +#include <iterator> +#include <list> +#include <numeric> +#include <type_traits> +#include <utility> + +#include <util/span.hpp> + +using namespace nest::mc; + +TEST(span, int_access) { + using span = util::span<int>; + + int n = 97; + int a = 3; + int b = a+n; + + span s(a, b); + EXPECT_EQ(s.left, a); + EXPECT_EQ(s.right, b); + + EXPECT_EQ(s.size(), std::size_t(n)); + + EXPECT_EQ(s.front(), a); + EXPECT_EQ(s.back(), b-1); + + EXPECT_EQ(s[0], a); + EXPECT_EQ(s[1], a+1); + EXPECT_EQ(s[n-1], b-1); + + EXPECT_NO_THROW(s.at(0)); + EXPECT_NO_THROW(s.at(n-1)); + EXPECT_THROW(s.at(n), std::out_of_range); + EXPECT_THROW(s.at(n+1), std::out_of_range); + EXPECT_THROW(s.at(-1), std::out_of_range); +} + +TEST(span, int_iterators) { + using span = util::span<int>; + + int n = 97; + int a = 3; + int b = a+n; + + span s(a, b); + + EXPECT_TRUE(util::is_iterator<span::iterator>::value); + EXPECT_TRUE(util::is_random_access_iterator<span::iterator>::value); + + EXPECT_EQ(n, std::distance(s.begin(), s.end())); + EXPECT_EQ(n, std::distance(s.cbegin(), s.cend())); + + int sum = 0; + for (auto i: span(a, b)) { + sum += i; + } + EXPECT_EQ(sum, (a+b-1)*(b-a)/2); +} + +TEST(span, make_span) { + auto s_empty = util::make_span(3, 3); + EXPECT_TRUE(s_empty.empty()); + + { + auto s = util::make_span((short)3, (unsigned long long)10); + auto first = s.front(); + auto last = s.back(); + + EXPECT_EQ(3u, first); + EXPECT_EQ(9u, last); + + EXPECT_TRUE((std::is_same<decltype(first), decltype(last)>::value)); + EXPECT_TRUE((std::is_same<unsigned long long, decltype(first)>::value)); + } + + { + // type abuse! should promote bool to long in span. + std::pair<long, bool> bounds(-3, false); + auto s = util::make_span(bounds); + auto first = s.front(); + auto last = s.back(); + + EXPECT_EQ(-3, first); + EXPECT_EQ(-1, last); + + EXPECT_TRUE((std::is_same<decltype(first), decltype(last)>::value)); + EXPECT_TRUE((std::is_same<long, decltype(first)>::value)); + } +} + diff --git a/tests/unit/test_spike_store.cpp b/tests/unit/test_spike_store.cpp new file mode 100644 index 0000000000000000000000000000000000000000..dd075f878bfccc7a7a07887e6736fcb2768e0237 --- /dev/null +++ b/tests/unit/test_spike_store.cpp @@ -0,0 +1,84 @@ +#include "gtest.h" + +#include <thread_private_spike_store.hpp> + +TEST(spike_store, insert) +{ + using store_type = nest::mc::thread_private_spike_store<float>; + + store_type store; + + // insert 3 spike events and check that they were inserted correctly + store.insert({ + {{0,0}, 0.0f}, + {{1,2}, 0.5f}, + {{2,4}, 1.0f} + }); + + { + EXPECT_EQ(store.get().size(), 3u); + auto i = 0u; + for (auto& spike : store.get()) { + EXPECT_EQ(spike.source.gid, i); + EXPECT_EQ(spike.source.index, 2*i); + EXPECT_EQ(spike.time, float(i)/2.f); + ++i; + } + } + + // insert another 3 events, then check that they were appended to the + // original three events correctly + store.insert({ + {{3,6}, 1.5f}, + {{4,8}, 2.0f}, + {{5,10}, 2.5f} + }); + + { + EXPECT_EQ(store.get().size(), 6u); + auto i = 0u; + for (auto& spike : store.get()) { + EXPECT_EQ(spike.source.gid, i); + EXPECT_EQ(spike.source.index, 2*i); + EXPECT_EQ(spike.time, float(i)/2.f); + ++i; + } + } +} + +TEST(spike_store, clear) +{ + using store_type = nest::mc::thread_private_spike_store<float>; + + store_type store; + + // insert 3 spike events + store.insert({ + {{0,0}, 0.0f}, {{1,2}, 0.5f}, {{2,4}, 1.0f} + }); + EXPECT_EQ(store.get().size(), 3u); + store.clear(); + EXPECT_EQ(store.get().size(), 0u); +} + +TEST(spike_store, gather) +{ + using store_type = nest::mc::thread_private_spike_store<float>; + + store_type store; + + auto spikes = std::vector<store_type::spike_type> + { {{0,0}, 0.0f}, {{1,2}, 0.5f}, {{2,4}, 1.0f} }; + + store.insert(spikes); + auto gathered_spikes = store.gather(); + + EXPECT_EQ(gathered_spikes.size(), spikes.size()); + + for(auto i=0u; i<spikes.size(); ++i) { + EXPECT_EQ(spikes[i].source.gid, gathered_spikes[i].source.gid); + EXPECT_EQ(spikes[i].source.index, gathered_spikes[i].source.index); + EXPECT_EQ(spikes[i].time, gathered_spikes[i].time); + } +} + diff --git a/tests/unit/test_spikes.cpp b/tests/unit/test_spikes.cpp index b16f89cda31768f43b8509b9f9cdde375ed9bc25..5546ebd30dd6021cf74d98618dd043ed0eea79ce 100644 --- a/tests/unit/test_spikes.cpp +++ b/tests/unit/test_spikes.cpp @@ -1,10 +1,11 @@ #include "gtest.h" -#include <communication/spike.hpp> -#include <communication/spike_source.hpp> +#include <spike.hpp> +#include <spike_source.hpp> struct cell_proxy { - double voltage(nest::mc::segment_location loc) const { + using detector_handle = int; + double detector_voltage(detector_handle) const { return v; } @@ -15,16 +16,17 @@ TEST(spikes, spike_detector) { using namespace nest::mc; using detector_type = spike_detector<cell_proxy>; + using detector_handle = cell_proxy::detector_handle; + cell_proxy proxy; float threshold = 10.f; float t = 0.f; float dt = 1.f; - auto loc = segment_location(1, 0.1); + detector_handle handle{}; - auto detector = detector_type(proxy, loc, threshold, t); + auto detector = detector_type(proxy, handle, threshold, t); EXPECT_FALSE(detector.is_spiking()); - EXPECT_EQ(loc, detector.location()); EXPECT_EQ(proxy.v, detector.v()); EXPECT_EQ(t, detector.t()); @@ -35,7 +37,6 @@ TEST(spikes, spike_detector) EXPECT_FALSE(spike); EXPECT_FALSE(detector.is_spiking()); - EXPECT_EQ(loc, detector.location()); EXPECT_EQ(proxy.v, detector.v()); EXPECT_EQ(t, detector.t()); } @@ -49,7 +50,6 @@ TEST(spikes, spike_detector) EXPECT_EQ(spike.get(), 1.5); EXPECT_TRUE(detector.is_spiking()); - EXPECT_EQ(loc, detector.location()); EXPECT_EQ(proxy.v, detector.v()); EXPECT_EQ(t, detector.t()); } @@ -62,7 +62,6 @@ TEST(spikes, spike_detector) EXPECT_FALSE(spike); EXPECT_FALSE(detector.is_spiking()); - EXPECT_EQ(loc, detector.location()); EXPECT_EQ(proxy.v, detector.v()); EXPECT_EQ(t, detector.t()); } diff --git a/tests/unit/test_swcio.cpp b/tests/unit/test_swcio.cpp index 32eaff75b967ccf9c5f8b7ef7f06c8b3d8b10f3e..c927224ffaef7e6e9d026a53dd81377b061a24eb 100644 --- a/tests/unit/test_swcio.cpp +++ b/tests/unit/test_swcio.cpp @@ -502,7 +502,7 @@ TEST(swc_io, cell_construction) cell cell = io::swc_read_cell(is); EXPECT_TRUE(cell.has_soma()); - EXPECT_EQ(4, cell.num_segments()); + EXPECT_EQ(4u, cell.num_segments()); EXPECT_EQ(norm(points[1]-points[2]), cell.cable(1)->length()); EXPECT_EQ(norm(points[2]-points[3]), cell.cable(2)->length()); @@ -515,13 +515,13 @@ TEST(swc_io, cell_construction) EXPECT_EQ(2.1, cell.soma()->radius()); EXPECT_EQ(point_type(0, 0, 0), cell.soma()->center()); - for (auto i = 1; i < cell.num_segments(); ++i) { + for (auto i = 1u; i < cell.num_segments(); ++i) { EXPECT_TRUE(cell.segment(i)->is_dendrite()); } - EXPECT_EQ(1, cell.cable(1)->num_sub_segments()); - EXPECT_EQ(1, cell.cable(2)->num_sub_segments()); - EXPECT_EQ(2, cell.cable(3)->num_sub_segments()); + EXPECT_EQ(1u, cell.cable(1)->num_sub_segments()); + EXPECT_EQ(1u, cell.cable(2)->num_sub_segments()); + EXPECT_EQ(2u, cell.cable(3)->num_sub_segments()); // Check the radii @@ -563,7 +563,7 @@ TEST(swc_parser, from_file_ball_and_stick) auto cell = nest::mc::io::swc_read_cell(fid); // verify that the correct number of nodes was read - EXPECT_EQ(cell.num_segments(), 2); + EXPECT_EQ(cell.num_segments(), 2u); EXPECT_EQ(cell.num_compartments(), 2u); // make an equivalent cell via C++ interface diff --git a/tests/unit/test_synapses.cpp b/tests/unit/test_synapses.cpp index 9ad68c2444ef17d6f2b2355e3c388f76ca9220e6..357a5a2a8a3f166476474a2c538a01f34d2f2853 100644 --- a/tests/unit/test_synapses.cpp +++ b/tests/unit/test_synapses.cpp @@ -14,7 +14,7 @@ TEST(synapses, add_to_cell) nest::mc::cell cell; // setup global state for the mechanisms - nest::mc::mechanisms::setup_mechanism_helpers(); + // nest::mc::mechanisms::setup_mechanism_helpers(); // Soma with diameter 12.6157 um and HH channel auto soma = cell.add_soma(12.6157/2.0); @@ -30,15 +30,15 @@ TEST(synapses, add_to_cell) EXPECT_EQ(3u, cell.synapses().size()); const auto& syns = cell.synapses(); - EXPECT_EQ(syns[0].location.segment, 0); + EXPECT_EQ(syns[0].location.segment, 0u); EXPECT_EQ(syns[0].location.position, 0.1); EXPECT_EQ(syns[0].mechanism.name(), "expsyn"); - EXPECT_EQ(syns[1].location.segment, 1); + EXPECT_EQ(syns[1].location.segment, 1u); EXPECT_EQ(syns[1].location.position, 0.2); EXPECT_EQ(syns[1].mechanism.name(), "exp2syn"); - EXPECT_EQ(syns[2].location.segment, 0); + EXPECT_EQ(syns[2].location.segment, 0u); EXPECT_EQ(syns[2].location.position, 0.3); EXPECT_EQ(syns[2].mechanism.name(), "expsyn"); } @@ -58,30 +58,33 @@ TEST(synapses, expsyn_basic_state) auto ptr = dynamic_cast<synapse_type*>(mech.get()); + auto n = ptr->size(); + using view = synapse_type::view_type; + // parameters initialized to default values - for(auto e : ptr->e) { + for(auto e : view(ptr->e, n)) { EXPECT_EQ(e, 0.); } - for(auto tau : ptr->tau) { + for(auto tau : view(ptr->tau, n)) { EXPECT_EQ(tau, 2.0); } // current and voltage vectors correctly hooked up - for(auto v : ptr->vec_v_) { + for(auto v : view(ptr->vec_v_, n)) { EXPECT_EQ(v, -65.); } - for(auto i : ptr->vec_i_) { + for(auto i : view(ptr->vec_i_, n)) { EXPECT_EQ(i, 1.0); } // should be initialized to NaN - for(auto g : ptr->g) { + for(auto g : view(ptr->g, n)) { EXPECT_NE(g, g); } // initialize state then check g has been set to zero ptr->nrn_init(); - for(auto g : ptr->g) { + for(auto g : view(ptr->g, n)) { EXPECT_EQ(g, 0.); } @@ -106,32 +109,35 @@ TEST(synapses, exp2syn_basic_state) auto ptr = dynamic_cast<synapse_type*>(mech.get()); + auto n = ptr->size(); + using view = synapse_type::view_type; + // parameters initialized to default values - for(auto e : ptr->e) { + for(auto e : view(ptr->e, n)) { EXPECT_EQ(e, 0.); } - for(auto tau1: ptr->tau1) { + for(auto tau1: view(ptr->tau1, n)) { EXPECT_EQ(tau1, 0.5); } - for(auto tau2: ptr->tau2) { + for(auto tau2: view(ptr->tau2, n)) { EXPECT_EQ(tau2, 2.0); } // should be initialized to NaN - for(auto factor: ptr->factor) { + for(auto factor: view(ptr->factor, n)) { EXPECT_NE(factor, factor); } // initialize state then check factor has sane (positive) value // and A and B are zero ptr->nrn_init(); - for(auto factor: ptr->factor) { + for(auto factor: view(ptr->factor, n)) { EXPECT_GT(factor, 0.); } - for(auto A: ptr->A) { + for(auto A: view(ptr->A, n)) { EXPECT_EQ(A, 0.); } - for(auto B: ptr->B) { + for(auto B: view(ptr->B, n)) { EXPECT_EQ(B, 0.); } @@ -142,3 +148,4 @@ TEST(synapses, exp2syn_basic_state) EXPECT_NEAR(ptr->A[1], ptr->factor[1]*3.14, 1e-6); EXPECT_NEAR(ptr->B[3], ptr->factor[3]*1.04, 1e-6); } + diff --git a/tests/unit/test_transform.cpp b/tests/unit/test_transform.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9212293fbf7a59674eb81f3ccac1638b1df04077 --- /dev/null +++ b/tests/unit/test_transform.cpp @@ -0,0 +1,56 @@ +#include "gtest.h" + +#include <cctype> +#include <forward_list> +#include <vector> + +#include <util/range.hpp> +#include <util/transform.hpp> + +using namespace nest::mc; + +TEST(transform, transform_view) { + std::forward_list<int> fl = {1, 4, 6, 8, 10 }; + std::vector<double> result; + + auto r = util::transform_view(fl, [](int i) { return i*i+0.5; }); + + EXPECT_EQ(5u, util::size(r)); + EXPECT_EQ(16.5, *(std::next(std::begin(r), 1))); + + std::copy(r.begin(), r.end(), std::back_inserter(result)); + std::vector<double> expected = { 1.5, 16.5, 36.5, 64.5, 100.5 }; + + EXPECT_EQ(expected, result); +} + +struct null_terminated_t { + bool operator==(const char *p) const { return !*p; } + bool operator!=(const char *p) const { return !!*p; } + + friend bool operator==(const char *p, null_terminated_t x) { + return x==p; + } + + friend bool operator!=(const char *p, null_terminated_t x) { + return x!=p; + } + + constexpr null_terminated_t() {} +}; + +constexpr null_terminated_t null_terminated; + +char upper(char c) { return std::toupper(c); } + +TEST(transform, transform_view_sentinel) { + const char *hello = "hello"; + auto r = util::transform_view(util::make_range(hello, null_terminated), upper); + + std::string out; + for (auto i = r.begin(); i!=r.end(); ++i) { + out += *i; + } + EXPECT_EQ("HELLO", out); +} + diff --git a/tests/unit/test_tree.cpp b/tests/unit/test_tree.cpp index b82da064b6143fa120c92df2b223db8b95db15ff..4c69ce400d1c6146f7f704b2317a0189b8a41a53 100644 --- a/tests/unit/test_tree.cpp +++ b/tests/unit/test_tree.cpp @@ -17,20 +17,24 @@ using json = nlohmann::json; using range = memory::Range; using namespace nest::mc; +using int_type = cell_tree::int_type; + TEST(cell_tree, from_parent_index) { + auto no_parent = cell_tree::no_parent; + // tree with single branch corresponding to the root node // this is equivalent to a single compartment model // CASE 1 : single root node in parent_index { - std::vector<int> parent_index = {0}; + std::vector<int_type> parent_index = {0}; cell_tree tree(parent_index); EXPECT_EQ(tree.num_segments(), 1u); EXPECT_EQ(tree.num_children(0), 0u); } // CASE 2 : empty parent_index { - std::vector<int> parent_index; + std::vector<int_type> parent_index; cell_tree tree(parent_index); EXPECT_EQ(tree.num_segments(), 1u); EXPECT_EQ(tree.num_children(0), 0u); @@ -46,7 +50,7 @@ TEST(cell_tree, from_parent_index) { // / // 3 // - std::vector<int> parent_index = + std::vector<int_type> parent_index = {0, 0, 1, 2, 0, 4}; cell_tree tree(parent_index); EXPECT_EQ(tree.num_segments(), 3u); @@ -66,7 +70,7 @@ TEST(cell_tree, from_parent_index) { // / \. // 3 8 // - std::vector<int> parent_index = + std::vector<int_type> parent_index = {0, 0, 1, 2, 0, 4, 0, 6, 7, 8}; cell_tree tree(parent_index); @@ -79,10 +83,10 @@ TEST(cell_tree, from_parent_index) { EXPECT_EQ(tree.num_children(3), 0u); // Check new structure - EXPECT_EQ(-1, tree.parent(0)); - EXPECT_EQ(0, tree.parent(1)); - EXPECT_EQ(0, tree.parent(2)); - EXPECT_EQ(0, tree.parent(3)); + EXPECT_EQ(no_parent, tree.parent(0)); + EXPECT_EQ(0u, tree.parent(1)); + EXPECT_EQ(0u, tree.parent(2)); + EXPECT_EQ(0u, tree.parent(3)); } { // @@ -100,7 +104,7 @@ TEST(cell_tree, from_parent_index) { // \. // 13 // - std::vector<int> parent_index = + std::vector<int_type> parent_index = {0, 0, 1, 2, 0, 4, 0, 6, 7, 8, 9, 8, 11, 12}; cell_tree tree(parent_index); EXPECT_EQ(tree.num_segments(), 6u); @@ -115,12 +119,12 @@ TEST(cell_tree, from_parent_index) { EXPECT_EQ(tree.num_children(5), 0u); // Check new structure - EXPECT_EQ(-1, tree.parent(0)); - EXPECT_EQ(0, tree.parent(1)); - EXPECT_EQ(0, tree.parent(2)); - EXPECT_EQ(0, tree.parent(3)); - EXPECT_EQ(3, tree.parent(4)); - EXPECT_EQ(3, tree.parent(5)); + EXPECT_EQ(no_parent, tree.parent(0)); + EXPECT_EQ(0u, tree.parent(1)); + EXPECT_EQ(0u, tree.parent(2)); + EXPECT_EQ(0u, tree.parent(3)); + EXPECT_EQ(3u, tree.parent(4)); + EXPECT_EQ(3u, tree.parent(5)); } { // @@ -129,7 +133,7 @@ TEST(cell_tree, from_parent_index) { // 1 // / \. // 2 3 - std::vector<int> parent_index = {0,0,1,1}; + std::vector<int_type> parent_index = {0,0,1,1}; cell_tree tree(parent_index); EXPECT_EQ(tree.num_segments(), 4u); @@ -146,7 +150,7 @@ TEST(cell_tree, from_parent_index) { // 1 4 5 // / \. // 2 3 - std::vector<int> parent_index = {0,0,1,1,0,0}; + std::vector<int_type> parent_index = {0,0,1,1,0,0}; cell_tree tree(parent_index); EXPECT_EQ(tree.num_segments(), 6u); @@ -158,11 +162,11 @@ TEST(cell_tree, from_parent_index) { EXPECT_EQ(tree.num_children(4), 0u); // Check children - EXPECT_EQ(1, tree.children(0)[0]); - EXPECT_EQ(4, tree.children(0)[1]); - EXPECT_EQ(5, tree.children(0)[2]); - EXPECT_EQ(2, tree.children(1)[0]); - EXPECT_EQ(3, tree.children(1)[1]); + EXPECT_EQ(1u, tree.children(0)[0]); + EXPECT_EQ(4u, tree.children(0)[1]); + EXPECT_EQ(5u, tree.children(0)[2]); + EXPECT_EQ(2u, tree.children(1)[0]); + EXPECT_EQ(3u, tree.children(1)[1]); } /* FIXME { @@ -198,8 +202,8 @@ TEST(tree, change_root) { // 1 2 -> 1 // | // 2 - std::vector<int> parent_index = {0,0,0}; - tree t(parent_index); + std::vector<int_type> parent_index = {0,0,0}; + tree<int_type> t(parent_index); t.change_root(1); EXPECT_EQ(t.num_nodes(), 3u); @@ -216,8 +220,8 @@ TEST(tree, change_root) { // 1 2 -> 1 2 3 // / \ | // 3 4 4 - std::vector<int> parent_index = {0,0,0,1,1}; - tree t(parent_index); + std::vector<int_type> parent_index = {0,0,0,1,1}; + tree<int_type> t(parent_index); t.change_root(1u); EXPECT_EQ(t.num_nodes(), 5u); @@ -240,8 +244,8 @@ TEST(tree, change_root) { // 3 4 3 4 6 // / \. // 5 6 - std::vector<int> parent_index = {0,0,0,1,1,4,4}; - tree t(parent_index); + std::vector<int_type> parent_index = {0,0,0,1,1,4,4}; + tree<int_type> t(parent_index); t.change_root(1); @@ -258,6 +262,8 @@ TEST(tree, change_root) { } TEST(cell_tree, balance) { + auto no_parent = cell_tree::no_parent; + { // a cell with the following structure // will balance around 1 @@ -268,13 +274,13 @@ TEST(cell_tree, balance) { // 3 4 3 4 6 // / \. // 5 6 - std::vector<int> parent_index = {0,0,0,1,1,4,4}; + std::vector<int_type> parent_index = {0,0,0,1,1,4,4}; cell_tree t(parent_index); t.balance(); // the soma (original root) has moved to 5 in the new tree - EXPECT_EQ(t.soma(), 5); + EXPECT_EQ(t.soma(), 5u); EXPECT_EQ(t.num_segments(), 7u); EXPECT_EQ(t.num_children(0),3u); @@ -284,13 +290,13 @@ TEST(cell_tree, balance) { EXPECT_EQ(t.num_children(4),0u); EXPECT_EQ(t.num_children(5),1u); EXPECT_EQ(t.num_children(6),0u); - EXPECT_EQ(t.parent(0),-1); - EXPECT_EQ(t.parent(1), 0); - EXPECT_EQ(t.parent(2), 0); - EXPECT_EQ(t.parent(3), 0); - EXPECT_EQ(t.parent(4), 2); - EXPECT_EQ(t.parent(5), 2); - EXPECT_EQ(t.parent(6), 5); + EXPECT_EQ(t.parent(0), no_parent); + EXPECT_EQ(t.parent(1), 0u); + EXPECT_EQ(t.parent(2), 0u); + EXPECT_EQ(t.parent(3), 0u); + EXPECT_EQ(t.parent(4), 2u); + EXPECT_EQ(t.parent(5), 2u); + EXPECT_EQ(t.parent(6), 5u); //t.to_graphviz("cell.dot"); } @@ -307,7 +313,7 @@ TEST(cell_tree, json_load) std::ifstream(path) >> cell_data; for(auto c : range(0,cell_data.size())) { - std::vector<int> parent_index = cell_data[c]["parent_index"]; + std::vector<int_type> parent_index = cell_data[c]["parent_index"]; cell_tree tree(parent_index); //tree.to_graphviz("cell" + std::to_string(c) + ".dot"); } diff --git a/tests/unit/test_uninitialized.cpp b/tests/unit/test_uninitialized.cpp index dcc9e47ce6a7fa266376e054e8f84a04d8ec818d..1f04510d7df6af054eaec854d7e0c954ad5e73c8 100644 --- a/tests/unit/test_uninitialized.cpp +++ b/tests/unit/test_uninitialized.cpp @@ -7,16 +7,16 @@ using namespace nest::mc::util; namespace { struct count_ops { count_ops() {} - count_ops(const count_ops &n) { ++copy_ctor_count; } - count_ops(count_ops &&n) { ++move_ctor_count; } + count_ops(const count_ops& n) { ++copy_ctor_count; } + count_ops(count_ops&& n) { ++move_ctor_count; } - count_ops &operator=(const count_ops &n) { ++copy_assign_count; return *this; } - count_ops &operator=(count_ops &&n) { ++move_assign_count; return *this; } + count_ops& operator=(const count_ops& n) { ++copy_assign_count; return *this; } + count_ops& operator=(count_ops&& n) { ++move_assign_count; return *this; } static int copy_ctor_count,copy_assign_count; static int move_ctor_count,move_assign_count; static void reset_counts() { - copy_ctor_count=copy_assign_count=0; + copy_ctor_count=copy_assign_count=0; move_ctor_count=move_assign_count=0; } }; @@ -53,11 +53,11 @@ TEST(uninitialized,ctor) { namespace { struct nocopy { nocopy() {} - nocopy(const nocopy &n) = delete; - nocopy(nocopy &&n) { ++move_ctor_count; } + nocopy(const nocopy& n) = delete; + nocopy(nocopy&& n) { ++move_ctor_count; } - nocopy &operator=(const nocopy &n) = delete; - nocopy &operator=(nocopy &&n) { ++move_assign_count; return *this; } + nocopy& operator=(const nocopy& n) = delete; + nocopy& operator=(nocopy&& n) { ++move_assign_count; return *this; } static int move_ctor_count,move_assign_count; static void reset_counts() { move_ctor_count=move_assign_count=0; } @@ -85,11 +85,11 @@ TEST(uninitialized,ctor_nocopy) { namespace { struct nomove { nomove() {} - nomove(const nomove &n) { ++copy_ctor_count; } - nomove(nomove &&n) = delete; + nomove(const nomove& n) { ++copy_ctor_count; } + nomove(nomove&& n) = delete; - nomove &operator=(const nomove &n) { ++copy_assign_count; return *this; } - nomove &operator=(nomove &&n) = delete; + nomove& operator=(const nomove& n) { ++copy_assign_count; return *this; } + nomove& operator=(nomove&& n) = delete; static int copy_ctor_count,copy_assign_count; static void reset_counts() { copy_ctor_count=copy_assign_count=0; } @@ -129,7 +129,7 @@ TEST(uninitialized,void) { } TEST(uninitialized,ref) { - uninitialized<int &> x,y; + uninitialized<int&> x,y; int a; x.construct(a); @@ -151,8 +151,8 @@ namespace { mutable int op_count=0; mutable int const_op_count=0; - int operator()(const int &a) const { ++const_op_count; return a+1; } - int operator()(int &a) const { ++op_count; return ++a; } + int operator()(const int& a) const { ++const_op_count; return a+1; } + int operator()(int& a) const { ++op_count; return ++a; } }; } @@ -165,14 +165,14 @@ TEST(uninitialized,apply) { EXPECT_EQ(11,ua.cref()); EXPECT_EQ(11,r); - uninitialized<int &> ub; + uninitialized<int&> ub; ub.construct(ua.ref()); r=ub.apply(A); EXPECT_EQ(12,ua.cref()); EXPECT_EQ(12,r); - uninitialized<const int &> uc; + uninitialized<const int&> uc; uc.construct(ua.ref()); r=uc.apply(A); diff --git a/tests/validation/CMakeLists.txt b/tests/validation/CMakeLists.txt index c6c72fa04efab7394190288ba3face603d141faf..ae892560492dccca331036ad1481d2f4ce5da7bb 100644 --- a/tests/validation/CMakeLists.txt +++ b/tests/validation/CMakeLists.txt @@ -19,19 +19,20 @@ set(TARGETS validate.exe) foreach(target ${TARGETS}) target_link_libraries(${target} LINK_PUBLIC cellalgo gtest) - + if(WITH_TBB) - target_link_libraries(${target} LINK_PUBLIC ${TBB_LIBRARIES}) + target_link_libraries(${target} LINK_PUBLIC ${TBB_LIBRARIES}) endif() if(WITH_MPI) - target_link_libraries(${target} LINK_PUBLIC ${MPI_C_LIBRARIES}) - set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS "${MPI_C_LINK_FLAGS}") + target_link_libraries(${target} LINK_PUBLIC ${MPI_C_LIBRARIES}) + set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS "${MPI_C_LINK_FLAGS}") endif() - set_target_properties(${target} - PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests" + set_target_properties( + ${target} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests" ) endforeach() diff --git a/tests/validation/validate_ball_and_stick.cpp b/tests/validation/validate_ball_and_stick.cpp index 0e41bd021534af6020482659f082eca0867fb63a..59e952de58de61ae32cdbfb721ac991ec2119f8b 100644 --- a/tests/validation/validate_ball_and_stick.cpp +++ b/tests/validation/validate_ball_and_stick.cpp @@ -1,8 +1,10 @@ #include <fstream> #include <json/src/json.hpp> +#include <common_types.hpp> #include <cell.hpp> #include <fvm_cell.hpp> +#include <util/singleton.hpp> #include "gtest.h" #include "../test_util.hpp" @@ -16,9 +18,6 @@ TEST(ball_and_stick, neuron_baseline) nest::mc::cell cell; - // setup global state for the mechanisms - nest::mc::mechanisms::setup_mechanism_helpers(); - // Soma with diameter 12.6157 um and HH channel auto soma = cell.add_soma(12.6157/2.0); soma->add_mechanism(hh_parameters()); @@ -84,6 +83,11 @@ TEST(ball_and_stick, neuron_baseline) } }; + using fvm_cell = fvm::fvm_cell<double, cell_local_size_type>; + std::vector<fvm_cell::detector_handle> detectors(cell.detectors().size()); + std::vector<fvm_cell::target_handle> targets(cell.synapses().size()); + std::vector<fvm_cell::probe_handle> probes(cell.probes().size()); + std::vector<result> results; for(auto run_index=0u; run_index<cell_data.size(); ++run_index) { auto& run = cell_data[run_index]; @@ -92,13 +96,10 @@ TEST(ball_and_stick, neuron_baseline) std::vector<std::vector<double>> v(3); // make the lowered finite volume cell - fvm::fvm_cell<double, int> model(cell); - auto graph = cell.model(); - // set initial conditions - using memory::all; - model.voltage()(all) = -65.; - model.initialize(); // have to do this _after_ initial conditions are set + fvm_cell model; + model.initialize(util::singleton_view(cell), detectors, targets, probes); + auto graph = cell.model(); // run the simulation auto soma_comp = nest::mc::find_compartment_index({0,0}, graph); @@ -165,9 +166,6 @@ TEST(ball_and_3stick, neuron_baseline) nest::mc::cell cell; - // setup global state for the mechanisms - nest::mc::mechanisms::setup_mechanism_helpers(); - // Soma with diameter 12.6157 um and HH channel auto soma = cell.add_soma(12.6157/2.0); soma->add_mechanism(hh_parameters()); @@ -239,6 +237,11 @@ TEST(ball_and_3stick, neuron_baseline) } }; + using fvm_cell = fvm::fvm_cell<double, cell_local_size_type>; + std::vector<fvm_cell::detector_handle> detectors(cell.detectors().size()); + std::vector<fvm_cell::target_handle> targets(cell.synapses().size()); + std::vector<fvm_cell::probe_handle> probes(cell.probes().size()); + std::vector<result> results; auto start = testing::tic(); for(auto run_index=0u; run_index<cell_data.size(); ++run_index) { @@ -250,13 +253,11 @@ TEST(ball_and_3stick, neuron_baseline) std::vector<std::vector<double>> v(3); // make the lowered finite volume cell - fvm::fvm_cell<double, int> model(cell); + fvm_cell model; + model.initialize(util::singleton_view(cell), detectors, targets, probes); auto graph = cell.model(); // set initial conditions - using memory::all; - model.voltage()(all) = -65.; - model.initialize(); // have to do this _after_ initial conditions are set // run the simulation auto soma_comp = nest::mc::find_compartment_index({0,0.}, graph); diff --git a/tests/validation/validate_soma.cpp b/tests/validation/validate_soma.cpp index 9c3c9fdfa56b14b9aa948c762023355541866d9a..af4d32826acb12bcc522daf7aa3f84c80dcc6bb2 100644 --- a/tests/validation/validate_soma.cpp +++ b/tests/validation/validate_soma.cpp @@ -1,8 +1,10 @@ #include <fstream> #include <json/src/json.hpp> +#include <common_types.hpp> #include <cell.hpp> #include <fvm_cell.hpp> +#include <util/singleton.hpp> #include "gtest.h" #include "../test_util.hpp" @@ -17,9 +19,6 @@ TEST(soma, neuron_baseline) nest::mc::cell cell; - // setup global state for the mechanisms - nest::mc::mechanisms::setup_mechanism_helpers(); - // Soma with diameter 18.8um and HH channel auto soma = cell.add_soma(18.8/2.0); soma->mechanism("membrane").set("r_L", 123); // no effect for single compartment cell @@ -29,7 +28,13 @@ TEST(soma, neuron_baseline) cell.add_stimulus({0,0.5}, {10., 100., 0.1}); // make the lowered finite volume cell - fvm::fvm_cell<double, int> model(cell); + using fvm_cell = fvm::fvm_cell<double, cell_local_size_type>; + std::vector<fvm_cell::detector_handle> detectors(cell.detectors().size()); + std::vector<fvm_cell::target_handle> targets(cell.synapses().size()); + std::vector<fvm_cell::probe_handle> probes(cell.probes().size()); + + fvm_cell model; + model.initialize(util::singleton_view(cell), detectors, targets, probes); // load data from file auto cell_data = testing::g_validation_data.load("soma.json"); @@ -51,9 +56,7 @@ TEST(soma, neuron_baseline) double dt = run["dt"]; // set initial conditions - using memory::all; - model.voltage()(all) = -65.; - model.initialize(); // have to do this _after_ initial conditions are set + model.reset(); // run the simulation auto tfinal = 120.; // ms @@ -85,9 +88,6 @@ TEST(soma, convergence) nest::mc::cell cell; - // setup global state for the mechanisms - nest::mc::mechanisms::setup_mechanism_helpers(); - // Soma with diameter 18.8um and HH channel auto soma = cell.add_soma(18.8/2.0); soma->mechanism("membrane").set("r_L", 123); // no effect for single compartment cell @@ -97,7 +97,13 @@ TEST(soma, convergence) cell.add_stimulus({0,0.5}, {10., 100., 0.1}); // make the lowered finite volume cell - fvm::fvm_cell<double, int> model(cell); + using fvm_cell = fvm::fvm_cell<double, cell_local_size_type>; + std::vector<fvm_cell::detector_handle> detectors(cell.detectors().size()); + std::vector<fvm_cell::target_handle> targets(cell.synapses().size()); + std::vector<fvm_cell::probe_handle> probes(cell.probes().size()); + + fvm_cell model; + model.initialize(util::singleton_view(cell), detectors, targets, probes); // generate baseline solution with small dt=0.0001 std::vector<double> baseline_spike_times; @@ -106,9 +112,7 @@ TEST(soma, convergence) std::vector<double> v; // set initial conditions - using memory::all; - model.voltage()(all) = -65.; - model.initialize(); // have to do this _after_ initial conditions are set + model.reset(); // run the simulation auto tfinal = 120.; // ms @@ -128,9 +132,7 @@ TEST(soma, convergence) std::vector<double> v; // set initial conditions - using memory::all; - model.voltage()(all) = -65.; - model.initialize(); // have to do this _after_ initial conditions are set + model.reset(); // run the simulation auto tfinal = 120.; // ms diff --git a/tests/validation/validate_synapses.cpp b/tests/validation/validate_synapses.cpp index 42ace29c3393bb177b878ab3e8c6eb6de42d8676..9aa90abef6f6178a6285f9a1d5db2e9d1cb1d4f1 100644 --- a/tests/validation/validate_synapses.cpp +++ b/tests/validation/validate_synapses.cpp @@ -3,6 +3,7 @@ #include <json/src/json.hpp> +#include <common_types.hpp> #include <cell.hpp> #include <cell_group.hpp> #include <fvm_cell.hpp> @@ -50,12 +51,10 @@ void run_neuron_baseline(const char* syn_type, const char* data_file) { using namespace nest::mc; using namespace nlohmann; + using lowered_cell = fvm::fvm_cell<double, cell_local_size_type>; nest::mc::cell cell; - // setup global state for the mechanisms - mechanisms::setup_mechanism_helpers(); - // Soma with diameter 12.6157 um and HH channel auto soma = cell.add_soma(12.6157/2.0); soma->add_mechanism(hh_parameters()); @@ -72,14 +71,17 @@ void run_neuron_baseline(const char* syn_type, const char* data_file) cell.add_synapse({1, 0.5}, syn_default); // add probes - auto probe_soma = cell.add_probe({0,0}, probeKind::membrane_voltage); - auto probe_dend = cell.add_probe({1,0.5}, probeKind::membrane_voltage); + auto probe_soma_idx = cell.add_probe({{0,0}, probeKind::membrane_voltage}); + auto probe_dend_idx = cell.add_probe({{1,0.5}, probeKind::membrane_voltage}); + + cell_member_type probe_soma{0u, probe_soma_idx}; + cell_member_type probe_dend{0u, probe_dend_idx}; // injected spike events - postsynaptic_spike_event synthetic_events[] = { - {0u, 10.0, 0.04}, - {0u, 20.0, 0.04}, - {0u, 40.0, 0.04} + postsynaptic_spike_event<float> synthetic_events[] = { + {{0u, 0u}, 10.0, 0.04}, + {{0u, 0u}, 20.0, 0.04}, + {{0u, 0u}, 40.0, 0.04} }; // load data from file @@ -106,23 +108,21 @@ void run_neuron_baseline(const char* syn_type, const char* data_file) std::vector<std::vector<double>> v(2); // make the lowered finite volume cell - cell_group<fvm::fvm_cell<double, int>> group(cell); - group.set_source_gids(0); - group.set_target_gids(0); + cell_group<lowered_cell> group(0, cell); // add the 3 spike events to the queue group.enqueue_events(synthetic_events); // run the simulation - v[0].push_back(group.cell().probe(probe_soma)); - v[1].push_back(group.cell().probe(probe_dend)); + v[0].push_back(group.probe(probe_soma)); + v[1].push_back(group.probe(probe_dend)); double t = 0.; while(t < tfinal) { t += dt; group.advance(t, dt); // save voltage at soma and dendrite - v[0].push_back(group.cell().probe(probe_soma)); - v[1].push_back(group.cell().probe(probe_dend)); + v[0].push_back(group.probe(probe_soma)); + v[1].push_back(group.probe(probe_dend)); } results.push_back({num_compartments, dt, v, measurements});