diff --git a/miniapp/io.cpp b/miniapp/io.cpp index f3cffdeb2cc7c842bbc30be8156ce0e97a15b5ac..66a8bdecb1b35d1b400b6aec8708b4f649a77be8 100644 --- a/miniapp/io.cpp +++ b/miniapp/io.cpp @@ -1,10 +1,13 @@ +#include <algorithm> #include <exception> #include <fstream> #include <istream> +#include <type_traits> #include <tclap/CmdLine.h> #include <json/src/json.hpp> +#include <util/meta.hpp> #include <util/optional.hpp> #include "io.hpp" @@ -21,9 +24,8 @@ namespace TCLAP { namespace nest { namespace mc { -// Using static here because we do not want external linkage for this operator - 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; @@ -36,18 +38,84 @@ namespace util { namespace io { -/// 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 +// 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 = util::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(); + } +} + +// Update an option value from json object if key present. +template <typename T> +static void update_option(T& opt, const nlohmann::json& j, const std::string& key) { + if (j.count(key)) { + opt = j[key]; + } +} + +// --- special case for string due to ambiguous overloading in json library. +static void update_option(std::string& opt, const nlohmann::json& j, const std::string& key) { + if (j.count(key)) { + opt = j[key].get<std::string>(); + } +} + +// --- special case for optional values. +template <typename T> +static void update_option(util::optional<T>& opt, const nlohmann::json& j, const std::string& key) { + if (j.count(key)) { + auto value = j[key]; + if (value.is_null()) { + opt = util::nothing; + } + else { + opt = value; + } + } +} + +// Read options from (optional) json file and command line arguments. cl_options read_options(int argc, char** argv) { - // set default options - // TODO: the declaration of this defopts is not realistic if we have allot - // more options. We should use a named scheme or import the defopts from - // a valid json file - const cl_options defopts{"", 1000, 500, "expsyn", 100, 100., 0.025, false, - false, 1.0, "trace_", util::nothing, + // Default options: + const cl_options defopts{1000, 500, "expsyn", 100, 100., 0.025, false, + + false, 1.0, "trace_", util::nothing, // spike_output_parameters: false, // no spike output @@ -55,83 +123,136 @@ cl_options read_options(int argc, char** argv) { true, // Overwrite outputfile if exists "./", // output path "spikes", // file name - "gdf" // file extention + "gdf" // file extention }; cl_options options; - // parse command line arguments + std::string save_file = ""; + + // Parse command line arguments. try { - TCLAP::CmdLine cmd("mod2c performance benchmark harness", ' ', "0.1"); + CustomCmdLine cmd("nest mc miniapp harness", "0.1"); + TCLAP::ValueArg<std::string> ifile_arg( + "i", "ifile", + "read parameters from json-formatted file <file name>", + false, "","file name", cmd); + TCLAP::ValueArg<std::string> ofile_arg( + "o", "ofile", + "save parameters to json-formatted file <file name>", + false, "","file name", cmd); TCLAP::ValueArg<uint32_t> ncells_arg( "n", "ncells", "total number of cells in the model", - false, defopts.cells, "non negative integer", cmd); + false, defopts.cells, "integer", cmd); TCLAP::ValueArg<uint32_t> nsynapses_arg( "s", "nsynapses", "number of synapses per cell", - false, defopts.synapses_per_cell, "non negative integer", cmd); + false, defopts.synapses_per_cell, "integer", cmd); TCLAP::ValueArg<std::string> syntype_arg( - "S", "syntype", "type of synapse (expsyn or exp2syn)", - false, defopts.syn_type, "synapse type", cmd); + "S", "syntype", "specify synapse type: expsyn or exp2syn", + false, defopts.syn_type, "string", cmd); TCLAP::ValueArg<uint32_t> ncompartments_arg( "c", "ncompartments", "number of compartments per segment", - false, defopts.compartments_per_segment, "non negative integer", cmd); - TCLAP::ValueArg<std::string> ifile_arg( - "i", "ifile", "json file with model parameters", - false, "","file name string", cmd); + false, defopts.compartments_per_segment, "integer", cmd); TCLAP::ValueArg<double> tfinal_arg( - "t", "tfinal", "time to simulate in ms", - false, defopts.tfinal, "positive real number", cmd); + "t", "tfinal", "run simulation to <time> ms", + false, defopts.tfinal, "time", cmd); TCLAP::ValueArg<double> dt_arg( - "d", "dt", "time step size in ms", - false, defopts.dt, "positive real number", cmd); + "d", "dt", "set simulation time step to <time> ms", + false, defopts.dt, "time", 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); + "p", "probe-ratio", "proportion between 0 and 1 of cells to probe", + false, defopts.probe_ratio, "proportion", 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", + "P", "prefix", "write traces to files with prefix <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); + "T", "trace-max-gid", "only trace probes on cells up to and including <gid>", + false, defopts.trace_max_gid, "gid", cmd); + cmd.reorder_arguments(); cmd.parse(argc, argv); - options.cells = ncells_arg.getValue(); - options.synapses_per_cell = nsynapses_arg.getValue(); - options.syn_type = syntype_arg.getValue(); - options.compartments_per_segment = ncompartments_arg.getValue(); - options.ifname = ifile_arg.getValue(); - 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(); + options = defopts; + + std::string ifile_name = ifile_arg.getValue(); + if (ifile_name != "") { + // Read parameters from specified JSON file first, to allow + // overriding arguments on the command line. + std::ifstream fid(ifile_name); + if (fid) { + try { + nlohmann::json fopts; + fid >> fopts; + + update_option(options.cells, fopts, "cells"); + update_option(options.synapses_per_cell, fopts, "synapses"); + update_option(options.syn_type, fopts, "syn_type"); + update_option(options.compartments_per_segment, fopts, "compartments"); + update_option(options.dt, fopts, "dt"); + update_option(options.tfinal, fopts, "tfinal"); + update_option(options.all_to_all, fopts, "all_to_all"); + update_option(options.probe_ratio, fopts, "probe_ratio"); + update_option(options.probe_soma_only, fopts, "probe_soma_only"); + update_option(options.trace_prefix, fopts, "trace_prefix"); + update_option(options.trace_max_gid, fopts, "trace_max_gid"); + } + catch (std::exception& e) { + throw model_description_error( + "unable to parse parameters in "+ifile_name+": "+e.what()); + } + } + else { + throw usage_error("unable to open model parameter file "+ifile_name); + } + } + + update_option(options.cells, ncells_arg); + update_option(options.synapses_per_cell, nsynapses_arg); + update_option(options.syn_type, syntype_arg); + update_option(options.compartments_per_segment, ncompartments_arg); + update_option(options.tfinal, tfinal_arg); + update_option(options.dt, dt_arg); + update_option(options.all_to_all, all_to_all_arg); + update_option(options.probe_ratio, probe_ratio_arg); + update_option(options.probe_soma_only, probe_soma_only_arg); + update_option(options.trace_prefix, trace_prefix_arg); + update_option(options.trace_max_gid, trace_max_gid_arg); + + save_file = ofile_arg.getValue(); } - // catch any exceptions in command line handling catch (TCLAP::ArgException& e) { throw usage_error("error parsing command line argument "+e.argId()+": "+e.error()); } - if (options.ifname != "") { - std::ifstream fid(options.ifname); + // Save option values if requested. + if (save_file != "") { + std::ofstream fid(save_file); if (fid) { - // 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"]; - options.dt = fopts["dt"]; - options.tfinal = fopts["tfinal"]; - options.all_to_all = fopts["all_to_all"]; + nlohmann::json fopts; + + fopts["cells"] = options.cells; + fopts["synapses"] = options.synapses_per_cell; + fopts["syn_type"] = options.syn_type; + fopts["compartments"] = options.compartments_per_segment; + fopts["dt"] = options.dt; + fopts["tfinal"] = options.tfinal; + fopts["all_to_all"] = options.all_to_all; + fopts["probe_ratio"] = options.probe_ratio; + fopts["probe_soma_only"] = options.probe_soma_only; + fopts["trace_prefix"] = options.trace_prefix; + if (options.trace_max_gid) { + fopts["trace_max_gid"] = options.trace_max_gid.get(); + } + else { + fopts["trace_max_gid"] = nullptr; + } + + fid << std::setw(3) << fopts << "\n"; // Parameters for spike output options.spike_file_output = fopts["spike_file_output"]; @@ -145,14 +266,13 @@ cl_options read_options(int argc, char** argv) { } catch (std::exception& e) { throw model_description_error( - "unable to parse parameters in "+options.ifname+": "+e.what()); + "unable to save parameters in "+save_file+": "+e.what()); } } else { - throw usage_error("unable to open model parameter file "+options.ifname); + throw usage_error("unable to write to model parameter file "+save_file); } } - return options; } @@ -172,7 +292,6 @@ std::ostream& operator<<(std::ostream& o, const cl_options& options) { 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 52cca594358d2543b696d90f39b42355237e187f..9de6299f2a582dccda0913fb520cf133d2546bc4 100644 --- a/miniapp/io.hpp +++ b/miniapp/io.hpp @@ -14,7 +14,6 @@ namespace io { // holds the options for a simulation run struct cl_options { - std::string ifname; uint32_t cells; uint32_t synapses_per_cell; std::string syn_type; diff --git a/miniapp/miniapp.cpp b/miniapp/miniapp.cpp index 24ea92483e85c7e184e08726c10fe7e11d248169..195bfa55813bc47893e0f022cd0a42cabbe57b4c 100644 --- a/miniapp/miniapp.cpp +++ b/miniapp/miniapp.cpp @@ -70,7 +70,7 @@ int main(int argc, char** argv) { model_type m(*recipe, cell_range.first, cell_range.second); // File output is depending on the input arguments - std::unique_ptr<file_export_type> file_exporter; + std::unique_ptr<file_export_type> file_exporter; if (!options.spike_file_output) { // TODO: use the no_function if PR:77 m.set_global_spike_callback(file_export_type::do_nothing); @@ -79,22 +79,24 @@ int main(int argc, char** argv) { else { // The exporter is the same for both global and local output // just registered as a different callback - file_exporter = + file_exporter = util::make_unique<file_export_type>( - options.file_name, options.output_path, + options.file_name, options.output_path, options.file_extention, options.over_write); if (options.single_file_per_rank) { m.set_global_spike_callback(file_export_type::do_nothing); m.set_local_spike_callback( - [&](const std::vector<spike_type>& spikes) { - file_exporter->do_export(spikes); }); + [&](const std::vector<spike_type>& spikes) { + file_exporter->do_export(spikes); + }); } else { m.set_global_spike_callback( [&](const std::vector<spike_type>& spikes) { - file_exporter->do_export(spikes); }); - m.set_local_spike_callback(file_export_type::do_nothing); + file_exporter->do_export(spikes); + }); + m.set_local_spike_callback(file_export_type::do_nothing); } } @@ -189,7 +191,7 @@ std::unique_ptr<sample_trace_type> make_trace(cell_member_type probe_id, probe_s case probeKind::membrane_current: name = "i"; units = "mA/cm²"; - break; + break; default: ; } name += probe.location.segment? "dend" : "soma"; diff --git a/src/communication/communicator.hpp b/src/communication/communicator.hpp index 72659352ca8f8140aa8f2fd70ce745cbbe6dde40..50ce644c2bc62904625fd74c68fe7a98de059395 100644 --- a/src/communication/communicator.hpp +++ b/src/communication/communicator.hpp @@ -93,7 +93,7 @@ public: /// group as a result of the global spike exchange. std::vector<event_queue> exchange(const std::vector<spike_type>& local_spikes, std::function<void(const std::vector<spike_type>&)> global_export_callback) - { + { // global all-to-all to gather a local copy of the global spike list on each node. auto global_spikes = communication_policy_.gather_spikes( local_spikes ); num_spikes_ += global_spikes.size(); diff --git a/src/util/nop.hpp b/src/util/nop.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6ec568b9d5fe9bfe4904b23ea6babcdf8b4ca84e --- /dev/null +++ b/src/util/nop.hpp @@ -0,0 +1,35 @@ +#pragma once + +/* + * Provide object that implicitly converts to + * a std::function object that does nothing but return a + * default-constructed type or void. + */ + +#include <functional> + +namespace nest { +namespace mc { +namespace util { + +struct nop_function_t { + template <typename R, typename... Args> + operator std::function<R (Args...)>() const { + return [](Args...) { return R{}; }; + } + + template <typename... Args> + operator std::function<void (Args...)>() const { + return [](Args...) { }; + } + + // keep clang happy: see CWG issue #253, + // http://open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#253 + constexpr nop_function_t() {} +}; + +static constexpr nop_function_t nop_function; + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/optional.hpp b/src/util/optional.hpp index 6b4d0f8b66cbdcf24c59914466a01621895ca2bb..01e055c1730812eb04c7b329abc80a71a21f075c 100644 --- a/src/util/optional.hpp +++ b/src/util/optional.hpp @@ -268,6 +268,11 @@ struct optional: detail::optional_base<X> { template <typename T> optional(optional<T>&& ot): base(ot.set, std::move(ot.ref())) {} + optional& operator=(nothing_t) { + reset(); + return *this; + } + template < typename Y, typename = detail::enable_unless_optional_t<Y> @@ -331,6 +336,7 @@ struct optional<X&>: detail::optional_base<X&> { using base::set; using base::ref; using base::data; + using base::reset; optional(): base() {} optional(nothing_t): base() {} @@ -339,6 +345,11 @@ struct optional<X&>: detail::optional_base<X&> { template <typename T> optional(optional<T&>& ot): base(ot.set,ot.ref()) {} + optional& operator=(nothing_t) { + reset(); + return *this; + } + template <typename Y> optional& operator=(Y& y) { set = true; @@ -364,6 +375,7 @@ template <> struct optional<void>: detail::optional_base<void> { using base = detail::optional_base<void>; using base::set; + using base::reset; optional(): base() {} @@ -373,6 +385,11 @@ struct optional<void>: detail::optional_base<void> { template <typename T> optional(const optional<T>& o): base(o.set,true) {} + optional& operator=(nothing_t) { + reset(); + return *this; + } + template <typename T> optional& operator=(T) { set = true; @@ -394,7 +411,7 @@ struct optional<void>: detail::optional_base<void> { }; -template <typename A,typename B> +template <typename A, typename B> typename std::enable_if< detail::is_optional<A>::value || detail::is_optional<B>::value, optional< @@ -408,7 +425,7 @@ operator|(A&& a,B&& b) { return detail::decay_bool(a) ? a : b; } -template <typename A,typename B> +template <typename A, typename B> typename std::enable_if< detail::is_optional<A>::value || detail::is_optional<B>::value, optional<detail::wrapped_type_t<B>> diff --git a/tests/performance/io/disk_io.cpp b/tests/performance/io/disk_io.cpp index fa9973939d49db62b6427d80f2f9198c69dee26c..6800176ef27def4cff03810d4be60e34c2c825ec 100644 --- a/tests/performance/io/disk_io.cpp +++ b/tests/performance/io/disk_io.cpp @@ -33,7 +33,7 @@ int main(int argc, char** argv) // very simple command line parsing if (argc < 3) { - std::cout << "disk_io <int nrspikes> <int nr_repeats> <file_per_rank (true|false)> [simple_output (false|true)]\n" + std::cout << "disk_io <int nrspikes> <int nr_repeats> [simple_output (false|true)]\n" << " Simple performance test runner for the exporter manager\n" << " It exports nrspikes nr_repeats using the export_manager and will produce\n" << " the total, mean and std of the time needed to perform the output to disk\n" @@ -58,15 +58,9 @@ int main(int argc, char** argv) return 1; } - bool file_per_rank = false; - std::string single(argv[3]); - if (single == std::string("true")) { - file_per_rank = true; - } - bool simple_stats = false; if (argc == 5) { - std::string simple(argv[4]); + std::string simple(argv[3]); if (simple == std::string("true")) { simple_stats = true; diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index fb1b9369d4e38498c8953fc0ce2f16c1dbc715dc..83c537b26385b62ece833d59931ece57bc1cd8cc 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -21,6 +21,7 @@ set(TEST_SOURCES test_mask_stream.cpp test_matrix.cpp test_mechanisms.cpp + test_nop.cpp test_optional.cpp test_parameters.cpp test_point.cpp diff --git a/tests/unit/test_nop.cpp b/tests/unit/test_nop.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e3571ad827555e23040658f3f7275f3bcf53d9d0 --- /dev/null +++ b/tests/unit/test_nop.cpp @@ -0,0 +1,75 @@ +#include "gtest.h" +#include "util/nop.hpp" + +using namespace nest::mc::util; + +TEST(nop, void_fn) { + std::function<void ()> f{nop_function}; + + EXPECT_TRUE(f); + f(); // should do nothing + + bool flag = false; + f = [&]() { flag = true; }; + f(); + EXPECT_TRUE(flag); + + flag = false; + f = nop_function; + f(); + EXPECT_FALSE(flag); + + // with some arguments + std::function<void (int, int)> g{nop_function}; + EXPECT_TRUE(g); + g(2, 3); // should do nothing + + int sum = 0; + g = [&](int a, int b) { sum = a+b; }; + g(2, 3); + EXPECT_EQ(5, sum); + + sum = 0; + g = nop_function; + g(2, 3); + EXPECT_EQ(0, sum); +} + +struct check_default { + int value = 100; + + check_default() = default; + explicit check_default(int n): value(n) {} +}; + +TEST(nop, default_return_fn) { + std::function<check_default ()> f{nop_function}; + + EXPECT_TRUE(f); + auto result = f(); + EXPECT_EQ(result.value, 100); + + f = []() { return check_default(17); }; + result = f(); + EXPECT_EQ(result.value, 17); + + f = nop_function; + result = f(); + EXPECT_EQ(result.value, 100); + + std::function<check_default (double, double)> g{nop_function}; + + EXPECT_TRUE(g); + result = g(1.4, 1.5); + EXPECT_EQ(result.value, 100); + + g = [](double x, double y) { return check_default{(int)(x*y)}; }; + result = g(1.4, 1.5); + EXPECT_EQ(result.value, 2); + + g = nop_function; + result = g(1.4, 1.5); + EXPECT_EQ(result.value, 100); + +} + diff --git a/tests/unit/test_optional.cpp b/tests/unit/test_optional.cpp index 4610f0c48cf6b317f97e8a895f9bb09245ee7220..0c7758bcf84708cc873827bad5d33cceceabbf18 100644 --- a/tests/unit/test_optional.cpp +++ b/tests/unit/test_optional.cpp @@ -92,6 +92,18 @@ TEST(optionalm,assign_returns) { auto bp=&(a=4); EXPECT_EQ(&a,bp); + + auto b2=(a=optional<int>(10)); + EXPECT_EQ(typeid(optional<int>),typeid(b2)); + + auto bp2=&(a=4); + EXPECT_EQ(&a,bp2); + + auto b3=(a=nothing); + EXPECT_EQ(typeid(optional<int>),typeid(b3)); + + auto bp3=&(a=4); + EXPECT_EQ(&a,bp3); } TEST(optionalm,assign_reference) { @@ -104,11 +116,16 @@ TEST(optionalm,assign_reference) { *ar = 5.0; EXPECT_EQ(5.0, a); - br = ar; + auto& check_rval=(br=ar); EXPECT_TRUE(br); + EXPECT_EQ(&br, &check_rval); *br = 7.0; EXPECT_EQ(7.0, a); + + auto& check_rval2=(br=nothing); + EXPECT_FALSE(br); + EXPECT_EQ(&br, &check_rval2); } struct nomove { @@ -212,6 +229,10 @@ TEST(optionalm,void) { x=b >> []() { return 1; }; EXPECT_TRUE((bool)x); EXPECT_EQ(1,x.get()); + + auto& check_rval=(b=nothing); + EXPECT_FALSE((bool)b); + EXPECT_EQ(&b,&check_rval); } TEST(optionalm,bind_to_void) {