diff --git a/CMakeLists.txt b/CMakeLists.txt index 8155f906579b801f17b0e2376b5869020f8bcf6f..0d522c7d5c15fb7f4bf3cd4e45924ae57a9bd750 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -160,9 +160,8 @@ install(TARGETS arbor-config-defs EXPORT arbor-targets) # Interface library `arbor-private-deps` collects dependencies, options etc. # for the arbor library. - add_library(arbor-private-deps INTERFACE) -target_link_libraries(arbor-private-deps INTERFACE arbor-config-defs ext-random123) +target_link_libraries(arbor-private-deps INTERFACE arbor-config-defs ext-random123 ${CMAKE_DL_LIBS} arbor-compiler-compat) install(TARGETS arbor-private-deps EXPORT arbor-targets) # Interface library `arborenv-private-deps` collects dependencies, options etc. diff --git a/arbor/arbexcept.cpp b/arbor/arbexcept.cpp index 2bc11e9274d82eb57a14c440928ed8b194a90f2c..d12883b5fcb1d6f0ad853a6a0dbf308fbdc28f7e 100644 --- a/arbor/arbexcept.cpp +++ b/arbor/arbexcept.cpp @@ -15,11 +15,13 @@ bad_cell_probe::bad_cell_probe(cell_kind kind, cell_gid_type gid): gid(gid), kind(kind) {} + bad_cell_description::bad_cell_description(cell_kind kind, cell_gid_type gid): arbor_exception(pprintf("recipe::get_cell_kind(gid={}) -> {} does not match the cell type provided by recipe::get_cell_description(gid={})", gid, kind, gid)), gid(gid), kind(kind) {} + bad_target_description::bad_target_description(cell_gid_type gid, cell_size_type rec_val, cell_size_type cell_val): arbor_exception(pprintf("Model building error on cell {}: recipe::num_targets(gid={}) = {} is greater than the number of synapses on the cell = {}", gid, gid, rec_val, cell_val)), gid(gid), rec_val(rec_val), cell_val(cell_val) @@ -148,5 +150,16 @@ range_check_failure::range_check_failure(const std::string& whatstr, double valu value(value) {} +file_not_found_error::file_not_found_error(const std::string &fn) + : arbor_exception(pprintf("Could not find file '{}'", fn)), + filename{fn} +{} + +bad_catalogue_error::bad_catalogue_error(const std::string &fn, const std::string& call) + : arbor_exception(pprintf("Error in '{}' while opening catalogue '{}'", call, fn)), + filename{fn}, + failed_call{call} +{} + } // namespace arb diff --git a/arbor/include/arbor/arbexcept.hpp b/arbor/include/arbor/arbexcept.hpp index f9e1a7eb7befc50a5961a95df633190be066113a..f2c49675604bc038267c01ec3652cb70431057e4 100644 --- a/arbor/include/arbor/arbexcept.hpp +++ b/arbor/include/arbor/arbexcept.hpp @@ -1,5 +1,6 @@ #pragma once +#include <any> #include <stdexcept> #include <string> @@ -170,4 +171,15 @@ struct range_check_failure: arbor_exception { double value; }; +struct file_not_found_error: arbor_exception { + file_not_found_error(const std::string& fn); + std::string filename; +}; + +struct bad_catalogue_error: arbor_exception { + bad_catalogue_error(const std::string& fn, const std::string& call); + std::string filename; + std::string failed_call; +}; + } // namespace arb diff --git a/arbor/include/arbor/mechcat.hpp b/arbor/include/arbor/mechcat.hpp index 436edbc2d21d794c2b26e4065c920a271825c914..c10b6f4bd96fc1ada8ad2830e35cf88ad323c309 100644 --- a/arbor/include/arbor/mechcat.hpp +++ b/arbor/include/arbor/mechcat.hpp @@ -1,5 +1,6 @@ #pragma once +#include <filesystem> #include <map> #include <memory> #include <string> @@ -127,4 +128,8 @@ const mechanism_catalogue& global_default_catalogue(); const mechanism_catalogue& global_allen_catalogue(); const mechanism_catalogue& global_bbp_catalogue(); +// Load catalogue from disk. + +const mechanism_catalogue& load_catalogue(const std::filesystem::path&); + } // namespace arb diff --git a/arbor/mechcat.cpp b/arbor/mechcat.cpp index 001ab95eb75dca4b06f9465239328997ac01a1b9..aefb964ff09b3cef03207ac452b5d517c410f049 100644 --- a/arbor/mechcat.cpp +++ b/arbor/mechcat.cpp @@ -3,6 +3,9 @@ #include <memory> #include <string> #include <vector> +#include <cassert> + +#include <dlfcn.h> #include <arbor/arbexcept.hpp> #include <arbor/mechcat.hpp> @@ -579,4 +582,29 @@ std::pair<mechanism_ptr, mechanism_overrides> mechanism_catalogue::instance_impl mechanism_catalogue::~mechanism_catalogue() = default; +static void check_dlerror(const std::string& fn, const std::string& call) { + auto error = dlerror(); + if (error) { throw arb::bad_catalogue_error{fn, call}; } +} + +const mechanism_catalogue& load_catalogue(const std::filesystem::path& fn) { + typedef const void* global_catalogue_t(); + + if (!std::filesystem::exists(fn)) { throw arb::file_not_found_error{fn}; } + + auto plugin = dlopen(fn.c_str(), RTLD_LAZY); + check_dlerror(fn, "dlopen"); + assert(plugin); + + auto get_catalogue = (global_catalogue_t*)dlsym(plugin, "get_catalogue"); + check_dlerror(fn, "dlsym"); + + /* NOTE We do not free the DSO handle here and accept retaining the handles + until termination since the mechanisms provided by the catalogue may have + a different lifetime than the actual catalogue itfself. This is not a + leak proper as `dlopen` caches handles for us. + */ + return *((const mechanism_catalogue*)get_catalogue()); +} + } // namespace arb diff --git a/cmake/CompilerOptions.cmake b/cmake/CompilerOptions.cmake index 9780d534e5b8c1b241f3649a7b8e6b215cf07cea..cf8b248fcf8cfb181791e40c1070f05e75769065 100644 --- a/cmake/CompilerOptions.cmake +++ b/cmake/CompilerOptions.cmake @@ -17,6 +17,23 @@ if(${ARBDEV_COLOR}) add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:${colorflags}>") endif() +# A library to collect compiler-specific linking adjustments. + +add_library(arbor-compiler-compat INTERFACE) +# TODO Remove when upgrading GCC. +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.1) + target_link_libraries(arbor-compiler-compat INTERFACE stdc++fs) + endif() +endif() +# TODO Remove when upgrading Clang +if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0) + target_link_libraries(arbor-compiler-compat INTERFACE stdc++fs) + endif() +endif() +install(TARGETS arbor-compiler-compat EXPORT arbor-targets) + # Warning options: disable specific spurious warnings as required. set(CXXOPT_WALL diff --git a/cmake/arbor-config.cmake.in b/cmake/arbor-config.cmake.in index 246d92d51d52a6e55bc1f642b74a1d95e8341668..d551de69d3108bfc7ff2f8553f0e6ae3bcc256db 100644 --- a/cmake/arbor-config.cmake.in +++ b/cmake/arbor-config.cmake.in @@ -44,6 +44,13 @@ function(_append_property target property) endif() endfunction() +set(ARB_VECTORIZE @ARB_VECTORIZE@) +set(ARB_ARCH @ARB_ARCH@) +set(ARB_MODCC_FLAGS @ARB_MODCC_FLAGS@) +set(ARB_CXX @CMAKE_CXX_COMPILER@) +set(ARB_CXX_FLAGS @CMAKE_CXX_FLAGS@) +set(ARB_CXXOPT_ARCH @ARB_CXXOPT_ARCH@) + _append_property(arbor::arbor INTERFACE_LINK_LIBRARIES @arbor_add_import_libs@) _append_property(arbor::arborenv INTERFACE_LINK_LIBRARIES @arborenv_add_import_libs@) _append_property(arbor::arbornml INTERFACE_LINK_LIBRARIES @arbornml_add_import_libs@) diff --git a/doc/concepts/mechanisms.rst b/doc/concepts/mechanisms.rst index e507443090c2d7af47befa0ba0b564695ff5d2d4..b67ea243d9bf6d5464ecb6c7ebe0e0a746a96ce4 100644 --- a/doc/concepts/mechanisms.rst +++ b/doc/concepts/mechanisms.rst @@ -51,19 +51,33 @@ used by the `Allen Institute <https://alleninstitute.org/>`_ and the `Blue Brain the `Allen institute mechanisms <https://github.com/arbor-sim/arbor/tree/master/mechanisms/allen>`_ and the `BBP mechanisms <https://github.com/arbor-sim/arbor/tree/master/mechanisms/bbp>`_ at the provided links.) -Default catalogue -''''''''''''''''' +Built-in Catalogues +''''''''''''''''''' -Arbor provides a default catalogue with the following mechanisms: +Arbor provides the *default* catalogue with the following mechanisms: * *pas*: Leaky current (:ref:`density mechanism <mechanisms-density>`). -* *hh*: Classic Hodgkin-Huxley dynamics (:ref:`density mechanism <mechanisms-density>`). -* *nernst*: Calculate reversal potential for an ionic species using the Nernst equation (:ref:`reversal potential mechanism <mechanisms-revpot>`) -* *expsyn*: Synapse with discontinuous change in conductance at an event followed by an exponential decay (:ref:`point mechanism <mechanisms-point>`). -* *exp2syn*: Bi-exponential conductance synapse described by two time constants: rise and decay (:ref:`point mechanism <mechanisms-point>`). +* *hh*: Classic Hodgkin-Huxley dynamics (:ref:`density mechanism + <mechanisms-density>`). +* *nernst*: Calculate reversal potential for an ionic species using the Nernst + equation (:ref:`reversal potential mechanism <mechanisms-revpot>`). **NB** + This is not meant to be used directly +* *expsyn*: Synapse with discontinuous change in conductance at an event + followed by an exponential decay (:ref:`point mechanism <mechanisms-point>`). +* *exp2syn*: Bi-exponential conductance synapse described by two time constants: + rise and decay (:ref:`point mechanism <mechanisms-point>`). With the exception of *nernst*, these mechanisms are the same as those available in NEURON. +Two catalogues are provided that collect mechanisms associated with specific projects and model databases: + +* *bbp* For models published by the Blue Brain Project (BBP). +* *allen* For models published on the Allen Brain Atlas Database. + +Further catalogues can be added by extending the list of built-in catalogues in +the arbor source tree or by compiling a dynamically loadable catalogue +(:ref:`extending catalogues <extending-catalogues>`). + Parameters '''''''''' diff --git a/doc/internals/extending-catalogues.rst b/doc/internals/extending-catalogues.rst new file mode 100644 index 0000000000000000000000000000000000000000..5ac0374815f347059f174cbbcd924ec42df99020 --- /dev/null +++ b/doc/internals/extending-catalogues.rst @@ -0,0 +1,57 @@ +.. _extending-catalogues: + +Adding Catalogues to Arbor +========================== + +There are two ways new mechanisms catalogues can be added to Arbor, statically +or dynamically. None is considered to be part of the stable user-facing API at +the moment, although the dynamic approach is aligned with our eventual goals. + +Both require a copy of the Arbor source tree and the compiler toolchain used to +build Arbor in addition to the installed library. + +Static Extensions +''''''''''''''''' + +This will produce a catalogue of the same level of integration as the built-in +catalogues (*default*, *bbp*, and *allen*). The required steps are as follows + +1. Go to the Arbor source tree. +2. Create a new directory under *mechanisms*. +3. Add your .mod files. +4. Edit *mechanisms/CMakeLists.txt* to add a definition like this + + .. code-block :: cmake + + make_catalogue( + NAME <catalogue-name> # Name of your catalogue + SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/<directory>" # Directory name (added above) + OUTPUT "<output-name>" # Variable name to output to + MECHS <names>) # Space separated list of mechanism + # names w/o .mod suffix. + +5. Add your `output-name` to the `arbor_mechanism_sources` list. +6. Add a `global_NAME_catalogue` function in `mechcat.hpp` and `mechcat.cpp` +7. Bind this function in `python/mechanisms.cpp`. + +All steps can be more or less copied from the surrounding code. + +Dynamic Extensions +'''''''''''''''''' + +This will produce a catalogue loadable at runtime by calling `load_catalogue` +with a filename in both C++ and Python. The steps are + +1. Prepare a directory containing your NMODL files (.mod suffixes required) +2. Call `build_catalogue` from the `scripts` directory + + .. code-block :: bash + + build-catalogue <name> <path/to/nmodl> + +All files with the suffix `.mod` located in `<path/to/nmodl>` will be baked into +a catalogue named `lib<name>-catalogue.so` and placed into your current working +directory. Note that these files are platform-specific and should only be used +on the combination of OS, compiler, arbor, and machine they were built with. + +See the demonstration in `python/example/dynamic-catalogue.py` for an example. diff --git a/mechanisms/BuildModules.cmake b/mechanisms/BuildModules.cmake index 945671f21d565ad2ca487979f56d74d9baf95915..d35a361b2c64314d5c4730d2281c01d8c845ff6e 100644 --- a/mechanisms/BuildModules.cmake +++ b/mechanisms/BuildModules.cmake @@ -56,3 +56,81 @@ function(build_modules) add_custom_target(${build_modules_TARGET} DEPENDS ${depends}) endif() endfunction() + +function("make_catalogue") + cmake_parse_arguments(MK_CAT "" "NAME;SOURCES;OUTPUT;ARBOR;STANDALONE" "MECHS" ${ARGN}) + set(MK_CAT_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated/${MK_CAT_NAME}") + + # Need to set ARB_WITH_EXTERNAL_MODCC *and* modcc + set(external_modcc) + if(ARB_WITH_EXTERNAL_MODCC) + set(external_modcc MODCC ${modcc}) + endif() + + message("Catalogue name: ${MK_CAT_NAME}") + message("Catalogue mechanisms: ${MK_CAT_MECHS}") + message("Catalogue sources: ${MK_CAT_SOURCES}") + message("Catalogue output: ${MK_CAT_OUT_DIR}") + message("Arbor source tree: ${MK_CAT_ARBOR}") + message("Build as standalone: ${MK_CAT_STANDALONE}") + message("Source directory: ${PROJECT_SOURCE_DIR}") + message("Arbor arch: ${ARB_CXXOPT_ARCH}") + + file(MAKE_DIRECTORY "${MK_CAT_OUT_DIR}") + + if (NOT TARGET build_all_mods) + add_custom_target(build_all_mods) + endif() + + build_modules( + ${MK_CAT_MECHS} + SOURCE_DIR "${MK_CAT_SOURCES}" + DEST_DIR "${MK_CAT_OUT_DIR}" + ${external_modcc} # NB: expands to 'MODCC <binary>' to add an optional argument + MODCC_FLAGS -t cpu -t gpu ${ARB_MODCC_FLAGS} -N arb::${MK_CAT_NAME}_catalogue + GENERATES .hpp _cpu.cpp _gpu.cpp _gpu.cu + TARGET build_catalogue_${MK_CAT_NAME}_mods) + + set(catalogue_${MK_CAT_NAME}_source ${CMAKE_CURRENT_BINARY_DIR}/${MK_CAT_NAME}_catalogue.cpp) + set(catalogue_${MK_CAT_NAME}_options -A arbor -I ${MK_CAT_OUT_DIR} -o ${catalogue_${MK_CAT_NAME}_source} -B multicore -C ${MK_CAT_NAME} -N arb::${MK_CAT_NAME}_catalogue) + if(ARB_WITH_GPU) + list(APPEND catalogue_${MK_CAT_NAME}_options -B gpu) + endif() + + add_custom_command( + OUTPUT ${catalogue_${MK_CAT_NAME}_source} + COMMAND ${PROJECT_SOURCE_DIR}/mechanisms/generate_catalogue ${catalogue_${MK_CAT_NAME}_options} ${MK_CAT_MECHS} + COMMENT "Building catalogue ${MK_CAT_NAME}" + DEPENDS ${PROJECT_SOURCE_DIR}/mechanisms/generate_catalogue) + + add_custom_target(${MK_CAT_NAME}_catalogue_cpp_target DEPENDS ${catalogue_${MK_CAT_NAME}_source}) + add_dependencies(build_catalogue_${MK_CAT_NAME}_mods ${MK_CAT_NAME}_catalogue_cpp_target) + add_dependencies(build_all_mods build_catalogue_${MK_CAT_NAME}_mods) + + foreach(mech ${MK_CAT_MECHS}) + list(APPEND catalogue_${MK_CAT_NAME}_source ${MK_CAT_OUT_DIR}/${mech}_cpu.cpp) + if(ARB_WITH_GPU) + list(APPEND catalogue_${MK_CAT_NAME}_source ${MK_CAT_OUT_DIR}/${mech}_gpu.cpp ${MK_CAT_OUT_DIR}/${mech}_gpu.cu) + endif() + endforeach() + set(${MK_CAT_OUTPUT} ${catalogue_${MK_CAT_NAME}_source} PARENT_SCOPE) + + set_source_files_properties(${catalogue_${MK_CAT_NAME}_source} COMPILE_FLAGS ${ARB_CXXOPT_ARCH}) + + if(${MK_CAT_STANDALONE}) + add_library(${MK_CAT_NAME}-catalogue SHARED ${catalogue_${MK_CAT_NAME}_source}) + target_compile_definitions(${MK_CAT_NAME}-catalogue PUBLIC STANDALONE=1) + set_target_properties(${MK_CAT_NAME}-catalogue + PROPERTIES + SUFFIX ".so" + PREFIX "" + CXX_STANDARD 17) + target_include_directories(${MK_CAT_NAME}-catalogue PUBLIC "${MK_CAT_ARBOR}/arbor/") + + if(TARGET arbor) + target_link_libraries(${MK_CAT_NAME}-catalogue PRIVATE arbor) + else() + target_link_libraries(${MK_CAT_NAME}-catalogue PRIVATE arbor::arbor) + endif() + endif() +endfunction() diff --git a/mechanisms/CMakeLists.txt b/mechanisms/CMakeLists.txt index 032adf1244614217b5cad45498b20dbf9813903f..bb56bfc25c89e7cb5b15b8e03eb064341ffbbedb 100644 --- a/mechanisms/CMakeLists.txt +++ b/mechanisms/CMakeLists.txt @@ -1,135 +1,37 @@ include(BuildModules.cmake) -set(external_modcc) -if(ARB_WITH_EXTERNAL_MODCC) - set(external_modcc MODCC ${modcc}) -endif() - -add_custom_target(build_all_mods) - -set(mech_sources "") - -# BBP -set(bbp_mechanisms CaDynamics_E2 Ca_HVA Ca_LVAst Ih Im K_Pst K_Tst Nap_Et2 NaTa_t NaTs2_t SK_E2 SKv3_1) -set(bbp_mod_srcdir "${CMAKE_CURRENT_SOURCE_DIR}/bbp") -set(mech_dir "${CMAKE_CURRENT_BINARY_DIR}/generated/bbp") -file(MAKE_DIRECTORY "${mech_dir}") - -build_modules( - ${bbp_mechanisms} - SOURCE_DIR "${bbp_mod_srcdir}" - DEST_DIR "${mech_dir}" - ${external_modcc} - MODCC_FLAGS -t cpu -t gpu ${ARB_MODCC_FLAGS} -N arb::bbp_catalogue - GENERATES .hpp _cpu.cpp _gpu.cpp _gpu.cu - TARGET build_catalogue_bbp_mods) - -set(bbp_catalogue_source ${CMAKE_CURRENT_BINARY_DIR}/bbp_catalogue.cpp) -set(bbp_catalogue_options -A arbor -I ${mech_dir} -o ${bbp_catalogue_source} -B multicore -C bbp -N arb::bbp_catalogue) -if(ARB_WITH_GPU) - list(APPEND bbp_catalogue_options -B gpu) -endif() - -add_custom_command( - OUTPUT ${bbp_catalogue_source} - COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/generate_catalogue ${bbp_catalogue_options} ${bbp_mechanisms} - DEPENDS generate_catalogue -) - -add_custom_target(bbp_catalogue_cpp_target DEPENDS ${bbp_catalogue_source}) -add_dependencies(build_catalogue_bbp_mods bbp_catalogue_cpp_target) -add_dependencies(build_all_mods build_catalogue_bbp_mods) - -list(APPEND mech_sources ${bbp_catalogue_source}) -foreach(mech ${bbp_mechanisms}) - list(APPEND mech_sources ${mech_dir}/${mech}_cpu.cpp) - if(ARB_WITH_GPU) - list(APPEND mech_sources ${mech_dir}/${mech}_gpu.cpp) - list(APPEND mech_sources ${mech_dir}/${mech}_gpu.cu) - endif() -endforeach() - -# ALLEN -set(allen_mechanisms CaDynamics Ca_HVA Ca_LVA Ih Im Im_v2 K_P K_T Kd Kv2like Kv3_1 NaTa NaTs NaV Nap SK) -set(allen_mod_srcdir "${CMAKE_CURRENT_SOURCE_DIR}/allen") -set(mech_dir "${CMAKE_CURRENT_BINARY_DIR}/generated/allen") -file(MAKE_DIRECTORY "${mech_dir}") - -build_modules( - ${allen_mechanisms} - SOURCE_DIR "${allen_mod_srcdir}" - DEST_DIR "${mech_dir}" - ${external_modcc} - MODCC_FLAGS -t cpu -t gpu ${ARB_MODCC_FLAGS} -N arb::allen_catalogue - GENERATES .hpp _cpu.cpp _gpu.cpp _gpu.cu - TARGET build_catalogue_allen_mods) - -set(allen_catalogue_source ${CMAKE_CURRENT_BINARY_DIR}/allen_catalogue.cpp) -set(allen_catalogue_options -A arbor -I ${mech_dir} -o ${allen_catalogue_source} -B multicore -C allen -N arb::allen_catalogue) -if(ARB_WITH_GPU) - list(APPEND allen_catalogue_options -B gpu) -endif() - -add_custom_command( - OUTPUT ${allen_catalogue_source} - COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/generate_catalogue ${allen_catalogue_options} ${allen_mechanisms} - DEPENDS generate_catalogue -) - -add_custom_target(allen_catalogue_cpp_target DEPENDS ${allen_catalogue_source}) -add_dependencies(build_catalogue_allen_mods allen_catalogue_cpp_target) -add_dependencies(build_all_mods build_catalogue_allen_mods) - -list(APPEND mech_sources ${allen_catalogue_source}) -foreach(mech ${allen_mechanisms}) - list(APPEND mech_sources ${mech_dir}/${mech}_cpu.cpp) - if(ARB_WITH_GPU) - list(APPEND mech_sources ${mech_dir}/${mech}_gpu.cpp) - list(APPEND mech_sources ${mech_dir}/${mech}_gpu.cu) - endif() -endforeach() - -# DEFAULT -set(default_mechanisms exp2syn expsyn hh kamt kdrmt nax nernst pas) -set(default_mod_srcdir "${CMAKE_CURRENT_SOURCE_DIR}/default") -set(mech_dir "${CMAKE_CURRENT_BINARY_DIR}/generated/default") -file(MAKE_DIRECTORY "${mech_dir}") - -build_modules( - ${default_mechanisms} - SOURCE_DIR "${default_mod_srcdir}" - DEST_DIR "${mech_dir}" - ${external_modcc} - MODCC_FLAGS -t cpu -t gpu ${ARB_MODCC_FLAGS} -N arb::default_catalogue - GENERATES .hpp _cpu.cpp _gpu.cpp _gpu.cu - TARGET build_catalogue_default_mods) - -set(default_catalogue_source ${CMAKE_CURRENT_BINARY_DIR}/default_catalogue.cpp) -set(default_catalogue_options -A arbor -I ${mech_dir} -o ${default_catalogue_source} -B multicore -C default -N arb::default_catalogue) -if(ARB_WITH_GPU) - list(APPEND default_catalogue_options -B gpu) -endif() - -add_custom_command( - OUTPUT ${default_catalogue_source} - COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/generate_catalogue ${default_catalogue_options} ${default_mechanisms} - DEPENDS generate_catalogue) - -add_custom_target(default_catalogue_cpp_target DEPENDS ${default_catalogue_source}) -add_dependencies(build_catalogue_default_mods default_catalogue_cpp_target) -add_dependencies(build_all_mods build_catalogue_default_mods) - -list(APPEND mech_sources ${default_catalogue_source}) -foreach(mech ${default_mechanisms}) - list(APPEND mech_sources ${mech_dir}/${mech}_cpu.cpp) - if(ARB_WITH_GPU) - list(APPEND mech_sources ${mech_dir}/${mech}_gpu.cpp) - list(APPEND mech_sources ${mech_dir}/${mech}_gpu.cu) - endif() -endforeach() +# Define catalogues +make_catalogue( + NAME bbp + SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/bbp" + OUTPUT "CAT_BBP_SOURCES" + MECHS CaDynamics_E2 Ca_HVA Ca_LVAst Ih Im K_Pst K_Tst Nap_Et2 NaTa_t NaTs2_t SK_E2 SKv3_1 + ARBOR "${PROJECT_SOURCE_DIR}" + STANDALONE FALSE) + +make_catalogue( + NAME allen + SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/allen" + OUTPUT "CAT_ALLEN_SOURCES" + MECHS CaDynamics Ca_HVA Ca_LVA Ih Im Im_v2 K_P K_T Kd Kv2like Kv3_1 NaTa NaTs NaV Nap SK + ARBOR "${PROJECT_SOURCE_DIR}" + STANDALONE FALSE) + +make_catalogue( + NAME default + SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/default" + OUTPUT "CAT_DEFAULT_SOURCES" + MECHS exp2syn expsyn hh kamt kdrmt nax nernst pas + ARBOR "${PROJECT_SOURCE_DIR}" + STANDALONE FALSE) + +# Join sources +set(arbor_mechanism_sources + ${CAT_BBP_SOURCES} + ${CAT_ALLEN_SOURCES} + ${CAT_DEFAULT_SOURCES} + PARENT_SCOPE) -# -set(arbor_mechanism_sources ${mech_sources} PARENT_SCOPE) if(ARB_WITH_CUDA_CLANG OR ARB_WITH_HIP_CLANG) set_source_files_properties(${arbor_mechanism_sources} PROPERTIES LANGUAGE CXX) endif() diff --git a/mechanisms/generate_catalogue b/mechanisms/generate_catalogue index a4ad3cfd05f6a0f073169c67c54d9e21db12faf7..8e8df375cd9e3d45b3897b5f355907e9b5be9d7d 100755 --- a/mechanisms/generate_catalogue +++ b/mechanisms/generate_catalogue @@ -116,7 +116,17 @@ const mechanism_catalogue& global_${catalogue}_catalogue() { return cat; } -} // namespace arb''') +} // namespace arb + +#ifdef STANDALONE +extern "C" { + const void* get_catalogue() { + static auto cat = arb::build_${catalogue}_catalogue(); + return (void*)&cat; + } +} +#endif +''') def indent(n, lines): return '{{:<{0!s}}}'.format(n+1).format('\n').join(lines) diff --git a/python/example/cat/dummy.mod b/python/example/cat/dummy.mod new file mode 100644 index 0000000000000000000000000000000000000000..df712b023df091c60cb826a00066ccd0485d2231 --- /dev/null +++ b/python/example/cat/dummy.mod @@ -0,0 +1,54 @@ +: Reference: Adams et al. 1982 - M-currents and other potassium currents in bullfrog sympathetic neurones +: Comment: corrected rates using q10 = 2.3, target temperature 34, orginal 21 + +NEURON { + SUFFIX dummy + USEION k READ ek WRITE ik + RANGE gImbar +} + +UNITS { + (S) = (siemens) + (mV) = (millivolt) + (mA) = (milliamp) +} + +PARAMETER { + gImbar = 0.00001 (S/cm2) +} + +STATE { + m +} + +BREAKPOINT { + SOLVE states METHOD cnexp + ik = gImbar*m*(v - ek) +} + +DERIVATIVE states { + LOCAL qt, mAlpha, mBeta + + qt = 2.3^((34-21)/10) + mAlpha = m_alpha(v) + mBeta = m_beta(v) + + m' = qt*(mAlpha - m*(mAlpha + mBeta)) +} + +INITIAL { + LOCAL mAlpha, mBeta + + mAlpha = m_alpha(v) + mBeta = m_beta(v) + + m = mAlpha/(mAlpha + mBeta) +} + +FUNCTION m_alpha(v) { + m_alpha = 3.3e-3*exp( 2.5*0.04*(v + 35)) +} +FUNCTION m_beta(v) { + m_beta = 3.3e-3*exp(-2.5*0.04*(v + 35)) +} + diff --git a/python/example/dynamic-catalogue.py b/python/example/dynamic-catalogue.py new file mode 100644 index 0000000000000000000000000000000000000000..d8da71925fbeca6c7e9706fc63760efdce2b374f --- /dev/null +++ b/python/example/dynamic-catalogue.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 + +from pathlib import Path + +import arbor as arb + +cat = 'cat-catalogue.so' + +class recipe(arb.recipe): + def __init__(self): + arb.recipe.__init__(self) + self.tree = arb.segment_tree() + self.tree.append(arb.mnpos, (0, 0, 0, 10), (1, 0, 0, 10), 1) + self.props = arb.neuron_cable_properties() + self.cat = arb.load_catalogue(cat) + self.props.register(self.cat) + d = arb.decor() + d.paint('(all)', 'dummy') + d.set_property(Vm=0.0) + self.cell = arb.cable_cell(self.tree, arb.label_dict(), d) + + def global_properties(self, _): + return self.props + + def num_cells(self): + return 1 + + def num_targets(self, gid): + return 0 + + def num_sources(self, gid): + return 0 + + def cell_kind(self, gid): + return arb.cell_kind.cable + + def cell_description(self, gid): + return self.cell + +if not Path(cat).is_file(): + print("""Catalogue not found in this directory. +Please ensure it has been compiled by calling") + <arbor>/scripts/build-catalogue cat <arbor>/python/examples/cat +where <arbor> is the location of the arbor source tree.""") + exit(1) + +rcp = recipe() +ctx = arb.context() +dom = arb.partition_load_balance(rcp, ctx) +sim = arb.simulation(rcp, dom, ctx) +sim.run(tfinal=30) diff --git a/python/mechanism.cpp b/python/mechanism.cpp index a1ac7a9d8f86120fa3d762dd8304f29b0193fc95..a2c93be2c0a6e15cb2258f84226dd134578cdf3a 100644 --- a/python/mechanism.cpp +++ b/python/mechanism.cpp @@ -156,6 +156,7 @@ void register_mechanisms(pybind11::module& m) { m.def("default_catalogue", [](){return arb::global_default_catalogue();}); m.def("allen_catalogue", [](){return arb::global_allen_catalogue();}); m.def("bbp_catalogue", [](){return arb::global_bbp_catalogue();}); + m.def("load_catalogue", [](const std::string& fn){return arb::load_catalogue(fn);}); // arb::mechanism_desc // For specifying a mechanism in the cable_cell interface. diff --git a/python/test/unit/runner.py b/python/test/unit/runner.py index b727c46647d251114cfef81d85c36d2703cf9cad..fb796792b6de99c5958cb778d2f655960083109f 100644 --- a/python/test/unit/runner.py +++ b/python/test/unit/runner.py @@ -18,10 +18,12 @@ try: import test_schedules import test_cable_probes import test_morphology + import test_catalogues # add more if needed except ModuleNotFoundError: from test import options from test.unit import test_contexts + from test.unit import test_catalogues from test.unit import test_domain_decompositions from test.unit import test_event_generators from test.unit import test_identifiers @@ -32,6 +34,7 @@ except ModuleNotFoundError: test_modules = [\ test_contexts,\ + test_catalogues,\ test_domain_decompositions,\ test_event_generators,\ test_identifiers,\ diff --git a/python/test/unit/test_catalogues.py b/python/test/unit/test_catalogues.py new file mode 100644 index 0000000000000000000000000000000000000000..eac665c1d0d3ce3052ddf58e7d8c4eca53745747 --- /dev/null +++ b/python/test/unit/test_catalogues.py @@ -0,0 +1,91 @@ +import unittest + +import arbor as arb + +# to be able to run .py file from child directory +import sys, os +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) + +try: + import options +except ModuleNotFoundError: + from test import options + +""" +tests for (dynamically loaded) catalogues +""" + +class recipe(arb.recipe): + def __init__(self): + arb.recipe.__init__(self) + self.tree = arb.segment_tree() + self.tree.append(arb.mnpos, (0, 0, 0, 10), (1, 0, 0, 10), 1) + self.props = arb.neuron_cable_properties() + try: + self.cat = arb.default_catalogue() + self.props.register(self.cat) + except: + print("Catalogue not found. Are you running from build directory?") + raise + + d = arb.decor() + d.paint('(all)', 'pas') + d.set_property(Vm=0.0) + self.cell = arb.cable_cell(self.tree, arb.label_dict(), d) + + def global_properties(self, _): + return self.props + + def num_cells(self): + return 1 + + def num_targets(self, gid): + return 0 + + def num_sources(self, gid): + return 0 + + def cell_kind(self, gid): + return arb.cell_kind.cable + + def cell_description(self, gid): + return self.cell + + +class Catalogues(unittest.TestCase): + def test_nonexistent(self): + with self.assertRaises(RuntimeError): + arb.load_catalogue("_NO_EXIST_.so") + + def test_shared_catalogue(self): + try: + cat = arb.load_catalogue("lib/dummy-catalogue.so") + except: + print("BBP catalogue not found. Are you running from build directory?") + raise + nms = [m for m in cat] + self.assertEqual(nms, ['dummy'], "Expected equal names.") + for nm in nms: + prm = list(cat[nm].parameters.keys()) + self.assertEqual(prm, ['gImbar'], "Expected equal parameters on mechanism '{}'.".format(nm)) + + def test_simulation(self): + rcp = recipe() + ctx = arb.context() + dom = arb.partition_load_balance(rcp, ctx) + sim = arb.simulation(rcp, dom, ctx) + sim.run(tfinal=30) + + +def suite(): + # specify class and test functions in tuple (here: all tests starting with 'test' from class Contexts + suite = unittest.makeSuite(Catalogues, ('test')) + return suite + +def run(): + v = options.parse_arguments().verbosity + runner = unittest.TextTestRunner(verbosity = v) + runner.run(suite()) + +if __name__ == "__main__": + run() diff --git a/scripts/build-catalogue b/scripts/build-catalogue new file mode 100755 index 0000000000000000000000000000000000000000..034b2079d571c39fa9cf90986dfbd9bf2659dbcf --- /dev/null +++ b/scripts/build-catalogue @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 + +import subprocess as sp +import sys +from tempfile import TemporaryDirectory +import os +from pathlib import Path +import shutil +import stat +import string +import argparse + +def parse_arguments(): + def append_slash(s): + return s+'/' if s and not s.endswith('/') else s + + class ConciseHelpFormatter(argparse.HelpFormatter): + def __init__(self, **kwargs): + super(ConciseHelpFormatter, self).__init__(max_help_position=20, **kwargs) + + def _format_action_invocation(self, action): + if not action.option_strings: + return super(ConciseHelpFormatter, self)._format_action_invocation(action) + else: + optstr = ', '.join(action.option_strings) + if action.nargs==0: + return optstr + else: + return optstr+' '+self._format_args(action, action.dest.upper()) + + parser = argparse.ArgumentParser( + description = 'Generate dynamic catalogue and build it into a shared object.', + usage = '%(prog)s catalogue_name mod_source_dir', + add_help = False, + formatter_class = ConciseHelpFormatter) + + parser.add_argument('name', + metavar='name', + type=str, + help='Catalogue name.') + + parser.add_argument('-s', '--source', + metavar='source', + type=str, + default=Path(__file__).parents[1].resolve(), + help='Path to arbor sources; defaults to parent of this script\'s directory') + + parser.add_argument('modpfx', + metavar='modpfx', + type=str, + help='Directory name where *.mod files live.') + + parser.add_argument('-v', '--verbose', + action='store_true', + help='Verbose.') + + + parser.add_argument('-h', '--help', + action='help', + help='display this help and exit') + + return vars(parser.parse_args()) + +args = parse_arguments() +pwd = Path.cwd() +name = args['name'] +mod_dir = pwd / Path(args['modpfx']) +mods = [ f[:-4] for f in os.listdir(mod_dir) if f.endswith('.mod') ] +verbose = args['verbose'] +arb = args['source'] + +cmake = """ +cmake_minimum_required(VERSION 3.9) +project({catalogue}-cat LANGUAGES CXX) + +find_package(arbor REQUIRED) + +include(BuildModules.cmake) + +set(ARB_WITH_EXTERNAL_MODCC true) +find_program(modcc NAMES modcc) + +make_catalogue( + NAME {catalogue} + SOURCES "${{CMAKE_CURRENT_SOURCE_DIR}}/mod" + OUTPUT "CAT_{catalogue}_SOURCES" + MECHS {mods} + ARBOR {arb}) +""" + +print(f"Building catalogue '{name}' from mechanisms in {mod_dir}") +for m in mods: + print(" *", m) + +with TemporaryDirectory() as tmp: + print(f"Setting up temporary build directory: {tmp}") + tmp = Path(tmp) + shutil.copytree(mod_dir, tmp / 'mod') + os.mkdir(tmp / 'build') + os.chdir(tmp / 'build') + with open(tmp / 'CMakeLists.txt', 'w') as fd: + fd.write(cmake.format(catalogue=name, mods=' '.join(mods), arb=arb)) + shutil.copy2(f'{arb}/mechanisms/BuildModules.cmake', tmp) + shutil.copy2(f'{arb}/mechanisms/generate_catalogue', tmp) + print("Configuring...") + sp.run('cmake ..', shell=True, check=True, capture_output=not verbose) + print("Building...") + sp.run('make', shell=True, capture_output=not verbose) + print("Cleaning-up...") + shutil.copy2(f'{name}-catalogue.so', pwd) + print(f'Catalogue has been built and copied to {pwd}/{name}-catalogue.so') diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 7a0abc1d8ad016724f22bca074635d26a2326775..040752618a9af914f1131b527d42d18a85d060a5 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -173,6 +173,16 @@ set(unit_sources unit_test_catalogue.cpp ) + +# for unit testing +make_catalogue( + NAME dummy + SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/dummy" + OUTPUT "CAT_DUMMY_SOURCES" + MECHS dummy + ARBOR "${PROJECT_SOURCE_DIR}" + STANDALONE ON) + if(ARB_WITH_GPU) list(APPEND unit_sources test_intrin.cu @@ -204,6 +214,7 @@ endif() add_executable(unit EXCLUDE_FROM_ALL ${unit_sources} ${test_mech_sources}) add_dependencies(unit build_test_mods) add_dependencies(tests unit) +add_dependencies(unit dummy-catalogue) if(ARB_WITH_NVCC) target_compile_options(unit PRIVATE -DARB_CUDA) @@ -221,6 +232,7 @@ endif() target_compile_options(unit PRIVATE ${ARB_CXXOPT_ARCH}) target_compile_definitions(unit PRIVATE "-DDATADIR=\"${CMAKE_CURRENT_SOURCE_DIR}/swc\"") +target_compile_definitions(unit PRIVATE "-DLIBDIR=\"${PROJECT_BINARY_DIR}/lib\"") target_include_directories(unit PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") target_link_libraries(unit PRIVATE gtest arbor arborenv arborio arbor-private-headers arbor-sup) diff --git a/test/unit/dummy/dummy.mod b/test/unit/dummy/dummy.mod new file mode 100644 index 0000000000000000000000000000000000000000..df712b023df091c60cb826a00066ccd0485d2231 --- /dev/null +++ b/test/unit/dummy/dummy.mod @@ -0,0 +1,54 @@ +: Reference: Adams et al. 1982 - M-currents and other potassium currents in bullfrog sympathetic neurones +: Comment: corrected rates using q10 = 2.3, target temperature 34, orginal 21 + +NEURON { + SUFFIX dummy + USEION k READ ek WRITE ik + RANGE gImbar +} + +UNITS { + (S) = (siemens) + (mV) = (millivolt) + (mA) = (milliamp) +} + +PARAMETER { + gImbar = 0.00001 (S/cm2) +} + +STATE { + m +} + +BREAKPOINT { + SOLVE states METHOD cnexp + ik = gImbar*m*(v - ek) +} + +DERIVATIVE states { + LOCAL qt, mAlpha, mBeta + + qt = 2.3^((34-21)/10) + mAlpha = m_alpha(v) + mBeta = m_beta(v) + + m' = qt*(mAlpha - m*(mAlpha + mBeta)) +} + +INITIAL { + LOCAL mAlpha, mBeta + + mAlpha = m_alpha(v) + mBeta = m_beta(v) + + m = mAlpha/(mAlpha + mBeta) +} + +FUNCTION m_alpha(v) { + m_alpha = 3.3e-3*exp( 2.5*0.04*(v + 35)) +} +FUNCTION m_beta(v) { + m_beta = 3.3e-3*exp(-2.5*0.04*(v + 35)) +} + diff --git a/test/unit/test_mechcat.cpp b/test/unit/test_mechcat.cpp index 1b3656765d1fe35bb812f3f309ecbd7938fb679b..2b6b1a6465f0ac85559e8c809ed3be3daa19e51d 100644 --- a/test/unit/test_mechcat.cpp +++ b/test/unit/test_mechcat.cpp @@ -8,6 +8,11 @@ #include "common.hpp" +#ifndef LIBDIR +#warning "LIBDIR not set; defaulting to '.'" +#define LIBDIR "." +#endif + using namespace std::string_literals; using namespace arb; @@ -286,6 +291,15 @@ TEST(mechcat, names) { } } +TEST(mechcat, loading) { + EXPECT_THROW(load_catalogue(LIBDIR "/does-not-exist-catalogue.so"), file_not_found_error); + EXPECT_THROW(load_catalogue(LIBDIR "/libarbor.a"), bad_catalogue_error); + const mechanism_catalogue* cat = nullptr; + EXPECT_NO_THROW(cat = &load_catalogue(LIBDIR "/dummy-catalogue.so")); + ASSERT_NE(cat, nullptr); + EXPECT_EQ(std::vector<std::string>{"dummy"}, cat->mechanism_names()); +} + TEST(mechcat, derived_info) { auto cat = build_fake_catalogue();