root/trunk/src/stgen.py

Revision 463, 33.8 kB (checked in by pierre, 6 days ago)

Fix documentation of the gamma_generator function

Line 
1 """
2 NeuroTools.stgen
3 ================
4
5 A collection of tools for stochastic process generation.
6
7
8 Classes
9 -------
10
11 StGen - Object to generate stochastic processes of various kinds
12         and return them as SpikeTrain or AnalogSignal objects.
13
14
15 Functions
16 ---------
17
18 shotnoise_fromspikes - Convolves the provided spike train with shot decaying exponential.
19
20 gamma_hazard - Compute the hazard function for a gamma process with parameters a,b.
21 """
22
23
24 from NeuroTools import check_dependency
25 from signals import SpikeTrain, AnalogSignal
26 from numpy import array, log
27 import numpy
28
29
30 def gamma_hazard_scipy(x, a, b, dt=1e-4):
31     """
32     Compute the hazard function for a gamma process with parameters a,b
33     where a and b are the parameters of the gamma PDF:
34     y(t) = x^(a-1) \exp(-x/b) / (\Gamma(a)*b^a)
35
36     Inputs:
37         x   - in units of seconds
38         a   - dimensionless
39         b   - in units of seconds
40
41     See also:
42         inh_gamma_generator
43     """
44
45     # This algorithm is presently not used by
46     # inh_gamma_generator as it has numerical problems
47     # Try:
48     # plot(stgen.gamma_hazard(arange(0,1000.0,0.1),10.0,1.0/50.0))
49     # and look for the kinks.
50
51     if check_dependency('scipy'):
52         from scipy.special import gammaincc
53     Hpre = -log(gammaincc(a,(x-dt)/b))
54     Hpost = -log(gammaincc(a,(x+dt)/b))
55     val =  0.5*(Hpost-Hpre)/dt
56
57     if isinstance(val,numpy.ndarray):
58         val[numpy.isnan(val)] = 1.0/b
59         return val
60     elif numpy.isnan(val):
61         return 1.0/b
62     else:
63         return val
64
65
66 def gamma_hazard(x, a, b, dt=1e-4):
67     """
68     Compute the hazard function for a gamma process with parameters a,b
69     where a and b are the parameters of the gamma PDF:
70     y(t) = x^(a-1) \exp(-x/b) / (\Gamma(a)*b^a)
71
72     Inputs:
73         x   - in units of seconds
74         a   - dimensionless
75         b   - in units of seconds
76
77     See also:
78         inh_gamma_generator
79
80     """
81
82     # Used by inh_gamma_generator
83
84     # Ideally, I would like to see an implementation which does not depend on RPy
85     # but the gamma_hazard_scipy above using scipy exhibits numerical problems, as it does not
86     # support directly returning the log.
87
88     if check_dependency('rpy'):
89         from rpy import r
90
91         # scipy.special.gammaincc has numerical problems
92         #Hpre = -log(scipy.special.gammaincc(a,(x-dt)/b))
93         #Hpost = -log(scipy.special.gammaincc(a,(x+dt)/b))
94
95         # reverting to the good old r.pgamma
96         Hpre = -r.pgamma(x-dt,shape=a,scale=b,lower=False,log=True)
97         Hpost = -r.pgamma(x+dt,shape=a,scale=b,lower=False,log=True)
98         val =  0.5*(Hpost-Hpre)/dt
99
100         return val
101
102     elif check_dependency('rpy2'):
103
104         from rpy2.robjects import r
105
106         # scipy.special.gammaincc has numerical problems
107         #Hpre = -log(scipy.special.gammaincc(a,(x-dt)/b))
108         #Hpost = -log(scipy.special.gammaincc(a,(x+dt)/b))
109
110         # reverting to the good old r.pgamma
111         Hpre = -r.pgamma(x-dt,shape=a,scale=b,lower=False,log=True)[0]
112         Hpost = -r.pgamma(x+dt,shape=a,scale=b,lower=False,log=True)[0]
113         val =  0.5*(Hpost-Hpre)/dt
114
115         return val
116     else:
117         raise ImportError("gamma_hazard requires RPy or RPy2 (http://rpy.sourceforge.net/)")
118
119
120    
121
122 class StGen:
123
124     def __init__(self, rng=None, seed=None):
125         """
126         Stochastic Process Generator
127         ============================
128
129         Object to generate stochastic processes of various kinds
130         and return them as SpikeTrain or AnalogSignal objects.
131       
132
133         Inputs:
134             rng - The random number generator state object (optional). Can be None, or
135                   a numpy.random.RandomState object, or an object with the same
136                   interface.
137
138             seed - A seed for the rng (optional).
139
140         If rng is not None, the provided rng will be used to generate random numbers,
141         otherwise StGen will create its own random number generator.
142         If a seed is provided, it is passed to rng.seed(seed)
143
144         Examples:
145             >> x = StGen()
146
147
148
149         StGen Methods:
150
151         Spiking point processes:
152         ------------------------
153  
154         poisson_generator - homogeneous Poisson process
155         inh_poisson_generator - inhomogeneous Poisson process (time varying rate)
156         inh_gamma_generator - inhomogeneous Gamma process (time varying a,b)
157         inh_adaptingmarkov_generator - inhomogeneous adapting markov process (time varying)
158         inh_2Dadaptingmarkov_generator - inhomogeneous adapting and
159                                          refractory markov process (time varying)
160
161         Continuous time processes:
162         --------------------------
163
164         OU_generator - Ohrnstein-Uhlenbeck process
165         
166
167         See also:
168           shotnoise_fromspikes
169
170         """
171
172         if rng==None:
173             self.rng = numpy.random.RandomState()
174         else:
175             self.rng = rng
176
177         if seed != None:
178             self.rng.seed(seed)
179         self.rpy_checked = False
180
181     def seed(self,seed):
182         """ seed the gsl rng with a given seed """
183         self.rng.seed(seed)
184
185
186     def poisson_generator(self, rate, t_start=0.0, t_stop=1000.0, array=False,debug=False):
187         """
188         Returns a SpikeTrain whose spikes are a realization of a Poisson process
189         with the given rate (Hz) and stopping time t_stop (milliseconds).
190
191         Note: t_start is always 0.0, thus all realizations are as if
192         they spiked at t=0.0, though this spike is not included in the SpikeList.
193
194         Inputs:
195             rate    - the rate of the discharge (in Hz)
196             t_start - the beginning of the SpikeTrain (in ms)
197             t_stop  - the end of the SpikeTrain (in ms)
198             array   - if True, a numpy array of sorted spikes is returned,
199                       rather than a SpikeTrain object.
200
201         Examples:
202             >> gen.poisson_generator(50, 0, 1000)
203             >> gen.poisson_generator(20, 5000, 10000, array=True)
204         
205         See also:
206             inh_poisson_generator, inh_gamma_generator, inh_adaptingmarkov_generator
207         """
208
209         #number = int((t_stop-t_start)/1000.0*2.0*rate)
210         
211         # less wasteful than double length method above
212         n = (t_stop-t_start)/1000.0*rate
213         number = numpy.ceil(n+3*numpy.sqrt(n))
214         if number<100:
215             number = min(5+numpy.ceil(2*n),100)
216        
217         if number > 0:
218             isi = self.rng.exponential(1.0/rate, number)*1000.0
219             if number > 1:
220                 spikes = numpy.add.accumulate(isi)
221             else:
222                 spikes = isi
223         else:
224             spikes = numpy.array([])
225
226         spikes+=t_start
227         i = numpy.searchsorted(spikes, t_stop)
228
229         extra_spikes = []
230         if i==len(spikes):
231             # ISI buf overrun
232             
233             t_last = spikes[-1] + self.rng.exponential(1.0/rate, 1)[0]*1000.0
234
235             while (t_last<t_stop):
236                 extra_spikes.append(t_last)
237                 t_last += self.rng.exponential(1.0/rate, 1)[0]*1000.0
238            
239             spikes = numpy.concatenate((spikes,extra_spikes))
240
241             if debug:
242                 print "ISI buf overrun handled. len(spikes)=%d, len(extra_spikes)=%d" % (len(spikes),len(extra_spikes))
243
244
245         else:
246             spikes = numpy.resize(spikes,(i,))
247
248         if not array:
249             spikes = SpikeTrain(spikes, t_start=t_start,t_stop=t_stop)
250
251
252         if debug:
253             return spikes, extra_spikes
254         else:
255             return spikes
256
257     def gamma_generator(self, a, b, t_start=0.0, t_stop=1000.0, array=False,debug=False):
258         """
259         Returns a SpikeTrain whose spikes are a realization of a gamma process
260         with the given shape a, b and stopping time t_stop (milliseconds).
261         (average rate will be a*b)
262
263         Note: t_start is always 0.0, thus all realizations are as if
264         they spiked at t=0.0, though this spike is not included in the SpikeList.
265
266         Inputs:
267             a,b     - the parameters of the gamma process
268             t_start - the beginning of the SpikeTrain (in ms)
269             t_stop  - the end of the SpikeTrain (in ms)
270             array   - if True, a numpy array of sorted spikes is returned,
271                       rather than a SpikeTrain object.
272
273         Examples:
274             >> gen.gamma_generator(10, 1/10., 0, 1000)
275             >> gen.gamma_generator(20, 1/5., 5000, 10000, array=True)
276         
277         See also:
278             inh_poisson_generator, inh_gamma_generator, inh_adaptingmarkov_generator
279         """
280
281         #number = int((t_stop-t_start)/1000.0*2.0*rate)
282         
283         # less wasteful than double length method above
284         n = (t_stop-t_start)/1000.0*(a*b)
285         number = numpy.ceil(n+3*numpy.sqrt(n))
286         if number<100:
287             number = min(5+numpy.ceil(2*n),100)
288        
289         if number > 0:
290             isi = self.rng.gamma(a, b, number)*1000.0
291             if number > 1:
292                 spikes = numpy.add.accumulate(isi)
293             else:
294                 spikes = isi
295         else:
296             spikes = numpy.array([])
297
298         spikes+=t_start
299         i = numpy.searchsorted(spikes, t_stop)
300
301         extra_spikes = []
302         if i==len(spikes):
303             # ISI buf overrun
304             
305             t_last = spikes[-1] + self.rng.gamma(a, b, 1)[0]*1000.0
306
307             while (t_last<t_stop):
308                 extra_spikes.append(t_last)
309                 t_last += self.rng.gamma(a, b, 1)[0]*1000.0
310            
311             spikes = numpy.concatenate((spikes,extra_spikes))
312
313             if debug:
314                 print "ISI buf overrun handled. len(spikes)=%d, len(extra_spikes)=%d" % (len(spikes),len(extra_spikes))
315
316
317         else:
318             spikes = numpy.resize(spikes,(i,))
319
320         if not array:
321             spikes = SpikeTrain(spikes, t_start=t_start,t_stop=t_stop)
322
323
324         if debug:
325             return spikes, extra_spikes
326         else:
327             return spikes
328            
329     def inh_poisson_generator(self, rate, t, t_stop, array=False):
330         """
331         Returns a SpikeTrain whose spikes are a realization of an inhomogeneous
332         poisson process (dynamic rate). The implementation uses the thinning
333         method, as presented in the references.
334
335         Inputs:
336             rate   - an array of the rates (Hz) where rate[i] is active on interval
337                      [t[i],t[i+1]]
338             t      - an array specifying the time bins (in milliseconds) at which to
339                      specify the rate
340             t_stop - length of time to simulate process (in ms)
341             array  - if True, a numpy array of sorted spikes is returned,
342                      rather than a SpikeList object.
343
344         Note:
345             t_start=t[0]
346
347         References:
348
349         Eilif Muller, Lars Buesing, Johannes Schemmel, and Karlheinz Meier
350         Spike-Frequency Adapting Neural Ensembles: Beyond Mean Adaptation and Renewal Theories
351         Neural Comput. 2007 19: 2958-3010.
352         
353         Devroye, L. (1986). Non-uniform random variate generation. New York: Springer-Verlag.
354
355         Examples:
356             >> time = arange(0,1000)
357             >> stgen.inh_poisson_generator(time,sin(time), 1000)
358
359         See also:
360             poisson_generator, inh_gamma_generator, inh_adaptingmarkov_generator
361         """
362
363         if numpy.shape(t)!=numpy.shape(rate):
364             raise ValueError('shape mismatch: t,rate must be of the same shape')
365
366         # get max rate and generate poisson process to be thinned
367         rmax = numpy.max(rate)
368         ps = self.poisson_generator(rmax, t_start=t[0], t_stop=t_stop, array=True)
369
370         # return empty if no spikes
371         if len(ps) == 0:
372             if array:
373                 return numpy.array([])
374             else:
375                 return SpikeTrain(numpy.array([]), t_start=t[0],t_stop=t_stop)
376        
377         # gen uniform rand on 0,1 for each spike
378         rn = numpy.array(self.rng.uniform(0, 1, len(ps)))
379
380         # instantaneous rate for each spike
381         
382         idx=numpy.searchsorted(t,ps)-1
383         spike_rate = rate[idx]
384
385         # thin and return spikes
386         spike_train = ps[rn<spike_rate/rmax]
387
388         if array:
389             return spike_train
390
391         return SpikeTrain(spike_train, t_start=t[0],t_stop=t_stop)
392
393
394
395     def _inh_gamma_generator_python(self, a, b, t, t_stop, array=False):
396         """
397         Returns a SpikeList whose spikes are a realization of an inhomogeneous gamma process
398         (dynamic rate). The implementation uses the thinning method, as presented in the
399         references.
400
401         Inputs:
402             a,b    - arrays of the parameters of the gamma PDF where a[i] and b[i]
403                      will be active on interval [t[i],t[i+1]]
404             t      - an array specifying the time bins (in milliseconds) at which to
405                      specify the rate
406             t_stop - length of time to simulate process (in ms)
407             array  - if True, a numpy array of sorted spikes is returned,
408                      rather than a SpikeList object.
409
410         Note:
411             t_start=t[0]
412             a is a dimensionless quantity > 0, but typically on the order of 2-10.
413             a = 1 results in a poisson process.
414             b is assumed to be in units of 1/Hz (seconds).
415
416         References:
417
418         Eilif Muller, Lars Buesing, Johannes Schemmel, and Karlheinz Meier
419         Spike-Frequency Adapting Neural Ensembles: Beyond Mean Adaptation and Renewal Theories
420         Neural Comput. 2007 19: 2958-3010.
421         
422         Devroye, L. (1986). Non-uniform random variate generation. New York: Springer-Verlag.
423
424         Examples:
425             See source:trunk/examples/stgen/inh_gamma_psth.py
426
427         See also:
428             inh_poisson_generator, gamma_hazard
429         """
430
431         from numpy import shape
432
433         if shape(t)!=shape(a) or shape(a)!=shape(b):
434             raise ValueError('shape mismatch: t,a,b must be of the same shape')
435
436         # get max rate and generate poisson process to be thinned
437         rmax = numpy.max(1.0/b)
438         ps = self.poisson_generator(rmax, t_start=t[0], t_stop=t_stop, array=True)
439
440         # return empty if no spikes
441         if len(ps) == 0:
442             if array:
443                 return numpy.array([])
444             else:
445                 return SpikeTrain(numpy.array([]), t_start=t[0],t_stop=t_stop)
446
447
448         # gen uniform rand on 0,1 for each spike
449         rn = numpy.array(self.rng.uniform(0, 1, len(ps)))
450
451         # instantaneous a,b for each spike
452
453         idx=numpy.searchsorted(t,ps)-1
454         spike_a = a[idx]
455         spike_b = b[idx]
456
457         keep = numpy.zeros(shape(ps),bool)
458
459         # thin spikes
460
461         i = 0
462         t_last = 0.0
463         t_i = 0
464
465         while(i<len(ps)):
466             # find index in "t" time
467             t_i = numpy.searchsorted(t[t_i:],ps[i],'right')-1+t_i
468             if rn[i]<gamma_hazard((ps[i]-t_last)/1000.0,a[t_i],b[t_i])/rmax:
469                 # keep spike
470                 t_last = ps[i]
471                 keep[i] = True
472             i+=1
473
474
475         spike_train = ps[keep]
476
477         if array:
478             return spike_train
479
480         return SpikeTrain(spike_train, t_start=t[0],t_stop=t_stop)
481
482
483     # use slow python implementation for the time being
484     # TODO: provide optimized C/weave implementation if possible
485       
486     def inh_gamma_generator(self, a, b, t, t_stop, array=False):
487         """
488         Returns a SpikeList whose spikes are a realization of an inhomogeneous gamma process
489         (dynamic rate). The implementation uses the thinning method, as presented in the
490         references.
491
492         Inputs:
493             a,b    - arrays of the parameters of the gamma PDF where a[i] and b[i]
494                      will be active on interval [t[i],t[i+1]]
495             t      - an array specifying the time bins (in milliseconds) at which to
496                      specify the rate
497             t_stop - length of time to simulate process (in ms)
498             array  - if True, a numpy array of sorted spikes is returned,
499                      rather than a SpikeList object.
500
501         Note:
502             t_start=t[0]
503             a is a dimensionless quantity > 0, but typically on the order of 2-10.
504             a = 1 results in a poisson process.
505             b is assumed to be in units of 1/Hz (seconds).
506
507         References:
508
509         Eilif Muller, Lars Buesing, Johannes Schemmel, and Karlheinz Meier
510         Spike-Frequency Adapting Neural Ensembles: Beyond Mean Adaptation and Renewal Theories
511         Neural Comput. 2007 19: 2958-3010.
512         
513         Devroye, L. (1986). Non-uniform random variate generation. New York: Springer-Verlag.
514
515         Examples:
516             See source:trunk/examples/stgen/inh_gamma_psth.py
517
518         See also:
519             inh_poisson_generator, gamma_hazard
520         """
521
522         if not self.rpy_checked:
523             self.have_rpy = check_dependency('rpy') or check_dependency('rpy2')
524             self.rpy_checked = True
525         if self.have_rpy:
526             return self._inh_gamma_generator_python(a, b, t, t_stop, array)
527         else:
528             raise Exception("inh_gamma_generator is disabled as dependency RPy|RPy2 was not found.")
529
530
531
532     def _inh_adaptingmarkov_generator_python(self, a, bq, tau, t, t_stop, array=False):
533
534         """
535         Returns a SpikeList whose spikes are an inhomogeneous
536         realization (dynamic rate) of the so-called adapting markov
537         process (see references). The implementation uses the thinning
538         method, as presented in the references.
539
540         This is the 1d implementation, with no relative refractoriness.
541         For the 2d implementation with relative refractoriness,
542         see the inh_2dadaptingmarkov_generator.
543
544         Inputs:
545             a,bq    - arrays of the parameters of the hazard function where a[i] and bq[i]
546                      will be active on interval [t[i],t[i+1]]
547             tau    - the time constant of adaptation (in milliseconds).
548             t      - an array specifying the time bins (in milliseconds) at which to
549                      specify the rate
550             t_stop - length of time to simulate process (in ms)
551             array  - if True, a numpy array of sorted spikes is returned,
552                      rather than a SpikeList object.
553
554         Note:
555             - t_start=t[0]
556
557             - a is in units of Hz.  Typical values are available
558               in Fig. 1 of Muller et al 2007, a~5-80Hz (low to high stimulus)
559
560             - bq here is taken to be the quantity b*q_s in Muller et al 2007, is thus
561               dimensionless, and has typical values bq~3.0-1.0 (low to high stimulus)
562
563             - tau_s has typical values on the order of 100 ms
564
565
566         References:
567
568         Eilif Muller, Lars Buesing, Johannes Schemmel, and Karlheinz Meier
569         Spike-Frequency Adapting Neural Ensembles: Beyond Mean Adaptation and Renewal Theories
570         Neural Comput. 2007 19: 2958-3010.
571         
572         Devroye, L. (1986). Non-uniform random variate generation. New York: Springer-Verlag.
573
574         Examples:
575             See source:trunk/examples/stgen/inh_2Dmarkov_psth.py
576
577         
578         See also:
579             inh_poisson_generator, inh_gamma_generator, inh_2dadaptingmarkov_generator
580
581         """
582
583         from numpy import shape
584
585         if shape(t)!=shape(a) or shape(a)!=shape(bq):
586             raise ValueError('shape mismatch: t,a,b must be of the same shape')
587
588         # get max rate and generate poisson process to be thinned
589         rmax = numpy.max(a)
590         ps = self.poisson_generator(rmax, t_start=t[0], t_stop=t_stop, array=True)
591
592         isi = numpy.zeros_like(ps)
593         isi[1:] = ps[1:]-ps[:-1]
594         isi[0] = ps[0] #-0.0 # assume spike at 0.0
595
596         # return empty if no spikes
597         if len(ps) == 0:
598             return SpikeTrain(numpy.array([]), t_start=t[0],t_stop=t_stop)
599
600
601         # gen uniform rand on 0,1 for each spike
602         rn = numpy.array(self.rng.uniform(0, 1, len(ps)))
603
604         # instantaneous a,bq for each spike
605
606         idx=numpy.searchsorted(t,ps)-1
607         spike_a = a[idx]
608         spike_bq = bq[idx]
609
610         keep = numpy.zeros(shape(ps),bool)
611
612         # thin spikes
613
614         i = 0
615         t_last = 0.0
616         t_i = 0
617         # initial adaptation state is unadapted, i.e. large t_s
618         t_s = 1000*tau
619
620         while(i<len(ps)):
621             # find index in "t" time, without searching whole array each time
622             t_i = numpy.searchsorted(t[t_i:],ps[i],'right')-1+t_i
623
624             # evolve adaptation state
625             t_s+=isi[i]
626
627             if rn[i]<a[t_i]*numpy.exp(-bq[t_i]*numpy.exp(-t_s/tau))/rmax:
628                 # keep spike
629                 keep[i] = True
630                 # remap t_s state
631                 t_s = -tau*numpy.log(numpy.exp(-t_s/tau)+1)
632             i+=1
633
634
635         spike_train = ps[keep]
636
637         if array:
638             return spike_train
639
640         return SpikeTrain(spike_train, t_start=t[0],t_stop=t_stop)
641
642
643     # use slow python implementation for the time being
644     # TODO: provide optimized C/weave implementation if possible
645
646        
647     inh_adaptingmarkov_generator = _inh_adaptingmarkov_generator_python
648
649
650     def _inh_2Dadaptingmarkov_generator_python(self, a, bq, tau_s, tau_r, qrqs, t, t_stop, array=False):
651
652         """
653         Returns a SpikeList whose spikes are an inhomogeneous
654         realization (dynamic rate) of the so-called 2D adapting markov
655         process (see references).  2D implies the process has two
656         states, an adaptation state, and a refractory state, both of
657         which affect its probability to spike.  The implementation
658         uses the thinning method, as presented in the references.
659
660         For the 1d implementation, with no relative refractoriness,
661         see the inh_adaptingmarkov_generator.
662
663         Inputs:
664             a,bq    - arrays of the parameters of the hazard function where a[i] and bq[i]
665                      will be active on interval [t[i],t[i+1]]
666             tau_s    - the time constant of adaptation (in milliseconds).
667             tau_r    - the time constant of refractoriness (in milliseconds).
668             qrqs     - the ratio of refractoriness conductance to adaptation conductance.
669                        typically on the order of 200.
670             t      - an array specifying the time bins (in milliseconds) at which to
671                      specify the rate
672             t_stop - length of time to simulate process (in ms)
673             array  - if True, a numpy array of sorted spikes is returned,
674                      rather than a SpikeList object.
675
676         Note:
677             - t_start=t[0]
678
679             - a is in units of Hz.  Typical values are available
680               in Fig. 1 of Muller et al 2007, a~5-80Hz (low to high stimulus)
681
682             - bq here is taken to be the quantity b*q_s in Muller et al 2007, is thus
683               dimensionless, and has typical values bq~3.0-1.0 (low to high stimulus)
684
685             - qrqs is the quantity q_r/q_s in Muller et al 2007,
686               where a value of qrqs = 3124.0nS/14.48nS = 221.96 was used.
687
688             - tau_s has typical values on the order of 100 ms
689             - tau_r has typical values on the order of 2 ms
690
691
692         References:
693
694         Eilif Muller, Lars Buesing, Johannes Schemmel, and Karlheinz Meier
695         Spike-Frequency Adapting Neural Ensembles: Beyond Mean Adaptation and Renewal Theories
696         Neural Comput. 2007 19: 2958-3010.
697         
698         Devroye, L. (1986). Non-uniform random variate generation. New York: Springer-Verlag.
699
700         Examples:
701             See source:trunk/examples/stgen/inh_2Dmarkov_psth.py
702         
703         See also:
704             inh_poisson_generator, inh_gamma_generator, inh_adaptingmarkov_generator
705
706         """
707
708         from numpy import shape
709
710         if shape(t)!=shape(a) or shape(a)!=shape(bq):
711             raise ValueError('shape mismatch: t,a,b must be of the same shape')
712
713         # get max rate and generate poisson process to be thinned
714         rmax = numpy.max(a)
715         ps = self.poisson_generator(rmax, t_start=t[0], t_stop=t_stop, array=True)
716
717         isi = numpy.zeros_like(ps)
718         isi[1:] = ps[1:]-ps[:-1]
719         isi[0] = ps[0] #-0.0 # assume spike at 0.0
720
721         # return empty if no spikes
722         if len(ps) == 0:
723             return SpikeTrain(numpy.array([]), t_start=t[0],t_stop=t_stop)
724
725
726         # gen uniform rand on 0,1 for each spike
727         rn = numpy.array(self.rng.uniform(0, 1, len(ps)))
728
729         # instantaneous a,bq for each spike
730
731         idx=numpy.searchsorted(t,ps)-1
732         spike_a = a[idx]
733         spike_bq = bq[idx]
734
735         keep = numpy.zeros(shape(ps),bool)
736
737         # thin spikes
738
739         i = 0
740         t_last = 0.0
741         t_i = 0
742         # initial adaptation state is unadapted, i.e. large t_s
743         t_s = 1000*tau_s
744         t_r = 1000*tau_s
745
746         while(i<len(ps)):
747             # find index in "t" time, without searching whole array each time
748             t_i = numpy.searchsorted(t[t_i:],ps[i],'right')-1+t_i
749
750             # evolve adaptation state
751             t_s+=isi[i]
752             t_r+=isi[i]
753
754             if rn[i]<a[t_i]*numpy.exp(-bq[t_i]*(numpy.exp(-t_s/tau_s)+qrqs*numpy.exp(-t_r/tau_r)))/rmax:
755                 # keep spike
756                 keep[i] = True
757                 # remap t_s state
758                 t_s = -tau_s*numpy.log(numpy.exp(-t_s/tau_s)+1)
759                 t_r = -tau_r*numpy.log(numpy.exp(-t_r/tau_r)+1)
760             i+=1
761
762
763         spike_train = ps[keep]
764
765         if array:
766             return spike_train
767
768         return SpikeTrain(spike_train, t_start=t[0],t_stop=t_stop)
769
770
771     # use slow python implementation for the time being
772     # TODO: provide optimized C/weave implementation if possible
773
774        
775     inh_2Dadaptingmarkov_generator = _inh_2Dadaptingmarkov_generator_python
776
777
778
779
780
781
782
783     def _OU_generator_python(self, dt, tau, sigma, y0, t_start=0.0, t_stop=1000.0, array=False,time_it=False):
784         """
785         Generates an Orstein Ulbeck process using the forward euler method. The function returns
786         an AnalogSignal object.
787         
788         Inputs:
789             dt      - the time resolution in milliseconds of th signal
790             tau     - the correlation time in milliseconds
791             sigma   - std dev of the process
792             y0      - initial value of the process, at t_start
793             t_start - start time in milliseconds
794             t_stop  - end time in milliseconds
795             array   - if True, the functions returns the tuple (y,t)
796                       where y and t are the OU signal and the time bins, respectively,
797                       and are both numpy arrays.
798         
799         Examples:
800             >> stgen.OU_generator(0.1, 2, 3, 0, 0, 10000)
801
802         See also:
803             OU_generator_weave1
804         """
805
806         import time
807
808         if time_it:
809             t1 = time.time()
810
811         t     = numpy.arange(t_start,t_stop,dt)
812         N     = len(t)
813         y     = numpy.zeros(N,float)
814         gauss = self.rng.standard_normal(N-1)
815         y[0]  = y0
816         fac   = dt/tau
817         noise = numpy.sqrt(2*fac)*sigma
818        
819
820         # python loop... bad+slow!
821         for i in xrange(1,N):
822             y[i] = y[i-1]+fac*(y0-y[i-1])+noise*gauss[i-1]
823
824         if time_it:
825             print time.time()-1
826        
827         if array:
828             return (y,t)
829
830         result = AnalogSignal(y, dt, t_start, t_stop)
831         return result
832        
833     # use slow python implementation for the time being
834     # TODO: provide optimized C/weave implementation if possible
835
836
837     def _OU_generator_python2(self, dt, tau, sigma, y0, t_start=0.0, t_stop=1000.0, array=False,time_it=False):
838         """
839         Generates an Orstein Ulbeck process using the forward euler method. The function returns
840         an AnalogSignal object.
841         
842         Inputs:
843             dt      - the time resolution in milliseconds of th signal
844             tau     - the correlation time in milliseconds
845             sigma   - std dev of the process
846             y0      - initial value of the process, at t_start
847             t_start - start time in milliseconds
848             t_stop  - end time in milliseconds
849             array   - if True, the functions returns the tuple (y,t)
850                       where y and t are the OU signal and the time bins, respectively,
851                       and are both numpy arrays.
852         
853         Examples:
854             >> stgen.OU_generator(0.1, 2, 3, 0, 0, 10000)
855
856         See also:
857             OU_generator_weave1
858         """
859
860         import time
861
862         if time_it:
863             t1 = time.time()
864
865         t     = numpy.arange(t_start,t_stop,dt)
866         N     = len(t)
867         y     = numpy.zeros(N,float)
868         y[0]  = y0
869         fac   = dt/tau
870         gauss = fac*y0+numpy.sqrt(2*fac)*sigma*self.rng.standard_normal(N-1)
871         mfac = 1-fac
872        
873         # python loop... bad+slow!
874         for i in xrange(1,N):
875             idx = i-1
876             y[i] = y[idx]*mfac+gauss[idx]
877
878         if time_it:
879             print time.time()-t1
880
881         if array:
882             return (y,t)
883
884         result = AnalogSignal(y, dt, t_start, t_stop)
885         return result
886        
887     # use slow python implementation for the time being
888     # TODO: provide optimized C/weave implementation if possible
889
890
891     def OU_generator_weave1(self, dt,tau,sigma,y0,t_start=0.0,t_stop=1000.0,time_it=False):
892         """
893         Generates an Orstein Ulbeck process using the forward euler method. The function returns
894         an AnalogSignal object.
895
896         OU_generator_weave1, as opposed to OU_generator, uses scipy.weave
897         and is thus much faster.
898         
899         Inputs:
900             dt      - the time resolution in milliseconds of th signal
901             tau     - the correlation time in milliseconds
902             sigma   - std dev of the process
903             y0      - initial value of the process, at t_start
904             t_start - start time in milliseconds
905             t_stop  - end time in milliseconds
906             array   - if True, the functions returns the tuple (y,t)
907                       where y and t are the OU signal and the time bins, respectively,
908                       and are both numpy arrays.
909         
910         Examples:
911             >> stgen.OU_generator_weave1(0.1, 2, 3, 0, 0, 10000)
912
913         See also:
914             OU_generator
915         """
916         import scipy.weave
917        
918         import time
919
920         if time_it:
921             t1 = time.time()
922
923
924         t = numpy.arange(t_start,t_stop,dt)
925         N = len(t)
926         y = numpy.zeros(N,float)
927         y[0] = y0
928         fac = dt/tau
929         gauss = fac*y0+numpy.sqrt(2*fac)*sigma*self.rng.standard_normal(N-1)
930        
931        
932         # python loop... bad+slow!
933         #for i in xrange(1,len(t)):
934         #    y[i] = y[i-1]+dt/tau*(y0-y[i-1])+numpy.sqrt(2*dt/tau)*sigma*numpy.random.normal()
935
936         # use weave instead
937
938         code = """
939
940         double f = 1.0-fac;
941
942         for(int i=1;i<Ny[0];i++) {
943           y(i) = y(i-1)*f + gauss(i-1);
944         }
945         """
946
947         scipy.weave.inline(code,['y', 'gauss', 'fac'],
948                      type_converters=scipy.weave.converters.blitz)
949
950
951         if time_it:
952             print 'Elapsed ',time.time()-t1,' seconds.'
953
954         if array:
955             return (y,t)
956
957         result = AnalogSignal(y,dt,t_start,t_stop)
958         return result
959
960
961
962     OU_generator = _OU_generator_python2
963
964     # TODO: optimized inhomogeneous OU generator
965
966
967 # TODO: have a array generator with spatio-temporal correlations
968
969 # TODO fix shotnoise stuff below  ... and write tests
970
971 # Operations on spike trains
972
973
974 def shotnoise_fromspikes(spike_train,q,tau,dt=0.1,t_start=None, t_stop=None,array=False, eps = 1.0e-8):
975     """
976     Convolves the provided spike train with shot decaying exponentials
977     yielding so called shot noise if the spike train is Poisson-like. 
978     Returns an AnalogSignal if array=False, otherwise (shotnoise,t) as numpy arrays.
979
980    Inputs:
981       spike_train - a SpikeTrain object
982       q - the shot jump for each spike
983       tau - the shot decay time constant in milliseconds
984       dt - the resolution of the resulting shotnoise in milliseconds
985       t_start - start time of the resulting AnalogSignal
986                 If unspecified, t_start of spike_train is used
987       t_stop  - stop time of the resulting AnalogSignal
988                 If unspecified, t_stop of spike_train is used
989       array - if True, returns (shotnoise,t) as numpy arrays, otherwise an AnalogSignal.
990       eps - a numerical parameter indicating at what value of
991       the shot kernal the tail is cut.  The default is usually fine.
992
993    Note:
994       Spikes in spike_train before t_start are taken into account in the convolution.
995
996    Examples:
997       >> stg = stgen.StGen()
998       >> st = stg.poisson_generator(10.0,0.0,1000.0)
999       >> g_e = shotnoise_fromspikes(st,2.0,10.0,dt=0.1)
1000
1001
1002    See also:
1003       poisson_generator, inh_gamma_generator, inh_adaptingmarkov_generator, OU_generator ...
1004    """
1005
1006     st = spike_train
1007
1008     if t_start is not None and t_stop is not None:
1009         assert t_stop>t_start
1010
1011     # time of vanishing significance
1012     vs_t = -tau*numpy.log(eps/q)
1013
1014
1015     if t_stop == None:
1016         t_stop = st.t_stop
1017
1018     # need to be clever with start time
1019     # because we want to take spikes into
1020     # account which occured in spikes_times
1021     # before t_start
1022     if t_start == None:
1023         t_start = st.t_start
1024         window_start = st.t_start
1025     else:
1026         window_start = t_start
1027         if t_start>st.t_start:
1028             t_start = st.t_start
1029            
1030
1031     t = numpy.arange(t_start,t_stop,dt)
1032
1033
1034     kern = q*numpy.exp(-numpy.arange(0.0,vs_t,dt)/tau)
1035
1036     idx = numpy.clip(numpy.searchsorted(t,st.spike_times,'right')-1,0,len(t)-1)
1037
1038     a = numpy.zeros(numpy.shape(t),float)
1039
1040     a[idx] = 1.0
1041
1042     y = numpy.convolve(a,kern)[0:len(t)]
1043
1044     if array:
1045        signal_t = numpy.arange(window_start,t_stop,dt)
1046        signal_y = y[-len(t):]
1047        return (signal_y,signal_t)
1048
1049
1050     result = AnalogSignal(y,dt,t_start=0.0,t_stop=t_stop-t_start)
1051     result.time_offset(t_start)
1052     if window_start>t_start:
1053         result = result.time_slice(window_start,t_stop)
1054     return result
1055
1056
1057
1058
1059
1060 def _gen_g_add(spikes,tau,q,t,eps = 1.0e-8):
1061
1062     #spikes = poisson_generator(rate,t[-1])
1063
1064     gd_s = zeros(shape(t),Float)
1065
1066     dt = t[1]-t[0]
1067
1068     # time of vanishing significance
1069     vs_t = -tau*log(eps/q)
1070     kern = q*exp(-arrayrange(0.0,vs_t,dt)/tau)
1071
1072     vs_idx = len(kern)
1073
1074     idx = clip(searchsorted(t,spikes),0,len(t)-1)
1075     idx2 = clip(idx+vs_idx,0,len(gd_s))
1076     idx3 = idx2-idx
1077
1078     for i in xrange(len(idx)):
1079
1080         gd_s[idx[i]:idx2[i]] += kern[0:idx3[i]]
1081
1082     return gd_s
1083
Note: See TracBrowser for help on using the browser.