NEURON
treeset.cpp
Go to the documentation of this file.
1 #include <../../nrnconf.h>
2 /* /local/src/master/nrn/src/nrnoc/treeset.cpp,v 1.39 1999/07/08 14:25:07 hines Exp */
3 
4 #include "cabcode.h"
5 #include "cvodeobj.h"
6 #include "membfunc.h"
7 #include "multisplit.h"
8 #include "nrn_ansi.h"
9 #include "neuron.h"
14 #include "nonvintblock.h"
15 #include "nrndae_c.h"
16 #include "nrniv_mf.h"
17 #include "nrnmpi.h"
18 #include "ocnotify.h"
19 #include "partrans.h"
20 #include "section.h"
21 #include "spmatrix.h"
22 #include "utils/profile/profiler_interface.h"
23 #include "multicore.h"
24 
25 #include <cstdio>
26 #include <cstdlib>
27 #include <cerrno>
28 #include <cmath>
29 
30 #include <algorithm>
31 #include <string>
32 
33 #include <fmt/format.h>
34 
35 extern spREAL* spGetElement(char*, int, int);
36 
37 int nrn_shape_changed_; /* for notifying Shape class in nrniv */
39 
40 extern double chkarg(int, double low, double high);
41 #if !defined(NRNMPI) || NRNMPI == 0
42 extern double nrnmpi_wtime();
43 #endif
45 extern int* nrn_prop_dparam_size_;
46 extern int* nrn_dparam_ptr_start_;
47 extern int* nrn_dparam_ptr_end_;
48 extern void nrn_define_shape();
49 
51 
52 /*
53 Do not use unless necessary (loops in tree structure) since overhead
54 (for gaussian elimination) is
55 about a factor of 3 in space and 2 in time even for a tree.
56 */
58 int use_sparse13 = 0;
60 
61 /*
62 When properties are allocated to nodes or freed, v_structure_change is
63 set to 1. This means that the mechanism vectors need to be re-determined.
64 */
68 
69 extern int section_count;
70 extern Section** secorder;
71 #if 1 /* if 0 then handled directly to save space : see finitialize*/
72 extern short* nrn_is_artificial_;
74 #endif
75 
76 /*
77 (2002-04-06)
78 One reason for going into the following in depth is to allow
79 comparison of the fixed step implicit method
80 with the implementation of the corresponding
81 cvode (vext not allowed) and daspk (vext allowed) versions.
82 
83 In the fixed step method the result of a setup is a linear matrix
84 equation of the form
85  G*(vinew,vxnew) = i(vm,vx)
86 which is solved and followed by an update step
87  vx=vxnew
88  vm = vinew - vxnew
89 
90 When cvode was added, as much as possible of the existing setup was re-used
91 through the addition of flags. Cvode solves a system of the form
92  dy/dt = f(y)
93 and this is not obtainable in the presence of extracellular or LinearMechanism
94 instances. And even in the cases it can handle, it
95 required a good deal of manipulation to remove the zero area compartments
96 from the state vector and compute the algebraic equations separately.
97 In fact, as of 2002-04-06, the CONSERVE statement for kinetic schemes
98 in model descriptions is still ignored.
99 At any rate, the issues involved are
100 1) How to compute f(y). Any algebraic equations are solved inside f(y)
101 so that the algebraic states are consistent with y.
102 2) How to setup the jacobian (I - gamma*df/dy)*y = b
103 
104 When daspk was added which solves the form
105  0 = f(y, y')
106 having one implementation serving the setup for
107 three very distinct methods became too cumbersome. Therefore setup_tree_matrix
108 was copied twice and modified separately so that each method
109 had its own setup code derived from this file.
110 
111 The issues that have
112 arisen with daspk are
113 1) How to actually implement a residual function res = f(y, y')
114 2) How to actually implement a preconditioner setup of the form
115  (df/dy + cj*df/dy')*y = b
116 3) how to initialize so that 0 = f(y(0), y'(0))
117 when it is not possible to know which are the algebraic and which
118 are the differential equations. Also, it is often the case that
119 a linear combination
120 of apparently differential equations yields an algebraic equation and we
121 don't know the linearly dependent subsets.
122 DASPK itself will only handle initialization if one can mark some states
123 as differential and the remaining states as algebraic.
124 
125 With that, here's what is going on in this file with respect to setup of
126  G*(vinew, vxnew) = i(vm,vx)
127 
128 The current balance equations are sum of outward currents = sum of inward currents
129 For an internal node
130  cm*dvm/dt + i(vm) = is(vi) + ai_j*(vi_j - vi)
131 For an external node (one layer)
132  cx*dvx/dt + gx*(vx - ex) = cm*dvm/dt + i(vm) + ax_j*(vx_j - vx)
133 where vm = vi - vx
134 and every term has the dimensions mA/cm2
135 (the last terms in each equation represent the second derivative cable properties)
136 The chosen convention that determines the side of the terms and their sign
137 is that
138 positive membrane current is outward but electrode stimulus
139 currents are toward the node. We place the terms involving
140 currents leaving a node on the left, and currents entering a node
141 on the right. i.e.
142  |is
143  ia `.'
144  ---> <---
145  |im
146  ie `.'
147  ---> <---
148  |gx*vx
149  `.'
150  ---
151  \ /
152  -
153 
154 At the hoc level, v refers to vm and vext refers to vx.
155 It should also be realized that LinearMechanism instances overlay equations of
156 the form
157 c*dy/dt + g*y = b
158 onto the current balance equation set thus adding new states,
159 equations, and terms to the above equations. However,
160 note that when y refers to a voltage of a segment in those equations
161 it always means vi or vx and never vm.
162 
163 At this point it is up to us whether we setup and solve the form
164 G*(vm,vx) = i or G*(vi,vx) = i
165 since any particular choice is resolved back to (vm,vx) in the update step.
166 At present, particularly for the implicit fixed step method whose setup
167 implementation resides in this file, we've chosen G*(vi,vx) = i,
168 because there are fewer operations in setting up second derivatives and it
169 makes life easy for the LinearMechanism. i.e. on setup each element of the
170 linear mechanism gets added to only one element of the complete matrix equations
171 and on update we merely copy the
172 vi solution to the proper LinearMechanism states and update v=vi-vext
173 for the compartment membrane potentials.
174 
175 The implicit linear form for the compartment internal node is
176 cm/dt*(vmnew - vmold) + (di/dvm*(vmnew - vmold) + i(vmold))
177  = (dis/dvi*(vinew - viold) + is(viold)) + ai_j*(vinew_j - vinew)
178 and for the compartment external node is
179 cx/dt*(vxnew - vxold) - gx(vxnew - ex)
180  = cm/dt*(vmnew - vmold) + (di/dvm*(vmnew - vmold) + i(vmold))
181  + ax_j*(vxnew_j - vxnew) = 0
182 
183 and this forms the matrix equation row (for an internal node)
184  (cm/dt + di/dvm - dis/dvi + ai_j)*vinew
185  - ai_j*vinew_j
186  -(cm/dt + di/dvm)*vxnew
187  =
188  cm/dt*vmold + di/dvm*vmold - i(vmold) - dis/dvi*viold + is(viold)
189 
190 where old is present value of vm, vi, or vx and new will be found by solving
191 this matrix equation (which is of the form G*v = rhs)
192 The matrix equation row for the extracellular node is
193  (cx/dt + gx + ax_j + cm/dt + di/dvm)*vext
194  -ax_j*vxnew_j
195  -(cm/dt + di/dvm)*vinew
196  = cx/dt*vxold - (cm/dt*vmold + di/dv*vmold - i(vmold)) - gx*ex
197 
198 Node.v is always interpreted as membrane potential and it is important to
199 distinguish this from internal potential (Node.v + Node.extnode.v)
200 when the extracellular mechanism is present.
201 
202 In dealing with these current balance equations, all three methods
203 use the same mechanism calls to calculate di/dvm, i(vm), dis/dvi, is(vi)
204 and add them to the matrix equation.
205 Thus the passive channel, i=g*(v - e), is a transmembrane current
206 (convention is positive outward) and the mechanism adds g to the
207 diagonal element of the matrix and subtracts i - g*v from the right hand side.
208 (see nrn_cur of passive.cpp) It does NOT add anything to the extracellular
209 current balance row.
210 
211 An electrode current (convention positive current increases the internal potential)
212 such as a voltage clamp with small resistance, is=g*(vc - (v+vext))
213 (see nrn_cur of svclmp.cpp)
214 subtracts -g from the matrix diagonal (since dis/dvi = -g)
215 and adds i - g*(v+vext) to the
216 right hand side. In the presence of an extracellular mechanism, such electrode
217 mechanisms add the dis/dvi term to the extracellular equation diagonal and also add
218 is - dis/dvi*(viold) to the extracellular equation right hand side.
219 
220 This is where a trick comes in. Note that in the equations it is the
221 membrane currents that appear in both rows whereas the stimulus appears
222 in the internal row only. But the membrane mechanisms add membrane currents
223 only to the internal row and stimuli to both rows. Perhaps the additional
224 confusion was not worth the mechanism efficiency gain ( since there are many
225 more membrane (and synaptic) currents than stimulus currents) but at any rate
226 the matrix filling is ordered so that membrane mechanisms are done before
227 the axial currents. This means that only membrane and stimulus currents are present
228 on the internal diagonal and right hand side. However, only stimulus current
229 is present on the external diagonal and right hand side. The difference
230 of course is the membrane current elements by themselves and the extracellular
231 code fills the elements appropriately.
232 So, in the code below, the order for doing the extracellular setup is very
233 important with respect to filling the matrix and mechanisms must be first,
234 then the obsolete activsynapse, then the extracellular setup, then
235 linear models and the obsolete activstim and activclamp. I.e it is crucial
236 to realize that before extracellular has been called that the internal
237 current balance matrix rows are with respect to vm whereas (except for special
238 handling of stimulus terms by keeping separate track of those currents)
239 and after extracellular has been called the current balance matrix rows are
240 with respect to vi and vext.
241 Finally at the end it is possible to add the internal axial terms.
242 */
243 
244 /*
245 2002-04-06
246 General comments toward a reorganization of the implementation to
247 promote code sharing of the fixed, cvode, and daspk methods and with
248 regard to the efficiency of cvode and daspk.
249 
250 The existing fixed step setup stategy is almost identical to that needed
251 by the current balance portion jacobian solvers for cvode and daspk.
252 However it is wasteful to calculate the right hand side since that is
253 thrown away because those solvers demand a calculation of M*x=b where b
254 is provided and the answer ends up in b. Also, for the function evaluation
255 portion of those solvers, the reuse of the setup is wastful because
256 after setting up the matrix, we essentially calculate rhs - M*x
257 and then throw away M.
258 
259 So, without reducing the efficiency of the fixed step it would be best
260 to re-implement the setup functions to separate the calculation of M
261 from that of b. Those pieces could then be used by all three methods as
262 needed. Also at this level of strategy we can properly take into
263 account cvode's and daspk's requirement of separating the dy/dt terms
264 from the y dependent terms. Note that the problem we are dealing with
265 is entirely confined to the current balance equations and little or
266 no change is required for the mechanism ode's.
267 
268 One other detail must be considered.
269 The computation that memb_func[i].current presently
270 performs can be split into two parts only if the jacabian calculation is done
271 first. This is because the first BREAKPOINT evaluation is for
272 v+.001 and the second is for v. We can't turn that around. The best that
273 can be done (apart from analytic calculation of g) is to make use
274 of the simultaneous calculation of g for the fixed step, store it for
275 use when the jacobian is evalulated and eventually
276 have faster version of current only for use by cvode and daspk.
277 
278 A further remark. LinearMechanisms allow much greater generality in
279 the structure of the current balance equations, especially with regard
280 to extracellular coupling and nonlinear gap junctions. In both these
281 cases, the extracellular equivalent circuit apparatus is entirely
282 superfluous and only the idea of a floating extracellular node is
283 needed. Perhaps this, as well as the elimination of axial resistors
284 for single segment sections will be forthcoming. Therefore it
285 is also useful to keep a clear separation between the computation of
286 membrane currents intra- and extracellular contribution
287 and the calculation of intracellular axial and extracellular currents.
288 
289 
290 The most reusable fixed step strategy I can think of is to compute
291 M*(dvi,dvx) = i on the basis of
292 cm*dvm/dt = -i(vm) + is(vi) + ai_j*(vi_j - vi)
293 cx*dvx/dt - cm*dvm/dt = -gx*(vx - ex) + i(vm) + ax_j*(vx_j - vx)
294 It has advantage of not adding capacitance terms to the
295 right hand side but requires the calculation of axial currents
296 for addition to the right hand side, which would have to be done anyway
297 for cvode and daspk. The choice is re-usability. Note that the dvm/dt term
298 in the external equation is why cvode can't work with the extracellular
299 mechanism. Also the axial coupling terms are linear so both the matrix
300 and righthand side are easy to setup separately without much loss of
301 efficiency.
302 
303 1)Put -i(vm)+is(vi) into right hand side for internal node and,
304 if an extracellular node exists, store is(vi) as well so that we
305 can later carry out the trick of putting i(vm) into the external right
306 hand side. This is common also for cvode and daspk.
307 2)For extracellular nodes perform the trick and deal with gx*ex and
308 add the extracellular axial terms to the right hand side.
309 Common to daspk and cvode.
310 3) Add the internal axial terms to the right hand side. Common to
311 daspk and cvode
312 The above accomplishes a function evaluation for cvode. For daspk it would
313 remain only to use y' to calculate the residual from the derivative terms.
314 
315 4)Start setting up the matrix by calculating di/dvm from the mechanisms
316 and put it directly into the rhs. For stimulus currents, store dis/dvi
317 for later use of the trick. Common also for cvode and daspk
318 5) For extracellular nodes perform the trick and deal with gx and extracellular
319 axial terms to the matrix. Common to cvode and daspk
320 6) Add the internal axial terms to the matrix.Commond to cvode and daspk.
321 
322 At this point the detailed handling of capacitance may be different for the
323 three methods. For the fixed step, what is required is
324 7) add cm/dt to the internal diagonal
325 8) for external nodes fill in the cv/dt and cx/dt contributions.
326 This completes the setup of the matrix equations.
327 We then solve for dvi and dvx and update using (for implicit method)
328 vx += dvx
329 vm += dvi-dvx
330 
331 */
332 
333 /*
334  * Update actual_rhs based on _sp13_rhs used for sparse13 solver
335  */
337  for (int i = 0; i < nt->end; i++) {
338  nt->actual_rhs(i) = nt->_sp13_rhs[nt->_v_node[i]->eqn_index_];
339  }
340 }
341 
342 /*
343  * Update _sp13_rhs used for sparse13 solver based on changes on actual_rhs
344  */
346  for (int i = 0; i < nt->end; i++) {
347  nt->_sp13_rhs[nt->_v_node[i]->eqn_index_] = nt->actual_rhs(i);
348  }
349 }
350 
351 /*
352  * Update the SoA storage for node matrix diagonals from the sparse13 matrix.
353  */
355  for (int i = 0; i < nt->end; ++i) {
356  nt->actual_d(i) = *nt->_v_node[i]->_d_matelm;
357  }
358 }
359 
360 /*
361  * Update the SoA storage for node matrix diagonals from the sparse13 matrix.
362  */
364  for (int i = 0; i < nt->end; ++i) {
365  *nt->_v_node[i]->_d_matelm = nt->actual_d(i);
366  }
367 }
368 
369 /* calculate right hand side of
370 cm*dvm/dt = -i(vm) + is(vi) + ai_j*(vi_j - vi)
371 cx*dvx/dt - cm*dvm/dt = -gx*(vx - ex) + i(vm) + ax_j*(vx_j - vx)
372 This is a common operation for fixed step, cvode, and daspk methods
373 */
374 
375 void nrn_rhs(neuron::model_sorted_token const& cache_token, NrnThread& nt) {
376  auto* const _nt = &nt;
377  int i, i1, i2, i3;
378  double w;
379  int measure = 0;
380  NrnThreadMembList* tml;
381 
382  i1 = 0;
383  i2 = i1 + _nt->ncell;
384  i3 = _nt->end;
385  if (_nt->id == 0 && nrn_mech_wtime_) {
386  measure = 1;
387  }
388 
389  if (diam_changed) {
390  nrn_thread_error("need recalc_diam()");
391  recalc_diam();
392  }
393  auto* const vec_rhs = nt.node_rhs_storage();
394  if (use_sparse13) {
395  int i, neqn;
396  nrn_thread_error("nrn_rhs use_sparse13");
397  neqn = spGetSize(_nt->_sp13mat, 0);
398  for (i = 1; i <= neqn; ++i) {
399  _nt->_sp13_rhs[i] = 0.;
400  }
401  for (i = i1; i < i3; ++i) {
402  NODERHS(_nt->_v_node[i]) = 0.;
403  }
404  } else {
405  for (i = i1; i < i3; ++i) {
406  vec_rhs[i] = 0.;
407  }
408  }
409  auto const vec_sav_rhs = _nt->node_sav_rhs_storage();
410  if (vec_sav_rhs) {
411  for (i = i1; i < i3; ++i) {
412  vec_sav_rhs[i] = 0.;
413  }
414  }
415 
416  nrn_ba(cache_token, nt, BEFORE_BREAKPOINT);
417  /* note that CAP has no current */
418  for (tml = _nt->tml; tml; tml = tml->next)
419  if (auto const current = memb_func[tml->index].current; current) {
420  std::string mechname("cur-");
421  mechname += memb_func[tml->index].sym->name;
422  if (measure) {
423  w = nrnmpi_wtime();
424  }
426  current(cache_token, _nt, tml->ml, tml->index);
428  if (measure) {
429  nrn_mech_wtime_[tml->index] += nrnmpi_wtime() - w;
430  }
431  if (errno) {
432  if (nrn_errno_check(tml->index)) {
433  hoc_warning("errno set during calculation of currents", (char*) 0);
434  }
435  }
436  }
438 
439  if (vec_sav_rhs) {
440  /* vec_sav_rhs has only the contribution of electrode current
441  here we transform so it only has membrane current contribution
442  */
443  for (i = i1; i < i3; ++i) {
444  vec_sav_rhs[i] -= vec_rhs[i];
445  }
446  }
447 #if EXTRACELLULAR
448  /* Cannot have any axial terms yet so that i(vm) can be calculated from
449  i(vm)+is(vi) and is(vi) which are stored in rhs vector. */
450  nrn_rhs_ext(_nt);
451  /* nrn_rhs_ext has also computed the the internal axial current
452  for those nodes containing the extracellular mechanism
453  */
454 #endif
455  if (use_sparse13) {
456  /* must be after nrn_rhs_ext so that whatever is put in
457  nd->_rhs does not get added to nde->rhs */
458  nrndae_rhs(_nt);
459  }
460 
461  activstim_rhs();
462  activclamp_rhs();
463  /* now the internal axial currents.
464  The extracellular mechanism contribution is already done.
465  rhs += ai_j*(vi_j - vi)
466  */
467  auto* const vec_a = nt.node_a_storage();
468  auto* const vec_b = nt.node_b_storage();
469  auto* const vec_v = nt.node_voltage_storage();
470  auto* const parent_i = nt._v_parent_index;
471  for (i = i2; i < i3; ++i) {
472  auto const pi = parent_i[i];
473  auto const dv = vec_v[pi] - vec_v[i];
474  // our connection coefficients are negative so
475  vec_rhs[i] -= vec_b[i] * dv;
476  vec_rhs[pi] += vec_a[i] * dv;
477  }
478 }
479 
480 /* calculate left hand side of
481 cm*dvm/dt = -i(vm) + is(vi) + ai_j*(vi_j - vi)
482 cx*dvx/dt - cm*dvm/dt = -gx*(vx - ex) + i(vm) + ax_j*(vx_j - vx)
483 with a matrix so that the solution is of the form [dvm+dvx,dvx] on the right
484 hand side after solving.
485 This is a common operation for fixed step, cvode, and daspk methods
486 */
487 
488 void nrn_lhs(neuron::model_sorted_token const& sorted_token, NrnThread& nt) {
489  auto* const _nt = &nt;
490  int i, i1, i2, i3;
491  NrnThreadMembList* tml;
492 
493  i1 = 0;
494  i2 = i1 + _nt->ncell;
495  i3 = _nt->end;
496 
497  if (diam_changed) {
498  nrn_thread_error("need recalc_diam()");
499  }
500 
501  if (use_sparse13) {
502  // Zero the sparse13 matrix
503  spClear(_nt->_sp13mat);
504  }
505 
506  // Make sure the SoA node diagonals are also zeroed (is this needed?)
507  auto* const vec_d = _nt->node_d_storage();
508  for (int i = i1; i < i3; ++i) {
509  vec_d[i] = 0.;
510  }
511 
512  auto const vec_sav_d = _nt->node_sav_d_storage();
513  if (vec_sav_d) {
514  for (i = i1; i < i3; ++i) {
515  vec_sav_d[i] = 0.;
516  }
517  }
518 
519  /* note that CAP has no jacob */
520  for (tml = _nt->tml; tml; tml = tml->next)
521  if (auto const jacob = memb_func[tml->index].jacob; jacob) {
522  std::string mechname("cur-");
523  mechname += memb_func[tml->index].sym->name;
525  jacob(sorted_token, _nt, tml->ml, tml->index);
527  if (errno) {
528  if (nrn_errno_check(tml->index)) {
529  hoc_warning("errno set during calculation of jacobian", (char*) 0);
530  }
531  }
532  }
533  /* now the cap current can be computed because any change to cm by another model
534  has taken effect
535  */
536  /* note, the first is CAP */
537  if (_nt->tml) {
538  assert(_nt->tml->index == CAP);
539  nrn_cap_jacob(sorted_token, _nt, _nt->tml->ml);
540  }
541 
543 
544  if (vec_sav_d) {
545  /* vec_sav_d has only the contribution of electrode current
546  here we transform so it only has membrane current contribution
547  */
548  for (i = i1; i < i3; ++i) {
549  vec_sav_d[i] = vec_d[i] - vec_sav_d[i];
550  }
551  }
552 #if EXTRACELLULAR
553  /* nde->_d[0] contains the -ELECTRODE_CURRENT contribution to nd->_d */
554  nrn_setup_ext(_nt);
555 #endif
556  if (use_sparse13) {
557  /* must be after nrn_setup_ext so that whatever is put in
558  nd->_d does not get added to nde->d */
560  nrndae_lhs();
561  update_actual_d_based_on_sp13_mat(_nt); // because nrndae_lhs writes to sp13_mat
562  }
563 
564  activclamp_lhs();
565 
566  /* at this point d contains all the membrane conductances */
567 
568 
569  /* now add the axial currents */
570  if (use_sparse13) {
571  update_sp13_mat_based_on_actual_d(_nt); // just because of activclamp_lhs
572  for (i = i2; i < i3; ++i) { // note i2
573  Node* nd = _nt->_v_node[i];
574  auto const parent_i = _nt->_v_parent_index[i];
575  auto* const parent_nd = _nt->_v_node[parent_i];
576  auto const nd_a = NODEA(nd);
577  auto const nd_b = NODEB(nd);
578  // Update entries in sp13_mat
579  *nd->_a_matelm += nd_a;
580  *nd->_b_matelm += nd_b; /* b may have value from lincir */
581  *nd->_d_matelm -= nd_b;
582  // used to update NODED (sparse13 matrix) using NODEA and NODEB ("SoA")
583  *parent_nd->_d_matelm -= nd_a;
584  // Also update the Node's d value in the SoA storage (is this needed?)
585  vec_d[i] -= nd_b;
586  vec_d[parent_i] -= nd_a;
587  }
588  } else {
589  auto* const vec_a = _nt->node_a_storage();
590  auto* const vec_b = _nt->node_b_storage();
591  for (i = i2; i < i3; ++i) {
592  vec_d[i] -= vec_b[i];
593  vec_d[_nt->_v_parent_index[i]] -= vec_a[i];
594  }
595  }
596 }
597 
598 /* for the fixed step method */
600  nrn::Instrumentor::phase _{"setup-tree-matrix"};
601  nrn_rhs(cache_token, nt);
602  nrn_lhs(cache_token, nt);
605 }
606 
607 /* membrane mechanisms needed by other mechanisms (such as Eion by HH)
608 may require that the needed mechanism appear before it in the list.
609 (because ina must be initialized before it is incremented by HH)
610 With current implementation, a chain of "needs" may not be in list order.
611 */
612 static Prop** current_prop_list; /* the one prop_alloc is working on
613  when need_memb is called */
614 static int disallow_needmemb = 0; /* point processes cannot use need_memb
615  when inserted at locations 0 or 1 */
616 
618 
619 extern Prop* prop_alloc(Prop**, int, Node*);
620 
621 
623  int type;
624  Prop *mprev, *m;
625  if (disallow_needmemb) {
626  fprintf(stderr,
627  "You can not locate a point process at\n\
628  position 0 or 1 if it needs an ion\n");
629  hoc_execerror(sym->name, "can't be inserted in this node");
630  }
631  type = sym->subtype;
632  mprev = (Prop*) 0; /* may need to relink m */
633  for (m = *current_prop_list; m; mprev = m, m = m->next) {
634  if (m->_type == type) {
635  break;
636  }
637  }
638  if (m) {
639  /* a chain of "needs" will not be in list order
640  so it is important that order only important for Eion */
641  if (mprev) {
642  mprev->next = m->next;
643  m->next = *current_prop_list;
644  }
645  *current_prop_list = m;
646  } else if (nrn_pnt_sec_for_need_) {
647  /* The caller was a POINT_PROCESS and we need to make sure
648  that all segments of this section have the ion in order to
649  prevent the possibility of multiple instances of this ion
650  if a density mechanism that needs it is subsequently inserted
651  or if the ion mechanism itself is inserted. Any earlier
652  insertions of the latter or locating this kind of POINT_PROCESS
653  in this section will mean that we no longer get to this arm
654  of the if statement because m above is not nullptr.
655  */
657  Prop** cpl = current_prop_list;
660  current_prop_list = cpl;
661  m = need_memb(sym);
662  } else {
664  }
665  return m;
666 }
667 
668 
669 Node* nrn_alloc_node_; /* needed by models that use area */
670 
671 Prop* prop_alloc(Prop** pp, int type, Node* nd) {
672  /* link in new property at head of list */
673  /* returning *Prop because allocation may */
674  /* cause other properties to be linked ahead */
675  /* some models need the node (to find area) */
676  nrn_alloc_node_ = nd; // this might be null
677  v_structure_change = 1;
678  current_prop_list = pp;
679  auto* p = new Prop{nd, static_cast<short>(type)};
680  p->next = *pp;
681  p->ob = nullptr;
682  p->_alloc_seq = -1;
683  *pp = p;
684  assert(memb_func[type].alloc);
685  p->dparam = nullptr;
686  (memb_func[type].alloc)(p);
687  return p;
688 }
689 
694  auto const type = prop->_type;
695  assert(memb_func[type].alloc);
696  memb_func[type].alloc(prop);
697  current_prop_list = nullptr;
698  nrn_point_prop_ = nullptr;
699  nrn_alloc_node_ = nullptr;
700 }
701 
702 Prop* prop_alloc_disallow(Prop** pp, short type, Node* nd) {
703  disallow_needmemb = 1;
704  auto* p = prop_alloc(pp, type, nd);
705  disallow_needmemb = 0;
706  return p;
707 }
708 
709 // free an entire property list
710 void prop_free(Prop** pp) {
711  Prop* p = *pp;
712  *pp = nullptr;
713  while (p) {
714  single_prop_free(std::exchange(p, p->next));
715  }
716 }
717 
719  extern char* pnt_map;
720  v_structure_change = 1;
721  if (pnt_map[p->_type]) {
723  return;
724  }
725  if (auto got = nrn_mech_inst_destruct.find(p->_type); got != nrn_mech_inst_destruct.end()) {
726  (got->second)(p);
727  }
728  if (p->dparam) {
729  if (p->_type == CABLESECTION) {
730  notify_freed_val_array(&(p->dparam[2].literal_value<double>()), 6);
731  }
732  nrn_prop_datum_free(p->_type, p->dparam);
733  }
734  if (p->ob) {
735  hoc_obj_unref(p->ob);
736  }
737  delete p;
738 }
739 
740 
741 /* For now there is always one more node in a section than there are
742 segments */
743 /* except in section 0 in which all nodes serve as x=0 to connecting
744 sections */
745 
746 #undef PI
747 #define PI 3.14159265358979323846
748 
749 static double diam_from_list(Section* sec, int inode, Prop* p, double rparent);
750 
753  int j;
754  double ra, dx, rright, rleft;
755  Prop* p;
756  Node* nd;
757  if (nrn_area_ri_nocount_ == 0) {
759  }
760 #if DIAMLIST
761  if (sec->npt3d) {
762  sec->prop->dparam[2] = sec->pt3d[sec->npt3d - 1].arc;
763  }
764 #endif
765  ra = nrn_ra(sec);
766  dx = section_length(sec) / ((double) (sec->nnode - 1));
767  rright = 0.;
768  for (j = 0; j < sec->nnode - 1; j++) {
769  nd = sec->pnode[j];
770  for (p = nd->prop; p; p = p->next) {
771  if (p->_type == MORPHOLOGY) {
772  break;
773  }
774  }
775  assert(p);
776 #if DIAMLIST
777  if (sec->npt3d > 1) {
778  /* trapezoidal integration of diam, area, and resistance useing pt3d
779  the integration is over the span of the segment j.
780  and NODEAREA and NODERINV are filled in. The right half of the segment
781  resistance (MOhm) is returned.
782  */
783  rright = diam_from_list(sec, j, p, rright);
784  } else
785 #endif
786  {
787  /* area for right circular cylinders. Ri as right half of parent + left half
788  of this
789  */
790  auto& diam = p->param(0);
791  if (diam <= 0.) {
792  diam = 1e-6;
793  hoc_execerror(secname(sec), "diameter diam = 0. Setting to 1e-6");
794  }
795  nd->area() = PI * diam * dx; // um^2
796  rleft = 1e-2 * ra * (dx / 2) / (PI * diam * diam / 4.); /*left half segment Megohms*/
797  NODERINV(nd) = 1. / (rleft + rright); /*uS*/
798  rright = rleft;
799  }
800  }
801  /* last segment has 0 length. area is 1e2
802  in dimensionless units */
803  sec->pnode[j]->area() = 1.e2;
804  NODERINV(sec->pnode[j]) = 1. / rright;
805  sec->recalc_area_ = 0;
806  diam_changed = 1;
807 }
808 
810  return nd->_classical_parent;
811 }
812 
813 void connection_coef(void) /* setup a and b */
814 {
815  int j;
816  double area;
817  Node* nd;
818 #if RA_WARNING
819  extern int nrn_ra_set;
820 #endif
821 
822 #if 1
823  /* now only called from recalc_diam */
825 #else
826  if (tree_changed) {
827  setup_topology();
828  }
829 #endif
830 
831 #if RA_WARNING
832  if (nrn_ra_set > 0 && nrn_ra_set < section_count - 1) {
833  hoc_warning("Don't forget to set Ra in every section", "eg. forall Ra=35.4");
834  }
835 #endif
838  for (Section* sec: range_sec(section_list)) {
839  nrn_area_ri(sec);
840  }
842  /* assume that if only one connection at x=1, then they butte
843  together, if several connections at x=1
844  then last point is at x=1, has 0 area and other points are at
845  centers of nnode-1 segments.
846  If interior connection then child half
847  section connects straight to the point*/
848  /* for the near future we always have a last node at x=1 with
849  no properties */
850 
851  // To match legacy behaviour, make sure that the SoA storage for "a" and "b" is zeroed before
852  // the initilisation code below is run.
853  for (auto tid = 0; tid < nrn_nthread; ++tid) {
854  auto& nt = nrn_threads[tid];
855  std::fill_n(nt.node_a_storage(), nt.end, 0.0);
856  std::fill_n(nt.node_b_storage(), nt.end, 0.0);
857  }
858  for (Section* sec: range_sec(section_list)) {
859  // Unnecessary because they are unused, but help when looking at fmatrix.
860  if (!sec->parentsec) {
861  if (auto* const ptr = nrn_classicalNodeA(sec->parentnode)) {
862  *ptr = 0.0;
863  }
864  if (auto* const ptr = nrn_classicalNodeB(sec->parentnode)) {
865  *ptr = 0.0;
866  }
867  }
868  /* convert to siemens/cm^2 for all nodes except last
869  and microsiemens for last. This means that a*V = mamps/cm2
870  and a*v in last node = nanoamps. Note that last node
871  has no membrane properties and no area. It may perhaps receive
872  current stimulus later */
873  /* first the effect of node on parent equation. Note That
874  last nodes have area = 1.e2 in dimensionless units so that
875  last nodes have units of microsiemens */
876  nd = sec->pnode[0];
877  area = NODEAREA(sec->parentnode);
878  /* dparam[4] is rall_branch */
879  *nrn_classicalNodeA(nd) = -1.e2 * sec->prop->dparam[4].get<double>() * NODERINV(nd) / area;
880  for (j = 1; j < sec->nnode; j++) {
881  nd = sec->pnode[j];
882  area = NODEAREA(sec->pnode[j - 1]);
883  *nrn_classicalNodeA(nd) = -1.e2 * NODERINV(nd) / area;
884  }
885  }
886  /* now the effect of parent on node equation. */
887  for (const Section* sec: range_sec(section_list)) {
888  for (j = 0; j < sec->nnode; j++) {
889  nd = sec->pnode[j];
890  *nrn_classicalNodeB(nd) = -1.e2 * NODERINV(nd) / NODEAREA(nd);
891  }
892  }
893 #if EXTRACELLULAR
894  ext_con_coef();
895 #endif
896 }
897 
898 extern "C" void nrn_shape_update_always(void) {
899  static int updating;
900  if (!updating || updating != diam_change_cnt) {
901  updating = diam_change_cnt;
902  if (tree_changed) {
903  setup_topology();
904  }
905  if (v_structure_change) {
906  v_setup_vectors();
907  }
908  if (diam_changed) {
909  recalc_diam();
910  }
911  updating = 0;
912  }
913 }
914 
915 extern "C" void nrn_shape_update(void) {
916  if (section_list->next != section_list) {
918  }
919 }
920 
921 static void nrn_matrix_node_alloc(void);
922 
923 void recalc_diam(void) {
924  v_setup_vectors();
926  connection_coef();
927  diam_changed = 0;
928  ++diam_change_cnt;
929  stim_prepare();
930  synapse_prepare();
931  clamp_prepare();
932 }
933 
934 
935 void area(void) { /* returns area (um^2) of segment containing x */
936  double x;
937  Section* sec;
938  x = *getarg(1);
939  if (x == 0. || x == 1.) {
940  hoc_retpushx(0.);
941  } else {
942  sec = chk_access();
943  if (sec->recalc_area_) {
944  nrn_area_ri(sec);
945  }
946  hoc_retpushx(NODEAREA(sec->pnode[node_index(sec, x)]));
947  }
948 }
949 
950 
951 void ri(void) { /* returns resistance (Mohm) between center of segment containing x
952  and the center of the parent segment */
953  double area;
954  Node* np;
955  np = node_ptr(chk_access(), *getarg(1), &area);
956  if (NODERINV(np)) {
957  hoc_retpushx(1. / NODERINV(np)); /* Megohms */
958  } else {
959  hoc_retpushx(1.e30);
960  }
961 }
962 
963 
964 #if DIAMLIST
965 
966 static int pt3dconst_;
967 
968 void pt3dconst(void) {
969  int i = pt3dconst_;
970  if (ifarg(1)) {
971  pt3dconst_ = (int) chkarg(1, 0., 1.);
972  }
973  hoc_retpushx((double) i);
974 }
975 
977  if (sec->logical_connection) {
978  free(sec->logical_connection);
979  sec->logical_connection = (Pt3d*) 0;
981  diam_changed = 1;
982  }
983 }
984 
985 void nrn_pt3dstyle1(Section* sec, double x, double y, double z) {
986  Pt3d* p = sec->logical_connection;
987  if (!p) {
988  p = sec->logical_connection = (Pt3d*) ecalloc(1, sizeof(Pt3d));
989  }
990  p->x = x;
991  p->y = y;
992  p->z = z;
994  diam_changed = 1;
995 }
996 
997 void pt3dstyle(void) {
998  Section* sec = chk_access();
999  if (ifarg(1)) {
1000  /* Classical, Logical connection */
1001  /*
1002  Logical connection is used to turn off snapping to the
1003  centroid of the parent which sometimes ruins
1004  the 3-d shape rendering due to branches connected to a
1005  large diameter soma getting translated to the middle of
1006  the soma. Of course, the question then arises of what
1007  to do when the soma is moved to a new location or
1008  an ancestor branch length is changed (i.e. exactly what
1009  snapping to the parent deals with automatically)
1010  Our answer is that a pt3dstyle==1 branch gets translated
1011  exactly by the amount of the parent translation
1012  instead of snapping to a specific absolute x,y,z position.
1013  */
1014  if ((int) chkarg(1, 0., 1.) == 1) {
1015  if (hoc_is_pdouble_arg(2)) {
1016  Pt3d* p = sec->logical_connection;
1017  if (p) {
1018  double* px;
1019  px = hoc_pgetarg(2);
1020  *px = p->x;
1021  px = hoc_pgetarg(3);
1022  *px = p->y;
1023  px = hoc_pgetarg(4);
1024  *px = p->z;
1025  }
1026  } else {
1027  nrn_pt3dstyle1(sec, *getarg(2), *getarg(3), *getarg(4));
1028  }
1029  } else {
1031  }
1032  }
1033  hoc_retpushx((double) (sec->logical_connection ? 1 : 0));
1034 }
1035 
1036 void pt3dclear(void) { /*destroys space in current section for 3d points.*/
1037  Section* sec = chk_access();
1038  int req;
1039  if (ifarg(1)) {
1040  req = chkarg(1, 0., 30000.);
1041  } else {
1042  req = 0;
1043  }
1044  nrn_pt3dclear(sec, req);
1045  hoc_retpushx((double) sec->pt3d_bsize);
1046 }
1047 
1048 static void nrn_pt3dbufchk(Section* sec, int n) {
1049  if (n > sec->pt3d_bsize) {
1050  sec->pt3d_bsize = n;
1051  if ((sec->pt3d = (Pt3d*) hoc_Erealloc((char*) sec->pt3d, n * sizeof(Pt3d))) == (Pt3d*) 0) {
1052  sec->npt3d = 0;
1053  sec->pt3d_bsize = 0;
1054  hoc_malchk();
1055  }
1056  }
1057 }
1058 
1059 static void nrn_pt3dmodified(Section* sec, int i0) {
1060  int n, i;
1061 
1063  diam_changed = 1;
1064  sec->recalc_area_ = 1;
1065  n = sec->npt3d;
1066 #if NTS_SPINE
1067 #else
1068  if (sec->pt3d[i0].d < 0.) {
1069  hoc_execerror("Diameter less than 0", (char*) 0);
1070  }
1071 #endif
1072  if (i0 == 0) {
1073  sec->pt3d[0].arc = 0.;
1074  i0 = 1;
1075  }
1076  for (i = i0; i < n; ++i) {
1077  Pt3d* p = sec->pt3d + i - 1;
1078  double t1, t2, t3;
1079  t1 = sec->pt3d[i].x - p->x;
1080  t2 = sec->pt3d[i].y - p->y;
1081  t3 = sec->pt3d[i].z - p->z;
1082  sec->pt3d[i].arc = p->arc + sqrt(t1 * t1 + t2 * t2 + t3 * t3);
1083  }
1084  sec->prop->dparam[2] = sec->pt3d[n - 1].arc;
1085 }
1086 
1087 void nrn_pt3dclear(Section* sec, int req) {
1089  if (req != sec->pt3d_bsize) {
1090  if (sec->pt3d) {
1091  free((char*) (sec->pt3d));
1092  sec->pt3d = (Pt3d*) 0;
1093  sec->pt3d_bsize = 0;
1094  }
1095  if (req > 0) {
1096  sec->pt3d = (Pt3d*) ecalloc(req, sizeof(Pt3d));
1097  sec->pt3d_bsize = req;
1098  }
1099  }
1100  sec->npt3d = 0;
1101 }
1102 
1103 
1104 void nrn_pt3dinsert(Section* sec, int i0, double x, double y, double z, double d) {
1105  int i, n;
1106  n = sec->npt3d;
1107  nrn_pt3dbufchk(sec, n + 1);
1108  ++sec->npt3d;
1109  for (i = n - 1; i >= i0; --i) {
1110  Pt3d* p = sec->pt3d + i + 1;
1111  p->x = sec->pt3d[i].x;
1112  p->y = sec->pt3d[i].y;
1113  p->z = sec->pt3d[i].z;
1114  p->d = sec->pt3d[i].d;
1115  }
1116  sec->pt3d[i0].x = x;
1117  sec->pt3d[i0].y = y;
1118  sec->pt3d[i0].z = z;
1119  sec->pt3d[i0].d = d;
1120  nrn_pt3dmodified(sec, i0);
1121 }
1122 
1123 void pt3dinsert(void) {
1124  Section* sec;
1125  int n, i0;
1126  sec = chk_access();
1127  n = sec->npt3d;
1128  i0 = (int) chkarg(1, 0., (double) (n));
1129  nrn_pt3dinsert(sec, i0, *getarg(2), *getarg(3), *getarg(4), *getarg(5));
1130  hoc_retpushx(0.);
1131 }
1132 
1133 void nrn_pt3dchange1(Section* sec, int i, double d) {
1134  sec->pt3d[i].d = d;
1136  diam_changed = 1;
1137  sec->recalc_area_ = 1;
1138 }
1139 
1140 void nrn_pt3dchange2(Section* sec, int i, double x, double y, double z, double diam) {
1141  sec->pt3d[i].x = x;
1142  sec->pt3d[i].y = y;
1143  sec->pt3d[i].z = z;
1144  sec->pt3d[i].d = diam;
1146 }
1147 
1148 void pt3dchange(void) {
1149  int i, n;
1150  Section* sec = chk_access();
1151  n = sec->npt3d;
1152  i = (int) chkarg(1, 0., (double) (n - 1));
1153  if (ifarg(5)) {
1154  nrn_pt3dchange2(sec, i, *getarg(2), *getarg(3), *getarg(4), *getarg(5));
1155  } else {
1156  nrn_pt3dchange1(sec, i, *getarg(2));
1157  }
1158  hoc_retpushx(0.);
1159 }
1160 
1161 void nrn_pt3dremove(Section* sec, int i0) {
1162  int i, n;
1163  n = sec->npt3d;
1164  for (i = i0 + 1; i < n; ++i) {
1165  Pt3d* p = sec->pt3d + i - 1;
1166  p->x = sec->pt3d[i].x;
1167  p->y = sec->pt3d[i].y;
1168  p->z = sec->pt3d[i].z;
1169  p->d = sec->pt3d[i].d;
1170  }
1171  --sec->npt3d;
1172  nrn_pt3dmodified(sec, i0);
1173 }
1174 
1175 void pt3dremove(void) {
1176  int i0, n;
1177  Section* sec = chk_access();
1178  n = sec->npt3d;
1179  i0 = (int) chkarg(1, 0., (double) (n - 1));
1180  nrn_pt3dremove(sec, i0);
1181 
1182  hoc_retpushx(0.);
1183 }
1184 
1186  if (!pt3dconst_ && sec->npt3d) { /* fill 3dpoints as though constant diam segments */
1187  int i;
1188  double x, L;
1189  L = section_length(sec);
1190  if (fabs(L - sec->pt3d[sec->npt3d - 1].arc) > .001) {
1191  nrn_length_change(sec, L);
1192  }
1193  for (i = 0; i < sec->npt3d; ++i) {
1194  x = sec->pt3d[i].arc / L;
1195  if (x > 1.0) {
1196  x = 1.0;
1197  }
1198  node_index(sec, x);
1199  sec->pt3d[i].d = nrn_diameter(sec->pnode[node_index(sec, x)]);
1200  }
1202  }
1203 }
1204 
1205 void nrn_length_change(Section* sec, double d) {
1206  if (!pt3dconst_ && sec->npt3d) {
1207  int i;
1208  double x0, y0, z0;
1209  double fac;
1210  double l;
1211  x0 = sec->pt3d[0].x;
1212  y0 = sec->pt3d[0].y;
1213  z0 = sec->pt3d[0].z;
1214  l = sec->pt3d[sec->npt3d - 1].arc;
1215  fac = d / l;
1216  /*if (fac != 1) { printf("nrn_length_change from %g to %g\n", l, d);}*/
1217  for (i = 0; i < sec->npt3d; ++i) {
1218  sec->pt3d[i].arc = sec->pt3d[i].arc * fac;
1219  sec->pt3d[i].x = x0 + (sec->pt3d[i].x - x0) * fac;
1220  sec->pt3d[i].y = y0 + (sec->pt3d[i].y - y0) * fac;
1221  sec->pt3d[i].z = z0 + (sec->pt3d[i].z - z0) * fac;
1222  }
1224  }
1225 }
1226 
1227 /*ARGSUSED*/
1229  return pt3dconst_ == 0;
1230 }
1231 
1232 static void stor_pt3d_vec(Section* sec, IvocVect* xv, IvocVect* yv, IvocVect* zv, IvocVect* dv) {
1233  int i;
1234  int n = vector_capacity(xv);
1235  double* x = vector_vec(xv);
1236  double* y = vector_vec(yv);
1237  double* z = vector_vec(zv);
1238  double* d = vector_vec(dv);
1239  nrn_pt3dbufchk(sec, n);
1240  sec->npt3d = n;
1241  for (i = 0; i < n; i++) {
1242  sec->pt3d[i].x = x[i];
1243  sec->pt3d[i].y = y[i];
1244  sec->pt3d[i].z = z[i];
1245  sec->pt3d[i].d = d[i];
1246  }
1247  nrn_pt3dmodified(sec, 0);
1248 }
1249 
1250 void pt3dadd(void) {
1251  /*pt3add(x,y,z, d) stores 3d point at end of current pt3d list.
1252  first point assumed to be at arc length position 0. Last point
1253  at 1. arc length increases monotonically.
1254  */
1255  if (hoc_is_object_arg(1)) {
1257  } else {
1258  stor_pt3d(chk_access(), *getarg(1), *getarg(2), *getarg(3), *getarg(4));
1259  }
1260  hoc_retpushx(1.);
1261 }
1262 
1263 
1264 void n3d(void) { /* returns number of 3d points in section */
1265  Section* sec;
1266  sec = chk_access();
1267  hoc_retpushx((double) sec->npt3d);
1268 }
1269 
1270 void x3d(void) { /* returns x value at index of 3d list */
1271  Section* sec;
1272  int n, i;
1273  sec = chk_access();
1274  n = sec->npt3d - 1;
1275  i = chkarg(1, 0., (double) n);
1276  hoc_retpushx((double) sec->pt3d[i].x);
1277 }
1278 
1279 void y3d(void) { /* returns x value at index of 3d list */
1280  Section* sec;
1281  int n, i;
1282  sec = chk_access();
1283  n = sec->npt3d - 1;
1284  i = chkarg(1, 0., (double) n);
1285  hoc_retpushx((double) sec->pt3d[i].y);
1286 }
1287 
1288 void z3d(void) { /* returns x value at index of 3d list */
1289  Section* sec;
1290  int n, i;
1291  sec = chk_access();
1292  n = sec->npt3d - 1;
1293  i = chkarg(1, 0., (double) n);
1294  hoc_retpushx((double) sec->pt3d[i].z);
1295 }
1296 
1297 void arc3d(void) { /* returns x value at index of 3d list */
1298  Section* sec;
1299  int n, i;
1300  sec = chk_access();
1301  n = sec->npt3d - 1;
1302  i = chkarg(1, 0., (double) n);
1303  hoc_retpushx((double) sec->pt3d[i].arc);
1304 }
1305 
1306 void diam3d(void) { /* returns x value at index of 3d list */
1307  Section* sec;
1308  double d;
1309  int n, i;
1310  sec = chk_access();
1311  n = sec->npt3d - 1;
1312  i = chkarg(1, 0., (double) n);
1313  d = (double) sec->pt3d[i].d;
1314  hoc_retpushx(fabs(d));
1315 }
1316 
1317 void spine3d(void) { /* returns x value at index of 3d list */
1318  Section* sec;
1319  int n, i;
1320  double d;
1321  sec = chk_access();
1322  n = sec->npt3d - 1;
1323  i = chkarg(1, 0., (double) n);
1324  d = (double) sec->pt3d[i].d;
1325  if (d < 0) {
1326  hoc_retpushx(1.);
1327  } else {
1328  hoc_retpushx(0.);
1329  }
1330 }
1331 
1332 void stor_pt3d(Section* sec, double x, double y, double z, double d) {
1333  int n;
1334 
1335  n = sec->npt3d;
1336  nrn_pt3dbufchk(sec, n + 1);
1337  sec->npt3d++;
1338  sec->pt3d[n].x = x;
1339  sec->pt3d[n].y = y;
1340  sec->pt3d[n].z = z;
1341  sec->pt3d[n].d = d;
1343 }
1344 
1345 static double spinearea = 0.;
1346 
1347 void setSpineArea(void) {
1348  spinearea = *getarg(1);
1349  diam_changed = 1;
1351 }
1352 
1353 void getSpineArea(void) {
1355 }
1356 
1357 void define_shape(void) {
1358  nrn_define_shape();
1359  hoc_retpushx(1.);
1360 }
1361 
1362 static void nrn_translate_shape(Section* sec, float x, float y, float z) {
1363  int i;
1364  float dx, dy, dz;
1365  if (pt3dconst_) {
1366  return;
1367  }
1368  if (sec->logical_connection) { /* args are relative not absolute */
1369  Pt3d* p = sec->logical_connection;
1370  dx = x - p->x;
1371  dy = y - p->y;
1372  dz = z - p->z;
1373  p->x = x;
1374  p->y = y;
1375  p->z = z;
1376  } else {
1377  dx = x - sec->pt3d[0].x;
1378  dy = y - sec->pt3d[0].y;
1379  dz = z - sec->pt3d[0].z;
1380  }
1381  /* if (dx*dx + dy*dy + dz*dz < 10.)*/
1382  for (i = 0; i < sec->npt3d; ++i) {
1383  sec->pt3d[i].x += dx;
1384  sec->pt3d[i].y += dy;
1385  sec->pt3d[i].z += dz;
1386  }
1387 }
1388 
1389 void nrn_define_shape(void) {
1390  static int changed_;
1391  int i, j;
1392  Section *sec, *psec, *ch;
1393  float x, y, z, dz, x1, y1;
1394  float nch, ich = 0.0, angle;
1395  double arc, len;
1396  if (changed_ == nrn_shape_changed_ && !diam_changed && !tree_changed) {
1397  return;
1398  }
1399  recalc_diam();
1400  dz = 100.;
1401  for (i = 0; i < section_count; ++i) {
1402  sec = secorder[i];
1404  if ((psec = sec->parentsec) == (Section*) 0) {
1405  /* section has no parent */
1406  x = 0;
1407  y = 0;
1408  z = i * dz;
1409  x1 = 1;
1410  y1 = 0;
1411  } else {
1412  double arc1 = arc;
1413  x1 = psec->pt3d[psec->npt3d - 1].x - psec->pt3d[0].x;
1414  y1 = psec->pt3d[psec->npt3d - 1].y - psec->pt3d[0].y;
1415  if (!arc0at0(psec)) {
1416  arc1 = 1. - arc;
1417  }
1418  if (arc1 < .5) {
1419  x1 = -x1;
1420  y1 = -y1;
1421  }
1422  x = psec->pt3d[psec->npt3d - 1].x * arc1 + psec->pt3d[0].x * (1 - arc1);
1423  y = psec->pt3d[psec->npt3d - 1].y * arc1 + psec->pt3d[0].y * (1 - arc1);
1424  z = psec->pt3d[psec->npt3d - 1].z * arc1 + psec->pt3d[0].z * (1 - arc1);
1425  }
1426  if (sec->npt3d) {
1427  if (psec) {
1428  nrn_translate_shape(sec, x, y, z);
1429  }
1430  continue;
1431  }
1432  if (fabs(y1) < 1e-6 && fabs(x1) < 1e-6) {
1433  Printf("nrn_define_shape: %s first and last 3-d point at same (x,y)\n", secname(psec));
1434  angle = 0.;
1435  } else {
1436  angle = atan2(y1, x1);
1437  }
1438  if (arc > 0. && arc < 1.) {
1439  angle += 3.14159265358979323846 / 2.;
1440  }
1441  nch = 0.;
1442  if (psec)
1443  for (ch = psec->child; ch; ch = ch->sibling) {
1444  if (ch == sec) {
1445  ich = nch;
1446  }
1447  if (arc == nrn_connection_position(ch)) {
1448  ++nch;
1449  }
1450  }
1451  if (nch > 1) {
1452  angle += ich / (nch - 1.) * .8 - .4;
1453  }
1454  len = section_length(sec);
1455  x1 = x + len * cos(angle);
1456  y1 = y + len * sin(angle);
1457  stor_pt3d(sec, x, y, z, nrn_diameter(sec->pnode[0]));
1458  for (j = 0; j < sec->nnode - 1; ++j) {
1459  double frac = ((double) j + .5) / (double) (sec->nnode - 1);
1460  stor_pt3d(sec,
1461  x * (1 - frac) + x1 * frac,
1462  y * (1 - frac) + y1 * frac,
1463  z,
1464  nrn_diameter(sec->pnode[j]));
1465  }
1466  stor_pt3d(sec, x1, y1, z, nrn_diameter(sec->pnode[sec->nnode - 2]));
1467  /* don't let above change length due to round-off errors*/
1468  sec->pt3d[sec->npt3d - 1].arc = len;
1469  sec->prop->dparam[2] = len;
1470  }
1471  changed_ = nrn_shape_changed_;
1472 }
1473 
1474 static double diam_from_list(Section* sec, int inode, Prop* p, double rparent)
1475 /* p->param[0] is diam of inode in sec.*/
1476 /* rparent right half resistance of the parent segment*/
1477 {
1478  /* Basic algorithm assumes a set of monotonic points on which a
1479  function is defined. The extension is the piecewise continuous
1480  function. y(x) for 0<=x<=x[n] (extrapolate for x>x[n]).
1481  The problem is to form the integral of f(y(s)) over intervals
1482  s[i] <= s <= s[i+1]. Note the intervals don't have to be equal.
1483  This implementation is specific to a call to this function for
1484  each interval in order.
1485  */
1486  /* computes diam as average, area, and ri. Slightly weirder since
1487  interval spit in two to compute right and left half values of ri
1488  */
1489  /* fills NODEAREA and NODERINV and returns the right half resistance
1490  (MOhms) of the segment.
1491  */
1492  static int j;
1493  static double x1, y1, ds;
1494  int ihalf;
1495  double si, sip;
1496  double diam, delta, temp, ri, area, ra, rleft = 0.0;
1497  int npt, nspine;
1498 
1499  if (inode == 0) {
1500  j = 0;
1501  si = 0.;
1502  x1 = sec->pt3d[j].arc;
1503  y1 = fabs(sec->pt3d[j].d);
1504  ds = sec->pt3d[sec->npt3d - 1].arc / ((double) (sec->nnode - 1));
1505  }
1506  si = (double) inode * ds;
1507  npt = sec->npt3d;
1508  diam = 0.;
1509  area = 0.;
1510  nspine = 0;
1511  ra = nrn_ra(sec);
1512  for (ihalf = 0; ihalf < 2; ihalf++) {
1513  ri = 0.;
1514  sip = si + ds / 2.;
1515  for (;;) {
1516  int jp, jnext;
1517  double x2, y2, xj, xjp;
1518  jp = j + 1;
1519  xj = sec->pt3d[j].arc;
1520 #if NTS_SPINE
1521  if (sec->pt3d[j].d < 0 && xj >= si && xj < sip) {
1522  nspine++;
1523  }
1524 #endif
1525  xjp = sec->pt3d[jp].arc;
1526  if (xjp > sip || jp == npt - 1) {
1527  double frac;
1528  if (fabs(xjp - xj) < 1e-10) {
1529  frac = 1;
1530  } else {
1531  frac = (sip - xj) / (xjp - xj);
1532  }
1533  x2 = sip;
1534  y2 = (1. - frac) * fabs(sec->pt3d[j].d) + frac * fabs(sec->pt3d[jp].d);
1535  jnext = j;
1536  } else {
1537  x2 = xjp;
1538  y2 = fabs(sec->pt3d[jp].d);
1539  jnext = jp;
1540  }
1541 
1542  /* f += integrate(x1,y1, x2, y2) */
1543  delta = (x2 - x1);
1544  diam += (y2 + y1) * delta;
1545  if (delta < 1e-15) {
1546  delta = 1e-15;
1547  }
1548  if ((temp = y2 * y1 / delta) == 0) {
1549  temp = 1e-15;
1550  }
1551  ri += 1 / temp;
1552 #if 1 /*taper is very seldom taken into account*/
1553  temp = .5 * (y2 - y1);
1554  temp = sqrt(delta * delta + temp * temp);
1555 #else
1556  temp = delta;
1557 #endif
1558 
1559  area += (y2 + y1) * temp;
1560  /* end of integration */
1561 
1562  x1 = x2;
1563  y1 = y2;
1564  if (j == jnext) {
1565  break;
1566  }
1567  j = jnext;
1568  }
1569  if (ihalf == 0) {
1570  rleft = ri * ra / PI * (4. * .01); /*left seg resistance*/
1571  } else {
1572  ri = ri * ra / PI * (4. * .01); /* MegOhms */
1573  /* above is right half segment resistance */
1574  }
1575  si = sip;
1576  }
1577  /* answer for inode is here */
1578  NODERINV(sec->pnode[inode]) = 1. / (rparent + rleft);
1579  diam *= .5 / ds;
1580  if (fabs(diam - p->param(0)) > 1e-9 || diam < 1e-5) {
1581  p->param(0) = diam; /* microns */
1582  }
1583  sec->pnode[inode]->area() = area * .5 * PI; /* microns^2 */
1584 #if NTS_SPINE
1585  /* if last point has a spine then increment spine count for last node */
1586  if (inode == sec->nnode - 2 && sec->pt3d[npt - 1].d < 0.) {
1587  nspine += 1;
1588  }
1589  sec->pnode[inode]->area() = sec->pnode[inode]->area() + nspine * spinearea;
1590 #endif
1591  return ri;
1592 }
1593 
1594 #endif /*DIAMLIST*/
1595 
1596 void v_setup_vectors(void) {
1597  int inode, i;
1598 
1599  if (tree_changed) {
1600  setup_topology(); /* now classical secorder */
1601  v_structure_change = 1;
1603  }
1604  /* get rid of the old */
1605  if (!v_structure_change) {
1606  return;
1607  }
1608 
1609  nrn_threads_free();
1610 
1611  for (i = 0; i < n_memb_func; ++i) {
1612  if (nrn_is_artificial_[i] && memb_func[i].has_initialize()) {
1613  if (memb_list[i].nodecount) {
1614  memb_list[i].nodes_free();
1615  if (!memb_func[i].hoc_mech) {
1616  free(memb_list[i].pdata);
1617  }
1618  }
1619  }
1620  }
1621 
1622 #if 1 /* see finitialize */
1623  /* and allocate for the artificial cells */
1624  for (i = 0; i < n_memb_func; ++i) {
1625  if (nrn_is_artificial_[i] && memb_func[i].has_initialize()) {
1626  int node_count = nrn_pnt_template_[i]->count;
1627  bool alloc_pdata = !memb_func[i].hoc_mech;
1628  memb_list[i].nodes_alloc(node_count, alloc_pdata);
1629  memb_list[i].nodecount = 0; /* counted again below. TODO: Why? */
1630  }
1631  }
1632 #endif
1633 
1634 #if MULTICORE
1635  if (!nrn_user_partition()) {
1636  /* does not depend on memb_list */
1637  int ith, j;
1638  NrnThread* _nt;
1639  section_order(); /* could be already reordered */
1640  /* round robin distribution */
1641  for (ith = 0; ith < nrn_nthread; ++ith) {
1642  _nt = nrn_threads + ith;
1643  _nt->roots = hoc_l_newlist();
1644  _nt->ncell = 0;
1645  }
1646  j = 0;
1647  for (ith = 0; ith < nrn_nthread; ++ith) {
1648  _nt = nrn_threads + ith;
1649  for (i = ith; i < nrn_global_ncell; i += nrn_nthread) {
1650  hoc_l_lappendsec(_nt->roots, secorder[j]);
1651  ++_nt->ncell;
1652  ++j;
1653  }
1654  }
1655  }
1656  /* reorder. also fill NrnThread node indices, v_node, and v_parent */
1657  reorder_secorder();
1658 #endif
1659 
1661  for (inode = 0; inode < _nt->end; inode++) {
1662  if (_nt->_v_parent[inode] != NULL) {
1663  _nt->_v_parent_index[inode] = _nt->_v_parent[inode]->v_node_index;
1664  }
1665  }
1666  }
1667 
1669 
1670  /* fill in artificial cell info */
1671  for (i = 0; i < n_memb_func; ++i) {
1672  if (nrn_is_artificial_[i] && memb_func[i].has_initialize()) {
1673  hoc_Item* q;
1674  cTemplate* tmp = nrn_pnt_template_[i];
1675  memb_list[i].nodecount = tmp->count;
1676  int nti{}, j{};
1677  hoc_List* list = tmp->olist;
1678  std::vector<std::size_t> thread_counts(nrn_nthread);
1679  ITERATE(q, list) {
1680  Object* obj = OBJ(q);
1681  auto* pnt = static_cast<Point_process*>(obj->u.this_pointer);
1682  memb_list[i].nodelist[j] = nullptr;
1683  /* for now, round robin all the artificial cells */
1684  /* but put the non-threadsafe ones in thread 0 */
1685  /*
1686  Note that artficial cells are assumed not to need a thread specific
1687  Memb_list. But this implies that they do not have thread specific
1688  data. For this reason, for now, an otherwise thread-safe artificial
1689  cell model is declared by nmodl as thread-unsafe.
1690  */
1691  if (memb_func[i].vectorized == 0) {
1692  pnt->_vnt = nrn_threads;
1693  } else {
1694  pnt->_vnt = nrn_threads + nti;
1695  nti = (nti + 1) % nrn_nthread;
1696  }
1697  auto const tid = static_cast<NrnThread*>(pnt->_vnt)->id;
1698  ++thread_counts[tid];
1699  // pnt->_i_instance = j;
1700  ++j;
1701  }
1702  assert(j == memb_list[i].nodecount);
1703  // The following is a transition measure while data are SOA-backed
1704  // using the new neuron::container::soa scheme but pdata are not.
1705  // data get permuted so that artificial cells are blocked according
1706  // to the NrnThread they are assigned to, but without this change
1707  // then the pdata order encoded in the global non-thread-specific
1708  // memb_list[i] structure was different, with threads interleaved.
1709  // This was a problem when we wanted to e.g. run the initialisation
1710  // kernels in finitialize using that global structure, as the i-th
1711  // rows of data and pdata did not refer to the same mechanism
1712  // instance. The temporary solution here is to manually organise
1713  // pdata to match the data order, with all the instances associated
1714  // with thread 0 followed by all the instances associated with
1715  // thread 1, and so on. See CellGroup::mk_tml_with_art for another
1716  // side of this story and why it is useful to have artificial cell
1717  // data blocked by thread.
1718  std::vector<std::size_t> thread_offsets(nrn_nthread);
1719  for (auto j = 1; j < nrn_nthread; ++j) {
1720  thread_offsets[j] = std::exchange(thread_counts[j - 1], 0) + thread_offsets[j - 1];
1721  }
1722  thread_counts[nrn_nthread - 1] = 0;
1723  ITERATE(q, list) {
1724  auto* const pnt = static_cast<Point_process*>(OBJ(q)->u.this_pointer);
1725  auto const tid = static_cast<NrnThread*>(pnt->_vnt)->id;
1726  memb_list[i].pdata[thread_offsets[tid] + thread_counts[tid]++] = pnt->prop->dparam;
1727  }
1728  }
1729  }
1731  // The assumption here is that one can arbitrarily permute the
1732  // NrnThread node order (within the constraint that
1733  // parent index < node index). A NrnThread node order permutation
1734  // involves modifying NrnThread fields: _v_node, _v_parent, v_parent_index,
1735  // and Node.v_node_index.
1736  // Additionally, there appears to be a Memb_list node_indices ordering
1737  // presumption embodied in nrn_sort_mech_data. I.e. Preexisting Memb_list node
1738  // order is the order that results by iterating over Nrnthread nodes
1739  // asserted by ml->nodelist[nt_mech_count] == nd.
1740  // We <satisfy?> this by monotonically ordering the Memb_list with
1741  // sort_ml(Memb_list*) but there is a question whether the sort preserves
1742  // the order when there are many POINT_PROCESS instances in the same node.
1744 
1745  v_structure_change = 0;
1746  nrn_update_ps2nt();
1750  diam_changed = 1;
1751 
1752 #if 0
1753  for (int tid = 0; tid < nrn_nthread; ++tid) {
1754  printf("nrnthread %d node info\n", tid);
1755  auto& nt = nrn_threads[tid];
1756  for (int i = 0; i < nt.end; ++i) {
1757  printf(
1758  " _v_node[%2d]->v_node_index=%2d"
1759  " _v_parent[%2d]->v_node_index=%2d v=%g\n",
1760  i,
1761  nt._v_node[i]->v_node_index,
1762  i,
1763  nt._v_parent[i] ? nt._v_parent[i]->v_node_index : -1,
1764  (*nt._v_node[i]).v());
1765  }
1766  }
1767 #endif // 0
1768 }
1769 
1770 
1773  if (nt->_sp13_rhs) {
1774  free(std::exchange(nt->_sp13_rhs, nullptr));
1775  }
1776  if (nt->_sp13mat) {
1777  spDestroy(nt->_sp13mat);
1778  nt->_sp13mat = (char*) 0;
1779  }
1780  }
1781  diam_changed = 1;
1782 }
1783 
1784 /* 0 means no model, 1 means ODE, 2 means DAE */
1785 int nrn_modeltype(void) {
1786  int type;
1787  v_setup_vectors();
1788 
1789  if (!nrndae_list_is_empty()) {
1790  return 2;
1791  }
1792 
1793  type = 0;
1794  if (nrn_global_ncell > 0) {
1795  type = 1;
1796  for (const NrnThread* nt: for_threads(nrn_threads, nrn_nthread))
1797  if (nt->_ecell_memb_list) {
1798  type = 2;
1799  }
1800  }
1801  if (type == 0 && nrn_nonvint_block_ode_count(0, 0)) {
1802  type = 1;
1803  }
1804  return type;
1805 }
1806 
1807 /*
1808 minimally update flags consistent with the model type and cvode_active_
1809 return true if any flags changed.
1810 If DAE then variable step method requires daspk
1811 If DAE then must be sparse13
1812 If daspk then must be sparse13
1813 If cvode then must be classic tree method
1814 */
1815 
1817  int consist;
1818  int type;
1819  consist = 0;
1820  type = nrn_modeltype();
1821  if (cvode_active_) {
1822  if (type == 2) {
1823  if (nrn_use_daspk_ == 0) {
1824  nrn_use_daspk(1);
1825  consist = 1;
1826  }
1827  }
1828  if (nrn_use_daspk_ != use_sparse13) {
1830  consist = 1;
1831  }
1832  } else if (type == 2 && use_sparse13 == 0) {
1833  use_sparse13 = 1;
1834  consist = 1;
1835  }
1836  return consist;
1837 }
1838 
1839 
1840 /*
1841 sparse13 uses the mathematical index scheme in which the rows and columns
1842 range from 1...size instead of 0...size-1. Thus the calls to
1843 spGetElement(i,j) have args that are 1 greater than a normal c style.
1844 Also the actual_rhs_ uses this style, 1-neqn, when sparse13 is activated
1845 and therefore is passed to spSolve as actual_rhs intead of actual_rhs-1.
1846 */
1847 
1848 static void nrn_matrix_node_alloc(void) {
1849  int i;
1850 
1852  NrnThread* nt = nrn_threads;
1853  /*printf("use_sparse13=%d sp13mat=%lx rhs=%lx\n", use_sparse13, (long)nt->_sp13mat,
1854  * (long)nt->_actual_rhs);*/
1855  if (use_sparse13) {
1856  if (nt->_sp13mat) {
1857  return;
1858  } else {
1860  }
1861  } else {
1862  if (nt->_sp13mat) {
1863  v_structure_change = 1;
1864  v_setup_vectors();
1865  return;
1866  } else {
1867  // used to return here if the cache-efficient structures for a/b/... were non-null
1868  }
1869  }
1870  ++nrn_matrix_cnt_;
1871  if (use_sparse13) {
1872  int in, err, extn, neqn, j;
1873  nt = nrn_threads;
1874  neqn = nt->end + nrndae_extra_eqn_count();
1875  extn = 0;
1876  if (nt->_ecell_memb_list) {
1877  extn = nt->_ecell_memb_list->nodecount * nlayer;
1878  }
1879  /*printf(" %d extracellular nodes\n", extn);*/
1880  neqn += extn;
1881  nt->_sp13_rhs = (double*) ecalloc(neqn + 1, sizeof(double));
1882  nt->_sp13mat = spCreate(neqn, 0, &err);
1883  if (err != spOKAY) {
1884  hoc_execerror("Couldn't create sparse matrix", (char*) 0);
1885  }
1886  for (in = 0, i = 1; in < nt->end; ++in, ++i) {
1887  nt->_v_node[in]->eqn_index_ = i;
1888  if (nt->_v_node[in]->extnode) {
1889  i += nlayer;
1890  }
1891  }
1892  for (in = 0; in < nt->end; ++in) {
1893  int ie, k;
1894  Node *nd, *pnd;
1895  Extnode* nde;
1896  nd = nt->_v_node[in];
1897  nde = nd->extnode;
1898  pnd = nt->_v_parent[in];
1899  i = nd->eqn_index_;
1900  nt->_sp13_rhs[i] = nt->actual_rhs(in);
1901  nd->_d_matelm = spGetElement(nt->_sp13mat, i, i);
1902  if (nde) {
1903  for (ie = 0; ie < nlayer; ++ie) {
1904  k = i + ie + 1;
1905  nde->_d[ie] = spGetElement(nt->_sp13mat, k, k);
1906  nde->_rhs[ie] = nt->_sp13_rhs + k;
1907  nde->_x21[ie] = spGetElement(nt->_sp13mat, k, k - 1);
1908  nde->_x12[ie] = spGetElement(nt->_sp13mat, k - 1, k);
1909  }
1910  }
1911  if (pnd) {
1912  j = pnd->eqn_index_;
1913  nd->_a_matelm = spGetElement(nt->_sp13mat, j, i);
1914  nd->_b_matelm = spGetElement(nt->_sp13mat, i, j);
1915  if (nde && pnd->extnode)
1916  for (ie = 0; ie < nlayer; ++ie) {
1917  int kp = j + ie + 1;
1918  k = i + ie + 1;
1919  nde->_a_matelm[ie] = spGetElement(nt->_sp13mat, kp, k);
1920  nde->_b_matelm[ie] = spGetElement(nt->_sp13mat, k, kp);
1921  }
1922  } else { /* not needed if index starts at 1 */
1923  nd->_a_matelm = nullptr;
1924  nd->_b_matelm = nullptr;
1925  }
1926  }
1927  nrndae_alloc();
1928  } else {
1932  }
1933  }
1934 }
1935 
1936 /** @brief Sort the underlying storage for a particular mechanism.
1937  *
1938  * After model building is complete the storage vectors backing all Mechanism
1939  * instances can be permuted to ensure that preconditions are met for the
1940  * computations performed while time-stepping.
1941  *
1942  * This method ensures that the Mechanism data is ready for this compute phase.
1943  * It is guaranteed to remain "ready" until the returned tokens are destroyed.
1944  */
1947  neuron::cache::Model& cache,
1949  // Do the actual sorting here. For now the algorithm is just to ensure that
1950  // the mechanism instances are partitioned by NrnThread.
1951  auto const type = mech_data.type();
1952  // Some special types are not "really" mechanisms and don't need to be
1953  // sorted
1954  if (type != MORPHOLOGY) {
1955  std::size_t const mech_data_size{mech_data.size()};
1956  std::vector<short> pdata_fields_to_cache{};
1958  [mech_data_size,
1959  &pdata_fields_to_cache,
1960  &pdata_hack = cache.mechanism.at(type).pdata_hack](
1961  auto field) {
1962  if (field >= pdata_hack.size()) {
1963  // we get called with the largest field first
1964  pdata_hack.resize(field + 1);
1965  }
1966  pdata_hack.at(field).reserve(mech_data_size);
1967  pdata_fields_to_cache.push_back(field);
1968  });
1969  std::size_t global_i{}, trivial_counter{};
1970  std::vector<std::size_t> mech_data_permutation(mech_data_size,
1971  std::numeric_limits<std::size_t>::max());
1973  // the Memb_list for this mechanism in this thread, this might be
1974  // null if there are no entries, or if it's an artificial cell type(?)
1975  auto* const ml = nt->_ml_list[type];
1976  if (ml) {
1977  // Tell the Memb_list what global offset its values start at
1978  ml->set_storage_offset(global_i);
1979  }
1980  // Record where in the global storage this NrnThread's instances of
1981  // the mechanism start
1982  cache.thread.at(nt->id).mechanism_offset.at(type) = global_i;
1983  // Count how many times we see this mechanism in this NrnThread
1984  auto nt_mech_count = 0;
1985  // Loop through the Nodes in this NrnThread
1986  for (auto i = 0; i < nt->end; ++i) {
1987  auto* const nd = nt->_v_node[i];
1988  // See if this Node has a mechanism of this type
1989  for (Prop* p = nd->prop; p; p = p->next) {
1990  if (p->_type != type) {
1991  continue;
1992  }
1993  // this condition comes from thread_memblist_setup(...)
1994  if (!memb_func[type].current && !memb_func[type].state &&
1995  !memb_func[type].has_initialize()) {
1996  continue;
1997  }
1998  // OK, p is an instance of the mechanism we're currently
1999  // considering.
2000  auto const current_global_row = p->id().current_row();
2001  trivial_counter += (current_global_row == global_i);
2002  mech_data_permutation.at(current_global_row) = global_i++;
2003  for (auto const field: pdata_fields_to_cache) {
2004  cache.mechanism.at(type).pdata_hack.at(field).push_back(p->dparam + field);
2005  }
2006  // Checks
2007  assert(ml->nodelist[nt_mech_count] == nd);
2008  assert(ml->nodeindices[nt_mech_count] == nd->v_node_index);
2009  ++nt_mech_count;
2010  }
2011  }
2012  assert(!ml || ml->nodecount == nt_mech_count);
2013  // Look for any artificial cells attached to this NrnThread
2014  if (nrn_is_artificial_[type]) {
2016  hoc_Item* q;
2017  ITERATE(q, tmp->olist) {
2018  Object* obj = OBJ(q);
2019  auto* pnt = static_cast<Point_process*>(obj->u.this_pointer);
2020  assert(pnt->prop->_type == type);
2021  if (nt == pnt->_vnt) {
2022  auto const current_global_row = pnt->prop->id().current_row();
2023  trivial_counter += (current_global_row == global_i);
2024  mech_data_permutation.at(current_global_row) = global_i++;
2025  for (auto const field: pdata_fields_to_cache) {
2026  cache.mechanism.at(type).pdata_hack.at(field).push_back(
2027  pnt->prop->dparam + field);
2028  }
2029  }
2030  }
2031  }
2032  }
2033  if (global_i != mech_data_size) {
2034  // This means that we did not "positively" find all the instances of
2035  // this mechanism by traversing the model structure. This can happen
2036  // if HOC (or probably Python) scripts create instances and then do
2037  // not attach them anywhere, or do not explicitly destroy
2038  // interpreter variables that are preventing reference counts from
2039  // reaching zero. In this case we can figure out which the missing
2040  // entries are and permute them to the end of the vector.
2041  auto missing_elements = mech_data_size - global_i;
2042  // There are `missing_elements` integers from the range [0 ..
2043  // mech_data_size-1] whose values in `mech_data_permutation` are
2044  // still std::numeric_limits<std::size_t>::max().
2045  for (auto global_row = 0ul; global_row < mech_data_size; ++global_row) {
2046  if (mech_data_permutation[global_row] == std::numeric_limits<std::size_t>::max()) {
2047  trivial_counter += (global_row == global_i);
2048  mech_data_permutation[global_row] = global_i++;
2049  --missing_elements;
2050  if (missing_elements == 0) {
2051  break;
2052  }
2053  }
2054  }
2055  if (global_i != mech_data_size) {
2056  std::ostringstream oss;
2057  oss << "(global_i = " << global_i << ") != (mech_data_size = " << mech_data_size
2058  << ") for " << mech_data.name();
2059  throw std::runtime_error(oss.str());
2060  }
2061  }
2062  assert(trivial_counter <= mech_data_size);
2063  if (trivial_counter < mech_data_size) {
2064  // The `mech_data_permutation` vector is not a unit transformation
2065  mech_data.apply_reverse_permutation(std::move(mech_data_permutation), sorted_token);
2066  }
2067  }
2068  // Make sure that everything ends up flagged as sorted, even morphologies,
2069  // mechanism types with no instances, and cases where the permutation
2070  // vector calculated was found to be trivial.
2071  mech_data.mark_as_sorted(sorted_token);
2072 }
2073 
2076  // Generate some temporary "flattened" vectors from pdata
2077  // For example, when a mechanism uses an ion then one of its pdata fields holds Datum
2078  // (=generic_data_handle) objects that wrap data_handles to ion (RANGE) variables.
2079  // Dereferencing those fields to access the relevant double values can be indirect and
2080  // expensive, so here we can generate a std::vector<double*> that can be used directly in
2081  // hot loops. This is partitioned for the threads in the same way as the other data.
2082  // Note that this needs to come after *all* of the mechanism types' data have been permuted, not
2083  // just the type that we are filling the cache for.
2084  // TODO could identify the case that the pointers are all monotonically increasing and optimise
2085  // further?
2086  auto const type = mech_data.type();
2087  // Some special types are not "really" mechanisms and don't need to be
2088  // sorted
2089  if (type != MORPHOLOGY) {
2090  auto& mech_cache = cache.mechanism.at(type);
2091  // Transform the vector<Datum> in pdata_hack into vector<double*> in pdata
2092  std::transform(mech_cache.pdata_hack.begin(),
2093  mech_cache.pdata_hack.end(),
2094  std::back_inserter(mech_cache.pdata),
2095  [](std::vector<Datum*>& pdata_hack) {
2096  std::vector<double*> tmp{};
2097  std::transform(pdata_hack.begin(),
2098  pdata_hack.end(),
2099  std::back_inserter(tmp),
2100  [](Datum* datum) { return datum->get<double*>(); });
2101  pdata_hack.clear();
2102  pdata_hack.shrink_to_fit();
2103  return tmp;
2104  });
2105  mech_cache.pdata_hack.clear();
2106  // Create a flat list of pointers we can use inside generated code
2107  std::transform(mech_cache.pdata.begin(),
2108  mech_cache.pdata.end(),
2109  std::back_inserter(mech_cache.pdata_ptr_cache),
2110  [](auto const& pdata) { return pdata.empty() ? nullptr : pdata.data(); });
2111  }
2112 }
2113 
2114 /** @brief Sort the underlying storage for Nodes.
2115  *
2116  * After model building is complete the storage vectors backing all Node
2117  * objects can be permuted to ensure that preconditions are met for the
2118  * computations performed while time-stepping.
2119  *
2120  * This method ensures that the Node data is ready for this compute phase.
2121  */
2123  neuron::cache::Model& cache) {
2124  // Make sure the voltage storage follows the order encoded in _v_node.
2125  // Generate the permutation vector to update the underlying storage for
2126  // Nodes. This must come after nrn_multisplit_setup_, which can change the
2127  // Node order.
2128  auto& node_data = neuron::model().node_data();
2129  std::size_t const node_data_size{node_data.size()};
2130  std::size_t global_i{};
2131  std::vector<std::size_t> node_data_permutation(node_data_size,
2132  std::numeric_limits<std::size_t>::max());
2133  // Process threads one at a time -- this means that the data for each
2134  // NrnThread will be contiguous.
2136  // What offset in the global node data structure do the values for this thread
2137  // start at
2138  nt->_node_data_offset = global_i;
2139  cache.thread.at(nt - nrn_threads).node_data_offset = global_i;
2140  for (int i = 0; i < nt->end; ++i, ++global_i) {
2141  Node* nd = nt->_v_node[i];
2142  auto const current_node_row = nd->_node_handle.current_row();
2143  assert(current_node_row < node_data_size);
2144  assert(global_i < node_data_size);
2145  node_data_permutation.at(current_node_row) = global_i;
2146  }
2147  }
2148  if (global_i != node_data_size) {
2149  // This means that we did not "positively" find all the Nodes by traversing the NrnThread
2150  // objects. In this case we can figure out which the missing entries are and permute them to
2151  // the end of the global vectors.
2152  auto missing_elements = node_data_size - global_i;
2153  Printf(fmt::format("permuting {} 'lost' Nodes to the end\n", missing_elements).c_str());
2154  // There are `missing_elements` integers from the range [0 .. node_data_size-1] whose values
2155  // in `node_data_permutation` are still std::numeric_limits<std::size_t>::max().
2156  for (auto global_row = 0ul; global_row < node_data_size; ++global_row) {
2157  if (node_data_permutation[global_row] == std::numeric_limits<std::size_t>::max()) {
2158  node_data_permutation[global_row] = global_i++;
2159  --missing_elements;
2160  if (missing_elements == 0) {
2161  break;
2162  }
2163  }
2164  }
2165  if (global_i != node_data_size) {
2166  std::ostringstream oss;
2167  oss << "(global_i = " << global_i << ") != (node_data_size = " << node_data_size << ')';
2168  throw std::runtime_error(oss.str());
2169  }
2170  }
2171  // Apply the permutation, using `sorted_token` to authorise this operation
2172  // despite the container being frozen.
2173  node_data.apply_reverse_permutation(std::move(node_data_permutation), sorted_token);
2174 }
2175 
2176 /**
2177  * @brief Ensure neuron::container::* data are sorted.
2178  *
2179  * Set all of the containers to be in read-only mode, until the returned token
2180  * is destroyed. This method can be called from multi-threaded regions.
2181  */
2183  // Rather than a more complicated lower-level solution, just serialise all
2184  // calls to this method. The more complicated lower-level solution would
2185  // presumably involve a more fully-fledged std::shared_mutex-type model,
2186  // where the soa containers can be locked by many readers (clients that do
2187  // not do anything that invalidates pointers/caches) or a single writer
2188  // (who *is* authorised to perform those operations), with a deadlock
2189  // avoiding algorithm used to acquire those two types of lock for all the
2190  // different soa containers at once.
2191  static std::mutex s_mut{};
2192  std::unique_lock _{s_mut};
2193  // Two scenarii:
2194  // - model is already sorted, in which case we just assemble the return
2195  // value but don't mutate anything or do any real work
2196  // - something is not already sorted, and by extension the cache is not
2197  // valid.
2198  // In both cases, we want to start by acquiring tokens from all of the
2199  // data containers in the model. Once we have locked the whole model in
2200  // this way, we can trigger permuting the model (by loaning out the tokens
2201  // one by one) to mark it as sorted.
2202  auto& model = neuron::model();
2203  auto& node_data = model.node_data();
2204  // Get tokens for the whole model
2205  auto node_token = node_data.issue_frozen_token();
2206  auto already_sorted = node_data.is_sorted();
2207  // How big does an array have to be to be indexed by mechanism type?
2208  auto const mech_storage_size = model.mechanism_storage_size();
2209  std::vector<neuron::container::Mechanism::storage::frozen_token_type> mech_tokens{};
2210  mech_tokens.reserve(mech_storage_size);
2211  model.apply_to_mechanisms([&already_sorted, &mech_tokens](auto& mech_data) {
2212  mech_tokens.push_back(mech_data.issue_frozen_token());
2213  already_sorted = already_sorted && mech_data.is_sorted();
2214  });
2215  // Now the whole model is marked frozen/read-only, but it may or may not be
2216  // marked sorted (if it is, the cache should be valid, otherwise it should
2217  // not be).
2218  if (already_sorted) {
2219  // If everything was already sorted, the cache should already be valid.
2221  // There isn't any more work to be done, really.
2222  } else {
2223  // Some part of the model data is not already marked sorted. In this
2224  // case we expect that the cache is *not* valid, because whatever
2225  // caused something to not be sorted should also have invalidated the
2226  // cache.
2228  // Build a new cache (*not* in situ, so it doesn't get invalidated
2229  // under our feet while we're in the middle of the job) and populate it
2230  // by calling the various methods that sort the model data.
2231  neuron::cache::Model cache{};
2232  cache.thread.resize(nrn_nthread);
2233  for (auto& thread_cache: cache.thread) {
2234  thread_cache.mechanism_offset.resize(mech_storage_size);
2235  }
2236  cache.mechanism.resize(mech_storage_size);
2237  // The cache is initialised enough to be populated by the various data
2238  // sorting algorithms. The small "problem" here is that all of the
2239  // model data structures are already marked as frozen via the tokens
2240  // that we acquired above. The way around this is to transfer those
2241  // tokens back to the relevant containers, so they can check that the
2242  // only active token is the one that has been provided back to them. In
2243  // other words, any token is a "read lock" but a non-const token that
2244  // refers to a container with token reference count of exactly one has
2245  // an elevated "write lock" status.
2246  nrn_sort_node_data(node_token, cache);
2247  assert(node_data.is_sorted());
2248  // TODO: maybe we should separate out cache population from sorting.
2249  std::size_t n{};
2250  model.apply_to_mechanisms([&cache, &n, &mech_tokens](auto& mech_data) {
2251  // TODO do we need to pass `node_token` to `nrn_sort_mech_data`?
2252  nrn_sort_mech_data(mech_tokens[n], cache, mech_data);
2253  assert(mech_data.is_sorted());
2254  ++n;
2255  });
2256  // Now that all the mechanism data is sorted we can fill in pdata caches
2257  model.apply_to_mechanisms(
2258  [&cache](auto& mech_data) { nrn_fill_mech_data_caches(cache, mech_data); });
2259  // Move our working cache into the global storage.
2261  }
2262  // Move our tokens into the return value and be done with it.
2264  ret.mech_data_tokens = std::move(mech_tokens);
2265  return ret;
2266 }
Section * chk_access()
Definition: cabcode.cpp:449
const char * secname(Section *sec)
name of section (for use in error messages)
Definition: cabcode.cpp:1674
Node * node_ptr(Section *sec, double x, double *parea)
Definition: cabcode.cpp:1828
void setup_topology(void)
Definition: cabcode.cpp:1635
double nrn_ra(Section *sec)
Definition: cabcode.cpp:417
double section_length(Section *sec)
Definition: cabcode.cpp:401
int node_index(Section *sec, double x)
returns nearest index to x
Definition: cabcode.cpp:1406
double nrn_connection_position(Section *sec)
Definition: cabcode.cpp:1552
int arc0at0(Section *sec)
Definition: cabcode.cpp:413
int tree_changed
Definition: cabcode.cpp:51
double nrn_diameter(Node *nd)
Definition: cabcode.cpp:444
void mech_insert1(Section *sec, int type)
Definition: cabcode.cpp:852
void activclamp_lhs(void)
Definition: clamp.cpp:190
void activclamp_rhs(void)
Definition: clamp.cpp:172
static Node * pnd
Definition: clamp.cpp:33
void clamp_prepare()
Definition: clamp.cpp:154
#define sec
Definition: md1redef.h:20
#define nodecount
Definition: md1redef.h:39
#define i
Definition: md1redef.h:19
#define pdata
Definition: md1redef.h:37
#define prop
Definition: md1redef.h:38
void nrn_prop_datum_free(int type, Datum *ppd)
Definition: cxprop.cpp:54
void ext_con_coef(void)
Definition: extcelln.cpp:483
void nrn_rhs_ext(NrnThread *_nt)
Definition: extcelln.cpp:340
void nrn_setup_ext(NrnThread *_nt)
Definition: extcelln.cpp:417
int nrn_errno_check(int i)
Definition: fadvance.cpp:767
void activstim_rhs(void)
Definition: fstim.cpp:162
void stim_prepare(void)
Definition: fstim.cpp:154
static RNG::key_type k
Definition: nrnran123.cpp:9
int hoc_is_object_arg(int narg)
Definition: code.cpp:876
void hoc_retpushx(double x)
Definition: hocusr.cpp:154
IvocVect * vector_arg(int i)
Definition: ivocvect.cpp:265
double * hoc_pgetarg(int narg)
Definition: oc_ansi.h:253
int hoc_is_pdouble_arg(int narg)
Definition: code.cpp:868
void hoc_obj_unref(Object *obj)
Definition: hoc_oop.cpp:1881
#define assert(ex)
Definition: hocassrt.h:24
#define getarg
Definition: hocdec.h:17
#define OBJ(q)
Definition: hoclist.h:88
hoc_List * hoc_l_newlist()
hoc_Item * hoc_l_lappendsec(hoc_List *, struct Section *)
constexpr auto range_sec(hoc_List *iterable)
Definition: hoclist.h:50
Prop * nrn_point_prop_
Definition: point.cpp:29
char * pnt_map
Definition: init.cpp:150
void notify_freed_val_array(double *p, size_t size)
Definition: ivoc.cpp:152
void long_difus_solve(neuron::model_sorted_token const &sorted_token, int method, NrnThread &nt)
Definition: ldifus.cpp:86
#define neqn
Definition: lineq.h:3
#define CAP
Definition: membfunc.hpp:60
#define MORPHOLOGY
Definition: membfunc.hpp:59
#define CABLESECTION
Definition: membfunc.hpp:58
#define BEFORE_BREAKPOINT
Definition: membfunc.hpp:69
#define ITERATE(itm, lst)
Definition: model.h:18
sqrt
Definition: extdef.h:3
printf
Definition: extdef.h:5
sin
Definition: extdef.h:3
atan2
Definition: extdef.h:4
cos
Definition: extdef.h:3
fabs
Definition: extdef.h:3
void move(Item *q1, Item *q2, Item *q3)
Definition: list.cpp:200
auto for_threads(NrnThread *threads, int num_threads)
Definition: multicore.h:133
double * nrn_classicalNodeA(Node *nd)
double * nrn_classicalNodeB(Node *nd)
static void phase_end(const char *name)
static void phase_begin(const char *name)
phase
Reading phase number.
Definition: nrn_setup.hpp:53
NrnThread * nrn_threads
Definition: multicore.cpp:56
double * vector_vec(IvocVect *v)
Definition: ivocvect.cpp:19
static int np
Definition: mpispike.cpp:25
void hoc_malchk(void)
Definition: nrnoc_aux.cpp:83
int nrn_nthread
Definition: multicore.cpp:55
bool cvode_active_
Definition: netcvode.cpp:36
void hoc_execerror(const char *s1, const char *s2)
Definition: nrnoc_aux.cpp:39
void * ecalloc(size_t n, size_t size)
Definition: nrnoc_aux.cpp:85
void nrn_threads_free()
Definition: multicore.cpp:125
int vector_capacity(IvocVect *v)
Definition: ivocvect.cpp:16
void hoc_warning(const char *s1, const char *s2)
Definition: nrnoc_aux.cpp:44
void nrn_ba(NrnThread *nt, int bat)
int diam_changed
Definition: nrnoc_aux.cpp:21
void indices_to_cache(short type, Callable callable)
Call the given method with each dparam index that should be cached for a mechanism.
std::optional< Model > model
Definition: container.cpp:59
auto *const vec_d
Definition: cellorder.cpp:615
void nrn_permute_node_order()
Compute and carry out the permutation for interleave_permute_type.
Definition: cellorder.cpp:425
Model & model()
Access the global Model instance.
Definition: model_data.hpp:206
auto *const vec_b
Definition: cellorder.cpp:614
if(ncell==0)
Definition: cellorder.cpp:785
auto *const vec_rhs
Definition: cellorder.cpp:616
static char * mechname
Definition: nocpout.cpp:137
data_handle< T > transform(data_handle< T > handle, Transform type)
Definition: node.cpp:32
int nrndae_extra_eqn_count()
Definition: nrndae.cpp:26
#define nrn_nonvint_block_ode_count(offset, tid)
Definition: nonvintblock.h:55
#define nrn_nonvint_block_current(size, rhs, tid)
Definition: nonvintblock.h:43
#define nrn_nonvint_block_setup()
Definition: nonvintblock.h:37
#define nrn_nonvint_block_conductance(size, d, tid)
Definition: nonvintblock.h:48
void activsynapse_rhs(void)
Definition: synapse.cpp:206
void synapse_prepare(void)
Definition: synapse.cpp:198
void clear_point_process_struct(Prop *p)
Definition: point.cpp:347
void activsynapse_lhs(void)
Definition: synapse.cpp:215
void section_order(void)
Definition: solve.cpp:729
static Node * node(Object *)
Definition: netcvode.cpp:291
void nrn_use_daspk(int b)
Definition: netcvode.cpp:282
void nrn_update_ps2nt()
Definition: netcvode.cpp:4871
void nrndae_lhs()
Definition: nrndae.cpp:86
void nrndae_rhs(NrnThread *_nt)
Definition: nrndae.cpp:76
int nrndae_list_is_empty()
Definition: nrndae.cpp:13
void nrndae_alloc()
Definition: nrndae.cpp:42
int const size_t const size_t n
Definition: nrngsl.h:10
size_t q
size_t p
size_t j
int ifarg(int)
Definition: code.cpp:1607
std::vector< Memb_func > memb_func
Definition: init.cpp:145
short type
Definition: cabvars.h:10
void nrn_cap_jacob(neuron::model_sorted_token const &sorted_token, NrnThread *_nt, Memb_list *ml)
Definition: capac.cpp:39
hoc_List * section_list
Definition: init.cpp:113
std::vector< Memb_list > memb_list
Definition: init.cpp:146
int nrn_global_ncell
Definition: init.cpp:114
std::unordered_map< int, void(*)(Prop *)> nrn_mech_inst_destruct
Definition: init.cpp:167
int n_memb_func
Definition: init.cpp:448
void nrn_thread_memblist_setup()
Definition: multicore.cpp:629
void spDestroy(char *)
Definition: spalloc.cpp:533
void nrn_thread_error(const char *s)
Definition: multicore.cpp:297
int nrn_user_partition()
Definition: multicore.cpp:940
void reorder_secorder()
Definition: multicore.cpp:661
static List * current
Definition: nrnunit.cpp:13
void * hoc_Erealloc(void *ptr, std::size_t size)
Definition: memory.cpp:41
#define NODERINV(n)
Definition: section.h:102
#define NODEAREA(n)
Definition: section.h:101
#define NODEA(n)
Definition: section_fwd.hpp:52
#define NODEB(n)
Definition: section_fwd.hpp:53
#define NODERHS(n)
Definition: section_fwd.hpp:55
#define nlayer
Definition: section_fwd.hpp:31
int spGetSize(char *eMatrix, BOOLEAN External)
Definition: spalloc.cpp:638
char * spCreate(int Size, BOOLEAN Complex, int *pError)
Definition: spalloc.cpp:105
void spClear(char *eMatrix)
Definition: spbuild.cpp:82
#define NULL
Definition: spdefs.h:105
#define spREAL
Definition: spmatrix.h:106
#define spOKAY
Definition: spmatrix.h:86
double ** _a_matelm
Definition: section_fwd.hpp:45
double ** _b_matelm
Definition: section_fwd.hpp:46
double ** _x21
Definition: section_fwd.hpp:48
double ** _x12
Definition: section_fwd.hpp:47
double ** _d
Definition: section_fwd.hpp:43
double ** _rhs
Definition: section_fwd.hpp:44
int nodecount
Definition: nrnoc_ml.h:78
Definition: section.h:105
struct Node * _classical_parent
Definition: section.h:195
double * _d_matelm
Definition: section.h:186
int eqn_index_
Definition: section.h:187
double * _a_matelm
Definition: section.h:184
Extnode * extnode
Definition: section.h:199
neuron::container::Node::owning_handle _node_handle
Definition: section.h:109
auto & area()
Definition: section.h:120
double * _b_matelm
Definition: section.h:185
Prop * prop
Definition: section.h:190
Represent main neuron object computed by single thread.
Definition: multicore.h:58
int * _v_parent_index
Definition: multicore.h:89
double & actual_d(std::size_t row)
Definition: multicore.h:82
int ncell
Definition: multicore.h:64
double * node_a_storage()
Definition: multicore.cpp:1054
char * _sp13mat
Definition: multicore.h:94
int id
Definition: multicore.h:66
double & actual_rhs(std::size_t row)
Definition: multicore.h:85
double * node_rhs_storage()
Definition: multicore.cpp:1074
int end
Definition: multicore.h:65
Node ** _v_parent
Definition: multicore.h:91
hoc_List * roots
Definition: multicore.h:104
double * node_d_storage()
Definition: multicore.cpp:1069
Memb_list * _ecell_memb_list
Definition: multicore.h:95
double * _sp13_rhs
Definition: multicore.h:92
Node ** _v_node
Definition: multicore.h:90
double * node_voltage_storage()
Definition: multicore.cpp:1098
double * node_b_storage()
Definition: multicore.cpp:1064
struct NrnThreadMembList * next
Definition: multicore.h:34
Memb_list * ml
Definition: multicore.h:35
Definition: hocdec.h:173
void * this_pointer
Definition: hocdec.h:178
union Object::@47 u
A point process is computed just like regular mechanisms.
Definition: section_fwd.hpp:77
Definition: section.h:231
short _type
Definition: section.h:244
Prop * next
Definition: section.h:243
Definition: section.h:45
float z
Definition: section.h:46
float x
Definition: section.h:46
float y
Definition: section.h:46
Section * sibling
Definition: section.h:56
Pt3d * pt3d
Definition: section.h:67
Section * child
Definition: section.h:54
short npt3d
Definition: section.h:65
Definition: model.h:47
long subtype
Definition: model.h:49
char * name
Definition: model.h:61
Definition: hocdec.h:75
hoc_List * olist
Definition: hocdec.h:155
int count
Definition: hocdec.h:154
hoc_Item * next
Definition: hoclist.h:44
container::Node::storage & node_data()
Access the structure containing the data of all Nodes.
Definition: model_data.hpp:24
std::vector< Mechanism > mechanism
Definition: model_data.hpp:38
std::vector< Thread > thread
Definition: model_data.hpp:37
Underlying storage for all instances of a particular Mechanism.
std::string_view name() const
The name of this mechanism.
Definition: container.cpp:113
short type() const
The type of this mechanism.
Definition: container.cpp:116
Non-template stable handle to a generic value.
std::size_t current_row() const
Return current offset in the underlying storage where this object lives.
Definition: view_utils.hpp:44
std::size_t size() const
Get the size of the container.
void mark_as_unsorted()
Tell the container it is no longer sorted.
frozen_token_type apply_reverse_permutation(Arg &&permutation)
Permute the SoA-format data using an arbitrary range of integers.
Token whose lifetime manages the frozen state of a container.
void diam3d(void)
Definition: treeset.cpp:1306
Node * nrn_alloc_node_
Definition: treeset.cpp:669
Section ** secorder
Definition: solve.cpp:82
void stor_pt3d(Section *sec, double x, double y, double z, double d)
Definition: treeset.cpp:1332
void nrn_pt3dremove(Section *sec, int i0)
Definition: treeset.cpp:1161
Section * nrn_pnt_sec_for_need_
Definition: treeset.cpp:617
short * nrn_is_artificial_
Definition: init.cpp:214
void(* nrn_multisplit_setup_)()
Definition: treeset.cpp:50
int nrn_area_ri_nocount_
Definition: treeset.cpp:751
void update_actual_d_based_on_sp13_mat(NrnThread *nt)
Definition: treeset.cpp:354
void nrn_pt3dinsert(Section *sec, int i0, double x, double y, double z, double d)
Definition: treeset.cpp:1104
void update_actual_rhs_based_on_sp13_rhs(NrnThread *nt)
Definition: treeset.cpp:336
void n3d(void)
Definition: treeset.cpp:1264
double chkarg(int, double low, double high)
Definition: code2.cpp:626
void setSpineArea(void)
Definition: treeset.cpp:1347
int nrn_method_consistent(void)
Definition: treeset.cpp:1816
void spine3d(void)
Definition: treeset.cpp:1317
void x3d(void)
Definition: treeset.cpp:1270
void pt3dadd(void)
Definition: treeset.cpp:1250
void pt3dstyle(void)
Definition: treeset.cpp:997
Prop * prop_alloc_disallow(Prop **pp, short type, Node *nd)
Definition: treeset.cpp:702
Node * nrn_parent_node(Node *nd)
Definition: treeset.cpp:809
void pt3dremove(void)
Definition: treeset.cpp:1175
void pt3dinsert(void)
Definition: treeset.cpp:1123
void nrn_fill_mech_data_caches(neuron::cache::Model &cache, neuron::container::Mechanism::storage &mech_data)
Definition: treeset.cpp:2074
void setup_tree_matrix(neuron::model_sorted_token const &cache_token, NrnThread &nt)
Definition: treeset.cpp:599
static Prop ** current_prop_list
Definition: treeset.cpp:612
double * nrn_mech_wtime_
Definition: treeset.cpp:38
void nrn_define_shape()
Definition: treeset.cpp:1389
static void nrn_translate_shape(Section *sec, float x, float y, float z)
Definition: treeset.cpp:1362
int nrn_shape_changed_
Definition: treeset.cpp:37
#define PI
Definition: treeset.cpp:747
int can_change_morph(Section *sec)
Definition: treeset.cpp:1228
void nrn_area_ri(Section *sec)
Definition: treeset.cpp:752
int structure_change_cnt
Definition: treeset.cpp:66
static void stor_pt3d_vec(Section *sec, IvocVect *xv, IvocVect *yv, IvocVect *zv, IvocVect *dv)
Definition: treeset.cpp:1232
static int disallow_needmemb
Definition: treeset.cpp:614
int diam_change_cnt
Definition: treeset.cpp:67
static void nrn_sort_mech_data(neuron::container::Mechanism::storage::frozen_token_type &sorted_token, neuron::cache::Model &cache, neuron::container::Mechanism::storage &mech_data)
Sort the underlying storage for a particular mechanism.
Definition: treeset.cpp:1945
Prop * need_memb(Symbol *sym)
Definition: treeset.cpp:622
cTemplate ** nrn_pnt_template_
Definition: init.cpp:153
static double diam_from_list(Section *sec, int inode, Prop *p, double rparent)
Definition: treeset.cpp:1474
static void nrn_pt3dmodified(Section *sec, int i0)
Definition: treeset.cpp:1059
void nrn_pt3dchange2(Section *sec, int i, double x, double y, double z, double diam)
Definition: treeset.cpp:1140
void y3d(void)
Definition: treeset.cpp:1279
Prop * prop_alloc(Prop **, int, Node *)
Definition: treeset.cpp:671
void connection_coef(void)
Definition: treeset.cpp:813
int use_sparse13
Definition: treeset.cpp:58
void update_sp13_rhs_based_on_actual_rhs(NrnThread *nt)
Definition: treeset.cpp:345
void pt3dclear(void)
Definition: treeset.cpp:1036
void nrn_shape_update(void)
Definition: treeset.cpp:915
void nrn_pt3dstyle1(Section *sec, double x, double y, double z)
Definition: treeset.cpp:985
int nrn_matrix_cnt_
Definition: treeset.cpp:57
void pt3dchange(void)
Definition: treeset.cpp:1148
void nrn_pt3dchange1(Section *sec, int i, double d)
Definition: treeset.cpp:1133
void nrn_rhs(neuron::model_sorted_token const &cache_token, NrnThread &nt)
Definition: treeset.cpp:375
static void nrn_sort_node_data(neuron::container::Node::storage::frozen_token_type &sorted_token, neuron::cache::Model &cache)
Sort the underlying storage for Nodes.
Definition: treeset.cpp:2122
static void nrn_matrix_node_alloc(void)
Definition: treeset.cpp:1848
int nrn_area_ri_count_
Definition: treeset.cpp:751
int section_count
Definition: solve.cpp:81
int nrn_modeltype(void)
Definition: treeset.cpp:1785
int * nrn_dparam_ptr_start_
Definition: init.cpp:164
void define_shape(void)
Definition: treeset.cpp:1357
void nrn_shape_update_always(void)
Definition: treeset.cpp:898
void prop_update_ion_variables(Prop *prop, Node *node)
Definition: treeset.cpp:690
int recalc_diam_count_
Definition: treeset.cpp:751
void nrn_pt3dclear(Section *sec, int req)
Definition: treeset.cpp:1087
int * nrn_prop_dparam_size_
Definition: init.cpp:163
int nrn_use_daspk_
Definition: treeset.cpp:59
void nrn_diam_change(Section *sec)
Definition: treeset.cpp:1185
void nrn_length_change(Section *sec, double d)
Definition: treeset.cpp:1205
spREAL * spGetElement(char *, int, int)
Definition: spbuild.cpp:151
void update_sp13_mat_based_on_actual_d(NrnThread *nt)
Definition: treeset.cpp:363
void nrn_lhs(neuron::model_sorted_token const &sorted_token, NrnThread &nt)
Definition: treeset.cpp:488
void arc3d(void)
Definition: treeset.cpp:1297
int * nrn_dparam_ptr_end_
Definition: init.cpp:165
void ri(void)
Definition: treeset.cpp:951
void z3d(void)
Definition: treeset.cpp:1288
double nrnmpi_wtime()
Definition: nrnmpi.cpp:175
void nrn_pt3dstyle0(Section *sec)
Definition: treeset.cpp:976
void recalc_diam(void)
Definition: treeset.cpp:923
void single_prop_free(Prop *p)
Definition: treeset.cpp:718
Symlist * hoc_built_in_symlist
Definition: symbol.cpp:28
void nrn_matrix_node_free()
Definition: treeset.cpp:1771
void prop_free(Prop **pp)
Definition: treeset.cpp:710
void v_setup_vectors(void)
Definition: treeset.cpp:1596
void pt3dconst(void)
Definition: treeset.cpp:968
void getSpineArea(void)
Definition: treeset.cpp:1353
static void nrn_pt3dbufchk(Section *sec, int n)
Definition: treeset.cpp:1048
int v_structure_change
Definition: treeset.cpp:65
static int pt3dconst_
Definition: treeset.cpp:966
static double spinearea
Definition: treeset.cpp:1345
void area(void)
Definition: treeset.cpp:935
neuron::model_sorted_token nrn_ensure_model_data_are_sorted()
Ensure neuron::container::* data are sorted.
Definition: treeset.cpp:2182
int Printf(const char *fmt, Args... args)
Definition: logger.hpp:18