diff --git a/CMakeLists.txt b/CMakeLists.txt index ecdec04d5f..bcae036fe9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -590,7 +590,11 @@ endfunction() set(NRN_RUN_FROM_BUILD_DIR_ENV "NEURONHOME=${PROJECT_BINARY_DIR}/share/nrn" "NRNHOME=${PROJECT_BINARY_DIR}") prepend_to_var(PATH "${PROJECT_BINARY_DIR}/bin") -prepend_to_var(LD_LIBRARY_PATH "${PROJECT_BINARY_DIR}/lib") +if(APPLE) + prepend_to_var(DYLD_LIBRARY_PATH "${PROJECT_BINARY_DIR}/lib") +else() + prepend_to_var(LD_LIBRARY_PATH "${PROJECT_BINARY_DIR}/lib") +endif() if(NRN_ENABLE_CORENEURON) list(APPEND NRN_RUN_FROM_BUILD_DIR_ENV "CORENRNHOME=${PROJECT_BINARY_DIR}") endif() diff --git a/cmake/NeuronFileLists.cmake b/cmake/NeuronFileLists.cmake index 309a3f10cb..ad70963033 100644 --- a/cmake/NeuronFileLists.cmake +++ b/cmake/NeuronFileLists.cmake @@ -14,6 +14,7 @@ set(HEADER_FILES_TO_INSTALL gnu/nrnran123.h nrniv/backtrace_utils.h nrniv/bbsavestate.h + nrniv/neuronapi.h nrnmpi/nrnmpidec.h nrnoc/cabvars.h nrnoc/md1redef.h @@ -221,6 +222,7 @@ set(NRNIV_FILE_LIST netpar.cpp nmodlrandom.cpp nonlinz.cpp + neuronapi.cpp nrncore_write.cpp nrncore_write/callbacks/nrncore_callbacks.cpp nrncore_write/data/cell_group.cpp diff --git a/src/ivoc/ivocmain.cpp b/src/ivoc/ivocmain.cpp index 315a375eda..6767b7560e 100644 --- a/src/ivoc/ivocmain.cpp +++ b/src/ivoc/ivocmain.cpp @@ -231,6 +231,7 @@ extern void hoc_nrnmpi_init(); #if NRNMPI_DYNAMICLOAD extern void nrnmpi_stubs(); extern std::string nrnmpi_load(); +void nrnmpi_load_or_exit(); #endif // some things are defined in libraries earlier than they are used so... @@ -805,3 +806,28 @@ int run_til_stdin() { } void hoc_notify_value() {} #endif + + +/// A top-level initialization of MPI given argc and argv. +/// Sets stubs, load dyn lib, and initializes +std::tuple nrn_mpi_setup(int argc, const char** argv) { +#if defined(AUTO_DLOPEN_NRNMECH) && AUTO_DLOPEN_NRNMECH == 0 + extern int nrn_noauto_dlopen_nrnmech; + nrn_noauto_dlopen_nrnmech = 1; +#endif + +#if NRNMPI +#if NRNMPI_DYNAMICLOAD + nrnmpi_stubs(); + for (int i = 1; i < argc; ++i) { + if (strcmp("-mpi", argv[i]) == 0) { + nrnmpi_load_or_exit(); + break; + } + } +#endif // NRNMPI_DYNAMICLOAD + auto argv_ptr = const_cast(&argv); // safe if individual strings not modified + nrnmpi_init(1, &argc, argv_ptr); // may change argc and argv +#endif // NRNMPI + return {argc, argv}; +} diff --git a/src/neuron/cache/mechanism_range.hpp b/src/neuron/cache/mechanism_range.hpp index 751639c6dc..4e2827e3d2 100644 --- a/src/neuron/cache/mechanism_range.hpp +++ b/src/neuron/cache/mechanism_range.hpp @@ -15,7 +15,7 @@ namespace neuron::cache { template void indices_to_cache(short type, Callable callable) { auto const pdata_size = nrn_prop_dparam_size_[type]; - auto* const dparam_semantics = memb_func[type].dparam_semantics; + auto* const dparam_semantics = memb_func[type].dparam_semantics.get(); for (int field = pdata_size - 1; field >= 0; --field) { // Check if the field-th dparam of this mechanism type is an ion variable. See // hoc_register_dparam_semantics. diff --git a/src/nrniv/neuronapi.cpp b/src/nrniv/neuronapi.cpp new file mode 100644 index 0000000000..3a286c87c0 --- /dev/null +++ b/src/nrniv/neuronapi.cpp @@ -0,0 +1,507 @@ +#include "neuronapi.h" + +#include "../../nrnconf.h" +#include "hocdec.h" +#include "nrniv_mf.h" +#include "nrnmpi.h" +#include "nrnmpiuse.h" +#include "ocfunc.h" +#include "ocjump.h" +#include "parse.hpp" +#include "section.h" + +/// A public face of hoc_Item +struct nrn_Item: public hoc_Item {}; + +struct SectionListIterator { + explicit SectionListIterator(nrn_Item*); + Section* next(void); + int done(void) const; + + private: + hoc_Item* initial; + hoc_Item* current; +}; + +struct SymbolTableIterator { + explicit SymbolTableIterator(Symlist*); + char const* next(void); + int done(void) const; + + private: + Symbol* current; +}; + +/**************************************** + * Connections to the rest of NEURON + ****************************************/ +extern int nrn_nobanner_; +extern int diam_changed; +extern int nrn_try_catch_nest_depth; +extern "C" void nrnpy_set_pr_etal(int (*cbpr_stdoe)(int, char*), int (*cbpass)()); +int ivocmain_session(int, const char**, const char**, int start_session); +void simpleconnectsection(); +extern Object* hoc_newobj1(Symbol*, int); +extern void nrn_change_nseg(Section*, int); +extern Section* section_new(Symbol* sym); +extern std::tuple nrn_mpi_setup(int argc, const char** argv); + +extern "C" { + +/**************************************** + * Initialization + ****************************************/ + +int nrn_init(int argc, const char** argv) { + nrn_nobanner_ = 1; + auto [final_argc, final_argv] = nrn_mpi_setup(argc, argv); + errno = 0; + return ivocmain_session(final_argc, final_argv, nullptr, 0); +} + +void nrn_stdout_redirect(int (*myprint)(int, char*)) { + // the first argument of myprint is an integer indicating the output stream + // if the int is 1, then stdout, else stderr + // the char* is the message to display + nrnpy_set_pr_etal(myprint, nullptr); +} + +/**************************************** + * Sections + ****************************************/ + +Section* nrn_section_new(char const* const name) { + auto* symbol = new Symbol; + symbol->name = strdup(name); + symbol->type = 1; + symbol->u.oboff = 0; + symbol->arayinfo = 0; + hoc_install_object_data_index(symbol); + return section_new(symbol); +} + +void nrn_section_connect(Section* child_sec, double child_x, Section* parent_sec, double parent_x) { + nrn_pushsec(child_sec); + hoc_pushx(child_x); + nrn_pushsec(parent_sec); + hoc_pushx(parent_x); + simpleconnectsection(); +} + +void nrn_section_length_set(Section* sec, const double length) { + // TODO: call can_change_morph(sec) to check pt3dconst_; how should we handle + // that? + // TODO: is there a named constant so we don't have to use the magic number 2? + sec->prop->dparam[2] = length; + // nrn_length_change updates 3D points if needed + nrn_length_change(sec, length); + diam_changed = 1; + sec->recalc_area_ = 1; +} + +double nrn_section_length_get(Section* sec) { + return section_length(sec); +} + +double nrn_section_Ra_get(Section* sec) { + return nrn_ra(sec); +} + +void nrn_section_Ra_set(Section* sec, double const val) { + // TODO: ensure val > 0 + // TODO: is there a named constant so we don't have to use the magic number 7? + sec->prop->dparam[7] = val; + diam_changed = 1; + sec->recalc_area_ = 1; +} + +char const* nrn_secname(Section* sec) { + return secname(sec); +} + +void nrn_section_push(Section* sec) { + nrn_pushsec(sec); +} + +void nrn_section_pop(void) { + nrn_sec_pop(); +} + +void nrn_mechanism_insert(Section* sec, const Symbol* mechanism) { + // TODO: throw exception if mechanism is not an insertable mechanism? + mech_insert1(sec, mechanism->subtype); +} + +/**************************************** + * Segments + ****************************************/ + +int nrn_nseg_get(Section const* sec) { + // always one more node than nseg + return sec->nnode - 1; +} + +void nrn_nseg_set(Section* const sec, const int nseg) { + nrn_change_nseg(sec, nseg); +} + +void nrn_segment_diam_set(Section* const sec, const double x, const double diam) { + Node* const node = node_exact(sec, x); + // TODO: this is fine if no 3D points; does it work if there are 3D points? + for (auto prop = node->prop; prop; prop = prop->next) { + if (prop->_type == MORPHOLOGY) { + prop->param(0) = diam; + diam_changed = 1; + node->sec->recalc_area_ = 1; + break; + } + } +} + +double nrn_rangevar_get(Symbol* sym, Section* sec, double x) { + return *nrn_rangepointer(sec, sym, x); +} + +void nrn_rangevar_set(Symbol* sym, Section* sec, double x, double value) { + *nrn_rangepointer(sec, sym, x) = value; +} + +void nrn_rangevar_push(Symbol* sym, Section* sec, double x) { + hoc_push(nrn_rangepointer(sec, sym, x)); +} + +nrn_Item* nrn_allsec(void) { + return static_cast(section_list); +} + +nrn_Item* nrn_sectionlist_data(Object* obj) { + // TODO: verify the obj is in fact a SectionList + return (nrn_Item*) obj->u.this_pointer; +} + +/**************************************** + * Functions, objects, and the stack + ****************************************/ + +Symbol* nrn_symbol(char const* const name) { + return hoc_lookup(name); +} + +int nrn_symbol_type(Symbol const* sym) { + // TODO: these types are in parse.hpp and are not the same between versions, + // so we really should wrap + return sym->type; +} + +void nrn_symbol_push(Symbol* sym) { + hoc_pushpx(sym->u.pval); +} + +void nrn_double_push(double val) { + hoc_pushx(val); +} + +double nrn_double_pop(void) { + return hoc_xpop(); +} + +void nrn_double_ptr_push(double* addr) { + hoc_pushpx(addr); +} + +double* nrn_double_ptr_pop(void) { + return hoc_pxpop(); +} + +void nrn_str_push(char** str) { + hoc_pushstr(str); +} + +char** nrn_pop_str(void) { + return hoc_strpop(); +} + +void nrn_int_push(int i) { + hoc_pushi(i); +} + +int nrn_int_pop(void) { + return hoc_ipop(); +} + +void nrn_object_push(Object* obj) { + hoc_push_object(obj); +} + +Object* nrn_object_pop(void) { + // NOTE: the returned object should be unref'd when no longer needed + Object** obptr = hoc_objpop(); + Object* new_ob_ptr = *obptr; + new_ob_ptr->refcount++; + hoc_tobj_unref(obptr); + return new_ob_ptr; +} + +nrn_stack_types_t nrn_stack_type(void) { + switch (hoc_stack_type()) { + case STRING: + return STACK_IS_STR; + case VAR: + return STACK_IS_VAR; + case NUMBER: + return STACK_IS_NUM; + case OBJECTVAR: + return STACK_IS_OBJVAR; + case OBJECTTMP: + return STACK_IS_OBJTMP; + case USERINT: + return STACK_IS_INT; + case SYMBOL: + return STACK_IS_SYM; + } + return STACK_UNKNOWN; +} + +char const* nrn_stack_type_name(nrn_stack_types_t id) { + switch (id) { + case STACK_IS_STR: + return "STRING"; + case STACK_IS_VAR: + return "VAR"; + case STACK_IS_NUM: + return "NUMBER"; + case STACK_IS_OBJVAR: + return "OBJECTVAR"; + case STACK_IS_OBJTMP: + return "OBJECTTMP"; + case STACK_IS_INT: + return "INT"; + case STACK_IS_SYM: + return "SYMBOL"; + default: + return "UNKNOWN"; + } +} + +Object* nrn_object_new(Symbol* sym, int narg) { + return hoc_newobj1(sym, narg); +} + +Symbol* nrn_method_symbol(Object* obj, char const* const name) { + return hoc_table_lookup(name, obj->ctemplate->symtable); +} + +void nrn_method_call(Object* obj, Symbol* method_sym, int narg) { + OcJump::execute_throw_on_exception(obj, method_sym, narg); +} + +void nrn_function_call(Symbol* sym, int narg) { + // NOTE: this differs from hoc_call_func in that the response remains on the + // stack + OcJump::execute_throw_on_exception(sym, narg); +} + +void nrn_object_ref(Object* obj) { + obj->refcount++; +} + +void nrn_object_unref(Object* obj) { + hoc_obj_unref(obj); +} + +char const* nrn_class_name(const Object* obj) { + return obj->ctemplate->sym->name; +} + +/**************************************** + * Miscellaneous + ****************************************/ +int nrn_hoc_call(char const* const command) { + return hoc_oc(command); +} + +SectionListIterator::SectionListIterator(nrn_Item* my_sectionlist) + : initial(my_sectionlist) + , current(my_sectionlist->next) {} + +Section* SectionListIterator::next(void) { + // NOTE: if no next element, returns nullptr + while (true) { + Section* sec = current->element.sec; + + if (sec->prop) { + current = current->next; + return sec; + } + hoc_l_delete(current); + section_unref(sec); + current = current->next; + if (current == initial) { + return nullptr; + } + } +} + +int SectionListIterator::done(void) const { + if (initial == current) { + return 1; + } + return 0; +} + +SymbolTableIterator::SymbolTableIterator(Symlist* list) + : current(list->first) {} + +char const* SymbolTableIterator::next(void) { + auto result = current->name; + current = current->next; + return result; +} + +int SymbolTableIterator::done(void) const { + if (!current) { + return 1; + } + return 0; +} + +// copy semantics isn't great, but only two data items +// and is cleaner to use in a for loop than having to free memory at the end +SectionListIterator* nrn_sectionlist_iterator_new(nrn_Item* my_sectionlist) { + return new SectionListIterator(my_sectionlist); +} + +void nrn_sectionlist_iterator_free(SectionListIterator* sl) { + delete sl; +} + +Section* nrn_sectionlist_iterator_next(SectionListIterator* sl) { + return sl->next(); +} + +int nrn_sectionlist_iterator_done(SectionListIterator* sl) { + return sl->done(); +} + +SymbolTableIterator* nrn_symbol_table_iterator_new(Symlist* my_symbol_table) { + return new SymbolTableIterator(my_symbol_table); +} + +void nrn_symbol_table_iterator_free(SymbolTableIterator* st) { + delete st; +} + +char const* nrn_symbol_table_iterator_next(SymbolTableIterator* st) { + return st->next(); +} + +int nrn_symbol_table_iterator_done(SymbolTableIterator* st) { + return st->done(); +} + +int nrn_vector_capacity(Object const* vec) { + // TODO: throw exception if vec is not a Vector + return vector_capacity((IvocVect*) vec->u.this_pointer); +} + +double* nrn_vector_data(Object* vec) { + // TODO: throw exception if vec is not a Vector + return vector_vec((IvocVect*) vec->u.this_pointer); +} + +double nrn_property_get(Object const* obj, const char* name) { + auto sym = hoc_table_lookup(name, obj->ctemplate->symtable); + if (!obj->ctemplate->is_point_) { + hoc_pushs(sym); + // put the pointer for the memory location on the stack + obj->ctemplate->steer(obj->u.this_pointer); + return *hoc_pxpop(); + } else { + int index = sym->u.rng.index; + return ob2pntproc_0(const_cast(obj))->prop->param_legacy(index); + } +} + +double nrn_property_array_get(Object const* obj, const char* name, int i) { + auto sym = hoc_table_lookup(name, obj->ctemplate->symtable); + if (!obj->ctemplate->is_point_) { + hoc_pushs(sym); + // put the pointer for the memory location on the stack + obj->ctemplate->steer(obj->u.this_pointer); + return hoc_pxpop()[i]; + } else { + int index = sym->u.rng.index; + return ob2pntproc_0(const_cast(obj))->prop->param_legacy(index + i); + } +} + +void nrn_property_set(Object* obj, const char* name, double value) { + auto sym = hoc_table_lookup(name, obj->ctemplate->symtable); + if (!obj->ctemplate->is_point_) { + hoc_pushs(sym); + // put the pointer for the memory location on the stack + obj->ctemplate->steer(obj->u.this_pointer); + *hoc_pxpop() = value; + } else { + int index = sym->u.rng.index; + ob2pntproc_0(obj)->prop->param_legacy(index) = value; + } +} + +void nrn_property_array_set(Object* obj, const char* name, int i, double value) { + auto sym = hoc_table_lookup(name, obj->ctemplate->symtable); + if (!obj->ctemplate->is_point_) { + hoc_pushs(sym); + // put the pointer for the memory location on the stack + obj->ctemplate->steer(obj->u.this_pointer); + hoc_pxpop()[i] = value; + } else { + int index = sym->u.rng.index; + ob2pntproc_0(obj)->prop->param_legacy(index + i) = value; + } +} + +void nrn_pp_property_array_set(Object* pp, const char* name, int i, double value) { + int index = hoc_table_lookup(name, pp->ctemplate->symtable)->u.rng.index; + ob2pntproc_0(pp)->prop->param_legacy(index + i) = value; +} + +void nrn_property_push(Object* obj, const char* name) { + auto sym = hoc_table_lookup(name, obj->ctemplate->symtable); + if (!obj->ctemplate->is_point_) { + hoc_pushs(sym); + // put the pointer for the memory location on the stack + obj->ctemplate->steer(obj->u.this_pointer); + } else { + int index = sym->u.rng.index; + hoc_push(ob2pntproc_0(obj)->prop->param_handle_legacy(index)); + } +} + +void nrn_property_array_push(Object* obj, const char* name, int i) { + auto sym = hoc_table_lookup(name, obj->ctemplate->symtable); + if (!obj->ctemplate->is_point_) { + hoc_pushs(sym); + // put the pointer for the memory location on the stack + obj->ctemplate->steer(obj->u.this_pointer); + hoc_pushpx(hoc_pxpop() + i); + } else { + int index = sym->u.rng.index; + hoc_push(ob2pntproc_0(obj)->prop->param_handle_legacy(index + i)); + } +} + +char const* nrn_symbol_name(const Symbol* sym) { + return sym->name; +} + +Symlist* nrn_symbol_table(Symbol* sym) { + // TODO: ensure sym is an object or class + // NOTE: to use with an object, call nrn_get_symbol(nrn_class_name(obj)) + return sym->u.ctemplate->symtable; +} + +Symlist* nrn_global_symbol_table(void) { + return hoc_built_in_symlist; +} +} diff --git a/src/nrniv/neuronapi.h b/src/nrniv/neuronapi.h new file mode 100644 index 0000000000..0c39c8c0a9 --- /dev/null +++ b/src/nrniv/neuronapi.h @@ -0,0 +1,115 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +// forward declarations (c++) and opaque c types +typedef struct Symbol Symbol; +typedef struct Object Object; +typedef struct Section Section; +typedef struct SectionListIterator SectionListIterator; +typedef struct nrn_Item nrn_Item; +typedef struct SymbolTableIterator SymbolTableIterator; +typedef struct Symlist Symlist; + +typedef enum { + STACK_IS_STR = 1, + STACK_IS_VAR = 2, + STACK_IS_NUM = 3, + STACK_IS_OBJVAR = 4, + STACK_IS_OBJTMP = 5, + STACK_IS_INT = 6, + STACK_IS_SYM = 7, + STACK_UNKNOWN = -1 +} nrn_stack_types_t; + +/**************************************** + * Initialization + ****************************************/ +int nrn_init(int argc, const char** argv); +void nrn_stdout_redirect(int (*myprint)(int, char*)); + +/**************************************** + * Sections + ****************************************/ +Section* nrn_section_new(char const* name); +void nrn_section_connect(Section* child_sec, double child_x, Section* parent_sec, double parent_x); +void nrn_section_length_set(Section* sec, double length); +double nrn_section_length_get(Section* sec); +double nrn_section_Ra_get(Section* sec); +void nrn_section_Ra_set(Section* sec, double val); +char const* nrn_secname(Section* sec); +void nrn_section_push(Section* sec); +void nrn_section_pop(void); +void nrn_mechanism_insert(Section* sec, const Symbol* mechanism); +nrn_Item* nrn_allsec(void); +nrn_Item* nrn_sectionlist_data(Object* obj); + +/**************************************** + * Segments + ****************************************/ +int nrn_nseg_get(const Section* sec); +void nrn_nseg_set(Section* sec, int nseg); +void nrn_segment_diam_set(Section* sec, double x, double diam); +void nrn_rangevar_push(Symbol* sym, Section* sec, double x); +double nrn_rangevar_get(Symbol* sym, Section* sec, double x); +void nrn_rangevar_set(Symbol* sym, Section* sec, double x, double value); + +/**************************************** + * Functions, objects, and the stack + ****************************************/ +Symbol* nrn_symbol(const char* name); +void nrn_symbol_push(Symbol* sym); +int nrn_symbol_type(const Symbol* sym); +void nrn_double_push(double val); +double nrn_double_pop(void); +void nrn_double_ptr_push(double* addr); +double* nrn_double_ptr_pop(void); +void nrn_str_push(char** str); +char** nrn_pop_str(void); +void nrn_int_push(int i); +int nrn_int_pop(void); +void nrn_object_push(Object* obj); +Object* nrn_object_pop(void); +nrn_stack_types_t nrn_stack_type(void); +char const* nrn_stack_type_name(nrn_stack_types_t id); +Object* nrn_object_new(Symbol* sym, int narg); +Symbol* nrn_method_symbol(Object* obj, char const* name); +// TODO: the next two functions throw exceptions in C++; need a version that +// returns a bool success indicator instead (this is actually the +// classic behavior of OcJump) +void nrn_method_call(Object* obj, Symbol* method_sym, int narg); +void nrn_function_call(Symbol* sym, int narg); +void nrn_object_ref(Object* obj); +void nrn_object_unref(Object* obj); +char const* nrn_class_name(Object const* obj); + +/**************************************** + * Miscellaneous + ****************************************/ +int nrn_hoc_call(char const* command); +SectionListIterator* nrn_sectionlist_iterator_new(nrn_Item* my_sectionlist); +void nrn_sectionlist_iterator_free(SectionListIterator* sl); +Section* nrn_sectionlist_iterator_next(SectionListIterator* sl); +int nrn_sectionlist_iterator_done(SectionListIterator* sl); +SymbolTableIterator* nrn_symbol_table_iterator_new(Symlist* my_symbol_table); +void nrn_symbol_table_iterator_free(SymbolTableIterator* st); +char const* nrn_symbol_table_iterator_next(SymbolTableIterator* st); +int nrn_symbol_table_iterator_done(SymbolTableIterator* st); +int nrn_vector_capacity(const Object* vec); +double* nrn_vector_data(Object* vec); +double nrn_property_get(const Object* obj, const char* name); +double nrn_property_array_get(const Object* obj, const char* name, int i); +void nrn_property_set(Object* obj, const char* name, double value); +void nrn_property_array_set(Object* obj, const char* name, int i, double value); +void nrn_property_push(Object* obj, const char* name); +void nrn_property_array_push(Object* obj, const char* name, int i); +char const* nrn_symbol_name(const Symbol* sym); +Symlist* nrn_symbol_table(Symbol* sym); +Symlist* nrn_global_symbol_table(void); +// TODO: need shapeplot information extraction + +#ifdef __cplusplus +} +#endif diff --git a/src/nrniv/nrncore_write/callbacks/nrncore_callbacks.cpp b/src/nrniv/nrncore_write/callbacks/nrncore_callbacks.cpp index dfdfa77307..b4adad9f5a 100644 --- a/src/nrniv/nrncore_write/callbacks/nrncore_callbacks.cpp +++ b/src/nrniv/nrncore_write/callbacks/nrncore_callbacks.cpp @@ -302,7 +302,7 @@ int nrnthread_dat2_1(int tid, tml_index[j] = type; ml_nodecount[j] = ml->nodecount; cg.ml_vdata_offset[j] = vdata_offset; - int* ds = memb_func[type].dparam_semantics; + int* ds = memb_func[type].dparam_semantics.get(); for (int psz = 0; psz < bbcore_dparam_size[type]; ++psz) { if (ds[psz] == -4 || ds[psz] == -6 || ds[psz] == -7 || ds[psz] == -11 || ds[psz] == 0) { // printf("%s ds[%d]=%d vdata_offset=%d\n", memb_func[type].sym->name, psz, ds[psz], @@ -641,7 +641,7 @@ int* datum2int(int type, int isart = nrn_is_artificial_[di.type]; int sz = bbcore_dparam_size[type]; int* pdata = new int[ml->nodecount * sz]; - int* semantics = memb_func[type].dparam_semantics; + int* semantics = memb_func[type].dparam_semantics.get(); for (int i = 0; i < ml->nodecount; ++i) { int ioff = i * sz; for (int j = 0; j < sz; ++j) { @@ -880,7 +880,7 @@ static std::map type2movable; static void setup_type2semantics() { if (type2movable.empty()) { for (int type = 0; type < n_memb_func; ++type) { - int* ds = memb_func[type].dparam_semantics; + int* ds = memb_func[type].dparam_semantics.get(); if (ds) { for (int psz = 0; psz < bbcore_dparam_size[type]; ++psz) { if (ds[psz] == -4) { // netsend semantics diff --git a/src/nrniv/nrncore_write/data/cell_group.cpp b/src/nrniv/nrncore_write/data/cell_group.cpp index 02a277f2ce..e034359387 100644 --- a/src/nrniv/nrncore_write/data/cell_group.cpp +++ b/src/nrniv/nrncore_write/data/cell_group.cpp @@ -252,13 +252,12 @@ void CellGroup::datumindex_fill(int ith, CellGroup& cg, DatumIndices& di, Memb_l if (dsize == 0) { return; } - int* dmap = memb_func[di.type].dparam_semantics; + int* dmap = memb_func[di.type].dparam_semantics.get(); assert(dmap); // what is the size of the nt._vdata portion needed for a single ml->dparam[i] int vdata_size = 0; for (int i = 0; i < dsize; ++i) { - int* ds = memb_func[di.type].dparam_semantics; - if (ds[i] == -4 || ds[i] == -6 || ds[i] == -7 || ds[i] == -11 || ds[i] == 0) { + if (dmap[i] == -4 || dmap[i] == -6 || dmap[i] == -7 || dmap[i] == -11 || dmap[i] == 0) { ++vdata_size; } } diff --git a/src/nrniv/ocjump.cpp b/src/nrniv/ocjump.cpp index 91712f5ec8..09932c1e3f 100644 --- a/src/nrniv/ocjump.cpp +++ b/src/nrniv/ocjump.cpp @@ -131,6 +131,29 @@ bool OcJump::execute(const char* stmt, Object* ob) { } } +void OcJump::execute_throw_on_exception(Object* obj, Symbol* sym, int narg) { + saved_state before{}; + try_catch_depth_increment tell_children_we_will_catch{}; + try { + hoc_call_ob_proc(obj, sym, narg); + } catch (...) { + before.restore(); + throw; + } +} + +void OcJump::execute_throw_on_exception(Symbol* sym, int narg) { + // NOTE: return value is left on the stack + saved_state before{}; + try_catch_depth_increment tell_children_we_will_catch{}; + try { + hoc_pushx(hoc_call_func(sym, narg)); + } catch (...) { + before.restore(); + throw; + } +} + void* OcJump::fpycall(void* (*f)(void*, void*), void* a, void* b) { saved_state before{}; try_catch_depth_increment tell_children_we_will_catch{}; diff --git a/src/nrniv/ocjump.h b/src/nrniv/ocjump.h index 781ee844ec..89ec2538fe 100644 --- a/src/nrniv/ocjump.h +++ b/src/nrniv/ocjump.h @@ -4,7 +4,8 @@ struct Object; union Objectdata; struct Symlist; -/** @brief How many NEURON try { ... } catch(...) { ... } blocks are in the call stack. +/** @brief How many NEURON try { ... } catch(...) { ... } blocks are in the call + * stack. * * Errors inside NEURON are triggered using hoc_execerror, which ultimately * throws an exception. To replicate the old logic, we sometimes need to insert @@ -40,4 +41,6 @@ struct OcJump { static bool execute(Inst* p); static bool execute(const char*, Object* ob = NULL); static void* fpycall(void* (*) (void*, void*), void*, void*); + static void execute_throw_on_exception(Symbol* sym, int narg); + static void execute_throw_on_exception(Object* obj, Symbol* sym, int narg); }; diff --git a/src/nrnoc/cabcode.cpp b/src/nrnoc/cabcode.cpp index 8cc9ac7a07..d171f060d1 100644 --- a/src/nrnoc/cabcode.cpp +++ b/src/nrnoc/cabcode.cpp @@ -319,17 +319,19 @@ void new_sections(Object* ob, Symbol* sym, Item** pitm, int size) { } } +/// @brief Creates a new section and registers with the global section list +Section* section_new(Symbol* sym) { + Section* sec = new_section(nullptr, sym, 0); + auto itm = lappendsec(section_list, sec); + sec->prop->dparam[8] = {neuron::container::do_not_search, itm}; + return sec; +} + #if USE_PYTHON struct NPySecObj; Section* nrnpy_newsection(NPySecObj* v) { - Item* itm; - Section* sec; - sec = new_section((Object*) 0, (Symbol*) 0, 0); -#if USE_PYTHON + auto sec = section_new(nullptr); sec->prop->dparam[PROP_PY_INDEX] = static_cast(v); -#endif - itm = lappendsec(section_list, sec); - sec->prop->dparam[8] = itm; return sec; } #endif diff --git a/src/nrnoc/init.cpp b/src/nrnoc/init.cpp index 7b47891358..b423bc6f7e 100644 --- a/src/nrnoc/init.cpp +++ b/src/nrnoc/init.cpp @@ -841,6 +841,7 @@ void update_mech_ppsym_for_modlrandom( namespace neuron::mechanism::detail { + // Use this if string.c_str() causes possibility of // AddressSanitizer: stack-use-after-scope on address void register_data_fields(int mechtype, @@ -877,15 +878,17 @@ void register_data_fields(int mechtype, std::vector> const& dparam_info) { nrn_prop_param_size_[mechtype] = count_prop_param_size(param_info); nrn_prop_dparam_size_[mechtype] = dparam_info.size(); - delete[] std::exchange(memb_func[mechtype].dparam_semantics, nullptr); - if (!dparam_info.empty()) { - memb_func[mechtype].dparam_semantics = new int[dparam_info.size()]; + if (dparam_info.empty()) { + memb_func[mechtype].dparam_semantics = nullptr; + } else { + memb_func[mechtype].dparam_semantics.reset(new int[dparam_info.size()]); for (auto i = 0; i < dparam_info.size(); ++i) { // dparam_info[i].first is the name of the variable, currently unused... memb_func[mechtype].dparam_semantics[i] = dparam_semantics_to_int( dparam_info[i].second); } } + // Translate param_info into the type we want to use internally now we're fully inside NEURON // library code (wheels...) std::vector param_info_new{}; diff --git a/src/nrnoc/membfunc.h b/src/nrnoc/membfunc.h index ff980c0427..4d637c409e 100644 --- a/src/nrnoc/membfunc.h +++ b/src/nrnoc/membfunc.h @@ -84,7 +84,7 @@ struct Memb_func { int is_point; void* hoc_mech; void (*setdata_)(struct Prop*); - int* dparam_semantics; // for nrncore writing. + std::unique_ptr dparam_semantics; // for nrncore writing. const std::vector* parm_default; // for NrnProperty private: nrn_init_t m_initialize{}; diff --git a/src/nrnoc/memblist.cpp b/src/nrnoc/memblist.cpp index f8fe6677d1..c01093cee4 100644 --- a/src/nrnoc/memblist.cpp +++ b/src/nrnoc/memblist.cpp @@ -1,17 +1,66 @@ #include "neuron/container/generic_data_handle.hpp" #include "neuron/container/mechanism_data.hpp" #include "neuron/model_data.hpp" +#include "nrnassrt.h" #include "nrnoc_ml.h" #include #include // std::distance, std::next #include // std::accumulate +extern void* emalloc(size_t); + + Memb_list::Memb_list(int type) : m_storage{&neuron::model().mechanism_data(type)} { assert(type == m_storage->type()); } +void Memb_list::nodes_alloc(int node_count, bool also_pdata) { + if (node_count == 0) { + return; + } + m_owns_nodes = true; + nodecount = node_count; + nodelist = (Node**) emalloc(node_count * sizeof(Node*)); + nodeindices = (int*) emalloc(node_count * sizeof(int)); + // Prop used by ode_map even when hoc_mech is false + prop = new Prop*[node_count]; + if (also_pdata) { + pdata = (Datum**) emalloc(node_count * sizeof(Datum*)); + } else { + pdata = nullptr; + } +} + +void Memb_list::nodes_free() { + nodecount = 0; + nrn_assert(m_owns_nodes); + free(std::exchange(nodelist, nullptr)); + free(std::exchange(nodeindices, nullptr)); + delete[] std::exchange(prop, nullptr); + free(std::exchange(pdata, nullptr)); + m_owns_nodes = false; // make potentially reusable for a view +} + +Memb_list::Memb_list(Memb_list&& other) noexcept { + /// Other should not be used. But if it is, fine, but not the memory owner anymore + *this = other; + other.m_owns_nodes = false; +} + +Memb_list& Memb_list::operator=(Memb_list&& rhs) noexcept { + *this = rhs; + rhs.m_owns_nodes = false; + return *this; +} + +Memb_list::~Memb_list() noexcept { + if (m_owns_nodes) { + nodes_free(); + } +} + [[nodiscard]] std::vector Memb_list::data() { using Tag = neuron::container::Mechanism::field::FloatingPoint; assert(m_storage); diff --git a/src/nrnoc/multicore.cpp b/src/nrnoc/multicore.cpp index fc1bb40618..9e0a15fcaa 100644 --- a/src/nrnoc/multicore.cpp +++ b/src/nrnoc/multicore.cpp @@ -389,24 +389,23 @@ void nrn_threads_free() { for (tml = nt->tml; tml; tml = tml2) { Memb_list* ml = tml->ml; tml2 = tml->next; - free((char*) ml->nodelist); - free((char*) ml->nodeindices); - delete[] ml->prop; + free((char*) std::exchange(ml->nodelist, nullptr)); + free((char*) std::exchange(ml->nodeindices, nullptr)); + delete[] std::exchange(ml->prop, nullptr); if (!memb_func[tml->index].hoc_mech) { - free((char*) ml->pdata); + free((char*) std::exchange(ml->pdata, nullptr)); } if (ml->_thread) { if (memb_func[tml->index].thread_cleanup_) { (*memb_func[tml->index].thread_cleanup_)(ml->_thread); } - delete[] ml->_thread; + delete[] std::exchange(ml->_thread, nullptr); } - delete ml; - free((char*) tml); + delete std::exchange(ml, nullptr); + free((char*) std::exchange(tml, nullptr)); } if (nt->_ml_list) { - free((char*) nt->_ml_list); - nt->_ml_list = NULL; + free((char*) std::exchange(nt->_ml_list, nullptr)); } for (i = 0; i < BEFORE_AFTER_SIZE; ++i) { NrnThreadBAList *tbl, *tbl2; diff --git a/src/nrnoc/nrnoc_ml.h b/src/nrnoc/nrnoc_ml.h index 34e4134745..453bbc8154 100644 --- a/src/nrnoc/nrnoc_ml.h +++ b/src/nrnoc/nrnoc_ml.h @@ -43,7 +43,27 @@ struct Memb_list { * Defined in .cpp to hide neuron::container::Mechanism::storage layout from translated MOD file * code. */ - Memb_list(int type); + explicit Memb_list(int type); + + /** + * @brief Uninitialize, freeing any allocated mem for nodes. + */ + ~Memb_list() noexcept; + + // Move is ok. Copy is restricted + Memb_list(Memb_list&&) noexcept; + Memb_list& operator=(Memb_list&&) noexcept; + + /** + * @brief Allocate memory for node_count nodes. + * @param also_pdata Allocate also pdata Datum's + */ + void nodes_alloc(int node_count, bool also_pdata); + + /** + * @brief Free memory allocated for nodes (with nodes_alloc) + */ + void nodes_free(); Node** nodelist{}; /* nodeindices contains all nodes this extension is responsible for, @@ -229,4 +249,14 @@ struct Memb_list { * permanent...in which case this value should probably not live here. */ std::size_t m_storage_offset{neuron::container::invalid_row}; + + /** + * @brief Whether this memlist owns its nodes memory or whether we are a view + * Has implications on memory management + */ + bool m_owns_nodes{false}; + + // No copying since one may own memory and double free would occur + Memb_list(const Memb_list&) = delete; + Memb_list& operator=(const Memb_list&) = default; // private, used by move ctrs }; diff --git a/src/nrnoc/solve.cpp b/src/nrnoc/solve.cpp index 08b104c1b5..38aa3ebc53 100644 --- a/src/nrnoc/solve.cpp +++ b/src/nrnoc/solve.cpp @@ -501,7 +501,7 @@ void sec_free(hoc_Item* secitem) { prop_free(&(sec->prop)); node_free(sec); if (!sec->parentsec && sec->parentnode) { - delete sec->parentnode; + delete std::exchange(sec->parentnode, nullptr); } #if DIAMLIST if (sec->pt3d) { @@ -582,9 +582,9 @@ Node::~Node() { // this is delete[]...apart from the order? void node_destruct(Node** pnode, int n) { for (int i = n - 1; i >= 0; --i) { - delete pnode[i]; + delete std::exchange(pnode[i], nullptr); } - delete[] pnode; + delete[] std::exchange(pnode, nullptr); } #if KEEP_NSEG_PARM diff --git a/src/nrnoc/treeset.cpp b/src/nrnoc/treeset.cpp index 86e1e6992d..3ef6e3aff6 100644 --- a/src/nrnoc/treeset.cpp +++ b/src/nrnoc/treeset.cpp @@ -1621,45 +1621,29 @@ void v_setup_vectors(void) { nrn_threads_free(); - for (i = 0; i < n_memb_func; ++i) + for (i = 0; i < n_memb_func; ++i) { if (nrn_is_artificial_[i] && memb_func[i].has_initialize()) { if (memb_list[i].nodecount) { - memb_list[i].nodecount = 0; - free(memb_list[i].nodelist); - free(memb_list[i].nodeindices); - delete[] memb_list[i].prop; + memb_list[i].nodes_free(); if (!memb_func[i].hoc_mech) { - // free(memb_list[i]._data); free(memb_list[i].pdata); } } } + } #if 1 /* see finitialize */ - /* and count the artificial cells */ - for (i = 0; i < n_memb_func; ++i) + /* and allocate for the artificial cells */ + for (i = 0; i < n_memb_func; ++i) { if (nrn_is_artificial_[i] && memb_func[i].has_initialize()) { - cTemplate* tmp = nrn_pnt_template_[i]; - memb_list[i].nodecount = tmp->count; + int node_count = nrn_pnt_template_[i]->count; + bool alloc_pdata = !memb_func[i].hoc_mech; + memb_list[i].nodes_alloc(node_count, alloc_pdata); + memb_list[i].nodecount = 0; /* counted again below. TODO: Why? */ } + } #endif - /* allocate it*/ - - for (i = 0; i < n_memb_func; ++i) - if (nrn_is_artificial_[i] && memb_func[i].has_initialize()) { - if (memb_list[i].nodecount) { - memb_list[i].nodelist = (Node**) emalloc(memb_list[i].nodecount * sizeof(Node*)); - memb_list[i].nodeindices = (int*) emalloc(memb_list[i].nodecount * sizeof(int)); - // Prop used by ode_map even when hoc_mech is false - memb_list[i].prop = new Prop*[memb_list[i].nodecount]; - if (!memb_func[i].hoc_mech) { - memb_list[i].pdata = (Datum**) emalloc(memb_list[i].nodecount * sizeof(Datum*)); - } - memb_list[i].nodecount = 0; /* counted again below */ - } - } - #if MULTICORE if (!nrn_user_partition()) { /* does not depend on memb_list */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f5ac59824f..7af63ec56c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -40,6 +40,11 @@ foreach(target ${catch2_targets}) endif() endforeach() +# ============================================================================= +# API test executables +# ============================================================================= +add_subdirectory(api) + # ============================================================================= # Copy necessary hoc files to build directory if they have not been copied yet # ============================================================================= diff --git a/test/api/CMakeLists.txt b/test/api/CMakeLists.txt new file mode 100644 index 0000000000..2ebc7d9105 --- /dev/null +++ b/test/api/CMakeLists.txt @@ -0,0 +1,16 @@ +foreach(api_test_file hh_sim.cpp netcon.cpp sections.c vclamp.cpp) + string(REPLACE "." "_" api_test_name "${api_test_file}") + add_executable(${api_test_name} ${api_test_file}) + cpp_cc_configure_sanitizers(TARGET ${api_test_name}) + target_link_libraries(${api_test_name} ${CMAKE_DL_LIBS}) + target_link_libraries(${api_test_name} nrniv_lib) + target_link_options(${api_test_name} PRIVATE -rdynamic) + if(NRN_ENABLE_CORENEURON) + target_compile_definitions(${api_test_name} PUBLIC CORENEURON_ENABLED) + endif() + + set(test_name "api::${api_test_name}") + add_test(NAME "${test_name}" COMMAND ${api_test_name}) + set_property(TEST "${test_name}" PROPERTY ENVIRONMENT "${NRN_RUN_FROM_BUILD_DIR_ENV}" + "CURRENT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}") +endforeach() diff --git a/test/api/hh_sim.cpp b/test/api/hh_sim.cpp new file mode 100644 index 0000000000..0dd548c637 --- /dev/null +++ b/test/api/hh_sim.cpp @@ -0,0 +1,90 @@ +// NOTE: this assumes neuronapi.h is on your CPLUS_INCLUDE_PATH +#include "neuronapi.h" +#include + +#include +#include +#include +#include +#include +#include + +#include "./test_common.h" + +using std::cout; +using std::endl; +using std::ofstream; + +constexpr std::array EXPECTED_V{ +#ifndef CORENEURON_ENABLED + -0x1.04p+6, + -0x1.b254ad82e20edp+5, + -0x1.24a52af1ab463p+6, +#else + -0x1.04p+6, + -0x1.b0c75635b5bdbp+5, + -0x1.24a84bedb7246p+6, +#endif +}; + +extern "C" void modl_reg(){/* No modl_reg */}; + +int main(void) { + static std::array argv = {"hh_sim", "-nogui", "-nopython", nullptr}; + nrn_init(3, argv.data()); + + // load the stdrun library + char* temp_str = strdup("stdrun.hoc"); + nrn_str_push(&temp_str); + nrn_function_call(nrn_symbol("load_file"), 1); + nrn_double_pop(); + free(temp_str); + + // topology + Section* soma = nrn_section_new("soma"); + nrn_nseg_set(soma, 3); + + // define soma morphology with two 3d points + nrn_section_push(soma); + for (double x: {0, 0, 0, 10}) { + nrn_double_push(x); + } + nrn_function_call(nrn_symbol("pt3dadd"), 4); + nrn_double_pop(); // pt3dadd returns a number + for (double x: {10, 0, 0, 10}) { + nrn_double_push(x); + } + nrn_function_call(nrn_symbol("pt3dadd"), 4); + nrn_double_pop(); // pt3dadd returns a number + + // ion channels + nrn_mechanism_insert(soma, nrn_symbol("hh")); + + // current clamp at soma(0.5) + nrn_double_push(0.5); + Object* iclamp = nrn_object_new(nrn_symbol("IClamp"), 1); + nrn_property_set(iclamp, "amp", 0.3); + nrn_property_set(iclamp, "del", 1); + nrn_property_set(iclamp, "dur", 0.1); + + // setup recording + Object* v = nrn_object_new(nrn_symbol("Vector"), 0); + nrn_rangevar_push(nrn_symbol("v"), soma, 0.5); + nrn_double_push(5.); + nrn_method_call(v, nrn_method_symbol(v, "record"), 2); + nrn_object_unref(nrn_object_pop()); // record returns the vector + + // finitialize(-65) + nrn_double_push(-65); + nrn_function_call(nrn_symbol("finitialize"), 1); + nrn_double_pop(); + + // continuerun(10) + nrn_double_push(10.5); + nrn_function_call(nrn_symbol("continuerun"), 1); + nrn_double_pop(); + + if (!approximate(EXPECTED_V, v)) { + return 1; + } +} diff --git a/test/api/netcon.cpp b/test/api/netcon.cpp new file mode 100644 index 0000000000..87cfd567d5 --- /dev/null +++ b/test/api/netcon.cpp @@ -0,0 +1,103 @@ +// NOTE: this assumes neuronapi.h is on your CPLUS_INCLUDE_PATH +#include +#include +#include +#include +#include +#include "neuronapi.h" +#include "./test_common.h" + +using std::cout; +using std::endl; +using std::ofstream; + +constexpr std::array EXPECTED_V{ +#ifndef CORENEURON_ENABLED + -0x1.04p+6, + -0x1.085a63d029bc3p+6, + -0x1.112a5e95eb67cp+6, + -0x1.1795abaec26c1p+6, + -0x1.0422351f3f9dcp+6, + -0x1.03e5317ac368cp+6, +#else + -0x1.04p+6, + -0x1.085a703d657a7p+6, + -0x1.112d0039e9c38p+6, + -0x1.17974aa201b7bp+6, + -0x1.041fdf57a182bp+6, + -0x1.03e58fad20b92p+6, +#endif +}; + +extern "C" void modl_reg(){/* No modl_reg */}; + +int main(void) { + static std::array argv = {"netcon", "-nogui", "-nopython", nullptr}; + nrn_init(3, argv.data()); + + // load the stdrun library + char* temp_str = strdup("stdrun.hoc"); + nrn_str_push(&temp_str); + nrn_function_call(nrn_symbol("load_file"), 1); + nrn_double_pop(); + free(temp_str); + + // topology + auto soma = nrn_section_new("soma"); + + // ion channels + nrn_mechanism_insert(soma, nrn_symbol("hh")); + + // NetStim + auto ns = nrn_object_new(nrn_symbol("NetStim"), 0); + nrn_property_set(ns, "start", 5); + nrn_property_set(ns, "noise", 1); + nrn_property_set(ns, "interval", 5); + nrn_property_set(ns, "number", 10); + + nrn_double_push(1); + nrn_double_push(2); + nrn_double_push(3); + nrn_method_call(ns, nrn_method_symbol(ns, "noiseFromRandom123"), 3); + + // syn = h.ExpSyn(soma(0.5)) + nrn_double_push(0.5); + auto syn = nrn_object_new(nrn_symbol("ExpSyn"), 1); + nrn_property_set(syn, "tau", 3); // 3 ms timeconstant + nrn_property_set(syn, "e", 0); // 0 mV reversal potential (excitatory synapse) + + // nc = h.NetCon(ns, syn) + nrn_object_push(ns); + nrn_object_push(syn); + auto nc = nrn_object_new(nrn_symbol("NetCon"), 2); + nrn_property_array_set(nc, "weight", 0, 0.5); + nrn_property_set(nc, "delay", 0); + + auto vec = nrn_object_new(nrn_symbol("Vector"), 0); + + // nc.record(vec) + nrn_object_push(vec); + nrn_method_call(nc, nrn_method_symbol(nc, "record"), 1); + // TODO: record probably put something on the stack that should be removed + + // setup recording + Object* v = nrn_object_new(nrn_symbol("Vector"), 0); + nrn_rangevar_push(nrn_symbol("v"), soma, 0.5); + nrn_double_push(20); + nrn_method_call(v, nrn_method_symbol(v, "record"), 2); + nrn_object_unref(nrn_object_pop()); // record returns the vector + + // finitialize(-65) + nrn_double_push(-65); + nrn_function_call(nrn_symbol("finitialize"), 1); + nrn_double_pop(); + + // continuerun(100) + nrn_double_push(100.5); + nrn_function_call(nrn_symbol("continuerun"), 1); + nrn_double_pop(); + + if (!approximate(EXPECTED_V, v)) { + return 1; + } +} diff --git a/test/api/sections.c b/test/api/sections.c new file mode 100644 index 0000000000..1571bbcc6c --- /dev/null +++ b/test/api/sections.c @@ -0,0 +1,51 @@ +/* NOTE: this assumes neuronapi.h is on your CPLUS_INCLUDE_PATH */ +#include "neuronapi.h" +#include +#include +#include +#include + +void modl_reg(){}; + +int main(void) { + static const char* argv[] = {"sections", "-nogui", "-nopython", NULL}; + nrn_init(3, argv); + + // topology + Section* soma = nrn_section_new("soma"); + Section* dend1 = nrn_section_new("dend1"); + Section* dend2 = nrn_section_new("dend2"); + Section* dend3 = nrn_section_new("dend3"); + Section* axon = nrn_section_new("axon"); + nrn_section_connect(dend1, 0, soma, 1); + nrn_section_connect(dend2, 0, dend1, 1); + nrn_section_connect(dend3, 0, dend1, 1); + nrn_section_connect(axon, 0, soma, 0); + nrn_nseg_set(axon, 5); + + // print out the morphology + nrn_function_call(nrn_symbol("topology"), 0); + + /* create a SectionList that is dend1 and its children */ + Object* seclist = nrn_object_new(nrn_symbol("SectionList"), 0); + nrn_section_push(dend1); + nrn_method_call(seclist, nrn_method_symbol(seclist, "subtree"), 0); + nrn_section_pop(); + + /* loop over allsec, print out */ + printf("allsec:\n"); + SectionListIterator* sli = nrn_sectionlist_iterator_new(nrn_allsec()); + while (!nrn_sectionlist_iterator_done(sli)) { + Section* sec = nrn_sectionlist_iterator_next(sli); + printf(" %s\n", nrn_secname(sec)); + } + nrn_sectionlist_iterator_free(sli); + + printf("\ndend1's subtree:\n"); + sli = nrn_sectionlist_iterator_new(nrn_sectionlist_data(seclist)); + while (!nrn_sectionlist_iterator_done(sli)) { + Section* sec = nrn_sectionlist_iterator_next(sli); + printf(" %s\n", nrn_secname(sec)); + } + nrn_sectionlist_iterator_free(sli); +} diff --git a/test/api/test_common.h b/test/api/test_common.h new file mode 100644 index 0000000000..e3ee4bff13 --- /dev/null +++ b/test/api/test_common.h @@ -0,0 +1,29 @@ +#ifdef __cplusplus + +#include +#include +#include +#include + +constexpr double EPSILON = 0x1p-16; // 1>>16 for double + +template +bool approximate(const std::array& reference, Object* v) { + long v_size = nrn_vector_capacity(v); + const double* v_values = nrn_vector_data(v); + if (v_size != reference.size()) { + std::cerr << "Bad array length: " << v_size << "!=" << reference.size() << std::endl; + return false; + } + for (int i = 0; i < v_size; i++) { + // Uncomment to create ref: Diplay EXACT doubles (no conversion to decimal) + // printf("Vec[%d] %a\n", i, v_values[i]); + if (std::fabs(v_values[i] - reference[i]) > EPSILON) { + std::cerr << "DIFF at line " << i << ": " << v_values[i] << "!=" << reference[i] + << std::endl; + return false; + } + } + return true; +} +#endif diff --git a/test/api/vclamp.cpp b/test/api/vclamp.cpp new file mode 100644 index 0000000000..66b9576d67 --- /dev/null +++ b/test/api/vclamp.cpp @@ -0,0 +1,81 @@ +// NOTE: this assumes neuronapi.h is on your CPLUS_INCLUDE_PATH +#include "neuronapi.h" +#include + +#include +#include +#include +#include +#include +#include + +#include "./test_common.h" + +using std::cout; +using std::endl; +using std::ofstream; + +extern "C" void modl_reg(){/* No modl_reg */}; + +constexpr std::array EXPECTED_V{ + -0x1.04p+6, + -0x1.00d7f6756215p-182, + 0x1.3ffe5c93f70cep+3, + 0x1.3ffe5c93f70cep+3, + 0x1.3ffe5c93f70cep+2, + 0x1.3ffe5c93f70cep+2, + 0x1.3ffe5c93f70cep+2, +}; + +int main(void) { + static std::array argv = {"vclamp", "-nogui", "-nopython", nullptr}; + nrn_init(3, argv.data()); + + // load the stdrun library + char* temp_str = strdup("stdrun.hoc"); + nrn_str_push(&temp_str); + nrn_function_call(nrn_symbol("load_file"), 1); + nrn_double_pop(); + free(temp_str); + + // topology + Section* soma = nrn_section_new("soma"); + + // define soma morphology with two 3d points + nrn_section_push(soma); + assert(soma); + assert(nrn_section_length_set); + nrn_section_length_set(soma, 10); + nrn_segment_diam_set(soma, 0.5, 3); + + // voltage clamp at soma(0.5) + nrn_double_push(0.5); + Object* vclamp = nrn_object_new(nrn_symbol("VClamp"), 1); + // 0 mV for 1 ms; 10 mV for the next 2 ms; 5 mV for the next 3 ms + int i = 0; + for (auto& [amp, dur]: std::initializer_list>{{0, 1}, {10, 2}, {5, 3}}) { + nrn_property_array_set(vclamp, "amp", i, amp); + nrn_property_array_set(vclamp, "dur", i, dur); + ++i; + } + // setup recording + Object* v = nrn_object_new(nrn_symbol("Vector"), 0); + nrn_rangevar_push(nrn_symbol("v"), soma, 0.5); + nrn_double_push(1); + nrn_method_call(v, nrn_method_symbol(v, "record"), 2); + nrn_object_unref(nrn_object_pop()); // record returns the vector + + // finitialize(-65) + nrn_double_push(-65); + nrn_function_call(nrn_symbol("finitialize"), 1); + nrn_double_pop(); + + // continuerun(6) + nrn_double_push(6); + nrn_function_call(nrn_symbol("continuerun"), 1); + nrn_double_pop(); + + if (!approximate(EXPECTED_V, v)) { + return 1; + } +}