From 0aafd72d03776e99eeff3a647a6cf970252ec14f Mon Sep 17 00:00:00 2001 From: Ben Cumming <louncharf@gmail.com> Date: Wed, 16 Nov 2016 15:13:35 +0100 Subject: [PATCH] Feature/gpu validation issue #68 (#84) Fixes #68 Corresponding feature: #67 * Reproduce the hh-soma validation test on GPU. * Reproduce the ball and stick model on GPU. * Reproduce miniapp spike chains. * Add `cell_group` unit test to the cuda unit tests: builds simple ball and stick model and integrates for 50ms and records how many spikes occur; it is a simple early warning that something is broken, but is no substitute for the validation tests. * Update the `validate_soma`, `validate_ball_and_stick` and `validate_synapses` validation tests for the GPU backend: * refactor individual tests into test runner functions that are templated on lowered cell type; * for each of the original validation tests add a cuda (.cu) implementation, and write an additional "backend" field to the validation trace metadata. * Use a `CPrinter` to generate the same `net_receive` block that is used for the multicore backend. Note: this is not efficient, because each read/write requires a cuda memcpy between host and device memory, however it allows us to pass all unit and validation tests. A more efficient GPU-specific implementation is left for later optimization work. * Make paths to `gtest.h`, `test_common_cells.hpp` etc. in test sources consistent relative paths, and remove the `tests/` directory from the include path. --- modcc/cprinter.cpp | 27 +-- modcc/cprinter.hpp | 3 + modcc/cudaprinter.cpp | 68 ++++---- modcc/textbuffer.cpp | 7 + modcc/textbuffer.hpp | 3 + src/backends/fvm_multicore.hpp | 2 +- tests/CMakeLists.txt | 3 - tests/global_communication/mpi_listener.hpp | 4 +- tests/global_communication/test.cpp | 2 +- .../test_communicator.cpp | 2 +- .../test_exporter_spike_file.cpp | 2 +- .../test_mpi_gather_all.cpp | 4 +- tests/modcc/test.hpp | 2 +- tests/unit/CMakeLists.txt | 1 + tests/unit/test_cell_group.cu | 36 ++++ tests/unit/test_filter.cpp | 4 +- tests/validation/CMakeLists.txt | 35 ++-- tests/validation/convergence_test.hpp | 3 +- tests/validation/tinyopt.hpp | 2 +- tests/validation/trace_analysis.cpp | 2 +- tests/validation/trace_analysis.hpp | 2 +- tests/validation/validate.cpp | 4 +- tests/validation/validate_ball_and_stick.cpp | 153 +--------------- tests/validation/validate_ball_and_stick.cu | 22 +++ tests/validation/validate_ball_and_stick.hpp | 165 ++++++++++++++++++ .../validate_compartment_policy.cpp | 2 +- tests/validation/validate_soma.cpp | 58 +----- tests/validation/validate_soma.cu | 9 + tests/validation/validate_soma.hpp | 56 ++++++ tests/validation/validate_synapses.cpp | 73 +------- tests/validation/validate_synapses.cu | 16 ++ tests/validation/validate_synapses.hpp | 70 ++++++++ 32 files changed, 490 insertions(+), 352 deletions(-) create mode 100644 tests/unit/test_cell_group.cu create mode 100644 tests/validation/validate_ball_and_stick.cu create mode 100644 tests/validation/validate_ball_and_stick.hpp create mode 100644 tests/validation/validate_soma.cu create mode 100644 tests/validation/validate_soma.hpp create mode 100644 tests/validation/validate_synapses.cu create mode 100644 tests/validation/validate_synapses.hpp diff --git a/modcc/cprinter.cpp b/modcc/cprinter.cpp index d6b1e537..3d4b01d4 100644 --- a/modcc/cprinter.cpp +++ b/modcc/cprinter.cpp @@ -305,12 +305,9 @@ CPrinter::CPrinter(Module &m, bool o) ////////////////////////////////////////////// auto proctest = [] (procedureKind k) { - return - k == procedureKind::normal - || k == procedureKind::api - || k == procedureKind::net_receive; + return is_in(k, {procedureKind::normal, procedureKind::api, procedureKind::net_receive}); }; - for(auto &var : m.symbols()) { + for(auto const& var: m.symbols()) { auto isproc = var.second->kind()==symbolKind::procedure; if(isproc ) { @@ -461,10 +458,6 @@ void CPrinter::visit(BlockExpression *e) { } } if(names.size()>0) { - //for(auto it=names.begin(); it!=names.end(); ++it) { - // text_.add_gutter() << "value_type " << *it; - // text_.end_line("{0};"); - //} text_.add_gutter() << "value_type " << *(names.begin()); for(auto it=names.begin()+1; it!=names.end(); ++it) { text_ << ", " << *it; @@ -498,8 +491,9 @@ void CPrinter::visit(IfExpression *e) { text_ << "}"; } +// NOTE: net_receive() is classified as a ProcedureExpression void CPrinter::visit(ProcedureExpression *e) { - // ------------- print prototype ------------- // + // print prototype text_.add_gutter() << "void " << e->name() << "(int i_"; for(auto& arg : e->args()) { text_ << ", value_type " << arg->is_argument()->name(); @@ -518,19 +512,18 @@ void CPrinter::visit(ProcedureExpression *e) { e->location()); } + // print body increase_indentation(); - e->body()->accept(this); - // ------------- close up ------------- // + // close the function body decrease_indentation(); text_.add_line("}"); text_.add_line(); - return; } void CPrinter::visit(APIMethod *e) { - // ------------- print prototype ------------- // + // print prototype text_.add_gutter() << "void " << e->name() << "() override {"; text_.end_line(); @@ -566,7 +559,7 @@ void CPrinter::visit(APIMethod *e) { } } - // ------------- get loop dimensions ------------- // + // get loop dimensions text_.add_line("int n_ = node_index_.size();"); // hand off printing of loops to optimized or unoptimized backend @@ -578,14 +571,12 @@ void CPrinter::visit(APIMethod *e) { } } - // ------------- close up ------------- // + // close up the loop body text_.add_line("}"); text_.add_line(); } void CPrinter::print_APIMethod_unoptimized(APIMethod* e) { - //text_.add_line("START_PROFILE"); - // there can not be more than 1 instance of a density channel per grid point, // so we can assert that aliasing will not occur. if(optimize_) text_.add_line("#pragma ivdep"); diff --git a/modcc/cprinter.hpp b/modcc/cprinter.hpp index e669c25f..ef47977c 100644 --- a/modcc/cprinter.hpp +++ b/modcc/cprinter.hpp @@ -44,6 +44,9 @@ public: void decrease_indentation(){ text_.decrease_indentation(); } + void clear_text() { + text_.clear(); + } private: void print_APIMethod_optimized(APIMethod* e); diff --git a/modcc/cudaprinter.cpp b/modcc/cudaprinter.cpp index e5103ddf..175781a2 100644 --- a/modcc/cudaprinter.cpp +++ b/modcc/cudaprinter.cpp @@ -1,5 +1,6 @@ #include <algorithm> +#include "cprinter.hpp" // needed for printing net_receive method #include "cudaprinter.hpp" #include "lexer.hpp" @@ -114,27 +115,11 @@ CUDAPrinter::CUDAPrinter(Module &m, bool o) text_.decrease_indentation(); text_.add_line("}"); text_.add_line(); - /* - text_.add_line("__device__"); - text_.add_line("inline double atomicSub(double* address, double val) {"); - text_.increase_indentation(); - text_.add_line("return atomicAdd(address, -val);"); - text_.decrease_indentation(); - text_.add_line("}"); - text_.add_line(); - text_.add_line("__device__"); - text_.add_line("inline float atomicSub(float* address, float val) {"); - text_.increase_indentation(); - text_.add_line("return atomicAdd(address, -val);"); - text_.decrease_indentation(); - text_.add_line("}"); - text_.add_line(); - */ // forward declarations of procedures for(auto const &var : m.symbols()) { - if( var.second->kind()==symbolKind::procedure - && var.second->is_procedure()->kind() == procedureKind::normal) + if( var.second->kind()==symbolKind::procedure && + var.second->is_procedure()->kind() == procedureKind::normal) { print_procedure_prototype(var.second->is_procedure()); text_.end_line(";"); @@ -144,11 +129,10 @@ CUDAPrinter::CUDAPrinter(Module &m, bool o) // print stubs that call API method kernels that are defined in the // kernels::name namespace - auto proctest = [] (procedureKind k) {return k == procedureKind::normal - || k == procedureKind::api; }; for(auto const &var : m.symbols()) { if (var.second->kind()==symbolKind::procedure && - proctest(var.second->is_procedure()->kind())) + is_in(var.second->is_procedure()->kind(), + {procedureKind::normal, procedureKind::api})) { var.second->accept(this); } @@ -428,11 +412,9 @@ CUDAPrinter::CUDAPrinter(Module &m, bool o) ////////////////////////////////////////////// ////////////////////////////////////////////// - - auto proctest = [] (procedureKind k) {return k == procedureKind::api;}; for(auto const &var : m.symbols()) { - if( var.second->kind()==symbolKind::procedure - && proctest(var.second->is_procedure()->kind())) + if( var.second->kind()==symbolKind::procedure && + var.second->is_procedure()->kind()==procedureKind::api) { auto proc = var.second->is_api_method(); auto name = proc->name(); @@ -450,6 +432,29 @@ CUDAPrinter::CUDAPrinter(Module &m, bool o) text_.add_line("}"); text_.add_line(); } + else if( var.second->kind()==symbolKind::procedure && + var.second->is_procedure()->kind()==procedureKind::net_receive) + { + auto proc = var.second->is_procedure(); + auto name = proc->name(); + text_.add_line("void " + name + "(int i_, value_type weight) {"); + text_.increase_indentation(); + + // Print the body of the net_receive block. + // Use the same body as would be generated with the cprinter. + // This is not omptimal, because each read and write will require + // a copy between host and device memory, so we will need a + // GPU-specific implementation + auto cprinter = CPrinter(*module_); + cprinter.clear_text(); + cprinter.set_gutter(text_.get_gutter()); + proc->body()->accept(&cprinter); + text_ << cprinter.text(); + + text_.decrease_indentation(); + text_.add_line("}"); + text_.add_line(); + } } ////////////////////////////////////////////// @@ -648,28 +653,26 @@ void CUDAPrinter::visit(ProcedureExpression *e) { e->location()); } - // ------------- print prototype ------------- // + // print prototype print_procedure_prototype(e); text_.end_line(" {"); - // ------------- print body ------------- // + // print body increase_indentation(); text_.add_line("using value_type = T;"); - text_.add_line("using iarray = I;"); text_.add_line(); e->body()->accept(this); - // ------------- close up ------------- // + // close up decrease_indentation(); text_.add_line("}"); text_.add_line(); - return; } void CUDAPrinter::visit(APIMethod *e) { - // ------------- print prototype ------------- // + // print prototype text_.add_gutter() << "template <typename T, typename I>\n"; text_.add_line( "__global__"); text_.add_gutter() << "void " << e->name() @@ -702,7 +705,8 @@ void CUDAPrinter::visit(APIMethod *e) { text_.add_line("}"); decrease_indentation(); - text_.add_line("}\n"); + text_.add_line("}"); + text_.add_line(); } void CUDAPrinter::print_APIMethod_body(APIMethod* e) { diff --git a/modcc/textbuffer.cpp b/modcc/textbuffer.cpp index 5c8af1df..658d20c4 100644 --- a/modcc/textbuffer.cpp +++ b/modcc/textbuffer.cpp @@ -28,6 +28,9 @@ void TextBuffer::set_gutter(int width) { indent_ = width; gutter_ = std::string(indent_, ' '); } +int TextBuffer::get_gutter() { + return indent_; +} void TextBuffer::increase_indentation() { indent_ += indentation_width_; @@ -47,3 +50,7 @@ void TextBuffer::decrease_indentation() { std::stringstream& TextBuffer::text() { return text_; } + +void TextBuffer::clear() { + text_.str(""); +} diff --git a/modcc/textbuffer.hpp b/modcc/textbuffer.hpp index cdb3fad3..55b6da67 100644 --- a/modcc/textbuffer.hpp +++ b/modcc/textbuffer.hpp @@ -18,11 +18,14 @@ public: std::string str() const; void set_gutter(int width); + int get_gutter(); void increase_indentation(); void decrease_indentation(); std::stringstream &text(); + void clear(); + private: int indent_ = 0; diff --git a/src/backends/fvm_multicore.hpp b/src/backends/fvm_multicore.hpp index 73a0d988..71a0ba0d 100644 --- a/src/backends/fvm_multicore.hpp +++ b/src/backends/fvm_multicore.hpp @@ -132,7 +132,7 @@ struct backend { static bool has_mechanism(const std::string& name) { return mech_map_.count(name)>0; } static std::string name() { - return "multicore"; + return "cpu"; } private: diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5fd34932..7036ee30 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,8 +1,5 @@ # google test framework add_library(gtest gtest-all.cpp) -# tests look for gtest.h here -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) - # Unit tests add_subdirectory(unit) diff --git a/tests/global_communication/mpi_listener.hpp b/tests/global_communication/mpi_listener.hpp index e33aaebb..18d245e4 100644 --- a/tests/global_communication/mpi_listener.hpp +++ b/tests/global_communication/mpi_listener.hpp @@ -1,12 +1,12 @@ #pragma once #include <cstdio> -#include <ostream> +#include <fstream> #include <stdexcept> #include <communication/global_policy.hpp> -#include "gtest.h" +#include "../gtest.h" /// A specialized listener desinged for printing test results with MPI. /// diff --git a/tests/global_communication/test.cpp b/tests/global_communication/test.cpp index 1ddb6d41..fae25029 100644 --- a/tests/global_communication/test.cpp +++ b/tests/global_communication/test.cpp @@ -3,7 +3,7 @@ #include <numeric> #include <vector> -#include "gtest.h" +#include "../gtest.h" #include "mpi_listener.hpp" diff --git a/tests/global_communication/test_communicator.cpp b/tests/global_communication/test_communicator.cpp index f79d4e2d..b6bcc034 100644 --- a/tests/global_communication/test_communicator.cpp +++ b/tests/global_communication/test_communicator.cpp @@ -1,4 +1,4 @@ -#include "gtest.h" +#include "../gtest.h" #include <cstdio> #include <fstream> diff --git a/tests/global_communication/test_exporter_spike_file.cpp b/tests/global_communication/test_exporter_spike_file.cpp index 4a85b078..027a315b 100644 --- a/tests/global_communication/test_exporter_spike_file.cpp +++ b/tests/global_communication/test_exporter_spike_file.cpp @@ -1,4 +1,4 @@ -#include "gtest.h" +#include "../gtest.h" #include <cstdio> #include <fstream> diff --git a/tests/global_communication/test_mpi_gather_all.cpp b/tests/global_communication/test_mpi_gather_all.cpp index b5ecf73f..f67df15a 100644 --- a/tests/global_communication/test_mpi_gather_all.cpp +++ b/tests/global_communication/test_mpi_gather_all.cpp @@ -1,7 +1,7 @@ -#include "gtest.h" - #ifdef WITH_MPI +#include "../gtest.h" + #include <cstring> #include <vector> diff --git a/tests/modcc/test.hpp b/tests/modcc/test.hpp index d3de7250..45dced64 100644 --- a/tests/modcc/test.hpp +++ b/tests/modcc/test.hpp @@ -1,6 +1,6 @@ #pragma once -#include <gtest.h> +#include "../gtest.h" #include "parser.hpp" #include "modccutil.hpp" diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 2dff2310..75c46c46 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -1,4 +1,5 @@ set(TEST_CUDA_SOURCES + test_cell_group.cu test_matrix.cu test_vector.cu diff --git a/tests/unit/test_cell_group.cu b/tests/unit/test_cell_group.cu new file mode 100644 index 00000000..16bc124d --- /dev/null +++ b/tests/unit/test_cell_group.cu @@ -0,0 +1,36 @@ +#include "../gtest.h" + +#include <cell_group.hpp> +#include <common_types.hpp> +#include <fvm_multicell.hpp> +#include <util/rangeutil.hpp> + +#include "../test_common_cells.hpp" + +using namespace nest::mc; + +using fvm_cell = + fvm::fvm_multicell<nest::mc::gpu::backend>; + +nest::mc::cell make_cell() { + using namespace nest::mc; + + cell c = make_cell_ball_and_stick(); + + c.add_detector({0, 0}, 0); + c.segment(1)->set_compartments(101); + + return c; +} + +TEST(cell_group, test) +{ + using cell_group_type = cell_group<fvm_cell>; + auto group = cell_group_type{0, util::singleton_view(make_cell())}; + + group.advance(50, 0.01); + + // the model is expected to generate 4 spikes as a result of the + // fixed stimulus over 50 ms + EXPECT_EQ(4u, group.spikes().size()); +} diff --git a/tests/unit/test_filter.cpp b/tests/unit/test_filter.cpp index 0d357edd..d8ca4e64 100644 --- a/tests/unit/test_filter.cpp +++ b/tests/unit/test_filter.cpp @@ -1,5 +1,3 @@ -#include "gtest.h" - #include <cstring> #include <list> @@ -8,6 +6,8 @@ #include <util/filter.hpp> #include <util/transform.hpp> +#include "../gtest.h" + #include "common.hpp" using namespace nest::mc; diff --git a/tests/validation/CMakeLists.txt b/tests/validation/CMakeLists.txt index a5d217b2..d9e16533 100644 --- a/tests/validation/CMakeLists.txt +++ b/tests/validation/CMakeLists.txt @@ -13,27 +13,38 @@ set(VALIDATION_SOURCES validate.cpp ) +set(VALIDATION_CUDA_SOURCES + # unit tests + validate_soma.cu + validate_ball_and_stick.cu + validate_synapses.cu + + # support code + validation_data.cpp + trace_analysis.cpp + + # unit test driver + validate.cpp +) + if(VALIDATION_DATA_DIR) add_definitions("-DDATADIR=\"${VALIDATION_DATA_DIR}\"") endif() -add_executable(validate.exe ${VALIDATION_SOURCES} ${HEADERS}) +add_executable(validate.exe ${VALIDATION_SOURCES}) set(TARGETS validate.exe) -foreach(target ${TARGETS}) - target_link_libraries(${target} LINK_PUBLIC nestmc gtest) +if(WITH_CUDA) + cuda_add_executable(validate_cuda.exe ${VALIDATION_CUDA_SOURCES}) + list(APPEND TARGETS validate_cuda.exe) + target_link_libraries(validate_cuda.exe LINK_PUBLIC gpu) +endif() - if(WITH_TBB) - target_link_libraries(${target} LINK_PUBLIC ${TBB_LIBRARIES}) - endif() - if(WITH_CUDA) - target_link_libraries(${target} LINK_PUBLIC ${CUDA_LIBRARIES}) - endif() +foreach(target ${TARGETS}) + target_link_libraries(${target} LINK_PUBLIC nestmc gtest) - if(WITH_UNWIND) - target_link_libraries(${target} LINK_PUBLIC ${UNWIND_LIBRARIES}) - endif() + target_link_libraries(${target} LINK_PUBLIC ${EXTERNAL_LIBRARIES}) if(WITH_MPI) target_link_libraries(${target} LINK_PUBLIC ${MPI_C_LIBRARIES}) diff --git a/tests/validation/convergence_test.hpp b/tests/validation/convergence_test.hpp index e536a0f1..0561e7d7 100644 --- a/tests/validation/convergence_test.hpp +++ b/tests/validation/convergence_test.hpp @@ -2,10 +2,11 @@ #include <util/filter.hpp> #include <util/rangeutil.hpp> +#include <cell.hpp> #include <json/json.hpp> -#include "gtest.h" +#include "../gtest.h" #include "trace_analysis.hpp" #include "validation_data.hpp" diff --git a/tests/validation/tinyopt.hpp b/tests/validation/tinyopt.hpp index 85a2e4a7..90eb145f 100644 --- a/tests/validation/tinyopt.hpp +++ b/tests/validation/tinyopt.hpp @@ -6,7 +6,7 @@ #include <stdexcept> #include <string> -#include "gtest.h" +#include "../gtest.h" #include <communication/global_policy.hpp> diff --git a/tests/validation/trace_analysis.cpp b/tests/validation/trace_analysis.cpp index 78ef5ada..b83d41a2 100644 --- a/tests/validation/trace_analysis.cpp +++ b/tests/validation/trace_analysis.cpp @@ -4,7 +4,7 @@ #include <json/json.hpp> -#include "gtest.h" +#include "../gtest.h" #include <math.hpp> #include <simple_sampler.hpp> diff --git a/tests/validation/trace_analysis.hpp b/tests/validation/trace_analysis.hpp index 493a15f7..8b097623 100644 --- a/tests/validation/trace_analysis.hpp +++ b/tests/validation/trace_analysis.hpp @@ -2,7 +2,7 @@ #include <vector> -#include "gtest.h" +#include "../gtest.h" #include <simple_sampler.hpp> #include <math.hpp> diff --git a/tests/validation/validate.cpp b/tests/validation/validate.cpp index 9f071aa2..a2f2f832 100644 --- a/tests/validation/validate.cpp +++ b/tests/validation/validate.cpp @@ -4,10 +4,10 @@ #include <string> #include <exception> -#include "gtest.h" - #include <communication/global_policy.hpp> +#include "../gtest.h" + #include "tinyopt.hpp" #include "validation_data.hpp" diff --git a/tests/validation/validate_ball_and_stick.cpp b/tests/validation/validate_ball_and_stick.cpp index 01c9bbaa..0e31bcaa 100644 --- a/tests/validation/validate_ball_and_stick.cpp +++ b/tests/validation/validate_ball_and_stick.cpp @@ -1,159 +1,22 @@ -#include <json/json.hpp> - -#include <cell.hpp> -#include <common_types.hpp> -#include <fvm_multicell.hpp> -#include <model.hpp> -#include <recipe.hpp> -#include <simple_sampler.hpp> -#include <util/path.hpp> - #include "../gtest.h" +#include "validate_ball_and_stick.hpp" -#include "../test_common_cells.hpp" -#include "convergence_test.hpp" -#include "trace_analysis.hpp" -#include "validation_data.hpp" - -using namespace nest::mc; - -template < - typename lowered_cell, - typename SamplerInfoSeq -> -void run_ncomp_convergence_test( - const char* model_name, - const util::path& ref_data_path, - const cell& c, - SamplerInfoSeq& samplers, - float t_end=100.f) -{ - auto max_ncomp = g_trace_io.max_ncomp(); - auto dt = g_trace_io.min_dt(); - - nlohmann::json meta = { - {"name", "membrane voltage"}, - {"model", model_name}, - {"dt", dt}, - {"sim", "nestmc"}, - {"units", "mV"} - }; - - auto exclude = stimulus_ends(c); - - convergence_test_runner<int> R("ncomp", samplers, meta); - R.load_reference_data(ref_data_path); - - for (int ncomp = 10; ncomp<max_ncomp; ncomp*=2) { - for (auto& seg: c.segments()) { - if (!seg->is_soma()) { - seg->set_compartments(ncomp); - } - } - model<lowered_cell> m(singleton_recipe{c}); - - R.run(m, ncomp, t_end, dt, exclude); - } - R.report(); - R.assert_all_convergence(); -} - -TEST(ball_and_taper, neuron_ref) { - using lowered_cell = fvm::fvm_multicell<multicore::backend>; - - cell c = make_cell_ball_and_stick(); - add_common_voltage_probes(c); +#include <fvm_multicell.hpp> - float sample_dt = 0.025f; - sampler_info samplers[] = { - {"soma.mid", {0u, 0u}, simple_sampler(sample_dt)}, - {"dend.mid", {0u, 1u}, simple_sampler(sample_dt)}, - {"dend.end", {0u, 2u}, simple_sampler(sample_dt)} - }; +using lowered_cell = nest::mc::fvm::fvm_multicell<nest::mc::multicore::backend>; - run_ncomp_convergence_test<lowered_cell>( - "ball_and_stick", - "neuron_ball_and_stick.json", - c, - samplers); +TEST(ball_and_stick, neuron_ref) { + validate_ball_and_stick<lowered_cell>(); } TEST(ball_and_3stick, neuron_ref) { - using lowered_cell = fvm::fvm_multicell<multicore::backend>; - - cell c = make_cell_ball_and_3stick(); - add_common_voltage_probes(c); - - float sample_dt = 0.025f; - sampler_info samplers[] = { - {"soma.mid", {0u, 0u}, simple_sampler(sample_dt)}, - {"dend1.mid", {0u, 1u}, simple_sampler(sample_dt)}, - {"dend1.end", {0u, 2u}, simple_sampler(sample_dt)}, - {"dend2.mid", {0u, 3u}, simple_sampler(sample_dt)}, - {"dend2.end", {0u, 4u}, simple_sampler(sample_dt)}, - {"dend3.mid", {0u, 5u}, simple_sampler(sample_dt)}, - {"dend3.end", {0u, 6u}, simple_sampler(sample_dt)} - }; - - run_ncomp_convergence_test<lowered_cell>( - "ball_and_3stick", - "neuron_ball_and_3stick.json", - c, - samplers); + validate_ball_and_3stick<lowered_cell>(); } TEST(rallpack1, numeric_ref) { - using lowered_cell = fvm::fvm_multicell<multicore::backend>; - - cell c = make_cell_simple_cable(); - - // three probes: left end, 30% along, right end. - c.add_probe({{1, 0.0}, probeKind::membrane_voltage}); - c.add_probe({{1, 0.3}, probeKind::membrane_voltage}); - c.add_probe({{1, 1.0}, probeKind::membrane_voltage}); - - float sample_dt = 0.025f; - sampler_info samplers[] = { - {"cable.x0.0", {0u, 0u}, simple_sampler(sample_dt)}, - {"cable.x0.3", {0u, 1u}, simple_sampler(sample_dt)}, - {"cable.x1.0", {0u, 2u}, simple_sampler(sample_dt)}, - }; - - run_ncomp_convergence_test<lowered_cell>( - "rallpack1", - "numeric_rallpack1.json", - c, - samplers, - 250.f); + validate_rallpack1<lowered_cell>(); } TEST(ball_and_squiggle, neuron_ref) { - using lowered_cell = fvm::fvm_multicell<multicore::backend>; - - cell c = make_cell_ball_and_squiggle(); - add_common_voltage_probes(c); - - float sample_dt = 0.025f; - sampler_info samplers[] = { - {"soma.mid", {0u, 0u}, simple_sampler(sample_dt)}, - {"dend.mid", {0u, 1u}, simple_sampler(sample_dt)}, - {"dend.end", {0u, 2u}, simple_sampler(sample_dt)} - }; - -#if 0 - // *temporarily* disabled: compartment division policy will - // be moved into backend policy classes. - - run_ncomp_convergence_test<lowered_cell_div<div_compartment_sampler>>( - "ball_and_squiggle_sampler", - "neuron_ball_and_squiggle.json", - c, - samplers); -#endif - - run_ncomp_convergence_test<lowered_cell>( - "ball_and_squiggle_integrator", - "neuron_ball_and_squiggle.json", - c, - samplers); + validate_ball_and_squiggle<lowered_cell>(); } diff --git a/tests/validation/validate_ball_and_stick.cu b/tests/validation/validate_ball_and_stick.cu new file mode 100644 index 00000000..b753b6b5 --- /dev/null +++ b/tests/validation/validate_ball_and_stick.cu @@ -0,0 +1,22 @@ +#include "../gtest.h" +#include "validate_ball_and_stick.hpp" + +#include <fvm_multicell.hpp> + +using lowered_cell = nest::mc::fvm::fvm_multicell<nest::mc::gpu::backend>; + +TEST(ball_and_stick, neuron_ref) { + validate_ball_and_stick<lowered_cell>(); +} + +TEST(ball_and_3stick, neuron_ref) { + validate_ball_and_3stick<lowered_cell>(); +} + +TEST(rallpack1, numeric_ref) { + validate_rallpack1<lowered_cell>(); +} + +TEST(ball_and_squiggle, neuron_ref) { + validate_ball_and_squiggle<lowered_cell>(); +} diff --git a/tests/validation/validate_ball_and_stick.hpp b/tests/validation/validate_ball_and_stick.hpp new file mode 100644 index 00000000..bed16d4d --- /dev/null +++ b/tests/validation/validate_ball_and_stick.hpp @@ -0,0 +1,165 @@ +#include <json/json.hpp> + +#include <cell.hpp> +#include <common_types.hpp> +#include <fvm_multicell.hpp> +#include <model.hpp> +#include <recipe.hpp> +#include <simple_sampler.hpp> +#include <util/path.hpp> + +#include "../gtest.h" + +#include "../test_common_cells.hpp" +#include "convergence_test.hpp" +#include "trace_analysis.hpp" +#include "validation_data.hpp" + +template < + typename LoweredCell, + typename SamplerInfoSeq +> +void run_ncomp_convergence_test( + const char* model_name, + const nest::mc::util::path& ref_data_path, + const nest::mc::cell& c, + SamplerInfoSeq& samplers, + float t_end=100.f) +{ + using namespace nest::mc; + + auto max_ncomp = g_trace_io.max_ncomp(); + auto dt = g_trace_io.min_dt(); + + nlohmann::json meta = { + {"name", "membrane voltage"}, + {"model", model_name}, + {"dt", dt}, + {"sim", "nestmc"}, + {"units", "mV"}, + {"backend", LoweredCell::backend::name()} + }; + + auto exclude = stimulus_ends(c); + + convergence_test_runner<int> runner("ncomp", samplers, meta); + runner.load_reference_data(ref_data_path); + + for (int ncomp = 10; ncomp<max_ncomp; ncomp*=2) { + for (auto& seg: c.segments()) { + if (!seg->is_soma()) { + seg->set_compartments(ncomp); + } + } + model<LoweredCell> m(singleton_recipe{c}); + + runner.run(m, ncomp, t_end, dt, exclude); + } + runner.report(); + runner.assert_all_convergence(); +} + + +template <typename LoweredCell> +void validate_ball_and_stick() { + using namespace nest::mc; + + cell c = make_cell_ball_and_stick(); + add_common_voltage_probes(c); + + float sample_dt = 0.025f; + sampler_info samplers[] = { + {"soma.mid", {0u, 0u}, simple_sampler(sample_dt)}, + {"dend.mid", {0u, 1u}, simple_sampler(sample_dt)}, + {"dend.end", {0u, 2u}, simple_sampler(sample_dt)} + }; + + run_ncomp_convergence_test<LoweredCell>( + "ball_and_stick", + "neuron_ball_and_stick.json", + c, + samplers); +} + +template <typename LoweredCell> +void validate_ball_and_3stick() { + using namespace nest::mc; + + cell c = make_cell_ball_and_3stick(); + add_common_voltage_probes(c); + + float sample_dt = 0.025f; + sampler_info samplers[] = { + {"soma.mid", {0u, 0u}, simple_sampler(sample_dt)}, + {"dend1.mid", {0u, 1u}, simple_sampler(sample_dt)}, + {"dend1.end", {0u, 2u}, simple_sampler(sample_dt)}, + {"dend2.mid", {0u, 3u}, simple_sampler(sample_dt)}, + {"dend2.end", {0u, 4u}, simple_sampler(sample_dt)}, + {"dend3.mid", {0u, 5u}, simple_sampler(sample_dt)}, + {"dend3.end", {0u, 6u}, simple_sampler(sample_dt)} + }; + + run_ncomp_convergence_test<LoweredCell>( + "ball_and_3stick", + "neuron_ball_and_3stick.json", + c, + samplers); +} + +template <typename LoweredCell> +void validate_rallpack1() { + using namespace nest::mc; + + cell c = make_cell_simple_cable(); + + // three probes: left end, 30% along, right end. + c.add_probe({{1, 0.0}, probeKind::membrane_voltage}); + c.add_probe({{1, 0.3}, probeKind::membrane_voltage}); + c.add_probe({{1, 1.0}, probeKind::membrane_voltage}); + + float sample_dt = 0.025f; + sampler_info samplers[] = { + {"cable.x0.0", {0u, 0u}, simple_sampler(sample_dt)}, + {"cable.x0.3", {0u, 1u}, simple_sampler(sample_dt)}, + {"cable.x1.0", {0u, 2u}, simple_sampler(sample_dt)}, + }; + + run_ncomp_convergence_test<LoweredCell>( + "rallpack1", + "numeric_rallpack1.json", + c, + samplers, + 250.f); +} + +template <typename LoweredCell> +void validate_ball_and_squiggle() { + using namespace nest::mc; + + cell c = make_cell_ball_and_squiggle(); + add_common_voltage_probes(c); + + float sample_dt = 0.025f; + sampler_info samplers[] = { + {"soma.mid", {0u, 0u}, simple_sampler(sample_dt)}, + {"dend.mid", {0u, 1u}, simple_sampler(sample_dt)}, + {"dend.end", {0u, 2u}, simple_sampler(sample_dt)} + }; + +#if 0 + // *temporarily* disabled: compartment division policy will + // be moved into backend policy classes. + + run_ncomp_convergence_test<lowered_cell_div<div_compartment_sampler>>( + "ball_and_squiggle_sampler", + "neuron_ball_and_squiggle.json", + c, + samplers); +#endif + + run_ncomp_convergence_test<LoweredCell>( + "ball_and_squiggle_integrator", + "neuron_ball_and_squiggle.json", + c, + samplers); +} diff --git a/tests/validation/validate_compartment_policy.cpp b/tests/validation/validate_compartment_policy.cpp index e03f800c..4848ea12 100644 --- a/tests/validation/validate_compartment_policy.cpp +++ b/tests/validation/validate_compartment_policy.cpp @@ -11,7 +11,7 @@ #include <simple_sampler.hpp> #include <util/rangeutil.hpp> -#include "gtest.h" +#include "../gtest.h" #include "../test_common_cells.hpp" #include "../test_util.hpp" diff --git a/tests/validation/validate_soma.cpp b/tests/validation/validate_soma.cpp index f7dcf4dc..e4e58e7b 100644 --- a/tests/validation/validate_soma.cpp +++ b/tests/validation/validate_soma.cpp @@ -1,61 +1,9 @@ -#include <fstream> -#include <utility> - -#include <json/json.hpp> - -#include <common_types.hpp> -#include <cell.hpp> -#include <fvm_multicell.hpp> -#include <model.hpp> -#include <recipe.hpp> -#include <simple_sampler.hpp> -#include <util/rangeutil.hpp> +#include "validate_soma.hpp" #include "../gtest.h" -#include "../test_common_cells.hpp" -#include "convergence_test.hpp" -#include "trace_analysis.hpp" -#include "validation_data.hpp" - -using namespace nest::mc; +using lowered_cell = nest::mc::fvm::fvm_multicell<nest::mc::multicore::backend>; TEST(soma, numeric_ref) { - using lowered_cell = fvm::fvm_multicell<multicore::backend>; - - cell c = make_cell_soma_only(); - add_common_voltage_probes(c); - model<lowered_cell> m(singleton_recipe{c}); - - float sample_dt = .025f; - sampler_info samplers[] = {{"soma.mid", {0u, 0u}, simple_sampler(sample_dt)}}; - - nlohmann::json meta = { - {"name", "membrane voltage"}, - {"model", "soma"}, - {"sim", "nestmc"}, - {"units", "mV"} - }; - - convergence_test_runner<float> R("dt", samplers, meta); - R.load_reference_data("numeric_soma.json"); - - float t_end = 100.f; - - // use dt = 0.05, 0.025, 0.01, 0.005, 0.0025, ... - double max_oo_dt = std::round(1.0/g_trace_io.min_dt()); - for (double base = 100; ; base *= 10) { - for (double multiple: {5., 2.5, 1.}) { - double oo_dt = base/multiple; - if (oo_dt>max_oo_dt) goto end; - - m.reset(); - float dt = float(1./oo_dt); - R.run(m, dt, t_end, dt); - } - } -end: - - R.report(); - R.assert_all_convergence(); + validate_soma<lowered_cell>(); } diff --git a/tests/validation/validate_soma.cu b/tests/validation/validate_soma.cu new file mode 100644 index 00000000..35355ab9 --- /dev/null +++ b/tests/validation/validate_soma.cu @@ -0,0 +1,9 @@ +#include "validate_soma.hpp" + +#include "../gtest.h" + +using lowered_cell = nest::mc::fvm::fvm_multicell<nest::mc::gpu::backend>; + +TEST(soma, numeric_ref) { + validate_soma<lowered_cell>(); +} diff --git a/tests/validation/validate_soma.hpp b/tests/validation/validate_soma.hpp new file mode 100644 index 00000000..52bc7d5e --- /dev/null +++ b/tests/validation/validate_soma.hpp @@ -0,0 +1,56 @@ +#include <json/json.hpp> + +#include <common_types.hpp> +#include <cell.hpp> +#include <fvm_multicell.hpp> +#include <model.hpp> +#include <recipe.hpp> +#include <simple_sampler.hpp> +#include <util/rangeutil.hpp> + +#include "../test_common_cells.hpp" +#include "convergence_test.hpp" +#include "trace_analysis.hpp" +#include "validation_data.hpp" + +template <typename LoweredCell> +void validate_soma() { + using namespace nest::mc; + + cell c = make_cell_soma_only(); + add_common_voltage_probes(c); + model<LoweredCell> model(singleton_recipe{c}); + + float sample_dt = .025f; + sampler_info samplers[] = {{"soma.mid", {0u, 0u}, simple_sampler(sample_dt)}}; + + nlohmann::json meta = { + {"name", "membrane voltage"}, + {"model", "soma"}, + {"sim", "nestmc"}, + {"units", "mV"}, + {"backend", LoweredCell::backend::name()} + }; + + convergence_test_runner<float> runner("dt", samplers, meta); + runner.load_reference_data("numeric_soma.json"); + + float t_end = 100.f; + + // use dt = 0.05, 0.025, 0.01, 0.005, 0.0025, ... + double max_oo_dt = std::round(1.0/g_trace_io.min_dt()); + for (double base = 100; ; base *= 10) { + for (double multiple: {5., 2.5, 1.}) { + double oo_dt = base/multiple; + if (oo_dt>max_oo_dt) goto end; + + model.reset(); + float dt = float(1./oo_dt); + runner.run(model, dt, t_end, dt); + } + } +end: + + runner.report(); + runner.assert_all_convergence(); +} diff --git a/tests/validation/validate_synapses.cpp b/tests/validation/validate_synapses.cpp index e90fd494..23413cc9 100644 --- a/tests/validation/validate_synapses.cpp +++ b/tests/validation/validate_synapses.cpp @@ -1,81 +1,16 @@ -#include <json/json.hpp> - -#include <cell.hpp> -#include <cell_group.hpp> #include <fvm_multicell.hpp> -#include <model.hpp> -#include <recipe.hpp> -#include <simple_sampler.hpp> -#include <util/path.hpp> #include "../gtest.h" +#include "validate_synapses.hpp" -#include "../test_common_cells.hpp" -#include "convergence_test.hpp" -#include "trace_analysis.hpp" -#include "validation_data.hpp" - -using namespace nest::mc; - -void run_synapse_test( - const char* syn_type, - const util::path& ref_data_path, - float t_end=70.f, - float dt=0.001) -{ - using lowered_cell = fvm::fvm_multicell<multicore::backend>; - - auto max_ncomp = g_trace_io.max_ncomp(); - nlohmann::json meta = { - {"name", "membrane voltage"}, - {"model", syn_type}, - {"sim", "nestmc"}, - {"units", "mV"} - }; - - cell c = make_cell_ball_and_stick(false); // no stimuli - parameter_list syn_default(syn_type); - c.add_synapse({1, 0.5}, syn_default); - add_common_voltage_probes(c); - - // injected spike events - postsynaptic_spike_event<float> synthetic_events[] = { - {{0u, 0u}, 10.0, 0.04}, - {{0u, 0u}, 20.0, 0.04}, - {{0u, 0u}, 40.0, 0.04} - }; - - // exclude points of discontinuity from linf analysis - std::vector<float> exclude = {10.f, 20.f, 40.f}; - - float sample_dt = 0.025f; - sampler_info samplers[] = { - {"soma.mid", {0u, 0u}, simple_sampler(sample_dt)}, - {"dend.mid", {0u, 1u}, simple_sampler(sample_dt)}, - {"dend.end", {0u, 2u}, simple_sampler(sample_dt)} - }; - - convergence_test_runner<int> R("ncomp", samplers, meta); - R.load_reference_data(ref_data_path); - - for (int ncomp = 10; ncomp<max_ncomp; ncomp*=2) { - c.cable(1)->set_compartments(ncomp); - model<lowered_cell> m(singleton_recipe{c}); - m.group(0).enqueue_events(synthetic_events); - - R.run(m, ncomp, t_end, dt, exclude); - } - R.report(); - R.assert_all_convergence(); -} +using lowered_cell = nest::mc::fvm::fvm_multicell<nest::mc::multicore::backend>; TEST(simple_synapse, expsyn_neuron_ref) { SCOPED_TRACE("expsyn"); - run_synapse_test("expsyn", "neuron_simple_exp_synapse.json"); + run_synapse_test<lowered_cell>("expsyn", "neuron_simple_exp_synapse.json"); } TEST(simple_synapse, exp2syn_neuron_ref) { SCOPED_TRACE("exp2syn"); - run_synapse_test("exp2syn", "neuron_simple_exp2_synapse.json"); + run_synapse_test<lowered_cell>("exp2syn", "neuron_simple_exp2_synapse.json"); } - diff --git a/tests/validation/validate_synapses.cu b/tests/validation/validate_synapses.cu new file mode 100644 index 00000000..0dedd584 --- /dev/null +++ b/tests/validation/validate_synapses.cu @@ -0,0 +1,16 @@ +#include <fvm_multicell.hpp> + +#include "../gtest.h" +#include "validate_synapses.hpp" + +using lowered_cell = nest::mc::fvm::fvm_multicell<nest::mc::gpu::backend>; + +TEST(simple_synapse, expsyn_neuron_ref) { + SCOPED_TRACE("expsyn"); + run_synapse_test<lowered_cell>("expsyn", "neuron_simple_exp_synapse.json"); +} + +TEST(simple_synapse, exp2syn_neuron_ref) { + SCOPED_TRACE("exp2syn"); + run_synapse_test<lowered_cell>("exp2syn", "neuron_simple_exp2_synapse.json"); +} diff --git a/tests/validation/validate_synapses.hpp b/tests/validation/validate_synapses.hpp new file mode 100644 index 00000000..a6ee35e9 --- /dev/null +++ b/tests/validation/validate_synapses.hpp @@ -0,0 +1,70 @@ +#include <json/json.hpp> + +#include <cell.hpp> +#include <cell_group.hpp> +#include <fvm_multicell.hpp> +#include <model.hpp> +#include <recipe.hpp> +#include <simple_sampler.hpp> +#include <util/path.hpp> + +#include "../gtest.h" + +#include "../test_common_cells.hpp" +#include "convergence_test.hpp" +#include "trace_analysis.hpp" +#include "validation_data.hpp" + +template <typename LoweredCell> +void run_synapse_test( + const char* syn_type, + const nest::mc::util::path& ref_data_path, + float t_end=70.f, + float dt=0.001) +{ + using namespace nest::mc; + + auto max_ncomp = g_trace_io.max_ncomp(); + nlohmann::json meta = { + {"name", "membrane voltage"}, + {"model", syn_type}, + {"sim", "nestmc"}, + {"units", "mV"}, + {"backend", LoweredCell::backend::name()} + }; + + cell c = make_cell_ball_and_stick(false); // no stimuli + parameter_list syn_default(syn_type); + c.add_synapse({1, 0.5}, syn_default); + add_common_voltage_probes(c); + + // injected spike events + postsynaptic_spike_event<float> synthetic_events[] = { + {{0u, 0u}, 10.0, 0.04}, + {{0u, 0u}, 20.0, 0.04}, + {{0u, 0u}, 40.0, 0.04} + }; + + // exclude points of discontinuity from linf analysis + std::vector<float> exclude = {10.f, 20.f, 40.f}; + + float sample_dt = 0.025f; + sampler_info samplers[] = { + {"soma.mid", {0u, 0u}, simple_sampler(sample_dt)}, + {"dend.mid", {0u, 1u}, simple_sampler(sample_dt)}, + {"dend.end", {0u, 2u}, simple_sampler(sample_dt)} + }; + + convergence_test_runner<int> runner("ncomp", samplers, meta); + runner.load_reference_data(ref_data_path); + + for (int ncomp = 10; ncomp<max_ncomp; ncomp*=2) { + c.cable(1)->set_compartments(ncomp); + model<LoweredCell> m(singleton_recipe{c}); + m.group(0).enqueue_events(synthetic_events); + + runner.run(m, ncomp, t_end, dt, exclude); + } + runner.report(); + runner.assert_all_convergence(); +} -- GitLab