diff --git a/.gitignore b/.gitignore
index c23dfde0164c2ce71be3a2113f47b2f96e793753..f60f1b41e5cdbbb2ac445207ed0d2e41176e0104 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,6 +32,7 @@
 *.dot
 *.pdf
 *.jpg
+*.dat
 
 # latex output
 *.aux
@@ -47,3 +48,5 @@ CMakeCache.txt
 cmake_install.cmake
 Makefile
 
+# mechanism implementations generated my modparser
+include/mechanisms
diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py
index b8378fb82ab46999a5a31f6f5ff516f0c6cc5af6..55d6af4d257348526713c1fdc381c4c2792651fd 100644
--- a/.ycm_extra_conf.py
+++ b/.ycm_extra_conf.py
@@ -43,6 +43,8 @@ flags = [
     'src',
     '-I',
     '.',
+    '-I',
+    'include',
 #    '-isystem',
 #    '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/c++/4.2.1',
 #    '-I',
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 61278f34ff79a0b1dc688835f369df0b144926e0..57991c886fc85752b0d7a791375ba8004ce69012 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -14,6 +14,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS "YES")
 set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
 set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
 
+include_directories(${CMAKE_SOURCE_DIR}/include)
 include_directories(${CMAKE_SOURCE_DIR}/src)
 include_directories(${CMAKE_SOURCE_DIR})
 
diff --git a/data/hh.mod b/data/hh.mod
old mode 100755
new mode 100644
index 6020be0c1f6d99ab90a3639916eb32cdb1fab155..975d601d09491f6b6418200b795368dbd9b14f7e
--- a/data/hh.mod
+++ b/data/hh.mod
@@ -1,17 +1,17 @@
 TITLE hh.mod   squid sodium, potassium, and leak channels
 
-COMMENT
- This is the original Hodgkin-Huxley treatment for the set of sodium, 
-  potassium, and leakage channels found in the squid giant axon membrane.
-  ("A quantitative description of membrane current and its application 
-  conduction and excitation in nerve" J.Physiol. (Lond.) 117:500-544 (1952).)
- Membrane voltage is in absolute mV and has been reversed in polarity
-  from the original HH convention and shifted to reflect a resting potential
-  of -65 mV.
- Remember to set celsius=6.3 (or whatever) in your HOC file.
- See squid.hoc for an example of a simulation using this model.
- SW Jaslove  6 March, 1992
-ENDCOMMENT
+: COMMENT
+:  This is the original Hodgkin-Huxley treatment for the set of sodium, 
+:   potassium, and leakage channels found in the squid giant axon membrane.
+:   ("A quantitative description of membrane current and its application 
+:   conduction and excitation in nerve" J.Physiol. (Lond.) 117:500-544 (1952).)
+:  Membrane voltage is in absolute mV and has been reversed in polarity
+:   from the original HH convention and shifted to reflect a resting potential
+:   of -65 mV.
+:  Remember to set celsius=6.3 (or whatever) in your HOC file.
+:  See squid.hoc for an example of a simulation using this model.
+:  SW Jaslove  6 March, 1992
+: ENDCOMMENT
 
 UNITS {
     (mA) = (milliamp)
@@ -26,14 +26,14 @@ NEURON {
     NONSPECIFIC_CURRENT il
     RANGE gnabar, gkbar, gl, el, gna, gk
     GLOBAL minf, hinf, ninf, mtau, htau, ntau
-    THREADSAFE : assigned GLOBALs will be per thread
 }
 
 PARAMETER {
-    gnabar = .12 (S/cm2)    <0,1e9>
-    gkbar = .036 (S/cm2)    <0,1e9>
-    gl = .0003 (S/cm2)  <0,1e9>
+    gnabar = .12 (S/cm2)  : <0,1e9>
+    gkbar = .036 (S/cm2)  : <0,1e9>
+    gl = .0003 (S/cm2)    : <0,1e9>
     el = -54.3 (mV)
+    celsius = 37 (degC)
 }
 
 STATE {
@@ -42,20 +42,17 @@ STATE {
 
 ASSIGNED {
     v (mV)
-    celsius (degC)
-    ena (mV)
-    ek (mV)
 
     gna (S/cm2)
     gk (S/cm2)
-    ina (mA/cm2)
-    ik (mA/cm2)
-    il (mA/cm2)
-    minf hinf ninf
-    mtau (ms) htau (ms) ntau (ms)
+    minf
+    hinf
+    ninf
+    mtau (ms)
+    htau (ms)
+    ntau (ms)
 }
 
-? currents
 BREAKPOINT {
     SOLVE states METHOD cnexp
     gna = gnabar*m*m*m*h
@@ -72,18 +69,15 @@ INITIAL {
     n = ninf
 }
 
-DERIVATIVE states
-{
+DERIVATIVE states {
     rates(v)
     m' =  (minf-m)/mtau
     h' = (hinf-h)/htau
     n' = (ninf-n)/ntau
 }
 
-PROCEDURE rates(v(mV))
+PROCEDURE rates(v) :(mV))
 {
-    :Computes rate and other constants at current v.
-    :Call once from HOC to initialize inf at resting v.
     LOCAL  alpha, beta, sum, q10
 
     q10 = 3^((celsius - 6.3)/10)
@@ -103,18 +97,28 @@ PROCEDURE rates(v(mV))
     hinf = alpha/sum
 
     :"n" potassium activation system
-    alpha = .01*vtrap(-(v+55),10) 
+    alpha = .01*vtrap(-(v+55),10)
     beta = .125*exp(-(v+65)/80)
     sum = alpha + beta
     ntau = 1/(q10*sum)
     ninf = alpha/sum
 }
 
-FUNCTION vtrap(x,y) {  :Traps for 0 in denominator of rate eqns.
-    if (fabs(x/y) < 1e-6) {
-        vtrap = y*(1 - x/y/2)
-    }else{
-        vtrap = x/(exp(x/y) - 1)
-    }
+FUNCTION vtrap(x,y) {
+    : Traps for 0 in denominator of rate eqns.
+
+    : Disabled for function inlining...
+    : there is a very good case for making vtrap a builtin function for the language
+    : - it is ubiquitous: used in so many models of ion channels
+    : - platform specific optimizations written in assembly/intrinsics
+    :   required to facilitate vectorization/minimize branch misses
+
+    :if (fabs(x/y) < 1e-6) {
+    :    vtrap = y*(1 - x/y/2)
+    :}else{
+    :    vtrap = x/(exp(x/y) - 1)
+    :}
+
+    vtrap = x/(exp(x/y) - 1)
 }
 
diff --git a/data/passive.mod b/data/passive.mod
new file mode 100644
index 0000000000000000000000000000000000000000..413bd250c8e9a077287c97b82cb9dd2cd44927cb
--- /dev/null
+++ b/data/passive.mod
@@ -0,0 +1,28 @@
+TITLE passive membrane channel
+
+UNITS {
+    (mV) = (millivolt)
+    (mA) = (milliamp)
+    (S) = (siemens)
+}
+
+NEURON {
+    SUFFIX pas
+    NONSPECIFIC_CURRENT i
+    RANGE g, e
+}
+
+INITIAL {}
+
+PARAMETER {
+    g = .001    (S/cm2) :<0,1e9>
+    e = -70 (mV)
+}
+
+ASSIGNED {
+    v (mV)
+}
+
+BREAKPOINT {
+    i = g*(v - e)
+}
diff --git a/docs/report.tex b/docs/report.tex
index 54d93c8dcfd449fe70b095113e2a1930b12cf4e7..0f816e1c25a7aa22b7199df27a0cd3d8216f9dc2 100644
--- a/docs/report.tex
+++ b/docs/report.tex
@@ -72,6 +72,9 @@
 \newcommand{\dder}[2]{\frac{\deriv{#1}}{\deriv{#2}}}
 \newcommand{\vv}[1]{\bm{#1}\xspace}
 
+\newcommand{\unit}[1]{\left[{#1}\right]}
+\newcommand{\txtunit}[1]{$\left[{#1}\right]$}
+
 %----------------------------------------------------------------------------------------
 %   ARTICLE INFORMATION
 %----------------------------------------------------------------------------------------
diff --git a/docs/symbols.tex b/docs/symbols.tex
index 19db4a374eb9a3697bcb8971ca2cca9e0b40877e..e12b55820da048ffef27bb790fca4e0b363ba23e 100644
--- a/docs/symbols.tex
+++ b/docs/symbols.tex
@@ -1,3 +1,113 @@
+%-------------------------------------------------------------------------------
+%\subsubsection{Balancing Units}
+%-------------------------------------------------------------------------------
+Ensuring that units are balanced and correct requires care.
+Take the description of the nonlinear system of ODEs that arises from the finite volume discretisation
+\begin{equation}
+    \label{eq:linsys_FV}
+      V_i^{k+1} + \sum_{j\in\mathcal{N}_i} {\frac{\Delta t \alpha_{ij}}{\sigma_i} (V_i^{k+1}-V_j^{k+1})}
+    = V_i^k - \frac{\Delta t}{c_m}(i_m^{k} - i_e).
+\end{equation}
+The choice of units for a parameter, e.g. $\mu m^2$ or $m^2$ for the area $\sigma_{ij}$, introduces a constant of proportionality wherever it is used ($10^{-12}$ in the case of $\mu m^2 \rightarrow m^2$).
+Wherever terms are added in \eq{eq:linsys_FV} the units must be checked, and constants of proportionality balanced.
+
+First, appropriate units for each of the parameters and variables are chosen in~\tbl{tbl:units}.
+We try to use the same units as NEURON, except for the specific membrane capacitance $c_m$, for which $F\cdot m^{-2}$ is used in place of $nF\cdot mm^{-2}$.
+In \eq{eq:linsys_FV} we choose units of $mV \equiv 10^{-3}V$ for each term because of the $V_i$ terms on either side of the equation.
+
+\begin{table}[hp!]
+\begin{tabular}{lllr}
+    \hline
+    term                      &   units                 &  normalized units                         & NEURON \\
+    \hline
+    $t$                       &   $ms$                  &  $10^{-3} \cdot s$                        & yes    \\
+    $V$                       &   $mV$                  &  $10^{-3} \cdot V$                        & yes    \\
+    $a,~\Delta x$             &   $\mu m$               &  $10^{-6} \cdot m$                        & yes    \\
+    $\sigma_{i},~\sigma_{ij}$ &   $\mu m^2$             &  $10^{-12} \cdot m^2$                     & yes    \\
+    $c_m$                     &   $F\cdot m^{-2}$       &  $s\cdot A\cdot V^{-1}\cdot m^{-2}$       & no     \\
+    $r_L$                     &   $\Omega\cdot cm$ &    $  10^{-2} \cdot A^{-1}\cdot V\cdot m$      & yes    \\
+    $\overline{g}$            &   $S\cdot cm^{-2}$      &  $10^{4} \cdot A\cdot V^{-1}\cdot m^{-2}$ & yes    \\
+    $I_e$                     &   $nA$                  &  $10^{-9} \cdot A$                        & yes    \\
+    \hline
+\end{tabular}
+\caption{The units chosen for parameters and variables in NEST MC. The NEURON column indicates whether the same units have been used as NEURON.}
+\label{tbl:units}
+\end{table}
+
+%------------------------------------------
+\subsubsection{current terms}
+%------------------------------------------
+Membrane current is calculated as follows $i_m = \overline{g}(E-V)$, with units
+\begin{align}
+    \unit{ i_m } &=  \unit{ \overline{g} } \unit{ V } \nonumber \\
+                       &=  10^{4} \cdot A\cdot V^{-1}\cdot m^{-2} \cdot 10^{-3} \cdot V \nonumber \\
+                       &=  10 \cdot A \cdot m^{-2}. \label{eq:im_unit}
+\end{align}
+The injected current $I_e$ has units $nA$, which has to be expressed in terms of current per unit area $i_e=I_e / \sigma_i$ with units
+\begin{align}
+    \unit{ i_e } &=  \unit{ I_e } \unit{ \sigma_i }^{-1} \nonumber \\
+                       &=  10^{-9}\cdot A \cdot 10^{12} \cdot m^{-2} \nonumber \\
+                       &=  10^{3} \cdot A \cdot m ^{-2}, \label{eq:ie_unit}
+\end{align}
+which must be scaled by $10^2$ to match $i_m$ in \eq{eq:im_unit}.
+
+The units for the flux coefficent can be calculated as follows:
+\begin{align}
+    \unit{ \frac{\Delta t}{c_m} } &= 10^{-3} \cdot s \cdot s^{-1}\cdot A^{-1}\cdot V\cdot m^2 \nonumber \\
+                                  &= 10^{-3} \cdot A^{-1} \cdot V\cdot m^2. \label{eq:dtcm_unit}
+\end{align}
+From \eq{eq:im_unit} and \eq{eq:dtcm_unit} that the units of the full current term are
+\begin{align}
+    \unit{ \frac{\Delta t}{c_m}\left(i_m - i_e\right) }
+        &= 10^{-3} \cdot A^{-1} \cdot V\cdot m^2 \cdot 10 \cdot A \cdot m^{-2} \nonumber \\
+        &= 10^{-2} \cdot V,
+\end{align}
+which must be scaled by $10$ to match the units of $mV\equiv10^{-3}V$.
+%------------------------------------------
+\subsubsection{flux terms}
+%------------------------------------------
+The coefficients in the linear system have the units
+\begin{equation}
+    \unit{ \frac{\Delta t\alpha_{ij}}{\sigma_i} }
+    =
+    \unit{ \frac{\Delta t \sigma_{ij} } {c_m r_L \Delta x_{ij} \sigma_i} }
+    =
+    \unit{ \frac{\Delta t } {c_m r_L \Delta x_{ij} } },
+\end{equation}
+where we we simplify by noting that $\unit{\sigma_{ij}}=\unit{\sigma_i}$.
+The units of the term $c_m r_L$ on the denominator are calculated as follows
+\begin{align}
+    \unit{c_m r_L}
+    &= s \cdot A \cdot V^{-1} \cdot m^{-2} \cdot 10^{-2} \cdot A^{-1} \cdot V \cdot m \nonumber \\
+    &= 10^{-2} \cdot s \cdot m^{-1},
+\end{align}
+so the units of the denominator are
+\begin{align}
+    \unit{c_m r_L \Delta x_{ij}}
+    &= 10^{-2} \cdot s \cdot m^{-1} \cdot 10^{-6} \cdot m \nonumber \\
+    &= 10^{-8} \cdot s,
+\end{align}
+and hence
+\begin{align}
+    \unit{\frac{\Delta t } {c_m r_L \Delta x_{ij} }}
+    &= 10^{8} \cdot s^{-1} \cdot 10^{-3} \cdot s \nonumber \\
+    &= 10^{5}.
+\end{align}
+
+So, the terms with $\alpha_{ij}$ must be scaled by $10^5$ to match the units of $mV$.
+%------------------------------------------
+\subsubsection{discretization with scaling}
+%------------------------------------------
+Here is something that I wish the NEURON documentation had provided:
+\begin{align}
+&     V_i^{k+1} + \sum_{j\in\mathcal{N}_i} {10^5 \cdot \frac{\Delta t \alpha_{ij}}{\sigma_i} (V_i^{k+1}-V_j^{k+1})} \nonumber \\
+&   = V_i^k - 10\cdot \frac{\Delta t}{c_m}(i_m^{k} - 10^2\cdot I_e/\sigma_i).
+\end{align}
+%------------------------------------------
+\subsection{Supplementary Unit Information}
+%------------------------------------------
+Here is some information about units scraped from Wikipedia for convenience.
+
 \begin{table*}[htp!]
     \begin{center}
 
@@ -15,6 +125,8 @@
         \hline
     \end{tabular}
 
+    \vspace{20pt}
+
     \begin{tabular}{llll}
         \hline
         symbol & unit & equivalents & SI base \\
@@ -44,27 +156,4 @@
     \end{center}
     \caption{Symbols and quantities.}
 \end{table*}
-%-------------------------------------------------------------------------------
-\subsubsection{Units}
-%-------------------------------------------------------------------------------
-Reffering to the cable equation first defined in~\eq{eq:cable}
-\begin{equation*}
-    c_m \pder{V}{t} = \frac{1}{2\pi a r_{L}} \pder{}{x} \left( a^2 \pder{V}{x} \right) - i_m + i_e,
-\end{equation*}
-
-If the units are taken to be
-\begin{itemize}
-    \item $c_m = F\cdot cm^{-2}$
-    \item $V = V$
-    \item $a = cm$
-    \item $r_L = \Omega\cdot cm$
-\end{itemize}
-Then the units of each term in equation are $A\cdot cm^{-2}$.
-In practice, the units above are not used, for example distances are usually measured in $\mu m$ and areas in $cm^2$.
-But if we work term-by-term, scaling for these factors is manageable.
-
-A useful identity to use when performing the dimensional analysis relates capacitance and resistance
-\begin{equation*}
-    1~F = 1~\Omega^{-1} \cdot s
-\end{equation*}
 
diff --git a/include/mechanisms/hh_verb.hpp b/include/mechanisms/hh_verb.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..8b67a69d56a0f61b2db8baaa37d03fc34d7e9687
--- /dev/null
+++ b/include/mechanisms/hh_verb.hpp
@@ -0,0 +1,284 @@
+#pragma once
+
+#include <cmath>
+#include <limits>
+
+#include <mechanism.hpp>
+#include <mechanism_interface.hpp>
+#include <algorithms.hpp>
+
+namespace nest{ namespace mc{ namespace mechanisms{ namespace hh{
+
+template<typename T, typename I>
+class mechanism_hh : public mechanism<T, I> {
+public:
+    using base = mechanism<T, I>;
+    using value_type  = typename base::value_type;
+    using size_type   = typename base::size_type;
+    using vector_type = typename base::vector_type;
+    using view_type   = typename base::view_type;
+    using index_type  = typename base::index_type;
+    using index_view  = typename index_type::view_type;
+    using indexed_view_type= typename base::indexed_view_type;
+    using ion_type = typename base::ion_type;
+
+    struct Ionna {
+        view_type ena;
+        view_type ina;
+        index_type index;
+        std::size_t memory() const { return sizeof(size_type)*index.size(); }
+        std::size_t size() const { return index.size(); }
+    };
+    Ionna ion_na;
+    struct Ionk {
+        view_type ek;
+        view_type ik;
+        index_type index;
+        std::size_t memory() const { return sizeof(size_type)*index.size(); }
+        std::size_t size() const { return index.size(); }
+    };
+    Ionk ion_k;
+
+    mechanism_hh(view_type vec_v, view_type vec_i, index_view node_index)
+    :   base(vec_v, vec_i, node_index)
+    {
+        size_type num_fields = 15;
+
+        // calculate the padding required to maintain proper alignment of sub arrays
+        auto alignment  = data_.alignment();
+        auto field_size_in_bytes = sizeof(value_type)*size();
+        auto remainder  = field_size_in_bytes % alignment;
+        auto padding    = remainder ? (alignment - remainder)/sizeof(value_type) : 0;
+        auto field_size = size()+padding;
+
+        // allocate memory
+        data_ = vector_type(field_size * num_fields);
+        data_(memory::all) = std::numeric_limits<value_type>::quiet_NaN();
+
+        // asign the sub-arrays
+        gnabar          = data_(0*field_size, 1*size());
+        minf            = data_(1*field_size, 2*size());
+        h               = data_(2*field_size, 3*size());
+        m               = data_(3*field_size, 4*size());
+        gl              = data_(4*field_size, 5*size());
+        gkbar           = data_(5*field_size, 6*size());
+        el              = data_(6*field_size, 7*size());
+        ninf            = data_(7*field_size, 8*size());
+        mtau            = data_(8*field_size, 9*size());
+        gna             = data_(9*field_size, 10*size());
+        gk              = data_(10*field_size, 11*size());
+        n               = data_(11*field_size, 12*size());
+        hinf            = data_(12*field_size, 13*size());
+        ntau            = data_(13*field_size, 14*size());
+        htau            = data_(14*field_size, 15*size());
+
+        // set initial values for variables and parameters
+        std::fill(gnabar.data(), gnabar.data()+size(), 0.12);
+        std::fill(gkbar.data(), gkbar.data()+size(), 0.036);
+        std::fill(gl.data(), gl.data()+size(), 0.0003);
+        std::fill(el.data(), el.data()+size(), -54.3);
+    }
+
+    using base::size;
+
+    std::size_t memory() const override {
+        auto s = std::size_t{0};
+        s += data_.size()*sizeof(value_type);
+        s += ion_na.memory();
+        s += ion_k.memory();
+        return s;
+    }
+
+    void set_params(value_type t_, value_type dt_) override {
+        t = t_;
+        dt = dt_;
+    }
+
+    std::string name() const override {
+        return "hh";
+    }
+
+    mechanismKind kind() const override {
+        return mechanismKind::density;
+    }
+
+    bool uses_ion(ionKind k) const override {
+        switch(k) {
+            case ionKind::na : return true;
+            case ionKind::ca : return false;
+            case ionKind::k  : return true;
+        }
+        return false;
+    }
+
+    void set_ion(ionKind k, ion_type& i) override {
+        using nest::mc::algorithms::index_into;
+        if(k==ionKind::na) {
+            ion_na.index = index_into(i.node_index(), node_index_);
+            ion_na.ina = i.current();
+            ion_na.ena = i.reversal_potential();
+            return;
+        }
+        if(k==ionKind::k) {
+            ion_k.index = index_into(i.node_index(), node_index_);
+            ion_k.ik = i.current();
+            ion_k.ek = i.reversal_potential();
+            return;
+        }
+        throw std::domain_error(nest::mc::util::pprintf("mechanism % does not support ion type\n", name()));
+    }
+
+    void nrn_state() {
+        const indexed_view_type vec_v(vec_v_, node_index_);
+        int n_ = node_index_.size();
+        for(int i_=0; i_<n_; ++i_) {
+            value_type v = vec_v[i_];
+            rates(i_, v);
+            m[i_] = minf[i_]+(m[i_]-minf[i_])*exp( -dt/mtau[i_]);
+            h[i_] = hinf[i_]+(h[i_]-hinf[i_])*exp( -dt/htau[i_]);
+            n[i_] = ninf[i_]+(n[i_]-ninf[i_])*exp( -dt/ntau[i_]);
+            printf("m h n : %18.14f %18.14f %18.14f\n", m[i_], h[i_], n[i_]);
+        }
+    }
+
+    void rates(const int i_, value_type v) {
+        value_type ll3_, ll1_, ll0_, alpha, beta, ll2_, sum, q10;
+        q10 = std::pow( 3, (celsius- 6.2999999999999998)/ 10);
+        printf("q10 %18.14f\n", q10);
+        ll2_ =  -(v+ 40);
+        ll0_ = ll2_/(exp(ll2_/ 10)- 1);
+        alpha =  0.10000000000000001*ll0_;
+        beta =  4*exp( -(v+ 65)/ 18);
+
+        //std::cout << "v  " << v << " dt " << dt << "\n";
+        //std::cout << "m (alpha, beta) : " << alpha << " " << beta << "\n";
+
+        sum = alpha+beta;
+        mtau[i_] =  1/(q10*sum);
+        minf[i_] = alpha/sum;
+
+        //////////////////////////////////////////////////////////
+
+        alpha =  0.070000000000000007*exp( -(v+ 65)/ 20);
+        beta =  1/(exp( -(v+ 35)/ 10)+ 1);
+
+        //std::cout << "h (alpha, beta) : " << alpha << " " << beta << "\n";
+
+        sum = alpha+beta;
+        htau[i_] =  1/(q10*sum);
+        hinf[i_] = alpha/sum;
+
+        //////////////////////////////////////////////////////////
+
+        //ll3_ =  -v+ 55; // TODO : inlining is breaking
+        ll3_ =  -(v+ 55);
+        ll1_ = ll3_/(exp(ll3_/ 10)- 1);
+        alpha =  0.01*ll1_;
+        beta =  0.125*exp( -(v+ 65)/ 80);
+
+        //std::cout << "n (alpha, beta) : " << alpha << " " << beta << "\n";
+
+        //printf("m_inf, h_inf, n_inf : %18.16f %18.16f %18.16f\n",
+               //minf[i_], hinf[i_], ninf[i_]);
+
+        sum = alpha+beta;
+        ntau[i_] =  1/(q10*sum); // TODO : make modparser insert parenthesis
+        ninf[i_] = alpha/sum;
+    }
+
+    void nrn_current() {
+        const indexed_view_type ion_ek(ion_k.ek, ion_k.index);
+        indexed_view_type ion_ina(ion_na.ina, ion_na.index);
+        const indexed_view_type ion_ena(ion_na.ena, ion_na.index);
+        indexed_view_type ion_ik(ion_k.ik, ion_k.index);
+        indexed_view_type vec_i(vec_i_, node_index_);
+        const indexed_view_type vec_v(vec_v_, node_index_);
+        int n_ = node_index_.size();
+        for(int i_=0; i_<n_; ++i_) {
+            value_type ek = ion_ek[i_];
+            value_type ena = ion_ena[i_];
+            value_type v = vec_v[i_];
+            value_type il, ina, ik, current_;
+            gna[i_] = gnabar[i_]*m[i_]*m[i_]*m[i_]*h[i_];
+            ina = gna[i_]*(v-ena);
+            current_ = ina;
+            gk[i_] = gkbar[i_]*n[i_]*n[i_]*n[i_]*n[i_];
+            ik = gk[i_]*(v-ek);
+            current_ = current_+ik;
+            il = gl[i_]*(v-el[i_]);
+            current_ = current_+il;
+            ion_ina[i_] += ina;
+            ion_ik[i_] += ik;
+            vec_i[i_] += current_;
+            printf("i = (l+k+na) %18.16f = %18.16f %18.16f %18.16f\n",
+                   current_, il, ik, ina);
+        }
+    }
+
+    void nrn_init() {
+        const indexed_view_type vec_v(vec_v_, node_index_);
+        int n_ = node_index_.size();
+        for(int i_=0; i_<n_; ++i_) {
+            value_type v = vec_v[i_];
+            rates(i_, v);
+            m[i_] = minf[i_];
+            h[i_] = hinf[i_];
+            n[i_] = ninf[i_];
+            printf("initial conditions for m, h, n : %16.14f %16.14f %16.14f\n", m[0], h[0], n[0]);
+        }
+    }
+
+    vector_type data_;
+    view_type gnabar;
+    view_type minf;
+    view_type h;
+    view_type m;
+    view_type gl;
+    view_type gkbar;
+    view_type el;
+    view_type ninf;
+    view_type mtau;
+    view_type gna;
+    view_type gk;
+    view_type n;
+    view_type hinf;
+    view_type ntau;
+    view_type htau;
+    value_type t = std::numeric_limits<value_type>::quiet_NaN();
+    value_type dt = std::numeric_limits<value_type>::quiet_NaN();
+    value_type celsius = 6.3; // TODO change from 37
+
+    using base::vec_v_;
+    using base::vec_i_;
+    using base::node_index_;
+
+};
+
+template<typename T, typename I>
+struct helper : public mechanism_helper<T, I> {
+    using base = mechanism_helper<T, I>;
+    using index_view  = typename base::index_view;
+    using view_type  = typename base::view_type;
+    using mechanism_ptr_type  = typename base::mechanism_ptr_type;
+    using mechanism_type = mechanism_hh<T, I>;
+
+    std::string
+    name() const override
+    {
+        return "hh";
+    }
+
+    mechanism_ptr<T,I>
+    new_mechanism(view_type vec_v, view_type vec_i, index_view node_index) const override
+    {
+        return nest::mc::mechanisms::make_mechanism<mechanism_type>(vec_v, vec_i, node_index);
+    }
+
+    void
+    set_parameters(mechanism_ptr_type&, parameter_list const&) const override
+    {
+    }
+
+};
+
+}}}} // namespaces
diff --git a/mechanisms/generate.sh b/mechanisms/generate.sh
new file mode 100755
index 0000000000000000000000000000000000000000..9fe83f04d1b2775eae25f0e4ec7030cd89ee6163
--- /dev/null
+++ b/mechanisms/generate.sh
@@ -0,0 +1,4 @@
+for mech in pas hh
+do
+    modcc -t cpu -o ../include/mechanisms/$mech.hpp ./mod/$mech.mod
+done
diff --git a/mechanisms/mod/hh.mod b/mechanisms/mod/hh.mod
new file mode 100644
index 0000000000000000000000000000000000000000..8b23f44a4dc3e27cdbb2d78a161b9504c8651073
--- /dev/null
+++ b/mechanisms/mod/hh.mod
@@ -0,0 +1,125 @@
+TITLE hh.mod   squid sodium, potassium, and leak channels
+
+: COMMENT
+:  This is the original Hodgkin-Huxley treatment for the set of sodium, 
+:   potassium, and leakage channels found in the squid giant axon membrane.
+:   ("A quantitative description of membrane current and its application 
+:   conduction and excitation in nerve" J.Physiol. (Lond.) 117:500-544 (1952).)
+:  Membrane voltage is in absolute mV and has been reversed in polarity
+:   from the original HH convention and shifted to reflect a resting potential
+:   of -65 mV.
+:  Remember to set celsius=6.3 (or whatever) in your HOC file.
+:  See squid.hoc for an example of a simulation using this model.
+:  SW Jaslove  6 March, 1992
+: ENDCOMMENT
+
+UNITS {
+    (mA) = (milliamp)
+    (mV) = (millivolt)
+    (S) = (siemens)
+}
+
+NEURON {
+    SUFFIX hh
+    USEION na READ ena WRITE ina
+    USEION k READ ek WRITE ik
+    NONSPECIFIC_CURRENT il
+    RANGE gnabar, gkbar, gl, el, gna, gk
+    GLOBAL minf, hinf, ninf, mtau, htau, ntau
+}
+
+PARAMETER {
+    gnabar = .12 (S/cm2)  : <0,1e9>
+    gkbar = .036 (S/cm2)  : <0,1e9>
+    gl = .0003 (S/cm2)    : <0,1e9>
+    el = -54.3 (mV)
+    celsius = 6.3 (degC)
+    :celsius = 37 (degC)
+}
+
+STATE {
+    m h n
+}
+
+ASSIGNED {
+    v (mV)
+
+    gna (S/cm2)
+    gk (S/cm2)
+    minf
+    hinf
+    ninf
+    mtau (ms)
+    htau (ms)
+    ntau (ms)
+}
+
+BREAKPOINT {
+    SOLVE states METHOD cnexp
+    gna = gnabar*m*m*m*h
+    ina = gna*(v - ena)
+    gk = gkbar*n*n*n*n
+    ik = gk*(v - ek)
+    il = gl*(v - el)
+}
+
+INITIAL {
+    rates(v)
+    m = minf
+    h = hinf
+    n = ninf
+}
+
+DERIVATIVE states {
+    rates(v)
+    m' =  (minf-m)/mtau
+    h' = (hinf-h)/htau
+    n' = (ninf-n)/ntau
+}
+
+PROCEDURE rates(v) :(mV))
+{
+    LOCAL  alpha, beta, sum, q10
+
+    q10 = 3^((celsius - 6.3)/10)
+
+    :"m" sodium activation system
+    alpha = .1 * vtrap(-(v+40),10)
+    beta =  4 * exp(-(v+65)/18)
+    sum = alpha + beta
+    mtau = 1/(q10*sum)
+    minf = alpha/sum
+
+    :"h" sodium inactivation system
+    alpha = .07 * exp(-(v+65)/20)
+    beta = 1 / (exp(-(v+35)/10) + 1)
+    sum = alpha + beta
+    htau = 1/(q10*sum)
+    hinf = alpha/sum
+
+    :"n" potassium activation system
+    alpha = .01*vtrap(-(v+55),10)
+    beta = .125*exp(-(v+65)/80)
+    sum = alpha + beta
+    ntau = 1/(q10*sum)
+    ninf = alpha/sum
+}
+
+FUNCTION vtrap(x,y) {
+    : Traps for 0 in denominator of rate eqns.
+
+    : Disabled for function inlining...
+    : there is a very good case for making vtrap a builtin function for the language
+    : - it is ubiquitous: used in so many models of ion channels
+    : - platform specific optimizations written in assembly/intrinsics
+    :   required to facilitate vectorization/minimize branch misses
+
+    :if (fabs(x/y) < 1e-6) {
+    :    vtrap = y*(1 - x/y/2)
+    :}else{
+    :    vtrap = x/(exp(x/y) - 1)
+    :}
+
+    vtrap = x/(exp(x/y) - 1)
+}
+
diff --git a/mechanisms/mod/pas.mod b/mechanisms/mod/pas.mod
new file mode 100644
index 0000000000000000000000000000000000000000..b7dc2ad6d2a343bb2333c5b8ecfec17060150865
--- /dev/null
+++ b/mechanisms/mod/pas.mod
@@ -0,0 +1,28 @@
+TITLE passive membrane channel
+
+UNITS {
+    (mV) = (millivolt)
+    (mA) = (milliamp)
+    (S) = (siemens)
+}
+
+NEURON {
+    SUFFIX pas
+    NONSPECIFIC_CURRENT i
+    RANGE g, e
+}
+
+INITIAL {}
+
+PARAMETER {
+    g = .001    (S/cm2) :<0,1e9>
+    e = -65 (mV) : we use -65 for the ball and stick model, instead of Neuron default of -70
+}
+
+ASSIGNED {
+    v (mV)
+}
+
+BREAKPOINT {
+    i = g*(v - e)
+}
diff --git a/nrn/ball_and_stick.py b/nrn/ball_and_stick.py
new file mode 100644
index 0000000000000000000000000000000000000000..5fe977fe594ada556ebce1da09c9a15c3f3b03e3
--- /dev/null
+++ b/nrn/ball_and_stick.py
@@ -0,0 +1,138 @@
+from timeit import default_timer as timer
+import os.path
+from matplotlib import pyplot
+import numpy as np
+import json
+import argparse
+from neuron import gui, h
+
+parser = argparse.ArgumentParser(description='generate spike train ball and stick model with hh channels at soma and pas channels in dendrite')
+parser.add_argument('--plot', action='store_true', dest='plot')
+args = parser.parse_args()
+
+soma = h.Section(name='soma')
+dend = h.Section(name='dend')
+
+dend.connect(soma(1))
+
+# Surface area of cylinder is 2*pi*r*h (sealed ends are implicit).
+# Here we make a square cylinder in that the diameter
+# is equal to the height, so diam = h. ==> Area = 4*pi*r^2
+# We want a soma of 500 microns squared:
+# r^2 = 500/(4*pi) ==> r = 6.2078, diam = 12.6157
+soma.L = soma.diam = 12.6157 # Makes a soma of 500 microns squared.
+dend.L = 200 # microns
+dend.diam = 1 # microns
+
+for sec in h.allsec():
+    sec.Ra = 100    # Axial resistance in Ohm * cm
+    sec.cm = 1      # Membrane capacitance in micro Farads / cm^2
+
+# Insert active Hodgkin-Huxley current in the soma
+soma.insert('hh')
+soma.gnabar_hh = 0.12  # Sodium conductance in S/cm2
+soma.gkbar_hh = 0.036  # Potassium conductance in S/cm2
+soma.gl_hh = 0.0003    # Leak conductance in S/cm2
+soma.el_hh = -54.3     # Reversal potential in mV
+
+# Insert passive current in the dendrite
+dend.insert('pas')
+dend.g_pas = 0.001  # Passive conductance in S/cm2
+dend.e_pas = -65    # Leak reversal potential mV
+
+stim = h.IClamp(dend(1))
+stim.delay = 5
+stim.dur = 80
+stim.amp = 3*0.1
+
+if args.plot :
+    pyplot.figure(figsize=(8,4)) # Default figsize is (8,6)
+    pyplot.grid()
+
+simdur = 100.0
+h.tstop = simdur
+h.dt = 0.001
+
+start = timer()
+results = []
+for nseg in [5, 11, 21, 41, 81, 161] :
+
+    print 'simulation with ', nseg, ' compartments in dendrite...'
+
+    dend.nseg=nseg
+
+    # record voltages
+    v_soma = h.Vector() # soma
+    v_dend = h.Vector() # middle of dendrite
+    v_clamp= h.Vector() # end of dendrite at clamp location
+
+    v_soma.record( soma(0.5)._ref_v)
+    v_dend.record( dend(0.5)._ref_v)
+    v_clamp.record(dend(1.0)._ref_v)
+
+    # record spikes
+    # this is a bit verbose, no?
+    spike_counter_soma = h.APCount(soma(0.5))
+    spike_counter_soma.thresh = 0
+    spike_counter_dend = h.APCount(dend(0.5))
+    spike_counter_dend.thresh = -10
+    spike_counter_clamp = h.APCount(dend(1.0))
+    spike_counter_clamp.thresh = 10
+
+    spikes_soma = h.Vector() # soma
+    spikes_dend = h.Vector() # middle of dendrite
+    spikes_clamp= h.Vector() # end of dendrite at clamp location
+
+    spike_counter_soma.record(spikes_soma)
+    spike_counter_dend.record(spikes_dend)
+    spike_counter_clamp.record(spikes_clamp)
+
+    # record time stamps
+    t_vec = h.Vector()
+    t_vec.record(h._ref_t)
+
+    # finally it's time to run the simulation
+    h.run()
+
+    results.append(
+        {
+            "nseg": nseg,
+            "dt" : h.dt,
+            "measurements": {
+               "soma" : {
+                   "thresh" :  spike_counter_soma.thresh,
+                   "spikes" :  spikes_soma.to_python()
+               },
+               "dend" : {
+                   "thresh" :  spike_counter_dend.thresh,
+                   "spikes" :  spikes_dend.to_python()
+               },
+               "clamp" : {
+                   "thresh" :  spike_counter_clamp.thresh,
+                   "spikes" :  spikes_clamp.to_python()
+               }
+           }
+        }
+    )
+
+    if args.plot :
+        pyplot.plot(t_vec, v_soma,  'k', linewidth=1, label='soma ' + str(nseg))
+        pyplot.plot(t_vec, v_dend,  'b', linewidth=1, label='dend ' + str(nseg))
+        pyplot.plot(t_vec, v_clamp, 'r', linewidth=1, label='clamp ' + str(nseg))
+
+# time the simulations
+end = timer()
+print "took ", end-start, " seconds"
+
+# save the spike info as in json format
+fp = open('ball_and_stick.json', 'w')
+json.dump(results, fp, indent=2)
+
+if args.plot :
+    pyplot.xlabel('time (ms)')
+    pyplot.ylabel('mV')
+    pyplot.xlim([0, simdur])
+    pyplot.legend()
+
+    pyplot.show()
+
diff --git a/nrn/generate_validation_data.sh b/nrn/generate_validation_data.sh
new file mode 100755
index 0000000000000000000000000000000000000000..8627a0c56ecd1f2137178a193be99207cb06ef46
--- /dev/null
+++ b/nrn/generate_validation_data.sh
@@ -0,0 +1,2 @@
+python2.7 soma.py
+
diff --git a/nrn/soma.py b/nrn/soma.py
new file mode 100644
index 0000000000000000000000000000000000000000..3af4f50f3e6e1ae7761591ab68fd32a1187f9c87
--- /dev/null
+++ b/nrn/soma.py
@@ -0,0 +1,74 @@
+from timeit import default_timer as timer
+import os.path
+from matplotlib import pyplot
+import numpy as np
+import json
+import argparse
+from neuron import gui, h
+
+parser = argparse.ArgumentParser(description='generate spike train info for a soma with hh channels')
+parser.add_argument('--plot', action='store_true', dest='plot')
+args = parser.parse_args()
+
+if args.plot :
+    print '-- plotting turned on'
+else :
+    print '-- plotting turned off'
+
+soma = h.Section(name='soma')
+
+soma.L = soma.diam = 18.8
+soma.Ra = 123
+
+print "Surface area of soma =", h.area(0.5, sec=soma)
+
+soma.insert('hh')
+
+stim = h.IClamp(soma(0.5))
+stim.delay = 10
+stim.dur = 100
+stim.amp = 0.1
+
+spike_counter = h.APCount(soma(0.5))
+spike_counter.thresh = 0
+
+v_vec = h.Vector()        # Membrane potential vector
+t_vec = h.Vector()        # Time stamp vector
+s_vec = h.Vector()        # Time stamp vector
+v_vec.record(soma(0.5)._ref_v)
+t_vec.record(h._ref_t)
+spike_counter.record(s_vec)
+
+simdur = 120
+
+# initialize plot
+if args.plot :
+    pyplot.figure(figsize=(8,4)) # Default figsize is (8,6)
+
+h.tstop = simdur
+
+# run neuron with multiple dt
+start = timer()
+results = []
+for dt in [0.05, 0.02, 0.01, 0.001, 0.0001]:
+    h.dt = dt
+    h.run()
+    results.append({"dt": dt, "spikes": s_vec.to_python()})
+    if args.plot :
+        pyplot.plot(t_vec, v_vec, label='neuron ' + str(dt))
+
+end = timer()
+print "took ", end-start, " seconds"
+
+# save the spike info as in json format
+fp = open('soma.json', 'w')
+json.dump(results, fp, indent=1)
+
+if args.plot :
+    pyplot.xlabel('time (ms)')
+    pyplot.ylabel('mV')
+    pyplot.xlim([0, 120])
+    pyplot.grid()
+    pyplot.legend()
+    pyplot.show()
+
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 5696287e9b2c56096146c67fe0d891548a0abfd4..8a8286bda8e314d47f5e89b2b454bd6b84709e67 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,10 +1,11 @@
 set(HEADERS
     swcio.hpp
-    )
+)
 set(BASE_SOURCES
+    cell.cpp
+    mechanism_interface.cpp
     parameter_list.cpp
     swcio.cpp
-    cell.cpp
 )
 
 add_library(cellalgo ${BASE_SOURCES} ${HEADERS})
diff --git a/src/algorithms.hpp b/src/algorithms.hpp
index 8ac62055f351212fb75c72657fa6157463450f45..b3943c688b33b7a2114f2dc88d2f3d22905d273c 100644
--- a/src/algorithms.hpp
+++ b/src/algorithms.hpp
@@ -7,6 +7,8 @@
 #include <type_traits>
 #include <vector>
 
+#include "util.hpp"
+
 /*
  * Some simple wrappers around stl algorithms to improve readability of code
  * that uses them.
@@ -130,15 +132,16 @@ namespace algorithms{
             return false;
         }
 
-        std::vector<bool> is_leaf(parent_index.size(), false);
+        int n = parent_index.size();
+        std::vector<bool> is_leaf(n, false);
 
-        for (std::size_t i = 1; i < parent_index.size(); ++i) {
+        for(auto i=1; i<n; ++i) {
             auto p = parent_index[i];
-            if (is_leaf[p]) {
+            if(is_leaf[p]) {
                 return false;
             }
 
-            if (p != i-1) {
+            if(p != i-1) {
                 // we have a branch and i-1 is a leaf node
                 is_leaf[i-1] = true;
             }
@@ -163,7 +166,7 @@ namespace algorithms{
         return count;
     }
 
-    template<typename C, bool CheckStrict = true>
+    template<typename C>
     std::vector<typename C::value_type> branches(const C &parent_index)
     {
         static_assert(
@@ -171,11 +174,7 @@ namespace algorithms{
             "integral type required"
         );
 
-        if (CheckStrict && !has_contiguous_segments(parent_index)) {
-            throw std::invalid_argument(
-                "parent_index has not contiguous branch numbering"
-            );
-        }
+        EXPECTS(has_contiguous_segments(parent_index));
 
         auto num_child = child_count(parent_index);
         std::vector<typename C::value_type> branch_runs(
@@ -196,9 +195,54 @@ namespace algorithms{
     }
 
     template<typename C>
-    std::vector<typename C::value_type> branches_fast(const C &parent_index)
+    bool is_sorted(const C& c)
+    {
+        return std::is_sorted(c.begin(), c.end());
+    }
+
+    template<typename C>
+    bool is_unique(const C& c)
+    {
+        return std::adjacent_find(c.begin(), c.end()) == c.end();
+    }
+
+    /// Return and index that maps entries in sub to their corresponding
+    /// values in super, where sub is a subset of super.
+    ///
+    /// Both sets are sorted and have unique entries.
+    /// Complexity is O(n), where n is size of super
+    template<typename C>
+    // C::iterator models forward_iterator
+    // C::value_type is_integral
+    C index_into(const C& super, const C& sub)
     {
-        return branches<C,false>(parent_index);
+        //EXPECTS {s \in super : \forall s \in sub};
+        EXPECTS(is_unique(super) && is_unique(sub));
+        EXPECTS(is_sorted(super) && is_sorted(sub));
+        EXPECTS(sub.size() <= super.size());
+
+        static_assert(
+            std::is_integral<typename C::value_type>::value,
+            "index_into only applies to integral types"
+        );
+
+        C out(sub.size()); // out will have one entry for each index in sub
+
+        auto sub_it=sub.begin();
+        auto super_it=super.begin();
+        auto sub_idx=0u, super_idx = 0u;
+
+        while(sub_it!=sub.end() && super_it!=super.end()) {
+            if(*sub_it==*super_it) {
+                out[sub_idx] = super_idx;
+                ++sub_it; ++sub_idx;
+            }
+            ++super_it; ++super_idx;
+        }
+
+        EXPECTS(sub_idx==sub.size());
+
+        return out;
     }
 
 } // namespace algorithms
diff --git a/src/cell.cpp b/src/cell.cpp
index ab01007a206ea241befea21fbed83c531298a36d..a6986f0673ca05827ee89493aca2230ec16cec30 100644
--- a/src/cell.cpp
+++ b/src/cell.cpp
@@ -4,6 +4,20 @@
 namespace nest {
 namespace mc {
 
+int find_compartment_index(
+    segment_location const& location,
+    compartment_model const& graph
+) {
+    EXPECTS(location.segment<graph.segment_index.size());
+    const auto& si = graph.segment_index;
+    const auto seg = location.segment;
+
+    auto first = si[seg];
+    auto n = si[seg+1] - first;
+    auto index = std::floor(n*location.position);
+    return index<n ? first+index : first+n-1;
+}
+
 cell::cell()
 {
     // insert a placeholder segment for the soma
@@ -20,7 +34,7 @@ int cell::num_segments() const
 // note: I think that we have to enforce that the soma is the first
 //       segment that is added
 //
-void cell::add_soma(value_type radius, point_type center)
+soma_segment* cell::add_soma(value_type radius, point_type center)
 {
     if(has_soma()) {
         throw std::domain_error(
@@ -35,9 +49,11 @@ void cell::add_soma(value_type radius, point_type center)
     else {
         segments_[0] = make_segment<soma_segment>(radius);
     }
+
+    return segments_[0]->as_soma();
 }
 
-void cell::add_cable(cell::index_type parent, segment_ptr&& cable)
+cable_segment* cell::add_cable(cell::index_type parent, segment_ptr&& cable)
 {
     // check for a valid parent id
     if(cable->is_soma()) {
@@ -54,6 +70,8 @@ void cell::add_cable(cell::index_type parent, segment_ptr&& cable)
     }
     segments_.push_back(std::move(cable));
     parents_.push_back(parent);
+
+    return segments_.back()->as_cable();
 }
 
 segment* cell::segment(int index)
@@ -137,9 +155,9 @@ std::vector<int> cell::compartment_counts() const
     return comp_count;
 }
 
-int cell::num_compartments() const
+size_t cell::num_compartments() const
 {
-    auto n = 0;
+    auto n = 0u;
     for(auto &s : segments_) {
         n += s->num_compartments();
     }
@@ -158,6 +176,20 @@ compartment_model cell::model() const
     return m;
 }
 
+
+void cell::add_stimulus( segment_location loc, i_clamp stim)
+{
+    if(!(loc.segment<num_segments())) {
+        throw std::out_of_range(
+            util::pprintf(
+                "can't insert stimulus in segment % of a cell with % segments",
+                loc.segment, num_segments()
+            )
+        );
+    }
+    stimulii_.push_back({loc, std::move(stim)});
+}
+
 std::vector<int> const& cell::segment_parents() const
 {
     return parents_;
diff --git a/src/cell.hpp b/src/cell.hpp
index 68bc4c5a21f99237b18557f690226c0441e580c6..ba5f9b6bff227231cbb4f46e27dd2af4ff2943ce 100644
--- a/src/cell.hpp
+++ b/src/cell.hpp
@@ -5,8 +5,9 @@
 #include <thread>
 #include <vector>
 
-#include "segment.hpp"
-#include "cell_tree.hpp"
+#include <segment.hpp>
+#include <cell_tree.hpp>
+#include <stimulus.hpp>
 
 namespace nest {
 namespace mc {
@@ -19,6 +20,21 @@ struct compartment_model {
     std::vector<int> segment_index;
 };
 
+struct segment_location {
+    segment_location(int s, double l)
+    : segment(s), position(l)
+    {
+        EXPECTS(position>=0. && position<=1.);
+    }
+    int segment;
+    double position;
+};
+
+int find_compartment_index(
+    segment_location const& location,
+    compartment_model const& graph
+);
+
 /// high-level abstract representation of a cell and its segments
 class cell {
     public:
@@ -33,18 +49,18 @@ class cell {
 
     /// add a soma to the cell
     /// radius must be specified
-    void add_soma(value_type radius, point_type center=point_type());
+    soma_segment* add_soma(value_type radius, point_type center=point_type());
 
     /// add a cable
     /// parent is the index of the parent segment for the cable section
     /// cable is the segment that will be moved into the cell
-    void add_cable(index_type parent, segment_ptr&& cable);
+    cable_segment* add_cable(index_type parent, segment_ptr&& cable);
 
     /// add a cable by constructing it in place
     /// parent is the index of the parent segment for the cable section
     /// args are the arguments to be used to consruct the new cable
     template <typename... Args>
-    void add_cable(index_type parent, Args ...args);
+    cable_segment* add_cable(index_type parent, Args ...args);
 
     /// the number of segments in the cell
     int num_segments() const;
@@ -70,7 +86,7 @@ class cell {
     value_type area() const;
 
     /// the total number of compartments over all segments
-    int num_compartments() const;
+    size_t num_compartments() const;
 
     std::vector<segment_ptr> const& segments() const;
 
@@ -83,17 +99,33 @@ class cell {
 
     compartment_model model() const;
 
+    void add_stimulus(segment_location loc, i_clamp stim);
+
+    std::vector<std::pair<segment_location, i_clamp>>&
+    stimulii() {
+        return stimulii_;
+    }
+
+    const std::vector<std::pair<segment_location, i_clamp>>&
+    stimulii() const {
+        return stimulii_;
+    }
+
     private:
 
     // storage for connections
     std::vector<index_type> parents_;
+
     // the segments
     std::vector<segment_ptr> segments_;
+
+    // the stimulii
+    std::vector<std::pair<segment_location, i_clamp>> stimulii_;
 };
 
 // create a cable by forwarding cable construction parameters provided by the user
 template <typename... Args>
-void cell::add_cable(cell::index_type parent, Args ...args)
+cable_segment* cell::add_cable(cell::index_type parent, Args ...args)
 {
     // check for a valid parent id
     if(parent>=num_segments()) {
@@ -103,6 +135,8 @@ void cell::add_cable(cell::index_type parent, Args ...args)
     }
     segments_.push_back(make_segment<cable_segment>(std::forward<Args>(args)...));
     parents_.push_back(parent);
+
+    return segments_.back()->as_cable();
 }
 
 } // namespace mc
diff --git a/src/fvm.hpp b/src/fvm.hpp
index b0eedb412c94aec0e3a6e4b43aab86f14772f21a..69d23d0d1fe97e341ad8de3dc25ce8c0771ef78c 100644
--- a/src/fvm.hpp
+++ b/src/fvm.hpp
@@ -1,13 +1,20 @@
 #pragma once
 
 #include <algorithm>
+#include <map>
+#include <string>
+#include <vector>
 
+#include <algorithms.hpp>
 #include <cell.hpp>
-#include <segment.hpp>
+#include <ion.hpp>
 #include <math.hpp>
 #include <matrix.hpp>
+#include <mechanism.hpp>
+#include <mechanism_interface.hpp>
 #include <util.hpp>
-#include <algorithms.hpp>
+#include <segment.hpp>
+#include <stimulus.hpp>
 
 #include <vector/include/Vector.hpp>
 
@@ -24,8 +31,16 @@ class fvm_cell {
     /// the integral index type
     using size_type  = I;
 
+    /// the type used to store matrix information
     using matrix_type = matrix<value_type, size_type>;
 
+    /// mechanism type
+    using mechanism_type =
+        nest::mc::mechanisms::mechanism_ptr<value_type, size_type>;
+
+    /// 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
@@ -44,28 +59,85 @@ class fvm_cell {
 
     /// TODO this should be const
     /// which requires const_view in the vector library
-    matrix_type& jacobian();
+    matrix_type& jacobian() {
+        return matrix_;
+    }
 
     /// TODO this should be const
     /// return list of CV areas in :
     ///          um^2
     ///     1e-6.mm^2
     ///     1e-8.cm^2
-    vector_view cv_areas();
+    vector_view cv_areas() {
+        return cv_areas_;
+    }
 
     /// TODO this should be const
     /// return the capacitance of each CV surface
-    /// note that this is the total capacitance, not per unit area
-    /// which is equivalent to sigma_i * c_m
-    vector_view cv_capacitance();
+    /// this is the total capacitance, not per unit area,
+    /// i.e. equivalent to sigma_i * c_m
+    vector_view cv_capacitance() {
+        return cv_capacitance_;
+    }
 
-    std::size_t size() const
-    {
+    /// return the voltage in each CV
+    vector_view voltage() {
+        return voltage_;
+    }
+
+    std::size_t size() const {
         return matrix_.size();
     }
 
+    /// return reference to in iterable container of the mechanisms
+    std::vector<mechanism_type>& mechanisms() {
+        return mechanisms_;
+    }
+
+    /// return reference to list of ions
+    //std::map<mechanisms::ionKind, ion_type> ions_;
+    std::map<mechanisms::ionKind, ion_type>& ions() {
+        return ions_;
+    }
+    std::map<mechanisms::ionKind, ion_type> const& ions() const {
+        return ions_;
+    }
+
+    /// return reference to sodium ion
+    ion_type& ion_na() {
+        return ions_[mechanisms::ionKind::na];
+    }
+    ion_type const& ion_na() const {
+        return ions_[mechanisms::ionKind::na];
+    }
+
+    /// return reference to calcium ion
+    ion_type& ion_ca() {
+        return ions_[mechanisms::ionKind::ca];
+    }
+    ion_type const& ion_ca() const {
+        return ions_[mechanisms::ionKind::ca];
+    }
+
+    /// return reference to pottasium ion
+    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);
+
+    /// set initial states
+    void initialize();
+
     private:
 
+    /// current time
+    value_type t_ = value_type{0};
+
     /// the linear system for implicit time stepping of cell state
     matrix_type matrix_;
 
@@ -89,11 +161,18 @@ class fvm_cell {
     /// the potential in mV in each CV
     vector_type voltage_;
 
+    /// the set of mechanisms present in the cell
+    std::vector<mechanism_type> mechanisms_;
+
+    /// the ion species
+    std::map<mechanisms::ionKind, ion_type> ions_;
+
+    std::vector<std::pair<uint32_t, i_clamp>> stimulii_;
 };
 
-////////////////////////////////////////////////////////////
-// Implementation
-////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+//////////////////////////////// Implementation ////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
 
 template <typename T, typename I>
 fvm_cell<T, I>::fvm_cell(nest::mc::cell const& cell)
@@ -106,8 +185,16 @@ fvm_cell<T, I>::fvm_cell(nest::mc::cell const& cell)
     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);
+
     auto parent_index = matrix_.p();
     auto const& segment_index = graph.segment_index;
 
@@ -148,7 +235,6 @@ fvm_cell<T, I>::fvm_cell(nest::mc::cell const& cell)
                 auto radius_center = math::mean(c.radius);
                 auto area_face = math::area_circle( radius_center );
                 face_alpha_[i] = area_face  / (c_m * r_L * c.length);
-                cv_capacitance_[i] = c_m;
 
                 auto halflen = c.length/2;
 
@@ -170,27 +256,127 @@ fvm_cell<T, I>::fvm_cell(nest::mc::cell const& cell)
     for(auto i=0u; i<size(); ++i) {
         cv_capacitance_[i] /= cv_areas_[i];
     }
-}
 
-template <typename T, typename I>
-typename fvm_cell<T,I>::matrix_type&
-fvm_cell<T,I>::jacobian()
-{
-    return matrix_;
-}
+    /////////////////////////////////////////////
+    //  create mechanisms
+    /////////////////////////////////////////////
 
-template <typename T, typename I>
-typename fvm_cell<T,I>::vector_view
-fvm_cell<T,I>::cv_areas()
-{
-    return cv_areas_;
-}
+    // FIXME : candidate for a private member function
 
-template <typename T, typename I>
-typename fvm_cell<T,I>::vector_view
-fvm_cell<T,I>::cv_capacitance()
-{
-    return cv_capacitance_;
+    // 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;
+
+    for(auto 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
+            if(mech.name() != "membrane") {
+                mech_map[mech.name()].push_back(i);
+            }
+        }
+    }
+
+    // Create the mechanism implementations with the state for each mechanism
+    // 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);
+
+        // calculate the number of compartments that contain the mechanism
+        auto num_comp = 0u;
+        for(auto seg : mech.second) {
+            num_comp += segment_index[seg+1] - segment_index[seg];
+        }
+
+        // build a vector of the indexes of the compartments that contain
+        // the mechanism
+        std::vector<int> compartment_index(num_comp);
+        auto pos = 0u;
+        for(auto seg : mech.second) {
+            auto seg_size = segment_index[seg+1] - segment_index[seg];
+            std::iota(
+                compartment_index.data() + pos,
+                compartment_index.data() + pos + seg_size,
+                segment_index[seg]
+            );
+            pos += seg_size;
+        }
+
+        // instantiate the mechanism
+        index_view node_index(compartment_index.data(), compartment_index.size());
+        mechanisms_.push_back(
+            helper->new_mechanism(voltage_, current_, node_index)
+        );
+    }
+
+    /////////////////////////////////////////////
+    // build the ion species
+    // FIXME : this should be a private member function
+    /////////////////////////////////////////////
+    for(auto ion : mechanisms::ion_kinds()) {
+        auto indexes =
+            std::make_pair(std::vector<int>(size()), std::vector<int>(size()));
+        auto ends =
+            std::make_pair(indexes.first.begin(), indexes.second.begin());
+
+        // after the loop the range
+        //      [indexes.first.begin(), ends.first)
+        // will hold the indexes of the compartments that require ion
+        for(auto& mech : mechanisms_) {
+            if(mech->uses_ion(ion)) {
+                ends.second =
+                    std::set_union(
+                        mech->node_index().begin(), mech->node_index().end(),
+                        indexes.first.begin(), ends.first,
+                        indexes.second.begin()
+                    );
+                std::swap(indexes.first, indexes.second);
+                std::swap(ends.first, ends.second);
+            }
+        }
+
+        // create the ion state
+        if(auto n = std::distance(indexes.first.begin(), ends.first)) {
+            ions_.emplace(ion, index_view(indexes.first.data(), n));
+        }
+
+        // join the ion reference in each mechanism into the cell-wide ion state
+        for(auto& mech : mechanisms_) {
+            if(mech->uses_ion(ion)) {
+                mech->set_ion(ion, ions_[ion]);
+            }
+        }
+    }
+
+    // FIXME: Hard code parameters for now.
+    //        Take defaults for reversal potential of sodium and potassium from
+    //        the default values in Neuron.
+    //        Neuron's defaults are defined in the file
+    //          nrn/src/nrnoc/membdef.h
+    using memory::all;
+
+    constexpr value_type DEF_vrest = -65.0; // same name as #define in Neuron
+
+    ion_na().reversal_potential()(all)     = 115+DEF_vrest; // mV
+    ion_na().internal_concentration()(all) =  10.0;         // mM
+    ion_na().external_concentration()(all) = 140.0;         // mM
+
+    ion_k().reversal_potential()(all)     = -12.0+DEF_vrest;// mV
+    ion_k().internal_concentration()(all) =  54.4;          // mM
+    ion_k().external_concentration()(all) =  2.5;           // mM
+
+    ion_ca().reversal_potential()(all)     = 12.5 * std::log(2.0/5e-5);// mV
+    ion_ca().internal_concentration()(all) = 5e-5;          // mM
+    ion_ca().external_concentration()(all) = 2.0;           // mM
+
+    // add the stimulii
+    for(const auto& stim : cell.stimulii()) {
+        auto idx = find_compartment_index(stim.first, graph);
+        stimulii_.push_back( {idx, stim.second} );
+    }
 }
 
 template <typename T, typename I>
@@ -217,9 +403,12 @@ 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) {
-        auto a = dt * face_alpha_[i];
+        // TODO get this right
+        // probably requires scaling a by cv_areas_[i] and cv_areas_[p[i]]
+        auto a = 1e5*dt * face_alpha_[i];
 
         d[i] +=  a;
         l[i]  = -a;
@@ -228,14 +417,77 @@ void fvm_cell<T, I>::setup_matrix(T dt)
         // add contribution to the diagonal of parent
         d[p[i]] += a;
     }
+    //std::cout << "d " << d << " l " << l << " u " << u << "\n";
 
     // the RHS of the linear system is
-    //      sigma_i * (V[i] - dt/cm*(im - ie))
+    //      V[i] - dt/cm*(im - ie)
+    auto factor = 10.*dt;
     for(auto i=0u; i<d.size(); ++i) {
-        rhs[i] = cv_areas_[i] * (voltage_[i] - dt/cv_capacitance_[i]*current_[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>
+void fvm_cell<T, I>::initialize()
+{
+    t_ = 0.;
+
+    // initialize mechanism states
+    for(auto& m : mechanisms_) {
+        m->nrn_init();
+    }
+}
+
+template <typename T, typename I>
+void fvm_cell<T, I>::advance(T dt)
+{
+    using memory::all;
+
+    current_(all) = 0.;
+
+    // update currents from ion channels
+    for(auto& m : mechanisms_) {
+        m->set_params(t_, dt);
+        m->nrn_current();
+    }
+
+    // add current contributions from stimulii
+    for(auto& stim : stimulii_) {
+        auto ie = stim.second.amplitude(t_);
+        auto loc = stim.first;
+
+        // the factor of 100 scales the injected current to 10^2.nA
+        current_[loc] -= 100.*ie/cv_areas_[loc];
     }
+
+    //std::cout << "t " << t_ << " current " << current_;
+
+    // set matrix diagonals and rhs
+    setup_matrix(dt);
+
+    //printf("rhs %18.14f    d %18.14f\n", matrix_.rhs()[0], matrix_.d()[0]);
+
+    // solve the linear system
+    matrix_.solve();
+
+    voltage_(all) = matrix_.rhs();
+
+    //printf("v solve %18.14f\n", voltage_[0]);
+
+    //std::cout << " v " << voltage_ << "\n";
+
+    // update states
+    for(auto& m : mechanisms_) {
+        m->nrn_state();
+    }
+
+    t_ += dt;
+    //std::cout << "******************\n";
 }
 
 } // namespace fvm
 } // namespace mc
 } // namespace nest
+
+
diff --git a/src/indexed_view.hpp b/src/indexed_view.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..0cbc22a91b3235f70af6b851050af5e5fdefa3a2
--- /dev/null
+++ b/src/indexed_view.hpp
@@ -0,0 +1,50 @@
+#pragma once
+
+#include <vector/include/Vector.hpp>
+
+namespace nest {
+namespace mc {
+
+template <typename T, typename I>
+struct indexed_view {
+    using value_type      = T;
+    using size_type       = I;
+    using view_type       = typename memory::HostVector<T>::view_type;
+    using index_view_type = typename memory::HostVector<I>::view_type;;
+
+    view_type       view;
+    index_view_type index; // TODO make this a const view
+
+    indexed_view(view_type v, index_view_type i)
+    :   view(v),
+        index(i)
+    {}
+
+    size_type size() const
+    {
+        return index.size();
+    }
+
+    // TODO
+    //
+    //  these should either
+    //      - return the internal reference type implementations from the containers
+    //          e.g. the GPU reference that does a copy-on-read from GPU memory
+    //      - or ensure that the result of dereferencing these is properly handled
+    //          i.e. by just returning a value for the const version
+
+    value_type&
+    operator[] (const size_type i)
+    {
+        return view[index[i]];
+    }
+
+    value_type const&
+    operator[] (const size_type i) const
+    {
+        return view[index[i]];
+    }
+};
+
+} // namespace mc
+} // namespace nest
diff --git a/src/ion.hpp b/src/ion.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..311e86003b4cfba2b4cd4ceb569fc97a346daca0
--- /dev/null
+++ b/src/ion.hpp
@@ -0,0 +1,111 @@
+#pragma once
+
+#include <vector/include/Vector.hpp>
+#include "indexed_view.hpp"
+
+namespace nest {
+namespace mc {
+namespace mechanisms {
+
+/*******************************************************************************
+
+  ion channels have the following fields :
+
+    ---------------------------------------------------
+    label   Ca      Na      K   name
+    ---------------------------------------------------
+    iX      ica     ina     ik  current
+    eX      eca     ena     ek  reversal_potential
+    Xi      cai     nai     ki  internal_concentration
+    Xo      cao     nao     ko  external_concentration
+    gX      gca     gna     gk  conductance
+    ---------------------------------------------------
+*******************************************************************************/
+
+/// let's enumerate the ion channel types
+enum class ionKind {ca, na, k};
+
+[[gnu::unused]] static
+std::string to_string(ionKind k)
+{
+    switch(k) {
+        case ionKind::na : return "sodium";
+        case ionKind::ca : return "calcium";
+        case ionKind::k  : return "pottasium";
+    }
+    return "unkown";
+}
+
+/// and a little helper to iterate over them
+[[gnu::unused]] static
+std::vector<ionKind> ion_kinds()
+{
+    return {ionKind::ca, ionKind::na, ionKind::k};
+}
+
+/// storage for ion channel information in a cell group
+template<typename T, typename I>
+class ion {
+public :
+    // expose tempalte parameters
+    using value_type  = T;
+    using size_type   = I;
+
+    // define storage types
+    using vector_type      = memory::HostVector<value_type>;
+    using index_type       = memory::HostVector<size_type>;
+    using vector_view_type = typename vector_type::view_type;
+    using index_view_type  = typename index_type::view_type;
+
+    using indexed_view_type = indexed_view<value_type, size_type>;
+
+    ion() = default;
+
+    ion(index_view_type idx)
+    :   node_index_{idx}
+    ,   iX_{idx.size(), std::numeric_limits<value_type>::quiet_NaN()}
+    ,   eX_{idx.size(), std::numeric_limits<value_type>::quiet_NaN()}
+    ,   Xi_{idx.size(), std::numeric_limits<value_type>::quiet_NaN()}
+    ,   Xo_{idx.size(), std::numeric_limits<value_type>::quiet_NaN()}
+    { }
+
+    std::size_t memory() const {
+        return 4u*size() * sizeof(value_type)
+               +  size() * sizeof(index_type)
+               +  sizeof(ion);
+    }
+
+    vector_view_type current() {
+        return iX_;
+    }
+    vector_view_type reversal_potential() {
+        return eX_;
+    }
+    vector_view_type internal_concentration() {
+        return Xi_;
+    }
+    vector_view_type external_concentration() {
+        return Xo_;
+    }
+
+    index_type node_index() {
+        return node_index_;
+    }
+
+    size_t size() const {
+        return node_index_.size();
+    }
+
+private :
+
+    index_type node_index_;
+    vector_type iX_;
+    vector_type eX_;
+    vector_type Xi_;
+    vector_type Xo_;
+};
+
+} // namespace mechanisms
+} // namespace mc
+} // namespace nest
+
diff --git a/src/matrix.hpp b/src/matrix.hpp
index 16936fa47c26d3c632d08d008e59c76c2e4b0db0..db9d2df7f56e8bfffb73051e996acb13d9fa0369 100644
--- a/src/matrix.hpp
+++ b/src/matrix.hpp
@@ -19,10 +19,10 @@ class matrix {
     using size_type  = I;
 
     // define storage types
-    using vector_type = memory::HostVector<value_type>;
-    using view_type   = typename vector_type::view_type;
-    using index_type  = memory::HostVector<size_type>;
-    using index_view  = typename index_type::view_type;
+    using vector_type      = memory::HostVector<value_type>;
+    using vector_view_type = typename vector_type::view_type;
+    using index_type       = memory::HostVector<size_type>;
+    using index_view_type  = typename index_type::view_type;
 
     matrix() = default;
 
@@ -67,7 +67,7 @@ class matrix {
     /// the total memory used to store the matrix
     std::size_t memory() const
     {
-        auto s = 5 * (sizeof(value_type) * size() + sizeof(vector_type));
+        auto s = 6 * (sizeof(value_type) * size() + sizeof(vector_type));
         s     += sizeof(size_type) * (parent_index_.size() + cell_index_.size())
                 + 2*sizeof(index_type);
         s     += sizeof(matrix);
@@ -80,32 +80,45 @@ class matrix {
         return cell_index_.size() - 1;
     }
 
+    /// FIXME : make modparser use the correct accessors (l,d,u,rhs) instead of these
+    vector_view_type vec_rhs() { return rhs(); }
+    vector_view_type vec_d()   { return d(); }
+    vector_view_type vec_v()   { return v(); }
+
     /// the vector holding the lower part of the matrix
-    view_type l()
+    vector_view_type l()
     {
         return l_;
     }
 
     /// the vector holding the diagonal of the matrix
-    view_type d()
+    vector_view_type d()
     {
         return d_;
     }
 
     /// the vector holding the upper part of the matrix
-    view_type u()
+    vector_view_type u()
     {
         return u_;
     }
 
     /// the vector holding the right hand side of the linear equation system
-    view_type rhs()
+    vector_view_type rhs()
     {
         return rhs_;
     }
 
+    /// the vector holding the solution (voltage)
+    vector_view_type v()
+    {
+        EXPECTS(has_voltage_);
+
+        return v_;
+    }
+
     /// the vector holding the parent index
-    index_view p()
+    index_view_type p()
     {
         return parent_index_;
     }
@@ -115,7 +128,16 @@ class matrix {
     /// and can be accessed via rhs()
     void solve()
     {
-        index_view const& p = parent_index_;
+        /*
+        std::cout << "solving matrix :\n";
+        std::cout << "  l   " << l_   << "\n";
+        std::cout << "  d   " << d_   << "\n";
+        std::cout << "  u   " << u_   << "\n";
+        std::cout << "  rhs " << rhs_ << "\n";
+        */
+
+        // FIXME make a const view
+        index_view_type const& p = parent_index_;
         auto const ncells = num_cells();
 
         // loop over submatrices
@@ -137,6 +159,17 @@ class matrix {
                 rhs_[i] /= d_[i];
             }
         }
+        //std::cout << "  v   " << rhs_ << "\n";
+    }
+
+    void add_voltage(vector_view_type v_ext)
+    {
+        EXPECTS(v_ext.size()==size());
+
+        std::cout << "============ adding voltage" << std::endl;
+
+        v_ = v_ext;
+        has_voltage_ = true;
     }
 
     private:
@@ -164,8 +197,12 @@ class matrix {
     vector_type l_;
     vector_type d_;
     vector_type u_;
+
     /// after calling solve, the solution is stored in rhs_
     vector_type rhs_;
+    vector_view_type v_;
+
+    bool has_voltage_=false;
 };
 
 } // namespace nest
diff --git a/src/mechanism.hpp b/src/mechanism.hpp
index 3432225885848ed06f1f97513772709025757a20..ea69c292e1f5c50efe0fd0e6e7dbf092c32b16fb 100644
--- a/src/mechanism.hpp
+++ b/src/mechanism.hpp
@@ -5,17 +5,22 @@
 #include <memory>
 #include <string>
 
-#include "matrix.hpp"
+#include "indexed_view.hpp"
 #include "parameter_list.hpp"
 #include "util.hpp"
+#include "ion.hpp"
 
 namespace nest {
 namespace mc {
+namespace mechanisms {
 
 enum class mechanismKind {point, density};
 
 template <typename T, typename I>
 class mechanism {
+
+public:
+
     using value_type  = T;
     using size_type   = I;
 
@@ -24,31 +29,50 @@ class mechanism {
     using view_type   = typename vector_type::view_type;
     using index_type  = memory::HostVector<size_type>;
     using index_view  = typename index_type::view_type;
-    //using indexed_view= IndexedView<value_type, size_type, target()>;
+    using indexed_view_type = indexed_view<value_type, size_type>;
 
-    using matrix_type = matrix<value_type, size_type>;
+    using ion_type    = ion<value_type, size_type>;
 
-    mechanism(matrix_type *matrix, index_view node_indices)
-    :   matrix_(matrix)
-    ,   node_indices_(node_indices)
+    mechanism(view_type vec_v, view_type vec_i, index_view node_index)
+    :   vec_v_(vec_v)
+    ,   vec_i_(vec_i)
+    ,   node_index_(node_index)
     {}
 
     std::size_t size() const
     {
-        return node_indices_.size();
+        return node_index_.size();
+    }
+
+    index_view node_index() const
+    {
+        return node_index_;
+    }
+
+    value_type voltage(size_type i) const
+    {
+        return vec_v_[node_index_[i]];
+    }
+
+    value_type current(size_type i) const
+    {
+        return vec_i_[node_index_[i]];
     }
 
     virtual void set_params(value_type t_, value_type dt_) = 0;
     virtual std::string name() const = 0;
     virtual std::size_t memory() const = 0;
-    virtual void init()     = 0;
-    virtual void state()    = 0;
-    virtual void current()  = 0;
+    virtual void nrn_init()     = 0;
+    virtual void nrn_state()    = 0;
+    virtual void nrn_current()  = 0;
+    virtual bool uses_ion(ionKind) const = 0;
+    virtual void set_ion(ionKind k, ion_type& i) = 0;
 
     virtual mechanismKind kind() const = 0;
 
-    matrix_type* matrix_;
-    index_type node_indices_;
+    view_type vec_v_;
+    view_type vec_i_;
+    index_type node_index_;
 };
 
 template <typename T, typename I>
@@ -57,11 +81,13 @@ using mechanism_ptr = std::unique_ptr<mechanism<T,I>>;
 template <typename M>
 mechanism_ptr<typename M::value_type, typename M::size_type>
 make_mechanism(
-    typename M::matrix_type* matrix,
-    typename M::index_view   node_indices
+    typename M::view_type  vec_v,
+    typename M::view_type  vec_i,
+    typename M::index_view node_indices
 ) {
-    return util::make_unique<M>(matrix, node_indices);
+    return util::make_unique<M>(vec_v, vec_i, node_indices);
 }
 
+} // namespace mechanisms
 } // namespace nest
 } // namespace mc
diff --git a/src/mechanism_interface.cpp b/src/mechanism_interface.cpp
index ba24eb06ce13c3452bb5db6d8847e3b1b1ce3081..10fad45fa0d9776c4f45302e6bf196f995765e99 100644
--- a/src/mechanism_interface.cpp
+++ b/src/mechanism_interface.cpp
@@ -1,21 +1,41 @@
 #include "mechanism_interface.hpp"
 
-/*  include the mechanisms
+//
+//  include the mechanisms
+//
+
 #include <mechanisms/hh.hpp>
 #include <mechanisms/pas.hpp>
-*/
 
 namespace nest {
 namespace mc {
 namespace mechanisms {
 
-std::map<std::string, mechanism_helper<double, int>> mechanism_map;
+std::map<std::string, mechanism_helper_ptr<value_type, index_type>> mechanism_helpers;
 
 void setup_mechanism_helpers() {
-    /*  manually insert
-    mechanism_map["hh"]  = mechanisms::hh;
-    mechanism_map["pas"] = mechanisms::pas;
-    */
+    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_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
diff --git a/src/mechanism_interface.hpp b/src/mechanism_interface.hpp
index 389ef05997b90c96782dff8593aa5817ca4b2fde..c59caaad6c655d6a69c5206b956b1cb42959cfdf 100644
--- a/src/mechanism_interface.hpp
+++ b/src/mechanism_interface.hpp
@@ -3,30 +3,56 @@
 #include <map>
 #include <string>
 
-#include "matrix.hpp"
 #include "mechanism.hpp"
 #include "parameter_list.hpp"
 
 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 index_type = memory::HostView<I>;
+    using value_type = T;
+    using size_type = I;
+    using index_type = memory::HostVector<I>;
     using index_view = typename index_type::view_type;
-    using mechanism_type = mechanism<T, I>;
-    using matrix_type = typename mechanism_type::matrix_type;
+    using mechanism_ptr_type = mechanism_ptr<T, I>;
+    using view_type = typename mechanism<T,I>::view_type;
 
     virtual std::string name() const = 0;
 
-    virtual mechanism_type new_mechanism(matrix_type*, index_view) const = 0;
+    virtual mechanism_ptr<T,I> new_mechanism(view_type, view_type, index_view) const = 0;
 
-    virtual void set_parameters(mechanism_type&, parameter_list const&) const = 0;
+    virtual void set_parameters(mechanism_ptr_type&, parameter_list const&) const = 0;
 };
 
-extern std::map<std::string, mechanism_helper<double, int>> mechanism_helpers;
+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();
 
-} // namespace nest
+mechanism_helper_ptr<value_type, index_type>&
+get_mechanism_helper(const std::string& name);
+
+} // namespace mechanisms
 } // namespace mc
+} // namespace nest
diff --git a/src/parameter_list.hpp b/src/parameter_list.hpp
index 56848466f58f36171e44b2d6da2c371a4edd84a9..0c06988756a077fdbb860fc8f42043b178b1b7e7 100644
--- a/src/parameter_list.hpp
+++ b/src/parameter_list.hpp
@@ -146,8 +146,15 @@ namespace mc {
         membrane_parameters()
         : base("membrane")
         {
-            base::add_parameter({"r_L",   0.01, {0., 1e9}}); // typically 10 nF/mm^2 == 0.01 F/m2
-            base::add_parameter({"c_m", 180.00, {0., 1e9}}); // Ohm.cm
+            // from nrn/src/nrnoc/membdef.h
+            //#define DEF_cm       1.     // uF/cm^2
+            //#define DEF_Ra      35.4    // ohm-cm
+            //#define DEF_celsius  6.3    // deg-C
+            //#define DEF_vrest  -65.     // mV
+            // r_L is called Ra in Neuron
+            //base::add_parameter({"c_m",  10e-6, {0., 1e9}}); // typically 10 nF/mm^2 == 0.01 F/m^2 == 10^-6 F/cm^2
+            base::add_parameter({"c_m",   0.01, {0., 1e9}}); // typically 10 nF/mm^2 == 0.01 F/m^2 == 10^-6 F/cm^2
+            base::add_parameter({"r_L", 180.00, {0., 1e9}}); // equivalent to Ra in Neuron : Ohm.cm
         }
     };
 
@@ -169,10 +176,33 @@ namespace mc {
         hh_parameters()
         : base("hh")
         {
-            base::add_parameter({"gnabar", 0.12,  {0., 1e9}});
-            base::add_parameter({"gkbar",  0.036, {0., 1e9}});
-            base::add_parameter({"gl",     0.0003,{0., 1e9}});
-            base::add_parameter({"el",     -54.3});
+            base::add_parameter({"gnabar",  0.12,  {0, 1e9}});
+            base::add_parameter({"gkbar",   0.036, {0, 1e9}});
+            base::add_parameter({"gl",      0.0003,{0, 1e9}});
+            base::add_parameter({"el",    -54.3});
+        }
+    };
+
+    /// parameters for passive channel
+    class pas_parameters
+    : public parameter_list
+    {
+        public:
+
+        using base = parameter_list;
+
+        using base::value_type;
+
+        using base::set;
+        using base::get;
+        using base::parameters;
+        using base::has_parameter;
+
+        pas_parameters()
+        : base("pas")
+        {
+            base::add_parameter({"g", 0.001, {0, 1e9}});
+            base::add_parameter({"e", -70});
         }
     };
 
diff --git a/src/segment.hpp b/src/segment.hpp
index e339e3ac4bc974477a624fe698d11bf8eb521f26..fd1d8ded6101cc8e9834b9062df3da97de5cbc8c 100644
--- a/src/segment.hpp
+++ b/src/segment.hpp
@@ -113,6 +113,31 @@ class segment {
         return *it;
     }
 
+    const parameter_list& mechanism(std::string const& n) const
+    {
+        auto it = std::find_if(
+            mechanisms_.begin(), mechanisms_.end(),
+            [&n](parameter_list const& l){return l.name() == n;}
+        );
+        if(it==mechanisms_.end()) {
+            throw std::out_of_range(
+                "attempt to access a parameter that is not defined in a segment"
+            );
+        }
+
+        return *it;
+    }
+
+    std::vector<parameter_list>& mechanisms()
+    {
+        return mechanisms_;
+    }
+
+    const std::vector<parameter_list>& mechanisms() const
+    {
+        return mechanisms_;
+    }
+
     protected:
 
     segmentKind kind_;
diff --git a/src/stimulus.hpp b/src/stimulus.hpp
index 94a4dddfddf1cab80272de74bd3a94f9130989fd..f7c7538cf398a7154588126620d97b7c054953a4 100644
--- a/src/stimulus.hpp
+++ b/src/stimulus.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
-namespace nestmc {
+namespace nest {
+namespace mc {
 
 class i_clamp {
     public:
@@ -49,4 +50,5 @@ class i_clamp {
     value_type amplitude_ = 0;
 };
 
-} // namespace nestmc
+} // namespace mc
+} // namespace nest
diff --git a/src/util.hpp b/src/util.hpp
index d92392799cf98da27ee1ca62788686a308c02c1d..706ab7da5a17d493823b8c74d1f18fa6b4a9efaf 100644
--- a/src/util.hpp
+++ b/src/util.hpp
@@ -2,6 +2,11 @@
 
 #include "vector/include/Vector.hpp"
 
+#ifdef DEBUG
+#define EXPECTS(expression) assert(expression)
+#else
+#define EXPECTS(expression)
+#endif
 
 /*
 using memory::util::red;
@@ -23,7 +28,7 @@ operator << (std::ostream &o, std::vector<T>const& v)
 {
     o << "[";
     for(auto const& i: v) {
-        o << i << ", ";
+        o << i << ",";
     }
     o << "]";
     return o;
@@ -34,7 +39,7 @@ std::ostream& print(std::ostream &o, std::vector<T>const& v)
 {
     o << "[";
     for(auto const& i: v) {
-        o << i << ", ";
+        o << i << ",";
     }
     o << "]";
     return o;
@@ -80,6 +85,45 @@ namespace util {
              std::false_type
         >::type
     {};
+
+    // printf with variadic templates for simplified string creation
+    // mostly used to simplify error string creation
+    [[gnu::unused]] static
+    std::string pprintf(const char *s) {
+        std::string errstring;
+        while(*s) {
+            if(*s == '%' && s[1]!='%') {
+                // instead of throwing an exception, replace with ??
+                //throw std::runtime_error("pprintf: the number of arguments did not match the format ");
+                errstring += "<?>";
+            }
+            else {
+                errstring += *s;
+            }
+            ++s;
+        }
+        return errstring;
+    }
+
+    template <typename T, typename ... Args>
+    std::string pprintf(const char *s, T value, Args... args) {
+        std::string errstring;
+        while(*s) {
+            if(*s == '%' && s[1]!='%') {
+                std::stringstream str;
+                str << value;
+                errstring += str.str();
+                errstring += pprintf(++s, args...);
+                return errstring;
+            }
+            else {
+                errstring += *s;
+                ++s;
+            }
+        }
+        return errstring;
+    }
+
 } // namespace util
 } // namespace mc
 } // namespace nest
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index b555fc465f3e3292ae2fa48f11dfad17bbe0e6e5..cb33af4d972dd398952b1879cca764c502d8f42c 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -8,31 +8,49 @@ set(HEADERS
     ../src/tree.hpp
 )
 
-set(TEST_SOURCES
-    # google test framework
-    gtest-all.cpp
+# google test framework
+add_library(gtest gtest-all.cpp gtest.h)
 
+set(TEST_SOURCES
     # unit tests
     test_algorithms.cpp
     test_cell.cpp
     test_compartments.cpp
+    test_fvm.cpp
     test_matrix.cpp
+    test_mechanisms.cpp
+    test_parameters.cpp
     test_point.cpp
-    test_run.cpp
     test_segment.cpp
     test_stimulus.cpp
     test_swcio.cpp
     test_tree.cpp
 
     # unit test driver
-    main.cpp
+    test.cpp
+)
+
+set(VALIDATION_SOURCES
+    # unit tests
+    validate_soma.cpp
+    validate_ball_and_stick.cpp
+
+    # unit test driver
+    validate.cpp
 )
 
 add_executable(test.exe ${TEST_SOURCES} ${HEADERS})
+add_executable(validate.exe ${VALIDATION_SOURCES} ${HEADERS})
 
-target_link_libraries(test.exe LINK_PUBLIC cellalgo)
+target_link_libraries(test.exe LINK_PUBLIC cellalgo gtest)
+target_link_libraries(validate.exe LINK_PUBLIC cellalgo gtest)
 
 set_target_properties(test.exe
    PROPERTIES
    RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests"
 )
+
+set_target_properties(validate.exe
+   PROPERTIES
+   RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests"
+)
diff --git a/tests/main.cpp b/tests/test.cpp
similarity index 100%
rename from tests/main.cpp
rename to tests/test.cpp
diff --git a/tests/test_algorithms.cpp b/tests/test_algorithms.cpp
index 6503c07d9a413bf2e90f8cbf151b0df1e3e5949b..f69569c9611068250cc3265524cfe64e1e10bd55 100644
--- a/tests/test_algorithms.cpp
+++ b/tests/test_algorithms.cpp
@@ -255,6 +255,84 @@ TEST(algorithms, has_contiguous_segments)
     );
 }
 
+TEST(algorithms, is_unique)
+{
+    EXPECT_TRUE(
+        nest::mc::algorithms::is_unique(
+            std::vector<int>{}
+        )
+    );
+    EXPECT_TRUE(
+        nest::mc::algorithms::is_unique(
+            std::vector<int>{0}
+        )
+    );
+    EXPECT_TRUE(
+        nest::mc::algorithms::is_unique(
+            std::vector<int>{0,1,100}
+        )
+    );
+    EXPECT_FALSE(
+        nest::mc::algorithms::is_unique(
+            std::vector<int>{0,0}
+        )
+    );
+    EXPECT_FALSE(
+        nest::mc::algorithms::is_unique(
+            std::vector<int>{0,1,2,2,3,4}
+        )
+    );
+    EXPECT_FALSE(
+        nest::mc::algorithms::is_unique(
+            std::vector<int>{0,1,2,3,4,4}
+        )
+    );
+}
+
+TEST(algorithms, is_sorted)
+{
+    EXPECT_TRUE(
+        nest::mc::algorithms::is_sorted(
+            std::vector<int>{}
+        )
+    );
+    EXPECT_TRUE(
+        nest::mc::algorithms::is_sorted(
+            std::vector<int>{100}
+        )
+    );
+    EXPECT_TRUE(
+        nest::mc::algorithms::is_sorted(
+            std::vector<int>{0,1,2}
+        )
+    );
+    EXPECT_TRUE(
+        nest::mc::algorithms::is_sorted(
+            std::vector<int>{0,2,100}
+        )
+    );
+    EXPECT_TRUE(
+        nest::mc::algorithms::is_sorted(
+            std::vector<int>{0,0}
+        )
+    );
+    EXPECT_TRUE(
+        nest::mc::algorithms::is_sorted(
+            std::vector<int>{0,1,2,2,2,2,3,4,5,5,5}
+        )
+    );
+    EXPECT_FALSE(
+        nest::mc::algorithms::is_sorted(
+            std::vector<int>{0,1,2,1}
+        )
+    );
+    EXPECT_FALSE(
+        nest::mc::algorithms::is_sorted(
+            std::vector<int>{1,0}
+        )
+    );
+}
+
 TEST(algorithms, child_count)
 {
     {
@@ -310,7 +388,7 @@ TEST(algorithms, branches)
         std::vector<int> expected_branches =
             { 0, 1, 1, 1, 2, 2, 3, 3, 3, 4, 4, 5, 5, 5 };
 
-        auto actual_branches = algorithms::branches_fast(parent_index);
+        auto actual_branches = algorithms::branches(parent_index);
         EXPECT_EQ(expected_branches, actual_branches);
     }
 
@@ -329,7 +407,7 @@ TEST(algorithms, branches)
         std::vector<int> expected_branches =
             { 0, 1, 1, 1 };
 
-        auto actual_branches = algorithms::branches_fast(parent_index);
+        auto actual_branches = algorithms::branches(parent_index);
         EXPECT_EQ(expected_branches, actual_branches);
     }
 
@@ -350,7 +428,7 @@ TEST(algorithms, branches)
         std::vector<int> expected_branches =
             { 0, 1, 1, 2, 3, 3 };
 
-        auto actual_branches = algorithms::branches_fast(parent_index);
+        auto actual_branches = algorithms::branches(parent_index);
         EXPECT_EQ(expected_branches, actual_branches);
     }
 
@@ -358,7 +436,47 @@ TEST(algorithms, branches)
         std::vector<int> parent_index = { 0 };
         std::vector<int> expected_branches = { 0 };
 
-        auto actual_branches = algorithms::branches_fast(parent_index);
+        auto actual_branches = algorithms::branches(parent_index);
         EXPECT_EQ(expected_branches, actual_branches);
     }
 }
+
+TEST(algorithms, index_into)
+{
+    using C = std::vector<int>;
+
+    // by default index_into assumes that the inputs satisfy
+    // quite a strong set of prerequisites
+    //
+    // TODO: test that the EXPECTS() catch bad inputs when DEBUG mode is enabled
+    //       put this in a seperate unit test
+    auto tests = {
+        std::make_pair(C{}, C{}),
+        std::make_pair(C{100}, C{}),
+        std::make_pair(C{0,1,3,4,6,7,10,11}, C{0,4,6,7,11}),
+        std::make_pair(C{0,1,3,4,6,7,10,11}, C{0}),
+        std::make_pair(C{0,1,3,4,6,7,10,11}, C{11}),
+        std::make_pair(C{0,1,3,4,6,7,10,11}, C{4}),
+        std::make_pair(C{0,1,3,4,6,7,10,11}, C{0,11}),
+        std::make_pair(C{0,1,3,4,6,7,10,11}, C{4,11}),
+        std::make_pair(C{0,1,3,4,6,7,10,11}, C{0,1,3,4,6,7,10,11})
+    };
+
+    auto test_result = [] (const C& super, const C& sub, const C& index) {
+        if(sub.size()!=index.size()) return false;
+        for(auto i=0u; i<sub.size(); ++i) {
+            if(index[i]>=C::value_type(super.size())) return false;
+            if(super[index[i]]!=sub[i]) return false;
+        }
+        return true;
+    };
+
+    for(auto& t : tests) {
+        EXPECT_TRUE(
+            test_result(
+                t.first, t.second,
+                nest::mc::algorithms::index_into(t.first, t.second)
+            )
+        );
+    }
+}
diff --git a/tests/test_fvm.cpp b/tests/test_fvm.cpp
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..85a25f3579aa25e2e84860b3144294269ff0895a 100644
--- a/tests/test_fvm.cpp
+++ b/tests/test_fvm.cpp
@@ -0,0 +1,105 @@
+#include <fstream>
+
+#include "gtest.h"
+#include "util.hpp"
+
+#include "../src/cell.hpp"
+#include "../src/fvm.hpp"
+
+TEST(fvm, cable)
+{
+    using namespace nest::mc;
+
+    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
+    cell.add_cable(0, segmentKind::dendrite, 1e-4, 1e-4, 4e-1);
+    cell.add_cable(0, segmentKind::dendrite, 1e-4, 1e-4, 4e-1);
+
+    //std::cout << cell.segment(1)->area() << " is the area\n";
+    EXPECT_EQ(cell.model().tree.num_segments(), 3u);
+
+    // add passive to all 3 segments in the cell
+    for(auto& seg :cell.segments()) {
+        seg->add_mechanism(pas_parameters());
+    }
+
+    cell.soma()->add_mechanism(hh_parameters());
+    cell.segment(2)->add_mechanism(hh_parameters());
+
+    auto& soma_hh = cell.soma()->mechanism("hh");
+
+    soma_hh.set("gnabar", 0.12);
+    soma_hh.set("gkbar", 0.036);
+    soma_hh.set("gl", 0.0003);
+    soma_hh.set("el", -54.387);
+
+    cell.segment(1)->set_compartments(4);
+    cell.segment(2)->set_compartments(4);
+
+    using fvm_cell = fvm::fvm_cell<double, int>;
+    fvm_cell fvcell(cell);
+    auto& J = fvcell.jacobian();
+
+    EXPECT_EQ(cell.num_compartments(), 9u);
+
+    // assert that the matrix has one row for each compartment
+    EXPECT_EQ(J.size(), cell.num_compartments());
+
+    fvcell.setup_matrix(0.02);
+
+    // assert that the number of cv areas is the same as the matrix size
+    // i.e. both should equal the number of compartments
+    EXPECT_EQ(fvcell.cv_areas().size(), J.size());
+}
+
+TEST(fvm, init)
+{
+    using namespace nest::mc;
+
+    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;
+
+    cell.add_cable(0, segmentKind::dendrite, 0.5, 0.5, 200);
+
+    const auto m = cell.model();
+    EXPECT_EQ(m.tree.num_segments(), 2u);
+
+    // in this context (i.e. attached to a segment on a high-level cell)
+    // a mechanism is essentially a set of parameters
+    // - the only "state" is that used to define parameters
+    cell.soma()->add_mechanism(hh_parameters());
+
+    auto& soma_hh = cell.soma()->mechanism("hh");
+
+    soma_hh.set("gnabar", 0.12);
+    soma_hh.set("gkbar", 0.036);
+    soma_hh.set("gl", 0.0003);
+    soma_hh.set("el", -54.3);
+
+    // check that parameter values were set correctly
+    EXPECT_EQ(cell.soma()->mechanism("hh").get("gnabar").value, 0.12);
+    EXPECT_EQ(cell.soma()->mechanism("hh").get("gkbar").value, 0.036);
+    EXPECT_EQ(cell.soma()->mechanism("hh").get("gl").value, 0.0003);
+    EXPECT_EQ(cell.soma()->mechanism("hh").get("el").value, -54.3);
+
+    cell.segment(1)->set_compartments(10);
+
+    using fvm_cell = fvm::fvm_cell<double, int>;
+    fvm_cell fvcell(cell);
+    auto& J = fvcell.jacobian();
+    EXPECT_EQ(J.size(), 11u);
+
+    fvcell.setup_matrix(0.01);
+}
+
diff --git a/tests/test_matrix.cpp b/tests/test_matrix.cpp
index f0271bd40b6ee109489dfd1f1edb781dca49cfa3..73663d6815d876e1740da4cee084c46255a74e3f 100644
--- a/tests/test_matrix.cpp
+++ b/tests/test_matrix.cpp
@@ -116,7 +116,6 @@ TEST(matrix, solve)
 
             EXPECT_NEAR(0., std::sqrt(err), 1e-8);
         }
-
     }
 }
 
diff --git a/tests/test_mechanisms.cpp b/tests/test_mechanisms.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d61c89d20b40136044dfd7556157b68947d8e56f
--- /dev/null
+++ b/tests/test_mechanisms.cpp
@@ -0,0 +1,37 @@
+#include "gtest.h"
+
+#include <mechanism_interface.hpp>
+#include <matrix.hpp>
+
+TEST(mechanisms, helpers) {
+    nest::mc::mechanisms::setup_mechanism_helpers();
+
+    EXPECT_EQ(nest::mc::mechanisms::mechanism_helpers.size(), 2u);
+
+    // 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
+    );
+
+                                   //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();
+
+    //nest::mc::matrix<double, int> matrix(parent_index);
+    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);
+
+    EXPECT_EQ(mech->name(), "hh");
+    EXPECT_EQ(mech->size(), 5u);
+    //EXPECT_EQ(mech->matrix_, &matrix);
+}
diff --git a/tests/test_parameters.cpp b/tests/test_parameters.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0dce58b0c5472de6db9d721ad78dece7a9b3de94
--- /dev/null
+++ b/tests/test_parameters.cpp
@@ -0,0 +1,42 @@
+
+#include <fstream>
+
+#include "gtest.h"
+#include "util.hpp"
+
+#include <parameter_list.hpp>
+
+// test out the parameter infrastructure
+TEST(parameters, setting)
+{
+    nest::mc::parameter_list list("test");
+    EXPECT_EQ(list.name(), "test");
+    EXPECT_EQ(list.num_parameters(), 0);
+
+    nest::mc::parameter p("a", 0.12, {0, 10});
+
+    // add_parameter() returns a bool that indicates whether
+    // it was able to successfull add the parameter
+    EXPECT_TRUE(list.add_parameter(std::move(p)));
+    EXPECT_EQ(list.num_parameters(), 1);
+
+    // test in place construction of a parameter
+    EXPECT_TRUE(list.add_parameter({"b", -3.0}));
+    EXPECT_EQ(list.num_parameters(), 2);
+
+    // check that adding a parameter that already exists returns false
+    // and does not increase the number of parameters
+    EXPECT_FALSE(list.add_parameter({"b", -3.0}));
+    EXPECT_EQ(list.num_parameters(), 2);
+
+    auto &parms = list.parameters();
+    EXPECT_EQ(parms[0].name, "a");
+    EXPECT_EQ(parms[0].value, 0.12);
+    EXPECT_EQ(parms[0].range.min, 0);
+    EXPECT_EQ(parms[0].range.max, 10);
+
+    EXPECT_EQ(parms[1].name, "b");
+    EXPECT_EQ(parms[1].value, -3);
+    EXPECT_FALSE(parms[1].range.has_lower_bound());
+    EXPECT_FALSE(parms[1].range.has_upper_bound());
+}
diff --git a/tests/test_run.cpp b/tests/test_run.cpp
deleted file mode 100644
index c4e3b1025e82ca257295fdcf8980fd82240d1956..0000000000000000000000000000000000000000
--- a/tests/test_run.cpp
+++ /dev/null
@@ -1,142 +0,0 @@
-#include "gtest.h"
-
-#include "../src/cell.hpp"
-#include "../src/fvm.hpp"
-
-TEST(run, cable)
-{
-    using namespace nest::mc;
-
-    nest::mc::cell cell;
-
-    cell.add_soma(6e-4); // 6um in cm
-
-    // 1um radius and 4mm long, all in cm
-    cell.add_cable(0, segmentKind::dendrite, 1e-4, 1e-4, 4e-1);
-
-    std::cout << cell.segment(1)->area() << " is the area\n";
-    EXPECT_EQ(cell.model().tree.num_segments(), 2u);
-
-    cell.soma()->add_mechanism(hh_parameters());
-
-    auto& soma_hh = cell.soma()->mechanism("hh");
-
-    soma_hh.set("gnabar", 0.12);
-    soma_hh.set("gkbar", 0.036);
-    soma_hh.set("gl", 0.0003);
-    soma_hh.set("el", -54.387);
-
-    cell.segment(1)->set_compartments(4);
-
-    using fvm_cell = fvm::fvm_cell<double, int>;
-    fvm_cell fvcell(cell);
-    auto& J = fvcell.jacobian();
-    EXPECT_EQ(J.size(), 5u);
-
-    fvcell.setup_matrix(0.02);
-    EXPECT_EQ(fvcell.cv_areas().size(), J.size());
-
-    auto& cable_parms = cell.segment(1)->mechanism("membrane");
-    std::cout << soma_hh << std::endl;
-    std::cout << cable_parms << std::endl;
-
-    std::cout << "l " << J.l() << "\n";
-    std::cout << "d " << J.d() << "\n";
-    std::cout << "u " << J.u() << "\n";
-    std::cout << "p " << J.p() << "\n";
-
-    J.rhs()(memory::all) = 1.;
-    J.rhs()[0] = 10.;
-
-    J.solve();
-}
-
-TEST(run, init)
-{
-    using namespace nest::mc;
-
-    nest::mc::cell cell;
-
-    cell.add_soma(12.6157/2.0);
-    //auto& props = cell.soma()->properties;
-
-    cell.add_cable(0, segmentKind::dendrite, 0.5, 0.5, 200);
-
-    const auto m = cell.model();
-    EXPECT_EQ(m.tree.num_segments(), 2u);
-
-    // in this context (i.e. attached to a segment on a high-level cell)
-    // a mechanism is essentially a set of parameters
-    // - the only "state" is that used to define parameters
-    cell.soma()->add_mechanism(hh_parameters());
-
-    auto& soma_hh = cell.soma()->mechanism("hh");
-
-    soma_hh.set("gnabar", 0.12);
-    soma_hh.set("gkbar", 0.036);
-    soma_hh.set("gl", 0.0003);
-    soma_hh.set("el", -54.3);
-
-    // check that parameter values were set correctly
-    EXPECT_EQ(cell.soma()->mechanism("hh").get("gnabar").value, 0.12);
-    EXPECT_EQ(cell.soma()->mechanism("hh").get("gkbar").value, 0.036);
-    EXPECT_EQ(cell.soma()->mechanism("hh").get("gl").value, 0.0003);
-    EXPECT_EQ(cell.soma()->mechanism("hh").get("el").value, -54.3);
-
-    cell.segment(1)->set_compartments(10);
-
-    using fvm_cell = fvm::fvm_cell<double, int>;
-    fvm_cell fvcell(cell);
-    auto& J = fvcell.jacobian();
-    EXPECT_EQ(J.size(), 11u);
-
-    fvcell.setup_matrix(0.01);
-    std::cout << "areas " << fvcell.cv_areas() << "\n";
-
-    //std::cout << "l" << J.l() << "\n";
-    //std::cout << "d" << J.d() << "\n";
-    //std::cout << "u" << J.u() << "\n";
-
-    J.rhs()(memory::all) = 1.;
-    J.rhs()[0] = 10.;
-
-    J.solve();
-
-    //std::cout << "x" << J.rhs() << "\n";
-}
-
-// test out the parameter infrastructure
-TEST(run, parameters)
-{
-    nest::mc::parameter_list list("test");
-    EXPECT_EQ(list.name(), "test");
-    EXPECT_EQ(list.num_parameters(), 0);
-
-    nest::mc::parameter p("a", 0.12, {0, 10});
-
-    // add_parameter() returns a bool that indicates whether
-    // it was able to successfull add the parameter
-    EXPECT_EQ(list.add_parameter(std::move(p)), true);
-    EXPECT_EQ(list.num_parameters(), 1);
-
-    // test in place construction of a parameter
-    EXPECT_EQ(list.add_parameter({"b", -3.0}), true);
-    EXPECT_EQ(list.num_parameters(), 2);
-
-    // check that adding a parameter that already exists returns false
-    // and does not increase the number of parameters
-    EXPECT_EQ(list.add_parameter({"b", -3.0}), false);
-    EXPECT_EQ(list.num_parameters(), 2);
-
-    auto &parms = list.parameters();
-    EXPECT_EQ(parms[0].name, "a");
-    EXPECT_EQ(parms[0].value, 0.12);
-    EXPECT_EQ(parms[0].range.min, 0);
-    EXPECT_EQ(parms[0].range.max, 10);
-
-    EXPECT_EQ(parms[1].name, "b");
-    EXPECT_EQ(parms[1].value, -3);
-    EXPECT_EQ(parms[1].range.has_lower_bound(), false);
-    EXPECT_EQ(parms[1].range.has_upper_bound(), false);
-}
-
diff --git a/tests/test_stimulus.cpp b/tests/test_stimulus.cpp
index 43be56f158e07e409c94b21cca7c293026b8d830..8fd9307b61a780ad93f430338f7d76c941e20bc0 100644
--- a/tests/test_stimulus.cpp
+++ b/tests/test_stimulus.cpp
@@ -4,7 +4,7 @@
 
 TEST(stimulus, i_clamp)
 {
-    using namespace nestmc;
+    using namespace nest::mc;
 
     // stimulus with delay 2, duration 0.5, amplitude 6.0
     i_clamp stim(2.0, 0.5, 6.0);
diff --git a/tests/test_swcio.cpp b/tests/test_swcio.cpp
index 17a836c2065b0a56954d26fa3e1297cbfc16de45..679260d656c9c7964f0eea599951c371886123f4 100644
--- a/tests/test_swcio.cpp
+++ b/tests/test_swcio.cpp
@@ -137,7 +137,7 @@ TEST(swc_parser, invalid_input)
     {
         // Non-contiguous numbering in branches is considered invalid
         //        1
-        //       / \
+        //       / \.
         //      2   3
         //     /
         //    4
diff --git a/tests/test_tree.cpp b/tests/test_tree.cpp
index a9b923170e14a42edab6a1deb070cb380e8d2378..a468d80ca8d4a61804d4fd0e242cad466bff8f8c 100644
--- a/tests/test_tree.cpp
+++ b/tests/test_tree.cpp
@@ -34,9 +34,9 @@ TEST(cell_tree, from_parent_index) {
     {
         //
         //        0               0
-        //       / \             / \
+        //       / \             / \.
         //      1   4      =>   1   2
-        //     /     \
+        //     /     \.
         //    2       5
         //   /
         //  3
@@ -54,11 +54,11 @@ TEST(cell_tree, from_parent_index) {
     {
         //
         //        0               0
-        //       /|\             /|\
+        //       /|\             /|\.
         //      1 4 6      =>   1 2 3
-        //     /  |  \
+        //     /  |  \.
         //    2   5   7
-        //   /         \
+        //   /         \.
         //  3           8
         //
         std::vector<int> parent_index =
@@ -82,17 +82,17 @@ TEST(cell_tree, from_parent_index) {
     {
         //
         //        0               0
-        //       /|\             /|\
+        //       /|\             /|\.
         //      1 4 6      =>   1 2 3
-        //     /  |  \             / \
+        //     /  |  \             / \.
         //    2   5   7           4   5
-        //   /         \
+        //   /         \.
         //  3           8
-        //             / \
+        //             / \.
         //            9   11
-        //           /     \
+        //           /     \.
         //          10     12
-        //                   \
+        //                   \.
         //                   13
         //
         std::vector<int> parent_index =
@@ -278,7 +278,7 @@ TEST(cell_tree, balance) {
         EXPECT_EQ(t.parent(5), 2);
         EXPECT_EQ(t.parent(6), 5);
 
-        t.to_graphviz("cell.dot");
+        //t.to_graphviz("cell.dot");
     }
 }
 
@@ -292,8 +292,7 @@ TEST(cell_tree, json_load)
     for(auto c : range(0,cell_data.size())) {
         std::vector<int> parent_index = cell_data[c]["parent_index"];
         cell_tree tree(parent_index);
-        //tree.to_graphviz("cell_" + std::to_string(c) + ".dot");
-        tree.to_graphviz("cell" + std::to_string(c) + ".dot");
+        //tree.to_graphviz("cell" + std::to_string(c) + ".dot");
     }
 }
 
diff --git a/tests/util.hpp b/tests/util.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7a0027448e46c860471aeed68ebd8ed8e411dfe0
--- /dev/null
+++ b/tests/util.hpp
@@ -0,0 +1,153 @@
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+#include <iomanip>
+
+#include <cmath>
+
+#include <util.hpp>
+#include <json/src/json.hpp>
+
+// helpful code for running tests
+// a bit messy: refactor when it gets heavier and obvious patterns emerge...
+
+namespace testing{
+
+[[gnu::unused]] static
+void write_vis_file(const std::string& fname, std::vector<std::vector<double>> values)
+{
+    auto m = values.size();
+    if(!m) return;
+
+    std::ofstream fid(fname);
+    if(!fid.is_open()) return;
+
+    auto n = values[0].size();
+    for(const auto& v : values) {
+        if(n!=v.size()) {
+            std::cerr << "all output arrays must have the same length\n";
+            return;
+        }
+    }
+
+    for(auto i=0u; i<n; ++i) {
+        for(auto j=0u; j<m; ++j) {
+            fid << " " << values[j][i];
+        }
+        fid << "\n";
+    }
+}
+
+[[gnu::unused]] static
+nlohmann::json
+load_spike_data(const std::string& input_name)
+{
+    nlohmann::json cell_data;
+    auto fid = std::ifstream(input_name);
+    if(!fid.is_open()) {
+        std::cerr << "error : unable to open file " << input_name
+                  << " : run the validation generation script first\n";
+        return {};
+    }
+
+    try {
+        fid >> cell_data;
+    }
+    catch (...) {
+        std::cerr << "error : incorrectly formatted json file " << input_name << "\n";
+        return {};
+    }
+    return cell_data;
+}
+
+template <typename T>
+std::vector<T> find_spikes(std::vector<T> const& v, T threshold, T dt)
+{
+    if(v.size()<2) {
+        return {};
+    }
+
+    std::vector<T> times;
+    for(auto i=1u; i<v.size(); ++i) {
+        if(v[i]>=threshold && v[i-1]<threshold) {
+            auto pos = (threshold-v[i-1]) / (v[i]-v[i-1]);
+            times.push_back((i-1+pos)*dt);
+        }
+    }
+
+    return times;
+}
+
+struct spike_comparison {
+    double min = std::numeric_limits<double>::quiet_NaN();
+    double max = std::numeric_limits<double>::quiet_NaN();
+    double mean = std::numeric_limits<double>::quiet_NaN();
+    double rms = std::numeric_limits<double>::quiet_NaN();
+    std::vector<double> diff;
+
+    // check whether initialized (i.e. has valid results)
+    bool is_valid() const {
+        return min == min;
+    }
+
+    // return maximum relative error
+    double max_relative_error() const {
+        if(!is_valid()) {
+            return std::numeric_limits<double>::quiet_NaN();
+        }
+
+        return *std::max_element(diff.begin(), diff.end());
+    }
+};
+
+[[gnu::unused]] static
+std::ostream&
+operator<< (std::ostream& o, spike_comparison const& spikes)
+{
+    // use snprintf because C++ is just awful for formatting output
+    char buffer[512];
+    snprintf(
+        buffer, sizeof(buffer),
+        "min,max = %10.8f,%10.8f | mean,rms = %10.8f,%10.8f | max_rel = %10.8f",
+        spikes.min, spikes.max, spikes.mean, spikes.rms,
+        spikes.max_relative_error()*100
+    );
+    return o << buffer;
+}
+
+template <typename T>
+spike_comparison compare_spikes(
+    std::vector<T> const& spikes,
+    std::vector<T> const& baseline)
+{
+    spike_comparison c;
+
+    // return default initialized (all NaN) if number of spikes differs
+    if(spikes.size() != baseline.size()) {
+        return c;
+    }
+
+    c.min  = std::numeric_limits<double>::max();
+    c.max  = 0.;
+    c.mean = 0.;
+    c.rms  = 0.;
+
+    auto n = spikes.size();
+    for(auto i=0u; i<n; ++i) {
+        auto error = std::fabs(spikes[i] - baseline[i]);
+        c.min = std::min(c.min, error);
+        c.max = std::max(c.max, error);
+        c.mean += error;
+        c.rms += error*error;
+        // relative difference
+        c.diff.push_back(error/baseline[i]);
+    }
+
+    c.mean /= n;
+    c.rms = std::sqrt(c.rms/n);
+
+    return c;
+}
+
+} // namespace testing
diff --git a/tests/validate.cpp b/tests/validate.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d814fca1512dd79b22763a4f9a769f2984ca5269
--- /dev/null
+++ b/tests/validate.cpp
@@ -0,0 +1,7 @@
+#include "gtest.h"
+
+int main(int argc, char **argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
+
diff --git a/tests/validate_ball_and_stick.cpp b/tests/validate_ball_and_stick.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..eac29e2e84f20653acdb7555714f9ed6c6e3e555
--- /dev/null
+++ b/tests/validate_ball_and_stick.cpp
@@ -0,0 +1,162 @@
+#include <fstream>
+
+#include "gtest.h"
+#include "util.hpp"
+
+#include <cell.hpp>
+#include <fvm.hpp>
+
+#include <json/src/json.hpp>
+
+// compares results with those generated by nrn/soma.py
+TEST(ball_and_stick, neuron_baseline)
+{
+    using namespace nest::mc;
+    using namespace nlohmann;
+
+    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());
+
+    // add dendrite of length 200 um and diameter 1 um with passive channel
+    auto dendrite = cell.add_cable(0, segmentKind::dendrite, 0.5, 0.5, 200);
+    dendrite->add_mechanism(pas_parameters());
+
+    dendrite->mechanism("membrane").set("r_L", 100); // no effect for single compartment cell
+
+    // add stimulus
+    cell.add_stimulus({1,1}, {5., 80., 0.3});
+
+    // load data from file
+    auto cell_data = testing::load_spike_data("../nrn/ball_and_stick.json");
+    EXPECT_TRUE(cell_data.size()>0);
+    if(cell_data.size()==0) return;
+
+    json& nrn =
+        *std::max_element(
+            cell_data.begin(), cell_data.end(),
+            [](json& lhs, json& rhs) {return lhs["nseg"]<rhs["nseg"];}
+        );
+
+    auto& measurements = nrn["measurements"];
+
+    double dt = nrn["dt"];
+    double tfinal =   100.; // ms
+    int nt = tfinal/dt;
+
+    // inline type for storing the results of a simulation along with
+    // the information required to compare two simulations for accuracy
+    struct result {
+        std::vector<std::vector<double>> spikes;
+        std::vector<std::vector<double>> baseline_spikes;
+        std::vector<testing::spike_comparison> comparisons;
+        std::vector<double> thresh;
+        int n_comparments;
+
+        result(int nseg, double dt,
+          std::vector<std::vector<double>> &v,
+          json& measurements
+        ) {
+            n_comparments = nseg;
+            baseline_spikes = {
+                measurements["soma"]["spikes"],
+                measurements["dend"]["spikes"],
+                measurements["clamp"]["spikes"]
+            };
+            thresh = {
+                measurements["soma"]["thresh"],
+                measurements["dend"]["thresh"],
+                measurements["clamp"]["thresh"]
+            };
+            for(auto i=0; i<3; ++i) {
+                // calculate the NEST MC spike times
+                spikes.push_back
+                    (testing::find_spikes(v[i], thresh[i], dt));
+                // compare NEST MC and baseline NEURON spike times
+                comparisons.push_back
+                    (testing::compare_spikes(spikes[i], baseline_spikes[i]));
+            }
+        }
+    };
+
+    std::vector<result> results;
+    for(auto run_index=0u; run_index<cell_data.size(); ++run_index) {
+        auto& run = cell_data[run_index];
+        int num_compartments = run["nseg"];
+        dendrite->set_compartments(num_compartments);
+        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
+
+        // run the simulation
+        auto soma_comp = nest::mc::find_compartment_index({0,0}, graph);
+        auto dend_comp = nest::mc::find_compartment_index({1,0.5}, graph);
+        auto clamp_comp = nest::mc::find_compartment_index({1,1.0}, graph);
+        v[0].push_back(model.voltage()[soma_comp]);
+        v[1].push_back(model.voltage()[dend_comp]);
+        v[2].push_back(model.voltage()[clamp_comp]);
+        for(auto i=0; i<nt; ++i) {
+            model.advance(dt);
+            // save voltage at soma
+            v[0].push_back(model.voltage()[soma_comp]);
+            v[1].push_back(model.voltage()[dend_comp]);
+            v[2].push_back(model.voltage()[clamp_comp]);
+        }
+
+        results.push_back( {run["nseg"], dt, v, measurements} );
+    }
+
+    // print results
+    for(auto& r : results){
+        // select the location with the largest error for printing
+        auto m =
+            std::max_element(
+                r.comparisons.begin(), r.comparisons.end(),
+                [](testing::spike_comparison& l, testing::spike_comparison& r)
+                {return l.max_relative_error()<r.max_relative_error();}
+            );
+        std::cout << std::setw(5) << r.n_comparments
+                  << " compartments : " << *m
+                  << "\n";
+    }
+
+    // sort results in ascending order of compartments
+    std::sort(
+        results.begin(), results.end(),
+        [](const result& l, const result& r)
+            {return l.n_comparments<r.n_comparments;}
+    );
+
+    // the strategy for testing is the following:
+    //  1. test that the solution converges to the finest reference solution as
+    //     the number of compartments increases (i.e. spatial resolution is
+    //     refined)
+    for(auto i=1u; i<results.size(); ++i) {
+        for(auto j=0; j<3; ++j) {
+            EXPECT_TRUE(
+                  results[i].comparisons[j].max_relative_error()
+                < results[i-1].comparisons[j].max_relative_error()
+            );
+        }
+    }
+
+    //  2. test that the best solution (i.e. with most compartments) matches the
+    //     reference solution closely (less than 0.1% over the course of 100ms
+    //     simulation)
+    for(auto j=0; j<3; ++j) {
+        EXPECT_TRUE(results.back().comparisons[j].max_relative_error()*100<0.1);
+    }
+}
+
diff --git a/tests/validate_soma.cpp b/tests/validate_soma.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b65b4ecb3a0de12d475c6380b24b43b824b65599
--- /dev/null
+++ b/tests/validate_soma.cpp
@@ -0,0 +1,163 @@
+#include <fstream>
+
+#include "gtest.h"
+#include "util.hpp"
+
+#include <cell.hpp>
+#include <fvm.hpp>
+
+#include <json/src/json.hpp>
+
+// compares results with those generated by nrn/soma.py
+// single compartment model with HH channels
+TEST(soma, neuron_baseline)
+{
+    using namespace nest::mc;
+    using namespace nlohmann;
+
+    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
+    soma->add_mechanism(hh_parameters());
+
+    // add stimulus to the soma
+    cell.add_stimulus({0,0.5}, {10., 100., 0.1});
+
+    // make the lowered finite volume cell
+    fvm::fvm_cell<double, int> model(cell);
+
+    // load data from file
+    auto cell_data = testing::load_spike_data("../nrn/soma.json");
+    EXPECT_TRUE(cell_data.size()>0);
+    if(cell_data.size()==0) return;
+
+    json& nrn =
+        *std::min_element(
+            cell_data.begin(), cell_data.end(),
+            [](json& lhs, json& rhs) {return lhs["dt"]<rhs["dt"];}
+        );
+    std::vector<double> nrn_spike_times = nrn["spikes"];
+
+    std::cout << "baseline with time step size " << nrn["dt"] << " ms\n";
+    std::cout << "baseline spikes : " << nrn["spikes"] << "\n";
+
+    for(auto& run : cell_data) {
+        std::vector<double> v;
+        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
+
+        // run the simulation
+        auto tfinal =   120.; // ms
+        int nt = tfinal/dt;
+        v.push_back(model.voltage()[0]);
+        for(auto i=0; i<nt; ++i) {
+            model.advance(dt);
+            // save voltage at soma
+            v.push_back(model.voltage()[0]);
+        }
+
+        // get the spike times from the NEST MC simulation
+        auto nst_spike_times = testing::find_spikes(v, 0., dt);
+        // compare NEST MC and baseline NEURON results
+        auto comparison = testing::compare_spikes(nst_spike_times, nrn_spike_times);
+
+        // Assert that relative error is less than 1%.
+        // For a 100 ms simulation this asserts that the difference between
+        // NEST and the most accurate NEURON simulation is less than 1 ms.
+        std::cout << "MAX ERROR @ " << dt << " is " << comparison.max_relative_error()*100. << "\n";
+        EXPECT_TRUE(comparison.max_relative_error()*100. < 1.);
+    }
+}
+
+// check if solution converges
+TEST(soma, convergence)
+{
+    using namespace nest::mc;
+
+    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
+    soma->add_mechanism(hh_parameters());
+
+    // add stimulus to the soma
+    cell.add_stimulus({0,0.5}, {10., 100., 0.1});
+
+    // make the lowered finite volume cell
+    fvm::fvm_cell<double, int> model(cell);
+
+    // generate baseline solution with small dt=0.0001
+    std::vector<double> baseline_spike_times;
+    {
+        auto dt = 1e-4;
+        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
+
+        // run the simulation
+        auto tfinal =   120.; // ms
+        int nt = tfinal/dt;
+        v.push_back(model.voltage()[0]);
+        for(auto i=0; i<nt; ++i) {
+            model.advance(dt);
+            // save voltage at soma
+            v.push_back(model.voltage()[0]);
+        }
+
+        baseline_spike_times = testing::find_spikes(v, 0., dt);
+    }
+
+    testing::spike_comparison previous;
+    for(auto dt : {0.05, 0.02, 0.01, 0.005, 0.001}) {
+        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
+
+        // run the simulation
+        auto tfinal =   120.; // ms
+        int nt = tfinal/dt;
+        v.push_back(model.voltage()[0]);
+        for(auto i=0; i<nt; ++i) {
+            model.advance(dt);
+            // save voltage at soma
+            v.push_back(model.voltage()[0]);
+        }
+
+        // get the spike times from the NEST MC simulation
+        auto nst_spike_times = testing::find_spikes(v, 0., dt);
+
+        // compare NEST MC and baseline NEURON results
+        auto comparison = testing::compare_spikes(nst_spike_times, baseline_spike_times);
+        std::cout << "dt " << std::setw(8) << dt << " : " << comparison << "\n";
+        if(!previous.is_valid()) {
+            previous = std::move(comparison);
+        }
+        else {
+            // Assert that relative error is less than 1%.
+            // For a 100 ms simulation this asserts that the difference between
+            // NEST and the most accurate NEURON simulation is less than 1 ms.
+            EXPECT_TRUE(comparison.max_relative_error() < previous.max_relative_error());
+            EXPECT_TRUE(comparison.rms < previous.rms);
+            EXPECT_TRUE(comparison.max < previous.max);
+        }
+    }
+}
diff --git a/vector b/vector
index e95bcbf1de6f833b5113a82dcc6d47bb7f41c397..8153d030abba20ddd174da9804406139c393a808 160000
--- a/vector
+++ b/vector
@@ -1 +1 @@
-Subproject commit e95bcbf1de6f833b5113a82dcc6d47bb7f41c397
+Subproject commit 8153d030abba20ddd174da9804406139c393a808