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});