diff --git a/miniapp/io.cpp b/miniapp/io.cpp
index 6664cf47e4ddbf1fcd33cd4589553a29cb967255..ac7573ccec32a4036f49c515b80047ccb602a1e0 100644
--- a/miniapp/io.cpp
+++ b/miniapp/io.cpp
@@ -1,13 +1,37 @@
-#include <fstream>
 #include <exception>
+#include <fstream>
+#include <istream>
 
 #include <tclap/CmdLine.h>
 #include <json/src/json.hpp>
 
+#include <util/optional.hpp>
+
 #include "io.hpp"
 
+// Let TCLAP understand value arguments that are of an optional type.
+
+template <typename V>
+struct TCLAP::ArgTraits<nest::mc::util::optional<V>> {
+    using ValueCategory = ValueLike;
+};
+
 namespace nest {
 namespace mc {
+
+// Using static here because we do not want external linkage for this operator
+
+namespace util {
+    template <typename V>
+    static std::istream& operator>>(std::istream& I, optional<V>& v) {
+        V u;
+        if (I >> u) {
+            v = u;
+        }
+        return I;
+    }
+}
+
 namespace io {
 
 /// read simulation options from json file with name fname
@@ -17,7 +41,8 @@ namespace io {
 cl_options read_options(int argc, char** argv) {
 
     // set default options
-    const cl_options defopts{"", 1000, 500, "expsyn", 100, 100., 0.025, false};
+    const cl_options defopts{"", 1000, 500, "expsyn", 100, 100., 0.025, false,
+                             false, 1.0, "trace_", util::nothing};
 
     cl_options options;
     // parse command line arguments
@@ -47,6 +72,17 @@ cl_options read_options(int argc, char** argv) {
             false, defopts.dt, "positive real number", cmd);
         TCLAP::SwitchArg all_to_all_arg(
             "m","alltoall","all to all network", cmd, false);
+        TCLAP::ValueArg<double> probe_ratio_arg(
+            "p", "probe-ratio", "proportion of cells to probe",
+            false, defopts.probe_ratio, "real number in [0,1]", cmd);
+        TCLAP::SwitchArg probe_soma_only_arg(
+            "X", "probe-soma-only", "only probe cell somas, not dendrites", cmd, false);
+        TCLAP::ValueArg<std::string> trace_prefix_arg(
+            "P", "trace-prefix", "write traces to files with this prefix",
+            false, defopts.trace_prefix, "stringr", cmd);
+        TCLAP::ValueArg<util::optional<unsigned>> trace_max_gid_arg(
+            "T", "trace-max-gid", "only trace probes on cells up to this gid",
+            false, defopts.trace_max_gid, "unisgned integer", cmd);
 
         cmd.parse(argc, argv);
 
@@ -58,6 +94,10 @@ cl_options read_options(int argc, char** argv) {
         options.tfinal = tfinal_arg.getValue();
         options.dt = dt_arg.getValue();
         options.all_to_all = all_to_all_arg.getValue();
+        options.probe_ratio = probe_ratio_arg.getValue();
+        options.probe_soma_only = probe_soma_only_arg.getValue();
+        options.trace_prefix = trace_prefix_arg.getValue();
+        options.trace_max_gid = trace_max_gid_arg.getValue();
     }
     // catch any exceptions in command line handling
     catch (TCLAP::ArgException& e) {
@@ -100,6 +140,14 @@ std::ostream& operator<<(std::ostream& o, const cl_options& options) {
     o << "  simulation time      : " << options.tfinal << "\n";
     o << "  dt                   : " << options.dt << "\n";
     o << "  all to all network   : " << (options.all_to_all ? "yes" : "no") << "\n";
+    o << "  probe ratio          : " << options.probe_ratio << "\n";
+    o << "  probe soma only      : " << (options.probe_soma_only ? "yes" : "no") << "\n";
+    o << "  trace prefix         : " << options.trace_prefix << "\n";
+    o << "  trace max gid        : ";
+    if (options.trace_max_gid) {
+       o << *options.trace_max_gid;
+    }
+    o << "\n";
     o << "  input file name      : " << options.ifname << "\n";
 
     return o;
diff --git a/miniapp/io.hpp b/miniapp/io.hpp
index dab584fe2a132f289bb9a2f1ae02711aa29ef5a4..71af61810670d7a1f226d3eec2dabfd2f38ff20e 100644
--- a/miniapp/io.hpp
+++ b/miniapp/io.hpp
@@ -6,6 +6,8 @@
 #include <stdexcept>
 #include <utility>
 
+#include <util/optional.hpp>
+
 namespace nest {
 namespace mc {
 namespace io {
@@ -20,6 +22,10 @@ struct cl_options {
     double tfinal;
     double dt;
     bool all_to_all;
+    bool probe_soma_only;
+    double probe_ratio;
+    std::string trace_prefix;
+    util::optional<unsigned> trace_max_gid;
 };
 
 class usage_error: public std::runtime_error {
diff --git a/miniapp/miniapp.cpp b/miniapp/miniapp.cpp
index 9b26ff86ef37f829667161afca27a129791de3ed..3e9d4dd227efbb90823b99db2fedd00cc94e4f81 100644
--- a/miniapp/miniapp.cpp
+++ b/miniapp/miniapp.cpp
@@ -32,7 +32,7 @@ using model_type = model<lowered_cell>;
 using sample_trace_type = sample_trace<model_type::time_type, model_type::value_type>;
 
 void banner();
-std::unique_ptr<recipe> make_recipe(const io::cl_options&);
+std::unique_ptr<recipe> make_recipe(const io::cl_options&, const probe_distribution&);
 std::unique_ptr<sample_trace_type> make_trace(cell_member_type probe_id, probe_spec probe);
 std::pair<cell_gid_type, cell_gid_type> distribute_cells(cell_size_type ncells);
 
@@ -53,7 +53,12 @@ int main(int argc, char** argv) {
                   << std::ceil(options.tfinal / options.dt) << " steps of "
                   << options.dt << " ms" << std::endl;
 
-        auto recipe = make_recipe(options);
+        // determine what to attach probes to
+        probe_distribution pdist;
+        pdist.proportion = options.probe_ratio;
+        pdist.all_segments = !options.probe_soma_only;
+
+        auto recipe = make_recipe(options, pdist);
         auto cell_range = distribute_cells(recipe->num_cells());
 
         // build model from recipe
@@ -69,6 +74,10 @@ int main(int argc, char** argv) {
         std::vector<std::unique_ptr<sample_trace_type>> traces;
         const model_type::time_type sample_dt = 0.1;
         for (auto probe: m.probes()) {
+            if (options.trace_max_gid && probe.id.gid>*options.trace_max_gid) {
+                continue;
+            }
+
             traces.push_back(make_trace(probe.id, probe.probe));
             m.attach_sampler(probe.id, make_trace_sampler(traces.back().get(),sample_dt));
         }
@@ -81,7 +90,7 @@ int main(int argc, char** argv) {
 
         // save traces
         for (const auto& trace: traces) {
-            write_trace_json(*trace.get());
+            write_trace_json(*trace.get(), options.trace_prefix);
         }
     }
     catch (io::usage_error& e) {
@@ -119,7 +128,7 @@ void banner() {
     std::cout << "====================\n";
 }
 
-std::unique_ptr<recipe> make_recipe(const io::cl_options& options) {
+std::unique_ptr<recipe> make_recipe(const io::cl_options& options, const probe_distribution& pdist) {
     basic_recipe_param p;
 
     p.num_compartments = options.compartments_per_segment;
@@ -127,17 +136,17 @@ std::unique_ptr<recipe> make_recipe(const io::cl_options& options) {
     p.synapse_type = options.syn_type;
 
     if (options.all_to_all) {
-        return make_basic_kgraph_recipe(options.cells, p);
+        return make_basic_kgraph_recipe(options.cells, p, pdist);
     }
     else {
-        return make_basic_rgraph_recipe(options.cells, p);
+        return make_basic_rgraph_recipe(options.cells, p, pdist);
     }
 }
 
 std::unique_ptr<sample_trace_type> make_trace(cell_member_type probe_id, probe_spec probe) {
     std::string name = "";
     std::string units = "";
-    
+
     switch (probe.kind) {
     case probeKind::membrane_voltage:
         name = "v";
diff --git a/miniapp/miniapp_recipes.cpp b/miniapp/miniapp_recipes.cpp
index 22c59bd851a049e448784b9cfdd635d7e7a96970..742951dff6df83cb13c2f13af6e2d1ace2ae97eb 100644
--- a/miniapp/miniapp_recipes.cpp
+++ b/miniapp/miniapp_recipes.cpp
@@ -76,13 +76,15 @@ public:
         EXPECTS(cell.detectors().size()==cc.num_sources);
 
         // add probes
-        unsigned n_probe_segs = pdist_.all_segments? basic_cell_segments: 1u;
-        for (unsigned i = 0; i<n_probe_segs; ++i) {
-            if (pdist_.membrane_voltage) {
-                cell.add_probe({{i, i? 0.5: 0.0}, mc::probeKind::membrane_voltage});
-            }
-            if (pdist_.membrane_current) {
-                cell.add_probe({{i, i? 0.5: 0.0}, mc::probeKind::membrane_current});
+        if (cc.num_probes) {
+            unsigned n_probe_segs = pdist_.all_segments? basic_cell_segments: 1u;
+            for (unsigned i = 0; i<n_probe_segs; ++i) {
+                if (pdist_.membrane_voltage) {
+                    cell.add_probe({{i, i? 0.5: 0.0}, mc::probeKind::membrane_voltage});
+                }
+                if (pdist_.membrane_current) {
+                    cell.add_probe({{i, i? 0.5: 0.0}, mc::probeKind::membrane_current});
+                }
             }
         }
         EXPECTS(cell.probes().size()==cc.num_probes);