From 025bed06b6297a1dd7de32f6a19a3e1d11c52927 Mon Sep 17 00:00:00 2001
From: Ben Cumming <bcumming@cscs.ch>
Date: Thu, 29 Aug 2019 11:41:00 +0200
Subject: [PATCH] Consistent source naming and layout for examples (#848)

Refactor the example code so that all examples have a single C++ source file, and the source file, source path and executable all have the same name.

Fixes  #847.
---
 example/bench/CMakeLists.txt                  |   2 +-
 example/bench/bench.cpp                       | 171 ++++++++++-
 example/bench/parameters.cpp                  |  81 ------
 example/bench/parameters.hpp                  |  43 ---
 example/bench/readme.md                       |   2 +-
 example/bench/recipe.cpp                      |  72 -----
 example/bench/recipe.hpp                      |  22 --
 example/brunel/CMakeLists.txt                 |   8 +-
 .../brunel/{brunel_miniapp.cpp => brunel.cpp} | 265 ++++++++++++++++--
 example/brunel/io.cpp                         | 207 --------------
 example/brunel/io.hpp                         |  56 ----
 example/brunel/readme.md                      |   2 +-
 example/dryrun/dryrun.cpp                     |  71 ++++-
 example/dryrun/parameters.hpp                 |  81 ------
 example/gap_junctions/CMakeLists.txt          |   2 +-
 example/gap_junctions/gap_junctions.cpp       |  58 +++-
 example/gap_junctions/parameters.hpp          |  65 -----
 example/generators/CMakeLists.txt             |   6 +-
 .../{event_gen.cpp => generators.cpp}         |   0
 example/ring/parameters.hpp                   |  81 ------
 example/ring/ring.cpp                         |  76 ++++-
 21 files changed, 622 insertions(+), 749 deletions(-)
 delete mode 100644 example/bench/parameters.cpp
 delete mode 100644 example/bench/parameters.hpp
 delete mode 100644 example/bench/recipe.cpp
 delete mode 100644 example/bench/recipe.hpp
 rename example/brunel/{brunel_miniapp.cpp => brunel.cpp} (53%)
 delete mode 100644 example/brunel/io.cpp
 delete mode 100644 example/brunel/io.hpp
 delete mode 100644 example/dryrun/parameters.hpp
 delete mode 100644 example/gap_junctions/parameters.hpp
 rename example/generators/{event_gen.cpp => generators.cpp} (100%)
 delete mode 100644 example/ring/parameters.hpp

diff --git a/example/bench/CMakeLists.txt b/example/bench/CMakeLists.txt
index aa23ea36..3db3345f 100644
--- a/example/bench/CMakeLists.txt
+++ b/example/bench/CMakeLists.txt
@@ -1,4 +1,4 @@
-add_executable(bench EXCLUDE_FROM_ALL bench.cpp recipe.cpp parameters.cpp)
+add_executable(bench EXCLUDE_FROM_ALL bench.cpp)
 add_dependencies(examples bench)
 
 target_link_libraries(bench PRIVATE arbor arborenv arbor-sup ext-tclap ext-json)
diff --git a/example/bench/bench.cpp b/example/bench/bench.cpp
index af985c66..bd530d1f 100644
--- a/example/bench/bench.cpp
+++ b/example/bench/bench.cpp
@@ -9,6 +9,7 @@
 #include <nlohmann/json.hpp>
 
 #include <arbor/profile/meter_manager.hpp>
+#include <arbor/benchmark_cell.hpp>
 #include <arbor/context.hpp>
 #include <arbor/domain_decomposition.hpp>
 #include <arbor/load_balance.hpp>
@@ -23,14 +24,123 @@
 
 #include <sup/ioutil.hpp>
 #include <sup/json_meter.hpp>
+#include <sup/json_params.hpp>
 
 #ifdef ARB_MPI_ENABLED
 #include <mpi.h>
 #include <arborenv/with_mpi.hpp>
 #endif
 
-#include "parameters.hpp"
-#include "recipe.hpp"
+struct bench_params {
+    struct cell_params {
+        double spike_freq_hz = 10;   // Frequency in hz that cell will generate (poisson) spikes.
+        double realtime_ratio = 0.1; // Integration speed relative to real time, e.g. 10 implies
+                                     // that a cell is integrated 10 times slower than real time.
+    };
+    struct network_params {
+        unsigned fan_in = 5000;      // Number of incoming connections on each cell.
+        double min_delay = 10;       // Used as the delay on all connections.
+    };
+    std::string name = "default";    // Name of the model.
+    unsigned num_cells = 1000;       // Number of cells in model.
+    arb::time_type duration = 100;          // Simulation duration in ms.
+
+    cell_params cell;                // Cell parameters for all cells in model.
+    network_params network;          // Description of the network.
+
+    // Expected simulation performance properties based on model parameters.
+
+    // Time to finish simulation if only cell overheads are counted.
+    double expected_advance_time() const {
+        return cell.realtime_ratio * duration*1e-3 * num_cells;
+    }
+    // Total expected number of spikes generated by simulation.
+    unsigned expected_spikes() const {
+        return num_cells * duration*1e-3 * cell.spike_freq_hz;
+    }
+    // Expected number of spikes generated per min_delay/2 interval.
+    unsigned expected_spikes_per_interval() const {
+        return num_cells * network.min_delay*1e-3/2 * cell.spike_freq_hz;
+    }
+    // Expected number of post-synaptic events delivered over simulation.
+    unsigned expected_events() const {
+        return expected_spikes() * network.fan_in;
+    }
+    // Expected number of post-synaptic events delivered per min_delay/2 interval.
+    unsigned expected_events_per_interval() const {
+        return expected_spikes_per_interval() * network.fan_in;
+    }
+};
+
+bench_params read_options(int argc, char** argv);
+std::ostream& operator<<(std::ostream& o, const bench_params& p);
+
+class bench_recipe: public arb::recipe {
+    bench_params params_;
+
+public:
+    bench_recipe(bench_params p): params_(std::move(p)) {}
+
+    arb::cell_size_type num_cells() const override {
+        return params_.num_cells;
+    }
+
+    arb::util::unique_any get_cell_description(arb::cell_gid_type gid) const override {
+        std::mt19937_64 rng(gid);
+        arb::benchmark_cell cell;
+        cell.realtime_ratio = params_.cell.realtime_ratio;
+
+        // The time_sequence of the cell produces the series of time points at
+        // which it will spike. We use a poisson_schedule with a random sequence
+        // seeded with the gid. In this way, a cell's random stream depends only
+        // on its gid, and will hence give reproducable results when run with
+        // different MPI ranks and threads.
+        cell.time_sequence = arb::poisson_schedule(1e-3*params_.cell.spike_freq_hz, rng);
+        return std::move(cell);
+    }
+
+    arb::cell_kind get_cell_kind(arb::cell_gid_type gid) const override {
+        return arb::cell_kind::benchmark;
+    }
+
+    arb::cell_size_type num_targets(arb::cell_gid_type gid) const override {
+        // Only one target, to which all incoming connections connect.
+        // This could be parameterized, in which case the connections
+        // generated in connections_on should end on random cell-local targets.
+        return 1;
+    }
+
+    arb::cell_size_type num_sources(arb::cell_gid_type gid) const override {
+        return 1;
+    }
+
+    std::vector<arb::cell_connection> connections_on(arb::cell_gid_type gid) const override {
+        const auto n = params_.network.fan_in;
+        std::vector<arb::cell_connection> cons;
+        cons.reserve(n);
+        using rng_type = std::mt19937_64;
+        rng_type rng(gid);
+
+        // Generate n incoming connections on this cell with random sources, where
+        // the source can't equal gid (i.e. no self-connections).
+        // We want a random distribution that will uniformly draw values from the
+        // union of the two ranges: [0, gid-1] AND [gid+1, num_cells-1].
+        // To do this, we draw random values in the range [0, num_cells-2], then
+        // add 1 to values ≥ gid.
+
+        std::uniform_int_distribution<arb::cell_gid_type> dist(0, params_.num_cells-2);
+        for (unsigned i=0; i<n; ++i) {
+            // Draw random source and adjust to avoid self-connections if neccesary.
+            arb::cell_gid_type src = dist(rng);
+            if (src>=gid) ++src;
+            // Note: target is {gid, 0}, i.e. the first (and only) target on the cell.
+            arb::cell_connection con({src, 0}, {gid, 0}, 1.f, params_.network.min_delay);
+            cons.push_back(con);
+        }
+
+        return cons;
+    }
+};
 
 namespace profile = arb::profile;
 
@@ -105,3 +215,60 @@ int main(int argc, char** argv) {
         std::cerr << "exception caught running benchmark miniapp:\n" << e.what() << std::endl;
     }
 }
+
+std::ostream& operator<<(std::ostream& o, const bench_params& p) {
+    o << "benchmark parameters:\n"
+      << "  name:          " << p.name << "\n"
+      << "  num cells:     " << p.num_cells << "\n"
+      << "  duration:      " << p.duration << " ms\n"
+      << "  fan in:        " << p.network.fan_in << " connections/cell\n"
+      << "  min delay:     " << p.network.min_delay << " ms\n"
+      << "  spike freq:    " << p.cell.spike_freq_hz << " Hz\n"
+      << "  cell overhead: " << p.cell.realtime_ratio << " ms to advance 1 ms\n";
+    o << "expected:\n"
+      << "  cell advance: " << p.expected_advance_time() << " s\n"
+      << "  spikes:       " << p.expected_spikes() << "\n"
+      << "  events:       " << p.expected_events() << "\n"
+      << "  spikes:       " << p.expected_spikes_per_interval() << " per interval\n"
+      << "  events:       " << p.expected_events_per_interval()/p.num_cells << " per cell per interval";
+    return o;
+}
+
+bench_params read_options(int argc, char** argv) {
+    using sup::param_from_json;
+
+    bench_params params;
+    if (argc<2) {
+        std::cout << "Using default parameters.\n";
+        return params;
+    }
+    if (argc>2) {
+        throw std::runtime_error("More than command line one option not permitted.");
+    }
+
+    std::string fname = argv[1];
+    std::cout << "Loading parameters from file: " << fname << "\n";
+    std::ifstream f(fname);
+
+    if (!f.good()) {
+        throw std::runtime_error("Unable to open input parameter file: "+fname);
+    }
+
+    nlohmann::json json;
+    json << f;
+
+    param_from_json(params.name, "name", json);
+    param_from_json(params.num_cells, "num-cells", json);
+    param_from_json(params.duration, "duration", json);
+    param_from_json(params.network.min_delay, "min-delay", json);
+    param_from_json(params.network.fan_in, "fan-in", json);
+    param_from_json(params.cell.realtime_ratio, "realtime-ratio", json);
+    param_from_json(params.cell.spike_freq_hz, "spike-frequency", json);
+
+    for (auto it=json.begin(); it!=json.end(); ++it) {
+        std::cout << "  Warning: unused input parameter: \"" << it.key() << "\"\n";
+    }
+    std::cout << "\n";
+
+    return params;
+}
diff --git a/example/bench/parameters.cpp b/example/bench/parameters.cpp
deleted file mode 100644
index d2a42621..00000000
--- a/example/bench/parameters.cpp
+++ /dev/null
@@ -1,81 +0,0 @@
-#include <exception>
-#include <fstream>
-#include <iostream>
-#include <string>
-
-#include <sup/json_params.hpp>
-
-#include "parameters.hpp"
-
-double bench_params::expected_advance_time() const {
-    return cell.realtime_ratio * duration*1e-3 * num_cells;
-}
-unsigned bench_params::expected_spikes() const {
-    return num_cells * duration*1e-3 * cell.spike_freq_hz;
-}
-unsigned bench_params::expected_spikes_per_interval() const {
-    return num_cells * network.min_delay*1e-3/2 * cell.spike_freq_hz;
-}
-unsigned bench_params::expected_events() const {
-    return expected_spikes() * network.fan_in;
-}
-unsigned bench_params::expected_events_per_interval() const {
-    return expected_spikes_per_interval() * network.fan_in;
-}
-
-std::ostream& operator<<(std::ostream& o, const bench_params& p) {
-    o << "benchmark parameters:\n"
-      << "  name:          " << p.name << "\n"
-      << "  num cells:     " << p.num_cells << "\n"
-      << "  duration:      " << p.duration << " ms\n"
-      << "  fan in:        " << p.network.fan_in << " connections/cell\n"
-      << "  min delay:     " << p.network.min_delay << " ms\n"
-      << "  spike freq:    " << p.cell.spike_freq_hz << " Hz\n"
-      << "  cell overhead: " << p.cell.realtime_ratio << " ms to advance 1 ms\n";
-    o << "expected:\n"
-      << "  cell advance: " << p.expected_advance_time() << " s\n"
-      << "  spikes:       " << p.expected_spikes() << "\n"
-      << "  events:       " << p.expected_events() << "\n"
-      << "  spikes:       " << p.expected_spikes_per_interval() << " per interval\n"
-      << "  events:       " << p.expected_events_per_interval()/p.num_cells << " per cell per interval";
-    return o;
-}
-
-bench_params read_options(int argc, char** argv) {
-    using sup::param_from_json;
-
-    bench_params params;
-    if (argc<2) {
-        std::cout << "Using default parameters.\n";
-        return params;
-    }
-    if (argc>2) {
-        throw std::runtime_error("More than command line one option not permitted.");
-    }
-
-    std::string fname = argv[1];
-    std::cout << "Loading parameters from file: " << fname << "\n";
-    std::ifstream f(fname);
-
-    if (!f.good()) {
-        throw std::runtime_error("Unable to open input parameter file: "+fname);
-    }
-
-    nlohmann::json json;
-    json << f;
-
-    param_from_json(params.name, "name", json);
-    param_from_json(params.num_cells, "num-cells", json);
-    param_from_json(params.duration, "duration", json);
-    param_from_json(params.network.min_delay, "min-delay", json);
-    param_from_json(params.network.fan_in, "fan-in", json);
-    param_from_json(params.cell.realtime_ratio, "realtime-ratio", json);
-    param_from_json(params.cell.spike_freq_hz, "spike-frequency", json);
-
-    for (auto it=json.begin(); it!=json.end(); ++it) {
-        std::cout << "  Warning: unused input parameter: \"" << it.key() << "\"\n";
-    }
-    std::cout << "\n";
-
-    return params;
-}
diff --git a/example/bench/parameters.hpp b/example/bench/parameters.hpp
deleted file mode 100644
index 9f6fdc9f..00000000
--- a/example/bench/parameters.hpp
+++ /dev/null
@@ -1,43 +0,0 @@
-#pragma once
-
-#include <ostream>
-#include <string>
-
-#include <arbor/common_types.hpp>
-
-using arb::time_type;
-
-struct bench_params {
-    struct cell_params {
-        double spike_freq_hz = 10;   // Frequency in hz that cell will generate (poisson) spikes.
-        double realtime_ratio = 0.1; // Integration speed relative to real time, e.g. 10 implies
-                                     // that a cell is integrated 10 times slower than real time.
-    };
-    struct network_params {
-        unsigned fan_in = 5000;      // Number of incoming connections on each cell.
-        double min_delay = 10;       // Used as the delay on all connections.
-    };
-    std::string name = "default";    // Name of the model.
-    unsigned num_cells = 1000;       // Number of cells in model.
-    time_type duration = 100;          // Simulation duration in ms.
-
-    cell_params cell;                // Cell parameters for all cells in model.
-    network_params network;          // Description of the network.
-
-    // Expected simulation performance properties based on model parameters.
-
-    // Time to finish simulation if only cell overheads are counted.
-    double expected_advance_time() const;
-    // Total expected number of spikes generated by simulation.
-    unsigned expected_spikes() const;
-    // Expected number of spikes generated per min_delay/2 interval.
-    unsigned expected_spikes_per_interval() const;
-    // Expected number of post-synaptic events delivered over simulation.
-    unsigned expected_events() const;
-    // Expected number of post-synaptic events delivered per min_delay/2 interval.
-    unsigned expected_events_per_interval() const;
-};
-
-bench_params read_options(int argc, char** argv);
-
-std::ostream& operator<<(std::ostream& o, const bench_params& p);
diff --git a/example/bench/readme.md b/example/bench/readme.md
index c6dbc2f9..b862132e 100644
--- a/example/bench/readme.md
+++ b/example/bench/readme.md
@@ -21,7 +21,7 @@ pattern are varied, without having to tweak parameters on a model that uses
 The model can be configured using a json configuration file:
 
 ```
-./bench.exe params.json
+./bench params.json
 ```
 
 An example parameter file is:
diff --git a/example/bench/recipe.cpp b/example/bench/recipe.cpp
deleted file mode 100644
index aa9c94b8..00000000
--- a/example/bench/recipe.cpp
+++ /dev/null
@@ -1,72 +0,0 @@
-#include <random>
-
-#include <arbor/benchmark_cell.hpp>
-#include <arbor/common_types.hpp>
-#include <arbor/schedule.hpp>
-
-#include "recipe.hpp"
-
-using arb::cell_gid_type;
-using arb::cell_size_type;
-using arb::cell_kind;
-
-cell_size_type bench_recipe::num_cells() const {
-    return params_.num_cells;
-}
-
-arb::util::unique_any bench_recipe::get_cell_description(cell_gid_type gid) const {
-    std::mt19937_64 rng(gid);
-    arb::benchmark_cell cell;
-    cell.realtime_ratio = params_.cell.realtime_ratio;
-
-    // The time_sequence of the cell produces the series of time points at
-    // which it will spike. We use a poisson_schedule with a random sequence
-    // seeded with the gid. In this way, a cell's random stream depends only
-    // on its gid, and will hence give reproducable results when run with
-    // different MPI ranks and threads.
-    cell.time_sequence = arb::poisson_schedule(1e-3*params_.cell.spike_freq_hz, rng);
-    return std::move(cell);
-}
-
-cell_kind bench_recipe::get_cell_kind(cell_gid_type gid) const {
-    return cell_kind::benchmark;
-}
-
-std::vector<arb::cell_connection> bench_recipe::connections_on(cell_gid_type gid) const {
-    const auto n = params_.network.fan_in;
-    std::vector<arb::cell_connection> cons;
-    cons.reserve(n);
-    using rng_type = std::mt19937_64;
-    rng_type rng(gid);
-
-    // Generate n incoming connections on this cell with random sources, where
-    // the source can't equal gid (i.e. no self-connections).
-    // We want a random distribution that will uniformly draw values from the
-    // union of the two ranges: [0, gid-1] AND [gid+1, num_cells-1].
-    // To do this, we draw random values in the range [0, num_cells-2], then
-    // add 1 to values ≥ gid.
-
-    std::uniform_int_distribution<cell_gid_type> dist(0, params_.num_cells-2);
-    for (unsigned i=0; i<n; ++i) {
-        // Draw random source and adjust to avoid self-connections if neccesary.
-        cell_gid_type src = dist(rng);
-        if (src>=gid) ++src;
-        // Note: target is {gid, 0}, i.e. the first (and only) target on the cell.
-        arb::cell_connection con({src, 0}, {gid, 0}, 1.f, params_.network.min_delay);
-        cons.push_back(con);
-    }
-
-    return cons;
-}
-
-cell_size_type bench_recipe::num_targets(cell_gid_type gid) const {
-    // Only one target, to which all incoming connections connect.
-    // This could be parameterized, in which case the connections
-    // generated in connections_on should end on random cell-local targets.
-    return 1;
-}
-
-// one spike source per cell
-cell_size_type bench_recipe::num_sources(cell_gid_type gid) const {
-    return 1;
-}
diff --git a/example/bench/recipe.hpp b/example/bench/recipe.hpp
deleted file mode 100644
index e95f6077..00000000
--- a/example/bench/recipe.hpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#pragma once
-
-#include <arbor/common_types.hpp>
-#include <arbor/recipe.hpp>
-#include <arbor/util/unique_any.hpp>
-
-#include "parameters.hpp"
-
-class bench_recipe: public arb::recipe {
-private:
-    bench_params params_;
-
-public:
-    bench_recipe(bench_params p): params_(std::move(p)) {}
-    arb::cell_size_type num_cells() const override;
-    arb::util::unique_any get_cell_description(arb::cell_gid_type gid) const override;
-    arb::cell_kind get_cell_kind(arb::cell_gid_type gid) const override;
-    arb::cell_size_type num_targets(arb::cell_gid_type gid) const override;
-    arb::cell_size_type num_sources(arb::cell_gid_type gid) const override;
-    std::vector<arb::cell_connection> connections_on(arb::cell_gid_type) const override;
-};
-
diff --git a/example/brunel/CMakeLists.txt b/example/brunel/CMakeLists.txt
index 99b26743..79b68394 100644
--- a/example/brunel/CMakeLists.txt
+++ b/example/brunel/CMakeLists.txt
@@ -1,6 +1,4 @@
-add_executable(brunel-miniapp EXCLUDE_FROM_ALL
-    brunel_miniapp.cpp
-    io.cpp)
-add_dependencies(examples brunel-miniapp)
+add_executable(brunel EXCLUDE_FROM_ALL brunel.cpp)
+add_dependencies(examples brunel)
 
-target_link_libraries(brunel-miniapp PRIVATE arbor arborenv arbor-sup ext-tclap)
+target_link_libraries(brunel PRIVATE arbor arborenv arbor-sup ext-tclap)
diff --git a/example/brunel/brunel_miniapp.cpp b/example/brunel/brunel.cpp
similarity index 53%
rename from example/brunel/brunel_miniapp.cpp
rename to example/brunel/brunel.cpp
index 131f2730..22a49674 100644
--- a/example/brunel/brunel_miniapp.cpp
+++ b/example/brunel/brunel.cpp
@@ -7,6 +7,8 @@
 #include <set>
 #include <vector>
 
+#include <tclap/CmdLine.h>
+
 #include <arbor/context.hpp>
 #include <arbor/common_types.hpp>
 #include <arbor/domain_decomposition.hpp>
@@ -17,6 +19,7 @@
 #include <arbor/profile/profiler.hpp>
 #include <arbor/recipe.hpp>
 #include <arbor/simulation.hpp>
+#include <arbor/util/optional.hpp>
 #include <arbor/version.hpp>
 
 #include <arborenv/concurrency.hpp>
@@ -32,26 +35,46 @@
 #include <arborenv/with_mpi.hpp>
 #endif
 
-#include "io.hpp"
-
 using namespace arb;
 
+// Holds the options for a simulation run.
+// Default constructor gives default options.
+struct cl_options {
+    // Cell parameters:
+    uint32_t nexc = 400;
+    uint32_t ninh = 100;
+    uint32_t next = 40;
+    double syn_per_cell_prop = 0.05;
+    float weight = 1.2;
+    float delay = 0.1;
+    float rel_inh_strength = 1;
+    double poiss_lambda = 1;
+
+    // Simulation running parameters:
+    double tfinal = 100.;
+    double dt = 1;
+    uint32_t group_size = 10;
+    uint32_t seed = 42;
+
+    // Parameters for spike output.
+    bool spike_file_output = false;
+
+    // Turn on/off profiling output for all ranks.
+    bool profile_only_zero = false;
+
+    // Be more verbose with informational messages.
+    bool verbose = false;
+};
+
+std::ostream& operator<<(std::ostream& o, const cl_options& opt);
+
+cl_options read_options(int argc, char** argv);
+
 void banner(const context& ctx);
 
 // Samples m unique values in interval [start, end) - gid.
 // We exclude gid because we don't want self-loops.
-std::vector<cell_gid_type> sample_subset(cell_gid_type gid, cell_gid_type start, cell_gid_type end,  unsigned m) {
-    std::set<cell_gid_type> s;
-    std::mt19937 gen(gid + 42);
-    std::uniform_int_distribution<cell_gid_type> dis(start, end - 1);
-    while (s.size() < m) {
-        auto val = dis(gen);
-        if (val != gid) {
-            s.insert(val);
-        }
-    }
-    return {s.begin(), s.end()};
-}
+std::vector<cell_gid_type> sample_subset(cell_gid_type gid, cell_gid_type start, cell_gid_type end,  unsigned m);
 
 /*
    A Brunel network consists of nexc excitatory LIF neurons and ninh inhibitory LIF neurons.
@@ -214,7 +237,7 @@ int main(int argc, char** argv) {
         meters.start(context);
 
         // read parameters
-        io::cl_options options = io::read_options(argc, argv);
+        cl_options options = read_options(argc, argv);
 
         std::fstream spike_out;
         if (options.spike_file_output && root) {
@@ -296,16 +319,12 @@ int main(int argc, char** argv) {
             fid << std::setw(1) << sup::to_json(report) << "\n";
         }
     }
-    catch (io::usage_error& e) {
-        // only print usage/startup errors on master
+    catch (std::exception& e) {
+        // only print errors on master
         std::cerr << sup::mask_stream(root);
         std::cerr << e.what() << "\n";
         return 1;
     }
-    catch (std::exception& e) {
-        std::cerr << e.what() << "\n";
-        return 2;
-    }
     return 0;
 }
 
@@ -318,3 +337,207 @@ void banner(const context& ctx) {
     std::cout << "  - gpus        : " << (arb::has_gpu(ctx)? "yes": "no") << "\n";
     std::cout << "==========================================\n";
 }
+
+std::vector<cell_gid_type> sample_subset(cell_gid_type gid, cell_gid_type start, cell_gid_type end,  unsigned m) {
+    std::set<cell_gid_type> s;
+    std::mt19937 gen(gid + 42);
+    std::uniform_int_distribution<cell_gid_type> dis(start, end - 1);
+    while (s.size() < m) {
+        auto val = dis(gen);
+        if (val != gid) {
+            s.insert(val);
+        }
+    }
+    return {s.begin(), s.end()};
+}
+
+// Let TCLAP understand value arguments that are of an optional type.
+namespace TCLAP {
+    template <typename V>
+    struct ArgTraits<arb::util::optional<V>> {
+        using ValueCategory = ValueLike;
+    };
+} // namespace TCLAP
+
+namespace arb {
+    namespace util {
+        // Using static here because we do not want external linkage for this operator.
+        template <typename V>
+        static std::istream& operator>>(std::istream& I, optional<V>& v) {
+            V u;
+            if (I >> u) {
+                v = u;
+            }
+            return I;
+        }
+    }
+}
+
+// Override annoying parameters listed back-to-front behaviour.
+//
+// TCLAP argument creation _prepends_ its arguments to the internal
+// list (_argList), where standard options --help etc. are already
+// pre-inserted.
+//
+// reorder_arguments() reverses the arguments to restore ordering,
+// and moves the standard options to the end.
+class CustomCmdLine: public TCLAP::CmdLine {
+public:
+    CustomCmdLine(const std::string &message, const std::string &version = "none"):
+    TCLAP::CmdLine(message, ' ', version, true)
+    {}
+
+    void reorder_arguments() {
+        _argList.reverse();
+        for (auto opt: {"help", "version", "ignore_rest"}) {
+            auto i = std::find_if(
+                                  _argList.begin(), _argList.end(),
+                                  [&opt](TCLAP::Arg* a) { return a->getName()==opt; });
+
+            if (i!=_argList.end()) {
+                auto a = *i;
+                _argList.erase(i);
+                _argList.push_back(a);
+            }
+        }
+    }
+};
+
+// Update an option value from command line argument if set.
+template <
+    typename T,
+    typename Arg,
+    typename = std::enable_if_t<std::is_base_of<TCLAP::Arg, Arg>::value>
+>
+static void update_option(T& opt, Arg& arg) {
+    if (arg.isSet()) {
+        opt = arg.getValue();
+    }
+}
+
+// Read options from (optional) json file and command line arguments.
+cl_options read_options(int argc, char** argv) {
+    cl_options options;
+
+    // Parse command line arguments.
+    try {
+        cl_options defopts;
+
+        CustomCmdLine cmd("nest brunel miniapp harness", "0.1");
+
+        TCLAP::ValueArg<uint32_t> nexc_arg
+            ("n", "n-excitatory", "total number of cells in the excitatory population",
+             false, defopts.nexc, "integer", cmd);
+
+        TCLAP::ValueArg<uint32_t> ninh_arg
+            ("m", "n-inhibitory", "total number of cells in the inhibitory population",
+             false, defopts.ninh, "integer", cmd);
+
+        TCLAP::ValueArg<uint32_t> next_arg
+            ("e", "n-external", "total number of incoming Poisson (external) connections per cell.",
+             false, defopts.ninh, "integer", cmd);
+
+        TCLAP::ValueArg<double> syn_prop_arg
+            ("p", "in-degree-prop", "the proportion of connections both the excitatory and inhibitory populations that each neuron receives",
+             false, defopts.syn_per_cell_prop, "double", cmd);
+
+        TCLAP::ValueArg<float> weight_arg
+            ("w", "weight", "the weight of all excitatory connections",
+             false, defopts.weight, "float", cmd);
+
+        TCLAP::ValueArg<float> delay_arg
+            ("d", "delay", "the delay of all connections",
+             false, defopts.delay, "float", cmd);
+
+        TCLAP::ValueArg<float> rel_inh_strength_arg
+            ("g", "rel-inh-w", "relative strength of inhibitory synapses with respect to the excitatory ones",
+             false, defopts.rel_inh_strength, "float", cmd);
+
+        TCLAP::ValueArg<double> poiss_lambda_arg
+            ("l", "lambda", "Expected number of spikes from a single poisson cell per ms",
+             false, defopts.poiss_lambda, "double", cmd);
+
+        TCLAP::ValueArg<double> tfinal_arg
+            ("t", "tfinal", "length of the simulation period [ms]",
+             false, defopts.tfinal, "time", cmd);
+
+        TCLAP::ValueArg<double> dt_arg
+            ("s", "delta-t", "simulation time step [ms] (this parameter is ignored)",
+             false, defopts.dt, "time", cmd);
+
+        TCLAP::ValueArg<uint32_t> group_size_arg
+            ("G", "group-size", "number of cells per cell group",
+             false, defopts.group_size, "integer", cmd);
+
+        TCLAP::ValueArg<uint32_t> seed_arg
+            ("S", "seed", "seed for poisson spike generators",
+             false, defopts.seed, "integer", cmd);
+
+        TCLAP::SwitchArg spike_output_arg
+            ("f","spike-file-output","save spikes to file", cmd, false);
+
+        TCLAP::SwitchArg profile_only_zero_arg
+            ("z", "profile-only-zero", "Only output profile information for rank 0",
+             cmd, false);
+
+        TCLAP::SwitchArg verbose_arg
+            ("v", "verbose", "Present more verbose information to stdout", cmd, false);
+
+        cmd.reorder_arguments();
+        cmd.parse(argc, argv);
+
+        // Handle verbosity separately from other options: it is not considered part
+        // of the saved option state.
+        options.verbose = verbose_arg.getValue();
+        update_option(options.nexc, nexc_arg);
+        update_option(options.ninh, ninh_arg);
+        update_option(options.next, next_arg);
+        update_option(options.syn_per_cell_prop, syn_prop_arg);
+        update_option(options.weight, weight_arg);
+        update_option(options.delay, delay_arg);
+        update_option(options.rel_inh_strength, rel_inh_strength_arg);
+        update_option(options.poiss_lambda, poiss_lambda_arg);
+        update_option(options.tfinal, tfinal_arg);
+        update_option(options.dt, dt_arg);
+        update_option(options.group_size, group_size_arg);
+        update_option(options.seed, seed_arg);
+        update_option(options.spike_file_output, spike_output_arg);
+        update_option(options.profile_only_zero, profile_only_zero_arg);
+
+        if (options.group_size < 1) {
+            throw std::runtime_error("minimum of one cell per group");
+        }
+
+        if (options.rel_inh_strength <= 0 || options.rel_inh_strength > 1) {
+            throw std::runtime_error("relative strength of inhibitory connections must be in the interval (0, 1].");
+        }
+    }
+    catch (TCLAP::ArgException& e) {
+        throw std::runtime_error("error parsing command line argument "+e.argId()+": "+e.error());
+    }
+
+    // If verbose output requested, emit option summary.
+    if (options.verbose) {
+        std::cout << options << "\n";
+    }
+
+    return options;
+}
+
+std::ostream& operator<<(std::ostream& o, const cl_options& options) {
+    o << "simulation options:\n";
+    o << "  excitatory cells                                           : " << options.nexc << "\n";
+    o << "  inhibitory cells                                           : " << options.ninh << "\n";
+    o << "  Poisson connections per cell                               : " << options.next << "\n";
+    o << "  proportion of synapses/cell from each population           : " << options.syn_per_cell_prop << "\n";
+    o << "  weight of excitatory synapses                              : " << options.weight << "\n";
+    o << "  relative strength of inhibitory synapses                   : " << options.rel_inh_strength << "\n";
+    o << "  delay of all synapses                                      : " << options.delay << "\n";
+    o << "  expected number of spikes from a single poisson cell per ms: " << options.poiss_lambda << "\n";
+    o << "\n";
+    o << "  simulation time                                            : " << options.tfinal << "\n";
+    o << "  dt                                                         : " << options.dt << "\n";
+    o << "  group size                                                 : " << options.group_size << "\n";
+    o << "  seed                                                       : " << options.seed << "\n";
+    return o;
+}
diff --git a/example/brunel/io.cpp b/example/brunel/io.cpp
deleted file mode 100644
index 72b6ca81..00000000
--- a/example/brunel/io.cpp
+++ /dev/null
@@ -1,207 +0,0 @@
-#include <algorithm>
-#include <exception>
-#include <fstream>
-#include <iostream>
-#include <memory>
-#include <sstream>
-#include <string>
-#include <type_traits>
-
-#include <tclap/CmdLine.h>
-
-#include <arbor/util/optional.hpp>
-
-#include "io.hpp"
-
-// Let TCLAP understand value arguments that are of an optional type.
-namespace TCLAP {
-    template <typename V>
-    struct ArgTraits<arb::util::optional<V>> {
-        using ValueCategory = ValueLike;
-    };
-} // namespace TCLAP
-
-namespace arb {
-    namespace util {
-        // Using static here because we do not want external linkage for this operator.
-        template <typename V>
-        static std::istream& operator>>(std::istream& I, optional<V>& v) {
-            V u;
-            if (I >> u) {
-                v = u;
-            }
-            return I;
-        }
-    }
-}
-
-namespace io {
-    // Override annoying parameters listed back-to-front behaviour.
-    //
-    // TCLAP argument creation _prepends_ its arguments to the internal
-    // list (_argList), where standard options --help etc. are already
-    // pre-inserted.
-    //
-    // reorder_arguments() reverses the arguments to restore ordering,
-    // and moves the standard options to the end.
-    class CustomCmdLine: public TCLAP::CmdLine {
-    public:
-        CustomCmdLine(const std::string &message, const std::string &version = "none"):
-        TCLAP::CmdLine(message, ' ', version, true)
-        {}
-
-        void reorder_arguments() {
-            _argList.reverse();
-            for (auto opt: {"help", "version", "ignore_rest"}) {
-                auto i = std::find_if(
-                                      _argList.begin(), _argList.end(),
-                                      [&opt](TCLAP::Arg* a) { return a->getName()==opt; });
-
-                if (i!=_argList.end()) {
-                    auto a = *i;
-                    _argList.erase(i);
-                    _argList.push_back(a);
-                }
-            }
-        }
-    };
-
-    // Update an option value from command line argument if set.
-    template <
-        typename T,
-        typename Arg,
-        typename = std::enable_if_t<std::is_base_of<TCLAP::Arg, Arg>::value>
-    >
-    static void update_option(T& opt, Arg& arg) {
-        if (arg.isSet()) {
-            opt = arg.getValue();
-        }
-    }
-
-    // Read options from (optional) json file and command line arguments.
-    cl_options read_options(int argc, char** argv) {
-        cl_options options;
-
-        // Parse command line arguments.
-        try {
-            cl_options defopts;
-
-            CustomCmdLine cmd("nest brunel miniapp harness", "0.1");
-
-            TCLAP::ValueArg<uint32_t> nexc_arg
-                ("n", "n-excitatory", "total number of cells in the excitatory population",
-                 false, defopts.nexc, "integer", cmd);
-
-            TCLAP::ValueArg<uint32_t> ninh_arg
-                ("m", "n-inhibitory", "total number of cells in the inhibitory population",
-                 false, defopts.ninh, "integer", cmd);
-
-            TCLAP::ValueArg<uint32_t> next_arg
-                ("e", "n-external", "total number of incoming Poisson (external) connections per cell.",
-                 false, defopts.ninh, "integer", cmd);
-
-            TCLAP::ValueArg<double> syn_prop_arg
-                ("p", "in-degree-prop", "the proportion of connections both the excitatory and inhibitory populations that each neuron receives",
-                 false, defopts.syn_per_cell_prop, "double", cmd);
-
-            TCLAP::ValueArg<float> weight_arg
-                ("w", "weight", "the weight of all excitatory connections",
-                 false, defopts.weight, "float", cmd);
-
-            TCLAP::ValueArg<float> delay_arg
-                ("d", "delay", "the delay of all connections",
-                 false, defopts.delay, "float", cmd);
-
-            TCLAP::ValueArg<float> rel_inh_strength_arg
-                ("g", "rel-inh-w", "relative strength of inhibitory synapses with respect to the excitatory ones",
-                 false, defopts.rel_inh_strength, "float", cmd);
-
-            TCLAP::ValueArg<double> poiss_lambda_arg
-                ("l", "lambda", "Expected number of spikes from a single poisson cell per ms",
-                 false, defopts.poiss_lambda, "double", cmd);
-
-            TCLAP::ValueArg<double> tfinal_arg
-                ("t", "tfinal", "length of the simulation period [ms]",
-                 false, defopts.tfinal, "time", cmd);
-
-            TCLAP::ValueArg<double> dt_arg
-                ("s", "delta-t", "simulation time step [ms] (this parameter is ignored)",
-                 false, defopts.dt, "time", cmd);
-
-            TCLAP::ValueArg<uint32_t> group_size_arg
-                ("G", "group-size", "number of cells per cell group",
-                 false, defopts.group_size, "integer", cmd);
-
-            TCLAP::ValueArg<uint32_t> seed_arg
-                ("S", "seed", "seed for poisson spike generators",
-                 false, defopts.seed, "integer", cmd);
-
-            TCLAP::SwitchArg spike_output_arg
-                ("f","spike-file-output","save spikes to file", cmd, false);
-
-            TCLAP::SwitchArg profile_only_zero_arg
-                ("z", "profile-only-zero", "Only output profile information for rank 0",
-                 cmd, false);
-
-            TCLAP::SwitchArg verbose_arg
-                ("v", "verbose", "Present more verbose information to stdout", cmd, false);
-
-            cmd.reorder_arguments();
-            cmd.parse(argc, argv);
-
-            // Handle verbosity separately from other options: it is not considered part
-            // of the saved option state.
-            options.verbose = verbose_arg.getValue();
-            update_option(options.nexc, nexc_arg);
-            update_option(options.ninh, ninh_arg);
-            update_option(options.next, next_arg);
-            update_option(options.syn_per_cell_prop, syn_prop_arg);
-            update_option(options.weight, weight_arg);
-            update_option(options.delay, delay_arg);
-            update_option(options.rel_inh_strength, rel_inh_strength_arg);
-            update_option(options.poiss_lambda, poiss_lambda_arg);
-            update_option(options.tfinal, tfinal_arg);
-            update_option(options.dt, dt_arg);
-            update_option(options.group_size, group_size_arg);
-            update_option(options.seed, seed_arg);
-            update_option(options.spike_file_output, spike_output_arg);
-            update_option(options.profile_only_zero, profile_only_zero_arg);
-
-            if (options.group_size < 1) {
-                throw usage_error("minimum of one cell per group");
-            }
-
-            if (options.rel_inh_strength <= 0 || options.rel_inh_strength > 1) {
-                throw usage_error("relative strength of inhibitory connections must be in the interval (0, 1].");
-            }
-        }
-        catch (TCLAP::ArgException& e) {
-            throw usage_error("error parsing command line argument "+e.argId()+": "+e.error());
-        }
-
-        // If verbose output requested, emit option summary.
-        if (options.verbose) {
-            std::cout << options << "\n";
-        }
-
-        return options;
-    }
-
-    std::ostream& operator<<(std::ostream& o, const cl_options& options) {
-        o << "simulation options:\n";
-        o << "  excitatory cells                                           : " << options.nexc << "\n";
-        o << "  inhibitory cells                                           : " << options.ninh << "\n";
-        o << "  Poisson connections per cell                               : " << options.next << "\n";
-        o << "  proportion of synapses/cell from each population           : " << options.syn_per_cell_prop << "\n";
-        o << "  weight of excitatory synapses                              : " << options.weight << "\n";
-        o << "  relative strength of inhibitory synapses                   : " << options.rel_inh_strength << "\n";
-        o << "  delay of all synapses                                      : " << options.delay << "\n";
-        o << "  expected number of spikes from a single poisson cell per ms: " << options.poiss_lambda << "\n";
-        o << "\n";
-        o << "  simulation time                                            : " << options.tfinal << "\n";
-        o << "  dt                                                         : " << options.dt << "\n";
-        o << "  group size                                                 : " << options.group_size << "\n";
-        o << "  seed                                                       : " << options.seed << "\n";
-        return o;
-    }
-} // namespace io
diff --git a/example/brunel/io.hpp b/example/brunel/io.hpp
deleted file mode 100644
index 28c76bdb..00000000
--- a/example/brunel/io.hpp
+++ /dev/null
@@ -1,56 +0,0 @@
-#pragma once
-
-#include <stdexcept>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include <arbor/common_types.hpp>
-#include <arbor/util/optional.hpp>
-
-namespace io {
-    // Holds the options for a simulation run.
-    // Default constructor gives default options.
-    struct cl_options {
-        // Cell parameters:
-        uint32_t nexc = 400;
-        uint32_t ninh = 100;
-        uint32_t next = 40;
-        double syn_per_cell_prop = 0.05;
-        float weight = 1.2;
-        float delay = 0.1;
-        float rel_inh_strength = 1;
-        double poiss_lambda = 1;
-
-        // Simulation running parameters:
-        double tfinal = 100.;
-        double dt = 1;
-        uint32_t group_size = 10;
-        uint32_t seed = 42;
-
-        // Parameters for spike output.
-        bool spike_file_output = false;
-
-        // Turn on/off profiling output for all ranks.
-        bool profile_only_zero = false;
-
-        // Be more verbose with informational messages.
-        bool verbose = false;
-    };
-
-    class usage_error: public std::runtime_error {
-    public:
-        template <typename S>
-        usage_error(S&& whatmsg): std::runtime_error(std::forward<S>(whatmsg)) {}
-    };
-
-    class model_description_error: public std::runtime_error {
-    public:
-        template <typename S>
-        model_description_error(S&& whatmsg): std::runtime_error(std::forward<S>(whatmsg)) {}
-    };
-
-    std::ostream& operator<<(std::ostream& o, const cl_options& opt);
-
-    cl_options read_options(int argc, char** argv);
-} // namespace io
diff --git a/example/brunel/readme.md b/example/brunel/readme.md
index 46b7fc52..8168b6fe 100644
--- a/example/brunel/readme.md
+++ b/example/brunel/readme.md
@@ -31,5 +31,5 @@ The parameters that can be passed as command-line arguments are the following:
 For example, we could run the miniapp as follows:
 
 ```
-./example/brunel_miniapp.exe -n 400 -m 100 -e 20 -p 0.1 -w 1.2 -d 1 -g 0.5 -l 5 -t 100 -s 1 -G 50 -S 123 -f
+./brunel -n 400 -m 100 -e 20 -p 0.1 -w 1.2 -d 1 -g 0.5 -l 5 -t 100 -s 1 -G 50 -S 123 -f
 ```
diff --git a/example/dryrun/dryrun.cpp b/example/dryrun/dryrun.cpp
index b9e5619e..42b4ca7e 100644
--- a/example/dryrun/dryrun.cpp
+++ b/example/dryrun/dryrun.cpp
@@ -24,14 +24,38 @@
 
 #include <sup/ioutil.hpp>
 #include <sup/json_meter.hpp>
-
-#include "parameters.hpp"
+#include <sup/json_params.hpp>
 
 #ifdef ARB_MPI_ENABLED
 #include <mpi.h>
 #include <arborenv/with_mpi.hpp>
 #endif
 
+// Parameters used to generate the random cell morphologies.
+struct cell_parameters {
+    //  Maximum number of levels in the cell (not including the soma)
+    unsigned max_depth = 5;
+
+    // The following parameters are described as ranges.
+    // The first value is at the soma, and the last value is used on the last level.
+    // Values at levels in between are found by linear interpolation.
+    std::array<double,2> branch_probs = {1.0, 0.5}; //  Probability of a branch occuring.
+    std::array<unsigned,2> compartments = {20, 2};  //  Compartment count on a branch.
+    std::array<double,2> lengths = {200, 20};       //  Length of branch in μm.
+};
+
+struct run_params {
+    std::string name = "default";
+    bool dry_run = false;
+    unsigned num_cells_per_rank = 10;
+    unsigned num_ranks = 1;
+    double min_delay = 10;
+    double duration = 100;
+    cell_parameters cell;
+};
+
+run_params read_options(int argc, char** argv);
+
 using arb::cell_gid_type;
 using arb::cell_lid_type;
 using arb::cell_size_type;
@@ -360,3 +384,46 @@ arb::cable_cell branch_cell(arb::cell_gid_type gid, const cell_parameters& param
     return cell;
 }
 
+run_params read_options(int argc, char** argv) {
+    using sup::param_from_json;
+
+    run_params params;
+    if (argc<2) {
+        std::cout << "Using default parameters.\n";
+        return params;
+    }
+    if (argc>2) {
+        throw std::runtime_error("More than command line one option not permitted.");
+    }
+
+    std::string fname = argv[1];
+    std::cout << "Loading parameters from file: " << fname << "\n";
+    std::ifstream f(fname);
+
+    if (!f.good()) {
+        throw std::runtime_error("Unable to open input parameter file: "+fname);
+    }
+
+    nlohmann::json json;
+    json << f;
+
+    param_from_json(params.name, "name", json);
+    param_from_json(params.dry_run, "dry-run", json);
+    param_from_json(params.num_cells_per_rank, "num-cells-per-rank", json);
+    param_from_json(params.num_ranks, "num-ranks", json);
+    param_from_json(params.duration, "duration", json);
+    param_from_json(params.min_delay, "min-delay", json);
+    param_from_json(params.cell.max_depth, "depth", json);
+    param_from_json(params.cell.branch_probs, "branch-probs", json);
+    param_from_json(params.cell.compartments, "compartments", json);
+    param_from_json(params.cell.lengths, "lengths", json);
+
+    if (!json.empty()) {
+        for (auto it=json.begin(); it!=json.end(); ++it) {
+            std::cout << "  Warning: unused input parameter: \"" << it.key() << "\"\n";
+        }
+        std::cout << "\n";
+    }
+
+    return params;
+}
diff --git a/example/dryrun/parameters.hpp b/example/dryrun/parameters.hpp
deleted file mode 100644
index e355fcf8..00000000
--- a/example/dryrun/parameters.hpp
+++ /dev/null
@@ -1,81 +0,0 @@
-#include <iostream>
-
-#include <array>
-#include <cmath>
-#include <fstream>
-#include <random>
-
-#include <arbor/cable_cell.hpp>
-
-#include <sup/json_params.hpp>
-
-// Parameters used to generate the random cell morphologies.
-struct cell_parameters {
-    cell_parameters() = default;
-
-    //  Maximum number of levels in the cell (not including the soma)
-    unsigned max_depth = 5;
-
-    // The following parameters are described as ranges.
-    // The first value is at the soma, and the last value is used on the last level.
-    // Values at levels in between are found by linear interpolation.
-    std::array<double,2> branch_probs = {1.0, 0.5}; //  Probability of a branch occuring.
-    std::array<unsigned,2> compartments = {20, 2};  //  Compartment count on a branch.
-    std::array<double,2> lengths = {200, 20};       //  Length of branch in μm.
-};
-
-struct run_params {
-    run_params() = default;
-
-    std::string name = "default";
-    bool dry_run = false;
-    unsigned num_cells_per_rank = 10;
-    unsigned num_ranks = 1;
-    double min_delay = 10;
-    double duration = 100;
-    cell_parameters cell;
-};
-
-run_params read_options(int argc, char** argv) {
-    using sup::param_from_json;
-
-    run_params params;
-    if (argc<2) {
-        std::cout << "Using default parameters.\n";
-        return params;
-    }
-    if (argc>2) {
-        throw std::runtime_error("More than command line one option not permitted.");
-    }
-
-    std::string fname = argv[1];
-    std::cout << "Loading parameters from file: " << fname << "\n";
-    std::ifstream f(fname);
-
-    if (!f.good()) {
-        throw std::runtime_error("Unable to open input parameter file: "+fname);
-    }
-
-    nlohmann::json json;
-    json << f;
-
-    param_from_json(params.name, "name", json);
-    param_from_json(params.dry_run, "dry-run", json);
-    param_from_json(params.num_cells_per_rank, "num-cells-per-rank", json);
-    param_from_json(params.num_ranks, "num-ranks", json);
-    param_from_json(params.duration, "duration", json);
-    param_from_json(params.min_delay, "min-delay", json);
-    param_from_json(params.cell.max_depth, "depth", json);
-    param_from_json(params.cell.branch_probs, "branch-probs", json);
-    param_from_json(params.cell.compartments, "compartments", json);
-    param_from_json(params.cell.lengths, "lengths", json);
-
-    if (!json.empty()) {
-        for (auto it=json.begin(); it!=json.end(); ++it) {
-            std::cout << "  Warning: unused input parameter: \"" << it.key() << "\"\n";
-        }
-        std::cout << "\n";
-    }
-
-    return params;
-}
diff --git a/example/gap_junctions/CMakeLists.txt b/example/gap_junctions/CMakeLists.txt
index 60a68d61..abd78ebe 100644
--- a/example/gap_junctions/CMakeLists.txt
+++ b/example/gap_junctions/CMakeLists.txt
@@ -1,4 +1,4 @@
-add_executable(gap_junctions EXCLUDE_FROM_ALL gap_junctions.cpp parameters.hpp)
+add_executable(gap_junctions EXCLUDE_FROM_ALL gap_junctions.cpp)
 add_dependencies(examples gap_junctions)
 
 target_link_libraries(gap_junctions PRIVATE arbor arborenv arbor-sup ext-json)
diff --git a/example/gap_junctions/gap_junctions.cpp b/example/gap_junctions/gap_junctions.cpp
index 4a633a77..f3e1e26e 100644
--- a/example/gap_junctions/gap_junctions.cpp
+++ b/example/gap_junctions/gap_junctions.cpp
@@ -10,6 +10,7 @@
 #include <nlohmann/json.hpp>
 
 #include <arbor/assert_macro.hpp>
+#include <arbor/cable_cell.hpp>
 #include <arbor/common_types.hpp>
 #include <arbor/context.hpp>
 #include <arbor/load_balance.hpp>
@@ -26,14 +27,26 @@
 
 #include <sup/ioutil.hpp>
 #include <sup/json_meter.hpp>
-
-#include "parameters.hpp"
+#include <sup/json_params.hpp>
 
 #ifdef ARB_MPI_ENABLED
 #include <mpi.h>
 #include <arborenv/with_mpi.hpp>
 #endif
 
+struct gap_params {
+    std::string name = "default";
+    unsigned n_cables = 3;
+    unsigned n_cells_per_cable = 5;
+    double stim_duration = 30;
+    double event_min_delay = 10;
+    double event_weight = 0.05;
+    double sim_duration = 100;
+    bool print_all = true;
+};
+
+gap_params read_options(int argc, char** argv);
+
 using arb::cell_gid_type;
 using arb::cell_lid_type;
 using arb::cell_size_type;
@@ -371,3 +384,44 @@ arb::cable_cell gj_cell(cell_gid_type gid, unsigned ncell, double stim_duration)
     return cell;
 }
 
+gap_params read_options(int argc, char** argv) {
+    using sup::param_from_json;
+
+    gap_params params;
+    if (argc<2) {
+        std::cout << "Using default parameters.\n";
+        return params;
+    }
+    if (argc>2) {
+        throw std::runtime_error("More than command line one option not permitted.");
+    }
+
+    std::string fname = argv[1];
+    std::cout << "Loading parameters from file: " << fname << "\n";
+    std::ifstream f(fname);
+
+    if (!f.good()) {
+        throw std::runtime_error("Unable to open input parameter file: "+fname);
+    }
+
+    nlohmann::json json;
+    json << f;
+
+    param_from_json(params.name, "name", json);
+    param_from_json(params.n_cables, "n-cables", json);
+    param_from_json(params.n_cells_per_cable, "n-cells-per-cable", json);
+    param_from_json(params.stim_duration, "stim-duration", json);
+    param_from_json(params.event_min_delay, "event-min-delay", json);
+    param_from_json(params.event_weight, "event-weight", json);
+    param_from_json(params.sim_duration, "sim-duration", json);
+    param_from_json(params.print_all, "print-all", json);
+
+    if (!json.empty()) {
+        for (auto it=json.begin(); it!=json.end(); ++it) {
+            std::cout << "  Warning: unused input parameter: \"" << it.key() << "\"\n";
+        }
+        std::cout << "\n";
+    }
+
+    return params;
+}
diff --git a/example/gap_junctions/parameters.hpp b/example/gap_junctions/parameters.hpp
deleted file mode 100644
index f68ddfd2..00000000
--- a/example/gap_junctions/parameters.hpp
+++ /dev/null
@@ -1,65 +0,0 @@
-#include <iostream>
-
-#include <array>
-#include <cmath>
-#include <fstream>
-#include <random>
-
-#include <arbor/cable_cell.hpp>
-
-#include <sup/json_params.hpp>
-
-struct gap_params {
-    gap_params() = default;
-
-    std::string name = "default";
-    unsigned n_cables = 3;
-    unsigned n_cells_per_cable = 5;
-    double stim_duration = 30;
-    double event_min_delay = 10;
-    double event_weight = 0.05;
-    double sim_duration = 100;
-    bool print_all = true;
-};
-
-gap_params read_options(int argc, char** argv) {
-    using sup::param_from_json;
-
-    gap_params params;
-    if (argc<2) {
-        std::cout << "Using default parameters.\n";
-        return params;
-    }
-    if (argc>2) {
-        throw std::runtime_error("More than command line one option not permitted.");
-    }
-
-    std::string fname = argv[1];
-    std::cout << "Loading parameters from file: " << fname << "\n";
-    std::ifstream f(fname);
-
-    if (!f.good()) {
-        throw std::runtime_error("Unable to open input parameter file: "+fname);
-    }
-
-    nlohmann::json json;
-    json << f;
-
-    param_from_json(params.name, "name", json);
-    param_from_json(params.n_cables, "n-cables", json);
-    param_from_json(params.n_cells_per_cable, "n-cells-per-cable", json);
-    param_from_json(params.stim_duration, "stim-duration", json);
-    param_from_json(params.event_min_delay, "event-min-delay", json);
-    param_from_json(params.event_weight, "event-weight", json);
-    param_from_json(params.sim_duration, "sim-duration", json);
-    param_from_json(params.print_all, "print-all", json);
-
-    if (!json.empty()) {
-        for (auto it=json.begin(); it!=json.end(); ++it) {
-            std::cout << "  Warning: unused input parameter: \"" << it.key() << "\"\n";
-        }
-        std::cout << "\n";
-    }
-
-    return params;
-}
diff --git a/example/generators/CMakeLists.txt b/example/generators/CMakeLists.txt
index 5b917bad..7bfaca9f 100644
--- a/example/generators/CMakeLists.txt
+++ b/example/generators/CMakeLists.txt
@@ -1,4 +1,4 @@
-add_executable(event-gen EXCLUDE_FROM_ALL event_gen.cpp)
-add_dependencies(examples event-gen)
+add_executable(generators EXCLUDE_FROM_ALL generators.cpp)
+add_dependencies(examples generators)
 
-target_link_libraries(event-gen PRIVATE arbor arbor-sup ext-json)
+target_link_libraries(generators PRIVATE arbor arbor-sup ext-json)
diff --git a/example/generators/event_gen.cpp b/example/generators/generators.cpp
similarity index 100%
rename from example/generators/event_gen.cpp
rename to example/generators/generators.cpp
diff --git a/example/ring/parameters.hpp b/example/ring/parameters.hpp
deleted file mode 100644
index 6343cb3c..00000000
--- a/example/ring/parameters.hpp
+++ /dev/null
@@ -1,81 +0,0 @@
-#include <iostream>
-
-#include <array>
-#include <cmath>
-#include <fstream>
-#include <random>
-
-#include <arbor/cable_cell.hpp>
-
-#include <sup/json_params.hpp>
-
-// Parameters used to generate the random cell morphologies.
-struct cell_parameters {
-    cell_parameters() = default;
-
-    //  Maximum number of levels in the cell (not including the soma)
-    unsigned max_depth = 5;
-
-    // The following parameters are described as ranges.
-    // The first value is at the soma, and the last value is used on the last level.
-    // Values at levels in between are found by linear interpolation.
-    std::array<double,2> branch_probs = {1.0, 0.5}; //  Probability of a branch occuring.
-    std::array<unsigned,2> compartments = {20, 2};  //  Compartment count on a branch.
-    std::array<double,2> lengths = {200, 20};       //  Length of branch in μm.
-
-    // The number of synapses per cell.
-    unsigned synapses = 1;
-};
-
-struct ring_params {
-    ring_params() = default;
-
-    std::string name = "default";
-    unsigned num_cells = 10;
-    double min_delay = 10;
-    double duration = 100;
-    cell_parameters cell;
-};
-
-ring_params read_options(int argc, char** argv) {
-    using sup::param_from_json;
-
-    ring_params params;
-    if (argc<2) {
-        std::cout << "Using default parameters.\n";
-        return params;
-    }
-    if (argc>2) {
-        throw std::runtime_error("More than command line one option not permitted.");
-    }
-
-    std::string fname = argv[1];
-    std::cout << "Loading parameters from file: " << fname << "\n";
-    std::ifstream f(fname);
-
-    if (!f.good()) {
-        throw std::runtime_error("Unable to open input parameter file: "+fname);
-    }
-
-    nlohmann::json json;
-    json << f;
-
-    param_from_json(params.name, "name", json);
-    param_from_json(params.num_cells, "num-cells", json);
-    param_from_json(params.duration, "duration", json);
-    param_from_json(params.min_delay, "min-delay", json);
-    param_from_json(params.cell.max_depth, "depth", json);
-    param_from_json(params.cell.branch_probs, "branch-probs", json);
-    param_from_json(params.cell.compartments, "compartments", json);
-    param_from_json(params.cell.lengths, "lengths", json);
-    param_from_json(params.cell.synapses, "synapses", json);
-
-    if (!json.empty()) {
-        for (auto it=json.begin(); it!=json.end(); ++it) {
-            std::cout << "  Warning: unused input parameter: \"" << it.key() << "\"\n";
-        }
-        std::cout << "\n";
-    }
-
-    return params;
-}
diff --git a/example/ring/ring.cpp b/example/ring/ring.cpp
index 55c4cea2..6f426032 100644
--- a/example/ring/ring.cpp
+++ b/example/ring/ring.cpp
@@ -11,6 +11,7 @@
 
 #include <arbor/assert_macro.hpp>
 #include <arbor/common_types.hpp>
+#include <arbor/cable_cell.hpp>
 #include <arbor/context.hpp>
 #include <arbor/load_balance.hpp>
 #include <arbor/cable_cell.hpp>
@@ -26,14 +27,42 @@
 
 #include <sup/ioutil.hpp>
 #include <sup/json_meter.hpp>
-
-#include "parameters.hpp"
+#include <sup/json_params.hpp>
 
 #ifdef ARB_MPI_ENABLED
 #include <mpi.h>
 #include <arborenv/with_mpi.hpp>
 #endif
 
+// Parameters used to generate the random cell morphologies.
+struct cell_parameters {
+    cell_parameters() = default;
+
+    //  Maximum number of levels in the cell (not including the soma)
+    unsigned max_depth = 5;
+
+    // The following parameters are described as ranges.
+    // The first value is at the soma, and the last value is used on the last level.
+    // Values at levels in between are found by linear interpolation.
+    std::array<double,2> branch_probs = {1.0, 0.5}; //  Probability of a branch occuring.
+    std::array<unsigned,2> compartments = {20, 2};  //  Compartment count on a branch.
+    std::array<double,2> lengths = {200, 20};       //  Length of branch in μm.
+
+    // The number of synapses per cell.
+    unsigned synapses = 1;
+};
+
+struct ring_params {
+    ring_params() = default;
+
+    std::string name = "default";
+    unsigned num_cells = 10;
+    double min_delay = 10;
+    double duration = 100;
+    cell_parameters cell;
+};
+
+ring_params read_options(int argc, char** argv);
 using arb::cell_gid_type;
 using arb::cell_lid_type;
 using arb::cell_size_type;
@@ -364,3 +393,46 @@ arb::cable_cell branch_cell(arb::cell_gid_type gid, const cell_parameters& param
     return cell;
 }
 
+ring_params read_options(int argc, char** argv) {
+    using sup::param_from_json;
+
+    ring_params params;
+    if (argc<2) {
+        std::cout << "Using default parameters.\n";
+        return params;
+    }
+    if (argc>2) {
+        throw std::runtime_error("More than command line one option not permitted.");
+    }
+
+    std::string fname = argv[1];
+    std::cout << "Loading parameters from file: " << fname << "\n";
+    std::ifstream f(fname);
+
+    if (!f.good()) {
+        throw std::runtime_error("Unable to open input parameter file: "+fname);
+    }
+
+    nlohmann::json json;
+    json << f;
+
+    param_from_json(params.name, "name", json);
+    param_from_json(params.num_cells, "num-cells", json);
+    param_from_json(params.duration, "duration", json);
+    param_from_json(params.min_delay, "min-delay", json);
+    param_from_json(params.cell.max_depth, "depth", json);
+    param_from_json(params.cell.branch_probs, "branch-probs", json);
+    param_from_json(params.cell.compartments, "compartments", json);
+    param_from_json(params.cell.lengths, "lengths", json);
+    param_from_json(params.cell.synapses, "synapses", json);
+
+    if (!json.empty()) {
+        for (auto it=json.begin(); it!=json.end(); ++it) {
+            std::cout << "  Warning: unused input parameter: \"" << it.key() << "\"\n";
+        }
+        std::cout << "\n";
+    }
+
+    return params;
+}
+
-- 
GitLab