diff --git a/.gitignore b/.gitignore index 72fdfb288f3edec6b957f25e57877db963da62c4..c1833bba173b6133b0970eb5dcd6cae4a4c69791 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,4 @@ mechanisms/*.hpp build* commit.msg + diff --git a/mechanisms/generate.sh b/mechanisms/generate.sh new file mode 100755 index 0000000000000000000000000000000000000000..89dfd5e277f9811bd91cdc363a61f41a1e81b706 --- /dev/null +++ b/mechanisms/generate.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +#flags="-t cpu -O" +flags="-t cpu" + +for mech in pas hh expsyn +do + echo ../external/modparser/bin/modcc ${flags} -o ../include/mechanisms/$mech.hpp ./mod/$mech.mod + ../external/modparser/bin/modcc ${flags} -o ../include/mechanisms/$mech.hpp ./mod/$mech.mod +done diff --git a/miniapp/io.cpp b/miniapp/io.cpp index 17756e2cf5fba0bafa70354b2f7ebe5294316422..344fd56135b232bef3563d6a3cc61b570868a360 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" @@ -12,20 +15,17 @@ // Let TCLAP understand value arguments that are of an optional type. namespace TCLAP { - -template <typename V> -struct ArgTraits<nest::mc::util::optional<V>> { - using ValueCategory = ValueLike; -}; - + template <typename V> + struct ArgTraits<nest::mc::util::optional<V>> { + using ValueCategory = ValueLike; + }; } // 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; @@ -38,106 +38,259 @@ 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, - false, 1.0, "trace_", util::nothing}; + // Default options: + const cl_options defopts{ + 1000, // number of cells + 500, // synapses_per_cell + "expsyn", // synapse type + 100, // compartments_per_segment + 100., // tfinal + 0.025, // dt + false, // all_to_all + false, // probe_soma_only + 0.0, // probe_ratio + "trace_", // trace_prefix + util::nothing, // trace_max_gid + + // spike_output_parameters: + false, // spike output + false, // single_file_per_simulation + true, // Overwrite outputfile if exists + "./", // output path + "spikes", // file name + "gdf" // file extension + }; 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); + TCLAP::SwitchArg spike_output_arg( + "f","spike_file_output","save spikes to file", cmd, false); + 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"); + + // Parameters for spike output + update_option(options.spike_file_output, fopts, "spike_file_output"); + if (options.spike_file_output) { + update_option(options.single_file_per_rank, fopts, "single_file_per_rank"); + update_option(options.over_write, fopts, "over_write"); + update_option(options.output_path, fopts, "output_path"); + update_option(options.file_name, fopts, "file_name"); + update_option(options.file_extension, fopts, "file_extension"); + } + + } + 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); + update_option(options.spike_file_output, spike_output_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; } std::ostream& operator<<(std::ostream& o, const cl_options& options) { - o << "simultion options:\n"; + o << "simulation options:\n"; o << " cells : " << options.cells << "\n"; o << " compartments/segment : " << options.compartments_per_segment << "\n"; o << " synapses/cell : " << options.synapses_per_cell << "\n"; @@ -152,7 +305,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..c18e688c33ceff0c3966b5d66ae8eb1680bc48a1 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; @@ -26,6 +25,14 @@ struct cl_options { double probe_ratio; std::string trace_prefix; util::optional<unsigned> trace_max_gid; + + // Parameters for spike output + bool spike_file_output; + bool single_file_per_rank; + bool over_write; + std::string output_path; + std::string file_name; + std::string file_extension; }; class usage_error: public std::runtime_error { diff --git a/miniapp/miniapp.cpp b/miniapp/miniapp.cpp index b6a6e42b642268b8364974bbe45e307f8539c343..20082a7893aef381d8cfec365d51e80ae71043da 100644 --- a/miniapp/miniapp.cpp +++ b/miniapp/miniapp.cpp @@ -3,20 +3,23 @@ #include <iostream> #include <fstream> #include <memory> +#include <vector> #include <json/src/json.hpp> #include <common_types.hpp> #include <cell.hpp> #include <cell_group.hpp> +#include <communication/communicator.hpp> +#include <communication/global_policy.hpp> #include <fvm_cell.hpp> +#include <io/exporter_spike_file.hpp> #include <mechanism_catalogue.hpp> #include <model.hpp> -#include <threading/threading.hpp> #include <profiling/profiler.hpp> -#include <communication/communicator.hpp> -#include <communication/global_policy.hpp> +#include <threading/threading.hpp> #include <util/ioutil.hpp> +#include <util/nop.hpp> #include <util/optional.hpp> #include "io.hpp" @@ -26,15 +29,17 @@ using namespace nest::mc; using global_policy = communication::global_policy; - using lowered_cell = fvm::fvm_cell<double, cell_local_size_type>; using model_type = model<lowered_cell>; -using sample_trace_type = sample_trace<model_type::time_type, model_type::value_type>; - +using time_type = model_type::time_type; +using sample_trace_type = sample_trace<time_type, model_type::value_type>; +using file_export_type = io::exporter_spike_file<time_type, global_policy>; void banner(); 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); +using communicator_type = communication::communicator<time_type, communication::global_policy>; +using spike_type = typename communicator_type::spike_type; void write_trace_json(const sample_trace_type& trace, const std::string& prefix = "trace_"); @@ -61,9 +66,35 @@ int main(int argc, char** argv) { auto recipe = make_recipe(options, pdist); auto cell_range = distribute_cells(recipe->num_cells()); - // build model from recipe + model_type m(*recipe, cell_range.first, cell_range.second); + auto register_exporter = [] (const io::cl_options& options) { + return + util::make_unique<file_export_type>( + options.file_name, options.output_path, + options.file_extension, options.over_write); + }; + + // File output is depending on the input arguments + std::unique_ptr<file_export_type> file_exporter; + if (options.spike_file_output) { + if (options.single_file_per_rank) { + file_exporter = register_exporter(options); + m.set_local_spike_callback( + [&](const std::vector<spike_type>& spikes) { + file_exporter->output(spikes); + }); + } + else if(communication::global_policy::id()==0) { + file_exporter = register_exporter(options); + m.set_global_spike_callback( + [&](const std::vector<spike_type>& spikes) { + file_exporter->output(spikes); + }); + } + } + // inject some artificial spikes, 1 per 20 neurons. std::vector<cell_gid_type> local_sources; cell_gid_type first_spike_cell = 20*((cell_range.first+19)/20); @@ -169,7 +200,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"; @@ -197,5 +228,3 @@ void write_trace_json(const sample_trace_type& trace, const std::string& prefix) std::ofstream file(path); file << std::setw(1) << jrep << std::endl; } - - diff --git a/nrn/generate_validation.sh b/nrn/generate_validation.sh index 532a7cd738d53a1a0f8342d07f09c016b5e24857..15149cddc022aa47d2f94149fc4efc6c833e9ecd 100755 --- a/nrn/generate_validation.sh +++ b/nrn/generate_validation.sh @@ -1,5 +1,7 @@ -python2.7 ./soma.py -python2.7 ./ball_and_stick.py -python2.7 ./ball_and_3stick.py -python2.7 ./simple_synapse.py --synapse exp2 -python2.7 ./simple_synapse.py --synapse exp +#!/usr/bin/env bash + +python2 ./soma.py +python2 ./ball_and_stick.py +python2 ./ball_and_3stick.py +python2 ./simple_synapse.py --synapse exp2 +python2 ./simple_synapse.py --synapse exp diff --git a/src/algorithms.hpp b/src/algorithms.hpp index 5456417ddaea0e0fe6c4e777a0fa8ebf3344c2ed..37eeaf370acd54ef326f70cc4ae17e46f1c6f2b0 100644 --- a/src/algorithms.hpp +++ b/src/algorithms.hpp @@ -139,8 +139,9 @@ std::vector<typename C::value_type> child_count(const C& parent_index) template<typename C> bool has_contiguous_compartments(const C& parent_index) { + using value_type = typename C::value_type; static_assert( - std::is_integral<typename C::value_type>::value, + std::is_integral<value_type>::value, "integral type required" ); @@ -149,9 +150,9 @@ bool has_contiguous_compartments(const C& parent_index) } auto num_child = child_count(parent_index); - for (auto i = 1u; i < parent_index.size(); ++i) { + for (auto i=1u; i < parent_index.size(); ++i) { auto p = parent_index[i]; - if (num_child[p] == 1 && p != i-1) { + if (num_child[p]==1 && p!=value_type(i-1)) { return false; } } diff --git a/src/communication/communicator.hpp b/src/communication/communicator.hpp index 802d94f9d13053be78aa45721922e99ebe4b1c0f..7ae506aba03ba80a421b313193e9b00bc31b2e26 100644 --- a/src/communication/communicator.hpp +++ b/src/communication/communicator.hpp @@ -4,6 +4,7 @@ #include <iostream> #include <vector> #include <random> +#include <functional> #include <spike.hpp> #include <util/double_buffer.hpp> @@ -90,11 +91,15 @@ public: /// Returns a vector of event queues, with one queue for each local cell group. The /// events in each queue are all events that must be delivered to targets in that cell /// group as a result of the global spike exchange. - std::vector<event_queue> exchange(const std::vector<spike_type>& local_spikes) { + 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(); + global_export_callback(global_spikes); + // check each global spike in turn to see it generates local events. // if so, make the events and insert them into the appropriate event list. auto queues = std::vector<event_queue>(num_groups_local()); diff --git a/src/io/exporter.hpp b/src/io/exporter.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a5885b7b10841593059402a491ccf21148b78df3 --- /dev/null +++ b/src/io/exporter.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include <random> +#include <string> + +#include <common_types.hpp> +#include <spike.hpp> + +namespace nest { +namespace mc { +namespace io { + +// interface for exporters. +// Exposes one virtual functions: +// do_export(vector<type>) receiving a vector of parameters to export + +template <typename Time, typename CommunicationPolicy> +class exporter { + +public: + using time_type = Time; + using spike_type = spike<cell_member_type, time_type>; + + // Performs the export of the data + virtual void output(const std::vector<spike_type>&) = 0; + + // Returns the status of the exporter + virtual bool good() const = 0; +}; + +} //communication +} // namespace mc +} // namespace nest diff --git a/src/io/exporter_spike_file.hpp b/src/io/exporter_spike_file.hpp new file mode 100644 index 0000000000000000000000000000000000000000..67c714209becbe140d2244a8a7e28552c41d7b8c --- /dev/null +++ b/src/io/exporter_spike_file.hpp @@ -0,0 +1,102 @@ +#pragma once + +#include <fstream> +#include <iomanip> +#include <memory> +#include <random> +#include <stdexcept> +#include <vector> + +#include <cstring> +#include <cstdio> + +#include <common_types.hpp> +#include <io/exporter.hpp> +#include <spike.hpp> +#include <util.hpp> + +namespace nest { +namespace mc { +namespace io { + +template <typename Time, typename CommunicationPolicy> +class exporter_spike_file : public exporter<Time, CommunicationPolicy> { +public: + using time_type = Time; + using spike_type = spike<cell_member_type, time_type>; + using communication_policy_type = CommunicationPolicy; + + // Constructor + // over_write if true will overwrite the specified output file (default = true) + // output_path relative or absolute path + // file_name will be appended with "_x" with x the rank number + // file_extension a seperator will be added automatically + exporter_spike_file( + const std::string& file_name, + const std::string& path, + const std::string& file_extension, + bool over_write=true) + { + file_path_ = + create_output_file_path( + file_name, path, file_extension, communication_policy_.id()); + + //test if the file exist and depending on over_write throw or delete + if (!over_write && file_exists(file_path_)) { + throw std::runtime_error( + "Tried opening file for writing but it exists and over_write is false: " + file_path_); + } + + file_handle_.open(file_path_); + } + + // Performs the a export of the spikes to file + // one id and spike time with 4 decimals after the comma on a + // line space separated + void output(const std::vector<spike_type>& spikes) override { + for (auto spike : spikes) { + char linebuf[45]; + auto n = + std::snprintf( + linebuf, sizeof(linebuf), "%u %.4f\n", + unsigned{spike.source.gid}, float(spike.time)); + file_handle_.write(linebuf, n); + } + } + + bool good() const override { + return file_handle_.good(); + } + + // Creates an indexed filename + static std::string create_output_file_path( + const std::string& file_name, + const std::string& path, + const std::string& file_extension, + unsigned index) + { + return path + file_name + "_" + std::to_string(index) + "." + file_extension; + } + + // The name of the output path and file name. + // May be either relative or absolute path. + const std::string& file_path() const { + return file_path_; + } + +private: + bool file_exists(const std::string& file_path) { + std::ifstream fid(file_path); + return fid.good(); + } + + // Handle to opened file handle + std::ofstream file_handle_; + std::string file_path_; + + communication_policy_type communication_policy_; +}; + +} //communication +} // namespace mc +} // namespace nest diff --git a/src/model.hpp b/src/model.hpp index 35b7029115c4ebbb4dce78bbb2611f923efc9648..85137f205c4634fd928ab82cdea44c9f837206d7 100644 --- a/src/model.hpp +++ b/src/model.hpp @@ -1,17 +1,20 @@ #pragma once -#include <cstdlib> +#include <memory> #include <vector> +#include <cstdlib> + #include <common_types.hpp> #include <cell.hpp> #include <cell_group.hpp> -#include <fvm_cell.hpp> -#include <recipe.hpp> -#include <thread_private_spike_store.hpp> #include <communication/communicator.hpp> #include <communication/global_policy.hpp> +#include <fvm_cell.hpp> #include <profiling/profiler.hpp> +#include <recipe.hpp> +#include <thread_private_spike_store.hpp> +#include <util/nop.hpp> #include "trace_sampler.hpp" @@ -26,6 +29,8 @@ public: using value_type = typename cell_group_type::value_type; using communicator_type = communication::communicator<time_type, communication::global_policy>; using sampler_function = typename cell_group_type::sampler_function; + using spike_type = typename communicator_type::spike_type; + using spike_export_function = std::function<void(const std::vector<spike_type>&)>; struct probe_record { cell_member_type id; @@ -130,7 +135,7 @@ public: auto update_cells = [&] () { threading::parallel_for::apply( 0u, cell_groups_.size(), - [&](unsigned i) { + [&](unsigned i) { auto &group = cell_groups_[i]; PE("stepping","events"); @@ -150,10 +155,18 @@ public: // the previous integration period, generating the postsynaptic // events that must be delivered at the start of the next // integration period at the latest. + + //TODO: + //An improvement might be : + //the exchange method simply exchanges spikes, and does not generate the event queues.It returns a struct that has both 1) the global spike list 2) an integer vector that describes the distribution of spikes across the ranks + //another method called something like build_queues that takes this spike info and returns the local spikes + // and the callbacks can then be called on the spike information directly in the model. auto exchange = [&] () { PE("stepping", "exchange"); auto local_spikes = previous_spikes().gather(); - future_events() = communicator_.exchange(local_spikes); + local_export_callback_(local_spikes); + future_events() = + communicator_.exchange(local_spikes, global_export_callback_); PL(2); }; @@ -201,6 +214,18 @@ public: return num_groups(); } + // register a callback that will perform a export of the global + // spike vector + void set_global_spike_callback(spike_export_function export_callback) { + global_export_callback_ = export_callback; + } + + // register a callback that will perform a export of the rank local + // spike vector + void set_local_spike_callback(spike_export_function export_callback) { + local_export_callback_ = export_callback; + } + private: cell_gid_type cell_from_; cell_gid_type cell_to_; @@ -216,6 +241,9 @@ private: using local_spike_store_type = thread_private_spike_store<time_type>; util::double_buffer< local_spike_store_type > local_spikes_; + spike_export_function global_export_callback_ = util::nop_function; + spike_export_function local_export_callback_ = util::nop_function; + // Convenience functions that map the spike buffers and event queues onto // the appropriate integration interval. // diff --git a/src/util/counter.hpp b/src/util/counter.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d071752807f53fc72324367d4e4ea87e0dc6c7be --- /dev/null +++ b/src/util/counter.hpp @@ -0,0 +1,95 @@ +#pragma once + +/* Present an integral value as an iterator, for integral-range 'containers' */ + +#include <cstddef> +#include <iterator> +#include <type_traits> + +namespace nest { +namespace mc { +namespace util { + +template <typename V, typename = typename std::enable_if<std::is_integral<V>::value>::type> +struct counter { + using difference_type = V; + using value_type = V; + using pointer = const V*; + using reference = const V&; + using iterator_category = std::random_access_iterator_tag; + + counter(): v_{} {} + counter(V v): v_{v} {} + + counter(const counter&) = default; + counter(counter&&) = default; + + counter& operator++() { + ++v_; + return *this; + } + + counter operator++(int) { + counter c(*this); + ++v_; + return c; + } + + counter& operator--() { + --v_; + return *this; + } + + counter operator--(int) { + counter c(*this); + --v_; + return c; + } + + counter& operator+=(difference_type n) { + v_ += n; + return *this; + } + + counter& operator-=(difference_type n) { + v_ -= n; + return *this; + } + + counter operator+(difference_type n) { + return counter(v_+n); + } + + friend counter operator+(difference_type n, counter x) { + return counter(n+x.v_); + } + + counter operator-(difference_type n) { + return counter(v_-n); + } + + difference_type operator-(counter x) const { + return v_-x.v_; + } + + value_type operator*() const { return v_; } + + value_type operator[](difference_type n) const { return v_+n; } + + bool operator==(counter x) const { return v_==x.v_; } + bool operator!=(counter x) const { return v_!=x.v_; } + bool operator<=(counter x) const { return v_<=x.v_; } + bool operator>=(counter x) const { return v_>=x.v_; } + bool operator<(counter x) const { return v_<x.v_; } + bool operator>(counter x) const { return v_>x.v_; } + + counter& operator=(const counter&) = default; + counter& operator=(counter&&) = default; + +private: + V v_; +}; + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/debug.cpp b/src/util/debug.cpp index 51c646bbcb38b1722e40766f4886a957094bd1bb..f2f039708a860c7fcb9ec65db7345b608a019dd9 100644 --- a/src/util/debug.cpp +++ b/src/util/debug.cpp @@ -14,8 +14,11 @@ namespace util { std::mutex global_debug_cerr_mutex; -bool failed_assertion(const char* assertion, const char* file, - int line, const char* func) +bool abort_on_failed_assertion( + const char* assertion, + const char* file, + int line, + const char* func) { // Explicit flush, as we can't assume default buffering semantics on stderr/cerr, // and abort() might not flush streams. @@ -26,8 +29,13 @@ bool failed_assertion(const char* assertion, const char* file, return false; } -std::ostream& debug_emit_trace_leader(std::ostream& out, const char* file, - int line, const char* varlist) +failed_assertion_handler_t global_failed_assertion_handler = abort_on_failed_assertion; + +std::ostream& debug_emit_trace_leader( + std::ostream& out, + const char* file, + int line, + const char* varlist) { iosfmt_guard guard(out); diff --git a/src/util/debug.hpp b/src/util/debug.hpp index f258b37123755c7ec7a7324641249ed35290e8bb..be2b3d51e4d7bf9b5d4c5fb9b7c78855c5e4b4c5 100644 --- a/src/util/debug.hpp +++ b/src/util/debug.hpp @@ -10,7 +10,17 @@ namespace nest { namespace mc { namespace util { -bool failed_assertion(const char* assertion, const char* file, int line, const char* func); +using failed_assertion_handler_t = + bool (*)(const char* assertion, const char* file, int line, const char* func); + +bool abort_on_failed_assertion(const char* assertion, const char* file, int line, const char* func); +inline bool ignore_failed_assertion(const char*, const char*, int, const char*) { + return false; +} + +// defaults to abort_on_failed_assertion; +extern failed_assertion_handler_t global_failed_assertion_handler; + std::ostream& debug_emit_trace_leader(std::ostream& out, const char* file, int line, const char* varlist); inline void debug_emit(std::ostream& out) { @@ -66,7 +76,7 @@ void debug_emit_trace(const char* file, int line, const char* varlist, const Arg #define EXPECTS(condition) \ (void)((condition) || \ - nest::mc::util::failed_assertion(#condition, __FILE__, __LINE__, DEBUG_FUNCTION_NAME)) + nest::mc::util::global_failed_assertion_handler(#condition, __FILE__, __LINE__, DEBUG_FUNCTION_NAME)) #else #define EXPECTS(condition) #endif // def WITH_ASSERTIONS diff --git a/src/util/either.hpp b/src/util/either.hpp new file mode 100644 index 0000000000000000000000000000000000000000..36932c96e8be340638c98eb582d06ffa93450828 --- /dev/null +++ b/src/util/either.hpp @@ -0,0 +1,387 @@ +#pragma once + +/* + * A type-safe discriminated union of two members. + * + * Returns true in a bool context if the first of the two types holds a value. + */ + +#include <cstdlib> +#include <type_traits> +#include <stdexcept> +#include <utility> + +#include "util/meta.hpp" +#include "util/uninitialized.hpp" + +namespace nest { +namespace mc { +namespace util { + +struct either_invalid_access: std::runtime_error { + explicit either_invalid_access(const std::string& what_str) + : std::runtime_error(what_str) + {} + + either_invalid_access() + : std::runtime_error("access of unconstructed value in either") + {} +}; + +namespace detail { + template <typename A, typename B> + struct either_data { + union { + uninitialized<A> ua; + uninitialized<B> ub; + }; + + either_data() = default; + + either_data(const either_data&) = delete; + either_data(either_data&&) = delete; + either_data& operator=(const either_data&) = delete; + either_data& operator=(either_data&&) = delete; + }; + + template <std::size_t, typename A, typename B> struct either_select; + + template <typename A, typename B> + struct either_select<0, A, B> { + using type = uninitialized<A>; + static type& field(either_data<A, B>& data) { return data.ua; } + static const type& field(const either_data<A, B>& data) { return data.ua; } + }; + + template <typename A, typename B> + struct either_select<1, A, B> { + using type = uninitialized<B>; + static type& field(either_data<A, B>& data) { return data.ub; } + static const type& field(const either_data<A, B>& data) { return data.ub; } + }; + + template <std::size_t I, typename A, typename B> + struct either_get: either_select<I, A, B> { + using typename either_select<I, A, B>::type; + using either_select<I, A, B>::field; + + static typename type::reference unsafe_get(either_data<A, B>& data) { + return field(data).ref(); + } + + static typename type::const_reference unsafe_get(const either_data<A, B>& data) { + return field(data).cref(); + } + + static typename type::reference unsafe_get(char which, either_data<A, B>& data) { + if (I!=which) { + throw either_invalid_access(); + } + return field(data).ref(); + } + + static typename type::const_reference unsafe_get(char which, const either_data<A, B>& data) { + if (I!=which) { + throw either_invalid_access(); + } + return field(data).cref(); + } + + static typename type::pointer ptr(char which, either_data<A, B>& data) { + return I==which? field(data).ptr(): nullptr; + } + + static typename type::const_pointer ptr(char which, const either_data<A, B>& data) { + return I==which? field(data).cptr(): nullptr; + } + }; +} // namespace detail + +constexpr std::size_t variant_npos = -1; // emulating C++17 variant type + +template <typename A, typename B> +class either: public detail::either_data<A, B> { + using base = detail::either_data<A, B>; + using base::ua; + using base::ub; + + template <std::size_t I> + using getter = detail::either_get<I, A, B>; + + unsigned char which; + +public: + // default ctor if A is default-constructible or A is not and B is. + template < + typename A_ = A, + bool a_ = std::is_default_constructible<A_>::value, + bool b_ = std::is_default_constructible<B>::value, + typename = enable_if_t<a_ || (!a_ && b_)>, + std::size_t w_ = a_? 0: 1 + > + either() noexcept(std::is_nothrow_default_constructible<typename getter<w_>::type>::value): + which(w_) + { + getter<w_>::field(*this).construct(); + } + + // implicit constructors from A and B values by copy or move + either(const A& a) noexcept(std::is_nothrow_copy_constructible<A>::value): which(0) { + getter<0>::field(*this).construct(a); + } + + template < + typename B_ = B, + typename = enable_if_t<!std::is_same<A, B_>::value> + > + either(const B& b) noexcept(std::is_nothrow_copy_constructible<B>::value): which(1) { + getter<1>::field(*this).construct(b); + } + + either(A&& a) noexcept(std::is_nothrow_move_constructible<A>::value): which(0) { + getter<0>::field(*this).construct(std::move(a)); + } + + template < + typename B_ = B, + typename = enable_if_t<!std::is_same<A, B_>::value> + > + either(B&& b) noexcept(std::is_nothrow_move_constructible<B>::value): which(1) { + getter<1>::field(*this).construct(std::move(b)); + } + + // copy constructor + either(const either& x) + noexcept(std::is_nothrow_copy_constructible<A>::value && + std::is_nothrow_copy_constructible<B>::value): + which(x.which) + { + if (which==0) { + getter<0>::field(*this).construct(x.unsafe_get<0>()); + } + else if (which==1) { + getter<1>::field(*this).construct(x.unsafe_get<1>()); + } + } + + // move constructor + either(either&& x) + noexcept(std::is_nothrow_move_constructible<A>::value && + std::is_nothrow_move_constructible<B>::value): + which(x.which) + { + if (which==0) { + getter<0>::field(*this).construct(std::move(x.unsafe_get<0>())); + } + else if (which==1) { + getter<1>::field(*this).construct(std::move(x.unsafe_get<1>())); + } + } + + // copy assignment + either& operator=(const either& x) { + if (this==&x) { + return *this; + } + + switch (which) { + case 0: + if (x.which==0) { + unsafe_get<0>() = x.unsafe_get<0>(); + } + else { + if (x.which==1) { + B b_tmp(x.unsafe_get<1>()); + getter<0>::field(*this).destruct(); + which = (unsigned char)variant_npos; + getter<1>::field(*this).construct(std::move(b_tmp)); + which = 1; + } + else { + getter<0>::field(*this).destruct(); + which = (unsigned char)variant_npos; + } + } + break; + case 1: + if (x.which==1) { + unsafe_get<1>() = x.unsafe_get<1>(); + } + else { + if (x.which==0) { + A a_tmp(x.unsafe_get<0>()); + getter<1>::field(*this).destruct(); + which = (unsigned char)variant_npos; + getter<0>::field(*this).construct(std::move(a_tmp)); + which = 0; + } + else { + getter<1>::field(*this).destruct(); + which = (unsigned char)variant_npos; + } + } + break; + default: // variant_npos + if (x.which==0) { + getter<0>::field(*this).construct(x.unsafe_get<0>()); + } + else if (x.which==1) { + getter<1>::field(*this).construct(x.unsafe_get<1>()); + } + break; + } + return *this; + } + + // move assignment + either& operator=(either&& x) { + if (this==&x) { + return *this; + } + + switch (which) { + case 0: + if (x.which==0) { + unsafe_get<0>() = std::move(x.unsafe_get<0>()); + } + else { + which = (unsigned char)variant_npos; + getter<0>::field(*this).destruct(); + if (x.which==1) { + getter<1>::field(*this).construct(std::move(x.unsafe_get<1>())); + which = 1; + } + } + break; + case 1: + if (x.which==1) { + unsafe_get<1>() = std::move(x.unsafe_get<1>()); + } + else { + which = (unsigned char)variant_npos; + getter<1>::field(*this).destruct(); + if (x.which==0) { + getter<0>::field(*this).construct(std::move(x.unsafe_get<0>())); + which = 0; + } + } + break; + default: // variant_npos + if (x.which==0) { + getter<0>::field(*this).construct(std::move(x.unsafe_get<0>())); + } + else if (x.which==1) { + getter<1>::field(*this).construct(std::move(x.unsafe_get<1>())); + } + break; + } + return *this; + } + + // unchecked element access + template <std::size_t I> + typename getter<I>::type::reference unsafe_get() { + return getter<I>::unsafe_get(*this); + } + + template <std::size_t I> + typename getter<I>::type::const_reference unsafe_get() const { + return getter<I>::unsafe_get(*this); + } + + // checked element access + template <std::size_t I> + typename getter<I>::type::reference get() { + return getter<I>::unsafe_get(which, *this); + } + + template <std::size_t I> + typename getter<I>::type::const_reference get() const { + return getter<I>::unsafe_get(which, *this); + } + + // convenience getter aliases + typename getter<0>::type::reference first() { return get<0>(); } + typename getter<0>::type::const_reference first() const { return get<0>(); } + + typename getter<1>::type::reference second() { return get<1>(); } + typename getter<1>::type::const_reference second() const { return get<1>(); } + + // pointer to element access: return nullptr if it does not hold this item + template <std::size_t I> + auto ptr() -> decltype(getter<I>::ptr(which, *this)) { + return getter<I>::ptr(which, *this); + } + + template <std::size_t I> + auto ptr() const -> decltype(getter<I>::ptr(which, *this)) { + return getter<I>::ptr(which, *this); + } + + // true in bool context if holds first alternative + constexpr operator bool() const { return which==0; } + + constexpr bool valueless_by_exception() const noexcept { + return which==(unsigned char)variant_npos; + } + + constexpr std::size_t index() const noexcept { + return which; + } + + ~either() { + if (which==0) { + getter<0>::field(*this).destruct(); + } + else if (which==1) { + getter<1>::field(*this).destruct(); + } + } + + // comparison operators follow C++17 variant semantics + bool operator==(const either& x) const { + return index()==x.index() && + index()==0? unsafe_get<0>()==x.unsafe_get<0>(): + index()==1? unsafe_get<1>()==x.unsafe_get<1>(): + true; + } + + bool operator!=(const either& x) const { + return index()!=x.index() || + index()==0? unsafe_get<0>()!=x.unsafe_get<0>(): + index()==1? unsafe_get<1>()!=x.unsafe_get<1>(): + false; + } + + bool operator<(const either& x) const { + return !x.valueless_by_exception() && + index()==0? (x.index()==1 || unsafe_get<0>()<x.unsafe_get<0>()): + index()==1? (x.index()!=0 && unsafe_get<1>()<x.unsafe_get<1>()): + true; + } + + bool operator>=(const either& x) const { + return x.valueless_by_exception() || + index()==0? (x.index()!=1 && unsafe_get<0>()>=x.unsafe_get<0>()): + index()==1? (x.index()==0 || unsafe_get<1>()>=x.unsafe_get<1>()): + false; + } + + bool operator<=(const either& x) const { + return valueless_by_exception() || + x.index()==0? (index()!=1 && unsafe_get<0>()<=x.unsafe_get<0>()): + x.index()==1? (index()==0 || unsafe_get<1>()<=x.unsafe_get<1>()): + false; + } + + bool operator>(const either& x) const { + return !valueless_by_exception() && + x.index()==0? (index()==1 || unsafe_get<0>()>x.unsafe_get<0>()): + x.index()==1? (index()!=0 && unsafe_get<1>()>x.unsafe_get<1>()): + true; + } +}; + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/iterutil.hpp b/src/util/iterutil.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6a9fd159ffe9f75eddf57f2adb5c4d42cb9e7861 --- /dev/null +++ b/src/util/iterutil.hpp @@ -0,0 +1,178 @@ +#pragma once + +/* + * Utilities and base classes to help with + * implementing iterators and iterator adaptors. + */ + +#include <iterator> +#include <memory> +#include <type_traits> +#include <utility> + +#include <util/meta.hpp> + +namespace nest { +namespace mc { +namespace util { + +/* + * Return the iterator reachable from iter such that + * std::next(iter)==end + * + * Two implementations: the first applies generally, while the + * second is used when we can just return std::prev(end). + */ +template <typename I, typename E> +enable_if_t< + is_forward_iterator<I>::value && + (!is_bidirectional_iterator<E>::value || !std::is_constructible<I, E>::value), + I> +upto(I iter, E end) { + I j = iter; + while (j!=end) { + iter = j; + ++j; + } + return iter; +} + +template <typename I, typename E> +enable_if_t<is_bidirectional_iterator<E>::value && std::is_constructible<I, E>::value, I> +upto(I iter, E end) { + return iter==I{end}? iter: I{std::prev(end)}; +} + +/* + * Provide a proxy object for operator->() for iterator adaptors that + * present rvalues on dereference. + */ +template <typename V> +struct pointer_proxy: public V { + pointer_proxy(const V& v): V{v} {} + pointer_proxy(V&& v): V{std::move(v)} {} + const V* operator->() const { return this; } +}; + +/* + * Base class (using CRTP) for iterator adaptors that + * perform a transformation or present a proxy for + * an underlying iterator. + * + * Supplies default implementations for iterator concepts + * in terms of the derived class' methods and the + * inner iterator. + * + * Derived class must provide implementations for: + * operator*() + * operator[](difference_type) + * inner() // provides access to wrapped iterator + */ + +template <typename Derived, typename I> +class iterator_adaptor { +protected: + Derived& derived() { return static_cast<Derived&>(*this); } + const Derived& derived() const { return static_cast<const Derived&>(*this); } + +private: + // Access to inner iterator provided by derived class. + I& inner() { return derived().inner(); } + const I& inner() const { return derived().inner(); } + +public: + using value_type = typename std::iterator_traits<I>::value_type; + using difference_type = typename std::iterator_traits<I>::difference_type; + using iterator_category = typename std::iterator_traits<I>::iterator_category; + using pointer = typename std::iterator_traits<I>::pointer; + using reference = typename std::iterator_traits<I>::reference; + + iterator_adaptor() = default; + + // forward and input iterator requirements + + I operator->() { return inner(); } + I operator->() const { return inner(); } + + Derived& operator++() { + ++inner(); + return derived(); + } + + Derived operator++(int) { + Derived c(derived()); + ++derived(); + return c; + } + + bool operator==(const Derived& x) const { + return inner()==x.inner(); + } + + bool operator!=(const Derived& x) const { + return !(derived()==x); + } + + // bidirectional iterator requirements + + Derived& operator--() { + --inner(); + return derived(); + } + + Derived operator--(int) { + Derived c(derived()); + --derived(); + return c; + } + + // random access iterator requirements + + Derived& operator+=(difference_type n) { + inner() += n; + return derived(); + } + + Derived operator+(difference_type n) const { + Derived c(derived()); + return c += n; + } + + friend Derived operator+(difference_type n, const Derived& x) { + return x+n; + } + + Derived& operator-=(difference_type n) { + inner() -= n; + return *this; + } + + Derived operator-(difference_type n) const { + Derived c(derived()); + return c -= n; + } + + difference_type operator-(const Derived& x) const { + return inner()-x.inner(); + } + + bool operator<(const Derived& x) const { + return inner()<x.inner(); + } + + bool operator<=(const Derived& x) const { + return derived()<x || derived()==x; + } + + bool operator>=(const Derived& x) const { + return !(derived()<x); + } + + bool operator>(const Derived& x) const { + return !(derived()<=x); + } +}; + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/meta.hpp b/src/util/meta.hpp index 2e22e31ceda0ff83588625486ec7a36161569733..7b1fb0d6f228175377242b607a111457ed90b414 100644 --- a/src/util/meta.hpp +++ b/src/util/meta.hpp @@ -2,6 +2,7 @@ /* Type utilities and convenience expressions. */ +#include <iterator> #include <type_traits> namespace nest { @@ -13,8 +14,45 @@ namespace util { template <typename T> using result_of_t = typename std::result_of<T>::type; -template <bool V> -using enable_if_t = typename std::enable_if<V>::type; +template <bool V, typename R = void> +using enable_if_t = typename std::enable_if<V, R>::type; + +template <class...> +using void_t = void; + +template <typename T> +using decay_t = typename std::decay<T>::type; + +template <typename X> +std::size_t size(const X& x) { return x.size(); } + +template <typename X, std::size_t N> +constexpr std::size_t size(X (&)[N]) { return N; } + +template <typename T> +constexpr auto cbegin(const T& c) -> decltype(std::begin(c)) { + return std::begin(c); +} + +template <typename T> +constexpr auto cend(const T& c) -> decltype(std::end(c)) { + return std::end(c); +} + +// Types associated with a container or sequence + +template <typename Seq> +struct sequence_traits { + using iterator = decltype(std::begin(std::declval<Seq&>())); + using const_iterator = decltype(cbegin(std::declval<Seq&>())); + using value_type = typename std::iterator_traits<iterator>::value_type; + using reference = typename std::iterator_traits<iterator>::reference; + using difference_type = typename std::iterator_traits<iterator>::difference_type; + using size_type = decltype(size(std::declval<Seq&>())); + // for use with heterogeneous ranges + using sentinel = decltype(std::end(std::declval<Seq&>())); + using const_sentinel = decltype(cend(std::declval<Seq&>())); +}; // Convenience short cuts @@ -26,10 +64,91 @@ template <typename T> using enable_if_move_constructible_t = enable_if_t<std::is_move_constructible<T>::value>; +template <typename T> +using enable_if_default_constructible_t = + enable_if_t<std::is_default_constructible<T>::value>; + template <typename... T> using enable_if_constructible_t = enable_if_t<std::is_constructible<T...>::value>; +template <typename T> +using enable_if_copy_assignable_t = + enable_if_t<std::is_copy_assignable<T>::value>; + +template <typename T> +using enable_if_move_assignable_t = + enable_if_t<std::is_move_assignable<T>::value>; + +// Iterator class test +// (might not be portable before C++17) + +template <typename T, typename = void> +struct is_iterator: public std::false_type {}; + +template <typename T> +struct is_iterator<T, void_t<typename std::iterator_traits<T>::iterator_category>>: + public std::true_type {}; + +template <typename T> +using is_iterator_t = typename is_iterator<T>::type; + +// Random access iterator test + +template <typename T, typename = void> +struct is_random_access_iterator: public std::false_type {}; + +template <typename T> +struct is_random_access_iterator<T, enable_if_t< + std::is_same< + std::random_access_iterator_tag, + typename std::iterator_traits<T>::iterator_category>::value + >> : public std::true_type {}; + +template <typename T> +using is_random_access_iterator_t = typename is_random_access_iterator<T>::type; + +// Bidirectional iterator test + +template <typename T, typename = void> +struct is_bidirectional_iterator: public std::false_type {}; + +template <typename T> +struct is_bidirectional_iterator<T, enable_if_t< + std::is_same< + std::random_access_iterator_tag, + typename std::iterator_traits<T>::iterator_category>::value + || + std::is_same< + std::bidirectional_iterator_tag, + typename std::iterator_traits<T>::iterator_category>::value + >> : public std::true_type {}; + +template <typename T> +using is_bidirectional_iterator_t = typename is_bidirectional_iterator<T>::type; + +// Forward iterator test + +template <typename T, typename = void> +struct is_forward_iterator: public std::false_type {}; + +template <typename T> +struct is_forward_iterator<T, enable_if_t< + std::is_same< + std::random_access_iterator_tag, + typename std::iterator_traits<T>::iterator_category>::value + || + std::is_same< + std::bidirectional_iterator_tag, + typename std::iterator_traits<T>::iterator_category>::value + || + std::is_same< + std::forward_iterator_tag, + typename std::iterator_traits<T>::iterator_category>::value + >> : public std::true_type {}; + +template <typename T> +using is_forward_iterator_t = typename is_forward_iterator<T>::type; } // namespace util } // namespace mc 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..40b40d7c3ff27352638c789575172a5cd5fee8d4 100644 --- a/src/util/optional.hpp +++ b/src/util/optional.hpp @@ -38,16 +38,6 @@ struct optional_unset_error: std::runtime_error { {} }; -struct optional_invalid_dereference: std::runtime_error { - explicit optional_invalid_dereference(const std::string& what_str) - : std::runtime_error(what_str) - {} - - optional_invalid_dereference() - : std::runtime_error("derefernce of optional<void> value") - {} -}; - struct nothing_t {}; constexpr nothing_t nothing{}; @@ -68,21 +58,21 @@ namespace detail { struct optional_tag {}; template <typename X> - using is_optional = std::is_base_of<optional_tag, typename std::decay<X>::type>; + using is_optional = std::is_base_of<optional_tag, decay_t<X>>; - template <typename D,typename X> + template <typename D, typename X> struct wrapped_type_impl { using type = X; }; - template <typename D,typename X> - struct wrapped_type_impl<optional<D>,X> { + template <typename D, typename X> + struct wrapped_type_impl<optional<D>, X> { using type = D; }; template <typename X> struct wrapped_type { - using type = typename wrapped_type_impl<typename std::decay<X>::type,X>::type; + using type = typename wrapped_type_impl<decay_t<X>, X>::type; }; template <typename X> @@ -96,10 +86,10 @@ namespace detail { using data_type = util::uninitialized<X>; public: - using reference_type = typename data_type::reference_type; - using const_reference_type = typename data_type::const_reference_type; - using pointer_type = typename data_type::pointer_type; - using const_pointer_type = typename data_type::const_pointer_type; + using reference = typename data_type::reference; + using const_reference = typename data_type::const_reference; + using pointer = typename data_type::pointer; + using const_pointer = typename data_type::const_pointer; protected: bool set; @@ -114,8 +104,8 @@ namespace detail { } } - reference_type ref() { return data.ref(); } - const_reference_type ref() const { return data.cref(); } + reference ref() { return data.ref(); } + const_reference ref() const { return data.cref(); } public: ~optional_base() { @@ -124,28 +114,24 @@ namespace detail { } } - const_pointer_type operator->() const { return data.ptr(); } - pointer_type operator->() { return data.ptr(); } + pointer operator->() { return data.ptr(); } + const_pointer operator->() const { return data.ptr(); } - const_reference_type operator*() const { return ref(); } - reference_type operator*() { return ref(); } + reference operator*() { return ref(); } + const_reference operator*() const { return ref(); } - reference_type get() { - if (set) { - return ref(); - } - else { + reference get() { + if (!set) { throw optional_unset_error(); } + return ref(); } - const_reference_type get() const { - if (set) { - return ref(); - } - else { + const_reference get() const { + if (!set) { throw optional_unset_error(); } + return ref(); } explicit operator bool() const { return set; } @@ -206,16 +192,16 @@ namespace detail { private: template <typename R, bool F_void_return> struct bind_impl { - template <typename DT,typename F> - static R bind(DT& d,F&& f) { + template <typename DT, typename F> + static R bind(DT& d, F&& f) { return R(d.apply(std::forward<F>(f))); } }; template <typename R> - struct bind_impl<R,true> { - template <typename DT,typename F> - static R bind(DT& d,F&& f) { + struct bind_impl<R, true> { + template <typename DT, typename F> + static R bind(DT& d, F&& f) { d.apply(std::forward<F>(f)); return R(true); } @@ -243,30 +229,32 @@ struct optional: detail::optional_base<X> { using base::reset; using base::data; - optional(): base() {} - optional(nothing_t): base() {} + optional() noexcept: base() {} + optional(nothing_t) noexcept: base() {} - template < - typename Y = X, - typename = enable_if_copy_constructible_t<Y> - > - optional(const X& x): base(true, x) {} + optional(const X& x) + noexcept(std::is_nothrow_copy_constructible<X>::value): base(true, x) {} - template < - typename Y = X, - typename = enable_if_move_constructible_t<Y> - > - optional(X&& x): base(true, std::move(x)) {} + optional(X&& x) + noexcept(std::is_nothrow_move_constructible<X>::value): base(true, std::move(x)) {} optional(const optional& ot): base(ot.set, ot.ref()) {} template <typename T> - optional(const optional<T>& ot): base(ot.set, ot.ref()) {} + optional(const optional<T>& ot) + noexcept(std::is_nothrow_constructible<X, T>::value): base(ot.set, ot.ref()) {} - optional(optional&& ot): base(ot.set, std::move(ot.ref())) {} + optional(optional&& ot) + noexcept(std::is_nothrow_move_constructible<X>::value): base(ot.set, std::move(ot.ref())) {} template <typename T> - optional(optional<T>&& ot): base(ot.set, std::move(ot.ref())) {} + optional(optional<T>&& ot) + noexcept(std::is_nothrow_constructible<X, T&&>::value): base(ot.set, std::move(ot.ref())) {} + + optional& operator=(nothing_t) { + reset(); + return *this; + } template < typename Y, @@ -331,13 +319,19 @@ 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() {} - optional(X&x): base(true,x) {} + optional() noexcept: base() {} + optional(nothing_t) noexcept: base() {} + optional(X& x) noexcept: base(true, x) {} template <typename T> - optional(optional<T&>& ot): base(ot.set,ot.ref()) {} + optional(optional<T&>& ot) noexcept: base(ot.set, ot.ref()) {} + + optional& operator=(nothing_t) { + reset(); + return *this; + } template <typename Y> optional& operator=(Y& y) { @@ -364,14 +358,20 @@ template <> struct optional<void>: detail::optional_base<void> { using base = detail::optional_base<void>; using base::set; + using base::reset; optional(): base() {} template <typename T> - optional(T): base(true,true) {} + optional(T): base(true, true) {} template <typename T> - optional(const optional<T>& o): base(o.set,true) {} + optional(const optional<T>& o): base(o.set, true) {} + + optional& operator=(nothing_t) { + reset(); + return *this; + } template <typename T> optional& operator=(T) { @@ -394,7 +394,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< @@ -404,16 +404,16 @@ typename std::enable_if< >::type > >::type -operator|(A&& a,B&& b) { +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>> >::type -operator&(A&& a,B&& b) { +operator&(A&& a, B&& b) { using result_type = optional<detail::wrapped_type_t<B>>; return a ? b: result_type(); } diff --git a/src/util/partition.hpp b/src/util/partition.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b35f81de8fa1f40217e331dd96e09a11eba1c174 --- /dev/null +++ b/src/util/partition.hpp @@ -0,0 +1,165 @@ +#pragma once + +#include <iterator> +#include <stdexcept> +#include <type_traits> + +#include <util/either.hpp> +#include <util/meta.hpp> +#include <util/partition_iterator.hpp> +#include <util/range.hpp> + +namespace nest { +namespace mc { +namespace util { + +struct invalid_partition: std::runtime_error { + explicit invalid_partition(const std::string& what): std::runtime_error(what) {} + explicit invalid_partition(const char* what): std::runtime_error(what) {} +}; + +/* + * Present a sequence with monotically increasing values as a partition, + */ +template <typename I> +class partition_range: public range<partition_iterator<I>> { + using base = range<partition_iterator<I>>; + using inner_value_type = typename std::iterator_traits<I>::value_type; + +public: + using typename base::iterator; + using typename base::value_type; + using base::left; + using base::right; + using base::front; + using base::back; + using base::empty; + + template <typename Seq> + partition_range(const Seq& s): base{std::begin(s), upto(std::begin(s), std::end(s))} { + EXPECTS(is_valid()); + } + + // explicitly check invariants + void validate() const { + auto ok = is_valid(); + if (!ok) { + throw invalid_partition(ok.second()); + } + } + + // find half-open sub-interval containing x + iterator find(const inner_value_type& x) const { + if (empty()) { + return right; + } + + auto divs = divisions(); + auto i = std::upper_bound(divs.left, divs.right, x); + if (i==divs.left || i==divs.right) { + return right; + } + return iterator{std::prev(i)}; + } + + // access to underlying divisions + range<I> divisions() const { + return {left.get(), std::next(right.get())}; + } + + // global upper and lower bounds of partition + value_type bounds() const { + return {front().first, back().second}; + } + +private: + either<bool, std::string> is_valid() const { + if (!std::is_sorted(left.get(), right.get())) { + return std::string("offsets are not monotonically increasing"); + } + else { + return true; + } + } +}; + + +template < + typename Seq, + typename SeqIter = typename sequence_traits<Seq>::const_iterator, + typename = enable_if_t<is_forward_iterator<SeqIter>::value> +> +partition_range<SeqIter> partition_view(const Seq& r) { + return partition_range<SeqIter>(r); +} + +/* + * Construct a monotonically increasing sequence in a provided + * container representing a partition from a sequence of subset sizes. + * + * If the first parameter is `partition_in_place`, the provided + * container `divisions` will not be resized, and the partition will + * be of length `util::size(divisions)-1` or zero if `divisions` is + * empty. + * + * Otherwise, `divisions` will be be resized to `util::size(sizes)+1` + * and represent a partition of length `util::size(sizes)`. + * + * Returns a partition view over `divisions`. + */ + +struct partition_in_place_t { + constexpr partition_in_place_t() {} +}; + +constexpr partition_in_place_t partition_in_place; + +template < + typename Part, + typename Sizes, + typename T = typename sequence_traits<Part>::value_type +> +partition_range<typename sequence_traits<Part>::const_iterator> +make_partition(partition_in_place_t, Part& divisions, const Sizes& sizes, T from=T{}) { + auto pi = std::begin(divisions); + auto pe = std::end(divisions); + auto si = std::begin(sizes); + auto se = std::end(sizes); + + if (pi!=pe) { + *pi++ = from; + while (pi!=pe && si!=se) { + from += *si++; + *pi++ = from; + } + while (pi!=pe) { + *pi++ = from; + } + } + return partition_view(divisions); +} + +template < + typename Part, + typename Sizes, + typename T = typename sequence_traits<Part>::value_type +> +partition_range<typename sequence_traits<Part>::const_iterator> +make_partition(Part& divisions, const Sizes& sizes, T from=T{}) { + divisions.resize(size(sizes)+1); + + // (would use std::inclusive_scan in C++17) + auto pi = std::begin(divisions); + for (const auto& s: sizes) { + *pi++ = from; + from += s; + } + *pi = from; + + return partition_view(divisions); +} + + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/partition_iterator.hpp b/src/util/partition_iterator.hpp new file mode 100644 index 0000000000000000000000000000000000000000..f13b82f25d43c604680ff4e96b3efd6936e98aea --- /dev/null +++ b/src/util/partition_iterator.hpp @@ -0,0 +1,66 @@ +#pragma once + +/* + * Present a monotonically increasing sequence of values given by an iterator + * as a sequence of pairs representing half-open intervals. + * + * Implementation is a thin wrapper over underlying iterator. + */ + +#include <iterator> +#include <memory> +#include <type_traits> +#include <utility> + +#include <util/iterutil.hpp> +#include <util/meta.hpp> + +namespace nest { +namespace mc { +namespace util { + +template <typename I> +class partition_iterator: public iterator_adaptor<partition_iterator<I>, I> { + using base = iterator_adaptor<partition_iterator<I>, I>; + friend class iterator_adaptor<partition_iterator<I>, I>; + I inner_; + + // provides access to inner iterator for adaptor. + const I& inner() const { return inner_; } + I& inner() { return inner_; } + + using inner_value_type = decay_t<decltype(*inner_)>; + +public: + using typename base::difference_type; + using value_type = std::pair<inner_value_type, inner_value_type>; + using pointer = const value_type*; + using reference = const value_type&; + + template < + typename J, + typename = enable_if_t<!std::is_same<decay_t<J>, partition_iterator>::value> + > + explicit partition_iterator(J&& c): inner_{std::forward<J>(c)} {} + + // forward and input iterator requirements + + value_type operator*() const { + return {*inner_, *std::next(inner_)}; + } + + util::pointer_proxy<value_type> operator->() const { + return **this; + } + + value_type operator[](difference_type n) const { + return *(*this+n); + } + + // public access to inner iterator + const I& get() const { return inner_; } +}; + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/range.hpp b/src/util/range.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7777444d0f65d7feebc03f8f3e988a234462cdf6 --- /dev/null +++ b/src/util/range.hpp @@ -0,0 +1,332 @@ +#pragma once + +/* Present a pair of iterators as a non-owning collection. + * + * Two public member fields, `left` and `right`, describe + * the half-open interval [`left`, `right`). + * + * Constness of the range object only affects mutability + * of the iterators, and does not relate to the constness + * of the data to which the iterators refer. + * + * The `right` field may differ in type from the `left` field, + * in which case it is regarded as a sentinel type; the end of + * the interval is then marked by the first successor `i` of + * `left` that satisfies `i==right`. + * + * For an iterator `i` and sentinel `s`, it is expected that + * the tests `i==s` and `i!=s` are well defined, with the + * corresponding semantics. + */ + +#include <cstddef> +#include <iterator> +#include <limits> +#include <stdexcept> +#include <type_traits> +#include <utility> + +#ifdef WITH_TBB +#include <tbb/tbb_stddef.h> +#endif + +#include <util/counter.hpp> +#include <util/debug.hpp> +#include <util/either.hpp> +#include <util/iterutil.hpp> +#include <util/meta.hpp> + +namespace nest { +namespace mc { +namespace util { + +template <typename U, typename S = U> +struct range { + using iterator = U; + using sentinel = S; + using const_iterator = iterator; + using difference_type = typename std::iterator_traits<iterator>::difference_type; + using size_type = typename std::make_unsigned<difference_type>::type; + using value_type = typename std::iterator_traits<iterator>::value_type; + using reference = typename std::iterator_traits<iterator>::reference; + using const_reference = const value_type&; + + iterator left; + sentinel right; + + range() = default; + range(const range&) = default; + range(range&&) = default; + + template <typename U1, typename U2> + range(U1&& l, U2&& r): + left(std::forward<U1>(l)), right(std::forward<U2>(r)) + {} + + range& operator=(const range&) = default; + range& operator=(range&&) = default; + + bool empty() const { return left==right; } + + iterator begin() const { return left; } + const_iterator cbegin() const { return left; } + + sentinel end() const { return right; } + sentinel cend() const { return right; } + + template <typename V = iterator> + enable_if_t<is_forward_iterator<V>::value, size_type> + size() const { + return std::distance(begin(), end()); + } + + constexpr size_type max_size() const { return std::numeric_limits<size_type>::max(); } + + void swap(range& other) { + std::swap(left, other.left); + std::swap(right, other.right); + } + + decltype(*left) front() const { return *left; } + + decltype(*left) back() const { return *upto(left, right); } + + template <typename V = iterator> + enable_if_t<is_random_access_iterator<V>::value, decltype(*left)> + operator[](difference_type n) const { + return *std::next(begin(), n); + } + + template <typename V = iterator> + enable_if_t<is_random_access_iterator<V>::value, decltype(*left)> + at(difference_type n) const { + if (size_type(n) >= size()) { + throw std::out_of_range("out of range in range"); + } + return (*this)[n]; + } + +#ifdef WITH_TBB + template < + typename V = iterator, + typename = enable_if_t<is_forward_iterator<V>::value> + > + range(range& r, tbb::split): + left(r.left), right(r.right) + { + std::advance(left, r.size()/2u); + r.right = left; + } + + template < + typename V = iterator, + typename = enable_if_t<is_forward_iterator<V>::value> + > + range(range& r, tbb::proportional_split p): + left(r.left), right(r.right) + { + size_type i = (r.size()*p.left())/(p.left()+p.right()); + if (i<1) { + i = 1; + } + std::advance(left, i); + r.right = left; + } + + bool is_divisible() const { + return is_forward_iterator<U>::value && left != right && std::next(left) != right; + } + + static const bool is_splittable_in_proportion() { + return is_forward_iterator<U>::value; + } +#endif +}; + +template <typename U, typename V> +range<U, V> make_range(const U& left, const V& right) { + return range<U, V>(left, right); +} + +/* + * Use a proxy iterator to present a range as having the same begin and + * end types, for use with e.g. pre-C++17 ranged-for loops or STL + * algorithms. + */ +template <typename I, typename S> +class sentinel_iterator { + nest::mc::util::either<I, S> e_; + + bool is_sentinel() const { return e_.index()!=0; } + + I& iter() { + EXPECTS(!is_sentinel()); + return e_.template unsafe_get<0>(); + } + + const I& iter() const { + EXPECTS(!is_sentinel()); + return e_.template unsafe_get<0>(); + } + + S& sentinel() { + EXPECTS(is_sentinel()); + return e_.template unsafe_get<1>(); + } + + const S& sentinel() const { + EXPECTS(is_sentinel()); + return e_.template unsafe_get<1>(); + } + +public: + using difference_type = typename std::iterator_traits<I>::difference_type; + using value_type = typename std::iterator_traits<I>::value_type; + using pointer = typename std::iterator_traits<I>::pointer; + using reference = typename std::iterator_traits<I>::reference; + using iterator_category = typename std::iterator_traits<I>::iterator_category; + + sentinel_iterator(I i): e_(i) {} + + template <typename V = S, typename = enable_if_t<!std::is_same<I, V>::value>> + sentinel_iterator(S i): e_(i) {} + + sentinel_iterator() = default; + sentinel_iterator(const sentinel_iterator&) = default; + sentinel_iterator(sentinel_iterator&&) = default; + + sentinel_iterator& operator=(const sentinel_iterator&) = default; + sentinel_iterator& operator=(sentinel_iterator&&) = default; + + // forward and input iterator requirements + + auto operator*() const -> decltype(*iter()) { return *iter(); } + + I operator->() const { return e_.template ptr<0>(); } + + sentinel_iterator& operator++() { + ++iter(); + return *this; + } + + sentinel_iterator operator++(int) { + sentinel_iterator c(*this); + ++*this; + return c; + } + + bool operator==(const sentinel_iterator& x) const { + if (is_sentinel()) { + return x.is_sentinel() || x.iter()==sentinel(); + } + else { + return x.is_sentinel()? iter()==x.sentinel(): iter()==x.iter(); + } + } + + bool operator!=(const sentinel_iterator& x) const { + return !(*this==x); + } + + // bidirectional iterator requirements + + sentinel_iterator& operator--() { + --iter(); + return *this; + } + + sentinel_iterator operator--(int) { + sentinel_iterator c(*this); + --*this; + return c; + } + + // random access iterator requirements + + sentinel_iterator &operator+=(difference_type n) { + iter() += n; + return *this; + } + + sentinel_iterator operator+(difference_type n) const { + sentinel_iterator c(*this); + return c += n; + } + + friend sentinel_iterator operator+(difference_type n, sentinel_iterator x) { + return x+n; + } + + sentinel_iterator& operator-=(difference_type n) { + iter() -= n; + return *this; + } + + sentinel_iterator operator-(difference_type n) const { + sentinel_iterator c(*this); + return c -= n; + } + + difference_type operator-(sentinel_iterator x) const { + return iter()-x.iter(); + } + + auto operator[](difference_type n) const -> decltype(*iter()){ + return *(iter()+n); + } + + bool operator<=(const sentinel_iterator& x) const { + return x.is_sentinel() || (!is_sentinel() && iter()<=x.iter()); + } + + bool operator<(const sentinel_iterator& x) const { + return !is_sentinel() && (x.is_sentinel() || iter()<=x.iter()); + } + + bool operator>=(const sentinel_iterator& x) const { + return !(x<*this); + } + + bool operator>(const sentinel_iterator& x) const { + return !(x<=*this); + } +}; + +template <typename I, typename S> +using sentinel_iterator_t = + typename std::conditional<std::is_same<I, S>::value, I, sentinel_iterator<I, S>>::type; + +template <typename I, typename S> +sentinel_iterator_t<I, S> make_sentinel_iterator(const I& i, const S& s) { + return sentinel_iterator_t<I, S>(i); +} + +template <typename I, typename S> +sentinel_iterator_t<I, S> make_sentinel_end(const I& i, const S& s) { + return sentinel_iterator_t<I, S>(s); +} + +template <typename Seq> +auto canonical_view(const Seq& s) -> + range<sentinel_iterator_t<decltype(std::begin(s)), decltype(std::end(s))>> +{ + return {make_sentinel_iterator(std::begin(s), std::end(s)), make_sentinel_end(std::begin(s), std::end(s))}; +} + +/* + * Present a single item as a range + */ + +template <typename T> +range<T*> singleton_view(T& item) { + return {&item, &item+1}; +} + +template <typename T> +range<const T*> singleton_view(const T& item) { + return {&item, &item+1}; +} + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/span.hpp b/src/util/span.hpp new file mode 100644 index 0000000000000000000000000000000000000000..06cdfa36924324c589af8a16412d8c167fb0429f --- /dev/null +++ b/src/util/span.hpp @@ -0,0 +1,33 @@ +#pragma once + +/* + * Presents a half-open interval [a,b) of integral values as a container. + */ + +#include <type_traits> +#include <utility> + +#include <util/counter.hpp> +#include <util/range.hpp> + +namespace nest { +namespace mc { +namespace util { + +template <typename I> +using span = range<counter<I>>; + +template <typename I, typename J> +span<typename std::common_type<I, J>::type> make_span(I left, J right) { + return span<typename std::common_type<I, J>::type>(left, right); +} + +template <typename I, typename J> +span<typename std::common_type<I, J>::type> make_span(std::pair<I, J> interval) { + return span<typename std::common_type<I, J>::type>(interval.first, interval.second); +} + + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/transform.hpp b/src/util/transform.hpp new file mode 100644 index 0000000000000000000000000000000000000000..f7d9fed04c721f09611757c84c54e5f13cd15942 --- /dev/null +++ b/src/util/transform.hpp @@ -0,0 +1,109 @@ +#pragma once + +/* + * An iterator adaptor that presents the values from an underlying + * iterator after applying a provided functor. + */ + +#include <iterator> +#include <memory> +#include <type_traits> + +#include <util/iterutil.hpp> +#include <util/meta.hpp> +#include <util/range.hpp> + +namespace nest { +namespace mc { +namespace util { + +template <typename I, typename F> +class transform_iterator: public iterator_adaptor<transform_iterator<I, F>, I> { + using base = iterator_adaptor<transform_iterator<I, F>, I>; + friend class iterator_adaptor<transform_iterator<I, F>, I>; + + I inner_; + F f_; + + // provides access to inner iterator for adaptor. + const I& inner() const { return inner_; } + I& inner() { return inner_; } + + using inner_value_type = util::decay_t<decltype(*inner_)>; + +public: + using typename base::difference_type; + using value_type = decltype(f_(*inner_)); + using pointer = const value_type*; + using reference = const value_type&; + + template <typename J, typename G> + transform_iterator(J&& c, G&& g): inner_{std::forward<J>(c)}, f_{std::forward<G>(g)} {} + + transform_iterator(const transform_iterator&) = default; + transform_iterator(transform_iterator&&) = default; + transform_iterator& operator=(const transform_iterator&) = default; + transform_iterator& operator=(transform_iterator&&) = default; + + // forward and input iterator requirements + + value_type operator*() const { + return f_(*inner_); + } + + util::pointer_proxy<value_type> operator->() const { + return **this; + } + + value_type operator[](difference_type n) const { + return *(*this+n); + } + + // public access to inner iterator + const I& get() const { return inner_; } + + bool operator==(const transform_iterator& x) const { return inner_==x.inner_; } + bool operator!=(const transform_iterator& x) const { return inner_!=x.inner_; } + + // expose inner iterator for testing against a sentinel + template <typename Sentinel> + bool operator==(const Sentinel& s) const { return inner_==s; } + + template <typename Sentinel> + bool operator!=(const Sentinel& s) const { return !(inner_==s); } +}; + +template <typename I, typename F> +transform_iterator<I, util::decay_t<F>> make_transform_iterator(const I& i, const F& f) { + return transform_iterator<I, util::decay_t<F>>(i, f); +} + +template < + typename Seq, + typename F, + typename seq_citer = typename sequence_traits<Seq>::const_iterator, + typename seq_csent = typename sequence_traits<Seq>::const_sentinel, + typename = enable_if_t<std::is_same<seq_citer, seq_csent>::value> +> +range<transform_iterator<seq_citer, util::decay_t<F>>> +transform_view(const Seq& s, const F& f) { + return {make_transform_iterator(cbegin(s), f), make_transform_iterator(cend(s), f)}; +} + + +template < + typename Seq, + typename F, + typename seq_citer = typename sequence_traits<Seq>::const_iterator, + typename seq_csent = typename sequence_traits<Seq>::const_sentinel, + typename = enable_if_t<!std::is_same<seq_citer, seq_csent>::value> +> +range<transform_iterator<seq_citer, util::decay_t<F>>, seq_csent> +transform_view(const Seq& s, const F& f) { + return {make_transform_iterator(cbegin(s), f), cend(s)}; +} + + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/src/util/uninitialized.hpp b/src/util/uninitialized.hpp index 846e46d5a2451fe1535f026ee4b801e5303a6f0c..8e3613cd6b342b40c7f2cd76edfd4e7acec5c582 100644 --- a/src/util/uninitialized.hpp +++ b/src/util/uninitialized.hpp @@ -18,25 +18,26 @@ namespace nest { namespace mc { namespace util { -/* Maintains storage for a value of type X, with explicit +/* + * Maintains storage for a value of type X, with explicit * construction and destruction. */ template <typename X> -struct uninitialized { +class uninitialized { private: typename std::aligned_storage<sizeof(X), alignof(X)>::type data; public: - using pointer_type = X*; - using const_pointer_type = const X*; - using reference_type = X&; - using const_reference_type = const X&; + using pointer = X*; + using const_pointer = const X*; + using reference = X&; + using const_reference= const X&; - pointer_type ptr() { return reinterpret_cast<X*>(&data); } - const_pointer_type cptr() const { return reinterpret_cast<const X*>(&data); } + pointer ptr() { return reinterpret_cast<X*>(&data); } + const_pointer cptr() const { return reinterpret_cast<const X*>(&data); } - reference_type ref() { return *reinterpret_cast<X*>(&data); } - const_reference_type cref() const { return *reinterpret_cast<const X*>(&data); } + reference ref() { return *reinterpret_cast<X*>(&data); } + const_reference cref() const { return *reinterpret_cast<const X*>(&data); } // Copy construct the value. template < @@ -60,45 +61,46 @@ public: // Apply the one-parameter functor F to the value by reference. template <typename F> - result_of_t<F(reference_type)> apply(F&& f) { return f(ref()); } + result_of_t<F(reference)> apply(F&& f) { return f(ref()); } // Apply the one-parameter functor F to the value by const reference. template <typename F> - result_of_t<F(const_reference_type)> apply(F&& f) const { return f(cref()); } + result_of_t<F(const_reference)> apply(F&& f) const { return f(cref()); } }; -/* Maintains storage for a pointer of type X, representing +/* + * Maintains storage for a pointer of type X, representing * a possibly uninitialized reference. */ template <typename X> -struct uninitialized<X&> { +class uninitialized<X&> { private: X *data; public: - using pointer_type = X*; - using const_pointer_type = const X*; - using reference_type = X&; - using const_reference_type = const X&; + using pointer = X*; + using const_pointer = const X*; + using reference = X&; + using const_reference = const X&; - pointer_type ptr() { return data; } - const_pointer_type cptr() const { return data; } + pointer ptr() { return data; } + const_pointer cptr() const { return data; } - reference_type ref() { return *data; } - const_reference_type cref() const { return *data; } + reference ref() { return *data; } + const_reference cref() const { return *data; } void construct(X& x) { data = &x; } void destruct() {} // Apply the one-parameter functor F to the value by reference. template <typename F> - result_of_t<F(reference_type)> apply(F&& f) { + result_of_t<F(reference)> apply(F&& f) { return f(ref()); } // Apply the one-parameter functor F to the value by const reference. template <typename F> - result_of_t<F(const_reference_type)> apply(F&& f) const { + result_of_t<F(const_reference)> apply(F&& f) const { return f(cref()); } }; @@ -108,17 +110,18 @@ public: * Allows the use of uninitialized<X> for void X, for generic applications. */ template <> -struct uninitialized<void> { - using pointer_type = void*; - using const_pointer_type = const void*; - using reference_type = void; - using const_reference_type = void; +class uninitialized<void> { +public: + using pointer = void*; + using const_pointer = const void*; + using reference = void; + using const_reference = void; - pointer_type ptr() { return nullptr; } - const_pointer_type cptr() const { return nullptr; } + pointer ptr() { return nullptr; } + const_pointer cptr() const { return nullptr; } - reference_type ref() {} - const_reference_type cref() const {} + reference ref() {} + const_reference cref() const {} // No operation. void construct(...) {} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 10fb65efd052c1d9d7d7fdd5f50bdec0f93cb5a5..c39e22a80f408f824af43f31f1d5e965b1f37ff5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -14,6 +14,9 @@ add_subdirectory(validation) # Test for the internode communication (eg. mpi) add_subdirectory(global_communication) +# Tests for performance: This could include stand alone tests. These do not necessarily be run automatically +add_subdirectory(performance) + # Proposed additional test types: @@ -21,7 +24,6 @@ add_subdirectory(global_communication) # Test to check integration between components -# Tests for performance: This could include stand alone tests. These do not necessarily be run automatically # Numbered tests based on bugs in the tracker diff --git a/tests/global_communication/CMakeLists.txt b/tests/global_communication/CMakeLists.txt index f7d000157e4cfd130399d1f718b67fbc5eb2f86d..fc8531dac8561fa748479af8d7633e9339c9a4f4 100644 --- a/tests/global_communication/CMakeLists.txt +++ b/tests/global_communication/CMakeLists.txt @@ -1 +1,32 @@ -# Nothing to be done yet +set(HEADERS + ${PROJECT_SOURCE_DIR}/src/swcio.hpp +) +set(COMMUNICATION_SOURCES + test_exporter_spike_file.cpp + # unit test driver + test.cpp +) + +add_executable(global_communication.exe ${COMMUNICATION_SOURCES} ${HEADERS}) + +set(TARGETS global_communication.exe) + +foreach(target ${TARGETS}) + target_link_libraries(${target} LINK_PUBLIC cellalgo gtest) + + if(WITH_TBB) + target_link_libraries(${target} LINK_PUBLIC ${TBB_LIBRARIES}) + endif() + + if(WITH_MPI) + target_link_libraries(${target} LINK_PUBLIC ${MPI_C_LIBRARIES}) + set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS "${MPI_C_LINK_FLAGS}") + endif() + + set_target_properties( + ${target} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests" + ) +endforeach() + diff --git a/tests/global_communication/test.cpp b/tests/global_communication/test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c67a065bd88ed85aa56e865b1d4f8f9fa3d5677e --- /dev/null +++ b/tests/global_communication/test.cpp @@ -0,0 +1,19 @@ +#include <iostream> +#include <fstream> +#include <numeric> +#include <vector> + +#include "gtest.h" + +#include "../../src/communication/communicator.hpp" +#include "../../src/communication/global_policy.hpp" + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + + // We need to set the communicator policy at the top level + // this allows us to build multiple communicators in the tests + nest::mc::communication::global_policy_guard global_guard(argc, argv); + + return RUN_ALL_TESTS(); +} diff --git a/tests/global_communication/test_exporter_spike_file.cpp b/tests/global_communication/test_exporter_spike_file.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4a85b0784801df6f91c72db70b44b69b36337f2b --- /dev/null +++ b/tests/global_communication/test_exporter_spike_file.cpp @@ -0,0 +1,115 @@ +#include "gtest.h" + +#include <cstdio> +#include <fstream> +#include <iostream> +#include <string> +#include <vector> + +#include <communication/communicator.hpp> +#include <communication/global_policy.hpp> +#include <io/exporter_spike_file.hpp> + +class exporter_spike_file_fixture : public ::testing::Test { +protected: + using time_type = float; + using communicator_type = nest::mc::communication::global_policy; + + using exporter_type = + nest::mc::io::exporter_spike_file<time_type, communicator_type>; + using spike_type = exporter_type::spike_type; + + std::string file_name_; + std::string path_; + std::string extension_; + unsigned index_; + + exporter_spike_file_fixture() : + file_name_("spikes_exporter_spike_file_fixture"), + path_("./"), + extension_("gdf"), + index_(communicator_type::id()) + {} + + std::string get_standard_file_name() { + return exporter_type::create_output_file_path(file_name_, path_, extension_, index_); + } + + void SetUp() { + // code here will execute just before the test ensues + } + + void TearDown() { + // delete the start create file + std::remove(get_standard_file_name().c_str()); + } + + ~exporter_spike_file_fixture() + {} +}; + +TEST_F(exporter_spike_file_fixture, constructor) { + exporter_type exporter(file_name_, path_, extension_, true); + + //test if the file exist and depending on over_write throw or delete + std::ifstream f(get_standard_file_name()); + EXPECT_TRUE(f.good()); + + // We now know the file exists, so create a new exporter with overwrite false + try { + exporter_type exporter1(file_name_, path_, extension_, false); + FAIL() << "expected a file already exists error"; + } + catch (const std::runtime_error& err) { + EXPECT_EQ( + err.what(), + "Tried opening file for writing but it exists and over_write is false: " + + get_standard_file_name() + ); + } + catch (...) { + FAIL() << "expected a file already exists error"; + } +} + +TEST_F(exporter_spike_file_fixture, create_output_file_path) { + // Create some random paths, no need for fancy tests here + std::string produced_filename = + exporter_type::create_output_file_path("spikes", "./", "gdf", 0); + EXPECT_STREQ(produced_filename.c_str(), "./spikes_0.gdf"); + + produced_filename = + exporter_type::create_output_file_path("a_name", "../../", "txt", 5); + EXPECT_STREQ(produced_filename.c_str(), "../../a_name_5.txt"); +} + +TEST_F(exporter_spike_file_fixture, do_export) { + { + exporter_type exporter(file_name_, path_, extension_); + + // Create some spikes + std::vector<spike_type> spikes; + spikes.push_back({ { 0, 0 }, 0.0 }); + spikes.push_back({ { 0, 0 }, 0.1 }); + spikes.push_back({ { 1, 0 }, 1.0 }); + spikes.push_back({ { 1, 0 }, 1.1 }); + + // now do the export + exporter.output(spikes); + } + + // Test if we have spikes in the file? + std::ifstream f(get_standard_file_name()); + EXPECT_TRUE(f.good()); + + std::string line; + + EXPECT_TRUE(std::getline(f, line)); + EXPECT_STREQ(line.c_str(), "0 0.0000"); + EXPECT_TRUE(std::getline(f, line)); + EXPECT_STREQ(line.c_str(), "0 0.1000"); + EXPECT_TRUE(std::getline(f, line)); + EXPECT_STREQ(line.c_str(), "1 1.0000"); + EXPECT_TRUE(std::getline(f, line)); + EXPECT_STREQ(line.c_str(), "1 1.1000"); +} diff --git a/tests/performance/CMakeLists.txt b/tests/performance/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..351be926371b61bcf130d43efc42e5fb0ff53eee --- /dev/null +++ b/tests/performance/CMakeLists.txt @@ -0,0 +1,2 @@ +# Unit tests +add_subdirectory(io) diff --git a/tests/performance/io/CMakeLists.txt b/tests/performance/io/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..5bfe045ebf782c298702bd107201c9c343a2a297 --- /dev/null +++ b/tests/performance/io/CMakeLists.txt @@ -0,0 +1,22 @@ +set(HEADERS +) + +set(DISK_IO_SOURCES + disk_io.cpp +) + +add_executable(disk_io.exe ${DISK_IO_SOURCES} ${HEADERS}) + +target_link_libraries(disk_io.exe LINK_PUBLIC cellalgo) + +if(WITH_TBB) + target_link_libraries(disk_io.exe LINK_PUBLIC ${TBB_LIBRARIES}) +endif() + +if(WITH_MPI) + target_link_libraries(disk_io.exe LINK_PUBLIC ${MPI_C_LIBRARIES}) + set_property(TARGET disk_io.exe APPEND_STRING PROPERTY LINK_FLAGS "${MPI_C_LINK_FLAGS}") +endif() + +# Copy the python file that drives the performance tests and produces the output +file(COPY disk_io.py DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/tests/performance/io/disk_io.cpp b/tests/performance/io/disk_io.cpp new file mode 100644 index 0000000000000000000000000000000000000000..526263bf395a81eb36ed050b96cfe1923e04cd25 --- /dev/null +++ b/tests/performance/io/disk_io.cpp @@ -0,0 +1,160 @@ +#include <stdio.h> + +#include <fstream> +#include <iostream> +#include <numeric> + +#include <cell.hpp> +#include <cell_group.hpp> +#include <common_types.hpp> +#include <communication/communicator.hpp> +#include <communication/global_policy.hpp> +#include <fvm_cell.hpp> +#include <io/exporter_spike_file.hpp> +#include <profiling/profiler.hpp> + +using namespace nest::mc; + +using global_policy = communication::global_policy; +using lowered_cell = fvm::fvm_cell<double, cell_local_size_type>; +using cell_group_type = cell_group<lowered_cell>; +using time_type = typename cell_group_type::time_type; +using spike_type = io::exporter_spike_file<time_type, global_policy>::spike_type; +using timer = util::timer_type; + +int main(int argc, char** argv) { + + //Setup the possible mpi environment + communication::global_policy_guard global_guard(argc, argv); + + // very simple command line parsing + if (argc < 3) { + 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\n" + + << " <file_per_rank> true will produce a single file per mpi rank\n" + << " <simple_output> true will produce a simplyfied comma seperated output for automatic parsing\n\n" + + << " The application can be started with mpi support and will produce output on a single rank\n" + << " if nrspikes is not a multiple of the nr of mpi rank, floor is take\n" ; + return 1; + } + auto nr_spikes = atoi(argv[1]); + + if (nr_spikes == 0) { + std::cout << "disk_io <nrspikes>\n"; + std::cout << " nrspikes should be a valid integer higher then zero\n"; + + return 1; + } + auto nr_repeats = atoi(argv[2]); + + if (nr_repeats == 0) { + std::cout << "disk_io <nrspikes>\n"; + std::cout << " nr_repeats should be a valid integer higher then zero\n"; + return 1; + } + + auto simple_stats = false; + if (argc == 4) { + std::string simple(argv[3]); + if (simple == std::string("true")) + { + simple_stats = true; + } + } + + // Create the sut + io::exporter_spike_file<time_type, global_policy> exporter( + "spikes", "./", "gdf", true); + + // We need the nr of ranks to calculate the nr of spikes to produce per + // rank + global_policy communication_policy; + + auto nr_ranks = unsigned( communication_policy.size() ); + auto spikes_per_rank = nr_spikes / nr_ranks; + + // Create a set of spikes + std::vector<spike_type> spikes; + + // ********************************************************************* + // To have a somewhat realworld data set we calculate from the nr of spikes + // (assuming 20 hz average) the number of nr of 'simulated' neurons, + // and create idxs using this value. The number of chars in the number + // influences the size of the output and thus the speed + // Also taken that we have only a single second of simulated time + // all spike times should be between 0.0 and 1.0: + auto simulated_neurons = spikes_per_rank / 20; + for (auto idx = unsigned{ 0 }; idx < spikes_per_rank; ++idx) { + + spikes.push_back({ + {idx % simulated_neurons, 0 }, // correct idx + 0.0f + 1 / (0.05f + idx % 20) + }); // semi random float + } + + double timings_arr[nr_repeats]; + double time_total = 0; + + // now output to disk nr_repeats times, while keeping track of the times + for (auto idx = 0; idx < nr_repeats; ++idx) { + auto time_start = timer::tic(); + exporter.output(spikes); + auto run_time = timer::toc(time_start); + + time_total += run_time; + timings_arr[idx] = run_time; + } + + // create the vector here to prevent changes on the heap influencing the + // timeing + std::vector<double> timings; + for (auto idx = 0; idx < nr_repeats; ++idx) { + timings.push_back(timings_arr[idx]); + } + + + // Calculate some statistics + auto sum = std::accumulate(timings.begin(), timings.end(), 0.0); + auto mean = sum / timings.size(); + + std::vector<double> diff(timings.size()); + std::transform( + timings.begin(), timings.end(), diff.begin(), + std::bind2nd(std::minus<double>(), mean) + ); + auto sq_sum = std::inner_product( + diff.begin(), diff.end(), diff.begin(), + 0.0 + ); + auto stdev = std::sqrt(sq_sum / timings.size()); + + auto min = *std::min_element(timings.begin(), timings.end()); + auto max = *std::max_element(timings.begin(), timings.end()); + + + if (communication_policy.id() != 0) { + return 0; + } + + // and output + if (simple_stats) { + std::cout << time_total<< "," + << mean << "," + << stdev << "," + << min << "," + << max << std::endl; + } + else { + std::cout << "total time (ms): " << time_total << std::endl; + std::cout << "mean time (ms): " << mean << std::endl; + std::cout << "stdev time (ms): " << std::endl; + std::cout << "min time (ms): " << min << std::endl; + std::cout << "max time (ms): " << max << std::endl; + } + + return 0; +} diff --git a/tests/performance/io/disk_io.py b/tests/performance/io/disk_io.py new file mode 100644 index 0000000000000000000000000000000000000000..ea42304b23e2da851058b8b6d7625f8185ec0db3 --- /dev/null +++ b/tests/performance/io/disk_io.py @@ -0,0 +1,49 @@ +import matplotlib.pyplot as plt +import subprocess +import os + + + +current_script_dir = os.path.dirname(os.path.abspath(__file__)) + +spikes_to_save = 1000000 + +print ( "Simple performance runner for spike output to file. \n" + + str(spikes_to_save) + " spikes will be written to a file and the duration of this \n" + + "operation measured for different number of ranks\n" ) + + +range_nr_rank = [1, 2, 4, 8, 16, 24, 32, 48, 64] +mean = [] +std = [] +min = [] +max = [] +for n_rank in range_nr_rank: + # open the disk_io executable + p1 = subprocess.Popen(["mpirun", "-n",str(n_rank), + os.path.join(current_script_dir, "disk_io.exe"), + str(spikes_to_save), str(10), "true"], + stdout=subprocess.PIPE) + + #and grab the raw stats + stats = p1.communicate()[0] + + # convert into list + stats = stats.split(",") + + mean.append(float(stats[1])) + std.append(float(stats[2])) + min.append(float(stats[3])) + max.append(float(stats[4])) + + print ("performed test for n_rank= " + str(n_rank)) + +print (range_nr_rank) +print (mean) +print (std) + +plt.errorbar(range_nr_rank, mean, yerr=std, fmt='-o', label="mean (std)") +plt.errorbar(range_nr_rank, min, fmt='-', label="min") +plt.errorbar(range_nr_rank, max, fmt='-', label="max") +plt.legend() +plt.show() diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index fb1b9369d4e38498c8953fc0ce2f16c1dbc715dc..6b34d70125bb4d9792f8abb6b68daf411dfe78d8 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -14,6 +14,8 @@ set(TEST_SOURCES test_double_buffer.cpp test_cell.cpp test_compartments.cpp + test_counter.cpp + test_either.cpp test_event_queue.cpp test_fvm.cpp test_cell_group.cpp @@ -21,17 +23,22 @@ set(TEST_SOURCES test_mask_stream.cpp test_matrix.cpp test_mechanisms.cpp + test_nop.cpp test_optional.cpp test_parameters.cpp + test_partition.cpp test_point.cpp test_probe.cpp test_segment.cpp + test_range.cpp + test_span.cpp test_spikes.cpp test_spike_store.cpp test_stimulus.cpp test_swcio.cpp test_synapses.cpp test_tree.cpp + test_transform.cpp test_uninitialized.cpp # unit test driver diff --git a/tests/unit/test_counter.cpp b/tests/unit/test_counter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7c97f2277e08a865ebb0be0a7e7b9d4db18f7360 --- /dev/null +++ b/tests/unit/test_counter.cpp @@ -0,0 +1,131 @@ +#include "gtest.h" + +#include <iterator> +#include <type_traits> + +#include <util/counter.hpp> + +using namespace nest::mc; + +template <typename V> +class counter_test: public ::testing::Test {}; + +TYPED_TEST_CASE_P(counter_test); + +TYPED_TEST_P(counter_test, value) { + using int_type = TypeParam; + using counter = util::counter<int_type>; + + counter c0; + EXPECT_EQ(int_type{0}, *c0); + + counter c1{int_type{1}}; + counter c2{int_type{2}}; + + EXPECT_EQ(int_type{1}, *c1); + EXPECT_EQ(int_type{2}, *c2); + + c2 = c1; + EXPECT_EQ(int_type{1}, *c2); + + c2 = int_type{2}; + EXPECT_EQ(int_type{2}, *c2); +} + +TYPED_TEST_P(counter_test, compare) { + using int_type = TypeParam; + using counter = util::counter<int_type>; + + counter c1{int_type{1}}; + counter c2{int_type{2}}; + + EXPECT_LT(c1, c2); + EXPECT_LE(c1, c2); + EXPECT_NE(c1, c2); + EXPECT_GE(c2, c1); + EXPECT_GT(c2, c1); + + counter c1bis{int_type{1}}; + + EXPECT_LE(c1, c1bis); + EXPECT_EQ(c1, c1bis); + EXPECT_GE(c1, c1bis); +} + +TYPED_TEST_P(counter_test, arithmetic) { + using int_type = TypeParam; + using counter = util::counter<int_type>; + + counter c1{int_type{1}}; + counter c2{int_type{10}}; + int_type nine{9}; + + EXPECT_EQ(nine, c2-c1); + EXPECT_EQ(c2, c1+nine); + EXPECT_EQ(c2, nine+c1); + EXPECT_EQ(c1, c2-nine); + + counter c3 = c1; + counter c4 = (c3 += nine); + + EXPECT_EQ(c2, c3); + EXPECT_EQ(c3, c4); + + c3 = c2; + c4 = (c3 -= nine); + + EXPECT_EQ(c1, c3); + EXPECT_EQ(c3, c4); + + c3 = c1; + EXPECT_EQ(counter{2}, ++c3); + EXPECT_EQ(counter{3}, ++c3); + EXPECT_EQ(counter{2}, --c3); + EXPECT_EQ(counter{1}, --c3); + + c3 = c1; + EXPECT_EQ(counter{1}, c3++); + EXPECT_EQ(counter{2}, c3++); + EXPECT_EQ(counter{3}, c3--); + EXPECT_EQ(counter{2}, c3--); + + EXPECT_EQ(int_type{10}, c2[0]); + EXPECT_EQ(int_type{4}, c2[-6]); + EXPECT_EQ(int_type{19}, c2[9]); +} + +TYPED_TEST_P(counter_test, iterator_traits) { + using int_type = TypeParam; + using counter = util::counter<int_type>; + using traits = std::iterator_traits<counter>; + + typename traits::reference r = *counter{int_type{3}}; + EXPECT_EQ(r, int_type{3}); + + typename traits::difference_type d = counter{int_type{4}} - counter{int_type{7}}; + EXPECT_EQ(typename traits::difference_type(-3), d); + + EXPECT_TRUE((std::is_same<std::random_access_iterator_tag, typename traits::iterator_category>::value)); +} + +TYPED_TEST_P(counter_test, iterator_functions) { + using int_type = TypeParam; + using counter = util::counter<int_type>; + + counter c1{int_type{1}}; + counter c2{int_type{10}}; + + EXPECT_EQ(int_type{9}, std::distance(c1,c2)); + counter c3{c1}; + std::advance(c3, int_type{9}); + EXPECT_EQ(c2, c3); + + EXPECT_EQ(counter{int_type{2}}, std::next(c1)); + EXPECT_EQ(counter{int_type{9}}, std::prev(c2)); +} + +REGISTER_TYPED_TEST_CASE_P(counter_test, value, compare, arithmetic, iterator_traits, iterator_functions); + +using int_types = ::testing::Types<signed char, unsigned char, short, unsigned short, int, unsigned, std::size_t, std::ptrdiff_t>; +INSTANTIATE_TYPED_TEST_CASE_P(int_types, counter_test, int_types); + diff --git a/tests/unit/test_either.cpp b/tests/unit/test_either.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4d185d8bf5eea7c7f5d0e75395d6f7e6d0f46f53 --- /dev/null +++ b/tests/unit/test_either.cpp @@ -0,0 +1,64 @@ +#include <typeinfo> +#include <array> +#include <algorithm> + +#include "gtest.h" +#include "util/either.hpp" + +// TODO: coverage! + +using namespace nest::mc::util; + +TEST(either, basic) { + either<int, std::string> e0(17); + + EXPECT_TRUE(e0); + EXPECT_EQ(17, e0.get<0>()); + EXPECT_EQ(e0.unsafe_get<0>(), e0.get<0>()); + EXPECT_EQ(e0.unsafe_get<0>(), e0.first()); + EXPECT_THROW(e0.get<1>(), either_invalid_access); + either<int, std::string> e1("seventeen"); + + EXPECT_FALSE(e1); + EXPECT_EQ("seventeen", e1.get<1>()); + EXPECT_EQ(e1.unsafe_get<1>(), e1.get<1>()); + EXPECT_EQ(e1.unsafe_get<1>(), e1.second()); + EXPECT_THROW(e1.get<0>(), either_invalid_access); + + e0 = e1; + EXPECT_EQ("seventeen", e0.get<1>()); + EXPECT_THROW(e0.get<0>(), either_invalid_access); + + e0 = 19; + EXPECT_EQ(19, e0.get<0>()); +} + +struct no_copy { + int value; + + no_copy(): value(23) {} + explicit no_copy(int v): value(v) {} + no_copy(const no_copy&) = delete; + no_copy(no_copy&&) = default; + + no_copy& operator=(const no_copy&) = delete; + no_copy& operator=(no_copy&&) = default; +}; + +TEST(either, no_copy) { + either<no_copy, std::string> e0(no_copy{17}); + + EXPECT_TRUE(e0); + + either<no_copy, std::string> e1(std::move(e0)); + + EXPECT_TRUE(e1); + + either<no_copy, std::string> e2; + EXPECT_TRUE(e2); + EXPECT_EQ(23, e2.get<0>().value); + + e2 = std::move(e1); + EXPECT_TRUE(e2); + EXPECT_EQ(17, e2.get<0>().value); +} 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) { diff --git a/tests/unit/test_partition.cpp b/tests/unit/test_partition.cpp new file mode 100644 index 0000000000000000000000000000000000000000..edfb95d1021ea452c1701d9a84ef2341ed17e48f --- /dev/null +++ b/tests/unit/test_partition.cpp @@ -0,0 +1,150 @@ +#include "gtest.h" + +#include <array> +#include <forward_list> +#include <string> +#include <vector> + +#include <util/debug.hpp> +#include <util/nop.hpp> +#include <util/partition.hpp> + +using namespace nest::mc; + +TEST(partition, partition_view) { + std::forward_list<int> fl = {1, 4, 6, 8, 10 }; + + auto p1 = util::partition_view(fl); + EXPECT_EQ(std::make_pair(1,4), p1.front()); + EXPECT_EQ(std::make_pair(8,10), p1.back()); + EXPECT_EQ(std::make_pair(1,10), p1.bounds()); + EXPECT_EQ(4u, p1.size()); + + std::vector<double> v = {2.0, 3.6, 7.5}; + + auto p2 = util::partition_view(v); + EXPECT_EQ(2u, p2.size()); + + std::vector<double> ends; + std::vector<double> ends_expected = { 2.0, 3.6, 3.6, 7.5 }; + for (auto b: p2) { + ends.push_back(b.first); + ends.push_back(b.second); + } + EXPECT_EQ(ends_expected, ends); +} + +TEST(partition, short_partition_view) { + int two_divs[] = {10, 15}; + EXPECT_EQ(1u, util::partition_view(two_divs).size()); + + int one_div[] = {10}; + EXPECT_EQ(0u, util::partition_view(one_div).size()); + + std::array<int, 0> zero_divs; + EXPECT_EQ(0u, util::partition_view(zero_divs).size()); +} + +TEST(partition, check_monotonicity) { + // override any EXPECTS checks in partition + util::global_failed_assertion_handler = util::ignore_failed_assertion; + + int divs_ok[] = {1, 2, 2, 3, 3}; + EXPECT_NO_THROW(util::partition_view(divs_ok).validate()); + + int divs_bad[] = {3, 2, 1}; + EXPECT_THROW(util::partition_view(divs_bad).validate(), util::invalid_partition); +} + +TEST(partition, partition_view_find) { + std::vector<double> divs = { 1, 2.5, 3, 5.5 }; + double eps = 0.1; + auto p = util::partition_view(divs); + + EXPECT_EQ(p.end(), p.find(divs.front()-eps)); + EXPECT_NE(p.end(), p.find(divs.front())); + EXPECT_EQ(divs.front(), p.find(divs.front())->first); + + EXPECT_NE(p.end(), p.find(divs.back()-eps)); + EXPECT_EQ(divs.back(), p.find(divs.back()-eps)->second); + EXPECT_EQ(p.end(), p.find(divs.back())); + EXPECT_EQ(p.end(), p.find(divs.back()+eps)); + + EXPECT_EQ(divs[1], p.find(divs[1]+eps)->first); + EXPECT_EQ(divs[2], p.find(divs[1]+eps)->second); +} + +TEST(partition, partition_view_non_numeric) { + std::string divs[] = { "a", "dictionary", "of", "sorted", "words" }; + auto p = util::partition_view(divs); + + EXPECT_EQ("dictionary", p.find("elephant")->first); +} + +TEST(partition, make_partition_in_place) { + unsigned sizes[] = { 7, 3, 0, 2 }; + unsigned part_store[util::size(sizes)+1]; + + auto p = util::make_partition(util::partition_in_place, part_store, sizes, 10u); + ASSERT_EQ(4u, p.size()); + EXPECT_EQ(std::make_pair(10u, 17u), p[0]); + EXPECT_EQ(std::make_pair(17u, 20u), p[1]); + EXPECT_EQ(std::make_pair(20u, 20u), p[2]); + EXPECT_EQ(std::make_pair(20u, 22u), p[3]); + + // with short sizes sequence + unsigned short_sizes[] = { 1, 2 }; + p = util::make_partition(util::partition_in_place, part_store, short_sizes, 0u); + ASSERT_EQ(4u, p.size()); + EXPECT_EQ(std::make_pair(0u, 1u), p[0]); + EXPECT_EQ(std::make_pair(1u, 3u), p[1]); + EXPECT_EQ(std::make_pair(3u, 3u), p[2]); + EXPECT_EQ(std::make_pair(3u, 3u), p[3]); + + // with longer sizes sequence + unsigned long_sizes[] = {1, 2, 3, 4, 5, 6}; + p = util::make_partition(util::partition_in_place, part_store, long_sizes, 0u); + ASSERT_EQ(4u, p.size()); + EXPECT_EQ(std::make_pair(0u, 1u), p[0]); + EXPECT_EQ(std::make_pair(1u, 3u), p[1]); + EXPECT_EQ(std::make_pair(3u, 6u), p[2]); + EXPECT_EQ(std::make_pair(6u, 10u), p[3]); + + // with empty sizes sequence + std::array<unsigned, 0> no_sizes; + p = util::make_partition(util::partition_in_place, part_store, no_sizes, 17u); + ASSERT_EQ(4u, p.size()); + EXPECT_EQ(std::make_pair(17u, 17u), p[0]); + EXPECT_EQ(std::make_pair(17u, 17u), p[1]); + EXPECT_EQ(std::make_pair(17u, 17u), p[2]); + EXPECT_EQ(std::make_pair(17u, 17u), p[3]); + + // with short partition containers + unsigned part_store_one[1]; + p = util::make_partition(util::partition_in_place, part_store_one, sizes, 10u); + ASSERT_EQ(0u, p.size()); + ASSERT_TRUE(p.empty()); + + std::array<unsigned,0> part_store_zero; + p = util::make_partition(util::partition_in_place, part_store_zero, sizes, 10u); + ASSERT_EQ(0u, p.size()); + ASSERT_TRUE(p.empty()); +} + +TEST(partition, make_partition) { + // (also tests differing types for sizes and divisiosn) + unsigned sizes[] = { 7, 3, 0, 2 }; + std::forward_list<double> part_store = { 100.3 }; + + auto p = util::make_partition(part_store, sizes, 10.0); + ASSERT_EQ(4u, p.size()); + + auto pi = p.begin(); + EXPECT_EQ(10.0, pi++->first); + EXPECT_EQ(17.0, pi++->first); + EXPECT_EQ(20.0, pi++->first); + EXPECT_EQ(20.0, pi->first); + EXPECT_EQ(22.0, pi->second); + + EXPECT_EQ(p.end(), ++pi); +} diff --git a/tests/unit/test_range.cpp b/tests/unit/test_range.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7847212e3f0b0546c1f974dbdebab3303a15ea1d --- /dev/null +++ b/tests/unit/test_range.cpp @@ -0,0 +1,171 @@ +#include "gtest.h" + +#include <algorithm> +#include <iterator> +#include <sstream> +#include <list> +#include <numeric> +#include <type_traits> + +#ifdef WITH_TBB +#include <tbb/tbb_stddef.h> +#endif + +#include <util/range.hpp> + +using namespace nest::mc; + +TEST(range, list_iterator) { + std::list<int> l = { 2, 4, 6, 8, 10 }; + + auto s = util::make_range(l.begin(), l.end()); + + EXPECT_EQ(s.left, l.begin()); + EXPECT_EQ(s.right, l.end()); + + EXPECT_EQ(s.begin(), l.begin()); + EXPECT_EQ(s.end(), l.end()); + + EXPECT_EQ(s.size(), l.size()); + EXPECT_EQ(s.front(), *l.begin()); + EXPECT_EQ(s.back(), *std::prev(l.end())); + + int check = std::accumulate(l.begin(), l.end(), 0); + int sum = 0; + for (auto i: s) { + sum += i; + } + + EXPECT_EQ(check, sum); + + auto sum2 = std::accumulate(s.begin(), s.end(), 0); + EXPECT_EQ(check, sum2); +} + +TEST(range, pointer) { + int xs[] = { 10, 11, 12, 13, 14, 15, 16 }; + int l = 2; + int r = 5; + + util::range<int *> s(&xs[l], &xs[r]); + auto s_deduced = util::make_range(xs+l, xs+r); + + EXPECT_TRUE((std::is_same<decltype(s), decltype(s_deduced)>::value)); + EXPECT_EQ(s.left, s_deduced.left); + EXPECT_EQ(s.right, s_deduced.right); + + EXPECT_EQ(3u, s.size()); + + EXPECT_EQ(xs[l], *s.left); + EXPECT_EQ(xs[l], *s.begin()); + EXPECT_EQ(xs[l], s[0]); + EXPECT_EQ(xs[l], s.front()); + + EXPECT_EQ(xs[r], *s.right); + EXPECT_EQ(xs[r], *s.end()); + EXPECT_THROW(s.at(r-l), std::out_of_range); + + EXPECT_EQ(r-l, std::distance(s.begin(), s.end())); + + EXPECT_TRUE(std::equal(s.begin(), s.end(), &xs[l])); +} + +TEST(range, input_iterator) { + int nums[] = { 10, 9, 8, 7, 6 }; + std::istringstream sin("10 9 8 7 6"); + auto s = util::make_range(std::istream_iterator<int>(sin), std::istream_iterator<int>()); + + EXPECT_TRUE(std::equal(s.begin(), s.end(), &nums[0])); +} + +TEST(range, const_iterator) { + std::vector<int> xs = { 1, 2, 3, 4, 5 }; + auto r = util::make_range(xs.begin(), xs.end()); + EXPECT_TRUE((std::is_same<int&, decltype(r.front())>::value)); + + const auto& xs_const = xs; + auto r_const = util::make_range(xs_const.begin(), xs_const.end()); + EXPECT_TRUE((std::is_same<const int&, decltype(r_const.front())>::value)); +} + +struct null_terminated_t { + bool operator==(const char *p) const { return !*p; } + bool operator!=(const char *p) const { return !!*p; } + + friend bool operator==(const char *p, null_terminated_t x) { + return x==p; + } + + friend bool operator!=(const char *p, null_terminated_t x) { + return x!=p; + } + + constexpr null_terminated_t() {} +}; + +constexpr null_terminated_t null_terminated; + +TEST(range, sentinel) { + const char *cstr = "hello world"; + std::string s; + + auto cstr_range = util::make_range(cstr, null_terminated); + for (auto i=cstr_range.begin(); i!=cstr_range.end(); ++i) { + s += *i; + } + + EXPECT_EQ(s, std::string(cstr)); + + s.clear(); + for (auto c: canonical_view(cstr_range)) { + s += c; + } + + EXPECT_EQ(s, std::string(cstr)); +} + +#ifdef WITH_TBB + +TEST(range, tbb_split) { + constexpr std::size_t N = 20; + int xs[N]; + + for (unsigned i = 0; i<N; ++i) { + xs[i] = i; + } + + auto s = util::make_range(&xs[0], &xs[0]+N); + + while (s.size()>1) { + auto ssize = s.size(); + auto r = decltype(s){s, tbb::split{}}; + EXPECT_GT(r.size(), 0u); + EXPECT_GT(s.size(), 0u); + EXPECT_EQ(ssize, r.size()+s.size()); + EXPECT_EQ(s.end(), r.begin()); + + EXPECT_TRUE(r.size()>1 || !r.is_divisible()); + EXPECT_TRUE(s.size()>1 || !s.is_divisible()); + } + + for (unsigned i = 1; i<N-1; ++i) { + s = util::make_range(&xs[0], &xs[0]+N); + // expect exact splitting by proportion in this instance + + auto r = decltype(s){s, tbb::proportional_split{i, N-i}}; + EXPECT_EQ(&xs[0], s.left); + EXPECT_EQ(&xs[0]+i, s.right); + EXPECT_EQ(&xs[0]+i, r.left); + EXPECT_EQ(&xs[0]+N, r.right); + } +} + +TEST(range, tbb_no_split) { + std::istringstream sin("10 9 8 7 6"); + auto s = util::make_range(std::istream_iterator<int>(sin), std::istream_iterator<int>()); + + EXPECT_FALSE(decltype(s)::is_splittable_in_proportion()); + EXPECT_FALSE(s.is_divisible()); +} + +#endif diff --git a/tests/unit/test_span.cpp b/tests/unit/test_span.cpp new file mode 100644 index 0000000000000000000000000000000000000000..edb55f7d65e7f0321add82de6e0879143050fede --- /dev/null +++ b/tests/unit/test_span.cpp @@ -0,0 +1,93 @@ +#include "gtest.h" + +#include <algorithm> +#include <iterator> +#include <list> +#include <numeric> +#include <type_traits> +#include <utility> + +#include <util/span.hpp> + +using namespace nest::mc; + +TEST(span, int_access) { + using span = util::span<int>; + + int n = 97; + int a = 3; + int b = a+n; + + span s(a, b); + EXPECT_EQ(s.left, a); + EXPECT_EQ(s.right, b); + + EXPECT_EQ(s.size(), std::size_t(n)); + + EXPECT_EQ(s.front(), a); + EXPECT_EQ(s.back(), b-1); + + EXPECT_EQ(s[0], a); + EXPECT_EQ(s[1], a+1); + EXPECT_EQ(s[n-1], b-1); + + EXPECT_NO_THROW(s.at(0)); + EXPECT_NO_THROW(s.at(n-1)); + EXPECT_THROW(s.at(n), std::out_of_range); + EXPECT_THROW(s.at(n+1), std::out_of_range); + EXPECT_THROW(s.at(-1), std::out_of_range); +} + +TEST(span, int_iterators) { + using span = util::span<int>; + + int n = 97; + int a = 3; + int b = a+n; + + span s(a, b); + + EXPECT_TRUE(util::is_iterator<span::iterator>::value); + EXPECT_TRUE(util::is_random_access_iterator<span::iterator>::value); + + EXPECT_EQ(n, std::distance(s.begin(), s.end())); + EXPECT_EQ(n, std::distance(s.cbegin(), s.cend())); + + int sum = 0; + for (auto i: span(a, b)) { + sum += i; + } + EXPECT_EQ(sum, (a+b-1)*(b-a)/2); +} + +TEST(span, make_span) { + auto s_empty = util::make_span(3, 3); + EXPECT_TRUE(s_empty.empty()); + + { + auto s = util::make_span((short)3, (unsigned long long)10); + auto first = s.front(); + auto last = s.back(); + + EXPECT_EQ(3u, first); + EXPECT_EQ(9u, last); + + EXPECT_TRUE((std::is_same<decltype(first), decltype(last)>::value)); + EXPECT_TRUE((std::is_same<unsigned long long, decltype(first)>::value)); + } + + { + // type abuse! should promote bool to long in span. + std::pair<long, bool> bounds(-3, false); + auto s = util::make_span(bounds); + auto first = s.front(); + auto last = s.back(); + + EXPECT_EQ(-3, first); + EXPECT_EQ(-1, last); + + EXPECT_TRUE((std::is_same<decltype(first), decltype(last)>::value)); + EXPECT_TRUE((std::is_same<long, decltype(first)>::value)); + } +} + diff --git a/tests/unit/test_transform.cpp b/tests/unit/test_transform.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9212293fbf7a59674eb81f3ccac1638b1df04077 --- /dev/null +++ b/tests/unit/test_transform.cpp @@ -0,0 +1,56 @@ +#include "gtest.h" + +#include <cctype> +#include <forward_list> +#include <vector> + +#include <util/range.hpp> +#include <util/transform.hpp> + +using namespace nest::mc; + +TEST(transform, transform_view) { + std::forward_list<int> fl = {1, 4, 6, 8, 10 }; + std::vector<double> result; + + auto r = util::transform_view(fl, [](int i) { return i*i+0.5; }); + + EXPECT_EQ(5u, util::size(r)); + EXPECT_EQ(16.5, *(std::next(std::begin(r), 1))); + + std::copy(r.begin(), r.end(), std::back_inserter(result)); + std::vector<double> expected = { 1.5, 16.5, 36.5, 64.5, 100.5 }; + + EXPECT_EQ(expected, result); +} + +struct null_terminated_t { + bool operator==(const char *p) const { return !*p; } + bool operator!=(const char *p) const { return !!*p; } + + friend bool operator==(const char *p, null_terminated_t x) { + return x==p; + } + + friend bool operator!=(const char *p, null_terminated_t x) { + return x!=p; + } + + constexpr null_terminated_t() {} +}; + +constexpr null_terminated_t null_terminated; + +char upper(char c) { return std::toupper(c); } + +TEST(transform, transform_view_sentinel) { + const char *hello = "hello"; + auto r = util::transform_view(util::make_range(hello, null_terminated), upper); + + std::string out; + for (auto i = r.begin(); i!=r.end(); ++i) { + out += *i; + } + EXPECT_EQ("HELLO", out); +} + diff --git a/tests/validation/CMakeLists.txt b/tests/validation/CMakeLists.txt index c6c72fa04efab7394190288ba3face603d141faf..ae892560492dccca331036ad1481d2f4ce5da7bb 100644 --- a/tests/validation/CMakeLists.txt +++ b/tests/validation/CMakeLists.txt @@ -19,19 +19,20 @@ set(TARGETS validate.exe) foreach(target ${TARGETS}) target_link_libraries(${target} LINK_PUBLIC cellalgo gtest) - + if(WITH_TBB) - target_link_libraries(${target} LINK_PUBLIC ${TBB_LIBRARIES}) + target_link_libraries(${target} LINK_PUBLIC ${TBB_LIBRARIES}) endif() if(WITH_MPI) - target_link_libraries(${target} LINK_PUBLIC ${MPI_C_LIBRARIES}) - set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS "${MPI_C_LINK_FLAGS}") + target_link_libraries(${target} LINK_PUBLIC ${MPI_C_LIBRARIES}) + set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS "${MPI_C_LINK_FLAGS}") endif() - set_target_properties(${target} - PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests" + set_target_properties( + ${target} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests" ) endforeach()