diff --git a/.gitignore b/.gitignore
index f60f1b41e5cdbbb2ac445207ed0d2e41176e0104..a18a8b7d7af992216fe6d6bd229358fe0aa88b95 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,3 +50,17 @@ Makefile
 
 # mechanism implementations generated my modparser
 include/mechanisms
+
+# external build stuff
+external/bin
+external/modparser-build
+external/modparser-configure
+external/modparser-done
+external/modparser-download
+external/modparser-install
+external/modparser-mkdir
+external/modparser-patch
+external/modparser-update
+external/tmp
+mechanisms/*.hpp
+
diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py
index 55d6af4d257348526713c1fdc381c4c2792651fd..c356012c50dfe37e3a849f16809e390433511306 100644
--- a/.ycm_extra_conf.py
+++ b/.ycm_extra_conf.py
@@ -45,6 +45,8 @@ flags = [
     '.',
     '-I',
     'include',
+    '-I',
+    'external',
 #    '-isystem',
 #    '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/c++/4.2.1',
 #    '-I',
diff --git a/docs/symbols.tex b/docs/symbols.tex
index e12b55820da048ffef27bb790fca4e0b363ba23e..baa371ad8efe42d483d2f35325b6d04ef730ca43 100644
--- a/docs/symbols.tex
+++ b/docs/symbols.tex
@@ -25,8 +25,9 @@ In \eq{eq:linsys_FV} we choose units of $mV \equiv 10^{-3}V$ for each term becau
     $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    \\
+    $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    \\
+    $g_s$                     &   $\mu S$               &  $10^{-6} \cdot A\cdot V^{-1}$            & yes    \\
     $I_e$                     &   $nA$                  &  $10^{-9} \cdot A$                        & yes    \\
     \hline
 \end{tabular}
@@ -43,6 +44,16 @@ Membrane current is calculated as follows $i_m = \overline{g}(E-V)$, with units
                        &=  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 point process currents are calculated as point sources which must be turned into current densities as follows $i_m = g_s(E-V)/\sigma_i$.
+The units for the synaptic conductance $g_s$ are $\mu S$, so the units are calculated as follows
+\begin{align}
+    \unit{ i_m } &=  \unit{ g_s } \unit{ V } \unit{\sigma_i}^-1 \nonumber \\
+                 &=  10^{-6} \cdot A\cdot V^{-1} \cdot 10^{-3} \cdot 10^{12} \cdot m^{-2} \nonumber \\
+                 &=  10^{3} \cdot A \cdot m^{-2}, \label{eq:ims_unit}
+\end{align}
+which must be scaled by $10^{2}$ to match that of of the density channels in \eq{eq:im_unit}.
+
+
 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 \\
diff --git a/external/modparser b/external/modparser
index dcfb69f8cfc435418dfb95348d613c9830e6ad6f..588ca1a5ea28ef04d17b318e754d479e5489eb9a 160000
--- a/external/modparser
+++ b/external/modparser
@@ -1 +1 @@
-Subproject commit dcfb69f8cfc435418dfb95348d613c9830e6ad6f
+Subproject commit 588ca1a5ea28ef04d17b318e754d479e5489eb9a
diff --git a/external/vector b/external/vector
index 8153d030abba20ddd174da9804406139c393a808..193df3683a3eaa2bf203bbcfa935d7e7019b1719 160000
--- a/external/vector
+++ b/external/vector
@@ -1 +1 @@
-Subproject commit 8153d030abba20ddd174da9804406139c393a808
+Subproject commit 193df3683a3eaa2bf203bbcfa935d7e7019b1719
diff --git a/include/mechanisms/hh_verb.hpp b/include/mechanisms/hh_verb.hpp
deleted file mode 100644
index 8b67a69d56a0f61b2db8baaa37d03fc34d7e9687..0000000000000000000000000000000000000000
--- a/include/mechanisms/hh_verb.hpp
+++ /dev/null
@@ -1,284 +0,0 @@
-#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/CMakeLists.txt b/mechanisms/CMakeLists.txt
index ac68a5e963e42acf6b67557ca524d61572fc4902..203dedff949356b1a715146baf0ad6d6714aac56 100644
--- a/mechanisms/CMakeLists.txt
+++ b/mechanisms/CMakeLists.txt
@@ -1,4 +1,4 @@
-set(mechanisms pas hh)
+set(mechanisms pas hh expsyn)
 
 set(modcc "${CMAKE_BINARY_DIR}/external/bin/modcc")
 
diff --git a/mechanisms/generate.sh b/mechanisms/generate.sh
index 94755fbb91b912e5eada0148af1c72ea91945ecd..1fcd49beb79603e6d79cf35e1e4b9f699fcb2f49 100755
--- a/mechanisms/generate.sh
+++ b/mechanisms/generate.sh
@@ -1,4 +1,8 @@
-for mech in pas hh
+#flags="-t cpu -O"
+flags="-t cpu"
+
+for mech in pas hh expsyn
 do
-    ../external/modparser/bin/modcc -t cpu -o ../include/mechanisms/$mech.hpp ./mod/$mech.mod
+    echo ../external/modparser/bin/modcc ${flags} -o ../include/mechanisms/$mech.hpp ./mod/$mech.mod
+    ../external/modparser/bin/modcc ${flags} -o ../include/mechanisms/$mech.hpp ./mod/$mech.mod
 done
diff --git a/mechanisms/mod/expsyn.mod b/mechanisms/mod/expsyn.mod
new file mode 100644
index 0000000000000000000000000000000000000000..db1f6d6ed61a8b563141ea2516658b8f87a35637
--- /dev/null
+++ b/mechanisms/mod/expsyn.mod
@@ -0,0 +1,41 @@
+NEURON {
+    POINT_PROCESS ExpSyn
+    RANGE tau, e
+    NONSPECIFIC_CURRENT i
+}
+
+UNITS {
+    (nA) = (nanoamp)
+    (mV) = (millivolt)
+    (uS) = (microsiemens)
+}
+
+PARAMETER {
+    : the default for Neuron is 0.1
+    :tau = 0.1 (ms) : <1e-9,1e9>
+    tau = 2.0 (ms) : <1e-9,1e9>
+    e = 0   (mV)
+}
+
+ASSIGNED {}
+
+STATE {
+    g : (uS)
+}
+
+INITIAL {
+    g=0
+}
+
+BREAKPOINT {
+    SOLVE state METHOD cnexp
+    i = g*(v - e)
+}
+
+DERIVATIVE state {
+    g' = -g/tau
+}
+
+NET_RECEIVE(weight) {
+    g = g + weight
+}
diff --git a/nrn/generate_validation.sh b/nrn/generate_validation.sh
index f35d3521e9d2ce785a01bc14cd966d4910ecf7f7..c42b1d5ce66512b8dc5ff46bf13e85f4707bd8ef 100755
--- a/nrn/generate_validation.sh
+++ b/nrn/generate_validation.sh
@@ -1,3 +1,4 @@
 python2.7 ./soma.py
 python2.7 ./ball_and_stick.py
 python2.7 ./ball_and_3stick.py
+python2.7 ./simple_synapse.py
diff --git a/nrn/simple_synapse.py b/nrn/simple_synapse.py
new file mode 100644
index 0000000000000000000000000000000000000000..1779799f716157df995f2321346ad6ff52f1a608
--- /dev/null
+++ b/nrn/simple_synapse.py
@@ -0,0 +1,140 @@
+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
+
+# create a stimulator that stimulates once at 9 ms
+stim = h.NetStim()
+stim.number = 1
+stim.start  = 9
+
+if args.plot :
+    pyplot.figure(figsize=(8,4)) # Default figsize is (8,6)
+    pyplot.grid()
+
+simdur = 50.0
+h.tstop = simdur
+h.dt = 0.01
+
+start = timer()
+results = []
+for nseg in [5, 11, 51, 101] :
+
+    print 'simulation with ', nseg, ' compartments in dendrite...'
+
+    dend.nseg=nseg
+
+    # add a synapse
+    syn_ = h.ExpSyn(dend(0.5))
+    syn_.tau = 2
+    ncstim = h.NetCon(stim, syn_)
+    ncstim.delay = 1 # 1 ms delay (arrives at 10ms)
+    ncstim.weight[0] = 0.04 # NetCon weight is a vector
+
+    ncstim1 = h.NetCon(stim, syn_)
+    ncstim1.delay = 11 # 1 ms delay (arrives at 10ms)
+    ncstim1.weight[0] = 0.04 # NetCon weight is a vector
+    ncstim2 = h.NetCon(stim, syn_)
+    ncstim2.delay = 31 # 1 ms delay (arrives at 10ms)
+    ncstim2.weight[0] = 0.04 # NetCon weight is a vector
+
+    # record voltages
+    v_soma = h.Vector() # soma
+    v_dend = h.Vector() # middle of dendrite
+
+    v_soma.record( soma(0.5)._ref_v)
+    v_dend.record( dend(0.5)._ref_v)
+
+    # record spikes
+    spike_counter_soma = h.APCount(soma(0.5))
+    spike_counter_soma.thresh = 20
+    spike_counter_dend = h.APCount(dend(0.5))
+    spike_counter_dend.thresh = -10
+
+    spikes_soma = h.Vector() # soma
+    spikes_dend = h.Vector() # middle of dendrite
+
+    spike_counter_soma.record(spikes_soma)
+    spike_counter_dend.record(spikes_dend)
+
+    # 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()
+               },
+           }
+        }
+    )
+
+    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))
+
+# time the simulations
+end = timer()
+print "took ", end-start, " seconds"
+
+# save the spike info as in json format
+fp = open('simple_synapse.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/src/cell.cpp b/src/cell.cpp
index 8fdd1bb6e0f55cdfa2a3c7ea6632e0fdee74bba6..687831570cd0b5d332d983982305c8ec127cae47 100644
--- a/src/cell.cpp
+++ b/src/cell.cpp
@@ -195,6 +195,16 @@ std::vector<int> const& cell::segment_parents() const
     return parents_;
 }
 
+void cell::add_synapse(segment_location loc)
+{
+    synapses_.push_back(loc);
+}
+
+const std::vector<segment_location>& cell::synapses() const
+{
+    return synapses_;
+}
+
 // Rough and ready comparison of two cells.
 // We don't use an operator== because equality of two cells is open to
 // interpretation. For example, it is possible to have two viable representations
diff --git a/src/cell.hpp b/src/cell.hpp
index 98b871cb193ee09a595416128e6342b84fa028b9..2f336f8454bd8e3e4f4675865665c6bbb42ec654 100644
--- a/src/cell.hpp
+++ b/src/cell.hpp
@@ -111,6 +111,11 @@ class cell {
         return stimulii_;
     }
 
+    void add_synapse(segment_location loc);
+
+    const std::vector<segment_location>& synapses() const;
+
+
     private:
 
     // storage for connections
@@ -121,6 +126,9 @@ class cell {
 
     // the stimulii
     std::vector<std::pair<segment_location, i_clamp>> stimulii_;
+
+    // the synapses
+    std::vector<segment_location> synapses_;
 };
 
 // Checks that two cells have the same
diff --git a/src/cell_tree.hpp b/src/cell_tree.hpp
index fab1163b1bbf9b8c43dbf954b4a7081c4e9dcc7f..1e04fc2c3bcc4e0fe887c7048997afd272fa4bff 100644
--- a/src/cell_tree.hpp
+++ b/src/cell_tree.hpp
@@ -32,9 +32,10 @@ class cell_tree {
 public :
     // use a signed 16-bit integer for storage of indexes, which is reasonable given
     // that typical cells have at most 1000-2000 segments
-    using int_type = int16_t;
-    using index_type = memory::HostVector<int_type>;
-    using index_view = index_type::view_type;
+    using int_type        = int16_t;
+    using index_type      = memory::HostVector<int_type>;
+    using view_type       = index_type::view_type;
+    using const_view_type = index_type::const_view_type;
 
     /// default empty constructor
     cell_tree() = default;
@@ -133,7 +134,7 @@ public :
     }
 
     /// returns a list of the children of segment b
-    const index_view children(size_t b) const {
+    const_view_type children(size_t b) const {
         return tree_.children(b);
     }
 
diff --git a/src/event_queue.hpp b/src/event_queue.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..0a8b33707a2d82e949717fd243479ce33776120e
--- /dev/null
+++ b/src/event_queue.hpp
@@ -0,0 +1,74 @@
+#pragma once
+
+#include <ostream>
+#include <queue>
+
+#include <cstdint>
+
+namespace nest {
+namespace mc {
+
+struct local_event {
+    uint32_t target;
+    float time;
+    float weight;
+};
+
+inline bool operator < (local_event const& l, local_event const& r) {
+    return l.time < r.time;
+}
+
+inline bool operator > (local_event const& l, local_event const& r) {
+    return l.time > r.time;
+}
+
+class event_queue {
+public :
+    // create
+    event_queue() {}
+
+    // push stuff
+    template <typename Iter>
+    void push(Iter b, Iter e) {
+         for (; b!=e; ++b) {
+             queue_.push(*b);
+         }
+    }
+
+    // push thing
+    void push(local_event e) {
+         queue_.push(e);
+    }
+
+    std::size_t size() const {
+        return queue_.size();
+    }
+
+    // pop until
+    std::pair<bool, local_event> pop_if_before(float t_until) {
+         if (!queue_.empty() && queue_.top().time < t_until) {
+             auto ev = queue_.top();
+             queue_.pop();
+             return {true, ev};
+         }
+         else {
+             return {false, {}};
+         }
+    }
+
+private:
+    std::priority_queue<
+        local_event,
+        std::vector<local_event>,
+        std::greater<local_event>
+    > queue_;
+};
+
+} // namespace nest
+} // namespace mc
+
+inline
+std::ostream& operator<< (std::ostream& o, nest::mc::local_event e)
+{
+    return o << "event[" << e.target << "," << e.time << "," << e.weight << "]";
+}
diff --git a/src/fvm.hpp b/src/fvm.hpp
index d0c6f90272ee5f48fc7727a1aab519e3bebe5fdc..4a908bba49887948e37bfa7fe3409b19df90bbb3 100644
--- a/src/fvm.hpp
+++ b/src/fvm.hpp
@@ -2,20 +2,25 @@
 
 #include <algorithm>
 #include <map>
+#include <set>
 #include <string>
 #include <vector>
+
+#include <algorithms.hpp>
+#include <cell.hpp>
+#include <event_queue.hpp>
+#include <ion.hpp>
+#include <math.hpp>
+#include <matrix.hpp>
+#include <mechanism.hpp>
+#include <mechanism_interface.hpp>
+#include <segment.hpp>
+#include <stimulus.hpp>
+#include <util.hpp>
+
 #include <vector/include/Vector.hpp>
 
-#include "algorithms.hpp"
-#include "cell.hpp"
-#include "ion.hpp"
-#include "math.hpp"
-#include "matrix.hpp"
-#include "mechanism.hpp"
-#include "mechanism_interface.hpp"
-#include "segment.hpp"
-#include "stimulus.hpp"
-#include "util.hpp"
+#include <mechanisms/expsyn.hpp>
 
 namespace nest {
 namespace mc {
@@ -44,11 +49,13 @@ class fvm_cell {
     using index_type = memory::HostVector<size_type>;
     /// view into index container
     using index_view = typename index_type::view_type;
+    using const_index_view = typename index_type::const_view_type;
 
     /// the container used for values
     using vector_type = memory::HostVector<value_type>;
     /// view into value container
     using vector_view = typename vector_type::view_type;
+    using const_vector_view = typename vector_type::const_view_type;
 
     /// constructor
     fvm_cell(nest::mc::cell const& cell);
@@ -56,26 +63,23 @@ class fvm_cell {
     /// build the matrix for a given time step
     void setup_matrix(value_type dt);
 
-    /// TODO this should be const
     /// which requires const_view in the vector library
-    matrix_type& jacobian() {
+    const 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() {
+    const_vector_view cv_areas() const {
         return cv_areas_;
     }
 
-    /// TODO this should be const
     /// return the capacitance of each CV surface
     /// this is the total capacitance, not per unit area,
     /// i.e. equivalent to sigma_i * c_m
-    vector_view cv_capacitance() {
+    const_vector_view cv_capacitance() const {
         return cv_capacitance_;
     }
 
@@ -83,6 +87,9 @@ class fvm_cell {
     vector_view voltage() {
         return voltage_;
     }
+    const_vector_view voltage() const {
+        return voltage_;
+    }
 
     std::size_t size() const {
         return matrix_.size();
@@ -129,9 +136,16 @@ class fvm_cell {
     /// make a time step
     void advance(value_type dt);
 
+    /// advance solution to target time tfinal with maximum step size dt
+    void advance_to(value_type tfinal, value_type dt);
+
     /// set initial states
     void initialize();
 
+    event_queue& queue() {
+        return events_;
+    }
+
     private:
 
     /// current time
@@ -160,6 +174,11 @@ class fvm_cell {
     /// the potential in mV in each CV
     vector_type voltage_;
 
+    /// synapses
+    using synapse_type =
+        mechanisms::ExpSyn::mechanism_ExpSyn<value_type, size_type>;
+    std::size_t synapse_index_;
+
     /// the set of mechanisms present in the cell
     std::vector<mechanism_type> mechanisms_;
 
@@ -167,6 +186,9 @@ class fvm_cell {
     std::map<mechanisms::ionKind, ion_type> ions_;
 
     std::vector<std::pair<uint32_t, i_clamp>> stimulii_;
+
+    /// event queue
+    event_queue events_;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -292,7 +314,7 @@ fvm_cell<T, I>::fvm_cell(nest::mc::cell const& cell)
 
         // build a vector of the indexes of the compartments that contain
         // the mechanism
-        std::vector<int> compartment_index(num_comp);
+        index_type compartment_index(num_comp);
         auto pos = 0u;
         for(auto seg : mech.second) {
             auto seg_size = segment_index[seg+1] - segment_index[seg];
@@ -305,41 +327,30 @@ fvm_cell<T, I>::fvm_cell(nest::mc::cell const& cell)
         }
 
         // instantiate the mechanism
-        index_view node_index(compartment_index.data(), compartment_index.size());
         mechanisms_.push_back(
-            helper->new_mechanism(voltage_, current_, node_index)
+            helper->new_mechanism(voltage_, current_, compartment_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
+        // find the compartment indexes of all compartments that have a
+        // mechanism that depends on/influences ion
+        std::set<int> index_set;
         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);
+                for(auto idx : mech->node_index()) {
+                    index_set.insert(idx);
+                }
             }
         }
+        std::vector<int> indexes(index_set.begin(), index_set.end());
 
         // create the ion state
-        if(auto n = std::distance(indexes.first.begin(), ends.first)) {
-            ions_.emplace(ion, index_view(indexes.first.data(), n));
+        if(indexes.size()) {
+            ions_.emplace(ion, index_type(indexes));
         }
 
         // join the ion reference in each mechanism into the cell-wide ion state
@@ -355,7 +366,7 @@ fvm_cell<T, I>::fvm_cell(nest::mc::cell const& cell)
     //        the default values in Neuron.
     //        Neuron's defaults are defined in the file
     //          nrn/src/nrnoc/membdef.h
-    using memory::all;
+    auto all = memory::all;
 
     constexpr value_type DEF_vrest = -65.0; // same name as #define in Neuron
 
@@ -376,6 +387,24 @@ fvm_cell<T, I>::fvm_cell(nest::mc::cell const& cell)
         auto idx = find_compartment_index(stim.first, graph);
         stimulii_.push_back( {idx, stim.second} );
     }
+
+    // add the synapses
+    std::vector<size_type> synapse_indexes;
+    synapse_indexes.reserve(cell.synapses().size());
+    for(auto loc : cell.synapses()) {
+        synapse_indexes.push_back(
+            find_compartment_index(loc, graph)
+        );
+    }
+
+    mechanisms_.push_back(
+        mechanisms::make_mechanism<synapse_type>(
+            voltage_, current_, index_view(synapse_indexes)
+        )
+    );
+    synapse_index_ = mechanisms_.size()-1;
+    // don't forget to give point processes access to cv_areas_
+    mechanisms_[synapse_index_]->set_areas(cv_areas_);
 }
 
 template <typename T, typename I>
@@ -405,8 +434,6 @@ void fvm_cell<T, I>::setup_matrix(T dt)
     //d(all) = 1.0;
     d(all) = cv_areas_;
     for(auto i=1u; i<d.size(); ++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;
@@ -416,7 +443,6 @@ 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
     //      V[i] - dt/cm*(im - ie)
@@ -460,29 +486,44 @@ void fvm_cell<T, I>::advance(T dt)
         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";
+}
+
+template <typename T, typename I>
+void fvm_cell<T, I>::advance_to(T tfinal, T dt)
+{
+    if(t_>=tfinal) {
+        return;
+    }
+
+    do {
+        auto tnext = std::min(tfinal, t_+dt);
+        auto next = events_.pop_if_before(tnext);
+        // if there is an event before tnext...
+        if(next.first) {
+            tnext = next.second.time;
+        }
+        advance(tnext-t_);
+        t_ = tnext;
+        if(next.first) { // handle event
+            auto &e = next.second;
+
+            mechanisms_[synapse_index_]->net_receive(e.target, e.weight);
+        }
+    } while(t_<tfinal);
 }
 
 } // namespace fvm
diff --git a/src/matrix.hpp b/src/matrix.hpp
index e86c9b897b66a529252721b739ae20f96cfa16ae..9e2c9e7d10d68c2f973cffaaf005ceb939fdcf41 100644
--- a/src/matrix.hpp
+++ b/src/matrix.hpp
@@ -18,10 +18,12 @@ class matrix {
     using size_type  = I;
 
     // define storage types
-    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;
+    using vector_type            = memory::HostVector<value_type>;
+    using vector_view_type       = typename vector_type::view_type;
+    using const_vector_view_type = typename vector_type::const_view_type;
+    using index_type             = memory::HostVector<size_type>;
+    using index_view_type        = typename index_type::view_type;
+    using const_index_view_type  = typename index_type::const_view_type;
 
     matrix() = default;
 
@@ -84,41 +86,62 @@ class matrix {
     vector_view_type vec_d()   { return d(); }
     vector_view_type vec_v()   { return v(); }
 
+    const_vector_view_type vec_rhs() const { return rhs(); }
+    const_vector_view_type vec_d()   const { return d(); }
+    const_vector_view_type vec_v()   const { return v(); }
+
     /// the vector holding the lower part of the matrix
-    vector_view_type l()
-    {
+    vector_view_type l() {
+        return l_;
+    }
+
+    const_vector_view_type l() const {
         return l_;
     }
 
     /// the vector holding the diagonal of the matrix
-    vector_view_type d()
-    {
+    vector_view_type d() {
+        return d_;
+    }
+
+    const vector_view_type d() const {
         return d_;
     }
 
     /// the vector holding the upper part of the matrix
-    vector_view_type u()
-    {
+    vector_view_type u() {
+        return u_;
+    }
+    const vector_view_type u() const {
         return u_;
     }
 
     /// the vector holding the right hand side of the linear equation system
-    vector_view_type rhs()
-    {
+    vector_view_type rhs() {
+        return rhs_;
+    }
+
+    const_vector_view_type rhs() const {
         return rhs_;
     }
 
     /// the vector holding the solution (voltage)
-    vector_view_type v()
-    {
+    vector_view_type v() {
         EXPECTS(has_voltage_);
+        return v_;
+    }
 
+    const_vector_view_type v() const {
+        EXPECTS(has_voltage_);
         return v_;
     }
 
     /// the vector holding the parent index
-    index_view_type p()
-    {
+    index_view_type p() {
+        return parent_index_;
+    }
+
+    const_index_view_type p() const {
         return parent_index_;
     }
 
diff --git a/src/mechanism.hpp b/src/mechanism.hpp
index 7a06a7330e06bb14000e7c262c3db55fe4dc7135..c9c801fd198c764d61f3acf653055c8957daf00e 100644
--- a/src/mechanism.hpp
+++ b/src/mechanism.hpp
@@ -35,6 +35,7 @@ public:
     :   vec_v_(vec_v)
     ,   vec_i_(vec_i)
     ,   node_index_(node_index)
+    ,   vec_area_(nullptr, 0)
     {}
 
     std::size_t size() const
@@ -63,14 +64,20 @@ public:
     virtual void nrn_init()     = 0;
     virtual void nrn_state()    = 0;
     virtual void nrn_current()  = 0;
+    virtual void net_receive(int, value_type) {};
     virtual bool uses_ion(ionKind) const = 0;
     virtual void set_ion(ionKind k, ion_type& i) = 0;
 
+    void set_areas(view_type area) {
+        vec_area_ = area;
+    }
+
     virtual mechanismKind kind() const = 0;
 
     view_type vec_v_;
     view_type vec_i_;
     index_type node_index_;
+    view_type vec_area_;
 };
 
 template <typename T, typename I>
@@ -87,5 +94,5 @@ make_mechanism(
 }
 
 } // namespace mechanisms
-} // namespace nest
 } // namespace mc
+} // namespace nest
diff --git a/src/tree.hpp b/src/tree.hpp
index be96e3619424ec54b143e694108188419802e67b..3d76a9dc1e8171f62026320a98f71078e91f1dc5 100644
--- a/src/tree.hpp
+++ b/src/tree.hpp
@@ -20,7 +20,8 @@ class tree {
     using int_type = int16_t;
 
     using index_type = memory::HostVector<int_type>;
-    using index_view = index_type::view_type;
+    using view_type  = index_type::view_type;
+    using const_view_type = index_type::const_view_type;
 
     tree() = default;
 
@@ -163,22 +164,22 @@ class tree {
     }
 
     /// return the child index
-    const index_view child_index() const {
+    const_view_type child_index() {
         return child_index_;
     }
 
     /// return the list of all children
-    const index_view children() const {
+    const_view_type children() const {
         return children_;
     }
 
     /// return the list of all children of branch b
-    const index_view children(size_t b) const {
+    const_view_type children(size_t b) const {
         return children_(child_index_[b], child_index_[b+1]);
     }
 
     /// return the list of parents
-    const index_view parents() const {
+    const_view_type parents() const {
         return parents_;
     }
 
@@ -288,7 +289,7 @@ class tree {
         int new_node,
         int old_node,
         int parent_node,
-        index_view p,
+        view_type p,
         tree const& old_tree
     )
     {
@@ -350,9 +351,9 @@ class tree {
 
     // provide default parameters so that tree type can
     // be default constructed
-    index_view children_   = data_(0, 0);
-    index_view child_index_= data_(0, 0);
-    index_view parents_    = data_(0, 0);
+    view_type children_   = data_(0, 0);
+    view_type child_index_= data_(0, 0);
+    view_type parents_    = data_(0, 0);
 };
 
 template <typename C>
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 51d0aa9c97644c3e3ade1d35f6346c0c44bf7c77..9b61220058ee4f83d4102c68102dd85c7bfa9ece 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -16,6 +16,7 @@ set(TEST_SOURCES
     test_algorithms.cpp
     test_cell.cpp
     test_compartments.cpp
+    test_event_queue.cpp
     test_fvm.cpp
     test_matrix.cpp
     test_mechanisms.cpp
@@ -24,6 +25,7 @@ set(TEST_SOURCES
     test_segment.cpp
     test_stimulus.cpp
     test_swcio.cpp
+    test_synapses.cpp
     test_tree.cpp
 
     # unit test driver
@@ -32,8 +34,9 @@ set(TEST_SOURCES
 
 set(VALIDATION_SOURCES
     # unit tests
-    validate_soma.cpp
     validate_ball_and_stick.cpp
+    validate_soma.cpp
+    validate_synapses.cpp
 
     # unit test driver
     validate.cpp
diff --git a/tests/plot.py b/tests/plot.py
new file mode 100644
index 0000000000000000000000000000000000000000..754eb6f2cfcf336215d5b1c1ba440138833ad3c8
--- /dev/null
+++ b/tests/plot.py
@@ -0,0 +1,20 @@
+from matplotlib import pyplot
+import numpy as np
+
+raw = np.fromfile("den_syn.txt", sep=" ")
+n = raw.size/3
+data = raw.reshape(n,3)
+
+t    = data[:, 0]
+soma = data[:, 1]
+dend = data[:, 2]
+
+pyplot.plot(t, soma)
+pyplot.plot(t, dend)
+
+pyplot.xlabel('time (ms)')
+pyplot.ylabel('mV')
+pyplot.xlim([t[0], t[n-1]])
+pyplot.grid()
+pyplot.show()
+
diff --git a/tests/test_event_queue.cpp b/tests/test_event_queue.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3297784d506f6eff10078e249388942b85d2240f
--- /dev/null
+++ b/tests/test_event_queue.cpp
@@ -0,0 +1,99 @@
+#include "gtest.h"
+
+#include <vector>
+
+#include <event_queue.hpp>
+
+TEST(event_queue, push)
+{
+    using namespace nest::mc;
+
+    event_queue q;
+
+    q.push({1u, 2.f, 2.f});
+    q.push({4u, 1.f, 2.f});
+    q.push({8u, 20.f, 2.f});
+    q.push({2u, 8.f, 2.f});
+
+    std::vector<float> times;
+    while(q.size()) {
+        times.push_back(
+            q.pop_if_before(std::numeric_limits<float>::max()).second.time
+        );
+    }
+
+    //std::copy(times.begin(), times.end(), std::ostream_iterator<float>(std::cout, ","));
+    //std::cout << "\n";
+    EXPECT_TRUE(std::is_sorted(times.begin(), times.end()));
+}
+
+TEST(event_queue, push_range)
+{
+    using namespace nest::mc;
+
+    local_event events[] = {
+        {1u, 2.f, 2.f},
+        {4u, 1.f, 2.f},
+        {8u, 20.f, 2.f},
+        {2u, 8.f, 2.f}
+    };
+
+    event_queue q;
+    q.push(std::begin(events), std::end(events));
+
+    std::vector<float> times;
+    while(q.size()) {
+        times.push_back(
+            q.pop_if_before(std::numeric_limits<float>::max()).second.time
+        );
+    }
+
+    EXPECT_TRUE(std::is_sorted(times.begin(), times.end()));
+}
+
+TEST(event_queue, pop_if_before)
+{
+    using namespace nest::mc;
+
+    local_event events[] = {
+        {1u, 1.f, 2.f},
+        {2u, 2.f, 2.f},
+        {3u, 3.f, 2.f},
+        {4u, 4.f, 2.f}
+    };
+
+    event_queue q;
+    q.push(std::begin(events), std::end(events));
+
+    EXPECT_EQ(q.size(), 4u);
+
+    auto e1 = q.pop_if_before(0.);
+    EXPECT_FALSE(e1.first);
+    EXPECT_EQ(q.size(), 4u);
+
+    auto e2 = q.pop_if_before(5.);
+    EXPECT_TRUE(e2.first);
+    EXPECT_EQ(e2.second.target, 1u);
+    EXPECT_EQ(q.size(), 3u);
+
+    auto e3 = q.pop_if_before(5.);
+    EXPECT_TRUE(e3.first);
+    EXPECT_EQ(e3.second.target, 2u);
+    EXPECT_EQ(q.size(), 2u);
+
+    auto e4 = q.pop_if_before(2.5);
+    EXPECT_FALSE(e4.first);
+    EXPECT_EQ(q.size(), 2u);
+
+    auto e5 = q.pop_if_before(5.);
+    EXPECT_TRUE(e5.first);
+    EXPECT_EQ(e5.second.target, 3u);
+    EXPECT_EQ(q.size(), 1u);
+
+    q.pop_if_before(5.);
+    EXPECT_EQ(q.size(), 0u);
+
+    // empty queue should always return "false"
+    auto e6 = q.pop_if_before(100.);
+    EXPECT_FALSE(e6.first);
+}
diff --git a/tests/test_synapses.cpp b/tests/test_synapses.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5a8f2ffac903289663dc21e105523a593bd695a2
--- /dev/null
+++ b/tests/test_synapses.cpp
@@ -0,0 +1,83 @@
+#include "gtest.h"
+#include "util.hpp"
+
+#include <cell.hpp>
+#include <fvm.hpp>
+
+// compares results with those generated by nrn/ball_and_stick.py
+TEST(synapses, add_to_cell)
+{
+    using namespace nest::mc;
+
+    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());
+
+    cell.add_synapse({0, 0.1});
+    cell.add_synapse({1, 0.2});
+    cell.add_synapse({0, 0.3});
+
+    EXPECT_EQ(3u, cell.synapses().size());
+    EXPECT_EQ(cell.synapses()[0].segment, 0);
+    EXPECT_EQ(cell.synapses()[0].position, 0.1);
+    EXPECT_EQ(cell.synapses()[1].segment, 1);
+    EXPECT_EQ(cell.synapses()[1].position, 0.2);
+    EXPECT_EQ(cell.synapses()[2].segment, 0);
+    EXPECT_EQ(cell.synapses()[2].position, 0.3);
+}
+
+// compares results with those generated by nrn/ball_and_stick.py
+TEST(synapses, basic_state)
+{
+    using namespace nest::mc;
+
+    // setup global state for the mechanisms
+    nest::mc::mechanisms::setup_mechanism_helpers();
+
+    using synapse_type = mechanisms::ExpSyn::mechanism_ExpSyn<double, int>;
+    auto num_syn = 4;
+    synapse_type::index_type indexes(num_syn);
+    synapse_type::vector_type voltage(num_syn, -65.0);
+    synapse_type::vector_type current(num_syn,   1.0);
+    auto mech = mechanisms::make_mechanism<synapse_type>( voltage, current, indexes );
+
+    auto ptr = dynamic_cast<synapse_type*>(mech.get());
+
+    // parameters initialized to default values
+    for(auto e : ptr->e) {
+        EXPECT_EQ(e, 0.);
+    }
+    for(auto tau : ptr->tau) {
+        EXPECT_EQ(tau, 2.0);
+    }
+
+    // current and voltage vectors correctly hooked up
+    for(auto v : ptr->vec_v_) {
+        EXPECT_EQ(v, -65.);
+    }
+    for(auto i : ptr->vec_i_) {
+        EXPECT_EQ(i, 1.0);
+    }
+
+    // should be initialized to NaN
+    for(auto g : ptr->g) {
+        EXPECT_NE(g, g);
+    }
+
+    // initialize state then check g has been set to zero
+    ptr->nrn_init();
+    for(auto g : ptr->g) {
+        EXPECT_EQ(g, 0.);
+    }
+
+    // call net_receive on two of the synapses
+    ptr->net_receive(1, 3.14);
+    ptr->net_receive(3, 1.04);
+    EXPECT_EQ(ptr->g[1], 3.14);
+    EXPECT_EQ(ptr->g[3], 1.04);
+}
diff --git a/tests/validate_synapses.cpp b/tests/validate_synapses.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7b882a689c94cb5e6b70e8f89115917fc7a6ba0d
--- /dev/null
+++ b/tests/validate_synapses.cpp
@@ -0,0 +1,160 @@
+#include <fstream>
+
+#include "gtest.h"
+#include "util.hpp"
+
+#include <cell.hpp>
+#include <fvm.hpp>
+
+#include <json/src/json.hpp>
+
+
+// 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,
+      nlohmann::json& measurements
+    ) {
+        n_comparments = nseg;
+        baseline_spikes = {
+            measurements["soma"]["spikes"],
+            measurements["dend"]["spikes"]
+        };
+        thresh = {
+            measurements["soma"]["thresh"],
+            measurements["dend"]["thresh"]
+        };
+        for(auto i=0u; i<v.size(); ++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]));
+        }
+    }
+};
+
+// compares results with those generated by nrn/simple_synapse.py
+TEST(simple_synapse, 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);
+    soma->mechanism("membrane").set("r_L", 100);
+
+    // add stimulus
+    cell.add_synapse({1, 0.5});
+
+    // load data from file
+    auto cell_data = testing::load_spike_data("../nrn/simple_synapse.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 = 50.; // ms
+
+    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(2);
+
+        // 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
+
+        // add the 3 spike events to the queue
+        model.queue().push({0u, 10.0, 0.04});
+        model.queue().push({0u, 20.0, 0.04});
+        model.queue().push({0u, 40.0, 0.04});
+
+        // 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);
+        v[0].push_back(model.voltage()[soma_comp]);
+        v[1].push_back(model.voltage()[dend_comp]);
+        double t  = 0.;
+        while(t < tfinal) {
+            t += dt;
+            model.advance_to(t, dt);
+            // save voltage at soma
+            v[0].push_back(model.voltage()[soma_comp]);
+            v[1].push_back(model.voltage()[dend_comp]);
+        }
+
+        results.push_back( {num_compartments, dt, v, measurements} );
+    }
+
+    // print results
+    auto colors = {memory::util::kWhite, memory::util::kGreen, memory::util::kYellow};
+    for(auto& r : results){
+        auto color = colors.begin();
+        for(auto const& result : r.comparisons) {
+            std::cout << std::setw(5) << r.n_comparments << " compartments : ";
+            std::cout << memory::util::colorize(util::pprintf("%\n", result), *(color++));
+        }
+    }
+
+    // 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 j=0; j<2; ++j) {
+        EXPECT_TRUE(
+              results.back().comparisons[j].max_relative_error()
+            < results.front().comparisons[j].max_relative_error()
+        );
+    }
+
+    //  2. test that the best solution (i.e. with most compartments) matches the
+    //     reference solution closely (less than 0.5% over the course of 100ms
+    //     simulation)
+    auto tol = 0.5;
+    for(auto j=0; j<2; ++j) {
+        EXPECT_TRUE(results.back().comparisons[j].max_relative_error()*100<tol);
+    }
+}
+