NEURON
kssingle.cpp
Go to the documentation of this file.
1 #include <../../nrnconf.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include "nrnoc2iv.h"
5 #include "kschan.h"
6 #include "kssingle.h"
7 
8 // needed for DiscreteEvent aspect
9 #include "netcvode.h"
10 #include "cvodeobj.h"
11 
12 //----------------
13 // KSSingleTrans, KSSingleState is only apparently redundant with KSTrans
14 // and KSState. i.e. they are identical only if KSChan has only a single
15 // gating complex with a power of 1. If there are G gating complexes with
16 // powers pg and numbers of states sg (where g ranges from 0 to G-1) then
17 // the number of distinct states for the gate g is the binomial factor
18 // nsg = (sg + pg - 1, pg). i.e. (sg + pg - 1)! / pg! / (sg - 1)!
19 // and the total number of distinct states is the product of the nsg.
20 // consider m^3 * h, and suppose m is open and M is closed then there
21 // are three ways to go from mmmh to Mmmh and one way to go to mmmH. i.e
22 // MMMH <-> mMMH (3am, bm) MMMH <-> MMMh (ah, bh) MMMh <-> mMMh (3am, bm)
23 // mMMH <-> mmMH (2am, 2bm) mMMH <-> mMMh (ah, bh) mMMh <-> mmMh (2am, 2bm)
24 // mmMH <-> mmmH (am, 3bm) mmMH <-> mmMh (ah, bh) mmMh <-> mmmh (am, 3bm)
25 // there are 8 states and 18 transitions.
26 //
27 // Following a single particle as it moves from state to state is
28 // the simplest case. It suffices to keep track of the state index
29 // and, following ...
30 // use two random numbers. One random number is transformed into
31 // a negative exponential distribution
32 // and defines the time interval to the next transition. The rate is the
33 // sum of all the possible transition rates. The other is uniformly
34 // distibuted between 0 and 1 and is used to determine which transition
35 // branch was taken according to the
36 //
37 // For the case of N particles distributed among the states we use
38 // an integer array in which an element is the integer population of the
39 // corresponding state. Too bad we lose the efficiency of the implicit handling
40 // of 0 population states but I don't see how it can be helped.
41 // Again we use two random numbers. The transition rate of some event, i.e.
42 // some particle moves to another state, is the sum over all states of the
43 // state population times the sum of all the possible transition rates for
44 // that state. And, again, the second random number defines the branch taken.
45 // Note that this is still an exact stochastic simulation in that only
46 // one thing happens at a time.
47 //
48 // It remains to discuss how things are affected by changing rates.
49 // We make the approximation that rates are constant within a dt. In
50 // consequence we only need to compute the rate information once per dt
51 // (at least for the N particle case) no matter how many transitions have
52 // to be followed til the last one is beyond the t0+dt point. The question
53 // is what to do with that last interval which remains unused, ie. time
54 // has not reached the "transition time"
55 // Also we'd like to be efficient when the average interval is much greater
56 // than dt.
57 // The simplest implementation is to just discard the random numbers and
58 // assert that the time is t0 <- t0+dt and compute from that point
59 // at the next solve step. The alternative is to hold the last random
60 // pair and recalculate the interval (assuming the rate has changed)
61 // to obtain (from the last actual transition time) a new transition time.
62 // and see if it is now less than the new t0 + dt. Note that there is
63 // some extra error here because of the assumption of constant rate over the
64 // last entire random interval and not just the last dt.
65 // Note that we do not have to worry about this (or at least worry less)
66 // in voltage clamp context as well as in contexts in which intervals are
67 // seldom > dt. However no analysis has been done and I can't demonstrate
68 // that the statistical error of following a single channel
69 // during an action potential is not substantial. We compromise and
70 // test the voltage. If it has changed then we reset the transition time
71 // to t0 and repick random numbers. If it has not changed significantly,
72 // then we keep the last random interval and use that when t0+dt passes the
73 // up to then unexecuted transition.
74 //----------------
75 // The above broad design envisioned a parallel structure involving
76 // a single kinetic scheme isomorphic to the user level spec which
77 // may have multiple gates raised to powers. The initial implementation
78 // for the KSSingle constructor, however, for simplicity assumed that the
79 // user level spec was only one ks gate complex with power 1. In any case
80 // the question of state read out and conductance calculation arises
81 // and in either case it seems to make most sense that the standard
82 // state array in the prop->param array be used. With the current
83 // limited implementation, KSSingleNodeData.statelist_ is conceptually
84 // identical to the prop->param states. In the envisioned full implementation
85 // the least violent possibility is to keep the user level spec as much
86 // as possible but to expand the prop->param states to correspond
87 // to single channel requirements. Another possibility which does a
88 // lot more violence to the user spec but makes the current implementation
89 // almost complete is to replace the user spec with a single channel
90 // adequate ks scheme. Merely stating it is sufficient to reject it.
91 // A third possiblity is to not use the prop->param states at all since
92 // their user level spec does not correspond to the ks single channel
93 // states. But this makes GUI interface not re-usable (e.g. plotting
94 // a state) and the question of determining states values at the user
95 // level remains. The last possibility is to not allow single channel
96 // mode unless there is only one ks gate complex raised to a single power
97 // and coelesce the redundant structures.
98 // So it seems that the best extension so far is to
99 // keep the user level spec in KSChan but when in single channel mode,
100 // define the prop->param state array to correspond to single channel
101 // states. Since we are going that far we might as well go the whole
102 // way and also introduce a new Nsingle PARAMETER and make the
103 // dparam[2]._pvoid == KSSingleNodeData existent only in single channel
104 // mode. Note that since KSChan.nstate_ <= KSSingle.nstate_, initialization
105 // can fill the KSChan interpreted prop->param states with the fractions,
106 // and then, per normal, the prop->param states interpreted at KSSingle
107 // states get filled with the right integers. We'll also need a naming
108 // convention for KSSingle states since that is how they get accessed at
109 // the hoc level. We need names for the hh open and closed state
110 // if the hh state name is "a". We need names generated for powers.
111 // Products are easy, just concatenate the names. For powers of hh states we
112 // use the hh state name followed by the number that are open. eg an m^3
113 // gate gets the names m0, m1, m2, m3. Thus m^3*h has the names
114 // m0h0 m1h0 m2h0 m3h0 m0h1 m1h1 m2h1 m3h1 (m3h1 is the only open state)
115 // Kind of makes mh[4][2] attractive.
116 // The combinatorics for ks gates raised to a power
117 // is more complex and it is not clear that that is so useful. I think
118 // it makes sense to ignore that and use array indices only for hh gates
119 // raised to a power. Thus a 3 state gate multiplied by an h hh gate would
120 // have the names c0h[2], c1h[2], oh[2]
121 // The nice aspect of this is that the current implementation can be
122 // easily transformed since the transformation of prop->param states can
123 // be deferred to later. i.e there seems to be a nice incremental path
124 // to a full implementation without removing at one step what was added
125 // at the previous step. The first step is to replace the not always used
126 // int* KSSingleNodeData.statelist_ with an always used
127 // double* KSSingleNodeData.statepop_ which points into the prop->param.
128 //----------------
129 
131 
132 double KSSingle::vres_;
133 unsigned int KSSingle::idum_;
134 
136  // implemenation assumes one ks gate complex with power 1
137  int i;
138  sndindex_ = 2;
139  nstate_ = c->nstate_;
141  ntrans_ = 2 * c->ntrans_;
143  rval_ = new double[std::max(ntrans_, nstate_)];
144  uses_ligands_ = false;
145  for (i = 0; i < c->ntrans_; ++i) {
146  {
147  KSSingleTrans& tf = transitions_[2 * i];
148  tf.kst_ = c->trans_ + i;
149  if (tf.kst_->type_ >= 2) {
150  uses_ligands_ = true;
151  }
152  tf.f_ = true;
153  tf.fac_ = 1.;
154  tf.src_ = tf.kst_->src_;
155  tf.target_ = tf.kst_->target_;
156  }
157  {
158  KSSingleTrans& tf = transitions_[2 * i + 1];
159  tf.kst_ = c->trans_ + i;
160  tf.f_ = false;
161  tf.fac_ = 1.;
162  tf.src_ = tf.kst_->target_;
163  tf.target_ = tf.kst_->src_;
164  }
165  }
166  // The transition list for each source state is required for
167  // N=1 single channels.
168  // count first
169  for (i = 0; i < ntrans_; ++i) {
171  }
172  // allocate and reset count
173  for (i = 0; i < nstate_; ++i) {
174  KSSingleState& ss = states_[i];
175  ss.transitions_ = new int[ss.ntrans_];
176  ss.ntrans_ = 0;
177  }
178  // link
179  for (i = 0; i < ntrans_; ++i) {
181  ss.transitions_[ss.ntrans_] = i;
182  ++ss.ntrans_;
183  }
184 }
185 
187  delete[] states_;
188  delete[] transitions_;
189  delete[] rval_;
190 }
191 
193  ntrans_ = 0;
194  transitions_ = NULL;
195 }
197  if (transitions_) {
198  delete[] transitions_;
199  }
200 }
201 
205  if (kst_->type_ < 2) {
206  return rate(NODEV(pnt->node));
207  } else {
208  return rate(pnt->prop->dparam);
209  }
210 }
211 
213  nsingle_ = 1;
214 }
215 
217 
218 void KSSingleNodeData::deliver(double tt, NetCvode* nc, NrnThread* nt) {
220  Cvode* cv = (Cvode*) ((*ppnt_)->nvi_);
221  if (cv) {
222  nc->retreat(tt, cv);
223  cv->set_init_flag();
224  }
225  assert(nt->_t == tt);
226  vlast_ = NODEV((*ppnt_)->node);
227  if (nsingle_ == 1) {
228  kss_->do1trans(this);
229  } else {
230  kss_->doNtrans(this);
231  }
232  qi_ = nc->event(t1_, this, nt);
233 }
234 
235 void KSSingleNodeData::pr(const char* s, double tt, NetCvode* nc) {
236  Printf("%s %s %.15g\n", s, hoc_object_name((*ppnt_)->ob), tt);
237 }
238 
239 void KSSingle::state(Node* nd, Datum* pd, NrnThread* nt) {
240  // integrate from t-dt to t
241  int i;
242  double v = NODEV(nd);
243  auto* snd = pd[sndindex_].get<KSSingleNodeData*>();
244  // if truly single channel, as opposed to N single channels
245  // then follow the one populated state. Otherwise do the
246  // general case
247  if (snd->nsingle_ == 1) {
248  one(v, snd, nt);
249  } else {
250  multi(v, snd, nt);
251  }
252 }
253 
255  // if v changed then need to move the outstanding
256  // single channel event time to a recalculated time
257  int i;
258  double v = NODEV(nd);
259  auto* snd = pd[sndindex_].get<KSSingleNodeData*>();
260  if (uses_ligands_ || !vsame(v, snd->vlast_)) {
261  assert(nt->_t < snd->t1_);
262  snd->vlast_ = v;
263  snd->t0_ = nt->_t;
264  if (snd->nsingle_ == 1) {
265  next1trans(snd);
266  } else {
267  nextNtrans(snd);
268  }
269  net_cvode_instance->move_event(snd->qi_, snd->t1_, nt);
271  }
272 }
273 
275  int i;
276  --n;
277  double x = unifrand(rval_[n]);
278  for (i = 0; i < n; ++i) {
279  if (rval_[i] >= x) {
280  return i;
281  }
282  }
283  return n;
284 }
285 
286 void KSSingle::one(double v, KSSingleNodeData* snd, NrnThread* nt) {
287  if (uses_ligands_ || !vsame(v, snd->vlast_)) {
288  snd->vlast_ = v;
289  snd->t0_ = nt->_t - nt->_dt;
290  next1trans(snd);
291  }
292  while (snd->t1_ <= nt->_t) {
293  snd->vlast_ = v;
294  do1trans(snd);
295  }
296 }
297 
299  snd->t0_ = snd->t1_;
300  // printf("KSSingle::do1trans t1=%g old=%d ", snd->t1_, snd->filledstate_);
301  snd->statepop(snd->filledstate_) = 0.;
303  snd->statepop(snd->filledstate_) = 1.;
304  // printf("new=%d \n", snd->filledstate_);
305  next1trans(snd);
306 }
307 
309  int i;
310  KSSingleState* ss = states_ + snd->filledstate_;
311  double x = 0;
312  for (i = 0; i < ss->ntrans_; ++i) {
314  x += st->rate(*snd->ppnt_);
315  rval_[i] = x;
316  }
317  if (x > 1e-9) {
318  snd->t1_ = exprand() / x + snd->t0_;
319  snd->next_trans_ = ss->transitions_[rvalrand(ss->ntrans_)];
320  } else {
321  snd->t1_ = 1e9 + snd->t0_;
322  snd->next_trans_ = ss->transitions_[0];
323  }
324  // printf("KSSingle::next1trans v=%g t1_=%g %d (%d, %d)\n", snd->vlast_, snd->t1_,
325  // snd->next_trans_, transitions_[snd->next_trans_].src_,
326  // transitions_[snd->next_trans_].target_);
327 }
328 void KSSingle::multi(double v, KSSingleNodeData* snd, NrnThread* nt) {
329  if (uses_ligands_ || !vsame(v, snd->vlast_)) {
330  snd->vlast_ = v;
331  snd->t0_ = nt->_t - nt->_dt;
332  nextNtrans(snd);
333  }
334  while (snd->t1_ <= nt->_t) {
335  snd->vlast_ = v;
336  doNtrans(snd);
337  }
338 }
339 
341  snd->t0_ = snd->t1_;
343  assert(snd->statepop(st->src_) >= 1.);
344  --snd->statepop(st->src_);
345  ++snd->statepop(st->target_);
346  // printf("KSSingle::doNtrans t1=%g %d with %g -> %d with %g\n", snd->t1_,
347  // st->src_, snd->statepop(st->src_), st->target_, snd->statepop(st->target_));
348  nextNtrans(snd);
349 }
350 
352  int i;
353  double x = 0;
354  for (i = 0; i < ntrans_; ++i) {
355  KSSingleTrans* st = transitions_ + i;
356  x += snd->statepop(st->src_) * st->rate(*snd->ppnt_);
357  rval_[i] = x;
358  }
359  if (x > 1e-9) {
360  snd->t1_ = exprand() / x + snd->t0_;
361  snd->next_trans_ = rvalrand(ntrans_);
362  } else {
363  snd->t1_ = 1e9 + snd->t0_;
364  snd->next_trans_ = 0;
365  }
366 }
367 
368 void KSSingle::alloc(Prop* p, int sindex) { // and discard old if not NULL
369  auto* snd = p->dparam[2].get<KSSingleNodeData*>();
370  if (snd) {
371  delete snd;
372  }
373  snd = new KSSingleNodeData();
374  snd->kss_ = this;
375  snd->ppnt_ = &(p->dparam[1].literal_value<Point_process*>());
376  p->dparam[2] = snd;
377  snd->prop_ = p;
378  snd->statepop_offset_ = sindex;
379 }
380 
381 void KSSingle::init(double v,
382  KSSingleNodeData* snd,
383  NrnThread* nt,
384  Memb_list* ml,
385  std::size_t instance,
386  std::size_t offset) {
387  // assuming 1-1 correspondence between KSChan and KSSingle states
388  // place steady state population intervals end to end
389  int i;
390  double x = 0;
391  snd->qi_ = NULL;
392  snd->t0_ = nt->_t;
393  snd->vlast_ = v;
394  for (i = 0; i < nstate_; ++i) {
395  x += ml->data(instance, offset + i);
396  rval_[i] = x;
397  }
398  // initialization of complex kinetic schemes often not accurate to 9 decimal places
399  // assert(Math::equal(rval_[nstate_ - 1], 1., 1e-9));
400  for (i = 0; i < nstate_; ++i) {
401  snd->statepop(i) = 0;
402  }
403  if (snd->nsingle_ == 1) {
404  snd->filledstate_ = rvalrand(nstate_);
405  ++snd->statepop(snd->filledstate_);
406  next1trans(snd);
407  } else {
408  for (i = 0; i < snd->nsingle_; ++i) {
409  ++snd->statepop(rvalrand(nstate_));
410  }
411  nextNtrans(snd);
412  // for (i=0; i < nstate_; ++i) {
413  // printf(" state %d pop %g\n", i, snd->statepop(i));
414  //}
415  }
416  if (cvode_active_) {
417  snd->qi_ = net_cvode_instance->event(snd->t1_, snd, nrn_threads);
418  }
419 }
420 
422  if (auto* snd = pp->prop->dparam[2].get<KSSingleNodeData*>(); snd) {
423  snd->nsingle_ = n;
424  }
425 }
427  if (auto* snd = pp->prop->dparam[2].get<KSSingleNodeData*>(); snd) {
428  return snd->nsingle_;
429  } else {
430  return 1000000000;
431  }
432 }
Definition: cvodeobj.h:97
void set_init_flag()
Definition: cvodeobj.cpp:780
Definition: kschan.h:336
int nsingle(Point_process *)
Definition: kssingle.cpp:426
double unifrand(double range)
Definition: kssingle.h:75
double * rval_
Definition: kssingle.h:83
double exprand()
Definition: kssingle.h:72
int rvalrand(int)
Definition: kssingle.cpp:274
void alloc(Prop *, int sindex)
Definition: kssingle.cpp:368
void nextNtrans(KSSingleNodeData *)
Definition: kssingle.cpp:351
KSSingleTrans * transitions_
Definition: kssingle.h:81
virtual ~KSSingle()
Definition: kssingle.cpp:186
int nstate_
Definition: kssingle.h:80
void one(double, KSSingleNodeData *, NrnThread *)
Definition: kssingle.cpp:286
void next1trans(KSSingleNodeData *)
Definition: kssingle.cpp:308
int sndindex_
Definition: kssingle.h:80
void doNtrans(KSSingleNodeData *)
Definition: kssingle.cpp:340
static unsigned long singleevent_deliver_
Definition: kssingle.h:88
int ntrans_
Definition: kssingle.h:80
void state(Node *, Datum *, NrnThread *)
Definition: kssingle.cpp:239
void do1trans(KSSingleNodeData *)
Definition: kssingle.cpp:298
static unsigned int idum_
Definition: kssingle.h:86
bool vsame(double x, double y)
Definition: kssingle.h:69
void init(double v, KSSingleNodeData *snd, NrnThread *, Memb_list *, std::size_t instance, std::size_t offset)
Definition: kssingle.cpp:381
static unsigned long singleevent_move_
Definition: kssingle.h:89
void multi(double, KSSingleNodeData *, NrnThread *)
Definition: kssingle.cpp:328
static double vres_
Definition: kssingle.h:85
KSSingle(KSChan *)
Definition: kssingle.cpp:135
KSSingleState * states_
Definition: kssingle.h:82
void cv_update(Node *, Datum *, NrnThread *)
Definition: kssingle.cpp:254
bool uses_ligands_
Definition: kssingle.h:84
virtual ~KSSingleNodeData()
Definition: kssingle.cpp:216
double vlast_
Definition: kssingle.h:38
virtual void pr(const char *, double t, NetCvode *)
Definition: kssingle.cpp:235
double & statepop(int i)
Replacement for old double* statepop_ member.
Definition: kssingle.h:32
TQItem * qi_
Definition: kssingle.h:44
Point_process ** ppnt_
Definition: kssingle.h:42
virtual void deliver(double t, NetCvode *, NrnThread *)
Definition: kssingle.cpp:218
KSSingle * kss_
Definition: kssingle.h:43
virtual ~KSSingleState()
Definition: kssingle.cpp:196
int * transitions_
Definition: kssingle.h:115
double fac_
Definition: kssingle.h:107
virtual ~KSSingleTrans()
Definition: kssingle.cpp:203
KSTransition * kst_
Definition: kssingle.h:105
double rate(Point_process *)
Definition: kssingle.cpp:204
int src_
Definition: kschan.h:199
int target_
Definition: kschan.h:200
int type_
Definition: kschan.h:204
void retreat(double, Cvode *)
Definition: netcvode.cpp:3532
void move_event(TQItem *, double, NrnThread *)
Definition: netcvode.cpp:2235
TQItem * event(double tdeliver, DiscreteEvent *, NrnThread *)
Definition: netcvode.cpp:2522
#define v
Definition: md1redef.h:11
#define i
Definition: md1redef.h:19
char * hoc_object_name(Object *ob)
Definition: hoc_oop.cpp:73
static int c
Definition: hoc.cpp:169
#define assert(ex)
Definition: hocassrt.h:24
NetCvode * net_cvode_instance
Definition: cvodestb.cpp:26
NrnThread * nrn_threads
Definition: multicore.cpp:56
bool cvode_active_
Definition: netcvode.cpp:36
int const size_t const size_t n
Definition: nrngsl.h:10
size_t p
s
Definition: multisend.cpp:521
#define NODEV(n)
Definition: section_fwd.hpp:60
#define NULL
Definition: spdefs.h:105
A view into a set of mechanism instances.
Definition: nrnoc_ml.h:34
std::vector< double * > data()
Get a vector of double* representing the model data.
Definition: memblist.cpp:64
Definition: section.h:105
Represent main neuron object computed by single thread.
Definition: multicore.h:58
double _dt
Definition: multicore.h:60
double _t
Definition: multicore.h:59
A point process is computed just like regular mechanisms.
Definition: section_fwd.hpp:77
Definition: section.h:231
Datum * dparam
Definition: section.h:247
Non-template stable handle to a generic value.
T get() const
Explicit conversion to any T.
int Printf(const char *fmt, Args... args)
Definition: logger.hpp:18