diff --git a/miniapp/io.cpp b/miniapp/io.cpp index 46498fd260c2d47a823aab94c0adb1ad4e2d51ba..6b81a62c853fbc2098d343c0c61b926903f47c52 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,101 +38,221 @@ 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 - const cl_options defopts{"", 1000, 500, "expsyn", 100, 100., 0.025, false, + // Default options: + 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 + 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"; } 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; } @@ -150,7 +272,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 71af61810670d7a1f226d3eec2dabfd2f38ff20e..510a0611c9e72c4084cf3487579a1a5b239efadc 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/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/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) {