NEURON
partrans.cpp
Go to the documentation of this file.
1 #include <../../nrnconf.h>
2 #include "partrans.h" // sgid_t and SetupTransferInfo for CoreNEURON
3 
5 
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <errno.h>
9 #include <InterViews/resource.h>
10 #include <nrnoc2iv.h>
11 #include <nrniv_mf.h>
12 #include <nrnmpi.h>
13 #include <mymath.h>
14 #include <stdint.h>
15 
16 #include <complex>
17 #include <unordered_map> // Replaces NrnHash for MapSgid2Int
18 #include <utility>
19 #include <vector>
20 
21 #if NRNMPI
22 #include "have2want.hpp"
23 #endif
24 
25 #include "utils/formatting.hpp"
26 
27 
28 #if NRNLONGSGID
29 #if NRNMPI
30 static void sgid_alltoallv(Data<sgid_t>& s, Data<sgid_t>& r) {
31  if (nrn_sparse_partrans > 0) {
32  nrnmpi_long_alltoallv_sparse(s.data.data(),
33  s.cnt.data(),
34  s.displ.data(),
35  r.data.data(),
36  r.cnt.data(),
37  r.displ.data());
38  } else {
39  nrnmpi_long_alltoallv(s.data.data(),
40  s.cnt.data(),
41  s.displ.data(),
42  r.data.data(),
43  r.cnt.data(),
44  r.displ.data());
45  }
46 }
47 #endif // NRNMPI
48 #else // not NRNLONGSGID
49 #if NRNMPI
50 static void sgid_alltoallv(Data<sgid_t>& s, Data<sgid_t>& r) {
51  if (nrn_sparse_partrans > 0) {
52  nrnmpi_int_alltoallv_sparse(s.data.data(),
53  s.cnt.data(),
54  s.displ.data(),
55  r.data.data(),
56  r.cnt.data(),
57  r.displ.data());
58  } else {
59  nrnmpi_int_alltoallv(s.data.data(),
60  s.cnt.data(),
61  s.displ.data(),
62  r.data.data(),
63  r.cnt.data(),
64  r.displ.data());
65  }
66 }
67 #endif // NRNMPI
68 #endif // not NRNLONGSGID
69 
70 extern const char* bbcore_write_version;
71 // see lengthy comment in ../nrnoc/fadvance.cpp
72 // nrnmpi_v_transfer requires existence of nrnthread_v_transfer even if there
73 // is only one thread.
74 // Thread 0 does the nrnmpi_v_transfer into incoming_src_buf.
75 // Data destined for targets in thread owned memory
76 // is copied to the proper place by each thread via nrnthread_v_transfer
77 // MPI_Alltoallv is used to transfer interprocessor data.
78 // The basic assumption is that this will be mostly used for gap junctions in which
79 // most often one source voltage goes to one target or at least only a few targets.
80 // Note that the source data for nrnthread_v_transfer is in incoming_src_buf,
81 // source locations owned by thread, and source locations owned by other threads.
82 
83 /*
84 
85 16-5-2014
86 Gap junctions with extracellular require that the voltage source be v + vext.
87 
88 A solution to the v+vext problem is to create a thread specific source
89 value buffer just for extracellular nodes.
90  NODEV(_nd) + _nd->extnode->v[0] .
91  That is, if there is no extracellular the ttd.sv and and poutsrc_
92 pointers stay exactly the same. Whereas, if there is extracellular,
93 those would point into the source value buffer of the correct thread.
94  Of course, it is necessary that the source value buffer be computed
95 prior to either parallel transfer or mpi_transfer. Note that some
96 sources that are needed by another thread may not be needed by mpi and
97 vice versa. For the fixed step method, mpi transfer occurs prior to
98 thread transfer. For global variable step methods (cvode and lvardt do
99 not work with extracellular anyway):
100  1) for multisplit, mpi between ms_part3 and ms_part4
101  2) not multisplit, mpi between transfer_part1 and transfer_part2
102  with thread transfer in ms_part4 and transfer_part2.
103  Therefore it is possible to do the v+vext computation at the beginning
104 of mpi transfer except that mpi_transfer is not called if there is only
105 one process. Also, this would be very cache inefficient for threads
106 since mpi transfer is done only by thread 0.
107 
108 Therefore we need yet another callback.
109  void* nrnthread_vi_compute(NrnThread*)
110  called, if needed, at the end of update(nt) and before mpi transfer in
111 nrn_finitialize.
112 
113 */
114 
115 /*
116 
117 29-9-2016
118 
119 Want to allow a source to be any range variable at a node or in a
120 Point_process. e.g an ion concentration. The v+vext change restricted
121 sources to voltage partly in order to simplify pointer recalculation for
122 cache efficiency. If the source is not voltage, the calling context of
123 pc.source_var must be sufficient to easily store enough info to update
124 the pointer for cache efficiency or changed number of threads. ie. in
125 general, (Node*, mechtype, parray_index). If the variable is a member
126 of a Point_process, it would be simple to pass it as the third arg and
127 store the reference. However, we reject that due to not handling the
128 typical case of concentration. So we impose the limitation that
129 structural changes that destroy the Node or move the pointer to another
130 Node generally require clearing and re-declaring the source, sid, target
131 relations, (nseg change, point process relocation). Which is, in fact,
132 the current limitation on voltage sources. The substantive
133 implementation change is to provide a safe pointer update for when we
134 know the Node*. An extra map<sgid_t ssid, pair<int mechtype, int
135 parray_index> > should do nicely. with voltages NOT being in the map.
136 Also a decent error message can be generated. Finally, it is fairly
137 clear how to make this work with coreneuron by perhaps adding a triple
138 of sid, mechtype, parray_index integer vectors to the <gidgroup>_gap.dat
139 file.
140 
141 */
142 
143 extern void (*nrnthread_v_transfer_)(NrnThread*); // before nonvint and BEFORE INITIAL
144 extern void (*nrnthread_vi_compute_)(NrnThread*);
145 extern void (*nrnmpi_v_transfer_)(); // before nrnthread_v_transfer and after update. Called by
146  // thread 0.
147 extern void (*nrn_mk_transfer_thread_data_)();
148 #if NRNMPI
149 extern double nrnmpi_transfer_wait_;
150 #endif
151 
153  int cnt;
154  std::vector<neuron::container::data_handle<double>> tv; // pointers to the
155  // ParallelContext.target_var
156  std::vector<neuron::container::data_handle<double>> sv; // pointers to the
157  // ParallelContext.source_var (or into
158  // MPI target buffer)
159 };
162 
163 // for the case where we need vi = v + vext as the source voltage
164 struct SourceViBuf {
165  int cnt;
166  std::vector<Node*> nd;
167  std::vector<double> val;
168 };
169 static std::vector<SourceViBuf> source_vi_buf_;
170 typedef std::unordered_map<sgid_t, int> MapSgid2Int;
171 typedef std::vector<Node*> NodePList;
172 #define PPList partrans_PPList
173 typedef std::vector<Point_process*> PPList;
174 typedef std::vector<int> IntList;
175 typedef std::vector<sgid_t> SgidList;
176 
177 static double* insrc_buf_; // Receives the interprocessor data destined for other threads.
178 static double* outsrc_buf_;
179 static std::vector<neuron::container::data_handle<double>> poutsrc_; // prior to mpi copy src value
180  // to proper place in
181  // outsrc_buf_
182 static int* poutsrc_indices_; // for recalc pointers
183 static int insrc_buf_size_;
184 static std::vector<int> insrccnt_;
185 static std::vector<int> insrcdspl_;
186 static int outsrc_buf_size_;
187 static std::vector<int> outsrccnt_;
188 static std::vector<int> outsrcdspl_;
189 static MapSgid2Int sid2insrc_; // received interprocessor sid data is
190 // associated with which insrc_buf index. Created by nrnmpi_setup_transfer
191 // and used by mk_ttd
192 
193 // ordered by calls to nrnmpi_target_var()
194 static std::vector<neuron::container::data_handle<double>> targets_; // list of target variables
195 static SgidList sgid2targets_; // list of target sgid
196 static PPList target_pntlist_; // list of target Point_process
197 
198 // ordered by calls to nrnmpi_source_var()
199 static NodePList visources_; // list of source Node*, (multiples possible)
200 static SgidList sgids_; // source gids
201 static MapSgid2Int sgid2srcindex_; // sgid2srcindex[sgids[i]] == i
202 
203 // source ssid -> (type,parray_index)
204 static std::unordered_map<sgid_t, std::pair<int, neuron::container::field_index>>
206 
207 static int max_targets_;
208 static bool is_setup_;
209 
210 // deleted when setup_transfer called
211 // defined persistently when pargap_jacobi_setup(0) called.
215 
216 static void delete_imped_info() {
219  delete[] imped_current_type_;
220  delete[] imped_current_ml_;
221  }
222 }
223 
224 // pv2node extended to any range variable in the section
225 // This helper searches over all the mechanisms in the node.
226 // If h refers to a RANGE variable inside a mechanism in the Node, store the mechanism type, the
227 // index of the RANGE variable, and the array index into that RANGE variable.
228 static bool non_vsrc_setinfo(sgid_t ssid,
229  Node* nd,
231  for (Prop* p = nd->prop; p; p = p->next) {
232  for (auto i = 0; i < p->param_num_vars(); ++i) {
233  for (auto j = 0; j < p->param_array_dimension(i); ++j) {
234  if (h == p->param_handle(i, j)) {
235  non_vsrc_update_info_[ssid] = {p->_type, {i, j}};
236  return true;
237  }
238  }
239  }
240  }
241  return false;
242 }
243 
245  int type,
247  for (Prop* p = nd->prop; p; p = p->next) {
248  if (type == p->_type) {
249  return p->param_handle(ix);
250  }
251  }
252  hoc_execerror_fmt("partrans update: could not find parameter index ({}, {}) of {}",
253  ix.field,
254  ix.array_index,
255  memb_func[type].sym->name);
256 }
257 
258 // Find the Node associated with the voltage.
259 // Easy if v in the currently accessed section.
260 // Extended to any pointer to range variable in the section.
261 // If not a voltage save pv associated with mechtype, p_array_index
262 // in non_vsrc_update_info_
264  Section* const sec = chk_access();
265  if (auto* const nd = sec->parentnode; nd) {
266  if (v == nd->v_handle() || non_vsrc_setinfo(ssid, nd, v)) {
267  return nd;
268  }
269  }
270  for (int i = 0; i < sec->nnode; ++i) {
271  auto* const nd = sec->pnode[i];
272  if (v == nd->v_handle() || non_vsrc_setinfo(ssid, nd, v)) {
273  return nd;
274  }
275  }
276  hoc_execerr_ext("Pointer to src is not in the currently accessed section %s", secname(sec));
277 }
278 
279 static void thread_transfer(NrnThread* _nt);
281  nrnthread_v_transfer_ = thread_transfer; // otherwise can't check is_setup_
282  is_setup_ = false;
283  // Get the source variable pointer and promote it to a data_handle if
284  // possible (i.e. if it is a Node voltage, for the moment)
285  auto const psv = hoc_hgetarg<double>(1);
286  auto const sgid = []() -> sgid_t {
287  double const x{*hoc_getarg(2)};
288  if (x < 0) {
289  hoc_execerr_ext("source_var sgid must be >= 0: arg 2 is %g\n", x);
290  }
291  return x;
292  }();
293  auto const [_, inserted] = sgid2srcindex_.emplace(sgid, visources_.size());
294  if (!inserted) {
295  hoc_execerr_ext("source var sgid %lld already in use.", (long long) sgid);
296  }
297  visources_.push_back(pv2node(sgid, psv));
298  sgids_.push_back(sgid);
299  // printf("nrnmpi_source_var %p source_val=%g sgid=%ld\n", psv, *psv, (long)sgid);
300 }
301 
303  Point_process* pp{};
304  Object* ob{};
305  int iarg{1};
306  nrnthread_v_transfer_ = thread_transfer; // otherwise can't check is_setup_
307  is_setup_ = false;
308  if (hoc_is_object_arg(iarg)) {
309  ob = *hoc_objgetarg(iarg++);
310  pp = ob2pntproc(ob);
311  }
312  auto ptv = hoc_hgetarg<double>(iarg++);
313  double x = *hoc_getarg(iarg++);
314  if (x < 0) {
315  hoc_execerr_ext("target_var sgid must be >= 0: arg %d is %g\n", iarg - 1, x);
316  }
317  if (pp && !pp->prop->owns(ptv)) {
318  hoc_execerr_ext("Target ref not in %s", hoc_object_name(ob));
319  }
320  auto const sgid = static_cast<sgid_t>(x);
321  targets_.push_back(ptv);
322  target_pntlist_.push_back(pp);
323  sgid2targets_.push_back(sgid);
324  // printf("nrnmpi_target_var %p target_val=%g sgid=%ld\n", ptv, *ptv, (long)sgid);
325 }
326 
327 static void rm_ttd() {
328  if (!transfer_thread_data_) {
329  return;
330  }
331  delete[] std::exchange(transfer_thread_data_, nullptr);
333  nrnthread_v_transfer_ = nullptr;
334 }
335 
336 static void rm_svibuf() {
337  if (source_vi_buf_.empty()) {
338  return;
339  }
340  source_vi_buf_.clear();
341  nrnthread_vi_compute_ = nullptr;
342 }
343 
344 static void thread_vi_compute(NrnThread* _nt);
345 static std::unordered_map<Node*, double*> mk_svibuf() {
346  rm_svibuf();
347  if (visources_.empty()) {
348  return {};
349  }
350  // any use of extracellular?
351  int has_ecell = 0;
352  for (int tid = 0; tid < nrn_nthread; ++tid) {
353  if (nrn_threads[tid]._ecell_memb_list) {
354  has_ecell = 1;
355  break;
356  }
357  }
358  if (!has_ecell) {
359  return {};
360  }
361 
362  source_vi_buf_.resize(nrn_nthread);
363 
364  for (int tid = 0; tid < nrn_nthread; ++tid) {
365  source_vi_buf_[tid].cnt = 0;
366  }
367  // count
368  for (size_t i = 0; i < visources_.size(); ++i) {
369  Node* nd = visources_[i];
370  auto const it = non_vsrc_update_info_.find(sgids_[i]);
371  if (nd->extnode && it == non_vsrc_update_info_.end()) {
372  assert(nd->_nt >= nrn_threads && nd->_nt < (nrn_threads + nrn_nthread));
373  ++source_vi_buf_[nd->_nt->id].cnt;
374  }
375  }
376  // allocate
377  for (int tid = 0; tid < nrn_nthread; ++tid) {
378  SourceViBuf& svib = source_vi_buf_[tid];
379  svib.nd.resize(svib.cnt);
380  svib.val.resize(svib.cnt);
381  svib.cnt = 0; // recount on fill
382  }
383  // fill
384  for (size_t i = 0; i < visources_.size(); ++i) {
385  Node* nd = visources_[i];
386  auto const it = non_vsrc_update_info_.find(sgids_[i]);
387  if (nd->extnode && it == non_vsrc_update_info_.end()) {
388  int tid = nd->_nt->id;
389  SourceViBuf& svib = source_vi_buf_[tid];
390  svib.nd[svib.cnt++] = nd;
391  }
392  }
393  // now the only problem is how to get TransferThreadData and poutsrc_
394  // to point to the proper SourceViBuf given that sgid2srcindex
395  // only gives us the Node* and we dont want to search linearly
396  // (during setup) everytime we we want to associate.
397  // We can do the poutsrc_ now by creating a temporary Node* to
398  // double* map .. The TransferThreadData can be done later
399  // in mk_ttd using the same map and then deleted.
400  std::unordered_map<Node*, double*> ndvi2pd{1000};
401  // TODO can this be handle-ified?
402  for (int tid = 0; tid < nrn_nthread; ++tid) {
403  SourceViBuf& svib = source_vi_buf_[tid];
404  assert(svib.nd.size() == svib.cnt);
405  assert(svib.val.size() == svib.cnt);
406  for (int i = 0; i < svib.cnt; ++i) {
407  Node* nd = svib.nd[i];
408  ndvi2pd[nd] = &svib.val[i]; // pointer to a vector owned by source_vi_buf_
409  }
410  }
411  for (int i = 0; i < outsrc_buf_size_; ++i) {
412  int isrc = poutsrc_indices_[i];
413  Node* nd = visources_[isrc];
414  auto const it = non_vsrc_update_info_.find(sgids_[isrc]);
415  if (nd->extnode && it == non_vsrc_update_info_.end()) {
416  auto const search = ndvi2pd.find(nd);
417  nrn_assert(search != ndvi2pd.end());
418  // olupton 2022-11-28: looks like search->second is always a pointer to a private vector
419  // in source_vi_buf_, so do_not_search makes sense.
421  search->second};
422  }
423  }
425  return ndvi2pd;
426 }
427 
428 static void mk_ttd() {
429  int i, j, tid, n;
430  auto ndvi2pd = mk_svibuf();
431  rm_ttd();
432  if (targets_.empty()) {
433  // some MPI transfer code paths require that all ranks
434  // have a nrn_thread_v_transfer.
435  // As mentioned in http://static.msi.umn.edu/tutorial/scicomp/general/MPI/content3_new.html
436  // "Communications may, or may not, be synchronized,
437  // depending on how the vendor chose to implement them."
438  // In particular the BG/Q (and one other machine) is sychronizing.
439  // (but see: http://www-01.ibm.com/support/docview.wss?uid=isg1IZ58190 )
440  if (nrnmpi_numprocs > 1 && max_targets_) {
442  }
443  return;
444  }
445  n = targets_.size();
446  if (nrn_nthread > 1)
447  for (i = 0; i < n; ++i) {
449  int sgid = sgid2targets_[i];
450  if (!pp) {
452  "Do not know the POINT_PROCESS target for source id %lld\n"
453  "For multiple threads, the target pointer must reference a range variable\n"
454  "of a POINT_PROCESS. Note that even for a single thread, it is\n"
455  "fastest to supply a reference to the POINT_PROCESS as the first arg.",
456  (long long) sgid);
457  }
458  }
460  for (tid = 0; tid < nrn_nthread; ++tid) {
461  transfer_thread_data_[tid].cnt = 0;
462  }
464  // how many targets in each thread
465  if (nrn_nthread == 1) {
467  } else {
468  for (i = 0; i < n; ++i) {
470  tid = ((NrnThread*) target_pntlist_[i]->_vnt)->id;
471  ++transfer_thread_data_[tid].cnt;
472  }
473  }
474  // allocate
475  for (tid = 0; tid < nrn_nthread; ++tid) {
477  if (ttd.cnt) {
478  ttd.tv.resize(ttd.cnt);
479  ttd.sv.resize(ttd.cnt);
480  }
481  ttd.cnt = 0;
482  }
483  // count again and fill pointers
484  for (i = 0; i < n; ++i) {
485  if (nrn_nthread == 1) {
486  tid = 0;
487  } else {
488  tid = ((NrnThread*) target_pntlist_[i]->_vnt)->id;
489  }
491  j = ttd.cnt++;
492  ttd.tv.at(j) = targets_.at(i);
493  // perhaps inter- or intra-thread, perhaps interprocessor
494  // if inter- or intra-thread, perhaps SourceViBuf
495  sgid_t sid = sgid2targets_[i];
496  // cannot figure out how to get iterator and test within if
497  bool err = true;
498  auto search = sgid2srcindex_.find(sid);
499  if (search != sgid2srcindex_.end()) {
500  err = false;
501  Node* nd = visources_[search->second];
502  auto it = non_vsrc_update_info_.find(sid);
503  if (it != non_vsrc_update_info_.end()) {
504  ttd.sv[j] = non_vsrc_update(nd, it->second.first, it->second.second);
505  } else if (nd->extnode) {
506  auto search = ndvi2pd.find(nd);
507  nrn_assert(search != ndvi2pd.end());
508  // olupton 2022-11-28: looks like search->second is always a pointer to a private
509  // vector in source_vi_buf_, so do_not_search makes sense.
511  search->second};
512  } else {
513  ttd.sv[j] = nd->v_handle();
514  }
515  } else {
516  auto search = sid2insrc_.find(sid);
517  if (search != sid2insrc_.end()) {
518  err = false;
519  // olupton 2022-11-28: insrc_buf_ is not part of the global model data structure, so
520  // do_not_search is appropriate
522  insrc_buf_ + search->second};
523  }
524  }
525  if (err == true) {
526  hoc_execerr_ext("No source_var for target_var sid = %lld\n", (long long) sid);
527  }
528  }
530 }
531 
532 static void thread_vi_compute(NrnThread* _nt) {
533  // vi+vext needed by either mpi or thread transfer copied into
534  // the source value buffer for this thread. Note that relevant
535  // poutsrc_ and ttd[_nt->id].sv items
536  // point into this source value buffer
537  if (source_vi_buf_.empty()) {
538  return;
539  }
540  SourceViBuf& svb = source_vi_buf_[_nt->id];
541  for (int i = 0; i < svb.cnt; ++i) {
542  Node* nd = svb.nd[i];
543  assert(nd->extnode);
544  svb.val[i] = NODEV(nd) + nd->extnode->v[0];
545  }
546 }
547 
548 static void mpi_transfer() {
549  int i, n = outsrc_buf_size_;
550  for (i = 0; i < n; ++i) {
551  outsrc_buf_[i] = *poutsrc_[i];
552  }
553 #if NRNMPI
554  if (nrnmpi_numprocs > 1) {
555  double wt = nrnmpi_wtime();
556  if (nrn_sparse_partrans > 0) {
557  nrnmpi_dbl_alltoallv_sparse(outsrc_buf_,
558  outsrccnt_.data(),
559  outsrcdspl_.data(),
560  insrc_buf_,
561  insrccnt_.data(),
562  insrcdspl_.data());
563  } else {
565  outsrccnt_.data(),
566  outsrcdspl_.data(),
567  insrc_buf_,
568  insrccnt_.data(),
569  insrcdspl_.data());
570  }
571  nrnmpi_transfer_wait_ += nrnmpi_wtime() - wt;
572  errno = 0;
573  }
574 #endif
575  // insrc_buf_ will get transferred to targets by thread_transfer
576 }
577 
578 static void thread_transfer(NrnThread* _nt) {
579  if (!is_setup_) {
580  hoc_execerror("ParallelContext.setup_transfer()", "needs to be called.");
581  }
582  if (targets_.empty()) {
583  return;
584  }
585  // an edited old comment prior to allowing simultaneous threads and mpi.
586  // for threads we do direct transfers under the assumption
587  // that v is being transferred and they were set in a
588  // previous multithread job. For the fixed step method this
589  // call is from nonvint which in the same thread job as update
590  // and that is the case even with multisplit. So we really
591  // need to break the job between update and nonvint. Too bad.
592  // For global cvode, things are ok except if the source voltage
593  // is at a zero area node since nocap_v_part2 is a part
594  // of this job and in fact the v does not get updated til
595  // the next job in nocap_v_part3. Again, too bad. But it is
596  // quite ambiguous, stability wise,
597  // to have a gap junction in a zero area node, anyway, since
598  // the system is then truly a DAE.
599  // For now we presume we have dealt with these matters and
600  // do the transfer.
603  for (int i = 0; i < ttd.cnt; ++i) {
604  *(ttd.tv[i]) = *(ttd.sv[i]);
605  }
606 }
607 
608 // The simplest conceivable transfer is to use MPI_Allgatherv and send
609 // all sources to all machines. More complicated and possibly more efficient
610 // in terms of total received buffer size
611 // would be to use MPI_Alltoallv in which distinct data is sent and received.
612 // Most transfer are one to one, at most one to a few, so now we use alltoallv.
613 // The old comment read: "
614 // We begin with MPI_Allgatherv. We try
615 // to save a trivial copy by making
616 // outgoing_source_buf a pointer into the incoming_source_buf.
617 // " But this was a mistake as many mpi implementations do not allow overlap
618 // of send and receive buffers.
619 
621 #if !NRNMPI
622  if (nrnmpi_numprocs > 1) {
624  "To use ParallelContext.setup_transfer when nhost > 1, NEURON must be configured with "
625  "--with-paranrn",
626  0);
627  }
628 #endif
629  int nhost = nrnmpi_numprocs;
630  is_setup_ = true;
632  delete[] std::exchange(insrc_buf_, nullptr);
633  delete[] std::exchange(outsrc_buf_, nullptr);
634  outsrc_buf_size_ = 0;
635  sid2insrc_.clear();
636  poutsrc_.clear();
637  delete[] std::exchange(poutsrc_indices_, nullptr);
638 #if NRNMPI
639  // if there are no targets anywhere, we do not need to do anything
641  if (max_targets_ == 0) {
642  return;
643  }
644  if (nrnmpi_numprocs > 1) {
645  insrccnt_.clear();
646  insrccnt_.shrink_to_fit();
647  insrcdspl_.clear();
648  insrcdspl_.shrink_to_fit();
649  outsrccnt_.clear();
650  outsrccnt_.shrink_to_fit();
651  outsrcdspl_.clear();
652  outsrcdspl_.shrink_to_fit();
653  // This is an old comment prior to using the want_to_have rendezvous
654  // rank function in want2have.cpp. The old method did not scale
655  // to more sgids than could fit on a single rank, because
656  // each rank sent its "need" list to every rank.
657  // <old comment>
658  // This machine needs to send which sources to which other machines.
659  // It does not need to send to itself.
660  // Which targets have sources on other machines.(none if nrnmpi_numprocs=1)
661  // 1) list sources needed that are on other machines.
662  // 2) send that info to all machines.
663  // 3) source machine can figure out which machines want its sids
664  // and therefore construct outsrc_buf, etc.
665  // 4) Notify target machines which sids the source machine will send
666  // 5) The target machines can figure out where the sids are coming from
667  // and therefore construct insrc_buf, etc.
668  // <new comment>
669  // 1) List sources needed by this rank and sources that this rank owns.
670  // 2) Call the have_to_want function. Returns two sets of three
671  // vectors. The first set of three vectors is an sgid buffer,
672  // along with counts and displacements. The sgids in the ith region
673  // of the buffer are the sgids from this rank that are
674  // wanted by the ith rank. For the second set, the sgids in the ith
675  // region are the sgids on the ith rank that are wanted by this rank.
676  // 3) First return triple creates the proper outsrc_buf_.
677  // 4) The second triple is creates the insrc_buf_.
678 
679  // 1)
680  // It will often be the case that multiple targets will need the
681  // same source. We count the needed sids only once regardless of
682  // how often they are used.
683  // At the end of this section, needsrc is an array of needsrc_cnt
684  // sids needed by this machine. The 'seen' table values are unused
685  // but the keys are all the (unique) sgid needed by this process.
686  // At the end seen is in fact what we want for sid2insrc_.
687  int szalloc = targets_.size();
688  szalloc = szalloc ? szalloc : 1;
689 
690  // At the moment sid2insrc_ is serving as 'seen'
691  sid2insrc_.clear();
692  sid2insrc_.reserve(szalloc); // for single counting
693  std::vector<sgid_t> needsrc{};
694  for (size_t i = 0; i < sgid2targets_.size(); ++i) {
695  sgid_t sid = sgid2targets_[i];
696  auto search = sid2insrc_.find(sid);
697  if (search == sid2insrc_.end()) {
698  sid2insrc_[sid] = 0; // at the moment, value does not matter
699  needsrc.push_back(sid);
700  }
701  }
702 
703  // 1 continued) Create an array of sources this rank owns.
704  // This already exists as a vector in the SgidList sgids_ but
705  // that is private so go ahead and copy.
706  std::vector<sgid_t> ownsrc = sgids_;
707 
708  // 2) Call the have_to_want function.
709  auto [send_to_want, recv_from_have] = have_to_want<sgid_t>(ownsrc, needsrc, sgid_alltoallv);
710 
711  // sanity check. all the sgids we are asked to send, we actually have
712  int n = send_to_want.displ[nhost];
713  // sanity check. all the sgids we receive, we actually need.
714  // also set the sid2insrc_ value to the proper recv_from_have index.
715  n = recv_from_have.displ[nhost];
716  for (int i = 0; i < n; ++i) {
717  sgid_t sgid = recv_from_have.data[i];
718  nrn_assert(sid2insrc_.find(sgid) != sid2insrc_.end());
719  sid2insrc_[sgid] = i;
720  }
721 
722  // 3) First return triple creates the proper outsrc_buf_.
723  // Now that we know what machines are interested in our sids...
724  // construct outsrc_buf, outsrc_buf_size, outsrccnt_, outsrcdspl_
725  // and poutsrc_;
726  std::swap(outsrccnt_, send_to_want.cnt);
727  std::swap(outsrcdspl_, send_to_want.displ);
729  szalloc = std::max(1, outsrc_buf_size_);
730  outsrc_buf_ = new double[szalloc];
731  poutsrc_.resize(szalloc);
732  poutsrc_indices_ = new int[szalloc];
733  for (int i = 0; i < outsrc_buf_size_; ++i) {
734  sgid_t sid = send_to_want.data[i];
735  auto search = sgid2srcindex_.find(sid);
736  nrn_assert(search != sgid2srcindex_.end());
737  Node* nd = visources_[search->second];
738  auto const it = non_vsrc_update_info_.find(sid);
739  if (it != non_vsrc_update_info_.end()) {
740  poutsrc_[i] = non_vsrc_update(nd, it->second.first, it->second.second);
741  } else if (!nd->extnode) {
742  poutsrc_[i] = nd->v_handle();
743  } else {
744  // the v+vext case can only be done after mk_svib()
745  }
746  poutsrc_indices_[i] = search->second;
747  outsrc_buf_[i] = double(sid); // see step 5
748  }
749 
750  // 4) The second triple is creates the insrc_buf_.
751  // From the recv_from_have and sid2insrc_ table, construct the insrc...
752  std::swap(insrccnt_, recv_from_have.cnt);
753  std::swap(insrcdspl_, recv_from_have.displ);
755  szalloc = insrc_buf_size_ ? insrc_buf_size_ : 1;
756  insrc_buf_ = new double[szalloc];
757  // from sid2insrc_, mk_ttd can construct the right pointer to the source.
758 
760  }
761 #endif // NRNMPI
763  if (!v_structure_change) {
764  mk_ttd();
765  }
766 }
767 
769  nrnthread_v_transfer_ = nullptr;
770  nrnthread_vi_compute_ = nullptr;
771  nrnmpi_v_transfer_ = nullptr;
772  sgid2srcindex_.clear();
773  sgids_.resize(0);
774  visources_.resize(0);
775  sgid2targets_.resize(0);
776  target_pntlist_.resize(0);
777  targets_.clear();
778  max_targets_ = 0;
779  rm_svibuf();
780  rm_ttd();
781  delete[] std::exchange(insrc_buf_, nullptr);
782  delete[] std::exchange(outsrc_buf_, nullptr);
783  outsrc_buf_size_ = 0;
784  sid2insrc_.clear();
785  poutsrc_.clear();
786  delete[] std::exchange(poutsrc_indices_, nullptr);
787  non_vsrc_update_info_.clear();
789 }
790 
791 // assume one thread and no extracellular
792 
793 static double *vgap1, *vgap2;
794 static int imped_change_cnt;
795 
796 void pargap_jacobi_setup(int mode) {
797  if (!nrnthread_v_transfer_) {
798  return;
799  }
800 
801  // list of gap junction types and memb_list for each
802  if (mode == 0) {
806  }
807  if (imped_current_type_count_ == 0 && !targets_.empty()) {
808  for (size_t i = 0; i < targets_.size(); ++i) {
810  if (!pp) {
812  "For impedance, pc.target_var requires that its first arg be a reference "
813  "to the POINT_PROCESS",
814  0);
815  }
816  int type = pp->prop->_type;
817  if (imped_current_type_count_ == 0) {
819  imped_current_type_ = new int[5];
820  imped_current_ml_ = new Memb_list*[5];
822  }
823  int add = 1;
824  for (int k = 0; k < imped_current_type_count_; ++k) {
825  if (type == imped_current_type_[k]) {
826  add = 0;
827  break;
828  }
829  }
830  if (add) {
834  }
835  }
836  NrnThread* nt = nrn_threads;
837  for (int k = 0; k < imped_current_type_count_; ++k) {
838  for (NrnThreadMembList* tml = nt->tml; tml; tml = tml->next) {
839  if (imped_current_type_[k] == tml->index) {
840  imped_current_ml_[k] = tml->ml;
841  }
842  }
843  }
844  // are all the instances in use
845  size_t ninst = 0;
846  for (int k = 0; k < imped_current_type_count_; ++k) {
847  ninst += imped_current_ml_[k]->nodecount;
848  }
849  if (ninst != targets_.size()) {
851  "number of gap junctions, %zd, not equal to number of pc.transfer_var, %zd",
852  ninst,
853  targets_.size());
854  }
855  }
856  }
858  if (mode == 0) { // setup
859  if (visources_.size()) {
860  vgap1 = new double[visources_.size()];
861  }
862  if (ttd && ttd->cnt) {
863  vgap2 = new double[ttd->cnt];
864  }
865  for (size_t i = 0; i < visources_.size(); ++i) {
866  vgap1[i] = NODEV(visources_[i]);
867  }
868  if (ttd)
869  for (int i = 0; i < ttd->cnt; ++i) {
870  vgap2[i] = *(ttd->tv[i]);
871  }
872  } else { // tear down
873  for (size_t i = 0; i < visources_.size(); ++i) {
874  visources_[i]->v() = vgap1[i];
875  }
876  if (ttd) {
877  for (int i = 0; i < ttd->cnt; ++i) {
878  *(ttd->tv[i]) = vgap2[i];
879  }
880  }
881  delete[] std::exchange(vgap1, nullptr);
882  delete[] std::exchange(vgap2, nullptr);
883  }
884 }
885 
886 void pargap_jacobi_rhs(std::vector<std::complex<double>>& b,
887  const std::vector<std::complex<double>>& x) {
888  // First loop for real, second for imag
889  for (int real_imag = 0; real_imag < 2; ++real_imag) {
890  // helper for complex impedance with parallel gap junctions
891  // b = b - R*x R are the off diagonal gap elements of the jacobian.
892  // we presume 1 thread. First nrn_thread[0].end equations are in node order.
893  if (!nrnthread_v_transfer_) {
894  return;
895  }
896 
897  NrnThread* _nt = nrn_threads;
898 
899  // transfer gap node voltages to gap vpre
900  for (size_t i = 0; i < visources_.size(); ++i) {
901  Node* nd = visources_[i];
902  if (real_imag == 0) {
903  nd->v() = x[nd->v_node_index].real();
904  } else {
905  nd->v() = x[nd->v_node_index].imag();
906  }
907  }
908  mpi_transfer();
909  thread_transfer(_nt);
910 
911  // set gap node voltages to 0 so we can use nrn_cur to set rhs
912  for (size_t i = 0; i < visources_.size(); ++i) {
913  Node* nd = visources_[i];
914  nd->v() = 0.0;
915  }
916  auto const sorted_token = nrn_ensure_model_data_are_sorted();
917  auto* const vec_rhs = _nt->node_rhs_storage();
918  // Initialize rhs to 0.
919  for (int i = 0; i < _nt->end; ++i) {
920  vec_rhs[i] = 0.0;
921  }
922  for (int k = 0; k < imped_current_type_count_; ++k) {
923  int type = imped_current_type_[k];
925  memb_func[type].current(sorted_token, _nt, ml, type);
926  }
927 
928  // possibly many gap junctions in same node (and possible even different
929  // types) but rhs is the accumulation of all those instances at each node
930  // so ... The only thing that can go wrong is if there are intances of
931  // gap junctions that are not being used (not in the target list).
932  for (int i = 0; i < _nt->end; ++i) {
933  if (real_imag == 0) {
934  b[i] += vec_rhs[i];
935  } else {
936  b[i] += std::complex<double>(0, vec_rhs[i]);
937  }
938  }
939  }
940 }
941 
942 extern size_t nrnbbcore_gap_write(const char* path, int* group_ids);
943 
944 /*
945  file format for <path>/<group_id>_gap.dat
946  All gap info for thread. Extracellular not allowed
947  ntar // number of targets in this thread (vpre)
948  nsrc // number of sources in this thread (v)
949 
950  Note: type, index is sufficient for CoreNEURON legacy_index2pointer to determine
951  double* in its NrnThread.data array.
952 
953  src_sid // nsrc of these
954  src_type // nsrc mechanism type containing source variable, -1 is voltage.
955  src_index // range variable index relative to beginning of first instance.
956 
957  tar_sid // ntar of these
958  tar_type // ntar mechanism type containing target variable.
959  tar_index // range variable index relative to beginning of first instance.
960 
961  Assert no extracellular.
962 
963 */
964 
965 /*
966  The original file creation for each thread was accomplished by
967  a serial function that:
968  Verified the assertion constraints.
969  Created an nthread array of BBCoreGapInfo.
970  Wrote the <gid>_gap.dat files forall the threads.
971  Cleaned up the BBCoreGapInfo (and gap_ml).
972 
973  So a simple factoring of the verify and create portions suffices
974  for both files and direct memory transfer. Note that direct call
975  returns pointer to SetupTransferInfo array.
976  To cleanup, CoreNEURON should delete [] the return pointer.
977 */
978 
980 
981 SetupTransferInfo* nrn_get_partrans_setup_info(int ngroup, int cn_nthread, size_t cn_sidt_sz) {
982  assert(cn_sidt_sz == sizeof(sgid_t));
983  assert(ngroup == nrn_nthread);
984  return nrncore_transfer_info(cn_nthread);
985 }
986 
987 size_t nrnbbcore_gap_write(const char* path, int* group_ids) {
988  auto gi = nrncore_transfer_info(nrn_nthread); // gi stood for gapinfo
989  if (gi == nullptr) {
990  return 0;
991  }
992 
993  // print the files
994  for (int tid = 0; tid < nrn_nthread; ++tid) {
995  auto& g = gi[tid];
996 
997  if (g.src_sid.empty() && g.tar_sid.empty()) { // no file
998  continue;
999  }
1000 
1001  char fname[1000];
1002  Sprintf(fname, "%s/%d_gap.dat", path, group_ids[tid]);
1003  FILE* f = fopen(fname, "wb");
1004  assert(f);
1005  fprintf(f, "%s\n", bbcore_write_version);
1006  fprintf(f, "%d sizeof_sid_t\n", int(sizeof(sgid_t)));
1007 
1008  int ntar = int(g.tar_sid.size());
1009  int nsrc = int(g.src_sid.size());
1010  fprintf(f, "%d ntar\n", ntar);
1011  fprintf(f, "%d nsrc\n", nsrc);
1012 
1013  int chkpnt = 0;
1014 #define CHKPNT fprintf(f, "chkpnt %d\n", chkpnt++);
1015 
1016  if (!g.src_sid.empty()) {
1017  CHKPNT fwrite(g.src_sid.data(), nsrc, sizeof(sgid_t), f);
1018  CHKPNT fwrite(g.src_type.data(), nsrc, sizeof(int), f);
1019  CHKPNT fwrite(g.src_index.data(), nsrc, sizeof(int), f);
1020  }
1021 
1022  if (!g.tar_sid.empty()) {
1023  CHKPNT fwrite(g.tar_sid.data(), ntar, sizeof(sgid_t), f);
1024  CHKPNT fwrite(g.tar_type.data(), ntar, sizeof(int), f);
1025  CHKPNT fwrite(g.tar_index.data(), ntar, sizeof(int), f);
1026  }
1027 
1028  fclose(f);
1029  }
1030 
1031  // cleanup
1032  delete[] gi;
1033  return 0;
1034 }
1035 
1036 static SetupTransferInfo* nrncore_transfer_info(int cn_nthread) {
1037  assert(target_pntlist_.size() == targets_.size());
1038 
1039  // space for the info
1040  auto gi = new SetupTransferInfo[cn_nthread];
1041 
1042  // info for targets, segregate into threads
1043  if (targets_.size()) {
1044  for (size_t i = 0; i < targets_.size(); ++i) {
1045  sgid_t sid = sgid2targets_[i];
1047  NrnThread* nt = (NrnThread*) pp->_vnt;
1048  int tid = nt ? nt->id : 0;
1049  int type = pp->prop->_type;
1050  Memb_list& ml = *(nrn_threads[tid]._ml_list[type]);
1051  int ix = ml.legacy_index(targets_[i]);
1052  assert(ix >= 0);
1053 
1054  auto& g = gi[tid];
1055  g.tar_sid.push_back(sid);
1056  g.tar_type.push_back(type);
1057  g.tar_index.push_back(ix);
1058  }
1059  }
1060 
1061  // info for sources, segregate into threads.
1062  if (visources_.size()) {
1063  for (size_t i = 0; i < sgids_.size(); ++i) {
1064  sgid_t sid = sgids_[i];
1065  Node* nd = visources_[i];
1066  int tid = nd->_nt ? nd->_nt->id : 0;
1067  int type = -1; // default voltage
1068  int ix = 0; // fill below
1069  auto const it = non_vsrc_update_info_.find(sid);
1070  if (it != non_vsrc_update_info_.end()) { // not a voltage source
1071  type = it->second.first;
1072  // this entire context needs to be reworked. If the source is a
1073  // point process, then if more than one in this nd, it is an error.
1074  auto d = non_vsrc_update(nd, type, it->second.second);
1075  NrnThread* nt = nd->_nt ? nd->_nt : nrn_threads;
1076  Memb_list& ml = *nt->_ml_list[type];
1077  ix = ml.legacy_index(d);
1078  assert(ix >= 0);
1079  } else { // is a voltage source
1080  // Calculate the offset of the Node voltage in the section of
1081  // the underlying storage vector that is dedicated to NrnThread
1082  // number `tid`. Warning: this is only correct if no
1083  // modifications have been made to any Node since
1084  // reorder_secorder() was last called.
1085  auto const cache_token = nrn_ensure_model_data_are_sorted();
1086  ix = nd->_node_handle.current_row() -
1087  cache_token.thread_cache(tid).node_data_offset;
1088  assert(nd->extnode == NULL); // only if v
1089  assert(ix >= 0 && ix < nrn_threads[tid].end);
1090  }
1091 
1092  auto& g = gi[tid];
1093  g.src_sid.push_back(sid);
1094  g.src_type.push_back(type);
1095  g.src_index.push_back(ix);
1096  }
1097  }
1098  return gi;
1099 }
static void nrnmpi_dbl_alltoallv(const double *s, const int *scnt, const int *sdispl, double *r, int *rcnt, int *rdispl)
static void nrnmpi_int_alltoallv(const int *s, const int *scnt, const int *sdispl, int *r, int *rcnt, int *rdispl)
static int nrnmpi_int_allmax(int x)
Section * chk_access()
Definition: cabcode.cpp:449
const char * secname(Section *sec)
name of section (for use in error messages)
Definition: cabcode.cpp:1674
#define v
Definition: md1redef.h:11
#define sec
Definition: md1redef.h:20
#define i
Definition: md1redef.h:19
void hoc_execerror_fmt(const char *fmt, T &&... args)
Definition: formatting.hpp:8
static RNG::key_type k
Definition: nrnran123.cpp:9
int hoc_is_object_arg(int narg)
Definition: code.cpp:876
void hoc_execerr_ext(const char *fmt,...)
printf style specification of hoc_execerror message.
Definition: fileio.cpp:828
double * hoc_getarg(int narg)
Definition: code.cpp:1641
char * hoc_object_name(Object *ob)
Definition: hoc_oop.cpp:73
#define assert(ex)
Definition: hocassrt.h:24
Point_process * ob2pntproc(Object *ob)
Definition: hocmech.cpp:99
Object ** hoc_objgetarg(int)
Definition: code.cpp:1614
static double nrnmpi_wtime()
Definition: multisplit.cpp:48
NrnThread * nrn_threads
Definition: multicore.cpp:56
int nrn_nthread
Definition: multicore.cpp:55
int v_structure_change
Definition: nrnoc_aux.cpp:20
void hoc_execerror(const char *s1, const char *s2)
Definition: nrnoc_aux.cpp:39
int structure_change_cnt
constexpr do_not_search_t do_not_search
Definition: data_handle.hpp:11
int Sprintf(char(&buf)[N], const char *fmt, Args &&... args)
Redirect sprintf to snprintf if the buffer size can be deduced.
Definition: wrap_sprintf.h:14
auto *const vec_rhs
Definition: cellorder.cpp:616
neuron::model_sorted_token nrn_ensure_model_data_are_sorted()
Ensure neuron::container::* data are sorted.
Definition: treeset.cpp:2182
#define nrn_assert(x)
assert()-like macro, independent of NDEBUG status
Definition: nrn_assert.h:33
int chkpnt
Definition: nrncore_io.cpp:24
int const size_t const size_t n
Definition: nrngsl.h:10
size_t p
size_t j
s
Definition: multisend.cpp:521
void nrn_partrans_clear()
Definition: partrans.cpp:768
static Memb_list ** imped_current_ml_
Definition: partrans.cpp:214
static SgidList sgids_
Definition: partrans.cpp:200
static MapSgid2Int sgid2srcindex_
Definition: partrans.cpp:201
static std::vector< neuron::container::data_handle< double > > targets_
Definition: partrans.cpp:194
static std::unordered_map< sgid_t, std::pair< int, neuron::container::field_index > > non_vsrc_update_info_
Definition: partrans.cpp:205
static bool is_setup_
Definition: partrans.cpp:208
static double * vgap1
Definition: partrans.cpp:793
size_t nrnbbcore_gap_write(const char *path, int *group_ids)
Definition: partrans.cpp:987
void(* nrnthread_v_transfer_)(NrnThread *)
Definition: fadvance.cpp:139
static Node * pv2node(sgid_t ssid, neuron::container::data_handle< double > const &v)
Definition: partrans.cpp:263
void pargap_jacobi_setup(int mode)
Definition: partrans.cpp:796
static std::vector< int > outsrccnt_
Definition: partrans.cpp:187
static bool non_vsrc_setinfo(sgid_t ssid, Node *nd, neuron::container::data_handle< double > const &h)
Definition: partrans.cpp:228
static int n_transfer_thread_data_
Definition: partrans.cpp:161
static std::unordered_map< Node *, double * > mk_svibuf()
Definition: partrans.cpp:345
static int * imped_current_type_
Definition: partrans.cpp:213
static std::vector< int > insrcdspl_
Definition: partrans.cpp:185
static void rm_svibuf()
Definition: partrans.cpp:336
static int imped_current_type_count_
Definition: partrans.cpp:212
static NodePList visources_
Definition: partrans.cpp:199
static void rm_ttd()
Definition: partrans.cpp:327
static std::vector< neuron::container::data_handle< double > > poutsrc_
Definition: partrans.cpp:179
static void mk_ttd()
Definition: partrans.cpp:428
static SgidList sgid2targets_
Definition: partrans.cpp:195
static void delete_imped_info()
Definition: partrans.cpp:216
static SetupTransferInfo * nrncore_transfer_info(int)
Definition: partrans.cpp:1036
std::vector< Node * > NodePList
Definition: partrans.cpp:171
std::unordered_map< sgid_t, int > MapSgid2Int
Definition: partrans.cpp:170
static void mpi_transfer()
Definition: partrans.cpp:548
std::vector< sgid_t > SgidList
Definition: partrans.cpp:175
static TransferThreadData * transfer_thread_data_
Definition: partrans.cpp:160
static std::vector< int > insrccnt_
Definition: partrans.cpp:184
#define CHKPNT
static PPList target_pntlist_
Definition: partrans.cpp:196
static int outsrc_buf_size_
Definition: partrans.cpp:186
static double * vgap2
Definition: partrans.cpp:793
std::vector< int > IntList
Definition: partrans.cpp:174
static int * poutsrc_indices_
Definition: partrans.cpp:182
static std::vector< int > outsrcdspl_
Definition: partrans.cpp:188
void pargap_jacobi_rhs(std::vector< std::complex< double >> &b, const std::vector< std::complex< double >> &x)
Definition: partrans.cpp:886
void(* nrnthread_vi_compute_)(NrnThread *)
Definition: fadvance.cpp:141
static int imped_change_cnt
Definition: partrans.cpp:794
static neuron::container::data_handle< double > non_vsrc_update(Node *nd, int type, neuron::container::field_index ix)
Definition: partrans.cpp:244
#define PPList
Definition: partrans.cpp:172
static double * outsrc_buf_
Definition: partrans.cpp:178
void nrnmpi_target_var()
Definition: partrans.cpp:302
void nrnmpi_setup_transfer()
Definition: partrans.cpp:620
std::vector< Point_process * > PPList
Definition: partrans.cpp:173
static std::vector< SourceViBuf > source_vi_buf_
Definition: partrans.cpp:169
const char * bbcore_write_version
Definition: nrncore_io.cpp:25
static int max_targets_
Definition: partrans.cpp:207
static MapSgid2Int sid2insrc_
Definition: partrans.cpp:189
static void thread_transfer(NrnThread *_nt)
Definition: partrans.cpp:578
static int insrc_buf_size_
Definition: partrans.cpp:183
static void thread_vi_compute(NrnThread *_nt)
Definition: partrans.cpp:532
void nrnmpi_source_var()
Definition: partrans.cpp:280
static double * insrc_buf_
Definition: partrans.cpp:177
SetupTransferInfo * nrn_get_partrans_setup_info(int ngroup, int cn_nthread, size_t cn_sidt_sz)
Definition: partrans.cpp:981
void(* nrnmpi_v_transfer_)()
Definition: fadvance.cpp:138
void(* nrn_mk_transfer_thread_data_)()
Definition: multicore.cpp:60
std::vector< Memb_func > memb_func
Definition: init.cpp:145
short type
Definition: cabvars.h:10
int nrn_sparse_partrans
Definition: init.cpp:112
static double nhost(void *v)
Definition: ocbbs.cpp:201
int sgid_t
Definition: partrans.hpp:20
#define sgid_alltoallv
#define NODEV(n)
Definition: section_fwd.hpp:60
#define NULL
Definition: spdefs.h:105
std::vector< T > data
Definition: have2want.hpp:39
std::vector< int > cnt
Definition: have2want.hpp:40
std::vector< int > displ
Definition: have2want.hpp:41
double * v
Definition: section_fwd.hpp:40
A view into a set of mechanism instances.
Definition: nrnoc_ml.h:34
int nodecount
Definition: nrnoc_ml.h:78
std::ptrdiff_t legacy_index(double const *ptr) const
Calculate a legacy index of the given pointer in this mechanism data.
Definition: memblist.cpp:131
Definition: section.h:105
struct NrnThread * _nt
Definition: section.h:196
auto v_handle()
Definition: section.h:153
int v_node_index
Definition: section.h:212
Extnode * extnode
Definition: section.h:199
neuron::container::Node::owning_handle _node_handle
Definition: section.h:109
auto & v()
Definition: section.h:141
Prop * prop
Definition: section.h:190
Represent main neuron object computed by single thread.
Definition: multicore.h:58
NrnThreadMembList * tml
Definition: multicore.h:62
int id
Definition: multicore.h:66
double * node_rhs_storage()
Definition: multicore.cpp:1074
int end
Definition: multicore.h:65
Memb_list ** _ml_list
Definition: multicore.h:63
struct NrnThreadMembList * next
Definition: multicore.h:34
Definition: hocdec.h:173
A point process is computed just like regular mechanisms.
Definition: section_fwd.hpp:77
Definition: section.h:231
short _type
Definition: section.h:244
std::vector< double > val
Definition: partrans.cpp:167
std::vector< Node * > nd
Definition: partrans.cpp:166
std::vector< neuron::container::data_handle< double > > sv
Definition: partrans.cpp:156
std::vector< neuron::container::data_handle< double > > tv
Definition: partrans.cpp:154
Memb_list ** _ml_list
Definition: multicore.hpp:81
Struct used to index SoAoS data, such as array range variables.
std::size_t current_row() const
Return current offset in the underlying storage where this object lives.
Definition: view_utils.hpp:44