diff --git a/.gitignore b/.gitignore
index e9d54cb796031fdcbba7935bec51511be3f6de6b..b710bef2b72c6b9f6eea87de726bf180c4f3a107 100644
--- a/.gitignore
+++ b/.gitignore
@@ -66,3 +66,5 @@ mechanisms/*.hpp
 
 # build path
 build
+
+commit.msg
diff --git a/.gitmodules b/.gitmodules
index ae780729fd3c2e22a3a6f5029fb0b611977ccfeb..86e0c20fe68579e7b0f1ccdd1dba498cf16b13fe 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -7,3 +7,6 @@
 [submodule "modparser"]
 	path = external/modparser
 	url = git@github.com:eth-cscs/modparser.git
+[submodule "external/tclap"]
+	path = external/tclap
+	url = https://github.com/eile/tclap.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index bc45593eb2451be9818e2b75671d91341647e4e4..5d082f12be8d6c555372be33a8737959f4e2a08b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -65,6 +65,7 @@ externalproject_add(modparser
 
 
 include_directories(${CMAKE_SOURCE_DIR}/external)
+include_directories(${CMAKE_SOURCE_DIR}/external/tclap/include)
 include_directories(${CMAKE_SOURCE_DIR}/include)
 include_directories(${CMAKE_SOURCE_DIR}/src)
 include_directories(${CMAKE_SOURCE_DIR}/miniapp)
diff --git a/commit.msg b/commit.msg
deleted file mode 100644
index 3abb1935698a6efe63d5ea651d8c7d5c017541fe..0000000000000000000000000000000000000000
--- a/commit.msg
+++ /dev/null
@@ -1,9 +0,0 @@
-* change `probe_sort` enum to scoped enum
-    - renamed to `probeKind`
-    - refactored out of class
-    - updated coding guidelines wiki with enum rules
-* refactor `std::pair` into structs with meaningfull name types in `cell.hpp`
-    - not just probes: stimulii and detectors too.
-* add profiling region around sampling in `cell_group`
-* change output data format for traces to json
-* remove white space at end of lines (looking at you Sam)
diff --git a/external/tclap b/external/tclap
new file mode 160000
index 0000000000000000000000000000000000000000..f41dcb5ce3d063c9fe95623193bba693338f3edb
--- /dev/null
+++ b/external/tclap
@@ -0,0 +1 @@
+Subproject commit f41dcb5ce3d063c9fe95623193bba693338f3edb
diff --git a/miniapp/io.cpp b/miniapp/io.cpp
index 887848b549b7bad46574b92ac9fcde74faae5d5c..8367a1403a1ff58ab657239deafc0fd024731295 100644
--- a/miniapp/io.cpp
+++ b/miniapp/io.cpp
@@ -1,21 +1,95 @@
+#include <fstream>
+#include <exception>
+
+#include <tclap/CmdLine.h>
+
 #include "io.hpp"
 
 namespace nest {
 namespace mc {
 namespace io {
 
-// read simulation options from json file with name fname
-// for now this is just a placeholder
-options read_options(std::string fname) {
-    // 10 cells, 1 synapses per cell, 10 compartments per segment
-    return {200, 1, 100};
+/// read simulation options from json file with name fname
+/// if file name is empty or if file is not a valid json file a default
+/// set of parameters is returned :
+///      1000 cells, 500 synapses per cell, 100 compartments per segment
+cl_options read_options(int argc, char** argv) {
+
+    // set default options
+    const cl_options default_options{"", 1000, 500, 100};
+
+    cl_options options;
+    // parse command line arguments
+    try {
+        TCLAP::CmdLine cmd("mod2c performance benchmark harness", ' ', "0.1");
+
+        TCLAP::ValueArg<uint32_t> ncells_arg(
+            "n", "ncells", "total number of cells in the model",
+            false, 1000,"non negative integer");
+        TCLAP::ValueArg<uint32_t> nsynapses_arg(
+            "s", "nsynapses", "number of synapses per cell",
+            false, 500,"non negative integer");
+        TCLAP::ValueArg<uint32_t> ncompartments_arg(
+            "c", "ncompartments", "number of compartments per segment",
+            false, 100,"non negative integer");
+        TCLAP::ValueArg<std::string> ifile_arg(
+            "i", "ifile", "number of compartments per segment",
+            false, "","file name string");
+
+        cmd.add(ncells_arg);
+        cmd.add(nsynapses_arg);
+        cmd.add(ncompartments_arg);
+        cmd.add(ifile_arg);
+
+        cmd.parse(argc, argv);
+
+        options.cells = ncells_arg.getValue();
+        options.synapses_per_cell = nsynapses_arg.getValue();
+        options.compartments_per_segment = ncompartments_arg.getValue();
+        options.ifname = ifile_arg.getValue();
+    }
+    // catch any exceptions in command line handling
+    catch(TCLAP::ArgException &e) {
+        std::cerr << "error: parsing command line arguments:\n  "
+                  << e.error() << " for arg " << e.argId() << "\n";
+        exit(1);
+    }
+
+    if(options.ifname == "") {
+        return options;
+    }
+    else {
+        std::ifstream fid(options.ifname, std::ifstream::in);
+
+        if(fid.is_open()) {
+            // read json data in input file
+            nlohmann::json fopts;
+            fid >> fopts;
+
+            try {
+                options.cells = fopts["cells"];
+                options.synapses_per_cell = fopts["synapses"];
+                options.compartments_per_segment = fopts["compartments"];
+            }
+            catch(std::exception e) {
+                std::cerr << "error: unable to open parameters in "
+                          << options.ifname << ", using  defaults instead\n"
+                          << "... " << e.what() << "\n";
+                exit(1);
+            }
+            return options;
+        }
+    }
+
+    return default_options;
 }
 
-std::ostream& operator<<(std::ostream& o, const options& opt) {
+std::ostream& operator<<(std::ostream& o, const cl_options& options) {
     o << "simultion options:\n";
-    o << "  cells                : " << opt.cells << "\n";
-    o << "  compartments/segment : " << opt.compartments_per_segment << "\n";
-    o << "  synapses/cell        : " << opt.synapses_per_cell << "\n";
+    o << "  cells                : " << options.cells << "\n";
+    o << "  compartments/segment : " << options.compartments_per_segment << "\n";
+    o << "  synapses/cell        : " << options.synapses_per_cell << "\n";
+    o << "  input file name      : " << options.ifname << "\n";
 
     return o;
 }
diff --git a/miniapp/io.hpp b/miniapp/io.hpp
index e8a8762ceb34b7d7148dfb7eb831217ac633d3d3..fea4dbd911e4f7214500349c07611d1e8f93cf9d 100644
--- a/miniapp/io.hpp
+++ b/miniapp/io.hpp
@@ -7,15 +7,16 @@ namespace mc {
 namespace io {
 
 // holds the options for a simulation run
-struct options {
-    int cells;
-    int synapses_per_cell;
-    int compartments_per_segment;
+struct cl_options {
+    std::string ifname;
+    uint32_t cells;
+    uint32_t synapses_per_cell;
+    uint32_t compartments_per_segment;
 };
 
-std::ostream& operator<<(std::ostream& o, const options& opt);
+std::ostream& operator<<(std::ostream& o, const cl_options& opt);
 
-options read_options(std::string fname);
+cl_options read_options(int argc, char** argv);
 
 } // namespace io
 } // namespace mc
diff --git a/miniapp/miniapp.cpp b/miniapp/miniapp.cpp
index 74b551ff4f07fe795514caafde901de1721108dc..97824cfe4dbec20272c46a9db0939f096ab01dc5 100644
--- a/miniapp/miniapp.cpp
+++ b/miniapp/miniapp.cpp
@@ -32,14 +32,14 @@ struct model {
     communicator_type communicator;
     std::vector<cell_group> cell_groups;
 
-    int num_groups() const {
+    unsigned num_groups() const {
         return cell_groups.size();
     }
 
     void run(double tfinal, double dt) {
         auto t = 0.;
         auto delta = communicator.min_delay();
-        while(t<tfinal) {
+        while (t<tfinal) {
             mc::threading::parallel_for::apply(
                 0, num_groups(),
                 [&](int i) {
@@ -68,7 +68,7 @@ struct model {
         // calculate the source and synapse distribution serially
         std::vector<id_type> target_counts(num_groups());
         std::vector<id_type> source_counts(num_groups());
-        for (auto i=0; i<num_groups(); ++i) {
+        for (auto i=0u; i<num_groups(); ++i) {
             target_counts[i] = cell_groups[i].cell().synapses()->size();
             source_counts[i] = cell_groups[i].spike_sources().size();
         }
@@ -87,9 +87,13 @@ struct model {
         auto com_policy = communicator.communication_policy();
         auto global_source_map = com_policy.make_map(source_map.back());
         auto domain_idx = communicator.domain_id();
-        for (auto i=0; i<num_groups(); ++i) {
-            cell_groups[i].set_source_gids(source_map[i]+global_source_map[domain_idx]);
-            cell_groups[i].set_target_gids(target_map[i]+communicator.target_gid_from_group_lid(0));
+        for (auto i=0u; i<num_groups(); ++i) {
+            cell_groups[i].set_source_gids(
+                source_map[i]+global_source_map[domain_idx]
+            );
+            cell_groups[i].set_target_gids(
+                target_map[i]+communicator.target_gid_from_group_lid(0)
+            );
         }
             mc::util::profiler_leave(2);
     }
@@ -166,10 +170,10 @@ struct model {
 namespace parameters {
 namespace synapses {
     // synapse delay
-    constexpr double delay  = 5.0;  // ms
+    constexpr float delay  = 20.0;  // ms
 
     // connection weight
-    constexpr double weight = 0.005;  // uS
+    constexpr double weight_per_cell = 0.3;  // uS
 }
 }
 
@@ -187,9 +191,7 @@ void setup();
 cell_group make_lowered_cell(int cell_index, const mc::cell& c);
 
 /// models
-void ring_model(nest::mc::io::options& opt, model& m);
-void all_to_all_model(nest::mc::io::options& opt, model& m);
-
+void all_to_all_model(nest::mc::io::cl_options& opt, model& m);
 
 ///////////////////////////////////////
 // main
@@ -200,11 +202,11 @@ int main(int argc, char** argv) {
     setup();
 
     // read parameters
-    mc::io::options opt;
+    mc::io::cl_options options;
     try {
-        opt = mc::io::read_options("");
+        options = mc::io::read_options(argc, argv);
         if (!global_policy::id()) {
-            std::cout << opt << "\n";
+            std::cout << options << "\n";
         }
     }
     catch (std::exception& e) {
@@ -213,18 +215,29 @@ int main(int argc, char** argv) {
     }
 
     model m;
-    all_to_all_model(opt, m);
+    all_to_all_model(options, m);
 
     //
     //  time stepping
     //
-    auto tfinal = 20.;
-    auto dt = 0.01;
+    auto tfinal = 50.;
+    auto dt = 0.025;
 
     auto id = m.communicator.domain_id();
 
-    if (!id) {
-        m.communicator.add_spike({0, 5});
+    if (id==0) {
+        std::cout << "\n";
+        std::cout << ":: simulation to " << tfinal << " ms in "
+                  << std::ceil(tfinal / dt) << " steps of "
+                  << dt << " ms\n";
+    }
+
+    // add some spikes to the system to start it
+    auto first = m.communicator.group_gid_first(id);
+    first += 20 - (first%20); // round up to multiple of 20
+    auto last  = m.communicator.group_gid_first(id+1);
+    for (auto i=first; i<last; i+=20) {
+        m.communicator.add_spike({i, 0});
     }
 
     m.run(tfinal, dt);
@@ -234,44 +247,48 @@ int main(int argc, char** argv) {
     if (!id) {
         std::cout << "there were " << m.communicator.num_spikes() << " spikes\n";
     }
-    m.dump_traces();
 
-#ifdef SPLAT
-    if (!global_policy::id()) {
-        //for (auto i=0u; i<m.cell_groups.size(); ++i) {
-        m.cell_groups[0].splat("cell0.txt");
-        m.cell_groups[1].splat("cell1.txt");
-        m.cell_groups[2].splat("cell2.txt");
-        //}
-    }
-#endif
+    m.dump_traces();
 }
 
 ///////////////////////////////////////
 // models
 ///////////////////////////////////////
 
-void ring_model(nest::mc::io::options& opt, model& m) {
+void all_to_all_model(nest::mc::io::cl_options& options, model& m) {
     //
     //  make cells
     //
 
+    // calculate how many synapses per cell
+    auto synapses_per_cell =
+        std::min(options.synapses_per_cell, options.cells-1);
+    auto is_all_to_all = synapses_per_cell == (options.cells-1);
+
     // make a basic cell
-    auto basic_cell = make_cell(opt.compartments_per_segment, 1);
+    auto basic_cell =
+        make_cell(options.compartments_per_segment, synapses_per_cell);
+
+    auto num_domains = global_policy::size();
+    auto domain_id = global_policy::id();
 
     // make a vector for storing all of the cells
-    m.cell_groups = std::vector<cell_group>(opt.cells);
+    id_type ncell_global = options.cells;
+    id_type ncell_local  = ncell_global / num_domains;
+    int remainder = ncell_global - (ncell_local*num_domains);
+    if (domain_id<remainder) {
+        ncell_local++;
+    }
+
+    m.cell_groups = std::vector<cell_group>(ncell_local);
 
     // initialize the cells in parallel
     mc::threading::parallel_for::apply(
-        0, opt.cells,
+        0, ncell_local,
         [&](int i) {
-            // initialize cell
-                mc::util::profiler_enter("setup");
-                    mc::util::profiler_enter("make cell");
+                mc::util::profiler_enter("setup", "cells");
             m.cell_groups[i] = make_lowered_cell(i, basic_cell);
-                    mc::util::profiler_leave();
-                mc::util::profiler_leave();
+                mc::util::profiler_leave(2);
         }
     );
 
@@ -280,48 +297,52 @@ void ring_model(nest::mc::io::options& opt, model& m) {
     //
     m.init_communicator();
 
-    for (auto i=0u; i<(id_type)opt.cells; ++i) {
-        m.communicator.add_connection({
-            i, (i+1)%opt.cells,
-            parameters::synapses::weight, parameters::synapses::delay
-        });
-    }
-
-    m.update_gids();
-}
+        mc::util::profiler_enter("setup", "connections");
 
-void all_to_all_model(nest::mc::io::options& opt, model& m) {
-    //
-    //  make cells
-    //
+    // RNG distributions for connection delays and source cell ids
+    auto weight_distribution = std::exponential_distribution<float>(0.75);
+    auto source_distribution =
+        std::uniform_int_distribution<uint32_t>(0u, options.cells-2);
 
-    // make a basic cell
-    auto basic_cell = make_cell(opt.compartments_per_segment, opt.cells-1);
+    // calculate the weight of synaptic connections, which is chosen so that
+    // the sum of all synaptic weights on a cell is
+    // parameters::synapses::weight_per_cell
+    float weight = parameters::synapses::weight_per_cell / synapses_per_cell;
 
-    // make a vector for storing all of the cells
-    id_type ncell_global = opt.cells;
-    id_type ncell_local  = ncell_global / m.communicator.num_domains();
-    int remainder = ncell_global - (ncell_local*m.communicator.num_domains());
-    if (m.communicator.domain_id()<remainder) {
-        ncell_local++;
+    // loop over each local cell and build the list of synapse connections
+    // that terminate on the cell
+    for (auto lid=0u; lid<ncell_local; ++lid) {
+        auto target = m.communicator.target_gid_from_group_lid(lid);
+        auto gid = m.communicator.group_gid_from_group_lid(lid);
+        auto gen = std::mt19937(gid);  // seed with cell gid for reproducability
+        // add synapses to cell
+        auto i = 0u;
+        auto cells_added = 0u;
+        while (cells_added < synapses_per_cell) {
+            auto source = is_all_to_all ?  i : source_distribution(gen);
+            if (gid!=source) {
+                m.communicator.add_connection({
+                    source, target++, weight,
+                    parameters::synapses::delay + weight_distribution(gen)
+                });
+                cells_added++;
+            }
+            ++i;
+        }
     }
 
-    m.cell_groups = std::vector<cell_group>(ncell_local);
+    m.communicator.construct();
 
-    // initialize the cells in parallel
-    mc::threading::parallel_for::apply(
-        0, ncell_local,
-        [&](int i) {
-                mc::util::profiler_enter("setup", "cells");
-            m.cell_groups[i] = make_lowered_cell(i, basic_cell);
-                mc::util::profiler_leave(2);
-        }
-    );
+    //for (auto con : m.communicator.connections()) std::cout << con << "\n";
+
+    m.update_gids();
 
     //
-    //  network creation
+    //  setup probes
     //
-    m.init_communicator();
+
+        mc::util::profiler_leave();
+        mc::util::profiler_enter("probes");
 
     // monitor soma and dendrite on a few cells
     float sample_dt = 0.1;
@@ -335,30 +356,15 @@ void all_to_all_model(nest::mc::io::options& opt, model& m) {
         auto probe_soma = m.cell_groups[lid].probe_gid_range().first;
         auto probe_dend = probe_soma+1;
 
-        m.cell_groups[lid].add_sampler(m.make_simple_sampler(probe_soma, "vsoma", gid, sample_dt));
-        m.cell_groups[lid].add_sampler(m.make_simple_sampler(probe_dend, "vdend", gid, sample_dt));
-    }
-
-    mc::util::profiler_enter("setup", "connections");
-    // lid is local cell/group id
-    for (auto lid=0u; lid<ncell_local; ++lid) {
-        auto target = m.communicator.target_gid_from_group_lid(lid);
-        auto gid = m.communicator.group_gid_from_group_lid(lid);
-        // tid is global cell/group id
-        for (auto tid=0u; tid<ncell_global; ++tid) {
-            if (gid!=tid) {
-                m.communicator.add_connection({
-                    tid, target++,
-                    parameters::synapses::weight, parameters::synapses::delay
-                });
-            }
-        }
+        m.cell_groups[lid].add_sampler(
+            m.make_simple_sampler(probe_soma, "vsoma", gid, sample_dt)
+        );
+        m.cell_groups[lid].add_sampler(
+            m.make_simple_sampler(probe_dend, "vdend", gid, sample_dt)
+        );
     }
 
-    m.communicator.construct();
-    mc::util::profiler_leave(2);
-
-    m.update_gids();
+        mc::util::profiler_leave(2);
 }
 
 ///////////////////////////////////////
@@ -390,8 +396,8 @@ mc::cell make_cell(int compartments_per_segment, int num_synapses) {
     // add dendrite of length 200 um and diameter 1 um with passive channel
     std::vector<mc::cable_segment*> dendrites;
     dendrites.push_back(cell.add_cable(0, mc::segmentKind::dendrite, 0.5, 0.5, 200));
-    //dendrites.push_back(cell.add_cable(1, mc::segmentKind::dendrite, 0.5, 0.25, 100));
-    //dendrites.push_back(cell.add_cable(1, mc::segmentKind::dendrite, 0.5, 0.25, 100));
+    dendrites.push_back(cell.add_cable(1, mc::segmentKind::dendrite, 0.5, 0.25,100));
+    dendrites.push_back(cell.add_cable(1, mc::segmentKind::dendrite, 0.5, 0.25,100));
 
     for (auto d : dendrites) {
         d->add_mechanism(mc::pas_parameters());
@@ -399,13 +405,14 @@ mc::cell make_cell(int compartments_per_segment, int num_synapses) {
         d->mechanism("membrane").set("r_L", 100);
     }
 
-    // add stimulus
-    //cell.add_stimulus({1,1}, {5., 80., 0.3});
-
-    cell.add_detector({0,0}, 30);
+    cell.add_detector({0,0}, 20);
 
+    auto gen = std::mt19937();
+    auto distribution = std::uniform_real_distribution<float>(0.f, 1.0f);
+    // distribute the synapses at random locations the terminal dendrites in a
+    // round robin manner
     for (auto i=0; i<num_synapses; ++i) {
-        cell.add_synapse({1, 0.5});
+        cell.add_synapse({2+(i%2), distribution(gen)});
     }
 
     // add probes: 
diff --git a/src/cell_group.hpp b/src/cell_group.hpp
index 70bfdeb13123c780744c5875ae125bf631bf6142..e3582b2362c8fe48984a41c21373f3755781f522 100644
--- a/src/cell_group.hpp
+++ b/src/cell_group.hpp
@@ -102,6 +102,9 @@ public:
 
             // integrate cell state
             cell_.advance(tnext - cell_.time());
+            if(!cell_.is_physical_solution()) {
+                std::cerr << "warning: solution out of bounds\n";
+            }
 
                 nest::mc::util::profiler_enter("events");
             // check for new spikes
diff --git a/src/fvm_cell.hpp b/src/fvm_cell.hpp
index 90e7f7be0d373274aba0b4ff28b908465e674cb5..dde7637cea2221aa2e7d519d4a7c6bccdcc9599d 100644
--- a/src/fvm_cell.hpp
+++ b/src/fvm_cell.hpp
@@ -96,7 +96,6 @@ public:
     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_; }
 
@@ -133,6 +132,15 @@ public:
     /// returns voltage at a segment location
     value_type voltage(segment_location loc) const;
 
+    /// flags if solution is physically realistic.
+    /// here we define physically realistic as the voltage being within reasonable bounds.
+    /// use a simple test of the voltage at the soma is reasonable, i.e. in the range
+    ///     v_soma \in (-1000mv, 1000mv)
+    bool is_physical_solution() const {
+        auto v = voltage_[0];
+        return (v>-1000.) && (v<1000.);
+    }
+
     value_type time() const { return t_; }
 
     value_type probe(uint32_t i) const {