NEURON
nrncore_utils.cpp
Go to the documentation of this file.
1 #include "nrncore_utils.h"
3 
4 #include "nrnconf.h"
5 #include "nrniv_mf.h"
6 #include <cstdlib>
7 #include "nrndae_c.h"
8 #include "section.h"
9 #include "hocdec.h"
10 #include "nrnsection_mapping.h"
11 #include "vrecitem.h" // for nrnbbcore_vecplay_write
12 #include "parse.hpp"
13 #include <string>
14 #ifdef HAVE_UNISTD_H
15 #include <unistd.h>
16 #endif
17 #include <algorithm>
18 #include <cerrno>
19 #include <filesystem>
20 
21 #include "nrnwrap_dlfcn.h"
22 
23 // RTLD_NODELETE is used with dlopen
24 // if not defined it's safe to define as 0
25 #ifndef RTLD_NODELETE
26 #define RTLD_NODELETE 0
27 #endif
28 
29 extern bool corenrn_direct;
30 extern const char* bbcore_write_version;
31 extern NrnMappingInfo mapinfo;
32 extern void (*nrnthread_v_transfer_)(NrnThread*);
33 extern short* nrn_is_artificial_;
34 
35 
36 // prerequisites for a NEURON model to be transferred to CoreNEURON.
37 void model_ready() {
38  if (!nrndae_list_is_empty()) {
40  "CoreNEURON cannot simulate a model that contains extra LinearMechanism or RxD "
41  "equations",
42  NULL);
43  }
44  if (nrn_threads[0]._ecell_memb_list) {
46  "CoreNEURON cannot simulate a model that contains the extracellular mechanism", NULL);
47  }
48  if (corenrn_direct) {
49  if (cvode_active_) {
50  hoc_execerror("CoreNEURON can only use fixed step method.", NULL);
51  }
52  }
55  "NEURON model internal structures for CoreNEURON are out of date. Make sure call to "
56  "finitialize(...)",
57  NULL);
58  }
59 }
60 
61 /** @brief Count number of unique elements in the array.
62  * there is a copy of the vector but we are primarily
63  * using it for small section list vectors.
64  */
65 int count_distinct(double* data, int len) {
66  if (len == 0)
67  return 0;
68  std::vector<double> v;
69  v.assign(data, data + len);
70  std::sort(v.begin(), v.end());
71  return std::unique(v.begin(), v.end()) - v.begin();
72 }
73 
74 
75 /** @brief Validate electrode offsets: monotonically non-decreasing. */
76 static void validate_electrode_offsets(const std::vector<size_t>& offsets) {
77  size_t prev = 0;
78  for (const auto off: offsets) {
79  if (off < prev) {
80  hoc_execerror("electrode_offsets must be monotonically non-decreasing", nullptr);
81  }
82  prev = off;
83  }
84 }
85 
86 /** @brief For BBP use case, we want to write section-segment
87  * mapping to gid_3.dat file. This information will be
88  * provided through neurodamus HOC interface with following
89  * format:
90  * gid : number of non-empty neurons in the cellgroup
91  * name : name of section list (like soma, axon, apic)
92  * nsec : number of sections
93  * sections : list of sections
94  * segments : list of segments
95  * seg_lfp_factors: list of lfp factors
96  */
98  // gid of a cell
99  int gid = *hoc_getarg(1);
100 
101  // name of section list
102  std::string name = std::string(hoc_gargstr(2));
103 
104  // hoc vectors: sections and segments
105  Vect* sec = vector_arg(3);
106  Vect* seg = vector_arg(4);
107  Vect* lfp = ifarg(5) ? vector_arg(5) : new Vect();
108 
109  // Argument 6: Vector of electrode offsets (CSR-style partial sums, e.g. [0, N]).
110  std::vector<size_t> electrode_offsets;
111  if (ifarg(6)) {
112  if (hoc_is_double_arg(6)) {
114  "nrnbbcore_register_mapping: arg 6 must be a Vector of electrode offsets.",
115  "Version mismatch: update neurodamus or downgrade NEURON.");
116  }
117  Vect* offsets_vec = vector_arg(6);
118  const int n = vector_capacity(offsets_vec);
119  const double* vals = vector_vec(offsets_vec);
120  electrode_offsets.resize(n);
121  std::transform(vals, vals + n, electrode_offsets.begin(), [](double v) {
122  return static_cast<size_t>(v);
123  });
124  validate_electrode_offsets(electrode_offsets);
125  }
126 
127  double* sections = vector_vec(sec);
128  double* segments = vector_vec(seg);
129  double* seg_lfp_factors = vector_vec(lfp);
130 
131  int nsec = vector_capacity(sec);
132  int nseg = vector_capacity(seg);
133  int nlfp = vector_capacity(lfp);
134 
135  if (nsec != nseg) {
136  Printf("Error: Section and Segment mapping vectors should have same size!\n");
137  abort();
138  }
139 
140  // number of unique sections
141  nsec = count_distinct(sections, nsec);
142 
143  SecMapping* smap = new SecMapping(nsec, name);
144  smap->sections.assign(sections, sections + nseg);
145  smap->segments.assign(segments, segments + nseg);
146  smap->seglfp_factors.assign(seg_lfp_factors, seg_lfp_factors + nlfp);
147  smap->electrode_offsets = std::move(electrode_offsets);
148 
149  // store mapping information
150  mapinfo.add_sec_mapping(gid, smap);
151 }
152 
153 // This function is related to legacy_index2pointer in CoreNeuron to determine
154 // which values should be transferred from CoreNeuron. Types correspond to the
155 // value to be transferred based on mech_type enum or non-artificial cell
156 // mechanisms.
157 // Limited to pointers to voltage, nt.node_sav_rhs_storage() (fast_imem value) or
158 // data of non-artificial cell mechanisms.
159 // Input double* and NrnThread. Output type and index.
160 // type == 0 means could not determine index.
162  NrnThread& nt,
163  int& type,
164  int& index) {
165  int nnode = nt.end;
166  type = 0;
168  auto const cache_token = nrn_ensure_model_data_are_sorted();
169  type = voltage;
170  // In the CoreNEURON world this is an offset into the voltage array part
171  // of _data
172  index = dh.current_row() - cache_token.thread_cache(nt.id).node_data_offset;
173  return 0;
174  }
176  auto const cache_token = nrn_ensure_model_data_are_sorted();
177  type = i_membrane_; // signifies an index into i_membrane_ array portion of _data
178  index = dh.current_row() - cache_token.thread_cache(nt.id).node_data_offset;
179  return 0;
180  }
181  auto* const pd = static_cast<double*>(dh);
182  for (NrnThreadMembList* tml = nt.tml; tml; tml = tml->next) {
183  if (nrn_is_artificial_[tml->index]) {
184  continue;
185  }
186  if (auto const maybe_index = tml->ml->legacy_index(pd); maybe_index >= 0) {
187  type = tml->index;
188  index = maybe_index;
189  break;
190  }
191  }
192  return type == 0 ? 1 : 0;
193 }
194 
195 
196 #if defined(HAVE_DLFCN_H)
197 
198 extern char* neuron_home;
199 
200 /** Check if coreneuron is loaded into memory */
201 bool is_coreneuron_loaded() {
202  bool is_loaded = false;
203  // check if corenrn_embedded_run symbol can be found
204  void* handle = dlopen(NULL, RTLD_NOW | RTLD_GLOBAL);
205  if (handle) {
206  void* fn = dlsym(handle, "corenrn_embedded_run");
207  is_loaded = fn == NULL ? false : true;
208  dlclose(handle);
209  }
210  return is_loaded;
211 }
212 
213 
214 /** Open library with given path and return dlopen handle **/
215 void* get_handle_for_lib(std::filesystem::path const& path) {
216  // On windows path.c_str() is wchar_t*
217  void* handle = dlopen(path.string().c_str(), RTLD_NOW | RTLD_GLOBAL | RTLD_NODELETE);
218  if (!handle) {
219  fputs(dlerror(), stderr);
220  fputs("\n", stderr);
221  hoc_execerror("Could not dlopen CoreNEURON mechanism library : ", path.string().c_str());
222  }
223  return handle;
224 }
225 
226 /** Get CoreNEURON mechanism library */
227 void* get_coreneuron_handle() {
228  // if already loaded into memory, directly return handle
229  if (is_coreneuron_loaded()) {
230  return dlopen(NULL, RTLD_NOW | RTLD_GLOBAL);
231  }
232 
233  // record what we tried so we can give a helpful error message
234  std::vector<std::filesystem::path> paths_tried;
235  paths_tried.reserve(3);
236 
237  // env variable get highest preference
238  const char* corenrn_lib = std::getenv("CORENEURONLIB");
239  if (corenrn_lib) {
240  std::filesystem::path const corenrn_lib_path{corenrn_lib};
241  paths_tried.push_back(corenrn_lib_path);
242  if (std::filesystem::exists(corenrn_lib_path)) {
243  return get_handle_for_lib(corenrn_lib_path);
244  }
245  }
246 
247  auto const corenrn_lib_name = std::string{neuron::config::shared_library_prefix}
248  .append("corenrnmech")
249  .append(neuron::config::shared_library_suffix);
250 
251  // first check if coreneuron specific library exist in <arch>
252  // note that we need to get full path especially for OSX
253  {
254  auto const corenrn_lib_path = std::filesystem::current_path() /
255  neuron::config::system_processor / corenrn_lib_name;
256  paths_tried.push_back(corenrn_lib_path);
257  if (std::filesystem::exists(corenrn_lib_path)) {
258  return get_handle_for_lib(corenrn_lib_path);
259  }
260  }
261 
262  // last fallback is minimal library with internal mechanisms
263  // named libcorenrnmech_internal
264  std::filesystem::path corenrn_lib_path{neuron_home};
265  auto const corenrn_internal_lib_name = std::string{neuron::config::shared_library_prefix}
266  .append("corenrnmech_internal")
267  .append(neuron::config::shared_library_suffix);
268 #ifndef MINGW
269  (corenrn_lib_path /= "..") /= "..";
270 #endif
271  (corenrn_lib_path /= "lib") /= corenrn_internal_lib_name;
272  paths_tried.push_back(corenrn_lib_path);
273  if (std::filesystem::exists(corenrn_lib_path)) {
274  return get_handle_for_lib(corenrn_lib_path);
275  }
276  // Nothing worked => error
277  std::ostringstream oss;
278  oss << "Could not find CoreNEURON library, tried:";
279  for (auto const& path: paths_tried) {
280  oss << ' ' << path;
281  }
282  throw std::runtime_error(oss.str());
283 }
284 
285 /** Check if neuron & coreneuron are compatible */
286 void check_coreneuron_compatibility(void* handle) {
287  // get handle to function in coreneuron
288  void* cn_version_sym = dlsym(handle, "corenrn_version");
289  if (!cn_version_sym) {
290  hoc_execerror("Could not get symbol corenrn_version from CoreNEURON", NULL);
291  }
292  // call coreneuron function and get version string
293  const char* cn_bbcore_read_version = (*(const char* (*) ()) cn_version_sym)();
294 
295  // make sure neuron and coreneuron version are same; otherwise throw an error
296  if (strcmp(bbcore_write_version, cn_bbcore_read_version) != 0) {
297  std::stringstream s_path;
298  s_path << bbcore_write_version << " vs " << cn_bbcore_read_version;
299  hoc_execerror("Incompatible NEURON and CoreNEURON versions :", s_path.str().c_str());
300  }
301 }
302 
303 #endif //! HAVE_DLFCN_H
int tree_changed
Definition: cabcode.cpp:51
#define v
Definition: md1redef.h:11
#define sec
Definition: md1redef.h:20
DLFCN_EXPORT void * dlopen(const char *file, int mode)
Definition: dlfcn.c:331
DLFCN_EXPORT char * dlerror(void)
Definition: dlfcn.c:548
DLFCN_EXPORT int dlclose(void *handle)
Definition: dlfcn.c:423
DLFCN_NOINLINE DLFCN_EXPORT void * dlsym(void *handle, const char *name)
Definition: dlfcn.c:447
#define RTLD_NOW
Definition: dlfcn.h:47
#define RTLD_GLOBAL
Definition: dlfcn.h:56
double * hoc_getarg(int narg)
Definition: code.cpp:1641
char * hoc_gargstr(int)
int hoc_is_double_arg(int narg)
Definition: code.cpp:864
IvocVect * vector_arg(int i)
Definition: ivocvect.cpp:265
const char * neuron_home
Definition: hoc_init.cpp:227
IvocVect Vect
Definition: ivocvect.h:134
const char * name
Definition: init.cpp:16
Item * prev(Item *item)
Definition: list.cpp:94
void move(Item *q1, Item *q2, Item *q3)
Definition: list.cpp:200
NrnThread * nrn_threads
Definition: multicore.cpp:56
double * vector_vec(IvocVect *v)
Definition: ivocvect.cpp:19
bool cvode_active_
Definition: netcvode.cpp:36
int v_structure_change
Definition: nrnoc_aux.cpp:20
void hoc_execerror(const char *s1, const char *s2)
Definition: nrnoc_aux.cpp:39
int vector_capacity(IvocVect *v)
Definition: ivocvect.cpp:16
int diam_changed
Definition: nrnoc_aux.cpp:21
handle_interface< non_owning_identifier< storage > > handle
Non-owning handle to a Mechanism instance.
Model & model()
Access the global Model instance.
Definition: model_data.hpp:206
data_handle< T > transform(data_handle< T > handle, Transform type)
Definition: node.cpp:32
neuron::model_sorted_token nrn_ensure_model_data_are_sorted()
Ensure neuron::container::* data are sorted.
Definition: treeset.cpp:2182
@ i_membrane_
@ voltage
static void validate_electrode_offsets(const std::vector< size_t > &offsets)
Validate electrode offsets: monotonically non-decreasing.
short * nrn_is_artificial_
Definition: init.cpp:214
void(* nrnthread_v_transfer_)(NrnThread *)
Definition: fadvance.cpp:139
bool corenrn_direct
#define RTLD_NODELETE
int count_distinct(double *data, int len)
Count number of unique elements in the array.
void nrnbbcore_register_mapping()
For BBP use case, we want to write section-segment mapping to gid_3.dat file.
int nrn_dblpntr2nrncore(neuron::container::data_handle< double > dh, NrnThread &nt, int &type, int &index)
void model_ready()
const char * bbcore_write_version
Definition: nrncore_io.cpp:25
NrnMappingInfo mapinfo
mapping information
int nrndae_list_is_empty()
Definition: nrndae.cpp:13
int const size_t const size_t n
Definition: nrngsl.h:10
int ifarg(int)
Definition: code.cpp:1607
short index
Definition: cabvars.h:11
short type
Definition: cabvars.h:10
static double unique(void *v)
Definition: seclist.cpp:193
#define NULL
Definition: spdefs.h:105
Compartment mapping information for NrnThread.
void add_sec_mapping(int gid, SecMapping *s)
add section mapping information for given gid if cell is not peviously added, create new cell mapping...
Represent main neuron object computed by single thread.
Definition: multicore.h:58
NrnThreadMembList * tml
Definition: multicore.h:62
int id
Definition: multicore.h:66
int end
Definition: multicore.h:65
struct NrnThreadMembList * next
Definition: multicore.h:34
Section to segment mapping.
std::vector< int > segments
list of segments
std::vector< double > seglfp_factors
list of lfp factors associated with each segment
std::vector< size_t > electrode_offsets
Electrode offsets as partial sums (CSR-style).
std::vector< int > sections
list sections associated with each segment
container::Node::storage & node_data()
Access the structure containing the data of all Nodes.
Definition: model_data.hpp:24
Field for fast_imem calculation.
Definition: node.hpp:65
bool refers_to(Container const &container) const
Query whether this generic handle points to a value from the Tag field of the given container.
std::size_t current_row() const
Get the current logical row number.
int Printf(const char *fmt, Args... args)
Definition: logger.hpp:18